├── .gitignore ├── .jshintrc ├── LICENSE.txt ├── README.md ├── apigen.neon ├── composer.json ├── gulpfile.js ├── package.json └── src ├── Tev ├── Application │ ├── Application.php │ └── Bootstrap │ │ ├── AuthorFactory.php │ │ ├── FieldFactory.php │ │ ├── PluginLoader.php │ │ ├── Post.php │ │ ├── Taxonomy.php │ │ ├── Term.php │ │ └── Util.php ├── Author │ ├── Factory.php │ └── Model │ │ └── Author.php ├── Contracts │ ├── BootstrapperInterface.php │ └── WordpressWrapperInterface.php ├── Database │ ├── CustomTables │ │ └── AbstractInstaller.php │ └── Utils.php ├── Field │ ├── Factory.php │ ├── Model │ │ ├── AbstractField.php │ │ ├── AuthorField.php │ │ ├── BasicField.php │ │ ├── DateField.php │ │ ├── FileField.php │ │ ├── FlexibleContentField.php │ │ ├── GalleryField.php │ │ ├── GoogleMapField.php │ │ ├── ImageField.php │ │ ├── NullField.php │ │ ├── NumberField.php │ │ ├── PostField.php │ │ ├── RepeaterField.php │ │ ├── SelectField.php │ │ └── TaxonomyField.php │ └── Util │ │ ├── FieldGroup.php │ │ └── LayoutFieldGroup.php ├── Plugin │ ├── Action │ │ └── AbstractProvider.php │ ├── Loader.php │ └── Shortcode │ │ └── AbstractProvider.php ├── Post │ ├── Factory.php │ ├── Model │ │ ├── AbstractPost.php │ │ ├── Attachment.php │ │ ├── Page.php │ │ └── Post.php │ └── Repository │ │ └── PostRepository.php ├── Taxonomy │ ├── Factory.php │ ├── Model │ │ └── Taxonomy.php │ └── Repository │ │ └── TaxonomyRepository.php ├── Term │ ├── Factory.php │ ├── Model │ │ └── Term.php │ └── Repository │ │ └── TermRepository.php ├── Util │ └── TemplateExtras.php └── View │ ├── Exception │ └── NotFoundException.php │ └── Renderer.php └── helpers.php /.gitignore: -------------------------------------------------------------------------------- 1 | # Composer 2 | 3 | vendor/* 4 | composer.lock 5 | bin/ 6 | 7 | # NPM 8 | 9 | node_modules/ 10 | 11 | # Misc. 12 | 13 | docs/ 14 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "asi": true 3 | , "browser": true 4 | , "camelcase": true 5 | , "indent": 4 6 | , "laxbreak": true 7 | , "laxcomma": true 8 | , "node": true 9 | , "quotmark": "single" 10 | , "strict": false 11 | , "undef": true 12 | , "unused": true 13 | } 14 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 3ev 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | #3ev Core Wordpress library 2 | 3 | [![Latest Stable Version](https://poser.pugx.org/3ev/wordpress-core/v/stable.svg)](https://packagist.org/packages/3ev/wordpress-core) 4 | [![Total Downloads](https://poser.pugx.org/3ev/wordpress-core/downloads.svg)](https://packagist.org/packages/3ev/wordpress-core) 5 | [![License](https://poser.pugx.org/3ev/wordpress-core/license.svg)](https://packagist.org/packages/3ev/wordpress-core) 6 | 7 | > A modern API for structuring and building plugins and themes in Wordpress, whilst 8 | keeping the familiar environment that Wordpress developers are used to. 9 | 10 | ##Versions 11 | 12 | From version `2.0.0`, this packages supports Wordpress 4.4 or newer. Use a `1.x` version of this package for Wordpress `<= 4.3` support. 13 | 14 | ##Installation 15 | 16 | ``` 17 | $ composer require 3ev/wordpress-core 18 | ``` 19 | 20 | ##Documentation 21 | 22 | * [API documentation](http://3ev.github.io/wordpress-core/) - http://3ev.github.io/wordpress-core/ 23 | * [Usage, tutorials and other info](https://github.com/3ev/wordpress-core/wiki) - https://github.com/3ev/wordpress-core/wiki 24 | 25 | ##Licence 26 | 27 | MIT © 3ev 28 | -------------------------------------------------------------------------------- /apigen.neon: -------------------------------------------------------------------------------- 1 | destination: docs 2 | 3 | source: 4 | - src 5 | 6 | charset: 7 | - UTF-8 8 | 9 | main: Tev 10 | 11 | title: 3ev Core Wordpress library API 12 | 13 | templateTheme: bootstrap 14 | 15 | php: false 16 | 17 | sourceCode: false 18 | 19 | baseUrl: http://3ev.github.io/wordpress-core/ 20 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "3ev/wordpress-core", 3 | 4 | "type": "library", 5 | 6 | "description": "Utility belt for improving the way you build Wordpress plugins and themes", 7 | 8 | "keywords": ["modern", "wordpress", "framework"], 9 | 10 | "homepage": "https://github.com/3ev/wordpress-core", 11 | 12 | "license": "MIT", 13 | 14 | "authors": [ 15 | { 16 | "name": "3ev", 17 | "homepage": "http://www.3ev.com/", 18 | "role": "Developer" 19 | }, 20 | 21 | { 22 | "name": "Ben Constable", 23 | "email": "benconstable@3ev.com", 24 | "homepage": "http://github.com/BenConstable", 25 | "role": "Developer" 26 | } 27 | ], 28 | 29 | "require": { 30 | "php": ">= 5.3.2", 31 | "pimple/pimple": "~3.0", 32 | "nesbot/carbon": "~1.13" 33 | }, 34 | 35 | "require-dev": { 36 | "apigen/apigen": "~4.0" 37 | }, 38 | 39 | "autoload": { 40 | "psr-4": { 41 | "Tev\\": "src/Tev/" 42 | }, 43 | 44 | "files": [ 45 | "src/helpers.php" 46 | ] 47 | }, 48 | 49 | "config": { 50 | "preferred-install": "dist", 51 | "bin-dir": "bin" 52 | }, 53 | 54 | "extra": { 55 | "branch-alias": { 56 | "dev-master": "2.x-dev" 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp') 2 | , apigen = require('gulp-apigen') 3 | 4 | gulp.task('gendocs', function() { 5 | gulp.src('apigen.neon').pipe(apigen('bin/apigen')) 6 | }); 7 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "3ev-wordpress-core", 3 | "version": "0.0.1", 4 | "description": "Utility belt for improving the way you build Wordpress plugins and themes", 5 | "author": { 6 | "name": "3ev", 7 | "email": "dev@3ev.com", 8 | "url": "http://www.3ev.com" 9 | }, 10 | "private": true, 11 | "engines": { 12 | "node": ">=0.10.0" 13 | }, 14 | "dependencies": {}, 15 | "devDependencies": { 16 | "gulp": "^3.8.10", 17 | "gulp-apigen": "^0.1.3" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/Tev/Application/Application.php: -------------------------------------------------------------------------------- 1 | bootstrap(); 54 | } 55 | 56 | return self::$instance; 57 | } 58 | 59 | /** 60 | * Constructor. 61 | * 62 | * Private for singleton. Constructs container. 63 | * 64 | * @return void 65 | */ 66 | private function __construct() 67 | { 68 | $this->container = new Container; 69 | } 70 | 71 | /** 72 | * Bind a service into the container. 73 | * 74 | * @param string $name Service name 75 | * @param \Closure $bind Service location closure. Receives \Tev\Application\Application as argument 76 | * @return \Tev\Application\Application This, for chaining 77 | */ 78 | public function bind($name, Closure $bind) 79 | { 80 | $app = $this; 81 | 82 | $this->container[$name] = function ($c) use ($app, $bind) { 83 | return $bind($app); 84 | }; 85 | 86 | return $this; 87 | } 88 | 89 | /** 90 | * Fetch a service from the container. 91 | * 92 | * @param string $name Service name 93 | * @return mixed Bound service 94 | */ 95 | public function fetch($name) 96 | { 97 | return $this->container[$name]; 98 | } 99 | 100 | /** 101 | * Bootstrap default services into the container. 102 | * 103 | * @return \Tev\Application\Application This, for chaining 104 | */ 105 | protected function bootstrap() 106 | { 107 | foreach ($this->bootstraps as $bClass) { 108 | $b = new $bClass; 109 | 110 | if ($b instanceof BootstrapperInterface) { 111 | $b->bootstrap($this); 112 | } 113 | } 114 | 115 | return $this; 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /src/Tev/Application/Bootstrap/AuthorFactory.php: -------------------------------------------------------------------------------- 1 | bind('author_factory', function ($app) { 19 | return new Factory; 20 | }); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/Tev/Application/Bootstrap/FieldFactory.php: -------------------------------------------------------------------------------- 1 | bind('field_factory', function ($app) { 24 | 25 | $factory = new Factory($app); 26 | 27 | // Register defaults 28 | 29 | return $factory 30 | 31 | // Simple fields 32 | 33 | ->register('true_false', 'Tev\Field\Model\BasicField') 34 | ->register('page_link', 'Tev\Field\Model\BasicField') 35 | ->register('color_picker', 'Tev\Field\Model\BasicField') 36 | ->register('oembed', 'Tev\Field\Model\BasicField') 37 | ->register('text', 'Tev\Field\Model\BasicField') 38 | ->register('wysiwyg', 'Tev\Field\Model\BasicField') 39 | ->register('textarea', 'Tev\Field\Model\BasicField') 40 | ->register('url', 'Tev\Field\Model\BasicField') 41 | ->register('email', 'Tev\Field\Model\BasicField') 42 | ->register('date_picker', 'Tev\Field\Model\DateField') 43 | ->register('file', 'Tev\Field\Model\FileField') 44 | ->register('select', 'Tev\Field\Model\SelectField') 45 | ->register('checkbox', 'Tev\Field\Model\SelectField') 46 | ->register('radio', 'Tev\Field\Model\SelectField') 47 | ->register('google_map', 'Tev\Field\Model\GoogleMapField') 48 | ->register('image', 'Tev\Field\Model\ImageField') 49 | ->register('number', 'Tev\Field\Model\NumberField') 50 | ->register('gallery', 'Tev\Field\Model\GalleryField') 51 | 52 | // Post and other relationship fields 53 | 54 | ->register('post_object', function ($data, $app) { 55 | return new PostField($data, $app->fetch('post_factory')); 56 | }) 57 | ->register('relationship', function ($data, $app) { 58 | return new PostField($data, $app->fetch('post_factory')); 59 | }) 60 | ->register('taxonomy', function ($data, $app) { 61 | return new TaxonomyField( 62 | $data, 63 | $app->fetch('taxonomy_factory'), 64 | $app->fetch('term_factory') 65 | ); 66 | }) 67 | ->register('user', function ($data, $app) { 68 | return new AuthorField($data, $app->fetch('author_factory')); 69 | }) 70 | 71 | // Collection fields 72 | 73 | ->register('repeater', function ($data, $app) { 74 | return new RepeaterField( 75 | $data, 76 | $app->fetch('field_factory') 77 | ); 78 | }) 79 | ->register('flexible_content', function ($data, $app) { 80 | return new FlexibleContentField( 81 | $data, 82 | $app->fetch('field_factory') 83 | ); 84 | }); 85 | }); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/Tev/Application/Bootstrap/PluginLoader.php: -------------------------------------------------------------------------------- 1 | bind('plugin_loader', function ($app) { 19 | return new Loader($app); 20 | }); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/Tev/Application/Bootstrap/Post.php: -------------------------------------------------------------------------------- 1 | bind('post_factory', function ($app) { 22 | $f = new Factory( 23 | $app->fetch('author_factory'), 24 | $app->fetch('taxonomy_factory'), 25 | $app->fetch('field_factory') 26 | ); 27 | 28 | // Register defaut post types 29 | 30 | return $f 31 | ->register('post', 'Tev\Post\Model\Post') 32 | ->register('page', 'Tev\Post\Model\Page') 33 | ->register('attachment', 'Tev\Post\Model\Attachment'); 34 | }); 35 | 36 | // Bind a post repository instance 37 | 38 | $app->bind('post_repo', function ($app) { 39 | return new PostRepository($app->fetch('post_factory')); 40 | }); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/Tev/Application/Bootstrap/Taxonomy.php: -------------------------------------------------------------------------------- 1 | bind('taxonomy_factory', function ($app) { 22 | return new Factory($app->fetch('term_factory')); 23 | }); 24 | 25 | // Bind a taxonomy repository instance 26 | 27 | $app->bind('taxonomy_repo', function ($app) { 28 | return new TaxonomyRepository($app->fetch('taxonomy_factory')); 29 | }); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/Tev/Application/Bootstrap/Term.php: -------------------------------------------------------------------------------- 1 | bind('term_factory', function ($app) { 22 | return new Factory; 23 | }); 24 | 25 | // Bind a term repository instance 26 | 27 | $app->bind('term_repo', function ($app) { 28 | return new TermRepository( 29 | $app->fetch('taxonomy_factory'), 30 | $app->fetch('term_factory') 31 | ); 32 | }); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Tev/Application/Bootstrap/Util.php: -------------------------------------------------------------------------------- 1 | bind('template_extras', function ($app) { 19 | return new TemplateExtras($app->fetch('post_factory')); 20 | }); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/Tev/Author/Factory.php: -------------------------------------------------------------------------------- 1 | base = $base; 32 | } 33 | 34 | /** 35 | * Get the author ID. 36 | * 37 | * @return int 38 | */ 39 | public function getId() 40 | { 41 | return $this->base->ID; 42 | } 43 | 44 | /** 45 | * Get the author first name. 46 | * 47 | * @return string 48 | */ 49 | public function getFirstName() 50 | { 51 | return $this->base->first_name; 52 | } 53 | 54 | /** 55 | * Get the author last first name. 56 | * 57 | * @return string 58 | */ 59 | public function getLastName() 60 | { 61 | return $this->base->last_name; 62 | } 63 | 64 | /** 65 | * Get the author nice name. 66 | * 67 | * @return string 68 | */ 69 | public function getNiceName() 70 | { 71 | return $this->base->user_nicename; 72 | } 73 | 74 | /** 75 | * Get the author display name. 76 | * 77 | * @return string 78 | */ 79 | public function getDisplayName() 80 | { 81 | return $this->base->display_name; 82 | } 83 | 84 | /** 85 | * Get the author email address. 86 | * 87 | * @return string 88 | */ 89 | public function getEmail() 90 | { 91 | return $this->base->user_email; 92 | } 93 | 94 | /** 95 | * Get the author URL (link to their own blog or website). 96 | * 97 | * @return string 98 | */ 99 | public function getUrl() 100 | { 101 | return $this->base->user_url; 102 | } 103 | 104 | /** 105 | * Get the URL to the author's posts on this app. 106 | * 107 | * @return string 108 | */ 109 | public function getPostsUrl() 110 | { 111 | return get_author_posts_url($this->getId()); 112 | } 113 | 114 | /** 115 | * Get the total number of published posts the author has. 116 | * 117 | * @return int 118 | */ 119 | public function getPostCount() 120 | { 121 | return count_user_posts($this->getId()); 122 | } 123 | 124 | /** 125 | * Get a meta field value for this author. 126 | * 127 | * @param string $field Field name 128 | * @return string Field data. Empty string if field does not exist 129 | */ 130 | public function getMeta($field) 131 | { 132 | return get_the_author_meta($field, $this->getId()); 133 | } 134 | 135 | /** 136 | * Get an image tag for this author's avatar. 137 | * 138 | * @param int $size Avatar size. Max 512, default 96 139 | * @param string $alt Alt text. Defaults to display name 140 | * @return string 141 | */ 142 | public function getAvatarTag($size = 96, $alt = null) 143 | { 144 | return get_avatar($this->getId(), $size, null, $alt ?: $this->getDisplayName()); 145 | } 146 | 147 | /** 148 | * Get the underlying `WP_User` object. 149 | * 150 | * @return \WP_User 151 | */ 152 | public function getBaseObject() 153 | { 154 | return $this->base; 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /src/Tev/Contracts/BootstrapperInterface.php: -------------------------------------------------------------------------------- 1 | wpdb = $wpdb; 41 | $this->app = $app; 42 | } 43 | 44 | /** 45 | * Return the SQL statement that will install/update the database for 46 | * this installer. 47 | * 48 | * See http://codex.wordpress.org/Creating_Tables_with_Plugins#Creating_or_Updating_the_Table 49 | * for SQL idiosyncrasies in Wordpress. 50 | * 51 | * @return string SQL string 52 | */ 53 | abstract protected function getSql(); 54 | 55 | /** 56 | * Get the current database version. 57 | * 58 | * Should be updated every time the database needs to be updated. 59 | * 60 | * @return string Semver string, like 1.0.0 61 | */ 62 | abstract protected function getVersion(); 63 | 64 | /** 65 | * Install the database for this first. 66 | * 67 | * @return void 68 | */ 69 | public function install() 70 | { 71 | $this->run(); 72 | } 73 | 74 | /** 75 | * Update the database if it's out of date. 76 | * 77 | * @return void 78 | */ 79 | public function update() 80 | { 81 | if ($this->getVersion() !== $this->getCurrentVersion()) { 82 | $this->run(); 83 | } 84 | } 85 | 86 | /** 87 | * Run database scripts. 88 | * 89 | * See http://codex.wordpress.org/Creating_Tables_with_Plugins#Creating_or_Updating_the_Table 90 | * for SQL idiosyncrasies in Wordpress. 91 | * 92 | * @return void 93 | */ 94 | protected function run() 95 | { 96 | require_once(ABSPATH . 'wp-admin/includes/upgrade.php'); 97 | 98 | dbDelta($this->getSql()); 99 | 100 | $this->setCurrentVersion(); 101 | } 102 | 103 | /** 104 | * Get the current installed database version. 105 | * 106 | * @return string|null 107 | */ 108 | private function getCurrentVersion() 109 | { 110 | return get_option($this->getVersionOptionKey(), null); 111 | } 112 | 113 | /** 114 | * Set the current installed database version. 115 | * 116 | * @return \Lsg\Database\LocationsInstaller This, for chaining 117 | */ 118 | private function setCurrentVersion() 119 | { 120 | if ($this->getCurrentVersion() !== null) { 121 | update_option($this->getVersionOptionKey(), $this->getVersion()); 122 | } else { 123 | add_option($this->getVersionOptionKey(), $this->getVersion()); 124 | } 125 | 126 | return $this; 127 | } 128 | 129 | /** 130 | * Get the option key to store the database version in. 131 | * 132 | * @return string 133 | */ 134 | private function getVersionOptionKey() 135 | { 136 | return strtolower(str_replace('\\', '_', get_class($this))) . '_version'; 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /src/Tev/Database/Utils.php: -------------------------------------------------------------------------------- 1 | app = $app; 40 | $this->registry = array(); 41 | } 42 | 43 | /** 44 | * Register a new factory function with the factory. 45 | * 46 | * Can either be field class name or factory callback function. If class 47 | * name, the field data array will be passed to the constructor. If 48 | * callback, the field data array will be passed as the first parameter 49 | * and the application instance will be passed as the second. 50 | * 51 | * For example: 52 | * 53 | * // Class name 54 | * 55 | * $factory->register('text', 'Tev\Field\Model\BasicField'); 56 | * 57 | * // Callback 58 | * 59 | * $factory->register('text', function ($data, $app) { 60 | * return new \Tev\Field\Model\BasicField($data); 61 | * }); 62 | * 63 | * @param string $type Field type string 64 | * @param string|\Closure $factory Class name or factory function 65 | * @return \Tev\Field\Factory This, for chaining 66 | */ 67 | public function register($type, $factory) 68 | { 69 | $this->registry[$type] = $factory; 70 | 71 | return $this; 72 | } 73 | 74 | /** 75 | * Check if the given type is registered. 76 | * 77 | * @param string $type Field type string 78 | * @return boolean True if registered false if not 79 | */ 80 | public function registered($type) 81 | { 82 | return isset($this->registry[$type]); 83 | } 84 | 85 | /** 86 | * Create a new custom field object. 87 | * 88 | * If the field is not registered, a `\Tev\Field\Model\NullField` will be 89 | * returned. 90 | * 91 | * @param string $field Field name or ID 92 | * @param \Tev\Post\Model\AbstractPost $post Post object field is for 93 | * @return \Tev\Field\Model\AbstractField Field object 94 | * 95 | * @throws \Exception If field type is not registered 96 | */ 97 | public function create($field, AbstractPost $post) 98 | { 99 | $data = get_field_object($field, $post->getId()); 100 | 101 | return $this->createFromField($data); 102 | } 103 | 104 | /** 105 | * Create a new custom field object from an existing set of field data. 106 | * 107 | * If the field is not registered, a `\Tev\Field\Model\NullField` will be 108 | * returned. 109 | * 110 | * @param array $field Field data array 111 | * @param mixed $value If supplied, will be set as the fields value 112 | * @return \Tev\Field\Model\AbstractField Field object 113 | * 114 | * @throws \Exception If field type is not registered 115 | */ 116 | public function createFromField($field, $value = null) 117 | { 118 | if (!is_array($field)) { 119 | return new NullField; 120 | } 121 | 122 | if ($value !== null) { 123 | $field['value'] = $value; 124 | } 125 | 126 | return $this->resolve($field['type'], $field); 127 | } 128 | 129 | /** 130 | * Creata new custom field object that's not in the context of a post 131 | * and doesn't have a loaded value. 132 | * 133 | * @param string $field Field name or ID 134 | * @return \Tev\Field\Model\AbstractField Field object 135 | * 136 | * @throws \Exception If field type is not registered 137 | */ 138 | public function createEmpty($field) 139 | { 140 | return $this->createFromField(get_field_object($field, null, false, false)); 141 | } 142 | 143 | /** 144 | * Resolve a field object using its type, from the registered factory 145 | * functions. 146 | * 147 | * @param string $type Field type 148 | * @param array $data Field data 149 | * @return \Tev\Field\Model\AbstractField Field object 150 | * 151 | * @throws \Exception If field type is not registered 152 | */ 153 | protected function resolve($type, array $data) 154 | { 155 | if ($this->registered($type)) { 156 | $f = $this->registry[$type]; 157 | if ($f instanceof Closure) { 158 | return $f($data, $this->app); 159 | } else { 160 | return new $f($data); 161 | } 162 | } else { 163 | throw new Exception("Field type $type not registered"); 164 | } 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /src/Tev/Field/Model/AbstractField.php: -------------------------------------------------------------------------------- 1 | base = $base; 27 | } 28 | 29 | /** 30 | * Get the key for this field. 31 | * 32 | * @return string 33 | */ 34 | public function getKey() 35 | { 36 | return $this->base['key']; 37 | } 38 | 39 | /** 40 | * Get the name of this field. 41 | * 42 | * @return string 43 | */ 44 | public function getName() 45 | { 46 | return $this->base['name']; 47 | } 48 | 49 | /** 50 | * Get the field value. 51 | * 52 | * Alias for `getValue()`. 53 | * 54 | * @return mixed Field value 55 | */ 56 | public function val() 57 | { 58 | return $this->getValue(); 59 | } 60 | 61 | /** 62 | * Get the underlying field array 63 | * 64 | * @return array 65 | */ 66 | public function getBaseObject() 67 | { 68 | return $this->base; 69 | } 70 | 71 | /** 72 | * When attempting to echo this field print its value as a string. 73 | * 74 | * @return string Field value 75 | */ 76 | public function __toString() 77 | { 78 | return $this->getValue(); 79 | } 80 | 81 | /** 82 | * Get the value of this field. 83 | * 84 | * @return mixed Field value 85 | */ 86 | abstract public function getValue(); 87 | } 88 | -------------------------------------------------------------------------------- /src/Tev/Field/Model/AuthorField.php: -------------------------------------------------------------------------------- 1 | authorFactory = $authorFactory; 33 | } 34 | 35 | /** 36 | * Get a single author object or array of author objects. 37 | * 38 | * If no authors are configured, returned will result will be an empty array 39 | * if this is a mutli-select, or null if not. 40 | * 41 | * @return \Tev\Author\Model\Author|\Tev\Author\Model\Author[]|null 42 | */ 43 | public function getValue() 44 | { 45 | $val = $this->base['value']; 46 | 47 | if (is_array($val)) { 48 | if (isset($val['ID'])) { 49 | return $this->authorFactory->create($val['ID']); 50 | } else { 51 | $authors = array(); 52 | 53 | foreach ($val as $a) { 54 | $authors[] = $this->authorFactory->create($a['ID']); 55 | } 56 | 57 | return $authors; 58 | } 59 | } else { 60 | if (isset($this->base['multiple']) && $this->base['multiple']) { 61 | return array(); 62 | } else { 63 | return null; 64 | } 65 | } 66 | } 67 | 68 | /** 69 | * Get a string representation of this field. 70 | * 71 | * Author display name or names, comma-space separated. 72 | * 73 | * @return string 74 | */ 75 | public function __toString() 76 | { 77 | $author = $this->getValue(); 78 | 79 | if (is_array($author)) { 80 | return array_reduce($author, function ($string, $a) { 81 | return $string . (strlen($string) ? ', ' : '') . $a->getDisplayName(); 82 | }, ''); 83 | } elseif ($author !== null) { 84 | return $author->getDisplayName(); 85 | } else { 86 | return ''; 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/Tev/Field/Model/BasicField.php: -------------------------------------------------------------------------------- 1 | base['value']; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/Tev/Field/Model/DateField.php: -------------------------------------------------------------------------------- 1 | base['value']) { 31 | $this->date = Carbon::createFromFormat($this->base['return_format'], $this->base['value']); 32 | } else { 33 | $this->data = null; 34 | } 35 | 36 | $this->format = $this->base['display_format']; 37 | } 38 | 39 | /** 40 | * Get this date as a string. 41 | * 42 | * Empty string if no date is set. 43 | * 44 | * @return string 45 | */ 46 | public function getValue() 47 | { 48 | if ($this->date) { 49 | return $this->date->format($this->format); 50 | } else { 51 | return ''; 52 | } 53 | } 54 | 55 | /** 56 | * Get this date as a Carbon object. 57 | * 58 | * Returns null if no date is set. 59 | * 60 | * @return \Carbon\Carbon|null 61 | */ 62 | public function date() 63 | { 64 | return $this->date; 65 | } 66 | 67 | /** 68 | * Format the date, or return its format. 69 | * 70 | * @param string|null $newFormat If string, will set format. If omitted, 71 | * will return format 72 | * @return string|\Tev\Field\Model\DateField Format or this, for chaining 73 | */ 74 | public function format($newFormat = null) 75 | { 76 | if ($newFormat === null) { 77 | return $this->format; 78 | } 79 | 80 | $this->format = $newFormat; 81 | 82 | return $this; 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/Tev/Field/Model/FileField.php: -------------------------------------------------------------------------------- 1 | normalize(); 57 | } 58 | 59 | /** 60 | * Get the attachment URL. 61 | * 62 | * @return string 63 | */ 64 | public function getValue() 65 | { 66 | return $this->url(); 67 | } 68 | 69 | /** 70 | * Get the attachment ID. 71 | * 72 | * May be empty, depending on field config. 73 | * 74 | * @return string 75 | */ 76 | public function id() 77 | { 78 | return $this->atId; 79 | } 80 | 81 | /** 82 | * Get the attachment URL. 83 | * 84 | * May be empty, depending on field config. 85 | * 86 | * @return string 87 | */ 88 | public function url() 89 | { 90 | return $this->atUrl; 91 | } 92 | 93 | /** 94 | * Get the attachment title. 95 | * 96 | * May be empty, depending on field config. 97 | * 98 | * @return string 99 | */ 100 | public function title() 101 | { 102 | return $this->atTitle; 103 | } 104 | 105 | /** 106 | * Get the attachment caption. 107 | * 108 | * May be empty, depending on field config. 109 | * 110 | * @return string 111 | */ 112 | public function caption() 113 | { 114 | return $this->atCaption; 115 | } 116 | 117 | /** 118 | * Get the attachment description. 119 | * 120 | * May be empty, depending on field config. 121 | * 122 | * @return string 123 | */ 124 | public function description() 125 | { 126 | return $this->atDescription; 127 | } 128 | 129 | /** 130 | * Normalize attachment data, depending on how the underlying field is 131 | * configured. 132 | * 133 | * @return void 134 | * 135 | * @throws \Exception If invalid return_format is found 136 | */ 137 | protected function normalize() 138 | { 139 | $val = $this->base['value']; 140 | 141 | if ($val) { 142 | switch ($this->base['return_format']) { 143 | case 'array': 144 | $this->atId = $val['ID']; 145 | $this->atUrl = $val['url']; 146 | $this->atTitle = $val['title']; 147 | $this->atCaption = $val['caption']; 148 | $this->atDescription = $val['description']; 149 | break; 150 | 151 | case 'id': 152 | $atch = get_post($val); 153 | 154 | $this->atId = $val; 155 | $this->atUrl = wp_get_attachment_url($val); 156 | $this->atTitle = $atch->post_title; 157 | $this->atCaption = $atch->post_excerpt; 158 | $this->atDescription = $atch->description; 159 | break; 160 | 161 | case 'url': 162 | $this->atId = ''; 163 | $this->atUrl = $this->base['value']; 164 | $this->atTitle = ''; 165 | $this->atCaption = ''; 166 | $this->atDescription = ''; 167 | break; 168 | 169 | default: 170 | throw new Exception("Field format {$this->base['return_format']} not valid"); 171 | } 172 | } else { 173 | $this->atId = ''; 174 | $this->atUrl = ''; 175 | $this->atTitle = ''; 176 | $this->atCaption = ''; 177 | $this->atDescription = ''; 178 | } 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /src/Tev/Field/Model/FlexibleContentField.php: -------------------------------------------------------------------------------- 1 | base['value'][$this->position]; 22 | $layout = $val['acf_fc_layout']; 23 | 24 | $group = new LayoutFieldGroup( 25 | $this->getLayoutFields($layout), 26 | $val, 27 | $this, 28 | $this->fieldFactory 29 | ); 30 | 31 | return $group->setLayout($layout); 32 | } 33 | 34 | /** 35 | * Get sub fields for layout. 36 | * 37 | * @param string $layout Layout name 38 | * @return array Sub fields 39 | */ 40 | private function getLayoutFields($layout) 41 | { 42 | foreach ($this->base['layouts'] as $l) { 43 | if ($l['name'] === $layout) { 44 | return $l['sub_fields']; 45 | } 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/Tev/Field/Model/GalleryField.php: -------------------------------------------------------------------------------- 1 | base['value']; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/Tev/Field/Model/GoogleMapField.php: -------------------------------------------------------------------------------- 1 | lat() . ',' . $this->lng(); 20 | } 21 | 22 | /** 23 | * Get the latitude value. 24 | * 25 | * @return string 26 | */ 27 | public function lat() 28 | { 29 | if (is_array($this->base['value']) && isset($this->base['value']['lat'])) { 30 | return $this->base['value']['lat']; 31 | } 32 | 33 | return ''; 34 | } 35 | 36 | /** 37 | * Get the longitude value. 38 | * 39 | * @return string 40 | */ 41 | public function lng() 42 | { 43 | if (is_array($this->base['value']) && isset($this->base['value']['lng'])) { 44 | return $this->base['value']['lng']; 45 | } 46 | 47 | return ''; 48 | } 49 | 50 | /** 51 | * Get the address field. 52 | * 53 | * @return string 54 | */ 55 | public function address() 56 | { 57 | if (is_array($this->base['value']) && isset($this->base['value']['address'])) { 58 | return $this->base['value']['address']; 59 | } 60 | 61 | return ''; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/Tev/Field/Model/ImageField.php: -------------------------------------------------------------------------------- 1 | atWidth; 36 | } 37 | 38 | /** 39 | * Get full image height. 40 | * 41 | * May be 0 depending on field config. 42 | * 43 | * @return int 44 | */ 45 | public function height() 46 | { 47 | return $this->atHeight; 48 | } 49 | 50 | /** 51 | * Get the image thumbnail URL if possible. 52 | * 53 | * @return string URL or empty string 54 | */ 55 | public function thumbnailUrl() 56 | { 57 | return $this->sizeUrl('thumbnail'); 58 | } 59 | 60 | /** 61 | * Get the image medium URL if possible. 62 | * 63 | * @return string URL or empty string 64 | */ 65 | public function mediumUrl() 66 | { 67 | return $this->sizeUrl('medium'); 68 | } 69 | 70 | /** 71 | * Get the image large URL if possible. 72 | * 73 | * @return string URL or empty string 74 | */ 75 | public function largeUrl() 76 | { 77 | return $this->sizeUrl('large'); 78 | } 79 | 80 | /** 81 | * Get an image URL of a specic size. 82 | * 83 | * @param string $size Image size (e.g thumbnail, large or custom size) 84 | * @return string Image URL 85 | */ 86 | public function sizeUrl($size) 87 | { 88 | if (($this->base['return_format'] === 'array') && isset($this->base['value']['sizes'][$size])) { 89 | return $this->base['value']['sizes'][$size]; 90 | } elseif ($this->base['return_format'] === 'id') { 91 | if ($src = wp_get_attachment_image_src($this->id(), $size)) { 92 | return $src[0]; 93 | } 94 | } 95 | 96 | return ''; 97 | } 98 | 99 | /** 100 | * {@inheritDoc} 101 | */ 102 | protected function normalize() 103 | { 104 | parent::normalize(); 105 | 106 | $val = $this->base['value']; 107 | 108 | if ($val) { 109 | switch ($this->base['return_format']) { 110 | case 'array': 111 | $this->atWidth = $val['width']; 112 | $this->atHeight = $val['height']; 113 | break; 114 | 115 | case 'id': 116 | $src = wp_get_attachment_image_src($this->id(), 'full'); 117 | 118 | $this->atWidth = $src[1]; 119 | $this->atHeight = $src[2]; 120 | break; 121 | 122 | case 'url': 123 | $this->atWidth = 0; 124 | $this->atHeight = 0; 125 | break; 126 | 127 | default: 128 | throw new Exception("Field format {$this->base['return_format']} not valid"); 129 | } 130 | } else { 131 | $this->atWidth = 0; 132 | $this->atHeight = 0; 133 | } 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /src/Tev/Field/Model/NullField.php: -------------------------------------------------------------------------------- 1 | null, 22 | 'name' => null 23 | )); 24 | } 25 | 26 | /** 27 | * Get the value of this field. 28 | * 29 | * Value will be null. 30 | * 31 | * @return null 32 | */ 33 | public function getValue() 34 | { 35 | return null; 36 | } 37 | 38 | /** 39 | * Conver this field to string. 40 | * 41 | * @return string The empty string 42 | */ 43 | public function __toString() 44 | { 45 | return ''; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/Tev/Field/Model/NumberField.php: -------------------------------------------------------------------------------- 1 | base['value']; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/Tev/Field/Model/PostField.php: -------------------------------------------------------------------------------- 1 | postFactory = $postFactory; 33 | } 34 | 35 | /** 36 | * Get a single post object or array of post objects. 37 | * 38 | * If no posts are configured, returned will result will be an empty array 39 | * if this is a mutli-select, or null if not. 40 | * 41 | * @return \Tev\Post\Model\Post|\Tev\Post\Model\Post[]|null 42 | */ 43 | public function getValue() 44 | { 45 | $val = $this->base['value']; 46 | 47 | if (is_array($val)) { 48 | $posts = array(); 49 | 50 | foreach ($val as $p) { 51 | $posts[] = $this->postFactory->create($this->getPostObject($p)); 52 | } 53 | 54 | return $posts; 55 | } elseif (strlen($val)) { 56 | return $this->postFactory->create($this->getPostObject($val)); 57 | } else { 58 | if (isset($this->base['multiple']) && $this->base['multiple']) { 59 | return array(); 60 | } else { 61 | return null; 62 | } 63 | } 64 | } 65 | 66 | /** 67 | * Get a string representation of this field. 68 | * 69 | * Post title or titles, comma-space separated. 70 | * 71 | * @return string 72 | */ 73 | public function __toString() 74 | { 75 | $post = $this->getValue(); 76 | 77 | if (is_array($post)) { 78 | return array_reduce($post, function ($string, $p) { 79 | return $string . (strlen($string) ? ', ' : '') . $p->getTitle(); 80 | }, ''); 81 | } elseif ($post !== null) { 82 | return $post->getTitle(); 83 | } else { 84 | return ''; 85 | } 86 | } 87 | 88 | /** 89 | * If given $post is an ID, get a WP_Post object from it. 90 | * 91 | * @param int|\WP_Post $post Post ID or object 92 | * @return \WP_Post Post object 93 | */ 94 | private function getPostObject($post) 95 | { 96 | if (!($post instanceof WP_Post)) { 97 | return get_post($post); 98 | } 99 | 100 | return $post; 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/Tev/Field/Model/RepeaterField.php: -------------------------------------------------------------------------------- 1 | fieldFactory = $fieldFactory; 42 | $this->position = 0; 43 | } 44 | 45 | /** 46 | * Shows the number of items in the repeater. 47 | * 48 | * @return int 49 | */ 50 | public function getValue() 51 | { 52 | return $this->count(); 53 | } 54 | 55 | /** 56 | * Get the number of items in the repeater. 57 | * 58 | * @return int 59 | */ 60 | public function count() 61 | { 62 | if (is_array($this->base['value'])) { 63 | return count($this->base['value']); 64 | } 65 | 66 | return 0; 67 | } 68 | 69 | /** 70 | * Get the current repeater item. 71 | * 72 | * @return \Tev\Field\Util\FieldGroup 73 | */ 74 | public function current() 75 | { 76 | return new FieldGroup( 77 | $this->base['sub_fields'], 78 | $this->base['value'][$this->position], 79 | $this, 80 | $this->fieldFactory 81 | ); 82 | } 83 | 84 | /** 85 | * Get the current iteration key. 86 | * 87 | * @return int 88 | */ 89 | public function key() 90 | { 91 | return $this->position; 92 | } 93 | 94 | /** 95 | * Advance to the next position. 96 | * 97 | * @return void 98 | */ 99 | public function next() 100 | { 101 | $this->position++; 102 | } 103 | 104 | /** 105 | * Rewind to the beginning. 106 | * 107 | * @return void 108 | */ 109 | public function rewind() 110 | { 111 | $this->position = 0; 112 | } 113 | 114 | /** 115 | * Check if current position is valid. 116 | * 117 | * @return boolean 118 | */ 119 | public function valid() 120 | { 121 | return isset($this->base['value'][$this->position]) && is_array($this->base['value'][$this->position]); 122 | } 123 | 124 | /** 125 | * Required for `\ArrayAccess`. 126 | * 127 | * @param mixed $offset Array offset 128 | * @return boolean 129 | */ 130 | public function offsetExists($offset) 131 | { 132 | return isset($this->base['value'][$offset]) && is_array($this->base['value'][$offset]); 133 | } 134 | 135 | /** 136 | * Required for `\ArrayAccess`. 137 | * 138 | * @param mixed $offset Array offset 139 | * @return mixed 140 | */ 141 | public function offsetGet($offset) 142 | { 143 | if (isset($this->base['value'][$offset]) && is_array($this->base['value'][$offset])) { 144 | return new FieldGroup( 145 | $this->base['sub_fields'], 146 | $this->base['value'][$offset], 147 | $this, 148 | $this->fieldFactory 149 | ); 150 | } 151 | 152 | return null; 153 | } 154 | 155 | /** 156 | * Required for `\ArrayAccess`. 157 | * 158 | * @param mixed $offset Array offset 159 | * @param mixed $value Value to set 160 | * @return void 161 | */ 162 | public function offsetSet($offset, $value) 163 | { 164 | if (is_null($offset)) { 165 | $this->base['value'][] = $value; 166 | } else { 167 | $this->base['value'][$offset] = $value; 168 | } 169 | } 170 | 171 | /** 172 | * Required for `\ArrayAccess`. 173 | * 174 | * @param mixed $offset Array offset 175 | * @return void 176 | */ 177 | public function offsetUnset($offset) 178 | { 179 | unset($this->base['value'][$offset]); 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /src/Tev/Field/Model/SelectField.php: -------------------------------------------------------------------------------- 1 | options(); 19 | 20 | if ($this->multiSelected()) { 21 | $values = array(); 22 | 23 | foreach ($this->selected() as $val) { 24 | $values[] = $options[$val]; 25 | } 26 | 27 | return $values; 28 | } else { 29 | return $options[$this->selected()]; 30 | } 31 | } 32 | 33 | /** 34 | * Get the selected option values. 35 | * 36 | * @return mixed Single selected option value, or array of selected options values 37 | */ 38 | public function selected() 39 | { 40 | return $this->base['value']; 41 | } 42 | 43 | /** 44 | * Get all available options from this select. 45 | * 46 | * @return array Key-value pairs (values to labels) 47 | */ 48 | public function options() 49 | { 50 | return $this->base['choices']; 51 | } 52 | 53 | /** 54 | * Check if the given value is selected. 55 | * 56 | * @param mixed $val Value 57 | * @return boolean True if selected, false if not 58 | */ 59 | public function isSelected($val) 60 | { 61 | if ($this->isMulti()) { 62 | return in_array($val, $this->getValue()); 63 | } else { 64 | return $val === $this->getValue(); 65 | } 66 | } 67 | 68 | /** 69 | * Check if this is a multi-select or not. 70 | * 71 | * @return boolean True if multi-select, false if not 72 | */ 73 | public function isMulti() 74 | { 75 | return (boolean) $this->base['multiple']; 76 | } 77 | 78 | /** 79 | * Get a string representation of this selected options. 80 | * 81 | * If this is a multi-select, values will be comma-space separated. 82 | * 83 | * @return string 84 | */ 85 | public function __toString() 86 | { 87 | if ($this->multiSelected()) { 88 | return implode(', ', $this->getValue()); 89 | } else { 90 | return $this->getValue(); 91 | } 92 | } 93 | 94 | /** 95 | * Check if the selected options are in mult-format. 96 | * 97 | * @return boolean 98 | */ 99 | private function multiSelected() 100 | { 101 | return is_array($this->selected()); 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/Tev/Field/Model/TaxonomyField.php: -------------------------------------------------------------------------------- 1 | taxonomyFactory = $taxonomyFactory; 50 | $this->termFactory = $termFactory; 51 | $this->_taxonomy = null; 52 | } 53 | 54 | /** 55 | * Get Term or array of Terms. 56 | * 57 | * @return \Tev\Term\Model\Term|\Tev\Term\Model\Term[]|null 58 | */ 59 | public function getValue() 60 | { 61 | $terms = $this->base['value']; 62 | 63 | if (is_array($terms)) { 64 | $ret = array(); 65 | 66 | foreach ($terms as $t) { 67 | $ret[] = $this->termFactory->create($t, $this->taxonomy()); 68 | } 69 | 70 | return $ret; 71 | } elseif (is_object($terms)) { 72 | return $this->termFactory->create($terms, $this->taxonomy()); 73 | } else { 74 | if ($this->base['multiple'] || $this->base['field_type'] === 'multi_select' || $this->base['field_type'] === 'checkbox') { 75 | return array(); 76 | } else { 77 | return null; 78 | } 79 | } 80 | } 81 | 82 | /** 83 | * Get Term Taxonomy. 84 | * 85 | * @return \Tev\Taxonomy\Model\Taxonomy 86 | */ 87 | public function taxonomy() 88 | { 89 | if ($this->_taxonomy === null) { 90 | $this->_taxonomy = $this->taxonomyFactory->create($this->base['taxonomy']); 91 | } 92 | 93 | return $this->_taxonomy; 94 | } 95 | 96 | /** 97 | * Get a string representation of the terms. 98 | * 99 | * Term name, or list of comma-separated term names. 100 | * 101 | * @return string 102 | */ 103 | public function __toString() 104 | { 105 | $terms = $this->getValue(); 106 | 107 | if (is_array($terms)) { 108 | return array_reduce($terms, function ($string, $t) { 109 | return $string . (strlen($string) ? ', ' : '') . $t->getName(); 110 | }, ''); 111 | } else { 112 | return $terms->getName(); 113 | } 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/Tev/Field/Util/FieldGroup.php: -------------------------------------------------------------------------------- 1 | fields = $fields; 66 | $this->values = $values; 67 | $this->parent = $parent; 68 | $this->fieldFactory = $fieldFactory; 69 | $this->_fieldData = array(); 70 | } 71 | 72 | /** 73 | * Get a field from the group. 74 | * 75 | * @param string $field Field name or key 76 | * @return \Tev\Field\Model\AbstractField Field object 77 | * 78 | * @throws \Exception If field does not exist 79 | */ 80 | public function field($field) 81 | { 82 | return $this->fieldFactory->createFromField( 83 | $this->getFieldData($field), 84 | $this->getFieldValue($field) 85 | ); 86 | } 87 | 88 | /** 89 | * Get the parent of this field. 90 | * 91 | * @return \Tev\Field\Model\AbstractField 92 | */ 93 | public function parent() 94 | { 95 | return $this->parent; 96 | } 97 | 98 | /** 99 | * Get field data by field name or key. 100 | * 101 | * @param string $field Field name or key 102 | * @return array Field data 103 | * 104 | * @throws \Exception If field does not exist 105 | */ 106 | private function getFieldData($field) 107 | { 108 | if (!isset($this->_fieldData[$field])) { 109 | foreach ($this->fields as $f) { 110 | if (($f['key'] === $field) || ($f['name'] === $field)) { 111 | $this->_fieldData[$field] = $f; 112 | break; 113 | } 114 | } 115 | } 116 | 117 | if (isset($this->_fieldData[$field])) { 118 | return $this->_fieldData[$field]; 119 | } else { 120 | throw new Exception("Field $field does not exist"); 121 | } 122 | } 123 | 124 | /** 125 | * Get field value by name or key. 126 | * 127 | * @param string $field Field name or key 128 | * @return mixed Value or null if not set 129 | */ 130 | private function getFieldValue($field) 131 | { 132 | return isset($this->values[$field]) ? $this->values[$field] : null; 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /src/Tev/Field/Util/LayoutFieldGroup.php: -------------------------------------------------------------------------------- 1 | layoutName = $layoutName; 25 | 26 | return $this; 27 | } 28 | 29 | /** 30 | * Get the layout name, or check if it's equal to the supplied param. 31 | * 32 | * @param null|string $compare Optional. Layout name comparator 33 | * @return string|boolean If no param, layout name. If param, boolean 34 | * result of comparison 35 | */ 36 | public function layout($compare = null) 37 | { 38 | if ($compare === null) { 39 | return $this->layoutName; 40 | } 41 | 42 | return $compare === $this->layoutName; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/Tev/Plugin/Action/AbstractProvider.php: -------------------------------------------------------------------------------- 1 | app = $app; 41 | $this->renderer = $renderer; 42 | } 43 | 44 | /** 45 | * Get the action priority of this provider. 46 | * 47 | * @return int Default 10 48 | */ 49 | public function priority() 50 | { 51 | return 10; 52 | } 53 | 54 | /** 55 | * Get the number of arguments expected by the action method of this 56 | * provider. 57 | * 58 | * @return int Default 1 59 | */ 60 | public function numArgs() 61 | { 62 | return 1; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/Tev/Plugin/Loader.php: -------------------------------------------------------------------------------- 1 | load(__DIR__); 51 | * ``` 52 | * 53 | */ 54 | class Loader 55 | { 56 | /** 57 | * Application. 58 | * 59 | * @var \Tev\Application\Application 60 | */ 61 | protected $app; 62 | 63 | /** 64 | * Plugin base path. 65 | * 66 | * @var string 67 | */ 68 | protected $basePath; 69 | 70 | /** 71 | * View renderer. 72 | * 73 | * @var \Tev\View\Renderer 74 | */ 75 | protected $renderer; 76 | 77 | /** 78 | * Constructor. 79 | * 80 | * Inject dependencies. 81 | * 82 | * @param \Tev\Application\Application $app Application 83 | * @return void 84 | */ 85 | public function __construct(Application $app) 86 | { 87 | $this->app = $app; 88 | } 89 | 90 | /** 91 | * Load all plugin configuration. 92 | * 93 | * @param string $basePath Plugin path 94 | * @return \Tev\Plugin\Loader This, for chaining 95 | */ 96 | public function load($basePath) 97 | { 98 | $this->basePath = $basePath; 99 | $this->renderer = new Renderer($this->getViewsPath()); 100 | 101 | return 102 | $this 103 | ->loadCustomTables() 104 | ->loadPostTypes() 105 | ->loadFieldGroups() 106 | ->loadAcfJson() 107 | ->loadActions() 108 | ->loadShortCodes() 109 | ->loadOptionScreens() 110 | ->loadCliCommands(); 111 | } 112 | 113 | /** 114 | * Load custom database table installers from configuration files. 115 | * 116 | * @return \Tev\Plugin\Loader This, for chaining 117 | */ 118 | protected function loadCustomTables() 119 | { 120 | if ($config = $this->loadConfigFile('tables.php')) { 121 | $app = $this->app; 122 | 123 | foreach ($config as $installerClass) { 124 | if (is_string($installerClass) && is_subclass_of($installerClass, 'Tev\Database\CustomTables\AbstractInstaller')) { 125 | register_activation_hook($this->getPluginFile(), function () use ($installerClass, $app) { 126 | global $wpdb; 127 | $installer = new $installerClass($wpdb, $app); 128 | $installer->install(); 129 | }); 130 | 131 | add_action('plugins_loaded', function () use ($installerClass, $app) { 132 | global $wpdb; 133 | $installer = new $installerClass($wpdb, $app); 134 | $installer->update(); 135 | }); 136 | } 137 | } 138 | } 139 | 140 | return $this; 141 | } 142 | 143 | /** 144 | * Load custom post types from configuration files. 145 | * 146 | * @return \Tev\Plugin\Loader This, for chaining 147 | */ 148 | protected function loadPostTypes() 149 | { 150 | if ($config = $this->loadConfigFile('post_types.php')) { 151 | $callbacks = array(); 152 | 153 | // Create one callback for each post type, and register in 154 | // init action 155 | 156 | foreach ($config as $postTypeName => $args) { 157 | $callbacks[] = $cb = function () use ($postTypeName, $args) { 158 | register_post_type($postTypeName, $args); 159 | }; 160 | 161 | add_action('init', $cb, 0); 162 | } 163 | 164 | // Flush URL caches for (you need to register custom post types 165 | // first) 166 | 167 | register_activation_hook($this->getPluginFile(), function () use ($callbacks) { 168 | foreach ($callbacks as $cb) { 169 | $cb(); 170 | } 171 | 172 | flush_rewrite_rules(); 173 | }); 174 | } 175 | 176 | return $this; 177 | } 178 | 179 | /** 180 | * Load custom field groups from configuration files. 181 | * 182 | * @return \Tev\Plugin\Loader This, for chaining 183 | */ 184 | protected function loadFieldGroups() 185 | { 186 | if (function_exists('register_field_group') && ($config = $this->loadConfigFile('field_groups.php'))) { 187 | foreach ($config as $fieldGroupConfig) { 188 | register_field_group($fieldGroupConfig); 189 | } 190 | } 191 | 192 | return $this; 193 | } 194 | 195 | /** 196 | * Load actions from configuration providers. 197 | * 198 | * @return \Tev\Plugin\Loader This, for chaining 199 | */ 200 | protected function loadActions() 201 | { 202 | if ($config = $this->loadConfigFile('actions.php')) { 203 | $app = $this->app; 204 | $renderer = $this->renderer; 205 | 206 | foreach ($config as $actionName => $provider) { 207 | if (is_string($provider) && is_subclass_of($provider, 'Tev\Plugin\Action\AbstractProvider')) { 208 | $ap = new $provider($this->app, $this->renderer); 209 | 210 | add_action($actionName, function () use ($ap) { 211 | return call_user_func_array(array($ap, 'action'), func_get_args()); 212 | }, $ap->priority(), $ap->numArgs()); 213 | } elseif ($provider instanceof Closure) { 214 | add_action($actionName, function () use ($provider) 215 | { 216 | return call_user_func($provider, func_get_args()); 217 | }); 218 | } 219 | } 220 | } 221 | 222 | return $this; 223 | } 224 | 225 | /** 226 | * Load ACF JSON config if supplied. 227 | * 228 | * @return \Tev\Plugin\Loader This, for chaining 229 | */ 230 | protected function loadAcfJson() 231 | { 232 | $config = $this->getConfigPath() . '/acf-json'; 233 | 234 | if (file_exists($config)) { 235 | add_filter('acf/settings/load_json', function ($paths) use ($config) { 236 | $paths[] = $config; 237 | return $paths; 238 | }); 239 | } 240 | 241 | return $this; 242 | } 243 | 244 | /** 245 | * Load shortcodes from configuration files. 246 | * 247 | * @return \Tev\Plugin\Loader This, for chaining 248 | */ 249 | protected function loadShortCodes() 250 | { 251 | if ($config = $this->loadConfigFile('shortcodes.php')) { 252 | $renderer = $this->renderer; 253 | $app = $this->app; 254 | 255 | foreach ($config as $shortcode => $provider) { 256 | add_shortcode($shortcode, function ($attrs, $content) use ($app, $provider, $renderer) 257 | { 258 | if (is_string($provider) && is_subclass_of($provider, 'Tev\Plugin\Shortcode\AbstractProvider')) { 259 | $sp = new $provider($app, $renderer); 260 | return $sp->shortcode($attrs, $content); 261 | } elseif ($provider instanceof Closure) { 262 | return $provider($attrs, $content, $renderer); 263 | } 264 | }); 265 | } 266 | } 267 | 268 | return $this; 269 | } 270 | 271 | /** 272 | * Load custom option screens from configuration files. 273 | * 274 | * @return \Tev\Plugin\Loader This, for chaining 275 | */ 276 | protected function loadOptionScreens() 277 | { 278 | if (function_exists('acf_add_options_page') && ($config = $this->loadConfigFile('option_screens.php'))) { 279 | foreach ($config as $optionScreenConfig) { 280 | acf_add_options_page($optionScreenConfig); 281 | } 282 | } 283 | 284 | return $this; 285 | } 286 | 287 | /** 288 | * Load custom WP CLI commands from configuration files. 289 | * 290 | * @return \Tev\Plugin\Loader This, for chaining 291 | */ 292 | protected function loadCliCommands() 293 | { 294 | if (defined('WP_CLI') && WP_CLI && ($config = $this->loadConfigFile('commands.php'))) { 295 | foreach ($config as $command => $className) { 296 | \WP_CLI::add_command($command, $className); 297 | } 298 | } 299 | 300 | return $this; 301 | } 302 | 303 | /** 304 | * Get the path to the config directory. 305 | * 306 | * @return string 307 | */ 308 | protected function getConfigPath() 309 | { 310 | return $this->basePath . '/config'; 311 | } 312 | 313 | /** 314 | * Get the path to the config directory. 315 | * 316 | * @return string 317 | */ 318 | protected function getSrcPath() 319 | { 320 | return $this->basePath . '/src'; 321 | } 322 | 323 | /** 324 | * Get the path to the config directory. 325 | * 326 | * @return string 327 | */ 328 | protected function getViewsPath() 329 | { 330 | return $this->basePath . '/src/views'; 331 | } 332 | 333 | /** 334 | * Get the full path and filename of the plugin's registration file. 335 | * 336 | * @return string 337 | */ 338 | protected function getPluginFile() 339 | { 340 | return $this->basePath . '/' . strtolower(basename($this->basePath)) . '.php'; 341 | } 342 | 343 | /** 344 | * Load a config file from the config directory. 345 | * 346 | * @param string $file Filename 347 | * @return array|null Array config or null if not found 348 | */ 349 | protected function loadConfigFile($file) 350 | { 351 | $path = $this->getConfigPath() . '/' . $file; 352 | 353 | if (file_exists($path)) { 354 | $config = include $path; 355 | return is_array($config) ? $config : null; 356 | } 357 | 358 | return null; 359 | } 360 | } 361 | -------------------------------------------------------------------------------- /src/Tev/Plugin/Shortcode/AbstractProvider.php: -------------------------------------------------------------------------------- 1 | app = $app; 40 | $this->renderer = $renderer; 41 | } 42 | 43 | /** 44 | * Generate a shortcode. 45 | * 46 | * @param array $attrs Shortcode attributes 47 | * @param string $content Shortcode content 48 | * @return string Shortcode content 49 | */ 50 | abstract public function shortcode($attrs, $content); 51 | } 52 | -------------------------------------------------------------------------------- /src/Tev/Post/Factory.php: -------------------------------------------------------------------------------- 1 | authorFactory = $authorFactory; 63 | $this->taxonomyFactory = $taxonomyFactory; 64 | $this->fieldFactory = $fieldFactory; 65 | $this->registeredMappings = array(); 66 | } 67 | 68 | /** 69 | * Register a new post type to class name mapping. 70 | * 71 | * @param string $postType Post type identifier 72 | * @param string $className Class name to instantiate for $postType. Class 73 | * must inherit from `\Tev\Post\Model\AbstractPost` 74 | * @return \Tev\Post\Factory This, for chaining 75 | * 76 | * @throws \Exception If class name does not inherit from `\Tev\Post\Model\AbstractPost` 77 | */ 78 | public function register($postType, $className) 79 | { 80 | if (!is_subclass_of($className, 'Tev\Post\Model\AbstractPost')) { 81 | throw new Exception("Given class '$className' is not instance of 'Tev\Post\Model\AbstractPost'"); 82 | } 83 | 84 | $this->registeredMappings[$postType] = $className; 85 | 86 | return $this; 87 | } 88 | 89 | /** 90 | * Get the registered class name for the given post type. 91 | * 92 | * @param string $postType Post type identifier 93 | * @return string|null Class name or null if no registration 94 | */ 95 | public function registered($postType) 96 | { 97 | if (isset($this->registeredMappings[$postType])) { 98 | return $this->registeredMappings[$postType]; 99 | } 100 | 101 | return null; 102 | } 103 | 104 | /** 105 | * Instantiate a post entity from a given post object. 106 | * 107 | * @param \WP_Post $base Optional. Base post object. If not 108 | * supplied will attempt to get the current 109 | * post from The Loop 110 | * @param string $className Optional. Class name to instantiate. Will 111 | * use registered default if not supplied 112 | * @return \Tev\Post\Model\AbstractPost Post entity 113 | * 114 | * @throws \Exception If $base is not given and not in the The Loop 115 | */ 116 | public function create($base = null, $className = null) 117 | { 118 | if ($base === null) { 119 | if (have_posts()) { 120 | the_post(); 121 | $base = get_post(); 122 | } else { 123 | throw new Exception('Please supply a post object when not within The Loop'); 124 | } 125 | } 126 | 127 | $cls = 'Tev\Post\Model\Post'; 128 | 129 | if (($className !== null) && is_subclass_of($className, 'Tev\Post\Model\AbstractPost')) { 130 | $cls = $className; 131 | } elseif ($className = $this->registered($base->post_type)) { 132 | $cls = $className; 133 | } 134 | 135 | return new $cls($base, $this->authorFactory, $this->taxonomyFactory, $this->fieldFactory, $this); 136 | } 137 | 138 | /** 139 | * Instantiate a post entity from the current post object. 140 | * 141 | * Only works within The Loop. 142 | * 143 | * @param string $className Optional. Class name to instantiate. Will 144 | * use registered default if not supplied 145 | * @return \Tev\Post\Model\AbstractPost Post entity 146 | * 147 | * @throws \Exception If not in the The Loop 148 | */ 149 | public function current($className = null) 150 | { 151 | if ($p = get_post()) { 152 | return $this->create($p, $className); 153 | } else { 154 | throw new Exception('This method can only be called within The Loop'); 155 | } 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /src/Tev/Post/Model/AbstractPost.php: -------------------------------------------------------------------------------- 1 | base = $base; 107 | $this->authorFactory = $authorFactory; 108 | $this->taxonomyFactory = $taxonomyFactory; 109 | $this->fieldFactory = $fieldFactory; 110 | $this->postFactory = $postFactory; 111 | $this->author = null; 112 | $this->featuredImage = null; 113 | $this->postTypeObj = null; 114 | $this->parent = null; 115 | } 116 | 117 | /** 118 | * Get the post ID. 119 | * 120 | * @return int 121 | */ 122 | public function getId() 123 | { 124 | return $this->base->ID; 125 | } 126 | 127 | /** 128 | * Get the post type. 129 | * 130 | * @return string 131 | */ 132 | public function getType() 133 | { 134 | return $this->base->post_type; 135 | } 136 | 137 | /** 138 | * Get the singular name for the post type of this post. 139 | * 140 | * @return string 141 | */ 142 | public function getTypeName() 143 | { 144 | return $this->getTypeLabels()->singular_name; 145 | } 146 | 147 | /** 148 | * Get the post type labels for this post. 149 | * 150 | * See: 151 | * 152 | * http://codex.wordpress.org/Function_Reference/get_post_type_object 153 | * 154 | * for the list of labels returned. 155 | * 156 | * @return \stdClass 157 | */ 158 | public function getTypeLabels() 159 | { 160 | return $this->getPostTypeObject()->labels; 161 | } 162 | 163 | /** 164 | * Get the post slug. 165 | * 166 | * @return string 167 | */ 168 | public function getName() 169 | { 170 | return $this->base->post_name; 171 | } 172 | 173 | /** 174 | * Get the post status. One of: 175 | * 176 | * - publish 177 | * - pending 178 | * - draft 179 | * - auto-draft 180 | * - future 181 | * - private 182 | * - inherit 183 | * - trash 184 | * 185 | * @return string 186 | */ 187 | public function getStatus() 188 | { 189 | return $this->base->post_status; 190 | } 191 | 192 | /** 193 | * Get the post title. 194 | * 195 | * @return string 196 | */ 197 | public function getTitle() 198 | { 199 | return $this->base->post_title; 200 | } 201 | 202 | /** 203 | * Get the post content, formatted as HTML. 204 | * 205 | * @return string 206 | */ 207 | public function getContent() 208 | { 209 | $content = $this->getRawContent(); 210 | $content = apply_filters('the_content', $content); 211 | $content = str_replace( ']]>', ']]>', $content); 212 | 213 | return $content; 214 | } 215 | 216 | /** 217 | * Get the raw post content. 218 | * 219 | * @return string 220 | */ 221 | public function getRawContent() 222 | { 223 | return $this->base->post_content; 224 | } 225 | 226 | /** 227 | * Check if this post has a manually set excerpt. 228 | * 229 | * @return boolean True if has excerpt, false if not 230 | */ 231 | public function hasExcerpt() 232 | { 233 | return (boolean) strlen($this->base->post_excerpt); 234 | } 235 | 236 | /** 237 | * Get the post excerpt. 238 | * 239 | * Adapted from wp_trim_excerpt() in wp-includes/formatting.php. 240 | * 241 | * @param string|null $content Optional. Base content to use for excerpt 242 | * if post excerpt isn't defined. Defaults to 243 | * post content 244 | * @return string 245 | */ 246 | public function getExcerpt($content = null) 247 | { 248 | $raw = $exc = $this->base->post_excerpt; 249 | 250 | if (!strlen($exc)) { 251 | $exc = $content === null ? $this->getRawContent() : $content; 252 | $exc = strip_shortcodes($exc); 253 | $exc = apply_filters('the_content', $exc); 254 | $exc = str_replace(']]>', ']]>', $exc); 255 | $exc = wp_trim_words( 256 | $exc, 257 | apply_filters('excerpt_length', 55), 258 | apply_filters('excerpt_more', ' ' . '[…]') 259 | ); 260 | } 261 | 262 | return apply_filters('wp_trim_excerpt', $exc, $raw); 263 | } 264 | 265 | /** 266 | * Get post URL. 267 | * 268 | * @param array $query Query string args. Key value pairs 269 | * @return string 270 | */ 271 | public function getUrl($query = array()) 272 | { 273 | return add_query_arg($query, get_permalink($this->getId())); 274 | } 275 | 276 | /** 277 | * Get this Post's featured image. 278 | * 279 | * @return \Tev\Post\Model\Attachment|false Featured image object, or false if not 280 | * set 281 | */ 282 | public function getFeaturedImage() 283 | { 284 | if ($this->featuredImage === null) { 285 | if ($imgId = get_post_thumbnail_id((int) $this->getId())) { 286 | $this->featuredImage = $this->postFactory->create(get_post($imgId)); 287 | } else { 288 | $this->featuredImage = false; 289 | } 290 | } 291 | 292 | return $this->featuredImage; 293 | } 294 | 295 | /** 296 | * Get Post featured image URL. 297 | * 298 | * Convenient alias for: 299 | * 300 | * ``` 301 | * $pos->getFeaturedImage()->getImageUrl($size); 302 | * ``` 303 | * 304 | * @param mixed $size Arguments accepted by second param of wp_get_attachment_image_src() 305 | * @return string|null Image URL, or null if no image set 306 | */ 307 | public function getFeaturedImageUrl($size = 'thumbnail') 308 | { 309 | if ($img = $this->getFeaturedImage()) { 310 | return $img->getImageUrl($size); 311 | } 312 | 313 | return null; 314 | } 315 | 316 | /** 317 | * Get the post author ID. 318 | * 319 | * @return string 320 | */ 321 | public function getAuthorId() 322 | { 323 | return $this->base->post_author; 324 | } 325 | 326 | /** 327 | * Get post author. 328 | * 329 | * @return \Tev\Author\Model\Author 330 | */ 331 | public function getAuthor() 332 | { 333 | if ($this->author === null) { 334 | $this->author = $this->authorFactory->create($this->getAuthorId()); 335 | } 336 | 337 | return $this->author; 338 | } 339 | 340 | /** 341 | * Get the parent post ID. 342 | * 343 | * @return int|null Parent post ID or null if no parent 344 | */ 345 | public function getParentPostId() 346 | { 347 | return $this->base->post_parent ?: null; 348 | } 349 | 350 | /** 351 | * Get the parent post, if set. 352 | * 353 | * @return \Tev\Post\Model\AbstractPost|null 354 | */ 355 | public function getParent() 356 | { 357 | if ($this->parent === null) { 358 | if (($parentId = $this->getParentPostId()) !== null) { 359 | $this->parent = $this->postFactory->create(get_post($parentId)); 360 | } else { 361 | $this->parent = false; 362 | } 363 | } 364 | 365 | return $this->parent ?: null; 366 | } 367 | 368 | /** 369 | * Get the post published date. 370 | * 371 | * @return \Carbon\Carbon 372 | */ 373 | public function getPublishedDate() 374 | { 375 | return Carbon::createFromFormat('Y-m-d H:i:s', $this->base->post_date); 376 | } 377 | 378 | /** 379 | * Get the post published date in GMT. 380 | * 381 | * @return \Carbon\Carbon 382 | */ 383 | public function getPublishedDateGmt() 384 | { 385 | return Carbon::createFromFormat( 386 | 'Y-m-d H:i:s', 387 | $this->base->post_date_gmt, 388 | 'UTC' 389 | ); 390 | } 391 | 392 | /** 393 | * Get the post modified date. 394 | * 395 | * @return \Carbon\Carbon 396 | */ 397 | public function getModifiedDate() 398 | { 399 | return Carbon::createFromFormat('Y-m-d H:i:s', $this->base->post_modified); 400 | } 401 | 402 | /** 403 | * Get the post modified date in GMT. 404 | * 405 | * @return \Carbon\Carbon 406 | */ 407 | public function getModifiedDateGmt() 408 | { 409 | return Carbon::createFromFormat( 410 | 'Y-m-d H:i:s', 411 | $this->base->post_modified_gmt, 412 | 'UTC' 413 | ); 414 | } 415 | 416 | /** 417 | * Get all categories for this post. 418 | * 419 | * @return Tev\Term\Model\Term[] Array of categories 420 | */ 421 | public function getCategories() 422 | { 423 | return $this->getTermsFor('category'); 424 | } 425 | 426 | /** 427 | * Get all tags for this post. 428 | * 429 | * @return Tev\Term\Model\Term[] Array of tags 430 | */ 431 | public function getTags() 432 | { 433 | return $this->getTermsFor('post_tag'); 434 | } 435 | 436 | /** 437 | * Get all terms for this post in the given taxonomy. 438 | * 439 | * @param mixed|\Tev\Taxonomy\Model\Taxonomy $taxonomy Taxonomy name or object 440 | * @return \Tev\Term\Model\Term[] Array of terms 441 | */ 442 | public function getTermsFor($taxonomy) 443 | { 444 | if (!($taxonomy instanceof Taxonomy)) { 445 | $taxonomy = $this->taxonomyFactory->create($taxonomy); 446 | } 447 | 448 | return $taxonomy->getTermsForPost($this); 449 | } 450 | 451 | /** 452 | * Get a meta value on this post. 453 | * 454 | * See: http://codex.wordpress.org/Function_Reference/get_post_meta for 455 | * how `$single` works. 456 | * 457 | * @param string $key Meta item key 458 | * @param boolean $single Optional. Defaults to true 459 | * @return mixed Meta data 460 | */ 461 | public function meta($key, $single = true) 462 | { 463 | return get_post_meta($this->getId(), $key, $single); 464 | } 465 | 466 | /** 467 | * Get a custom field on this post. 468 | * 469 | * @param string $name Field name (or key) 470 | * @return \Tev\Field\Model\AbstractField Field object 471 | */ 472 | public function field($name) 473 | { 474 | return $this->fieldFactory->create($name, $this); 475 | } 476 | 477 | /** 478 | * Get the underlying `WP_Post` object. 479 | * 480 | * @return \WP_Post 481 | */ 482 | public function getBaseObject() 483 | { 484 | return $this->base; 485 | } 486 | 487 | /** 488 | * Get the post type object for this post. 489 | * 490 | * Contains information about the post type of this post. 491 | * 492 | * @return \stdClass 493 | */ 494 | protected function getPostTypeObject() 495 | { 496 | if ($this->postTypeObj === null) { 497 | $this->postTypeObj = get_post_type_object($this->getType()); 498 | } 499 | 500 | return $this->postTypeObj; 501 | } 502 | } 503 | -------------------------------------------------------------------------------- /src/Tev/Post/Model/Attachment.php: -------------------------------------------------------------------------------- 1 | getId()); 17 | } 18 | 19 | 20 | /** 21 | * Get attachment file. 22 | * 23 | * @return string 24 | */ 25 | public function getAttachmentFile() 26 | { 27 | return get_attached_file($this->getId()); 28 | } 29 | 30 | /** 31 | * Get attachment image URL. 32 | * 33 | * @param mixed $size Arguments accepted by second param of wp_get_attachment_image_src() 34 | * @return string|null Image URL or null if not found 35 | */ 36 | public function getImageUrl($size = 'thumbnail') 37 | { 38 | $res = wp_get_attachment_image_src($this->getId(), $size); 39 | return $res && isset($res[0]) ? $res[0] : null; 40 | } 41 | 42 | /** 43 | * Get alt text for this attachment. 44 | * 45 | * @return string 46 | */ 47 | public function getAlt() 48 | { 49 | return $this->meta('_wp_attachment_image_alt'); 50 | } 51 | 52 | /** 53 | * Get caption for this attachment. 54 | * 55 | * @return string 56 | */ 57 | public function getCaption() 58 | { 59 | return $this->getExcerpt(); 60 | } 61 | 62 | /** 63 | * Get description for this attachment. 64 | * 65 | * @return string 66 | */ 67 | public function getDescription() 68 | { 69 | return $this->getContent(); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/Tev/Post/Model/Page.php: -------------------------------------------------------------------------------- 1 | postFactory = $postFactory; 32 | } 33 | 34 | /** 35 | * Get all posts of the given type. 36 | * 37 | * @param string $type Post type 38 | * @param array $queryVars Extra query vars. See the params passed to WP_Query 39 | * @return \Tev\Post\Model\AbstractPost[] Post objects 40 | */ 41 | public function getAllByPostType($type, array $queryVars = array()) 42 | { 43 | $q = new WP_Query(array_merge(array( 44 | 'post_type' => $type, 45 | 'nopaging' => true 46 | ), $queryVars)); 47 | 48 | $posts = array(); 49 | 50 | foreach ($q->get_posts() as $p) { 51 | $posts[] = $this->postFactory->create($p); 52 | } 53 | 54 | return $posts; 55 | } 56 | 57 | /** 58 | * Get array of post years, in descending order. 59 | * 60 | * @return array 61 | */ 62 | public function getYears() 63 | { 64 | $years = array(); 65 | 66 | foreach ($this->getAllByPostType('post') as $post) { 67 | $year = (int) $post->getPublishedDate()->format('Y'); 68 | 69 | if (!in_array($year, $years)) { 70 | $years[] = $year; 71 | } 72 | } 73 | 74 | rsort($years); 75 | 76 | return $years; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/Tev/Taxonomy/Factory.php: -------------------------------------------------------------------------------- 1 | termFactory = $termFactory; 34 | } 35 | 36 | /** 37 | * Create a new Taxonomy object. 38 | * 39 | * @param string|\stdClass $taxonomy Taxonomy name or object 40 | * @return \Tev\Taxonomy\Model\Taxonomy Created taxonomy 41 | */ 42 | public function create($taxonomy) 43 | { 44 | if (!($taxonomy instanceof stdClass)) { 45 | $taxonomy = get_taxonomy($taxonomy); 46 | } 47 | 48 | return new Taxonomy($taxonomy, $this->termFactory); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/Tev/Taxonomy/Model/Taxonomy.php: -------------------------------------------------------------------------------- 1 | base = $base; 42 | $this->termFactory = $termFactory; 43 | } 44 | 45 | /** 46 | * Get the taxonomy name. 47 | * 48 | * @return string 49 | */ 50 | public function getName() 51 | { 52 | return $this->base->name; 53 | } 54 | 55 | /** 56 | * Get the taxonomy plural label. 57 | * 58 | * @return string 59 | */ 60 | public function getPluralLabel() 61 | { 62 | return $this->base->label; 63 | } 64 | 65 | /** 66 | * Get the taxonomy singular label. 67 | * 68 | * @return string 69 | */ 70 | public function getSingularLabel() 71 | { 72 | return $this->base->singular_label; 73 | } 74 | 75 | /** 76 | * Get all Terms in this taxonomy. 77 | * 78 | * @param boolean $topLevel Whether or not to just get top level terms 79 | * @param array $options Optional. Same options as passed to `get_terms()` 80 | * @return \Tev\Term\Model\Term[] Array of terms 81 | */ 82 | public function getTerms($topLevel = false, $options = array()) 83 | { 84 | $terms = array(); 85 | 86 | $wpTerms = get_terms($this->getName(), array_merge(array( 87 | 'hide_empty' => false, 88 | 'parent' => $topLevel ? 0 : '' 89 | ), $options)); 90 | 91 | foreach ($wpTerms as $termData) { 92 | $terms[] = $this->termFactory->create($termData, $this); 93 | } 94 | 95 | return $terms; 96 | } 97 | 98 | /** 99 | * Get all terms in this taxonomy for the given post. 100 | * 101 | * @param \Tev\Post\Model\AbstractPost $post Post object to get terms for 102 | * @return array[\Tev\Term\Model\Term] Array of terms 103 | */ 104 | public function getTermsForPost(AbstractPost $post) 105 | { 106 | $terms = array(); 107 | 108 | if ($dbTerms = get_the_terms($post->getId(), $this->getName())) { 109 | foreach ($dbTerms as $termData) { 110 | $terms[] = $this->termFactory->create($termData, $this); 111 | } 112 | } 113 | 114 | return $terms; 115 | } 116 | 117 | /** 118 | * Check whether or not this taxonomy is heirarchical. 119 | * 120 | * @return boolean True if heirarchical, false if not 121 | */ 122 | public function isHierarchical() 123 | { 124 | return (boolean) $this->base->hierarchical; 125 | } 126 | 127 | /** 128 | * Get the underlying taxonomy object. 129 | * 130 | * @return \stdClass 131 | */ 132 | public function getBaseObject() 133 | { 134 | return $this->base; 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /src/Tev/Taxonomy/Repository/TaxonomyRepository.php: -------------------------------------------------------------------------------- 1 | taxonomyFactory = $taxonomyFactory; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/Tev/Term/Factory.php: -------------------------------------------------------------------------------- 1 | getName()); 31 | } 32 | 33 | if ($term && !($term instanceof WP_Error)) { 34 | return new Term($term, $taxonomy, $this); 35 | } else { 36 | throw new Exception('Term not found'); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/Tev/Term/Model/Term.php: -------------------------------------------------------------------------------- 1 | base = $base; 60 | $this->taxonomy = $taxonomy; 61 | $this->termFactory = $termFactory; 62 | $this->parent = null; 63 | } 64 | 65 | /** 66 | * Get the term ID. 67 | * 68 | * @return int 69 | */ 70 | public function getId() 71 | { 72 | return $this->base->term_id; 73 | } 74 | 75 | /** 76 | * Get the term name. 77 | * 78 | * @return string 79 | */ 80 | public function getName() 81 | { 82 | return $this->base->name; 83 | } 84 | 85 | /** 86 | * Get the term slug. 87 | * 88 | * @return string 89 | */ 90 | public function getSlug() 91 | { 92 | return $this->base->slug; 93 | } 94 | 95 | /** 96 | * Get the taxonomy for this term. 97 | * 98 | * @return \Tev\Taxonomy\Model\Taxonomy 99 | */ 100 | public function getTaxonomy() 101 | { 102 | return $this->taxonomy; 103 | } 104 | 105 | /** 106 | * Get the term description. 107 | * 108 | * @return string 109 | */ 110 | public function getDescription() 111 | { 112 | return $this->base->description; 113 | } 114 | 115 | /** 116 | * Get term URL. 117 | * 118 | * @param array $query Query string args. Key value pairs 119 | * @return string 120 | */ 121 | public function getUrl($query = array()) 122 | { 123 | return add_query_arg($query, get_term_link($this->base)); 124 | } 125 | 126 | /** 127 | * Get the parent term ID of this term. 128 | * 129 | * @return int|null 130 | */ 131 | public function getParentId() 132 | { 133 | return $this->base->parent ?: null; 134 | } 135 | 136 | /** 137 | * Get the parent term of this term. 138 | * 139 | * @return \Tev\Term\Model\Term 140 | */ 141 | public function getParent() 142 | { 143 | if (($this->parent === null) && $this->getParentId()) { 144 | $this->parent = $this->termFactory->create($this->getParentId(), $this->taxonomy); 145 | } 146 | 147 | return $this->parent; 148 | } 149 | 150 | /** 151 | * Check whether or not this Term has a parent Term. 152 | * 153 | * @return boolean True if has a parent, false if not 154 | */ 155 | public function hasParent() 156 | { 157 | return (boolean) $this->getParentId(); 158 | } 159 | 160 | /** 161 | * Return array of direct child terms of this term. 162 | * 163 | * @return \Tev\Term\Model\Term[] 164 | */ 165 | public function getChildren() 166 | { 167 | $children = array(); 168 | 169 | $wpTerms = get_terms($this->taxonomy->getName(), array( 170 | 'hide_empty' => false, 171 | 'parent' => (int) $this->getId() 172 | )); 173 | 174 | foreach ($wpTerms as $termData) { 175 | $children[] = $this->termFactory->create($termData, $this->taxonomy); 176 | } 177 | 178 | return $children; 179 | } 180 | 181 | /** 182 | * Get the underlying term object. 183 | * 184 | * @return \WP_Term 185 | */ 186 | public function getBaseObject() 187 | { 188 | return $this->base; 189 | } 190 | } 191 | -------------------------------------------------------------------------------- /src/Tev/Term/Repository/TermRepository.php: -------------------------------------------------------------------------------- 1 | taxonomyFactory = $taxonomyFactory; 42 | $this->termFactory = $termFactory; 43 | } 44 | 45 | /** 46 | * Get all terms in the given taxonomy. 47 | * 48 | * @param string|\Tev\Taxonomy\Model\Taxonomy $taxonomy Taxonomy object or name 49 | * @return \Tev\Term\Model\Term[] 50 | */ 51 | public function getByTaxonomy($taxonomy) 52 | { 53 | if (!($taxonomy instanceof Taxonomy)) { 54 | $taxonomy = $this->taxonomyFactory->create($taxonomy); 55 | } 56 | 57 | return $this->convertTermsArray(get_terms($taxonomy->getName(), array( 58 | 'hide_empty' => false 59 | )), $taxonomy); 60 | } 61 | 62 | /** 63 | * Convert an array of Wordpress term objects to array of Term objects. 64 | * 65 | * @param \stdClass[] $terms Wordpress term objects 66 | * @param \Tev\Taxonomy\Model\Taxonomy $taxonomy Parent taxonomy 67 | * @return \Tev\Term\Model\Term[] Term objects 68 | */ 69 | private function convertTermsArray($terms, Taxonomy $taxonomy) 70 | { 71 | $res = array(); 72 | 73 | foreach ($terms as $t) { 74 | $res[] = $this->termFactory->create($t, $taxonomy); 75 | } 76 | 77 | return $res; 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/Tev/Util/TemplateExtras.php: -------------------------------------------------------------------------------- 1 | postFactory = $postFactory; 31 | } 32 | 33 | /** 34 | * Generate an array of breadcrumbs, based on the current page. 35 | * 36 | * Each item in the array is a hash containing 'title' and 'url' (which 37 | * might be null). 38 | * 39 | * You can use this to generate nice breadcrumb lists in your page. 40 | * 41 | * @param \Closure|\Closure[] Optional. Set of user-defined callbacks that can 42 | * return breadcrumbs. If any of the supplied callbacks 43 | * return data, then their data will be used rather than 44 | * the defaults supplied by this method. Note that 45 | * the initial 'Home' crumb is always included 46 | * @return array 47 | */ 48 | public function breadcrumbs($callbacks = null) 49 | { 50 | $breadcrumbs = array(); 51 | 52 | // Load blog page - it's used later, if configured 53 | 54 | $blogPage = null; 55 | if ($bp = get_page(get_option('page_for_posts'))) { 56 | $blogPage = $this->postFactory->create($bp); 57 | } 58 | 59 | // Home page is always present in breadcrumb trail 60 | 61 | $breadcrumbs[] = array( 62 | 'title' => 'Home', 63 | 'url' => !is_front_page() ? get_option('home') : null 64 | ); 65 | 66 | // Check if we've got any custom user defined crumbs 67 | 68 | if ($callbacks !== null) { 69 | if (!is_array($callbacks)) { 70 | $callbacks = array($callbacks); 71 | } 72 | 73 | foreach ($callbacks as $cb) { 74 | if ($cb instanceof Closure) { 75 | if ($res = $cb()) { 76 | $breadcrumbs[] = $res; 77 | } 78 | } 79 | } 80 | } 81 | 82 | // If we haven't got any user crumbs, proceed with default logic 83 | 84 | if (count($breadcrumbs) === 1) { 85 | 86 | // Blog home page (when different to front page) 87 | 88 | if ($blogPage && is_home() && !is_front_page()) { 89 | $breadcrumbs[] = array( 90 | 'title' => $blogPage->getTitle(), 91 | 'url' => null 92 | ); 93 | } 94 | 95 | // Custom post type archive page, no filtering 96 | 97 | if (is_archive()) { 98 | 99 | // Custom post type archive 100 | 101 | if (is_post_type_archive()) { 102 | if (!$this->isFilteredArchive()) { 103 | $breadcrumbs[] = array( 104 | 'title' => post_type_archive_title('', false), 105 | 'url' => null 106 | ); 107 | } else { 108 | $breadcrumbs[] = array( 109 | 'title' => post_type_archive_title('', false), 110 | 'url' => get_post_type_archive_link(get_post_type()) 111 | ); 112 | 113 | $breadcrumbs[] = array( 114 | 'title' => $this->archiveTitle(), 115 | 'url' => null 116 | ); 117 | } 118 | } 119 | 120 | // Normal post archive 121 | 122 | else { 123 | if ($blogPage && !$this->isBlogPageHome()) { 124 | $breadcrumbs[] = array( 125 | 'title' => $blogPage->getTitle(), 126 | 'url' => $blogPage->getUrl() 127 | ); 128 | } 129 | 130 | if ($this->isFilteredArchive()) { 131 | $breadcrumbs[] = array( 132 | 'title' => $this->archiveTitle(), 133 | 'url' => null 134 | ); 135 | } 136 | } 137 | } 138 | 139 | // Search page 140 | 141 | if (is_search()) { 142 | $breadcrumbs[] = array( 143 | 'title' => 'Search', 144 | 'url' => null 145 | ); 146 | } 147 | 148 | // 404 page 149 | 150 | if (is_404()) { 151 | $breadcrumbs[] = array( 152 | 'title' => '404 Not Found', 153 | 'url' => null 154 | ); 155 | } 156 | 157 | // Single post 158 | 159 | if (is_single()) { 160 | if (is_singular('post')) { 161 | if ($blogPage && !$this->isBlogPageHome()) { 162 | $breadcrumbs[] = array( 163 | 'title' => $blogPage->getTitle(), 164 | 'url' => $blogPage->getUrl() 165 | ); 166 | } 167 | } else { 168 | $postType = get_post_type(); 169 | 170 | $breadcrumbs[] = array( 171 | 'title' => get_post_type_object($postType)->labels->name, 172 | 'url' => get_post_type_archive_link($postType) 173 | ); 174 | } 175 | 176 | $breadcrumbs[] = array( 177 | 'title' => get_the_title(), 178 | 'url' => null 179 | ); 180 | } 181 | 182 | // Single Page 183 | 184 | if (is_page()) { 185 | global $post; 186 | 187 | $page = $post; 188 | 189 | $pageTree = array( 190 | array( 191 | 'title' => $page->post_title, 192 | 'url' => null 193 | ) 194 | ); 195 | 196 | while ($parentId = $page->post_parent) { 197 | $page = get_post($parentId); 198 | 199 | array_unshift($pageTree, array( 200 | 'title' => $page->post_title, 201 | 'url' => get_permalink($parentId) 202 | )); 203 | } 204 | 205 | foreach ($pageTree as $t) { 206 | $breadcrumbs[] = $t; 207 | } 208 | } 209 | } 210 | 211 | return $breadcrumbs; 212 | } 213 | 214 | /** 215 | * Render a view partial. 216 | * 217 | * @param string $file Partial file name 218 | * @param array $params Optional variables to make available to partial 219 | * @return string Rendered partial 220 | */ 221 | public function partial($file, array $variables = array()) 222 | { 223 | extract($variables); 224 | 225 | if (substr($file, -4) !== '.php') { 226 | $file .= '.php'; 227 | } 228 | 229 | $view = ''; 230 | 231 | if ($tmpl = locate_template($file)) { 232 | ob_start(); 233 | include($tmpl); 234 | $view = ob_get_contents(); 235 | ob_end_clean(); 236 | } 237 | 238 | return $view; 239 | } 240 | 241 | /** 242 | * Get a nice title for an archive page. 243 | * 244 | * Will return nice titles for taxonomy or date archive pages. 245 | * 246 | * Optionally config this method with formatting: 247 | * 248 | * - default ('Archive'): Title to display if archive page is not date or taxonomy 249 | * - prefix (''): Text to prefix your title with 250 | * - suffix (''): Text to suffix your title with 251 | * - year_format ('Y'): Date format for 'year' archive pages 252 | * - month_format ('F Y'): Date format for 'month' archive pages 253 | * - day_format ('js F Y'): Date format for 'day' archive pages 254 | * 255 | * @param array $config Optional formatting config, as described above 256 | * @return string Archive title 257 | */ 258 | public function archiveTitle(array $config = array()) 259 | { 260 | $defaults = array( 261 | 'default' => 'Archive', 262 | 'prefix' => '', 263 | 'suffix' => '', 264 | 'year_format' => 'Y', 265 | 'month_format' => 'F Y', 266 | 'day_format' => 'jS F Y' 267 | ); 268 | 269 | $config = array_merge($defaults, $config); 270 | 271 | $title = $config['default']; 272 | 273 | if (is_category() || is_tag() || is_tax()) { 274 | $title = single_term_title('', false); 275 | } elseif (is_year()) { 276 | $title = Carbon::create( 277 | get_query_var('year') 278 | )->format($config['year_format']); 279 | } elseif (is_month()) { 280 | $title = Carbon::create( 281 | get_query_var('year') ?: null, 282 | get_query_var('monthnum') 283 | )->format($config['month_format']); 284 | } elseif (is_day()) { 285 | $title = Carbon::create( 286 | get_query_var('year') ?: null, 287 | get_query_var('monthnum') ?: null, 288 | get_query_var('day') 289 | )->format($config['day_format']); 290 | } 291 | 292 | return $config['prefix'] . $title . $config['suffix']; 293 | } 294 | 295 | /** 296 | * Check if the 'Blog' page is the 'Home' page. 297 | * 298 | * @return boolean 299 | */ 300 | private function isBlogPageHome() 301 | { 302 | return !get_option('page_for_posts') ?: get_option('page_on_front') === get_option('page_for_posts'); 303 | } 304 | 305 | /** 306 | * Check if we're currently on an archive page that has some filtering. 307 | * 308 | * @return boolean 309 | */ 310 | private function isFilteredArchive() 311 | { 312 | return is_archive() && (is_category() || is_tag() || is_tax() || is_date() || is_author()); 313 | } 314 | } 315 | -------------------------------------------------------------------------------- /src/Tev/View/Exception/NotFoundException.php: -------------------------------------------------------------------------------- 1 | templateDir = $templateDir; 37 | $this->viewData = array(); 38 | } 39 | 40 | /** 41 | * Renders a template file. 42 | * 43 | * Assigns all $var keys to $this->$key for us in template. 44 | * 45 | * @param string $filename Full-filename 46 | * @param array $vars View variables 47 | * @return string Rendered view 48 | * 49 | * @throws \Tev\View\Exception\NotFoundException If view file not found 50 | */ 51 | public function render($filename, $vars = array()) 52 | { 53 | $localTemplate = $this->templateDir . '/' . $filename; 54 | $themeTemplate = locate_template($filename); 55 | 56 | if (!file_exists($localTemplate) && !file_exists($themeTemplate)) { 57 | throw new NotFoundException("View at $localTemplate or $themeTemplate not found"); 58 | } 59 | 60 | $this->viewData = $vars; 61 | 62 | $template = file_exists($localTemplate) ? $localTemplate : $themeTemplate; 63 | 64 | ob_start(); 65 | include($template); 66 | $view = ob_get_contents(); 67 | ob_end_clean(); 68 | 69 | return $view; 70 | } 71 | 72 | /** 73 | * Magic getter, for getting view variables. 74 | * 75 | * @param string $name View variable name 76 | * @return mixed View variable, or null if not set 77 | */ 78 | public function __get($name) 79 | { 80 | return isset($this->viewData[$name]) ? $this->viewData[$name] : null; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/helpers.php: -------------------------------------------------------------------------------- 1 | fetch($service); 27 | } 28 | } 29 | 30 | 31 | if (!function_exists('tev_partial')) { 32 | 33 | /** 34 | * Render a view partial. 35 | * 36 | * @param string $file Partial file name 37 | * @param array $params Optional variables to make available to partial 38 | * @return string Rendered partial 39 | */ 40 | function tev_partial($file, array $params = array()) { 41 | return tev_fetch('template_extras')->partial($file, $params); 42 | } 43 | } 44 | 45 | 46 | if (!function_exists('tev_post_factory_register')) { 47 | 48 | /** 49 | * Register a new post type to class name mapping. 50 | * 51 | * @param string $postType Post type identifier 52 | * @param string $className Class name to instantiate for $postType. Class 53 | * must inherit from `\Tev\Post\Model\AbstractPost` 54 | * @return void 55 | * 56 | * @throws \Exception If class name does not inherit from `\Tev\Post\Model\AbstractPost` 57 | */ 58 | function tev_post_factory_register($postType, $className) { 59 | Tev\Application\Application::getInstance() 60 | ->fetch('post_factory') 61 | ->register($postType, $className); 62 | } 63 | } 64 | 65 | 66 | if (!function_exists('tev_post_factory')) { 67 | 68 | /** 69 | * Instantiate a post entity from a given post object. 70 | * 71 | * @param \WP_Post $base Optional. Base post object. If not 72 | * supplied will attempt to get the current 73 | * post from The Loop 74 | * @param string $className Optional. Class name to instantiate. Will 75 | * use registered default if not supplied 76 | * @return \Tev\Post\Model\AbstractPost Post entity 77 | * 78 | * @throws \Exception If $base is not given and not in the The Loop 79 | */ 80 | function tev_post_factory($base = null, $className = null) { 81 | return Tev\Application\Application::getInstance() 82 | ->fetch('post_factory') 83 | ->create($base, $className); 84 | } 85 | } 86 | --------------------------------------------------------------------------------