├── .circleci └── config.yml ├── .gitignore ├── README.md ├── composer.json ├── functions.php ├── phpunit.xml.dist ├── src ├── Attachment.php ├── CacheHandler │ ├── BaseHandler.php │ ├── PostCache.php │ ├── TermCache.php │ └── UserCache.php ├── Meta.php ├── Page.php ├── Picture.php ├── Plugin │ ├── Plugin.php │ ├── PluginFactory.php │ └── Service.php ├── Post.php ├── Term.php └── User.php ├── tests ├── bin │ └── install-wp-tests.sh ├── bootstrap.php ├── test-post-controller.php └── test-term-controller.php ├── vendor ├── autoload.php └── composer │ ├── ClassLoader.php │ ├── LICENSE │ ├── autoload_classmap.php │ ├── autoload_namespaces.php │ ├── autoload_psr4.php │ ├── autoload_real.php │ └── autoload_static.php └── wp-controllers.php /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | job-references: 4 | mysql_image: &mysql_image 5 | circleci/mysql:5.6 6 | 7 | php_job: &php_job 8 | environment: 9 | - WP_TESTS_DIR: "/tmp/wordpress-tests-lib" 10 | - WP_CORE_DIR: "/tmp/wordpress/" 11 | - CIRCLE_PROJECT_REPONAME: "WP-Controllers" 12 | steps: 13 | - checkout 14 | - run: 15 | name: "Setup Environment Variables" 16 | command: | 17 | echo "export PATH=$HOME/.composer/vendor/bin:$PATH" >> $BASH_ENV 18 | source /home/circleci/.bashrc 19 | - run: 20 | name: "Install Dependencies" 21 | command: | 22 | sudo apt-get update && sudo apt-get install git subversion 23 | sudo docker-php-ext-install mysqli 24 | sudo sh -c "printf '\ndeb http://ftp.us.debian.org/debian sid main\n' >> /etc/apt/sources.list" 25 | sudo apt-get update && sudo apt-get install mysql-client-5.7 26 | 27 | - run: 28 | name: "Setup For Tests" 29 | command: | 30 | composer global require "phpunit/phpunit=5.7.*" 31 | rm -rf $WP_TESTS_DIR $WP_CORE_DIR 32 | bash tests/bin/install-wp-tests.sh wordpress_test root '' 127.0.0.1 33 | 34 | - run: 35 | name: "Run Tests" 36 | command: phpunit 37 | 38 | 39 | jobs: 40 | php56-build: 41 | <<: *php_job 42 | docker: 43 | - image: circleci/php:5.6 44 | - image: *mysql_image 45 | 46 | php70-build: 47 | <<: *php_job 48 | docker: 49 | - image: circleci/php:7.0 50 | - image: *mysql_image 51 | 52 | php71-build: 53 | <<: *php_job 54 | docker: 55 | - image: circleci/php:7.1 56 | - image: *mysql_image 57 | 58 | php72-build: 59 | <<: *php_job 60 | docker: 61 | - image: circleci/php:7.2 62 | - image: *mysql_image 63 | 64 | workflows: 65 | version: 2 66 | main: 67 | jobs: 68 | - php56-build 69 | - php70-build 70 | - php71-build 71 | - php72-build 72 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![CircleCI](https://circleci.com/gh/papertower/WP-Controllers.svg?style=svg)](https://circleci.com/gh/papertower/WP-Controllers) 2 | 3 | ## WordPress development for the [OOP](https://en.wikipedia.org/wiki/Object-oriented_programming) Programmer 4 | #### So what's the story? 5 | WordPress is an excellent framework. It's secure, robust, extensible, and is fanatical about legacy support. What it's not, however, is strongly written using modern techniques. Please understand, that's not a sleight at the expense of the hard-working contributors. A lot of the code in WordPress was written years ago for versions of PHP that preceded the mass amounts of improvements made in PHP 5.3.x. The other side of it is that it's catered towards non-developers. That is, folks who want to make a website, and are willing to learn a little PHP, but have no intention of learning more than they need to for their site. And that's ok! But, for those who do want to develop in a stronger, more OOP manner, it can be a drag. 6 | 7 | #### Enter WP Controllers! 8 | The WP Controllers were developed by a developer for developers. It's an attempt to take the base objects in WordPress (posts, terms, users, etc.) and give a class for each one. Instead of memorizing a lot of obscure functions, you simply handle the object and use all its provided functions. Not only does it simplify things, but it also speeds up development. A lot. 9 | 10 | #### Magic sucks 11 | OK, magic is fun, but not when developing. At times something working, but not knowing why, can be just as frustrating as a bug. Gah! WordPress, unfortunately, in the name of being friendly for non-developers, does a lot of magic. For example, when inside "the loop" of a wp_query, you're meant to use all these crazy functions that iterate the loop, grab the post in the loop, display specific things based on the current iteration of the loop, etc.. Wow! What ever happened to a foreach loop!? 12 | 13 | Ultimately, WordPress is a database-driven PHP framework. Nothing more, nothing less. The database is MySQL, and it operates using SQL. You can interact with the information like any other database, and the framework itself can be extended and amended like any other framework. No magic. The goal should be to be able to get all the benefits of the WordPress framework, without sacrificing the natural (and growing) capabilities of PHP and SQL. 14 | 15 | #### In a nutshell 16 | These classes make it possible to feel like you're working in PHP while enjoying all the benefits of WordPress. You lose nothing by using them, and it doesn't break *anything* in Wordpress. You're free to use as much or as little of these as you'd like. If you are (or aspire to be) a PHP developer, then you'll probably really like using this; if not, it may overwhelm you, and that's no problem. 17 | 18 | #### How to use it 19 | WP Controllers is a plugin, so just drop it in your plugins directory and activate it. From there you'll get access to all the base controllers. 20 | 21 | To extend the controllers just create a `wp-controllers/` directory in your root theme or plugin, and place your controllers in that directory. It is important that your naming convention for the controllers is that the name matches the file. So if your class name is `Services` then your file should be `services.php`. 22 | 23 | ##### Get a controller 24 | Easy as that. Now, to get a controller (e.g. post), use the `get_post_controller` function. Simply pass a post id, slug, or object. Don't worry about type casting, it will figure itself out. Or, if you're inside a post single template (or page), just call 25 | ``` 26 | $Post = get_post_controller() 27 | ``` 28 | with no arguments and it will return the controller for the active post. 29 | 30 | ##### Use the controller 31 | Once you have the controller, all the standard properties will be available minus the 'post_' prefix. So post_type, for example, would be 32 | ``` 33 | $Post->type 34 | ``` 35 | Meta is accessed via the Meta class, which is automatically added to every controller. You can retrieve single or all values and apply functions to the results 36 | 37 | ``` 38 | // Retrieves single meta 39 | $Post->meta->sub_title 40 | 41 | // All meta or empty array 42 | $Post->meta->all_movies('all') 43 | 44 | // The controllers for every value 45 | $Post->meta->related_posts('controllers') 46 | ``` 47 | 48 | ##### Familiarize yourself 49 | The best thing to do from here would be to look through the controllers and check out the available functions. There's some pretty cool ones and they keep being added. For example, 50 | 51 | ``` 52 | $Post->terms($taxonomies) 53 | ``` 54 | 55 | returns all the term controllers for that post. Another one is 56 | 57 | ``` 58 | $Post->excerpt(50) 59 | ``` 60 | 61 | which would return either all the excerpt (if used and has actual content) or the first 50 (or however many you want) **words** of the content, with all the shortcodes and tags stripped. 62 | 63 | #### Get Involved 64 | Have an idea? Make a pull request! 65 | 66 | Have a problem or question? Make an issue! 67 | 68 | This isn't just to make WordPress more object-oriented, it's also to make development faster, less buggy, and more efficient. It's a fantastic way to fulfill the [DRY](https://en.wikipedia.org/wiki/Don%27t_repeat_yourself) principal. 69 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "papertower/wp-controllers", 3 | "description": "Controllers to work in WordPress the OOP way", 4 | "type": "wordpress-plugin", 5 | "license": "MIT", 6 | "authors": [ 7 | { 8 | "name": "Jason Adams", 9 | "email": "jason@papertower.com" 10 | } 11 | ], 12 | "require": {}, 13 | "autoload": { 14 | "psr-4": {"WPControllers\\": "src/"} 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /functions.php: -------------------------------------------------------------------------------- 1 | 9 | 10 | 11 | ./tests/ 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/Attachment.php: -------------------------------------------------------------------------------- 1 | description =& $this->content; 35 | $this->caption =& $this->excerpt; 36 | $this->mime_type =& $post->post_mime_type; 37 | } 38 | 39 | public function alt() { 40 | return $this->meta->_wp_attachment_image_alt; 41 | } 42 | 43 | public function mime_type() { 44 | return $this->mime_type; 45 | } 46 | 47 | public function link() { 48 | return isset($this->_link) ? $this->_link 49 | : $this->_link = wp_get_attachment_url($this->id); 50 | } 51 | 52 | public function path() { 53 | return isset($this->_path) ? $this->_path 54 | : $this->_path = get_attached_file($this->id); 55 | } 56 | 57 | public function file_size() { 58 | return isset($this->_file_size) ? $this->_file_size 59 | : filesize($this->path()); 60 | } 61 | 62 | /** 63 | * @return string 64 | */ 65 | public function file_type() { 66 | $type = explode('/', $this->mime_type); 67 | return empty($type[1]) ? '' : strtoupper($type[1]); 68 | } 69 | 70 | } 71 | -------------------------------------------------------------------------------- /src/CacheHandler/BaseHandler.php: -------------------------------------------------------------------------------- 1 | post_type ) { 27 | $this->trigger_flush($post, $is_update ? self::EVENT_UPDATE : self::EVENT_INSERT); 28 | } 29 | } 30 | 31 | /** 32 | * Callback for the before_delete_post action, used to invalidate the cache 33 | * @ignore 34 | */ 35 | public function before_delete_post($post_id) { 36 | $post = get_post($post_id); 37 | if ( 'revision' !== $post && !did_action('before_delete_post') ) { 38 | $this->trigger_flush($post, self::EVENT_DELETE); 39 | } 40 | } 41 | 42 | /** 43 | * Triggers the flush to cache with the Post controller 44 | * @param WP_Post $post Post being affected 45 | * @param string $event Event triggering the flush 46 | */ 47 | private function trigger_flush($post, $event) { 48 | Post::trigger_cache_flush($post, $event); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/CacheHandler/TermCache.php: -------------------------------------------------------------------------------- 1 | trigger_flush(get_term($term_id, $taxonomy), self::EVENT_INSERT); 37 | } 38 | 39 | /** 40 | * Triggers when an existing term is updated 41 | * @param integer $term_id New term id 42 | * @param integer $term_taxonomy_id New term taxonomy id 43 | * @param string $taxonomy Taxonomy of new term 44 | */ 45 | public function edit_term($term_id, $term_taxonomy_id, $taxonomy) { 46 | $this->trigger_flush(get_term($term_id, $taxonomy), self::EVENT_UPDATE); 47 | } 48 | 49 | /** 50 | * Triggers when an existing term is deleted 51 | * @param integer $term_id New term id 52 | * @param string $taxonomy Taxonomy of new term 53 | */ 54 | public function pre_delete_term($term_id, $taxonomy) { 55 | $this->trigger_flush(get_term($term_id, $taxonomy), self::EVENT_DELETE); 56 | } 57 | 58 | /** 59 | * Triggers the flush to cache with the Term controller 60 | * @param WP_Term $term Term being affected 61 | * @param string $event Event triggering the flush 62 | */ 63 | private function trigger_flush($term, $event) { 64 | Term::trigger_flush_cache($term, $event); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/CacheHandler/UserCache.php: -------------------------------------------------------------------------------- 1 | trigger_flush(get_user_by('id', $user_id), self::EVENT_INSERT); 35 | } 36 | 37 | /** 38 | * Triggers when a user is updated 39 | * @param int $user_id 40 | * @param WP_User $old_user 41 | */ 42 | public function profile_update($user_id, $old_user) { 43 | $this->trigger_flush($old_user, self::EVENT_UPDATE); 44 | } 45 | 46 | /** 47 | * Triggers when a user is deleted 48 | * @param int $user_id 49 | * @param int $reassign_id 50 | */ 51 | public function delete_user($user_id, $reassign_id) { 52 | $this->trigger_flush(get_user_by('id', $user_id), self::EVENT_DELETE); 53 | } 54 | 55 | /** 56 | * Triggers the flush to cache with the User controller 57 | * @param WP_User $user User being affected 58 | * @param string $event Event triggering the flush 59 | */ 60 | private function trigger_flush($user, $event) { 61 | User::trigger_flush_cache($user, $event); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/Meta.php: -------------------------------------------------------------------------------- 1 | object_id = $id; 29 | $this->object_type = $type; 30 | } 31 | 32 | /** 33 | * @param string $name 34 | * @param array $arguments 35 | * 36 | * @return mixed 37 | */ 38 | public function __call($name, $arguments) { 39 | $this->get_meta($name); 40 | 41 | if ( empty($arguments) ) { 42 | return $this->data[$name]; 43 | } 44 | 45 | $function = array_shift($arguments); 46 | array_unshift($arguments, $this->data[$name]); 47 | 48 | if ( is_string($function) && method_exists($this, $function) ) { 49 | $function = array($this, $function); 50 | } 51 | 52 | if ( is_callable($function) ) { 53 | return call_user_func_array($function, $arguments); 54 | } else { 55 | return $this->data[$name]; 56 | } 57 | } 58 | 59 | /** 60 | * @param string $name 61 | * 62 | * @return mixed 63 | */ 64 | public function __get($name) { 65 | $this->get_meta($name); 66 | 67 | if ( is_array($this->data[$name]) ) { 68 | switch(count($this->data[$name])) { 69 | case 0: return ''; 70 | case 1: return $this->data[$name][0]; 71 | default: return $this->data[$name]; 72 | } 73 | } else { 74 | return $this->data[$name]; 75 | } 76 | } 77 | 78 | /** 79 | * @param string $name 80 | * 81 | * @return bool 82 | */ 83 | public function __isset($name) { 84 | if ( !empty($this->data[$name]) ) { 85 | return true; 86 | } 87 | 88 | $this->get_meta($name); 89 | return !empty($this->data[$name]); 90 | } 91 | 92 | /** 93 | * @param string $name 94 | */ 95 | public function __unset($name) { 96 | unset($this->data[$name]); 97 | } 98 | 99 | /** 100 | * Used to retrieve meta with a key not permitted by PHP 101 | * For example, if a key is 'my-field', the hyphen is not set 102 | * @param string $key key used in the database 103 | * @param string $name name to store under for future retrieval 104 | */ 105 | public function store($key, $name) { 106 | $this->get_meta($key, $name); 107 | } 108 | 109 | /** 110 | * @param string $key 111 | * @param null $name 112 | * @param bool|false $single 113 | */ 114 | protected function get_meta($key, $name = null, $single = false) { 115 | $name = is_null($name) ? $key : $name; 116 | if ( isset($this->data[$name]) ) return; 117 | 118 | switch ($this->object_type) { 119 | case 'post': 120 | case 'user': 121 | case 'comment': 122 | $this->data[$name] = get_metadata($this->object_type, $this->object_id, $key, $single); 123 | break; 124 | 125 | case 'term': 126 | $this->data[$name] = get_term_meta($this->object_id, $key, $single); 127 | break; 128 | } 129 | } 130 | 131 | /** 132 | * @param array $value 133 | * 134 | * @return mixed|null 135 | */ 136 | protected function single($value) { 137 | return isset($value[0]) ? $value[0] : null; 138 | } 139 | 140 | /** 141 | * @param $value 142 | * 143 | * @return mixed|null 144 | */ 145 | protected function all($value) { 146 | return is_array($value) ? $value : array(); 147 | } 148 | 149 | protected function controllers($values) { 150 | if ( is_array($values) ) { 151 | if ( !empty($values) ) { 152 | return get_post_controllers($values); 153 | } else { 154 | return array(); 155 | } 156 | } else { 157 | return $values; 158 | } 159 | } 160 | 161 | /** 162 | * @param array $values 163 | * 164 | * @return Post|null 165 | */ 166 | protected function controller($values) { 167 | if ( is_array($values) && !empty($values[0]) ) { 168 | return get_post_controller($values[0]); 169 | } else { 170 | return null; 171 | } 172 | } 173 | 174 | protected function date($values, $format) { 175 | if ( empty($values[0]) ) return ''; 176 | return date($format, strtotime($values[0])); 177 | } 178 | 179 | } 180 | -------------------------------------------------------------------------------- /src/Page.php: -------------------------------------------------------------------------------- 1 | _children ) return $this->_children; 40 | 41 | $this->_children = self::get_controllers(array( 42 | 'hierarchical' => false, 43 | 'parent' => $this->id, 44 | 'post_status' => 'publish,private' 45 | )); 46 | 47 | return $this->_children; 48 | } 49 | 50 | /** 51 | * Returns page parent controller, if any 52 | * @return object|null 53 | */ 54 | public function parent() { 55 | if ( $this->_parent ) return $this->_parent; 56 | if ( !$this->parent_id ) return null; 57 | 58 | return $this->_parent = self::get_controller($this->parent_id); 59 | } 60 | 61 | /** 62 | * Returns pages of specified template(s) 63 | * @param array|string $templates 64 | * @param array $options (optional) 65 | * @return array 66 | */ 67 | public static function get_page_templates($templates, $options = array()) { 68 | $options = array_merge_recursive($options, array( 69 | 'post_type' => 'page', 70 | 'numberposts' => -1, 71 | 'meta_query' => array( 72 | array( 73 | 'key' => '_wp_page_template', 74 | 'value' => $templates 75 | ) 76 | ) 77 | )); 78 | 79 | return parent::get_controllers($options); 80 | } 81 | 82 | /** 83 | * Returns parent controller for post in loop 84 | * @return object 85 | */ 86 | public static function get_parent() { 87 | global $post; 88 | return ( $post->post_parent ) ? self::get_controller($post->post_parent) : null; 89 | } 90 | 91 | } 92 | -------------------------------------------------------------------------------- /src/Picture.php: -------------------------------------------------------------------------------- 1 | sizes[$size]) ) 25 | $this->sizes[$size] = wp_get_attachment_image_src($this->id, $size); 26 | 27 | return $this->sizes[$size]; 28 | } 29 | 30 | /** 31 | * src 32 | * Returns the image src attribute for a given size. If $wide_size is provided the returned src 33 | * will depend on the original dimensions of the image. If the image is square then either 34 | * $square_size will be used or $size by default. 35 | * 36 | * @param string $size 37 | * @param string $wide_size 38 | * @param string $square_size 39 | * 40 | * @return string 41 | */ 42 | public function src($size, $wide_size = null, $square_size = null) { 43 | if ( !$wide_size ) { 44 | $details = $this->details($size); 45 | return $details[0]; 46 | } else { 47 | $width = $this->width('full'); 48 | $height = $this->height('full'); 49 | 50 | if ( $width > $height ) { 51 | $details = $this->details($wide_size); 52 | } elseif ( $height > $width ) { 53 | $details = $this->details($size); 54 | } else { 55 | $details = $square_size ? $this->details($square_size) : $this->details($size); 56 | } 57 | 58 | return $details[0]; 59 | } 60 | } 61 | 62 | /** 63 | * srcset. 64 | * Accepts an associative array where the key is the qualifier (e.g. 200w) and the value is the 65 | * size to display. If an array is passed as the value then it follows the same parameters as the 66 | * src function. 67 | * 68 | * A string can also be provided that's parsed into the usable parts: 69 | * 200w->small|600w->medium|1200->tall-large,tall-wide 70 | * 71 | * @param array|string $sizes associative array of sizes 72 | * 73 | * @return string 74 | */ 75 | public function srcset($sizes) { 76 | if ( is_string($sizes) ) { 77 | $sizes = explode('|', $sizes); 78 | $parsed_sizes = array(); 79 | foreach($sizes as $pair) { 80 | list($qualifier, $size) = explode('->', $pair); 81 | $parsed_sizes[trim($qualifier)] = explode(',', trim($size)); 82 | } 83 | $sizes = $parsed_sizes; 84 | } 85 | 86 | $list = array(); 87 | foreach($sizes as $qualifier => $size) { 88 | $src = is_array($size) ? call_user_func_array(array($this, 'src'), $size) : $this->src($size); 89 | $list[] = "$src $qualifier"; 90 | } 91 | 92 | return implode(', ', $list); 93 | } 94 | 95 | /** 96 | * @param string $size 97 | * 98 | * @return int 99 | */ 100 | public function width($size) { 101 | $details = $this->details($size); 102 | return $details[1]; 103 | } 104 | 105 | /** 106 | * @param string $size 107 | * 108 | * @return int 109 | */ 110 | public function height($size) { 111 | $details = $this->details($size); 112 | return $details[2]; 113 | } 114 | 115 | /** 116 | * @param string $size 117 | * 118 | * @return bool 119 | */ 120 | public function is_resized($size) { 121 | $details = $this->details($size); 122 | return $details[3]; 123 | } 124 | }; 125 | -------------------------------------------------------------------------------- /src/Plugin/Plugin.php: -------------------------------------------------------------------------------- 1 | 17 | */ 18 | private $_directories = []; 19 | 20 | /** 21 | * Main file of the WP Controllers plugin 22 | * @var string 23 | */ 24 | private $plugin_file; 25 | 26 | /** 27 | * Main directory of the WP Controllers plugin 28 | * @var string 29 | */ 30 | private $plugin_directory; 31 | 32 | /** 33 | * Constructor for plugin which sets the plugin file and directory 34 | * @param string $file Main plugin file 35 | * @param string $directory Main plugin directory 36 | */ 37 | public function __construct($file, $directory) { 38 | $this->plugin_file = $file; 39 | $this->plugin_directory = $directory; 40 | } 41 | 42 | /** 43 | * Kicks everything off 44 | */ 45 | public function register() { 46 | add_action('init', [$this, 'init']); 47 | add_filter('register_post_type_args', [$this, 'register_post_type_args'], 10, 2); 48 | 49 | $this->registerCacheServices(); 50 | 51 | require_once "{$this->plugin_directory}/functions.php"; 52 | } 53 | 54 | /** 55 | * Loads all the controller classes upon the init event of WordPress 56 | */ 57 | public function init() { 58 | $this->load_controller_directories(); 59 | 60 | spl_autoload_register([$this, 'autoload_register']); 61 | } 62 | 63 | public function registerCacheServices() { 64 | $services = [ 65 | PostCache::class, 66 | TermCache::class, 67 | UserCache::class 68 | ]; 69 | 70 | foreach($services as $service) { 71 | $service = new $service; 72 | $service->register(); 73 | } 74 | } 75 | 76 | /** 77 | * Sets the controllers classes for the default post types 78 | * @param array $args Post type registration arguments 79 | * @param string $post_type Post type being registered 80 | * @return array Modified registration arguments 81 | */ 82 | public function register_post_type_args($args, $post_type) { 83 | switch($post_type) { 84 | case 'post': 85 | $args['wp_controller_class'] = apply_filters("wp_controllers_default_{$post_type}_class", Post::class, $args); 86 | break; 87 | 88 | case 'attachment': 89 | $args['wp_controller_class'] = apply_filters("wp_controllers_default_{$post_type}_class", Attachment::class, $args); 90 | break; 91 | 92 | case 'page': 93 | $args['wp_controller_class'] = apply_filters("wp_controllers_default_{$post_type}_class", Page::class, $args); 94 | break; 95 | } 96 | 97 | return $args; 98 | } 99 | 100 | /** 101 | * autoload_register. 102 | * Used to autoload the classes in order of inheritance 103 | * @param string $class the class name 104 | */ 105 | public function autoload_register($class) { 106 | // Checks for file in lowerclass 107 | $lower_class = function_exists('mb_strtolower') ? mb_strtolower($class) : strtolower($class); 108 | 109 | // Checks for class with namespacing stripped 110 | $namespace_position = strrpos($class, '\\'); 111 | $base_class = $namespace_position ? substr($class, -1 * ( strlen($class) - $namespace_position - 1 ) ) : $class; 112 | 113 | foreach($this->_directories as $directory) { 114 | if ( file_exists("$directory/$base_class.php") ) { 115 | $include = "$directory/$base_class.php"; 116 | } elseif ( file_exists("$directory/$lower_class.php") ) { 117 | $include = "$directory/$lower_class.php"; 118 | } else { 119 | continue; 120 | } 121 | 122 | include $include; 123 | if ( class_exists($class, false) && method_exists($class, '_construct')) { 124 | call_user_func([$class, '_construct']); 125 | } 126 | 127 | break; 128 | } 129 | } 130 | 131 | /** 132 | * Goes through the theme and active plugins to check whether it has a wp-controllers directory 133 | * and adds this to the internal directories 134 | */ 135 | private function load_controller_directories() { 136 | $this->_directories = ["{$this->plugin_directory}/controllers"]; 137 | 138 | $parent_theme = get_template_directory(); 139 | $child_theme = get_stylesheet_directory(); 140 | 141 | // Check & add child theme 142 | if ( $parent_theme !== $child_theme ) { 143 | $child_theme = apply_filters('wp_controllers_child_theme_directory', "$child_theme/wp-controllers"); 144 | if ( is_dir($child_theme) ) { 145 | $this->_directories[] = $child_theme; 146 | $this->trigger_deprecation_notice($child_theme); 147 | } 148 | } 149 | 150 | // Check & add main/parent theme 151 | $parent_theme = apply_filters('wp_controllers_theme_directory', "$parent_theme/wp-controllers"); 152 | if ( is_dir($parent_theme) ) { 153 | $this->_directories[] = $parent_theme; 154 | $this->trigger_deprecation_notice($parent_theme); 155 | } 156 | 157 | // Include necessary plugin functions if front-end 158 | if ( !function_exists('get_plugins') ) { 159 | include_once( ABSPATH . 'wp-admin/includes/plugin.php' ); 160 | } 161 | 162 | // Check & add active plugins 163 | $plugins = get_plugins(); 164 | $plugins_path = WP_PLUGIN_DIR; 165 | foreach($plugins as $path => $data) { 166 | if ( is_plugin_active($path) && basename($path) !== basename($this->plugin_file) ) { 167 | $path = strstr($path, DIRECTORY_SEPARATOR, true); 168 | $directory = apply_filters('wp_controllers_plugin_directory', "$plugins_path/$path/wp-controllers", $path, $data); 169 | if ( is_dir($directory) ) { 170 | $this->trigger_deprecation_notice($path); 171 | $this->_directories[] = $directory; 172 | } 173 | } 174 | } 175 | } 176 | 177 | /** 178 | * @param string $source Plugin or theme the warning is for 179 | */ 180 | private function trigger_deprecation_notice(string $source) { 181 | if ( defined('WP_DEBUG') && WP_DEBUG ) { 182 | trigger_error("Warning for $source: Auto-loading from WP Controllers will be deprecated by 1.0. We suggest adding your own PSR-4 auto-loading.", E_USER_WARNING); 183 | } 184 | } 185 | } 186 | -------------------------------------------------------------------------------- /src/Plugin/PluginFactory.php: -------------------------------------------------------------------------------- 1 | controller 19 | * @var array $_controller_post_types post_type => controller 20 | */ 21 | private static 22 | $_controller_templates = array(), 23 | $_controller_post_types = array(); 24 | 25 | /** 26 | * @var object $post WP_Post class 27 | */ 28 | protected 29 | $post; 30 | 31 | /** 32 | * @var object $post WP_Post class 33 | * @var int id 34 | * @var string slug 35 | * @var string title 36 | * @var string excerpt 37 | * @var string content 38 | * @var string status 39 | * @var string type 40 | * @var int author 41 | * @var int parent_id 42 | * @var int menu_order 43 | * @var int comment_count 44 | * @var string comment_status 45 | * @var string date 46 | * @var string date_gmt 47 | * @var string modified 48 | * @var string modified_gmt 49 | * @var string password 50 | * @var string to_ping 51 | * @var string pinged 52 | * @var string guide 53 | * @var string filter 54 | * @var string ping_status 55 | * @var string mime_type 56 | * @var string content_filtered 57 | * @var Meta meta 58 | */ 59 | public 60 | $id, 61 | $slug, 62 | $title, 63 | $excerpt, 64 | $content, 65 | $status, 66 | $type, 67 | $author, 68 | $parent_id, 69 | $menu_order, 70 | $comment_count, 71 | $comment_status, 72 | $date, 73 | $date_gmt, 74 | $modified, 75 | $modified_gmt, 76 | $password, 77 | $to_ping, 78 | $pinged, 79 | $ping_status, 80 | $guid, 81 | $filter, 82 | $mime_type, 83 | $content_filtered, 84 | $meta; 85 | 86 | /** 87 | * Retrieves the controller for the post type 88 | * @since 0.7.0 89 | * @param string|int|object $key post id, slug, or WP_Post object 90 | * @param array $options 91 | * @return Post|WP_Error 92 | */ 93 | public static function get_controller($key = null, $options = array()) { 94 | if ( is_null($key) ) { 95 | global $post; 96 | $key = $post; 97 | if ( empty($key) ) 98 | return new WP_Error('no_global_post', 'Global post is null', $key); 99 | } 100 | 101 | // Check for cached object and set $post to the WP_Post otherwise 102 | if ( is_numeric($key) ) { 103 | $cached_post = wp_cache_get($key, 'postcontroller'); 104 | if ( false !== $cached_post ) return $cached_post; 105 | $_post = get_post(intval($key)); 106 | if ( !$_post ) return new WP_Error('post_id_not_found', 'No post was found for the provided ID', $key); 107 | 108 | } elseif ( is_string($key) ) { 109 | $post_id = wp_cache_get($key, 'postcontroller_slug'); 110 | if ( false !== $post_id ) return wp_cache_get($post_id, 'postcontroller'); 111 | 112 | $posts = get_posts(array( 113 | 'name' => $key, 114 | 'post_type' => empty($options['post_type']) ? 'any' : $options['post_type'], 115 | 'post_status' => array('publish', 'private'), 116 | 'numberposts' => 1 117 | )); 118 | 119 | if ( empty($posts) ) 120 | return new WP_Error('post_slug_not_found', 'No post was found for the provided slug', $key); 121 | 122 | $_post = $posts[0]; 123 | 124 | } elseif ( is_object($key) ) { 125 | $_post = wp_cache_get($key->ID, 'postcontroller'); 126 | if ( false !== $_post ) return $_post; 127 | 128 | $_post = $key; 129 | 130 | } else { 131 | return new WP_Error('invalid_key_type', 'Key provied is unsupported type', $key); 132 | } 133 | 134 | // Construct, cache, and return post 135 | $controller_class = self::get_controller_class($_post, $options); 136 | $controller = new $controller_class ($_post); 137 | 138 | wp_cache_set($controller->slug, $controller->id, 'postcontroller_slug', MINUTE_IN_SECONDS * 10); 139 | wp_cache_set($controller->id, $controller, 'postcontroller', MINUTE_IN_SECONDS * 10); 140 | 141 | return $controller; 142 | } 143 | 144 | /** 145 | * Returns controllers for an array of posts or wp_query arguments 146 | * @since 0.7.0 147 | * 148 | * @param array $args Either an array of WP_Post objects or get_posts arguments 149 | * @param array $options Options to pass with the get_controllers function 150 | * 151 | * @return array 152 | */ 153 | public static function get_controllers($args = null, $options = array()) { 154 | if ( is_null($args) ) { 155 | // Retrieve archive posts 156 | global $wp_query; 157 | if ( isset($wp_query->posts) ) { 158 | $posts = $wp_query->posts; 159 | } else { 160 | trigger_error('No posts found in the wp_query. Please use this only in a post archive template.', E_USER_WARNING); 161 | return array(); 162 | } 163 | 164 | } elseif ( isset($args[0]) && ( is_object($args[0]) || is_numeric($args[0]) ) ) { 165 | // Turn array of WP_Posts into controllers 166 | $posts = $args; 167 | 168 | } elseif ( !empty($args) ) { 169 | // Retrieve posts from get_posts arguments 170 | if ( !isset($args['suppress_filters']) ) $args['suppress_filters'] = false; 171 | $posts = get_posts($args); 172 | 173 | if ( isset($args['fields']) && in_array($args['fields'], ['ids', 'id=>parent']) ) { 174 | return $posts; 175 | } 176 | 177 | } else { 178 | // Probably empty array of what would be posts or something unexpected 179 | return array(); 180 | } 181 | 182 | $controllers = array(); 183 | foreach($posts as $post) { 184 | $controllers[] = self::get_controller($post, $options); 185 | } 186 | 187 | return $controllers; 188 | } 189 | 190 | /** 191 | * Returns the class name for the corresponding post 192 | * @param WP_Post $post WP_Post to get the class for 193 | * @param array $options Options passed for the post 194 | * @return string Class name 195 | */ 196 | public static function get_controller_class($post, $options = array()) { 197 | // Check for template-specific controller first 198 | $class = self::get_template_class($post); 199 | if ( $class ) return $class; 200 | 201 | // Set all the post type classes if not set 202 | if ( empty(self::$_controller_post_types) ) { 203 | self::set_post_type_controllers(); 204 | } 205 | 206 | if ( 'attachment' === $post->post_type && wp_attachment_is_image($post) ) { 207 | $class = self::$_controller_post_types['image']; 208 | } else { 209 | $class = isset(self::$_controller_post_types[$post->post_type]) 210 | ? self::$_controller_post_types[$post->post_type] 211 | : __CLASS__; 212 | } 213 | 214 | return apply_filters('wp_controllers_post_class', $class, $post, $options); 215 | } 216 | 217 | /** 218 | * Returns the post type name for a given class. If the class itself does not have an explicit post type, it will 219 | * traverse up inheritance until a class is found that is tied to a post type. 220 | * 221 | * @param string $class Class to look up 222 | * @param null $post_type_controllers Used for recursion 223 | * @param null $template_controllers Used for recursion 224 | * 225 | * @return string|false Post type name if found, otherwise false 226 | */ 227 | public static function get_controller_post_type($class, $post_type_controllers = null, $template_controllers = null) { 228 | if ( !$class ) { 229 | return false; 230 | } 231 | 232 | if ( null === $post_type_controllers ) { 233 | $post_type_controllers = array_flip(self::$_controller_post_types); 234 | $template_controllers = array_flip(self::$_controller_templates); 235 | } 236 | 237 | if ( empty(self::$_controller_post_types) ) { 238 | self::set_post_type_controllers(); 239 | } 240 | 241 | if ( isset($post_type_controllers[$class]) ) { 242 | return $post_type_controllers[$class]; 243 | } 244 | 245 | if ( isset($template_controllers[$class]) ) { 246 | $parent_class = get_parent_class($class); 247 | return self::get_controller_post_type($parent_class, $post_type_controllers, $template_controllers); 248 | } 249 | 250 | return false; 251 | } 252 | 253 | /** 254 | * Maps the post types to controllers for later use 255 | */ 256 | private static function set_post_type_controllers() { 257 | self::$_controller_post_types['image'] = 'Picture'; 258 | 259 | $post_types = get_post_types(array(), 'objects'); 260 | foreach($post_types as $type) { 261 | if ( !empty($type->wp_controller_class) ) { 262 | self::$_controller_post_types[$type->name] = $type->wp_controller_class; 263 | } 264 | } 265 | } 266 | 267 | /** 268 | * get_template_class 269 | * @param WP_Post $post post to retrieve the template class for 270 | * @return string|false class if there is one, false if not class or template 271 | */ 272 | private static function get_template_class($post) { 273 | $template = get_page_template_slug($post); 274 | if ( !$template ) return false; 275 | 276 | if ( isset(self::$_controller_templates[$template]) ) { 277 | return self::$_controller_templates[$template]; 278 | 279 | } else { 280 | $data = get_file_data(get_template_directory() . "/$template", array( 281 | 'controller_class' => 'WP Controller' 282 | )); 283 | 284 | $class = $data['controller_class']; 285 | 286 | if ( is_child_theme() ) { 287 | $data = get_file_data(get_stylesheet_directory() . "/$template", array( 288 | 'controller_class' => 'WP Controller' 289 | )); 290 | 291 | $class = !empty($data['controller_class']) ? $data['controller_class'] : $class; 292 | } 293 | 294 | return self::$_controller_templates[$template] = $class ? $class : false; 295 | } 296 | } 297 | 298 | /** 299 | * Called when the cache for a post controller needs to be flushed. Calls the flush_cache static 300 | * method for the class the post belongs to. 301 | * @param WP_Post $post post object that needs to be invalidated 302 | * @param string $event event which triggered the flush 303 | */ 304 | public static function trigger_cache_flush($post, $event) { 305 | wp_cache_delete($post->post_name, 'postcontroller_slug'); 306 | wp_cache_delete($post->ID, 'postcontroller'); 307 | 308 | $controller_class = self::get_controller_class($post); 309 | if ( $controller_class && method_exists($controller_class, 'flush_cache') ) { 310 | $controller_class::flush_cache($post, $event); 311 | } 312 | } 313 | 314 | /** 315 | * Returns the meta class to be used for the post 316 | * @param WP_Post $post Post the meta class will be used for 317 | * @return string Meta class 318 | */ 319 | protected static function get_meta_class($post) { 320 | return apply_filters('wp_controllers_meta_class', Meta::class, $post); 321 | } 322 | 323 | /** 324 | * Constructor. 325 | * Protect the constructor as we do not want the controllers 326 | * to be instantiated directly. This is to ensure caching. 327 | * @since 0.7.0 328 | * @param WP_Post $post 329 | */ 330 | protected function __construct($post) { 331 | // Standard Properties 332 | $this->post = $post; 333 | $this->id = $this->post->ID; 334 | $this->slug = $this->post->post_name; 335 | $this->title = $this->post->post_title; 336 | $this->excerpt = $this->post->post_excerpt; 337 | $this->content = $this->post->post_content; 338 | $this->status = $this->post->post_status; 339 | $this->type = $this->post->post_type; 340 | $this->parent_id = $this->post->post_parent; 341 | $this->author = $this->post->post_author; 342 | $this->menu_order = $this->post->menu_order; 343 | $this->password = $this->post->post_password; 344 | $this->to_ping = $this->post->to_ping; 345 | $this->pinged = $this->post->pinged; 346 | $this->guid = $this->post->guid; 347 | $this->filter = $this->post->filter; 348 | $this->ping_status = $this->post->ping_status; 349 | $this->mime_type = $this->post->post_mime_type; 350 | $this->content_filtered = $this->post->post_content_filtered; 351 | 352 | // Comments 353 | $this->comment_count = $this->post->comment_count; 354 | $this->comment_status = $this->post->comment_status; 355 | 356 | // Dates 357 | $this->date = $this->post->post_date; 358 | $this->date_gmt = $this->post->post_date_gmt; 359 | $this->modified = $this->post->post_modified; 360 | $this->modified_gmt = $this->post->post_modified_gmt; 361 | 362 | // Meta class 363 | $meta_class = static::get_meta_class($post); 364 | $this->meta = new $meta_class($this->id, 'post'); 365 | } 366 | 367 | public function __get($name) { 368 | if ( 'ID' === $name ) { 369 | return $this->id; 370 | } 371 | 372 | if ( 'post_name' === $name ) { 373 | return $this->slug; 374 | } 375 | 376 | if ( 'post_parent' === $name ) { 377 | return $this->parent_id; 378 | } 379 | 380 | if ( 0 === strpos($name, 'post_') ) { 381 | $short_key = substr($name, 5); 382 | if ( isset($this->$short_key) ) { 383 | return $this->$short_key; 384 | } 385 | } 386 | 387 | return null; 388 | } 389 | 390 | public function __isset($name) { 391 | if ( 0 === strpos($name, 'post_') ) { 392 | $short_key = substr($name, 5); 393 | return isset($this->$short_key); 394 | } 395 | 396 | return false; 397 | } 398 | 399 | 400 | /** 401 | * Returns adjacent post controller. 402 | * 403 | * @see https://codex.wordpress.org/Function_Reference/get_adjacent_post 404 | * 405 | * @param bool|false $same_term 406 | * @param array $excluded_terms 407 | * @param bool|true $previous 408 | * @param string $taxonomy 409 | * 410 | * @return Post|false Returns controller if available, and false if no post 411 | */ 412 | protected function adjacent_post($same_term = false, $excluded_terms = array(), $previous = true, $taxonomy = 'category') { 413 | $date_type = $previous ? 'before' : 'after'; 414 | 415 | $arguments = array( 416 | 'post_type' => $this->type, 417 | 'numberposts' => 1, 418 | 'order' => $previous ? 'DESC' : 'ASC', 419 | 'post__not_in'=> array($this->id), 420 | 'date_query' => array( 421 | array( 422 | $date_type => $this->date('F j, Y') 423 | ) 424 | ) 425 | ); 426 | 427 | if ( $taxonomy && $same_term ) { 428 | $terms = get_terms($taxonomy, array( 429 | 'fields' => 'ids', 430 | 'exclude' => $excluded_terms 431 | )); 432 | 433 | if ( ! (empty($terms) || is_wp_error($terms) ) ) { 434 | $arguments['tax_query'] = array( 435 | array( 436 | 'taxonomy' => $taxonomy, 437 | 'field' => 'term_id', 438 | 'terms' => $terms 439 | ) 440 | ); 441 | } 442 | } 443 | 444 | $posts = get_posts($arguments); 445 | return isset($posts[0]) ? static::get_controller($posts[0]) : false; 446 | } 447 | 448 | /** 449 | * Returns post url 450 | * @return string 451 | */ 452 | public function url() { 453 | return isset($this->_url) ? $this->_url : $this->_url = get_permalink($this->id); 454 | } 455 | 456 | /** 457 | * Returns archive url 458 | * @return string 459 | */ 460 | public static function archive_url() { 461 | return get_post_type_archive_link(self::get_controller_post_type(get_called_class())); 462 | } 463 | 464 | /** 465 | * Returns the author User controller 466 | * @return User|WP_Error 467 | */ 468 | public function author() { 469 | if ( isset($this->_author) ) return $this->_author; 470 | return $this->_author = get_user_controller($this->author); 471 | } 472 | 473 | /** 474 | * Returns timestamp or formatted date 475 | * @param string $format Date format 476 | * @param boolean $gmt whether to use gmt date 477 | * @return false|string 478 | */ 479 | public function date($format, $gmt = false) { 480 | if ( 'timestamp' === $format ) { 481 | return $gmt ? strtotime($this->date_gmt) : strtotime($this->date); 482 | } else { 483 | return $gmt ? date($format, $this->date('timestamp', $this->date_gmt)) : date($format, $this->date('timestamp', $this->date)); 484 | } 485 | } 486 | 487 | /** 488 | * Returns timestamp or formatted modified date 489 | * @param string $format Date format 490 | * @param boolean $gmt whether to use gmt date 491 | * @return false|string 492 | */ 493 | public function modified($format, $gmt = false) { 494 | if ( 'timestamp' === $format ) { 495 | return $gmt ? strtotime($this->modified_gmt) : strtotime($this->modified); 496 | } else { 497 | return $gmt ? date($format, $this->date('timestamp', $this->modified_gmt)) : date($format, $this->date('timestamp', $this->modified)); 498 | } 499 | } 500 | 501 | /** 502 | * Returns next post if available 503 | * @param boolean $same_term 504 | * @param array $excluded_terms 505 | * @param string $taxonomy 506 | * @return null|object 507 | */ 508 | public function next_post($same_term = false, $excluded_terms = array(), $taxonomy = 'category') { 509 | return $this->adjacent_post($same_term, $excluded_terms, false, $taxonomy); 510 | } 511 | 512 | /** 513 | * Returns previous post if available 514 | * @param boolean $same_term 515 | * @param array $excluded_terms 516 | * @param string $taxonomy 517 | * @return null|object 518 | */ 519 | public function previous_post($same_term = false, $excluded_terms = array(), $taxonomy = 'category') { 520 | return $this->adjacent_post($same_term, $excluded_terms, true, $taxonomy); 521 | } 522 | 523 | /** 524 | * Retrieves taxonomy terms and can apply urls 525 | * @since 0.1.0 526 | * @param string|array $taxonomies 527 | * @return array|WP_Error 528 | */ 529 | public function terms($taxonomies) { 530 | // Retrieve terms 531 | $terms = wp_get_post_terms($this->id, $taxonomies); 532 | 533 | if ( is_wp_error($terms) ) return $terms; 534 | 535 | $controllers = array(); 536 | foreach($terms as $term) { 537 | $controllers[] = Term::get_controller($term); 538 | } 539 | 540 | return $controllers; 541 | } 542 | 543 | /** 544 | * Returns array of posts that share taxonomy 545 | * @since 0.1.0 546 | * @param string $post_type 547 | * @param string|array $taxonomies 548 | * @param array $options (optional) 549 | * @return array|null of controllers 550 | */ 551 | public function related_posts($post_type, $taxonomies, $options = null) { 552 | $options = wp_parse_args( $options, array( 553 | 'count' => -1, 554 | 'meta_query'=> null 555 | )); 556 | 557 | $args = array( 558 | 'post_type' => $post_type, 559 | 'posts_per_page'=> $options['count'], 560 | 'post__not_in' => array($this->id), 561 | 'tax_query' => array( 562 | 'relation' => 'OR', 563 | ) 564 | ); 565 | 566 | if ( $options['meta_query'] ) { 567 | $args['meta_query'] = $options['meta_query']; 568 | } 569 | 570 | if ( !is_array($taxonomies) ) 571 | $taxonomies = array($taxonomies); 572 | 573 | foreach($taxonomies as $index => $taxonomy) { 574 | // Retrieve terms and continue if empty 575 | $terms = $this->terms($taxonomy); 576 | if ( is_null($terms) ) continue; 577 | 578 | // Store the ids into an array 579 | $term_ids = array(); 580 | foreach($terms as $term) 581 | $term_ids[] = $term->term_id; 582 | 583 | $args['tax_query'][] = array( 584 | 'taxonomy' => $taxonomy, 585 | 'field' => 'id', 586 | 'terms' => $term_ids 587 | ); 588 | } 589 | 590 | if ( count($args['tax_query']) === 1 ) 591 | return null; 592 | 593 | return static::get_controllers($args); 594 | } 595 | 596 | /** 597 | * Returns the featured image 598 | * @since 0.1.0 599 | * @param array $options (optional) 600 | * @return object|null 601 | */ 602 | public function featured_image($options = array()) { 603 | $id = $this->meta->_thumbnail_id('single'); 604 | return $id ? static::get_controller( $id, $options ) : null; 605 | } 606 | 607 | /** 608 | * Returns whether or not the given term(s) are associated with the post. If no terms are provided 609 | * then whether or not the post has any terms within the taxonomy 610 | * @param string $taxonomy Single taxonomy name 611 | * @param int|string|array $term Optional. Term term_id, name, slug or array of said. Default null. 612 | * @return boolean 613 | */ 614 | public function has_term($taxonomy, $term = null) { 615 | $result = is_object_in_term($this->id, $taxonomy, $term); 616 | return $result && !is_wp_error($result); 617 | } 618 | 619 | /** 620 | * Returns the content with standard filters applied 621 | * if password is required it returns the form 622 | * @return string 623 | */ 624 | public function content() { 625 | if ( $this->password_required() ) { 626 | return get_the_password_form($this->post); 627 | } else { 628 | return apply_filters('the_content', $this->content); 629 | } 630 | } 631 | 632 | /** 633 | * Returns true if the post is password protected and the password is still required 634 | * @return boolean 635 | */ 636 | public function password_required() { 637 | return post_password_required($this->post); 638 | } 639 | 640 | /** 641 | * Returns the title with the WP filters applied 642 | * @return string 643 | */ 644 | public function title() { 645 | return apply_filters('the_title', $this->title, $this->id, $this); 646 | } 647 | 648 | /** 649 | * Returns the filtered excpert or limited content if no filter exists 650 | * @since 0.1.0 651 | * @param int $word_count (default: 40) 652 | * @param string $ellipsis (default: '...') 653 | * @param boolean $apply_filters (default: true) 654 | * @return string 655 | */ 656 | public function excerpt($word_count = 40, $ellipsis = '...', $apply_filters = true) { 657 | if ( !empty($this->excerpt) ) 658 | return ($apply_filters) ? apply_filters('the_excerpt', $this->excerpt) : $this->excerpt; 659 | 660 | if ( $apply_filters ) { 661 | remove_filter('the_excerpt', 'wpautop'); 662 | $content = wp_kses($this->content, array( 663 | 'em' => array(), 664 | 'strong'=> array(), 665 | 'u' => array(), 666 | 'a' => array( 667 | 'href' => array(), 668 | 'title' => array() 669 | ) 670 | )); 671 | $content = strip_shortcodes($content); 672 | $content = apply_filters('the_excerpt', $content); 673 | } else 674 | $content = $this->content; 675 | 676 | return $word_count ? wp_trim_words($content, $word_count, $ellipsis) : $content; 677 | } 678 | 679 | /** 680 | * Returns post controllers organized by terms 681 | * @since 0.1.0 682 | * @param string $taxonomy 683 | * @return array 684 | */ 685 | protected static function get_categorized_posts($taxonomy) { 686 | $categorized_posts = array(); 687 | $terms = get_terms($taxonomy); 688 | 689 | foreach($terms as $term) { 690 | $posts = get_posts(array( 691 | 'post_type' => self::get_controller_post_type(get_called_class()), 692 | 'numberposts' => -1, 693 | 'tax_query' => array( 694 | array( 695 | 'taxonomy' => $taxonomy, 696 | 'field' => 'id', 697 | 'terms' => $term->term_id 698 | ) 699 | ) 700 | )); 701 | 702 | $term->posts = array(); 703 | foreach($posts as $post) 704 | $term->posts[] = static::get_controller($post); 705 | 706 | $categorized_posts[] = $term; 707 | } 708 | 709 | return $categorized_posts; 710 | } 711 | 712 | /** 713 | * Returns the most recent n posts 714 | * @since 0.1.0 715 | * @param int $numberposts 716 | * @param boolean|array $exclude excludes current post if true 717 | * @return array 718 | */ 719 | public static function get_recent($numberposts, $exclude = true) { 720 | if ( !is_array($exclude) ) { 721 | if ( true === $exclude ) { 722 | $id = get_the_ID(); 723 | if ( false !== $id ) 724 | $exclude = array($id); 725 | } else 726 | $exclude = array(); 727 | } 728 | 729 | return static::get_controllers(array( 730 | 'post_type' => self::get_controller_post_type(get_called_class()), 731 | 'numberposts' => $numberposts, 732 | 'post__not_in'=> $exclude 733 | )); 734 | } 735 | 736 | /** 737 | * Retrieves random posts 738 | * 739 | * Retrieves a number of randomized posts. Use this instead of the 740 | * ORDER BY RAND method, as that can have tremendous overhead 741 | * @since 0.5.0 742 | * @param int $count the number of posts to return randomized 743 | * @param array|string $post_type (optional) post type name or array thereof 744 | * @return array array of controllers or empty array 745 | */ 746 | public static function random_posts($count = -1, $post_type = null) { 747 | $post_type = ( $post_type ) ? $post_type : self::get_controller_post_type(get_called_class()); 748 | 749 | $ids = get_posts(array( 750 | 'post_type' => $post_type, 751 | 'numberposts' => -1, 752 | 'fields' => 'ids' 753 | )); 754 | 755 | if ( empty($ids) ) return array(); 756 | 757 | shuffle($ids); 758 | 759 | if ( $count !== -1 ) 760 | $ids = array_slice($ids, 0, $count); 761 | 762 | return static::get_controllers(array( 763 | 'post_type' => $post_type, 764 | 'numberposts' => $count, 765 | 'post__in' => $ids 766 | )); 767 | } 768 | 769 | /** 770 | * Returns whether provided id belongs to post type 771 | * @param integer $id 772 | * @param string $type 773 | * @return boolean 774 | */ 775 | public static function is_post($id, $type = 'any') { 776 | $args = array( 777 | 'suppress_filters'=> false 778 | ,'post_type' => $type 779 | ,'fields' => 'ids' 780 | ,'posts_per_page' => 1 781 | ); 782 | 783 | if ( is_numeric($id) ) { 784 | $args['post__in'] = array($id); 785 | } else { 786 | $args['name'] = $id; 787 | } 788 | 789 | return ( !empty(get_posts($args)) ); 790 | } 791 | }; 792 | -------------------------------------------------------------------------------- /src/Term.php: -------------------------------------------------------------------------------- 1 | term_id, self::CACHE_GROUP); 71 | if ( false !== $term ) return $term; 72 | $term = $key; 73 | 74 | } elseif ( $key ) { 75 | if ( $field == 'id' ) { 76 | $term = wp_cache_get($key, self::CACHE_GROUP); 77 | if ( false !== $term ) return $term; 78 | } else { 79 | $term_id = wp_cache_get($key, self::CACHE_GROUP . '_' . $field); 80 | if ( false !== $term_id ) 81 | return wp_cache_get($term_id, self::CACHE_GROUP); 82 | } 83 | $term = get_term_by($field, $key, $taxonomy); 84 | 85 | } else { 86 | $term = get_queried_object(); 87 | if ( !isset($term->term_id) ) 88 | return new WP_Error('invalid_queried_object', 'The queried object is not a term', $term); 89 | 90 | $controller = wp_cache_get($term->term_id, self::CACHE_GROUP); 91 | if ( false !== $controller ) return $controller; 92 | } 93 | 94 | if ( false === $term ) { 95 | return $term; 96 | } 97 | 98 | // Construct, cache, and return term 99 | $controller_class = self::get_controller_class($term, $options); 100 | $controller = new $controller_class ($term); 101 | 102 | wp_cache_set($controller->id, $controller, self::CACHE_GROUP, MINUTE_IN_SECONDS * 10); 103 | wp_cache_set($controller->slug, $controller->id, self::CACHE_GROUP . '_' . 'slug', MINUTE_IN_SECONDS * 10); 104 | wp_cache_set($controller->name, $controller->id, self::CACHE_GROUP . '_' . 'name', MINUTE_IN_SECONDS * 10); 105 | wp_cache_set($controller->taxonomy_id, $controller->id, self::CACHE_GROUP . '_' . 'term_taxonomy_id', MINUTE_IN_SECONDS * 10); 106 | 107 | return $controller; 108 | } 109 | 110 | /** 111 | * Returns an array of Term controllers from either arguments for an array of ids. If an array of 112 | * ids is supplied, the second parameter is the taxonomy the ids belong to 113 | * @param array $args get_terms arguments or array of ids 114 | * @param string $taxonomy taxonomy for array of ids 115 | * @param array $options Controller options 116 | * @return array array of controllers 117 | */ 118 | public static function get_controllers($args, $taxonomy = '', $options = array()) { 119 | if ( isset($args[0]) || empty($args) ) { 120 | $terms = $args; 121 | } else { 122 | $terms = get_terms($args); 123 | 124 | if ( isset($args['fields']) && 'all' !== $args['fields'] ) { 125 | return $terms; 126 | } 127 | } 128 | 129 | $Terms = array(); 130 | foreach($terms as $term) { 131 | $Terms[] = self::get_controller($term, $taxonomy, $options); 132 | } 133 | 134 | return $Terms; 135 | } 136 | 137 | /** 138 | * Returns the class name for the corresponding term 139 | * @param WP_Term $term WP_Term to get the class for 140 | * @param array $options Array of options 141 | * @return string Class name 142 | */ 143 | private static function get_controller_class($term, $options = array()) { 144 | if ( !is_array(self::$_controller_taxonomies) ) { 145 | self::$_controller_taxonomies = array(); 146 | 147 | $taxonomies = get_taxonomies(array(), 'objects'); 148 | foreach($taxonomies as $taxonomy) { 149 | if ( !empty($taxonomy->wp_controller_class) ) { 150 | self::$_controller_taxonomies[$taxonomy->name] = $taxonomy->wp_controller_class; 151 | } 152 | } 153 | } 154 | 155 | $class = isset(self::$_controller_taxonomies[$term->taxonomy]) 156 | ? self::$_controller_taxonomies[$term->taxonomy] 157 | : __CLASS__; 158 | 159 | return apply_filters('wp_controllers_term_class', $class, $term, $options); 160 | } 161 | 162 | /** 163 | * Called when the cache for a term controller needs to be flushed. Calls the flush_cache static 164 | * method for the class the term belongs to. 165 | * @param WP_Term $term term object that needs to be invalidated 166 | * @param string $event event which triggered the flush 167 | */ 168 | public static function trigger_flush_cache($term, $event) { 169 | wp_cache_delete($term->term_id, self::CACHE_GROUP); 170 | wp_cache_delete($term->slug, self::CACHE_GROUP . '_slug'); 171 | wp_cache_delete($term->name, self::CACHE_GROUP . '_name'); 172 | wp_cache_delete($term->term_taxonomy_id, self::CACHE_GROUP . '_term_taxonomy_id'); 173 | 174 | $controller_class = self::get_controller_class($term); 175 | if ( $controller_class && method_exists($controller_class, 'flush_cache') ) { 176 | $controller_class::flush_cache($term, $event); 177 | } 178 | } 179 | 180 | /** 181 | * Returns the meta class to be used for the term 182 | * @param WP_Term $term Term the meta class will be used for 183 | * @return string Meta class 184 | */ 185 | protected static function get_meta_class($term) { 186 | return apply_filters('wp_controllers_term_meta_class', Meta::class, $term); 187 | } 188 | 189 | /** 190 | * Term constructor. 191 | * 192 | * @param object $term 193 | */ 194 | protected function __construct($term) { 195 | // Load all the term properties 196 | foreach(get_object_vars($term) as $key => $value) 197 | $this->$key = $value; 198 | 199 | // Extra properties 200 | $this->term =& $term; 201 | $this->id =& $term->term_id; 202 | $this->group =& $term->term_group; 203 | $this->taxonomy_id =& $term->term_taxonomy_id; 204 | 205 | // Meta class 206 | $meta_class = static::get_meta_class($term); 207 | $this->meta = new $meta_class($this->id, 'post'); 208 | } 209 | 210 | /** 211 | * Returns the term url 212 | * @return string|WP_Error 213 | */ 214 | public function url() { 215 | return isset($this->url) ? $this->url : ( $this->url = get_term_link($this->term) ); 216 | } 217 | 218 | /** 219 | * Returns the term name filtered by the standard filters 220 | * @return string filtered term name 221 | */ 222 | public function title() { 223 | switch($this->taxonomy) { 224 | case 'category': return apply_filters('single_cat_title', $this->name); 225 | case 'post_tag': return apply_filters('single_tag_title', $this->name); 226 | default: return apply_filters('single_term_title', $this->name); 227 | } 228 | } 229 | 230 | /** 231 | * Returns the parent term controller if there is one 232 | * @return Term|null controller if has parent 233 | */ 234 | public function parent() { 235 | return $this->parent ? self::get_controller($this->parent, $this->taxonomy, 'id') : null; 236 | } 237 | 238 | /** 239 | * Returns the children term controllers 240 | * @return array child term controllers 241 | */ 242 | public function children() { 243 | return self::get_controllers(array( 244 | 'taxonomy' => $this->taxonomy, 245 | 'child_of' => $this->id 246 | )); 247 | } 248 | 249 | /** 250 | * Returns the term description filtered by the_content 251 | * @return string filtered description 252 | */ 253 | public function description() { 254 | return apply_filters('the_content', $this->description); 255 | } 256 | 257 | /** 258 | * Returns the posts that have this term 259 | * @param string $post_type post type(s) to limit the query to; default: any 260 | * @param integer $count the number of posts to return; default: -1 261 | * @return Post[] Post controllers 262 | */ 263 | public function posts($post_type = 'any', $count = -1) { 264 | if ( !$this->count ) return array(); 265 | 266 | return Post::get_controllers(array( 267 | 'post_type' => $post_type, 268 | 'numberposts' => $count, 269 | 'tax_query' => array( 270 | array( 271 | 'taxonomy' => $this->taxonomy, 272 | 'terms' => $this->id 273 | ) 274 | ) 275 | )); 276 | } 277 | 278 | /** 279 | * @param $post_type 280 | * @return Post|null 281 | */ 282 | public function oldest_post($post_type) { 283 | if ( !$this->count ) return null; 284 | 285 | $Post = get_post_controllers(array( 286 | 'post_type' => $post_type, 287 | 'numberposts' => 1, 288 | 'orderby' => 'date', 289 | 'order' => 'ASC', 290 | 'tax_query' => array( 291 | array( 292 | 'taxonomy' => $this->taxonomy, 293 | 'terms' => $this->id 294 | ) 295 | ) 296 | )); 297 | 298 | return empty($Post) ? $Post[0] : null; 299 | } 300 | 301 | /** 302 | * Returns the distinct terms for an array of posts 303 | * 304 | * @param array $posts 305 | * @param string $fields 306 | * 307 | * @return array|null|object 308 | */ 309 | public static function distinct_post_terms(array $posts, $fields = '') { 310 | $ids = array(); 311 | foreach($posts as $post) { 312 | if ( is_numeric($post) ) 313 | $ids[] = absint($post); 314 | elseif ( is_a($post, 'WP_Post') ) 315 | $ids[] = $post->ID; 316 | elseif ( is_a($post, 'Post') ) 317 | $ids[] = $post->id; 318 | } 319 | 320 | if ( empty($ids) ) return array(); 321 | 322 | global $wpdb; 323 | $ids = array_map( 'intval', $ids ); 324 | $ids = implode(',', $ids); 325 | 326 | $query = " 327 | SELECT DISTINCT t.term_id, t.name, t.slug, t.term_group 328 | 329 | FROM `wp_terms` as t 330 | JOIN `wp_term_taxonomy` as tax ON t.term_id = tax.term_id 331 | JOIN `wp_term_relationships` as rel ON tax.term_taxonomy_id = rel.term_taxonomy_id 332 | 333 | WHERE rel.object_id IN ($ids); 334 | "; 335 | 336 | 337 | switch($fields) { 338 | case 'raw': 339 | return $wpdb->get_results($query, OBJECT_K); 340 | case 'ids': 341 | return $wpdb->get_col($query); 342 | default: 343 | $results = $wpdb->get_results($query); 344 | foreach($results as &$result) 345 | $result = self::get_controller($result); 346 | return $results; 347 | } 348 | } 349 | }; 350 | -------------------------------------------------------------------------------- /src/User.php: -------------------------------------------------------------------------------- 1 | true 72 | )); 73 | 74 | if ( is_object($key) ) { 75 | $user = wp_cache_get($key->ID, self::CACHE_GROUP); 76 | if ( false !== $user ) return $user; 77 | $user = $key; 78 | 79 | } elseif ( $key ) { 80 | // Retrieve user and check if cached 81 | if ( $field == 'id' ) { 82 | $user = wp_cache_get($key, self::CACHE_GROUP); 83 | if ( false !== $user ) return $user; 84 | 85 | } else { 86 | $user_id = wp_cache_get("{$key}_{$field}", self::CACHE_GROUP); 87 | if ( false !== $user_id ) 88 | return wp_cache_get($user_id, self::CACHE_GROUP); 89 | } 90 | 91 | $user = get_user_by($field, $key); 92 | 93 | if ( false === $user ) 94 | return new WP_Error('user_not_found', 'No user was found with the provided parameters', array('field' => $field, 'value' => $key)); 95 | 96 | } else { 97 | if ( !is_user_logged_in() ) 98 | return new WP_Error('user_not_logged_in', 'No user is logged in to return'); 99 | 100 | $user = wp_get_current_user(); 101 | 102 | $controller = wp_cache_get($user->ID, self::CACHE_GROUP); 103 | if ( false !== $controller ) return $controller; 104 | } 105 | 106 | $controller_class = self::get_controller_class($user); 107 | $controller = new $controller_class ($user, $options['load_standard_meta']); 108 | 109 | wp_cache_set($controller->id, $controller, self::CACHE_GROUP, MINUTE_IN_SECONDS * 10); 110 | wp_cache_set($controller->email, $controller->id, self::CACHE_GROUP . '_email', MINUTE_IN_SECONDS * 10); 111 | wp_cache_set($controller->nice_name, $controller->id, self::CACHE_GROUP . '_slug', MINUTE_IN_SECONDS * 10); 112 | wp_cache_set($controller->login, $controller->id, self::CACHE_GROUP . '_login', MINUTE_IN_SECONDS * 10); 113 | 114 | return $controller; 115 | } 116 | 117 | /** 118 | * Returns controllers for an array of users or wp_user_query arguments 119 | * @param array $args 120 | * @return array 121 | */ 122 | public static function get_controllers($args) { 123 | if ( isset($args[0]) && ( is_object($args[0]) || is_numeric($args[0]) ) ) { 124 | // Turn array of WP_User or id's into controllers 125 | $users = $args; 126 | 127 | } elseif ( !empty($args) ) { 128 | // Retrieve users via get_users function 129 | $users = get_users($args); 130 | 131 | } else { 132 | // Just return empty 133 | return array(); 134 | } 135 | 136 | $controllers = array(); 137 | foreach($users as $user) { 138 | $controllers[] = self::get_controller($user); 139 | } 140 | 141 | return $controllers; 142 | } 143 | 144 | /** 145 | * Retrieves the controller class for the WP_User instance 146 | * @param WP_User $user WP_User the controller is for 147 | * @return string Fully qualified controller class 148 | */ 149 | private static function get_controller_class($user) { 150 | return apply_filters('wp_controllers_user_class', __CLASS__, $user); 151 | } 152 | 153 | /** 154 | * Called when the cache for a user controller needs to be flushed. Calls the flush_cache static 155 | * method for the class the user belongs to. 156 | * @param WP_User $user user object that needs to be invalidated 157 | * @param string $event event which triggered the flush 158 | */ 159 | public static function trigger_flush_cache($user, $event) { 160 | wp_cache_delete($user->ID, self::CACHE_GROUP); 161 | wp_cache_delete($user->data->user_email, self::CACHE_GROUP . '_email'); 162 | wp_cache_delete($user->data->user_nicename, self::CACHE_GROUP . '_slug'); 163 | wp_cache_delete($user->data->user_login, self::CACHE_GROUP . '_login'); 164 | } 165 | 166 | /** 167 | * User constructor. 168 | * 169 | * @param WP_User $user 170 | * @param bool $load_extra 171 | */ 172 | protected function __construct($user, $load_extra) { 173 | $this->user = $user; 174 | 175 | $this->id =& $user->ID; 176 | $this->capabilities =& $user->caps; 177 | $this->roles =& $user->roles; 178 | $this->all_capabilities =& $user->allcaps; 179 | 180 | $this->display_name =& $user->data->display_name; 181 | $this->nice_name =& $user->data->user_nicename; 182 | 183 | $this->login =& $user->data->user_login; 184 | $this->email =& $user->data->user_email; 185 | $this->status =& $user->data->user_status; 186 | $this->registered =& $user->data->user_registered; 187 | 188 | if ( $load_extra ) { 189 | $this->first_name = $user->first_name; 190 | $this->last_name = $user->last_name; 191 | $this->description = $user->description; 192 | } 193 | 194 | // Meta class 195 | $this->meta = new Meta($this->id, 'user'); 196 | } 197 | 198 | /** 199 | * @param string $format 200 | * 201 | * @return bool|int|string 202 | */ 203 | public function registered($format) { 204 | return ( 'timestamp' === $format ) 205 | ? strtotime($this->registered) 206 | : date($format, strtotime($this->registered)); 207 | } 208 | 209 | /** 210 | * @return string 211 | */ 212 | public function posts_url() { 213 | return get_author_posts_url($this->id); 214 | } 215 | 216 | /** 217 | * @param string[]|string|null $post_types 218 | * 219 | * @return User[] 220 | */ 221 | public static function get_authors($post_types = null) { 222 | global $wpdb; 223 | 224 | if ( is_array($post_types) ) { 225 | $place_holders = implode(',', array_fill(0, count($post_types), '%s')); 226 | $where = $wpdb->prepare("AND P.post_type IN ($place_holders)", $post_types); 227 | } else if ( $post_types ) { 228 | $where = $wpdb->prepare("AND P.post_type = %s", $post_types); 229 | } else 230 | $where = ''; 231 | 232 | $author_ids = $wpdb->get_col(" 233 | SELECT U.ID 234 | 235 | FROM $wpdb->users AS U 236 | JOIN $wpdb->posts AS P ON U.ID = P.post_author 237 | 238 | WHERE P.post_status = 'publish' 239 | $where 240 | 241 | GROUP BY U.ID; 242 | "); 243 | 244 | $authors = array(); 245 | foreach($author_ids as $index => $id) 246 | $authors[] = self::get_controller($id); 247 | 248 | return $authors; 249 | } 250 | 251 | /** 252 | * @param int[] $user_ids 253 | * @param null $post_type 254 | * 255 | * @return array 256 | */ 257 | public static function get_users_post_count($user_ids, $post_type = null) { 258 | $user_ids = ( $user_ids ) ? $user_ids : get_users(); 259 | $post_type = ( $post_type ) ? $post_type : get_post_types(); 260 | 261 | if ( is_array($post_type) && (count($post_type) > 1) ) { 262 | $results = array(); 263 | foreach($post_type as $type) { 264 | $counts = count_many_users_posts($user_ids, $type); 265 | 266 | foreach($counts as $user_id => $count) 267 | $results[$user_id][$type] = (integer) $count; 268 | } 269 | 270 | return $results; 271 | 272 | } else { 273 | $post_type = ( is_array($post_type) ) ? $post_type[0] : $post_type; 274 | return count_many_users_posts($user_ids, $post_type); 275 | } 276 | } 277 | }; 278 | -------------------------------------------------------------------------------- /tests/bin/install-wp-tests.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | if [ $# -lt 3 ]; then 4 | echo "usage: $0 [db-host] [wp-version] [skip-database-creation]" 5 | exit 1 6 | fi 7 | 8 | DB_NAME=$1 9 | DB_USER=$2 10 | DB_PASS=$3 11 | DB_HOST=${4-localhost} 12 | WP_VERSION=${5-latest} 13 | SKIP_DB_CREATE=${6-false} 14 | 15 | TMPDIR=${TMPDIR-/tmp} 16 | TMPDIR=$(echo $TMPDIR | sed -e "s/\/$//") 17 | WP_TESTS_DIR=${WP_TESTS_DIR-$TMPDIR/wordpress-tests-lib} 18 | WP_CORE_DIR=${WP_CORE_DIR-$TMPDIR/wordpress/} 19 | 20 | download() { 21 | if [ `which curl` ]; then 22 | curl -s "$1" > "$2"; 23 | elif [ `which wget` ]; then 24 | wget -nv -O "$2" "$1" 25 | fi 26 | } 27 | 28 | if [[ $WP_VERSION =~ ^[0-9]+\.[0-9]+$ ]]; then 29 | WP_TESTS_TAG="branches/$WP_VERSION" 30 | elif [[ $WP_VERSION =~ [0-9]+\.[0-9]+\.[0-9]+ ]]; then 31 | if [[ $WP_VERSION =~ [0-9]+\.[0-9]+\.[0] ]]; then 32 | # version x.x.0 means the first release of the major version, so strip off the .0 and download version x.x 33 | WP_TESTS_TAG="tags/${WP_VERSION%??}" 34 | else 35 | WP_TESTS_TAG="tags/$WP_VERSION" 36 | fi 37 | elif [[ $WP_VERSION == 'nightly' || $WP_VERSION == 'trunk' ]]; then 38 | WP_TESTS_TAG="trunk" 39 | else 40 | # http serves a single offer, whereas https serves multiple. we only want one 41 | download http://api.wordpress.org/core/version-check/1.7/ /tmp/wp-latest.json 42 | grep '[0-9]+\.[0-9]+(\.[0-9]+)?' /tmp/wp-latest.json 43 | LATEST_VERSION=$(grep -o '"version":"[^"]*' /tmp/wp-latest.json | sed 's/"version":"//') 44 | if [[ -z "$LATEST_VERSION" ]]; then 45 | echo "Latest WordPress version could not be found" 46 | exit 1 47 | fi 48 | WP_TESTS_TAG="tags/$LATEST_VERSION" 49 | fi 50 | 51 | set -ex 52 | 53 | install_wp() { 54 | 55 | if [ -d $WP_CORE_DIR ]; then 56 | return; 57 | fi 58 | 59 | mkdir -p $WP_CORE_DIR 60 | 61 | if [[ $WP_VERSION == 'nightly' || $WP_VERSION == 'trunk' ]]; then 62 | mkdir -p $TMPDIR/wordpress-nightly 63 | download https://wordpress.org/nightly-builds/wordpress-latest.zip $TMPDIR/wordpress-nightly/wordpress-nightly.zip 64 | unzip -q $TMPDIR/wordpress-nightly/wordpress-nightly.zip -d $TMPDIR/wordpress-nightly/ 65 | mv $TMPDIR/wordpress-nightly/wordpress/* $WP_CORE_DIR 66 | else 67 | if [ $WP_VERSION == 'latest' ]; then 68 | local ARCHIVE_NAME='latest' 69 | elif [[ $WP_VERSION =~ [0-9]+\.[0-9]+ ]]; then 70 | # https serves multiple offers, whereas http serves single. 71 | download https://api.wordpress.org/core/version-check/1.7/ $TMPDIR/wp-latest.json 72 | if [[ $WP_VERSION =~ [0-9]+\.[0-9]+\.[0] ]]; then 73 | # version x.x.0 means the first release of the major version, so strip off the .0 and download version x.x 74 | LATEST_VERSION=${WP_VERSION%??} 75 | else 76 | # otherwise, scan the releases and get the most up to date minor version of the major release 77 | local VERSION_ESCAPED=`echo $WP_VERSION | sed 's/\./\\\\./g'` 78 | LATEST_VERSION=$(grep -o '"version":"'$VERSION_ESCAPED'[^"]*' $TMPDIR/wp-latest.json | sed 's/"version":"//' | head -1) 79 | fi 80 | if [[ -z "$LATEST_VERSION" ]]; then 81 | local ARCHIVE_NAME="wordpress-$WP_VERSION" 82 | else 83 | local ARCHIVE_NAME="wordpress-$LATEST_VERSION" 84 | fi 85 | else 86 | local ARCHIVE_NAME="wordpress-$WP_VERSION" 87 | fi 88 | download https://wordpress.org/${ARCHIVE_NAME}.tar.gz $TMPDIR/wordpress.tar.gz 89 | tar --strip-components=1 -zxmf $TMPDIR/wordpress.tar.gz -C $WP_CORE_DIR 90 | fi 91 | 92 | download https://raw.github.com/markoheijnen/wp-mysqli/master/db.php $WP_CORE_DIR/wp-content/db.php 93 | } 94 | 95 | install_test_suite() { 96 | # portable in-place argument for both GNU sed and Mac OSX sed 97 | if [[ $(uname -s) == 'Darwin' ]]; then 98 | local ioption='-i .bak' 99 | else 100 | local ioption='-i' 101 | fi 102 | 103 | # set up testing suite if it doesn't yet exist 104 | if [ ! -d $WP_TESTS_DIR ]; then 105 | # set up testing suite 106 | mkdir -p $WP_TESTS_DIR 107 | svn co --quiet https://develop.svn.wordpress.org/${WP_TESTS_TAG}/tests/phpunit/includes/ $WP_TESTS_DIR/includes 108 | svn co --quiet https://develop.svn.wordpress.org/${WP_TESTS_TAG}/tests/phpunit/data/ $WP_TESTS_DIR/data 109 | fi 110 | 111 | if [ ! -f wp-tests-config.php ]; then 112 | download https://develop.svn.wordpress.org/${WP_TESTS_TAG}/wp-tests-config-sample.php "$WP_TESTS_DIR"/wp-tests-config.php 113 | # remove all forward slashes in the end 114 | WP_CORE_DIR=$(echo $WP_CORE_DIR | sed "s:/\+$::") 115 | sed $ioption "s:dirname( __FILE__ ) . '/src/':'$WP_CORE_DIR/':" "$WP_TESTS_DIR"/wp-tests-config.php 116 | sed $ioption "s/youremptytestdbnamehere/$DB_NAME/" "$WP_TESTS_DIR"/wp-tests-config.php 117 | sed $ioption "s/yourusernamehere/$DB_USER/" "$WP_TESTS_DIR"/wp-tests-config.php 118 | sed $ioption "s/yourpasswordhere/$DB_PASS/" "$WP_TESTS_DIR"/wp-tests-config.php 119 | sed $ioption "s|localhost|${DB_HOST}|" "$WP_TESTS_DIR"/wp-tests-config.php 120 | fi 121 | 122 | } 123 | 124 | install_db() { 125 | 126 | if [ ${SKIP_DB_CREATE} = "true" ]; then 127 | return 0 128 | fi 129 | 130 | # parse DB_HOST for port or socket references 131 | local PARTS=(${DB_HOST//\:/ }) 132 | local DB_HOSTNAME=${PARTS[0]}; 133 | local DB_SOCK_OR_PORT=${PARTS[1]}; 134 | local EXTRA="" 135 | 136 | if ! [ -z $DB_HOSTNAME ] ; then 137 | if [ $(echo $DB_SOCK_OR_PORT | grep -e '^[0-9]\{1,\}$') ]; then 138 | EXTRA=" --host=$DB_HOSTNAME --port=$DB_SOCK_OR_PORT --protocol=tcp" 139 | elif ! [ -z $DB_SOCK_OR_PORT ] ; then 140 | EXTRA=" --socket=$DB_SOCK_OR_PORT" 141 | elif ! [ -z $DB_HOSTNAME ] ; then 142 | EXTRA=" --host=$DB_HOSTNAME --protocol=tcp" 143 | fi 144 | fi 145 | 146 | # create database 147 | mysqladmin create $DB_NAME --user="$DB_USER" --password="$DB_PASS"$EXTRA 148 | } 149 | 150 | install_wp 151 | install_test_suite 152 | install_db 153 | -------------------------------------------------------------------------------- /tests/bootstrap.php: -------------------------------------------------------------------------------- 1 | factory()->post->create_and_get(); 8 | 9 | // Get controller by WP_Post 10 | $controller = Post::get_controller($post); 11 | $this->assertInstanceOf(Post::class, $controller); 12 | 13 | // Get controller by id 14 | $controller = Post::get_controller($post->ID); 15 | $this->assertInstanceOf(Post::class, $controller); 16 | 17 | // Get controller by slug 18 | $controller = Post::get_controller($post->post_name); 19 | $this->assertInstanceOf(Post::class, $controller); 20 | 21 | // Get controller by single template 22 | $this->go_to("/p={$post->ID}"); 23 | $controller = Post::get_controller(); 24 | $this->assertInstanceOf(Post::class, $controller); 25 | $this->assertEquals($post->ID, $controller->id); 26 | } 27 | 28 | public function test_get_controllers() { 29 | $posts = $this->factory()->post->create_many(2); 30 | 31 | // Array of posts 32 | $controllers = Post::get_controllers($posts); 33 | $this->assertEquals($posts, wp_list_pluck($controllers, 'id')); 34 | 35 | // Query 36 | $controllers = Post::get_controllers([ 37 | 'post_type' => 'post', 38 | 'order' => 'ASC' 39 | ]); 40 | $this->assertEquals($posts, wp_list_pluck($controllers, 'id')); 41 | 42 | // Something unexpected 43 | $controllers = Post::get_controllers(false); 44 | $this->assertEmpty($controllers); 45 | 46 | // Get controllers by posts archive 47 | $this->go_to(get_post_type_archive_link('post')); 48 | $controllers = Post::get_controllers(); 49 | $controller_ids = wp_list_pluck($controllers, 'id'); 50 | sort($posts); 51 | sort($controller_ids); 52 | $this->assertEquals($posts, $controller_ids); 53 | } 54 | 55 | public function test_get_controller_post_type() { 56 | // Root level post 57 | $this->assertSame('post', Post::get_controller_post_type(Post::class)); 58 | 59 | // Child post 60 | $this->assertSame('page', Post::get_controller_post_type(Page::class)); 61 | 62 | // TODO: Test classes with no explicit post type 63 | // TODO: Test template classes 64 | } 65 | 66 | public function test_wp_post_properties() { 67 | $post = $this->factory()->post->create_and_get(); 68 | $controller = Post::get_controller($post); 69 | 70 | $properties = get_object_vars($post); 71 | foreach($properties as $key => $value) { 72 | $this->assertSame($value, $controller->$key, "Post controller should support the WP_Post->$key property"); 73 | } 74 | } 75 | 76 | public function test_url() { 77 | $post = $this->factory()->post->create_and_get(); 78 | $controller = Post::get_controller($post); 79 | 80 | $this->assertSame(get_permalink($post), $controller->url()); 81 | } 82 | 83 | public function test_archive_url() { 84 | $this->assertEquals(get_post_type_archive_link('post'), Post::archive_url()); 85 | } 86 | 87 | public function test_author() { 88 | $user = $this->factory()->user->create_and_get(); 89 | $post = $this->factory()->post->create_and_get([ 90 | 'post_author' => $user->ID 91 | ]); 92 | 93 | $post_controller = Post::get_controller($post); 94 | $user_controller = $post_controller->author(); 95 | 96 | $this->assertEquals($user->ID, $user_controller->id); 97 | } 98 | 99 | public function test_date() { 100 | $post = $this->factory()->post->create_and_get(); 101 | $controller = Post::get_controller($post); 102 | 103 | // Local timezone 104 | $timestamp = strtotime($post->post_date); 105 | $this->assertSame($timestamp, $controller->date('timestamp')); 106 | $this->assertSame(date('d:m:Y', $timestamp), $controller->date('d:m:Y')); 107 | 108 | // GMT 109 | $timestamp = strtotime($post->post_date_gmt); 110 | $this->assertSame($timestamp, $controller->date('timestamp', true)); 111 | $this->assertSame(date('d:m:Y', $timestamp), $controller->date('d:m:Y', true)); 112 | } 113 | 114 | public function test_modified() { 115 | $post = $this->factory()->post->create_and_get(); 116 | $controller = Post::get_controller($post); 117 | 118 | // Local timezone 119 | $timestamp = strtotime($post->post_modified); 120 | $this->assertSame($timestamp, $controller->modified('timestamp')); 121 | $this->assertSame(date('d:m:Y', $timestamp), $controller->modified('d:m:Y')); 122 | 123 | // GMT 124 | $timestamp = strtotime($post->post_modified_gmt); 125 | $this->assertSame($timestamp, $controller->modified('timestamp', true)); 126 | $this->assertSame(date('d:m:Y', $timestamp), $controller->modified('d:m:Y', true)); 127 | } 128 | 129 | public function test_terms() { 130 | $term1 = $this->factory()->term->create_and_get(); 131 | $term2 = $this->factory()->term->create_and_get(); 132 | $term3 = $this->factory()->term->create_and_get(); 133 | 134 | // Post with multiple terms 135 | $post = $this->factory()->post->create_and_get(); 136 | wp_set_post_terms($post->ID, [ $term1->term_id, $term2->term_id ], 'post_tag'); 137 | 138 | $controller = Post::get_controller($post); 139 | $terms = $controller->terms('post_tag'); 140 | 141 | $this->assertSame(2, count($terms)); 142 | $this->assertSame([$term1->term_id, $term2->term_id], wp_list_pluck($terms, 'id')); 143 | 144 | // Post wih no terms 145 | $post = $this->factory()->post->create_and_get(); 146 | $controller = Post::get_controller($post); 147 | 148 | $this->assertEmpty($controller->terms('post_tag')); 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /tests/test-term-controller.php: -------------------------------------------------------------------------------- 1 | factory()->term->create_and_get(); 8 | $controller = Term::get_controller($term); 9 | 10 | $this->assertSame(get_term_link($term), $controller->url()); 11 | } 12 | 13 | public function test_parent() { 14 | $parent = $this->factory()->term->create_and_get(); 15 | $child = $this->factory()->term->create_and_get([ 16 | 'parent' => $parent->term_id 17 | ]); 18 | 19 | $controller = Term::get_controller($child); 20 | $parent_controller = $controller->parent(); 21 | 22 | $this->assertSame($parent->term_id, $parent_controller->id); 23 | } 24 | 25 | public function test_children() { 26 | $parent = $this->factory()->term->create_and_get(); 27 | $children = $this->factory()->term->create_many(2, [ 28 | 'parent' => $parent->term_id 29 | ]); 30 | 31 | $controller = Term::get_controller($parent); 32 | $children = $controller->children(); 33 | 34 | $this->assertSame($children, wp_list_pluck($controller->children(), 'term_id')); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /vendor/autoload.php: -------------------------------------------------------------------------------- 1 | 7 | * Jordi Boggiano 8 | * 9 | * For the full copyright and license information, please view the LICENSE 10 | * file that was distributed with this source code. 11 | */ 12 | 13 | namespace Composer\Autoload; 14 | 15 | /** 16 | * ClassLoader implements a PSR-0, PSR-4 and classmap class loader. 17 | * 18 | * $loader = new \Composer\Autoload\ClassLoader(); 19 | * 20 | * // register classes with namespaces 21 | * $loader->add('Symfony\Component', __DIR__.'/component'); 22 | * $loader->add('Symfony', __DIR__.'/framework'); 23 | * 24 | * // activate the autoloader 25 | * $loader->register(); 26 | * 27 | * // to enable searching the include path (eg. for PEAR packages) 28 | * $loader->setUseIncludePath(true); 29 | * 30 | * In this example, if you try to use a class in the Symfony\Component 31 | * namespace or one of its children (Symfony\Component\Console for instance), 32 | * the autoloader will first look for the class under the component/ 33 | * directory, and it will then fallback to the framework/ directory if not 34 | * found before giving up. 35 | * 36 | * This class is loosely based on the Symfony UniversalClassLoader. 37 | * 38 | * @author Fabien Potencier 39 | * @author Jordi Boggiano 40 | * @see http://www.php-fig.org/psr/psr-0/ 41 | * @see http://www.php-fig.org/psr/psr-4/ 42 | */ 43 | class ClassLoader 44 | { 45 | // PSR-4 46 | private $prefixLengthsPsr4 = array(); 47 | private $prefixDirsPsr4 = array(); 48 | private $fallbackDirsPsr4 = array(); 49 | 50 | // PSR-0 51 | private $prefixesPsr0 = array(); 52 | private $fallbackDirsPsr0 = array(); 53 | 54 | private $useIncludePath = false; 55 | private $classMap = array(); 56 | private $classMapAuthoritative = false; 57 | private $missingClasses = array(); 58 | private $apcuPrefix; 59 | 60 | public function getPrefixes() 61 | { 62 | if (!empty($this->prefixesPsr0)) { 63 | return call_user_func_array('array_merge', $this->prefixesPsr0); 64 | } 65 | 66 | return array(); 67 | } 68 | 69 | public function getPrefixesPsr4() 70 | { 71 | return $this->prefixDirsPsr4; 72 | } 73 | 74 | public function getFallbackDirs() 75 | { 76 | return $this->fallbackDirsPsr0; 77 | } 78 | 79 | public function getFallbackDirsPsr4() 80 | { 81 | return $this->fallbackDirsPsr4; 82 | } 83 | 84 | public function getClassMap() 85 | { 86 | return $this->classMap; 87 | } 88 | 89 | /** 90 | * @param array $classMap Class to filename map 91 | */ 92 | public function addClassMap(array $classMap) 93 | { 94 | if ($this->classMap) { 95 | $this->classMap = array_merge($this->classMap, $classMap); 96 | } else { 97 | $this->classMap = $classMap; 98 | } 99 | } 100 | 101 | /** 102 | * Registers a set of PSR-0 directories for a given prefix, either 103 | * appending or prepending to the ones previously set for this prefix. 104 | * 105 | * @param string $prefix The prefix 106 | * @param array|string $paths The PSR-0 root directories 107 | * @param bool $prepend Whether to prepend the directories 108 | */ 109 | public function add($prefix, $paths, $prepend = false) 110 | { 111 | if (!$prefix) { 112 | if ($prepend) { 113 | $this->fallbackDirsPsr0 = array_merge( 114 | (array) $paths, 115 | $this->fallbackDirsPsr0 116 | ); 117 | } else { 118 | $this->fallbackDirsPsr0 = array_merge( 119 | $this->fallbackDirsPsr0, 120 | (array) $paths 121 | ); 122 | } 123 | 124 | return; 125 | } 126 | 127 | $first = $prefix[0]; 128 | if (!isset($this->prefixesPsr0[$first][$prefix])) { 129 | $this->prefixesPsr0[$first][$prefix] = (array) $paths; 130 | 131 | return; 132 | } 133 | if ($prepend) { 134 | $this->prefixesPsr0[$first][$prefix] = array_merge( 135 | (array) $paths, 136 | $this->prefixesPsr0[$first][$prefix] 137 | ); 138 | } else { 139 | $this->prefixesPsr0[$first][$prefix] = array_merge( 140 | $this->prefixesPsr0[$first][$prefix], 141 | (array) $paths 142 | ); 143 | } 144 | } 145 | 146 | /** 147 | * Registers a set of PSR-4 directories for a given namespace, either 148 | * appending or prepending to the ones previously set for this namespace. 149 | * 150 | * @param string $prefix The prefix/namespace, with trailing '\\' 151 | * @param array|string $paths The PSR-4 base directories 152 | * @param bool $prepend Whether to prepend the directories 153 | * 154 | * @throws \InvalidArgumentException 155 | */ 156 | public function addPsr4($prefix, $paths, $prepend = false) 157 | { 158 | if (!$prefix) { 159 | // Register directories for the root namespace. 160 | if ($prepend) { 161 | $this->fallbackDirsPsr4 = array_merge( 162 | (array) $paths, 163 | $this->fallbackDirsPsr4 164 | ); 165 | } else { 166 | $this->fallbackDirsPsr4 = array_merge( 167 | $this->fallbackDirsPsr4, 168 | (array) $paths 169 | ); 170 | } 171 | } elseif (!isset($this->prefixDirsPsr4[$prefix])) { 172 | // Register directories for a new namespace. 173 | $length = strlen($prefix); 174 | if ('\\' !== $prefix[$length - 1]) { 175 | throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); 176 | } 177 | $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; 178 | $this->prefixDirsPsr4[$prefix] = (array) $paths; 179 | } elseif ($prepend) { 180 | // Prepend directories for an already registered namespace. 181 | $this->prefixDirsPsr4[$prefix] = array_merge( 182 | (array) $paths, 183 | $this->prefixDirsPsr4[$prefix] 184 | ); 185 | } else { 186 | // Append directories for an already registered namespace. 187 | $this->prefixDirsPsr4[$prefix] = array_merge( 188 | $this->prefixDirsPsr4[$prefix], 189 | (array) $paths 190 | ); 191 | } 192 | } 193 | 194 | /** 195 | * Registers a set of PSR-0 directories for a given prefix, 196 | * replacing any others previously set for this prefix. 197 | * 198 | * @param string $prefix The prefix 199 | * @param array|string $paths The PSR-0 base directories 200 | */ 201 | public function set($prefix, $paths) 202 | { 203 | if (!$prefix) { 204 | $this->fallbackDirsPsr0 = (array) $paths; 205 | } else { 206 | $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths; 207 | } 208 | } 209 | 210 | /** 211 | * Registers a set of PSR-4 directories for a given namespace, 212 | * replacing any others previously set for this namespace. 213 | * 214 | * @param string $prefix The prefix/namespace, with trailing '\\' 215 | * @param array|string $paths The PSR-4 base directories 216 | * 217 | * @throws \InvalidArgumentException 218 | */ 219 | public function setPsr4($prefix, $paths) 220 | { 221 | if (!$prefix) { 222 | $this->fallbackDirsPsr4 = (array) $paths; 223 | } else { 224 | $length = strlen($prefix); 225 | if ('\\' !== $prefix[$length - 1]) { 226 | throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); 227 | } 228 | $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; 229 | $this->prefixDirsPsr4[$prefix] = (array) $paths; 230 | } 231 | } 232 | 233 | /** 234 | * Turns on searching the include path for class files. 235 | * 236 | * @param bool $useIncludePath 237 | */ 238 | public function setUseIncludePath($useIncludePath) 239 | { 240 | $this->useIncludePath = $useIncludePath; 241 | } 242 | 243 | /** 244 | * Can be used to check if the autoloader uses the include path to check 245 | * for classes. 246 | * 247 | * @return bool 248 | */ 249 | public function getUseIncludePath() 250 | { 251 | return $this->useIncludePath; 252 | } 253 | 254 | /** 255 | * Turns off searching the prefix and fallback directories for classes 256 | * that have not been registered with the class map. 257 | * 258 | * @param bool $classMapAuthoritative 259 | */ 260 | public function setClassMapAuthoritative($classMapAuthoritative) 261 | { 262 | $this->classMapAuthoritative = $classMapAuthoritative; 263 | } 264 | 265 | /** 266 | * Should class lookup fail if not found in the current class map? 267 | * 268 | * @return bool 269 | */ 270 | public function isClassMapAuthoritative() 271 | { 272 | return $this->classMapAuthoritative; 273 | } 274 | 275 | /** 276 | * APCu prefix to use to cache found/not-found classes, if the extension is enabled. 277 | * 278 | * @param string|null $apcuPrefix 279 | */ 280 | public function setApcuPrefix($apcuPrefix) 281 | { 282 | $this->apcuPrefix = function_exists('apcu_fetch') && ini_get('apc.enabled') ? $apcuPrefix : null; 283 | } 284 | 285 | /** 286 | * The APCu prefix in use, or null if APCu caching is not enabled. 287 | * 288 | * @return string|null 289 | */ 290 | public function getApcuPrefix() 291 | { 292 | return $this->apcuPrefix; 293 | } 294 | 295 | /** 296 | * Registers this instance as an autoloader. 297 | * 298 | * @param bool $prepend Whether to prepend the autoloader or not 299 | */ 300 | public function register($prepend = false) 301 | { 302 | spl_autoload_register(array($this, 'loadClass'), true, $prepend); 303 | } 304 | 305 | /** 306 | * Unregisters this instance as an autoloader. 307 | */ 308 | public function unregister() 309 | { 310 | spl_autoload_unregister(array($this, 'loadClass')); 311 | } 312 | 313 | /** 314 | * Loads the given class or interface. 315 | * 316 | * @param string $class The name of the class 317 | * @return bool|null True if loaded, null otherwise 318 | */ 319 | public function loadClass($class) 320 | { 321 | if ($file = $this->findFile($class)) { 322 | includeFile($file); 323 | 324 | return true; 325 | } 326 | } 327 | 328 | /** 329 | * Finds the path to the file where the class is defined. 330 | * 331 | * @param string $class The name of the class 332 | * 333 | * @return string|false The path if found, false otherwise 334 | */ 335 | public function findFile($class) 336 | { 337 | // class map lookup 338 | if (isset($this->classMap[$class])) { 339 | return $this->classMap[$class]; 340 | } 341 | if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) { 342 | return false; 343 | } 344 | if (null !== $this->apcuPrefix) { 345 | $file = apcu_fetch($this->apcuPrefix.$class, $hit); 346 | if ($hit) { 347 | return $file; 348 | } 349 | } 350 | 351 | $file = $this->findFileWithExtension($class, '.php'); 352 | 353 | // Search for Hack files if we are running on HHVM 354 | if (false === $file && defined('HHVM_VERSION')) { 355 | $file = $this->findFileWithExtension($class, '.hh'); 356 | } 357 | 358 | if (null !== $this->apcuPrefix) { 359 | apcu_add($this->apcuPrefix.$class, $file); 360 | } 361 | 362 | if (false === $file) { 363 | // Remember that this class does not exist. 364 | $this->missingClasses[$class] = true; 365 | } 366 | 367 | return $file; 368 | } 369 | 370 | private function findFileWithExtension($class, $ext) 371 | { 372 | // PSR-4 lookup 373 | $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext; 374 | 375 | $first = $class[0]; 376 | if (isset($this->prefixLengthsPsr4[$first])) { 377 | $subPath = $class; 378 | while (false !== $lastPos = strrpos($subPath, '\\')) { 379 | $subPath = substr($subPath, 0, $lastPos); 380 | $search = $subPath.'\\'; 381 | if (isset($this->prefixDirsPsr4[$search])) { 382 | foreach ($this->prefixDirsPsr4[$search] as $dir) { 383 | $length = $this->prefixLengthsPsr4[$first][$search]; 384 | if (file_exists($file = $dir . DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $length))) { 385 | return $file; 386 | } 387 | } 388 | } 389 | } 390 | } 391 | 392 | // PSR-4 fallback dirs 393 | foreach ($this->fallbackDirsPsr4 as $dir) { 394 | if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) { 395 | return $file; 396 | } 397 | } 398 | 399 | // PSR-0 lookup 400 | if (false !== $pos = strrpos($class, '\\')) { 401 | // namespaced class name 402 | $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1) 403 | . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR); 404 | } else { 405 | // PEAR-like class name 406 | $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext; 407 | } 408 | 409 | if (isset($this->prefixesPsr0[$first])) { 410 | foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) { 411 | if (0 === strpos($class, $prefix)) { 412 | foreach ($dirs as $dir) { 413 | if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { 414 | return $file; 415 | } 416 | } 417 | } 418 | } 419 | } 420 | 421 | // PSR-0 fallback dirs 422 | foreach ($this->fallbackDirsPsr0 as $dir) { 423 | if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { 424 | return $file; 425 | } 426 | } 427 | 428 | // PSR-0 include paths. 429 | if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) { 430 | return $file; 431 | } 432 | 433 | return false; 434 | } 435 | } 436 | 437 | /** 438 | * Scope isolated include. 439 | * 440 | * Prevents access to $this/self from included files. 441 | */ 442 | function includeFile($file) 443 | { 444 | include $file; 445 | } 446 | -------------------------------------------------------------------------------- /vendor/composer/LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Copyright (c) Nils Adermann, Jordi Boggiano 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is furnished 9 | to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | THE SOFTWARE. 21 | 22 | -------------------------------------------------------------------------------- /vendor/composer/autoload_classmap.php: -------------------------------------------------------------------------------- 1 | array($baseDir . '/src'), 10 | ); 11 | -------------------------------------------------------------------------------- /vendor/composer/autoload_real.php: -------------------------------------------------------------------------------- 1 | = 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded()); 27 | if ($useStaticLoader) { 28 | require_once __DIR__ . '/autoload_static.php'; 29 | 30 | call_user_func(\Composer\Autoload\ComposerStaticInit1d6a803d906d480e35087e781c0c01c8::getInitializer($loader)); 31 | } else { 32 | $map = require __DIR__ . '/autoload_namespaces.php'; 33 | foreach ($map as $namespace => $path) { 34 | $loader->set($namespace, $path); 35 | } 36 | 37 | $map = require __DIR__ . '/autoload_psr4.php'; 38 | foreach ($map as $namespace => $path) { 39 | $loader->setPsr4($namespace, $path); 40 | } 41 | 42 | $classMap = require __DIR__ . '/autoload_classmap.php'; 43 | if ($classMap) { 44 | $loader->addClassMap($classMap); 45 | } 46 | } 47 | 48 | $loader->register(true); 49 | 50 | return $loader; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /vendor/composer/autoload_static.php: -------------------------------------------------------------------------------- 1 | 11 | array ( 12 | 'WPControllers\\' => 14, 13 | ), 14 | ); 15 | 16 | public static $prefixDirsPsr4 = array ( 17 | 'WPControllers\\' => 18 | array ( 19 | 0 => __DIR__ . '/../..' . '/src', 20 | ), 21 | ); 22 | 23 | public static function getInitializer(ClassLoader $loader) 24 | { 25 | return \Closure::bind(function () use ($loader) { 26 | $loader->prefixLengthsPsr4 = ComposerStaticInit1d6a803d906d480e35087e781c0c01c8::$prefixLengthsPsr4; 27 | $loader->prefixDirsPsr4 = ComposerStaticInit1d6a803d906d480e35087e781c0c01c8::$prefixDirsPsr4; 28 | 29 | }, null, ClassLoader::class); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /wp-controllers.php: -------------------------------------------------------------------------------- 1 | register(); 24 | --------------------------------------------------------------------------------