Query class, fix routing and usability tweaks
Diff
src/Facades/Query.php | 23 +++++++-
src/Models/Meta.php | 3 +-
src/Models/Post.php | 100 ++++++++++++++++++++++++++++--
src/Models/User.php | 18 ++++-
src/Models/UserMeta.php | 7 +-
src/Providers/WordpressServiceProvider.php | 5 ++-
src/Proxy/Query.php | 65 ++++++++++++++++++++-
src/Routing/ArchiveRoute.php | 12 +----
src/Routing/AuthorRoute.php | 81 ++++++++++++++++++++++++-
src/Routing/PageRoute.php | 12 ++--
src/Routing/Routing.php | 39 ++++++++++--
src/Routing/RoutingServiceProvider.php | 1 +-
src/Routing/SingularRoute.php | 12 +----
src/Routing/TemplateRoute.php | 23 ++++---
src/Support/Wordpress.php | 51 +++++++--------
15 files changed, 378 insertions(+), 74 deletions(-)
@@ -0,0 +1,23 @@
<?php
namespace Koselig\Facades;
use Illuminate\Support\Facades\Facade;
/**
* Facade for {@link Koselig\Proxy\Query} proxy. Provides access to the main query.
*
* @see \Koselig\Proxy\Query
* @author Jordan Doyle <jordan@doyle.wf>
*/
class Query extends Facade
{
/**
* Get the registered name of the component.
*
* @return string
*/
protected static function getFacadeAccessor()
{
return 'query';
}
}
@@ -14,7 +14,8 @@ use Koselig\Support\Wordpress;
*/
class Meta extends Model
{
public $table = DB_PREFIX . 'postmeta';
protected $table = DB_PREFIX . 'postmeta';
public $timestamps = false;
/**
* Cache for all meta values.
@@ -8,6 +8,7 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Koselig\Exceptions\UnsatisfiedDependencyException;
use Koselig\Support\Action;
use WP_Post;
/**
* Table containing all the items within the CMS.
@@ -16,13 +17,11 @@ use Koselig\Support\Action;
*/
class Post extends Model
{
public $table = DB_PREFIX . 'posts';
public $primaryKey = 'ID';
protected $table = DB_PREFIX . 'posts';
protected $primaryKey = 'ID';
protected $dates = ['post_date', 'post_date_gmt', 'post_modified', 'post_modified_gmt'];
public $timestamps = false;
public $dates = ['post_date', 'post_date_gmt', 'post_modified', 'post_modified_gmt'];
/**
* Get all the posts within a certain post type.
*
@@ -138,6 +137,75 @@ class Post extends Model
}
/**
* Get the categories of this post.
*
* @see get_the_category
* @return array
*/
public function category()
{
return get_the_category($this->ID);
}
/**
* Get the permalink for this post.
*
* @see get_permalink
* @return false|string
*/
public function link()
{
return get_permalink($this->toWordpressPost());
}
/**
* Get the tags of this post.
*
* @see get_the_tags
* @return array|false|\WP_Error
*/
public function tags()
{
return get_the_tags($this->ID);
}
/**
* Get the thumbnail of this post
*
* @see get_the_post_thumbnail
* @param string $size
* @param string $attr
* @return string
*/
public function thumbnail($size = 'post-thumbnail', $attr = '')
{
return get_the_post_thumbnail($this->toWordpressPost(), $size, $attr);
}
/**
* Get the excerpt of this post.
*
* @return string
*/
public function excerpt()
{
dd($this);
return Action::filter('get_the_excerpt', $this->post_excerpt);
}
/**
* Get the all the terms of this post.
*
* @see get_the_terms
* @param $taxonomy
* @return array|false|\WP_Error
*/
public function terms($taxonomy)
{
return get_the_terms($this->toWordpressPost(), $taxonomy);
}
/**
* Get the author that this post belongs to.
*
* @return BelongsTo
@@ -146,4 +214,26 @@ class Post extends Model
{
return $this->belongsTo(User::class, 'post_author');
}
/**
* Get the classes that should be applied to this post.
*
* @see get_post_class
* @return string
*/
public function classes()
{
return implode(' ', get_post_class('', $this->toWordpressPost()));
}
/**
* Get the {@link WP_Post} instance for this Post.
*
* @deprecated
* @return WP_Post
*/
public function toWordpressPost()
{
return new WP_Post((object) $this->toArray());
}
}
@@ -15,8 +15,10 @@ class User extends Model implements AuthenticatableContract
{
use Authenticatable;
public $table = DB_PREFIX . 'users';
public $primaryKey = 'ID';
protected $table = DB_PREFIX . 'users';
protected $primaryKey = 'ID';
protected $dates = ['user_registered'];
public $timestamps = false;
/**
* Get all the posts that belong to this user.
@@ -25,7 +27,7 @@ class User extends Model implements AuthenticatableContract
*/
public function posts()
{
return $this->hasMany(self::class, 'post_author');
return $this->hasMany(Post::class, 'post_author');
}
/**
@@ -37,4 +39,14 @@ class User extends Model implements AuthenticatableContract
{
return $this->user_pass;
}
/**
* Get a link to this user's author page.
*
* @return string
*/
public function link()
{
return get_author_posts_url($this->ID, $this->display_name);
}
}
@@ -12,14 +12,15 @@ use Koselig\Support\Wordpress;
*/
class UserMeta extends Model
{
public $table = DB_PREFIX . 'usermeta';
protected $table = DB_PREFIX . 'usermeta';
public $timestamps = false;
/**
* Cache for all meta values.
*
* @var array
*/
public static $cache = [];
private static $cache = [];
/**
* Get metadata for a user.
@@ -37,7 +38,7 @@ class UserMeta extends Model
}
if ($user === null) {
$user = Wordpress::currentUser()->ID;
$user = auth()->id();
}
if (!isset(self::$cache[$user])) {
@@ -4,6 +4,7 @@ namespace Koselig\Providers;
use Illuminate\Contracts\Routing\UrlGenerator;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\ServiceProvider;
use Koselig\Proxy\Query;
use Koselig\Support\Action;
use Koselig\Support\Wordpress;
@@ -37,6 +38,10 @@ class WordpressServiceProvider extends ServiceProvider
// Set up the WordPress query.
wp();
$this->app->singleton('query', function () {
return Query::instance($GLOBALS['wp_the_query']);
});
}
/**
@@ -0,0 +1,65 @@
<?php
namespace Koselig\Proxy;
use Illuminate\Support\Str;
use WP_Query;
/**
* Proxies the {@link \WP_Query} class from Wordpress for a more elegant syntax.
*
* @author Jordan Doyle <jordan@doyle.wf>
*/
class Query
{
/**
* Current query.
*
* @var WP_Query
*/
private $query;
public static function instance(WP_Query $query)
{
$instance = new static;
$instance->query = $query;
return $instance;
}
/**
* Get a property from {@link WP_Query}
*
* @param $name
* @return mixed
*/
public function __get($name)
{
return $this->query->{Str::snake($name)};
}
/**
* Pass a call to this function to {@link WP_Query}
*
* @param $name
* @param $arguments
* @return mixed
*/
public function __call($name, $arguments)
{
$name = Str::snake($name);
$name = str_replace('has', 'have', $name);
if (!method_exists($this->query, $name)) {
// try and find the method that was attempted to be called. Makes for a lot nicer code when reading over
// it.
if (method_exists($this->query, 'the_' . $name)) {
$name = 'the_' . $name;
} elseif (method_exists($this->query, 'is_' . $name)) {
$name = 'is_' . $name;
} elseif (method_exists($this->query, 'get_' . $name)) {
$name = 'get_' . $name;
}
}
return $this->query->{$name}(...$arguments);
}
}
@@ -33,17 +33,7 @@ class ArchiveRoute extends Route
parent::__construct($methods, $postTypes, $action);
$this->postTypes = $this->uri;
$this->uri = '';
}
/**
* Format a nice string for php artisan route:list.
*
* @return string
*/
public function uri()
{
return 'archive/' . (implode('/', $this->postTypes) ?: 'all');
$this->uri = 'archive/' . (implode('/', $this->postTypes) ?: 'all');
}
/**
@@ -0,0 +1,81 @@
<?php
namespace Koselig\Routing;
use Illuminate\Http\Request;
use Illuminate\Routing\Route;
use Koselig\Facades\Query;
use Koselig\Models\User;
use Koselig\Support\Wordpress;
use ReflectionFunction;
/**
* Author page route, used when Wordpress reports
* this page is an author page.
*
* @author Jordan Doyle <jordan@doyle.wf>
*/
class AuthorRoute extends Route
{
/**
* Users for this author route to hook onto.
*
* @var array
*/
private $users;
/**
* Create a new Route instance.
*
* @param array|string $methods
* @param array $users
* @param \Closure|array $action
* @return void
*/
public function __construct($methods, $users, $action)
{
parent::__construct($methods, $users, $action);
$this->users = $this->uri;
$this->uri = 'author/' . (implode('/', $this->users) ?: 'all');
}
/**
* Run the route action and return the response.
*
* @return mixed
*/
protected function runCallable()
{
// bind the current post to the parameters of the function
$function = new ReflectionFunction($this->action['uses']);
$params = $function->getParameters();
foreach ($params as $param) {
if ($param->getClass()
&& ($param->getClass()->isSubclassOf(User::class) || $param->getClass()->getName() === User::class)) {
$builder = $param->getClass()->getMethod('query')->invoke(null);
$post = $builder->find(Query::queriedObject()->ID);
$this->setParameter($param->getName(), $post);
}
}
return parent::runCallable();
}
/**
* Determine if the route matches given request.
*
* @param \Illuminate\Http\Request $request
* @param bool $includingMethod
* @return bool
*/
public function matches(Request $request, $includingMethod = true)
{
if (!empty($this->getAction()['domain']) && !Wordpress::multisite($this->getAction()['domain'])) {
return false;
}
return Wordpress::author($this->users);
}
}
@@ -16,13 +16,17 @@ use ReflectionFunction;
class PageRoute extends Route
{
/**
* Format a nice string for php artisan route:list.
* Create a new Route instance.
*
* @return string
* @param array|string $methods
* @param array $users
* @param \Closure|array $action
* @return void
*/
public function uri()
public function __construct($methods, $users, $action)
{
return 'page/' . parent::uri();
parent::__construct($methods, $users, $action);
$this->uri = 'page/' . $this->uri();
}
/**
@@ -10,7 +10,7 @@ class Routing
* Register a new template route with the router.
*
* @param string $slug slug to match
* @param \Closure|array|string|null $action
* @param callable|array|string|null $action
* @return \Illuminate\Routing\Route
*/
public function template($slug, $action)
@@ -30,7 +30,7 @@ class Routing
* Register a new page route with the router.
*
* @param string $slug slug to match
* @param \Closure|array|string|null $action
* @param callable|array|string|null $action
* @return \Illuminate\Routing\Route
*/
public function page($slug, $action)
@@ -50,8 +50,8 @@ class Routing
* Register a new archive route with the router. Optionally supply
* the post types you'd like to supply with this route.
*
* @param \Closure|string|array $postTypes
* @param \Closure|array|string|null $action
* @param callable|string|array $postTypes
* @param callable|array|string|null $action
* @return \Illuminate\Routing\Route
*/
public function archive($postTypes = [], $action = [])
@@ -102,6 +102,37 @@ class Routing
}
/**
* Register a author route with the router. This allows the user to
* create pages for an author or authors. Optionally supply the authors
* you'd like to supply using this route.
*
* @param callable|array|int $users authors to handle by this route
* @param callable|array|string|null $action
* @return mixed
*/
public function author($users, $action = [])
{
if (empty($action)) {
$action = $users;
$users = [];
}
if (!is_array($users)) {
$users = [$users];
}
$action = $this->formatAction($action);
$route = (new AuthorRoute($action['method'], $users, $action))
->setRouter(app('router'))
->setContainer(app(Container::class));
$route = $this->applyStack($route);
return Route::getRoutes()->add($route);
}
/**
* Format <pre>$action</pre> in a nice way to pass to the {@link \Illuminate\Routing\RouteCollection}.
*
* @param $action
@@ -28,5 +28,6 @@ class RoutingServiceProvider extends ServiceProvider
Router::macro('page', [$routing, 'page']);
Router::macro('archive', [$routing, 'archive']);
Router::macro('singular', [$routing, 'singular']);
Router::macro('author', [$routing, 'author']);
}
}
@@ -35,7 +35,7 @@ class SingularRoute extends Route
parent::__construct($methods, $types, $action);
$this->types = $this->uri;
$this->uri = '';
$this->uri = 'singular/' . implode('/', $this->types);
}
/**
@@ -63,16 +63,6 @@ class SingularRoute extends Route
}
/**
* Format a nice string for php artisan route:list.
*
* @return string
*/
public function uri()
{
return 'singular/' . implode('/', $this->types);
}
/**
* Determine if the route matches given request.
*
* @param \Illuminate\Http\Request $request
@@ -1,8 +1,8 @@
<?php
namespace Koselig\Routing;
use Illuminate\Http\Request;
use Illuminate\Routing\Route;
use Koselig\Http\Request;
use Koselig\Models\Post;
use Koselig\Support\Wordpress;
use ReflectionFunction;
@@ -16,13 +16,18 @@ use ReflectionFunction;
class TemplateRoute extends Route
{
/**
* Format a nice string for php artisan route:list.
* Create a new Route instance.
*
* @return string
* @param array|string $methods
* @param array $types
* @param \Closure|array $action
* @return void
*/
public function uri()
public function __construct($methods, $types, $action)
{
return 'template/' . parent::uri();
parent::__construct($methods, $types, $action);
$this->uri = 'template/' . parent::uri();
}
/**
@@ -52,19 +57,21 @@ class TemplateRoute extends Route
/**
* Determine if the route matches given request.
*
* @param \Illuminate\Http\Request $request
* @param Request $request
* @param bool $includingMethod
* @return bool
*/
public function matches(Request $request, $includingMethod = true)
{
$slug = Wordpress::templateSlug();
$post = $request->post();
if (!$slug) {
if (!$post) {
// the page we are on either isn't in the CMS or doesn't have a template.
return false;
}
$slug = $post->getMeta('_wp_page_template');
if (!empty($this->getAction()['domain']) && !Wordpress::multisite($this->getAction()['domain'])) {
return false;
}
@@ -1,6 +1,9 @@
<?php
namespace Koselig\Support;
use Koselig\Facades\Query;
use Koselig\Models\User;
/**
* Provides various base Wordpress helper functionality in a nice
* OO way.
@@ -10,17 +13,6 @@ namespace Koselig\Support;
class Wordpress
{
/**
* Get the current Wordpress query.
*
* @SuppressWarnings(PHPMD.CamelCaseVariableName)
* @return \WP_Query
*/
public static function query()
{
return $GLOBALS['wp_query'];
}
/**
* Get the current page id.
*
* @return int
@@ -31,17 +23,6 @@ class Wordpress
}
/**
* Get the slug of the template of a page.
*
* @param string $page
* @return false|string
*/
public static function templateSlug($page = null)
{
return get_page_template_slug($page);
}
/**
* Check if the current page is a singular item (eg. a news post).
*
* @param array|string $types
@@ -49,7 +30,7 @@ class Wordpress
*/
public static function singular($types = '')
{
return is_singular($types);
return Query::singular($types);
}
/**
@@ -60,7 +41,28 @@ class Wordpress
*/
public static function archive($types = null)
{
return $types === null || empty($types) ? is_archive() : is_post_type_archive($types);
return $types === null || empty($types) ? Query::archive() : Query::postTypeArchive($types);
}
/**
* Check if the current page is an author page.
*
* @param int|array|User $users
* @return bool
*/
public static function author($users = [])
{
if (!is_array($users)) {
$users = [$users];
}
foreach ($users as $key => $user) {
if ($user instanceof User) {
$users[$key] = $user->ID;
}
}
return Query::author($users);
}
/**
@@ -110,6 +112,7 @@ class Wordpress
*
* Use of WP_User is deprecated, however this method will not be removed.
*
* @deprecated use <code>Auth::user()</code> instead.
* @return \WP_User
*/
public static function currentUser()