diff --git a/Herbert/Framework/API.php b/Herbert/Framework/API.php
index f1cee73..aee15b7 100644
--- a/Herbert/Framework/API.php
+++ b/Herbert/Framework/API.php
@@ -1,6 +1,6 @@
methods[$method] = $fn;
}
+ /**
+ * Gets a method.
+ *
+ * @param string $method
+ * @return Callable
+ */
+ public function get($method)
+ {
+ return array_get($this->methods, $method);
+ }
+
/**
* Magic call from the function collection.
*
@@ -46,9 +57,9 @@ public function add($method, $fn)
*/
public function __call($method, $params)
{
- if (!isset($this->methods[$method]))
+ if ( ! isset($this->methods[$method]))
{
- throw new WP_Error('broke', "Method '{$method}' not set!");
+ throw new Exception("Method '{$method}' not set!");
}
return $this->app->call(
diff --git a/Herbert/Framework/Application.php b/Herbert/Framework/Application.php
index 211a51e..9f9ae25 100644
--- a/Herbert/Framework/Application.php
+++ b/Herbert/Framework/Application.php
@@ -3,16 +3,26 @@
use Illuminate\Support\ServiceProvider;
use vierbergenlars\SemVer\version as SemVersion;
use vierbergenlars\SemVer\expression as SemVersionExpression;
+use Illuminate\Database\Capsule\Manager as CapsuleManager;
+use Illuminate\Database\Schema\Blueprint as SchemaBlueprint;
/**
* @see http://getherbert.com
*/
class Application extends \Illuminate\Container\Container implements \Illuminate\Contracts\Foundation\Application {
+ public function getCachedPackagesPath()
+ {
+
+ }
+ public function runningInConsole()
+ {
+
+ }
/**
* The application's version.
*/
- const VERSION = '0.9.2';
+ const VERSION = '0.9.13';
/**
* The application's version.
@@ -96,6 +106,13 @@ class Application extends \Illuminate\Container\Container implements \Illuminate
*/
protected $matched = [];
+ /**
+ * The plugin apis.
+ *
+ * @var array
+ */
+ protected $apis = [];
+
/**
* The plugin configurations.
*
@@ -144,11 +161,11 @@ public function __construct()
/**
* Added to satisfy interface
*
- * @return null
+ * @return string
*/
public function basePath()
{
- return;
+ return content_directory() . '/herbert-cache';
}
/**
@@ -441,6 +458,7 @@ protected function loadPluginAPIs($requires = [])
{
global $$name;
$api = $$name = new API($this);
+ $this->apis[] = [$name, $api];
require "$require";
}
@@ -481,6 +499,24 @@ public function activatePlugin($root)
$config = $this->getPluginConfig($root);
+ foreach (array_get($config, 'tables', []) as $table => $class)
+ {
+ if ( ! class_exists($class))
+ {
+ continue;
+ }
+
+ if (CapsuleManager::schema()->hasTable($table))
+ {
+ continue;
+ }
+
+ CapsuleManager::schema()->create($table, function (SchemaBlueprint $table) use ($class)
+ {
+ $this->call($class . '@activate', ['table' => $table, 'app' => $this]);
+ });
+ }
+
foreach (array_get($config, 'activators', []) as $activator)
{
if ( ! file_exists($activator))
@@ -535,6 +571,85 @@ public function deactivatePlugin($root)
'widget'
]);
}
+
+ foreach (array_get($config, 'tables', []) as $table => $class)
+ {
+ if ( ! class_exists($class))
+ {
+ continue;
+ }
+
+ if ( ! CapsuleManager::schema()->hasTable($table))
+ {
+ continue;
+ }
+
+ CapsuleManager::schema()->table($table, function (SchemaBlueprint $table) use ($class)
+ {
+ $this->call($class . '@deactivate', ['table' => $table, 'app' => $this]);
+ });
+ }
+ }
+
+ /**
+ * Deletes a plugin.
+ *
+ * @see register_uninstall_hook
+ * @param $root
+ */
+ public function deletePlugin($root)
+ {
+ $plugins = array_filter($this->plugins, function (Plugin $plugin) use ($root)
+ {
+ return $plugin->getBasePath() === $root;
+ });
+
+ foreach ($plugins as $plugin)
+ {
+ if ( ! method_exists($plugin, 'delete'))
+ {
+ continue;
+ }
+
+ $plugin->deactivate();
+ }
+
+ $config = $this->getPluginConfig($root);
+
+ foreach (array_get($config, 'deleters', []) as $deleter)
+ {
+ if ( ! file_exists($deleter))
+ {
+ continue;
+ }
+
+ $this->loadWith($deleter, [
+ 'http',
+ 'router',
+ 'enqueue',
+ 'panel',
+ 'shortcode',
+ 'widget'
+ ]);
+ }
+
+ foreach (array_get($config, 'tables', []) as $table => $class)
+ {
+ if ( ! class_exists($class))
+ {
+ continue;
+ }
+
+ if ( ! CapsuleManager::schema()->hasTable($table))
+ {
+ continue;
+ }
+
+ CapsuleManager::schema()->table($table, function (SchemaBlueprint $table) use ($class)
+ {
+ $this->call($class . '@delete', ['table' => $table, 'app' => $this]);
+ });
+ }
}
/**
@@ -1032,6 +1147,13 @@ protected function buildViewGlobals()
$globals = array_merge($globals, $val);
}
+ foreach ($this->apis as $api)
+ {
+ list($name, $instance) = $api;
+
+ $globals[$name] = $instance;
+ }
+
$this->builtViewGlobals = $globals;
}
@@ -1140,4 +1262,24 @@ public static function getInstance()
return static::$instance;
}
+ /**
+ * Get the path to the cached "compiled.php" file.
+ *
+ * @return string
+ */
+ public function getCachedCompilePath()
+ {
+ return $this->basePath() . '/vendor/compiled.php';
+ }
+
+ /**
+ * Get the path to the cached services.json file.
+ *
+ * @return string
+ */
+ public function getCachedServicesPath()
+ {
+ return $this->basePath() . '/vendor/services.json';
+ }
+
}
diff --git a/Herbert/Framework/Enqueue.php b/Herbert/Framework/Enqueue.php
index 2fbaf8c..c0cf67a 100644
--- a/Herbert/Framework/Enqueue.php
+++ b/Herbert/Framework/Enqueue.php
@@ -73,6 +73,10 @@ public function buildInclude($attrs, $footer)
else
{
wp_enqueue_script($attrs['as'], $attrs['src'], [], false, $footer);
+
+ if(isset($attrs['localize'])) {
+ wp_localize_script( $attrs['as'], $attrs['as'], $attrs['localize'] );
+ }
}
}
@@ -183,32 +187,23 @@ public function filterHook($attrs, $filterWith)
public function filterPanel($attrs, $filterWith)
{
$panels = $this->app['panel']->getPanels();
- $http = $this->app['http'];
+ $page = $this->app['http']->get('page', false);
- if ($filterWith[0] === '*') {
- if (!$http->has('page'))
- {
- return false;
- }
-
- foreach ($panels as $panel)
- {
- if ($panel['slug'] === $http->get('page'))
- {
- return true;
- }
- }
+ if (!$page && function_exists('get_current_screen'))
+ {
+ $page = object_get(get_current_screen(), 'id', $page);
}
- else
+
+ foreach ($filterWith as $filter)
{
- foreach ($filterWith as $filter)
+ $filtered = array_filter($panels, function ($panel) use ($page, $filter) {
+ return $page === $panel['slug'] && str_is($filter, $panel['slug']);
+ });
+
+ if (count($filtered) > 0)
{
- if ($panels[$filter]['slug'] === $http->get('page'))
- {
- return true;
- }
+ return true;
}
-
}
return false;
@@ -332,7 +327,7 @@ public function filterSearch($attrs, $filterWith)
*/
public function filterPostType($attrs, $filterWith)
{
- return array_search(get_post_type(), $filterWith) !== null;
+ return array_search(get_post_type(), $filterWith) !== FALSE;
}
}
diff --git a/Herbert/Framework/Exceptions/HttpErrorException.php b/Herbert/Framework/Exceptions/HttpErrorException.php
new file mode 100644
index 0000000..4bc1a69
--- /dev/null
+++ b/Herbert/Framework/Exceptions/HttpErrorException.php
@@ -0,0 +1,57 @@
+status = $status;
+
+ if ( ! is_string($message))
+ {
+ $this->response = $message;
+ }
+ }
+
+ /**
+ * Gets the Http status code.
+ *
+ * @return integer
+ */
+ public function getStatus()
+ {
+ return $this->status;
+ }
+
+ /**
+ * Gets the response.
+ *
+ * @return mixed
+ */
+ public function getResponse()
+ {
+ return $this->response;
+ }
+
+}
diff --git a/Herbert/Framework/Models/Comment.php b/Herbert/Framework/Models/Comment.php
index 3cfa9f3..4e7364f 100644
--- a/Herbert/Framework/Models/Comment.php
+++ b/Herbert/Framework/Models/Comment.php
@@ -21,6 +21,20 @@ class Comment extends Model {
*/
protected $primaryKey = 'comment_ID';
+ /**
+ * The name of the "created at" column.
+ *
+ * @var string
+ */
+ const CREATED_AT = 'comment_date';
+
+ /**
+ * The name of the "updated at" column.
+ *
+ * @var string
+ */
+ const UPDATED_AT = null;
+
/**
* The attributes that are mass assignable.
*
@@ -50,7 +64,7 @@ class Comment extends Model {
*/
public function post()
{
- return $this->belongsTo(__NAMESPACE__ . '/Post', 'comment_post_ID');
+ return $this->belongsTo(__NAMESPACE__ . '\Post', 'comment_post_ID');
}
/**
@@ -63,4 +77,33 @@ public function meta()
return $this->hasMany(__NAMESPACE__ . '\CommentMeta', 'comment_id');
}
+ /**
+ * Set the value of the "created at" attribute.
+ *
+ * @param mixed $value
+ * @return void
+ */
+ public function setCreatedAt($value)
+ {
+ $this->{static::CREATED_AT} = $value;
+
+ if ( ! $value instanceof Carbon)
+ {
+ $value = new Carbon($value);
+ }
+
+ $this->{static::CREATED_AT . '_gmt'} = $value->timezone('GMT');
+ }
+
+ /**
+ * Set the value of the "updated at" attribute.
+ *
+ * @param mixed $value
+ * @return void
+ */
+ public function setUpdatedAt($value)
+ {
+ //
+ }
+
}
diff --git a/Herbert/Framework/Models/CommentMeta.php b/Herbert/Framework/Models/CommentMeta.php
index 3ac0835..3cd4faf 100644
--- a/Herbert/Framework/Models/CommentMeta.php
+++ b/Herbert/Framework/Models/CommentMeta.php
@@ -7,6 +7,13 @@
*/
class CommentMeta extends Model {
+ /**
+ * Disable timestamps.
+ *
+ * @var boolean
+ */
+ public $timestamps = false;
+
/**
* The table associated with the model.
*
diff --git a/Herbert/Framework/Models/Option.php b/Herbert/Framework/Models/Option.php
index 4a1ba65..628c9ae 100644
--- a/Herbert/Framework/Models/Option.php
+++ b/Herbert/Framework/Models/Option.php
@@ -7,6 +7,13 @@
*/
class Option extends Model {
+ /**
+ * Disable timestamps.
+ *
+ * @var boolean
+ */
+ public $timestamps = false;
+
/**
* The table associated with the model.
*
diff --git a/Herbert/Framework/Models/Post.php b/Herbert/Framework/Models/Post.php
index 59dcf3a..9f9857c 100644
--- a/Herbert/Framework/Models/Post.php
+++ b/Herbert/Framework/Models/Post.php
@@ -1,12 +1,16 @@
where('post_type', $type);
}
+ /**
+ * Set the value of the "created at" attribute.
+ *
+ * @param mixed $value
+ * @return void
+ */
+ public function setCreatedAt($value)
+ {
+ $this->{static::CREATED_AT} = $value;
+
+ if ( ! $value instanceof Carbon)
+ {
+ $value = new Carbon($value);
+ }
+
+ $this->{static::CREATED_AT . '_gmt'} = $value->timezone('GMT');
+ }
+
+ /**
+ * Set the value of the "updated at" attribute.
+ *
+ * @param mixed $value
+ * @return void
+ */
+ public function setUpdatedAt($value)
+ {
+ $this->{static::UPDATED_AT} = $value;
+
+ if ( ! $value instanceof Carbon)
+ {
+ $value = new Carbon($value);
+ }
+
+ $this->{static::UPDATED_AT . '_gmt'} = $value->timezone('GMT');
+ }
+
}
diff --git a/Herbert/Framework/Models/PostMeta.php b/Herbert/Framework/Models/PostMeta.php
index d63afa7..a4380c1 100644
--- a/Herbert/Framework/Models/PostMeta.php
+++ b/Herbert/Framework/Models/PostMeta.php
@@ -7,6 +7,13 @@
*/
class PostMeta extends Model {
+ /**
+ * Disable timestamps.
+ *
+ * @var boolean
+ */
+ public $timestamps = false;
+
/**
* The table associated with the model.
*
diff --git a/Herbert/Framework/Models/SoftDeletes/SoftDeletes.php b/Herbert/Framework/Models/SoftDeletes/SoftDeletes.php
new file mode 100644
index 0000000..b9676fd
--- /dev/null
+++ b/Herbert/Framework/Models/SoftDeletes/SoftDeletes.php
@@ -0,0 +1,95 @@
+newQuery()->where($this->getKeyName(), $this->getKey());
+
+ $this->{$this->getDeletedAtColumn()} = 'trash';
+
+ $query->update([$this->getDeletedAtColumn() => 'trash']);
+ }
+
+ /**
+ * Restore a soft-deleted model instance.
+ *
+ * @return bool|null
+ */
+ public function restore()
+ {
+ // If the restoring event does not return false, we will proceed with this
+ // restore operation. Otherwise, we bail out so the developer will stop
+ // the restore totally. We will clear the deleted timestamp and save.
+ if ($this->fireModelEvent('restoring') === false) {
+ return false;
+ }
+
+ $this->{$this->getDeletedAtColumn()} = 'publish';
+
+ // Once we have saved the model, we will fire the "restored" event so this
+ // developer will do anything they need to after a restore operation is
+ // totally finished. Then we will return the result of the save call.
+ $this->exists = true;
+
+ $result = $this->save();
+
+ $this->fireModelEvent('restored', false);
+
+ return $result;
+ }
+
+ /**
+ * Determine if the model instance has been soft-deleted.
+ *
+ * @return bool
+ */
+ public function trashed()
+ {
+ return $this->{$this->getDeletedAtColumn()} === 'trash';
+ }
+
+ /**
+ * Get a new query builder that includes soft deletes.
+ *
+ * @return \Illuminate\Database\Eloquent\Builder|static
+ */
+ public static function withTrashed()
+ {
+ return (new static)->newQueryWithoutScope(new SoftDeletingScope)->withTrashed();
+ }
+
+ /**
+ * Get a new query builder that only includes soft deletes.
+ *
+ * @return \Illuminate\Database\Eloquent\Builder|static
+ */
+ public static function onlyTrashed()
+ {
+ $instance = new static;
+
+ $column = $instance->getQualifiedDeletedAtColumn();
+
+ return $instance->newQueryWithoutScope(new SoftDeletingScope)->onlyTrashed();
+ }
+
+}
diff --git a/Herbert/Framework/Models/SoftDeletes/SoftDeletingScope.php b/Herbert/Framework/Models/SoftDeletes/SoftDeletingScope.php
new file mode 100644
index 0000000..a9a89bc
--- /dev/null
+++ b/Herbert/Framework/Models/SoftDeletes/SoftDeletingScope.php
@@ -0,0 +1,130 @@
+where($model->getQualifiedDeletedAtColumn(), '!=', 'trash');
+
+ $this->extend($builder);
+ }
+
+ /**
+ * Remove the scope from the given Eloquent query builder.
+ *
+ * @param \Illuminate\Database\Eloquent\Builder $builder
+ * @param \Illuminate\Database\Eloquent\Model $model
+ * @return void
+ */
+ public function remove(Builder $builder, Model $model)
+ {
+ $column = $model->getQualifiedDeletedAtColumn();
+
+ $query = $builder->getQuery();
+
+ $query->wheres = collect($query->wheres)->reject(function ($where) use ($column) {
+ return $this->isSoftDeleteConstraint($where, $column);
+ })->values()->all();
+
+ $bindings = $query->getRawBindings();
+
+ foreach ($bindings['where'] as $k => $v)
+ {
+ if ($v !== 'trash')
+ {
+ continue;
+ }
+
+ unset($bindings['where'][$k]);
+
+ break;
+ }
+
+ $query->setBindings($bindings['where']);
+ }
+
+ /**
+ * Extend the query builder with the needed functions.
+ *
+ * @param \Illuminate\Database\Eloquent\Builder $builder
+ * @return void
+ */
+ public function extend(Builder $builder)
+ {
+ foreach ($this->extensions as $extension) {
+ $this->{"add{$extension}"}($builder);
+ }
+
+ $builder->onDelete(function (Builder $builder) {
+ $column = $this->getDeletedAtColumn($builder);
+
+ return $builder->update([
+ $column => 'trash',
+ ]);
+ });
+ }
+
+ /**
+ * Add the restore extension to the builder.
+ *
+ * @param \Illuminate\Database\Eloquent\Builder $builder
+ * @return void
+ */
+ protected function addRestore(Builder $builder)
+ {
+ $builder->macro('restore', function (Builder $builder) {
+ $builder->withTrashed();
+
+ return $builder->update([$builder->getModel()->getDeletedAtColumn() => 'publish']);
+ });
+ }
+
+ /**
+ * Add the only-trashed extension to the builder.
+ *
+ * @param \Illuminate\Database\Eloquent\Builder $builder
+ * @return void
+ */
+ protected function addOnlyTrashed(Builder $builder)
+ {
+ $builder->macro('onlyTrashed', function (Builder $builder) {
+ $model = $builder->getModel();
+
+ $this->remove($builder, $model);
+
+ $builder->getQuery()->where($model->getQualifiedDeletedAtColumn(), 'trash');
+
+ return $builder;
+ });
+ }
+
+ /**
+ * Determine if the given where clause is a soft delete constraint.
+ *
+ * @param array $where
+ * @param string $column
+ * @return bool
+ */
+ protected function isSoftDeleteConstraint(array $where, $column)
+ {
+ return $where === [
+ 'type' => 'Basic',
+ 'column' => $column,
+ 'operator' => '!=',
+ 'value' => 'trash',
+ 'boolean' => 'and'
+ ];
+ }
+
+}
diff --git a/Herbert/Framework/Models/Taxonomy.php b/Herbert/Framework/Models/Taxonomy.php
index fc621d9..109d2ef 100644
--- a/Herbert/Framework/Models/Taxonomy.php
+++ b/Herbert/Framework/Models/Taxonomy.php
@@ -7,6 +7,13 @@
*/
class Taxonomy extends Model {
+ /**
+ * Disable timestamps.
+ *
+ * @var boolean
+ */
+ public $timestamps = false;
+
/**
* The table associated with the model.
*
diff --git a/Herbert/Framework/Models/Term.php b/Herbert/Framework/Models/Term.php
index b3ea6e6..8c69039 100644
--- a/Herbert/Framework/Models/Term.php
+++ b/Herbert/Framework/Models/Term.php
@@ -7,6 +7,13 @@
*/
class Term extends Model {
+ /**
+ * Disable timestamps.
+ *
+ * @var boolean
+ */
+ public $timestamps = false;
+
/**
* The table associated with the model.
*
diff --git a/Herbert/Framework/Notifier.php b/Herbert/Framework/Notifier.php
index c72d98a..7563eb3 100644
--- a/Herbert/Framework/Notifier.php
+++ b/Herbert/Framework/Notifier.php
@@ -8,7 +8,7 @@ class Notifier {
/**
* The notifier instance.
*
- * @var \Herbet\Framework\Notifier
+ * @var \Herbert\Framework\Notifier
*/
protected static $instance;
@@ -24,51 +24,74 @@ class Notifier {
*/
public function __construct()
{
+ if ( ! self::$instance)
+ {
+ self::$instance = $this;
+
+ $this->gatherFlashed();
+ }
+
add_action('admin_notices', [$this, 'sendNotices']);
+ add_action('shutdown', [$this, 'sendNotices']);
}
/**
* Adds a notice.
*
- * @param $message
- * @param string $class
+ * @param string $message
+ * @param string $class
+ * @param boolean $flash
*/
- protected function notify($message, $class = 'updated')
+ protected function notify($message, $class = 'updated', $flash = false)
{
- $this->notices[] = [
+ $notification = [
'message' => $message,
'class' => $class
];
+
+ if ( ! $flash)
+ {
+ $this->notices[] = $notification;
+
+ return;
+ }
+
+ $notices = session('__notifier_flashed', []);
+ $notices[] = $notification;
+ session()->getFlashBag()->set('__notifier_flashed', $notices);
}
/**
* Adds a success notice.
*
- * @param $message
+ * @param string $message
+ * @param boolean $flash
*/
- protected function success($message)
+ protected function success($message, $flash = false)
{
- $this->notify($message, 'updated');
+ $this->notify($message, 'updated', $flash);
}
/**
* Adds a warning notice.
*
- * @param $message
+ * @param string $message
+ * @param boolean $flash
*/
- protected function warning($message)
+ protected function warning($message, $flash = false)
{
- $this->notify($message, 'update-nag');
+ $this->notify($message, 'update-nag', $flash);
}
/**
* Adds an error notice.
*
- * @param $message
+ * @param string $message
+ * @param boolean $flash
*/
- protected function error($message)
+ protected function error($message, $flash = false)
{
- $this->notify($message, 'error');
+ $this->notify($message, 'error', $flash);
}
/**
@@ -82,6 +105,18 @@ public function sendNotices()
{
echo "
";
}
+
+ $this->notices = [];
+ }
+
+ /**
+ * Gathers all the flashed notify messages.
+ *
+ * @return void
+ */
+ protected function gatherFlashed()
+ {
+ $this->notices = session()->getFlashBag()->get('__notifier_flashed', []);
}
/**
@@ -95,7 +130,7 @@ public static function __callStatic($name, $arguments)
{
if ( ! self::$instance)
{
- self::$instance = new self;
+ new self;
}
return call_user_func_array([self::$instance, $name], $arguments);
diff --git a/Herbert/Framework/Panel.php b/Herbert/Framework/Panel.php
index e16aaac..c23d43b 100644
--- a/Herbert/Framework/Panel.php
+++ b/Herbert/Framework/Panel.php
@@ -1,14 +1,27 @@
app = $app;
+ $this->http = $http;
+
+ if ( ! is_admin())
+ {
+ return;
+ }
add_action('admin_menu', [$this, 'boot']);
+
+ $http->setMethod($http->get('_method'), $old = $http->method());
+
+ if ( ! in_array($http->method(), self::$methods))
+ {
+ $http->setMethod($old);
+ }
+
+ if ($http->method() !== 'GET')
+ {
+ add_action('init', [$this, 'bootEarly']);
+ }
}
/**
@@ -74,6 +111,31 @@ public function boot()
}
}
+ /**
+ * Boots early.
+ *
+ * @return void
+ */
+ public function bootEarly()
+ {
+ if (($slug = $this->http->get('page')) === null)
+ {
+ return;
+ }
+
+ if (($panel = $this->getPanel($slug, true)) === null)
+ {
+ return;
+ }
+
+ if ( ! $this->handler($panel, true))
+ {
+ return;
+ }
+
+ die;
+ }
+
/**
* Adds a panel.
*
@@ -125,7 +187,7 @@ public function add($data, $uses = null)
$data['as'] = $this->namespaceAs($data['as']);
}
- if (isset($data['parent']))
+ if ($data['type'] === 'sub-panel' && isset($data['parent']))
{
$data['parent'] = $this->namespaceAs($data['parent']);
}
@@ -144,10 +206,11 @@ protected function addPanel($panel)
add_menu_page(
$panel['title'],
$panel['title'],
- 'manage_options',
+ isset($panel['capability']) && $panel['capability'] ? $panel['capability'] : 'manage_options',
$panel['slug'],
- $this->makeCallable($panel['uses']),
- isset($panel['icon']) ? $this->fetchIcon($panel['icon']) : ''
+ $this->makeCallable($panel),
+ isset($panel['icon']) ? $this->fetchIcon($panel['icon']) : '',
+ isset($panel['order']) ? $panel['order'] : null
);
if (isset($panel['rename']) && !empty($panel['rename']))
@@ -183,9 +246,9 @@ protected function addSubPanel($panel)
$panel['parent'],
$panel['title'],
$panel['title'],
- 'manage_options',
+ isset($panel['capability']) && $panel['capability'] ? $panel['capability'] : 'manage_options',
$panel['slug'],
- isset($panel['rename']) && $panel['rename'] ? null : $this->makeCallable($panel['uses'])
+ isset($panel['rename']) && $panel['rename'] ? null : $this->makeCallable($panel)
);
}
@@ -202,8 +265,8 @@ protected function fetchIcon($icon)
return '';
}
- if (substr($icon, 0, 9) === "dashicons" || substr($icon, 0, 5) === "data:"
- || substr($icon, 0, 2) === "//" || $icon == 'none')
+ if (substr($icon, 0, 9) === 'dashicons' || substr($icon, 0, 5) === 'data:'
+ || substr($icon, 0, 2) === '//' || $icon == 'none')
{
return $icon;
}
@@ -214,13 +277,13 @@ protected function fetchIcon($icon)
/**
* Makes a callable for the panel hook.
*
- * @param $callable
+ * @param $panel
* @return callable
*/
- protected function makeCallable($callable)
+ protected function makeCallable($panel)
{
- return function () use ($callable) {
- $this->call($callable);
+ return function () use ($panel) {
+ return $this->handler($panel);
};
}
@@ -237,8 +300,20 @@ protected function call($callable)
['app' => $this->app]
);
+ if ($response instanceof RedirectResponse)
+ {
+ $response->flash();
+ }
+
if ($response instanceof Response)
{
+ status_header($response->getStatusCode());
+
+ foreach ($response->getHeaders() as $key => $value)
+ {
+ @header($key . ': ' . $value);
+ }
+
echo $response->getBody();
return;
@@ -257,29 +332,67 @@ protected function call($callable)
return;
}
+
+ throw new Exception('Unknown response type!');
}
/**
- * Get the URL to a panel.
+ * Gets a panel.
*
- * @param string $name
- * @return string
+ * @param string $name
+ * @param boolean $slug
+ * @return array
*/
- public function url($name)
+ protected function getPanel($name, $slug = false)
{
+ $slug = $slug ? 'slug' : 'as';
+
foreach ($this->panels as $panel)
{
- if (array_get($panel, 'as') !== $name)
+ if (array_get($panel, $slug) !== $name)
{
continue;
}
- return menu_page_url(array_get($panel, 'slug'), false);
+ return $panel;
}
return null;
}
+ /**
+ * Gets the panels.
+ *
+ * @return array
+ */
+ public function getPanels()
+ {
+ return array_values($this->panels);
+ }
+
+ /**
+ * Get the URL to a panel.
+ *
+ * @param string $name
+ * @return string
+ */
+ public function url($name)
+ {
+ if (($panel = $this->getPanel($name)) === null)
+ {
+ return null;
+ }
+
+ $slug = array_get($panel, 'slug');
+
+ if (array_get($panel, 'type') === 'wp-sub-panel')
+ {
+ return admin_url(add_query_arg('page', $slug, array_get($panel, 'parent')));
+ }
+
+ return admin_url('admin.php?page=' . $slug);
+ }
+
/**
* Sets the current namespace.
*
@@ -317,4 +430,67 @@ protected function namespaceAs($as)
return $this->namespace . '::' . $as;
}
+
+ /**
+ * Return the correct callable based on action
+ *
+ * @param array $panel
+ * @param boolean $strict
+ * @return void
+ */
+ protected function handler($panel, $strict = false)
+ {
+ $callable = $uses = $panel['uses'];
+ $method = strtolower($this->http->method());
+ $action = strtolower($this->http->get('action', 'uses'));
+
+ $callable = array_get($panel, $method, false) ?: $callable;
+
+ if ($callable === $uses || is_array($callable))
+ {
+ $callable = array_get($panel, $action, false) ?: $callable;
+ }
+
+ if ($callable === $uses || is_array($callable))
+ {
+ $callable = array_get($panel, "{$method}.{$action}", false) ?: $callable;
+ }
+
+ if (is_array($callable))
+ {
+ $callable = $uses;
+ }
+
+ if ($strict && $uses === $callable)
+ {
+ return false;
+ }
+
+ try {
+ $this->call($callable);
+ } catch (HttpErrorException $e) {
+ if ($e->getStatus() === 301 || $e->getStatus() === 302)
+ {
+ $this->call(function () use (&$e)
+ {
+ return $e->getResponse();
+ });
+ }
+
+ global $wp_query;
+ $wp_query->set_404();
+
+ status_header($e->getStatus());
+
+ define('HERBERT_HTTP_ERROR_CODE', $e->getStatus());
+ define('HERBERT_HTTP_ERROR_MESSAGE', $e->getMessage());
+
+ Notifier::error('' . $e->getStatus() . ': ' . $e->getMessage());
+
+ do_action('admin_notices');
+ }
+
+ return true;
+ }
+
}
diff --git a/Herbert/Framework/Providers/HerbertServiceProvider.php b/Herbert/Framework/Providers/HerbertServiceProvider.php
index 5b8c476..6bb5826 100644
--- a/Herbert/Framework/Providers/HerbertServiceProvider.php
+++ b/Herbert/Framework/Providers/HerbertServiceProvider.php
@@ -2,6 +2,8 @@
use Illuminate\Database\Capsule\Manager as Capsule;
use Illuminate\Support\ServiceProvider;
+use Illuminate\Cookie\CookieJar;
+use Herbert\Framework\Session;
/**
* @see http://getherbert.com
@@ -83,6 +85,36 @@ public function register()
'widget',
'Herbert\Framework\Widget'
);
+
+ $this->app->instance(
+ 'session',
+ $this->app->make('Herbert\Framework\Session', ['app' => $this->app])
+ );
+
+ $this->app->alias(
+ 'session',
+ 'Herbert\Framework\Session'
+ );
+
+ $this->app->instance(
+ 'notifier',
+ $this->app->make('Herbert\Framework\Notifier', ['app' => $this->app])
+ );
+
+ $this->app->alias(
+ 'notifier',
+ 'Herbert\Framework\Notifier'
+ );
+
+ $this->app->singleton(
+ 'errors',
+ function ()
+ {
+ return session_flashed('__validation_errors', []);
+ }
+ );
+
+ $_GLOBALS['errors'] = $this->app['errors'];
}
/**
@@ -103,7 +135,7 @@ protected function registerEloquent()
'username' => DB_USER,
'password' => DB_PASSWORD,
'charset' => DB_CHARSET,
- 'collation' => 'utf8_unicode_ci',
+ 'collation' => DB_COLLATE ?: $wpdb->collate,
'prefix' => $wpdb->prefix
]);
@@ -111,4 +143,14 @@ protected function registerEloquent()
$capsule->bootEloquent();
}
+ /**
+ * Boots the service provider.
+ *
+ * @return void
+ */
+ public function boot()
+ {
+ $this->app['session']->start();
+ }
+
}
diff --git a/Herbert/Framework/Providers/TwigServiceProvider.php b/Herbert/Framework/Providers/TwigServiceProvider.php
index b1ef8e6..cce2573 100644
--- a/Herbert/Framework/Providers/TwigServiceProvider.php
+++ b/Herbert/Framework/Providers/TwigServiceProvider.php
@@ -20,7 +20,7 @@ public function register()
{
$this->app->singleton('twig.loader', function ()
{
- $loader = new Twig_Loader_Filesystem('/');
+ $loader = new Twig_Loader_Filesystem();
foreach ($this->app->getPlugins() as $plugin)
{
@@ -35,7 +35,7 @@ public function register()
return [
'debug' => $this->app->environment() === 'local',
'charset' => 'utf-8',
- 'cache' => ABSPATH . 'wp-content/twig-cache',
+ 'cache' => content_directory() . '/twig-cache',
'auto_reload' => true,
'strict_variables' => false,
'autoescape' => true,
@@ -49,8 +49,13 @@ public function register()
'dd',
'herbert',
'view',
+ 'content_directory',
+ 'plugin_directory',
'panel_url',
- 'route_url'
+ 'route_url',
+ 'session',
+ 'session_flashed',
+ 'errors'
];
});
@@ -84,6 +89,8 @@ public function constructTwig()
$twig->addGlobal($key, $value);
}
+ $twig->addGlobal('errors', $this->app['errors']);
+
foreach ((array) $this->app['twig.functions'] as $function)
{
$twig->addFunction(new Twig_SimpleFunction($function, $function));
diff --git a/Herbert/Framework/RedirectResponse.php b/Herbert/Framework/RedirectResponse.php
new file mode 100644
index 0000000..8ce9829
--- /dev/null
+++ b/Herbert/Framework/RedirectResponse.php
@@ -0,0 +1,108 @@
+target = $url;
+ $this->updateBody();
+ $this->updateHeaders();
+ }
+
+ /**
+ * Update the body.
+ *
+ * @return void
+ */
+ protected function updateBody()
+ {
+ $this->body = sprintf('
+
+
+
+
+
+ Redirecting to %1$s
+
+
+ Redirecting to %1$s.
+
+
+
+', str_replace('"', '\\"', $this->target));
+ }
+
+ /**
+ * Update the headers.
+ *
+ * @return void
+ */
+ protected function updateHeaders()
+ {
+ array_set($this->headers, 'Location', $this->target);
+ }
+
+ /**
+ * Flashes the :key of value :val to the session.
+ *
+ * @param string $key
+ * @param mixed $val
+ * @return \Herbert\Framework\RedirectResponse
+ */
+ public function with($key, $val = null)
+ {
+ if ( ! is_array($key))
+ {
+ $key = [$key => $val];
+ }
+
+ foreach ($key as $k => $v)
+ {
+ array_set($this->data, $k, $v);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Actually flashes in the session data.
+ *
+ * @return \Herbert\Framework\RedirectResponse
+ */
+ public function flash()
+ {
+ $bag = session()->getFlashBag();
+
+ foreach ($this->data as $key => $val)
+ {
+ $bag->add($key, $val);
+ }
+
+ return $this;
+ }
+
+}
diff --git a/Herbert/Framework/Route.php b/Herbert/Framework/Route.php
index 82b6c23..457fd73 100644
--- a/Herbert/Framework/Route.php
+++ b/Herbert/Framework/Route.php
@@ -1,6 +1,6 @@
app = $app;
$this->parameters = $parameters;
$this->uri = $data['uri'];
- $this->name = isset($data['as']) ? $data['as'] : $this->uri;
+ $this->name = array_get($data, 'as', $this->uri);
$this->uses = $data['uses'];
}
@@ -73,7 +73,7 @@ public function handle()
return new JsonResponse($response);
}
- var_dump($response);
+ throw new Exception('Unknown response type!');
}
/**
@@ -85,7 +85,7 @@ public function handle()
*/
public function parameter($name, $default = null)
{
- if (!isset($this->parameters[$name]))
+ if ( ! isset($this->parameters[$name]))
{
return $default;
}
diff --git a/Herbert/Framework/Router.php b/Herbert/Framework/Router.php
index 8455f35..0af15af 100644
--- a/Herbert/Framework/Router.php
+++ b/Herbert/Framework/Router.php
@@ -2,6 +2,7 @@
use Closure;
use InvalidArgumentException;
+use Herbert\Framework\Exceptions\HttpErrorException;
/**
* @see http://getherbert.com
@@ -93,11 +94,15 @@ public function __construct(Application $app, Http $http)
public function boot()
{
add_rewrite_tag('%herbert_route%', '(.+)');
-
- foreach ($this->routes[$this->http->method()] as $id => $route)
+
+ if(is_array($this->routes[$this->http->method()]))
{
- $this->addRoute($route, $id, $this->http->method());
+ foreach ($this->routes[$this->http->method()] as $id => $route)
+ {
+ $this->addRoute($route, $id, $this->http->method());
+ }
}
+
}
/**
@@ -232,12 +237,41 @@ public function parseRequest($wp)
$data['parameters'][$key] = $wp->query_vars['herbert_param_' . $key];
}
- $this->processRequest(
- $this->buildRoute(
- $route,
- $data['parameters']
- )
- );
+ try {
+ $this->processRequest(
+ $this->buildRoute(
+ $route,
+ $data['parameters']
+ )
+ );
+ } catch (HttpErrorException $e) {
+ if ($e->getStatus() === 301 || $e->getStatus() === 302)
+ {
+ $this->processResponse($e->getResponse());
+
+ die;
+ }
+
+ if ($e->getStatus() === 404)
+ {
+ global $wp_query;
+ $wp_query->set_404();
+ }
+
+ status_header($e->getStatus());
+
+ define('HERBERT_HTTP_ERROR_CODE', $e->getStatus());
+ define('HERBERT_HTTP_ERROR_MESSAGE', $e->getMessage());
+
+ if ($e->getStatus() === 404)
+ {
+ @include get_404_template();
+ }
+ else
+ {
+ echo $e->getMessage();
+ }
+ }
die;
}
@@ -258,11 +292,25 @@ protected function buildRoute($data, $params)
* Processes a request.
*
* @param \Herbert\Framework\Route $route
- * @return mixed
+ * @return void
*/
protected function processRequest(Route $route)
{
- $response = $route->handle();
+ $this->processResponse($route->handle());
+ }
+
+ /**
+ * Processes a response.
+ *
+ * @param \Herbert\Framework\Response $response
+ * @return void
+ */
+ protected function processResponse(Response $response)
+ {
+ if ($response instanceof RedirectResponse)
+ {
+ $response->flash();
+ }
status_header($response->getStatusCode());
diff --git a/Herbert/Framework/Session.php b/Herbert/Framework/Session.php
new file mode 100644
index 0000000..815f499
--- /dev/null
+++ b/Herbert/Framework/Session.php
@@ -0,0 +1,9 @@
+renameArguments($arguments, $attributes);
}
- return $this->app->call(
+ if (is_string($callable) && strpos($callable, '::') !== false)
+ {
+ list($api, $method) = explode('::', $callable);
+
+ global $$api;
+
+ if ($$api === null)
+ {
+ throw new Exception("API '{$api}' not set!");
+ }
+
+ $callable = $$api->get($method);
+
+ if ($callable === null)
+ {
+ throw new Exception("Method '{$method}' not set!");
+ }
+ }
+
+ $response = $this->app->call(
$callable,
array_merge([
'_attributes' => $attributes,
'_content' => $content
], $attributes)
);
+
+ if ($response instanceof RedirectResponse)
+ {
+ $response->flash();
+ }
+
+ if ($response instanceof Response)
+ {
+ status_header($response->getStatusCode());
+
+ foreach ($response->getHeaders() as $key => $value)
+ {
+ @header($key . ': ' . $value);
+ }
+
+ return $response->getBody();
+ }
+
+ if (is_null($response) || is_string($response))
+ {
+ return $response;
+ }
+
+ if (is_array($response) || $response instanceof Jsonable || $response instanceof JsonSerializable)
+ {
+ return (new JsonResponse($response))->getBody();
+ }
+
+ throw new Exception('Unknown response type!');
});
}
diff --git a/Herbert/Framework/Widget.php b/Herbert/Framework/Widget.php
index 5a2d775..c4e9a92 100644
--- a/Herbert/Framework/Widget.php
+++ b/Herbert/Framework/Widget.php
@@ -60,7 +60,7 @@ public function boot()
public function add($widget, Plugin $plugin = null)
{
$this->widgets[] = [
- 'widget' => $widget,
+ 'class' => $widget,
'plugin' => $plugin
];
}
diff --git a/bootstrap/autoload.php b/bootstrap/autoload.php
index adc77e3..ae2859d 100644
--- a/bootstrap/autoload.php
+++ b/bootstrap/autoload.php
@@ -28,7 +28,8 @@
/**
* Load all herbert.php files in plugin roots.
*/
-$iterator = new DirectoryIterator(ABSPATH . 'wp-content/plugins');
+$iterator = new DirectoryIterator(plugin_directory());
+
foreach ($iterator as $directory)
{
@@ -46,7 +47,10 @@
$config = $herbert->getPluginConfig($root);
- register_activation_hook($root . '/plugin.php', function () use ($herbert, $config, $root)
+ $plugin = substr($root . '/plugin.php', strlen(plugin_directory()));
+ $plugin = ltrim($plugin, '/');
+
+ register_activation_hook($plugin, function () use ($herbert, $config, $root)
{
if ( ! $herbert->pluginMatches($config))
{
@@ -58,12 +62,16 @@
$herbert->activatePlugin($root);
});
- register_deactivation_hook($root . '/plugin.php', function () use ($herbert, $root)
+ register_deactivation_hook($plugin, function () use ($herbert, $root)
{
$herbert->deactivatePlugin($root);
});
- if ( ! is_plugin_active(substr($root, strlen(ABSPATH . 'wp-content/plugins/')) . '/plugin.php'))
+ // Ugly hack to make the install hook work correctly
+ // as WP doesn't allow closures to be passed here
+ register_uninstall_hook($plugin, create_function('', 'herbert()->deletePlugin(\'' . $root . '\');'));
+
+ if ( ! is_plugin_active($plugin))
{
continue;
}
@@ -76,6 +84,9 @@
}
$herbert->pluginMatched($root);
+
+ @require_once $root.'/plugin.php';
+
$herbert->loadPlugin($config);
}
diff --git a/bootstrap/helpers.php b/bootstrap/helpers.php
index 9aa15f8..2ea9328 100644
--- a/bootstrap/helpers.php
+++ b/bootstrap/helpers.php
@@ -15,6 +15,32 @@ function dd()
}
}
+if ( ! function_exists('content_directory'))
+{
+ /**
+ * Gets the content directory.
+ *
+ * @return string
+ */
+ function content_directory()
+ {
+ return WP_CONTENT_DIR;
+ }
+}
+
+if ( ! function_exists('plugin_directory'))
+{
+ /**
+ * Gets the plugin directory.
+ *
+ * @return string
+ */
+ function plugin_directory()
+ {
+ return WP_PLUGIN_DIR;
+ }
+}
+
if ( ! function_exists('response'))
{
/**
@@ -47,6 +73,22 @@ function json_response($jsonable, $status = 200, $headers = null)
}
}
+if ( ! function_exists('redirect_response'))
+{
+ /**
+ * Generates a redirect response.
+ *
+ * @param string $url
+ * @param integer $status
+ * @param array $headers
+ * @return \Herbert\Framework\Response
+ */
+ function redirect_response($url, $status = 302, $headers = null)
+ {
+ return new Herbert\Framework\RedirectResponse($url, $status, $headers);
+ }
+}
+
if ( ! function_exists('herbert'))
{
/**
@@ -68,6 +110,68 @@ function herbert($binding = null)
}
}
+if ( ! function_exists('errors'))
+{
+ /**
+ * Get the errors.
+ *
+ * @param string key
+ * @return array
+ */
+ function errors($key = null)
+ {
+ $errors = herbert('errors');
+ $errors = isset($errors[0]) ? $errors[0] : $errors;
+
+ if (!$key)
+ {
+ return $errors;
+ }
+
+ return array_get($errors, $key);
+ }
+}
+
+if ( ! function_exists('session'))
+{
+ /**
+ * Gets the session or a key from the session.
+ *
+ * @param string $key
+ * @param mixed $default
+ * @return \Illuminate\Session\Store|mixed
+ */
+ function session($key = null, $default = null)
+ {
+ if ($key === null)
+ {
+ return herbert('session');
+ }
+
+ return herbert('session')->get($key, $default);
+ }
+}
+
+if ( ! function_exists('session_flashed'))
+{
+ /**
+ * Gets the session flashbag or a key from the session flashbag.
+ *
+ * @param string $key
+ * @param mixed $default
+ * @return \Illuminate\Session\Store|mixed
+ */
+ function session_flashed($key = null, $default = [])
+ {
+ if ($key === null)
+ {
+ return herbert('session')->getFlashBag();
+ }
+
+ return herbert('session')->getFlashBag()->get($key, $default);
+ }
+}
+
if ( ! function_exists('view'))
{
/**
@@ -79,7 +183,7 @@ function herbert($binding = null)
*/
function view($name, $context = [])
{
- return herbert('Twig_Environment')->render($name, $context);
+ return response(herbert('Twig_Environment')->render($name, $context));
}
}
@@ -89,11 +193,12 @@ function view($name, $context = [])
* Gets the url to a panel.
*
* @param string $name
+ * @param array $query
* @return string
*/
- function panel_url($name)
+ function panel_url($name, $query = [])
{
- return herbert('panel')->url($name);
+ return add_query_arg($query, herbert('panel')->url($name));
}
}
@@ -104,10 +209,11 @@ function panel_url($name)
*
* @param string $name
* @param array $args
+ * @param array $query
* @return string
*/
- function route_url($name, $args = [])
+ function route_url($name, $args = [], $query = [])
{
- return herbert('router')->url($name, $args);
+ return add_query_arg($query, herbert('router')->url($name, $args));
}
}
diff --git a/composer.json b/composer.json
index ab2e78a..274ddd8 100644
--- a/composer.json
+++ b/composer.json
@@ -1,17 +1,17 @@
{
- "name": "getherbert/herbert",
+ "name": "lexisvar/framework",
"description": "Herbert",
"license": "MIT",
"require": {
"twig/twig": "~1.16",
- "illuminate/database": "~5.1",
+ "illuminate/database": "~5.0",
"vierbergenlars/php-semver": "~3.0",
"symfony/var-dumper": "~3.0",
- "illuminate/http": "~5.1"
+ "illuminate/http": "~5.0"
},
"autoload": {
"psr-4": {
- "Herbert\\Framework\\": "Herbert/Framework/"
+ "Herbert\\Framework\\": "Lexisvar/Framework/"
}
},
"config": {