├── .gitignore ├── CHANGELOG.md ├── LICENSE.md ├── README.md ├── composer.json └── src ├── Comments_Node.php ├── Comments_Token_Parser.php ├── Core.php ├── Extension.php ├── Loop_Node.php ├── Loop_Token_Parser.php ├── Template_Hierarchy.php ├── Type_Template_Hierarchy.php ├── blank.php └── twig └── searchform.twig /.gitignore: -------------------------------------------------------------------------------- 1 | vendor 2 | composer.lock -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](http://keepachangelog.com/) 5 | and this project adheres to [Semantic Versioning](http://semver.org/). 6 | 7 | ## Unreleased 8 | 9 | ## 0.3 - 2018-12-31 10 | 11 | ### Changed 12 | - code structure for PDS and PSR-4. 13 | - dependencies (Twig 2+, PHP 7+). 14 | 15 | ### Fixed 16 | - possible double output in `template_include`. 17 | 18 | ## 0.2 - 2016-12-10 19 | 20 | ### Added 21 | - new hierarchy implementation for WP 4.7+ 22 | 23 | ### Deprecated 24 | - old hierarchy implementation for WP <4.7, to be removed in 1.0 25 | 26 | ### Fixed 27 | - deprecated methods in Twig extension 28 | 29 | ## 0.1 - 2015-04-13 -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2014 Andrey Savchenko 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 | # Meadow — WordPress Templating DSL 2 | 3 | _Write WordPress theme templates with familiar ease and modern features._ 4 | 5 | [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/Rarst/meadow/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/Rarst/meadow/?branch=master) 6 | [![Version](https://img.shields.io/packagist/v/rarst/meadow.svg?label=version)](https://packagist.org/packages/rarst/meadow) 7 | [![PHP required](https://img.shields.io/packagist/php-v/rarst/meadow.svg)](https://packagist.org/packages/rarst/meadow) 8 | [![PDS Skeleton](https://img.shields.io/badge/pds-skeleton-blue.svg?style=flat-square)](https://github.com/php-pds/skeleton) 9 | 10 | Meadow is a theme templating solution, aiming to find a balance between native WordPress concepts and power of [Twig](http://twig.sensiolabs.org/) dedicated templating language. 11 | 12 | ## Installation 13 | 14 | Require package in your theme project with [Composer](https://getcomposer.org/): 15 | 16 | ```bash 17 | composer require rarst/meadow 18 | ``` 19 | 20 | Instantiate object some time during theme load: 21 | 22 | ```php 23 | $meadow = new \Rarst\Meadow\Core; 24 | $meadow->enable(); 25 | ``` 26 | 27 | ## Templating 28 | 29 | Meadow follows conventions of WordPress [template hierarchy](https://codex.wordpress.org/Template_Hierarchy#Visual_Overview): 30 | 31 | - for example `index.php` becomes `index.twig`. 32 | - `{{ get_header() }}` will look for `header.twig` (with fallback to `header.php`) 33 | - and so on. 34 | 35 | ### Template Tags 36 | 37 | Template Tags API (and PHP functions in general) are set up to work transparently from Twig templates: 38 | 39 | ```twig 40 | {{ the_title() }} 41 | ``` 42 | 43 | ### Filters 44 | 45 | WordPress filters set up to be available as Twig filters: 46 | 47 | ```twig 48 | {{ 'This is the title'|the_title }} 49 | ``` 50 | 51 | ### Template Inheritance 52 | 53 | Full range of Twig functionality is naturally available, including [template inheritance](http://twig.sensiolabs.org/doc/templates.html#template-inheritance): 54 | 55 | ```twig 56 | {# single.twig #} 57 | {% extends 'index.twig' %} 58 | 59 | {% block entry_title %} 60 | 61 | {% endblock %} 62 | ``` 63 | 64 | To inherit parent template in child theme prepend it with folder's name: 65 | 66 | ```twig 67 | {# child-theme/index.twig #} 68 | {% extends 'parent-theme/index.twig' %} 69 | ``` 70 | 71 | ## Domain Specific Language 72 | 73 | Meadow attempts not just "map" WordPress to Twig, but also meaningfully extend both to improve historically clunky WP constructs. 74 | 75 | This is primarily achieved by implementing custom Twig tags, abstracting away complexities for specific tasks. 76 | 77 | ### Loop 78 | 79 | ```twig 80 | {% loop %} 81 |

{{ the_title() }}

82 | {{ the_content() }} 83 | {% endloop %} 84 | ``` 85 | 86 | ### Secondary Loop 87 | 88 | ```twig 89 | {% loop { 'post_type' : 'book', 'orderby' : 'title' } %} {# expression for arguments #} 90 |

{{ the_title() }}

91 | {{ the_content() }} 92 | {% endloop %} 93 | ``` 94 | 95 | ### Comments 96 | 97 | ```twig 98 | 105 | ``` 106 | 107 | ## Template Examples 108 | 109 | In [Hybrid Wing](https://github.com/Rarst/hybrid-wing) theme (work in progress): 110 | 111 | - [`index.twig`](https://github.com/Rarst/hybrid-wing/blob/master/index.twig) 112 | - [`single.twig`](https://github.com/Rarst/hybrid-wing/blob/master/single.twig) 113 | - [`single-post.twig`](https://github.com/Rarst/hybrid-wing/blob/master/single-post.twig) 114 | - [`comments.twig`](https://github.com/Rarst/hybrid-wing/blob/master/comments.twig) 115 | 116 | ## License 117 | 118 | MIT -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name" : "rarst/meadow", 3 | "description" : "WordPress templating DSL", 4 | "keywords" : ["wordpress", "twig"], 5 | "homepage" : "https://github.com/Rarst/meadow", 6 | "license" : "MIT", 7 | "authors" : [ 8 | { 9 | "name" : "Andrey Savchenko", 10 | "homepage": "https://www.Rarst.net/" 11 | } 12 | ], 13 | "support" : { 14 | "issues": "https://github.com/Rarst/meadow/issues" 15 | }, 16 | "require" : { 17 | "php": ">=7.0", 18 | "pimple/pimple": "^3.2.3", 19 | "twig/twig": "^2.6" 20 | }, 21 | "require-dev": { 22 | "pds/skeleton": "^1.0" 23 | }, 24 | "autoload" : { 25 | "psr-4": { 26 | "Rarst\\Meadow\\" : "src/" 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /src/Comments_Node.php: -------------------------------------------------------------------------------- 1 | addDebugInfo( $this ) 18 | ->write( '$callback = function() {' ) 19 | ->subcompile( $this->getNode( 'callback' ) ) 20 | ->write( '};' ) 21 | ->write( 'wp_list_comments( array( \'callback\' => $callback ) );' ); 22 | } 23 | } -------------------------------------------------------------------------------- /src/Comments_Token_Parser.php: -------------------------------------------------------------------------------- 1 | parser; 19 | $stream = $parser->getStream(); 20 | 21 | $stream->expect( Twig_Token::BLOCK_END_TYPE ); 22 | $nodes['callback'] = $parser->subparse( array( $this, 'decide_comments_end' ), true ); 23 | $stream->expect( Twig_Token::BLOCK_END_TYPE ); 24 | 25 | return new Comments_Node( $nodes, array(), $token->getLine(), $this->getTag() ); 26 | } 27 | 28 | /** 29 | * @return string 30 | */ 31 | public function getTag() { 32 | 33 | return 'comments'; 34 | } 35 | 36 | /** 37 | * @param Twig_Token $token 38 | * 39 | * @return bool 40 | */ 41 | public function decide_comments_end( Twig_Token $token ) { 42 | return $token->test( 'endcomments' ); 43 | } 44 | } -------------------------------------------------------------------------------- /src/Core.php: -------------------------------------------------------------------------------- 1 | addExtension( $meadow_extension ); 55 | $environment->registerUndefinedFunctionCallback( $meadow['twig.undefined_function'] ); 56 | $environment->registerUndefinedFilterCallback( $meadow['twig.undefined_filter'] ); 57 | 58 | if ( \defined( 'WP_DEBUG' ) && WP_DEBUG ) { 59 | $debug_extension = new \Twig_Extension_Debug(); 60 | $environment->addExtension( $debug_extension ); 61 | $environment->enableDebug(); 62 | } 63 | 64 | return $environment; 65 | }; 66 | 67 | if ( version_compare( rtrim( $wp_version, '-src' ), '4.7', '>=' ) ) { 68 | 69 | $defaults['hierarchy'] = function () { 70 | return new Type_Template_Hierarchy(); 71 | }; 72 | } else { 73 | 74 | trigger_error( 'Pre–WP 4.7 implementation of Meadow hierarchy is deprecated and will be removed in 1.0.', E_USER_DEPRECATED ); 75 | 76 | $defaults['hierarchy'] = function () { 77 | /** @noinspection PhpDeprecationInspection */ 78 | return new Template_Hierarchy(); 79 | }; 80 | } 81 | 82 | parent::__construct( array_merge( $defaults, $values ) ); 83 | } 84 | 85 | /** 86 | * Handler for undefined functions in Twig to pass them through to PHP and buffer echoing versions. 87 | * 88 | * @param string $function_name Name of the function to handle. 89 | * 90 | * @return bool|\Twig_Function 91 | */ 92 | public static function undefined_function( $function_name ) { 93 | 94 | if ( \function_exists( $function_name ) ) { 95 | return new \Twig_Function( 96 | $function_name, 97 | function () use ( $function_name ) { 98 | 99 | ob_start(); 100 | $return = \call_user_func_array( $function_name, \func_get_args() ); 101 | $echo = ob_get_clean(); 102 | 103 | return empty( $echo ) ? $return : $echo; 104 | }, 105 | array( 'is_safe' => array( 'all' ) ) 106 | ); 107 | } 108 | 109 | return false; 110 | } 111 | 112 | /** 113 | * Handler for fallback to WordPress filters for undefined Twig filters in template. 114 | * 115 | * @param string $filter_name Name of the filter to handle. 116 | * 117 | * @return bool|\Twig_Filter 118 | */ 119 | public static function undefined_filter( $filter_name ) { 120 | 121 | return new \Twig_Filter( 122 | $filter_name, 123 | function () use ( $filter_name ) { 124 | 125 | return apply_filters( $filter_name, func_get_arg( 0 ) ); 126 | }, 127 | array( 'is_safe' => array( 'all' ) ) 128 | ); 129 | } 130 | 131 | public function enable() { 132 | 133 | /** @var Template_Hierarchy $hierarchy */ 134 | $hierarchy = $this['hierarchy']; 135 | $hierarchy->enable(); 136 | add_filter( 'template_include', [ $this, 'template_include' ], 100 ); 137 | add_filter( 'get_search_form', array( $this, 'get_search_form' ), 9 ); 138 | } 139 | 140 | public function disable() { 141 | 142 | /** @var Template_Hierarchy $hierarchy */ 143 | $hierarchy = $this['hierarchy']; 144 | $hierarchy->disable(); 145 | remove_filter( 'template_include', [ $this, 'template_include' ], 100 ); 146 | remove_filter( 'get_search_form', array( $this, 'get_search_form' ), 9 ); 147 | } 148 | 149 | /** 150 | * @param string $template Template found by loader. 151 | * 152 | * @return string|bool 153 | */ 154 | public function template_include( $template ) { 155 | 156 | if ( '.twig' === substr( $template, - 5 ) ) { 157 | /** @var \Twig_Environment $twig */ 158 | $twig = $this['twig.environment']; 159 | 160 | echo $twig->render( basename( $template ), apply_filters( 'meadow_context', array() ) ); 161 | 162 | die(); 163 | } 164 | 165 | return $template; 166 | } 167 | 168 | /** 169 | * @param string $form Form markup. 170 | * 171 | * @return string 172 | */ 173 | public function get_search_form( $form ) { 174 | 175 | // Because first time it's an action. 176 | if ( ! empty( $form ) ) { 177 | /** @var \Twig_Environment $twig */ 178 | $twig = $this['twig.environment']; 179 | 180 | return $twig->render( 'searchform.twig' ); 181 | } 182 | 183 | return $form; 184 | } 185 | } -------------------------------------------------------------------------------- /src/Extension.php: -------------------------------------------------------------------------------- 1 | true, 14 | 'needs_context' => true, 15 | 'is_safe' => array( 'all' ) 16 | ); 17 | 18 | $functions = array(); 19 | 20 | foreach ( array( 'get_header', 'get_footer', 'get_sidebar', 'get_template_part', 'get_search_form', 'comments_template' ) as $function ) { 21 | $functions[] = new \Twig_Function( $function, array( $this, $function ), $options ); 22 | } 23 | 24 | return $functions; 25 | } 26 | 27 | public function getGlobals() { 28 | 29 | global $wp_query; 30 | 31 | return compact( 'wp_query' ); 32 | } 33 | 34 | public function getTokenParsers( ) { 35 | 36 | return array( 37 | new Loop_Token_Parser(), 38 | new Comments_Token_Parser(), 39 | ); 40 | } 41 | 42 | public function get_header( \Twig_Environment $env, $context, $name = null ) { 43 | 44 | return $this->get_template( $env, $context, 'header', $name ); 45 | } 46 | 47 | public function get_templates( $slug, $name = null ) { 48 | 49 | $templates = array(); 50 | 51 | if ( ! empty( $name ) ) { 52 | $templates[] = "{$slug}-{$name}.twig"; 53 | } 54 | 55 | $templates[] = "{$slug}.twig"; 56 | 57 | return $templates; 58 | } 59 | 60 | public function get_footer( \Twig_Environment $env, $context, $name = null ) { 61 | 62 | return $this->get_template( $env, $context, 'footer', $name ); 63 | } 64 | 65 | public function get_sidebar( \Twig_Environment $env, $context, $name = null ) { 66 | 67 | return $this->get_template( $env, $context, 'sidebar', $name ); 68 | } 69 | 70 | public function get_template_part( \Twig_Environment $env, $context, $slug, $name = null ) { 71 | 72 | try { 73 | $return = twig_include( $env, $context, $this->get_templates( $slug, $name ) ); 74 | do_action( "get_template_part_{$slug}", $slug, $name ); 75 | } catch ( \Twig_Error_Loader $e ) { 76 | ob_start(); 77 | get_template_part( $slug, $name ); 78 | $return = ob_get_clean(); 79 | } 80 | 81 | return $return; 82 | } 83 | 84 | /** 85 | * Skips rendering in native function in favor of Plugin->get_search_form() in filter 86 | * 87 | * @return string 88 | */ 89 | public function get_search_form() { 90 | 91 | return apply_filters( 'get_search_form', true ); 92 | } 93 | 94 | protected function get_template( \Twig_Environment $env, $context, $type, $name = null ) { 95 | 96 | try { 97 | $return = twig_include( $env, $context, $this->get_templates( $type, $name ) ); 98 | do_action( 'get_' . $type, $name ); 99 | } catch ( \Twig_Error_Loader $e ) { 100 | ob_start(); 101 | \call_user_func( 'get_' . $type, $name ); 102 | $return = ob_get_clean(); 103 | } 104 | 105 | return $return; 106 | } 107 | 108 | public function comments_template( \Twig_Environment $env, $context, $file = 'comments.twig', $separate_comments = false ) { 109 | 110 | try { 111 | $env->load( $file ); 112 | } catch ( \Twig_Error_Loader $e ) { 113 | ob_start(); 114 | comments_template( '/comments.php', $separate_comments ); 115 | 116 | return ob_get_clean(); 117 | } 118 | 119 | add_filter( 'comments_template', array( $this, 'return_blank_template' ) ); 120 | comments_template( '/comments.php', $separate_comments ); 121 | remove_filter( 'comments_template', array( $this, 'return_blank_template' ) ); 122 | 123 | return twig_include( $env, $context, $file ); 124 | } 125 | 126 | public function return_blank_template() { 127 | 128 | return __DIR__ . '/blank.php'; 129 | } 130 | } -------------------------------------------------------------------------------- /src/Loop_Node.php: -------------------------------------------------------------------------------- 1 | addDebugInfo( $this ); 19 | 20 | if ( $this->hasNode( 'query' ) ) { 21 | $compiler 22 | ->write( '$loop = new WP_Query(' ) 23 | ->subcompile( $this->getNode( 'query' ) ) 24 | ->raw( ");\n" ) 25 | ->write( 'while( $loop->have_posts() ) : $loop->the_post();' . "\n" ); // TODO nested loops 26 | } 27 | else { 28 | $compiler->write( 'while( have_posts() ) : the_post();' . "\n" ); 29 | } 30 | 31 | $compiler 32 | ->subcompile( $this->getNode( 'body' ) ) 33 | ->write( 'endwhile;' . "\n" ); 34 | } 35 | } -------------------------------------------------------------------------------- /src/Loop_Token_Parser.php: -------------------------------------------------------------------------------- 1 | parser; 20 | $stream = $parser->getStream(); 21 | 22 | if ( ! $stream->getCurrent()->test( Twig_Token::BLOCK_END_TYPE ) ) { 23 | $nodes['query'] = $parser->getExpressionParser()->parseExpression(); 24 | } 25 | 26 | $stream->expect( Twig_Token::BLOCK_END_TYPE ); 27 | $nodes['body'] = $parser->subparse( array( $this, 'decide_loop_end' ), true ); 28 | $stream->expect( Twig_Token::BLOCK_END_TYPE ); 29 | 30 | return new Loop_Node( $nodes, array(), $token->getLine(), $this->getTag() ); 31 | } 32 | 33 | /** 34 | * @return string 35 | */ 36 | public function getTag() { 37 | 38 | return 'loop'; 39 | } 40 | 41 | /** 42 | * @param Twig_Token $token 43 | * 44 | * @return bool 45 | */ 46 | public function decide_loop_end( Twig_Token $token ) { 47 | return $token->test( 'endloop' ); 48 | } 49 | } -------------------------------------------------------------------------------- /src/Template_Hierarchy.php: -------------------------------------------------------------------------------- 1 | template_types as $type ) { 40 | add_filter( "{$type}_template", array( $this, 'query_template' ) ); 41 | } 42 | } 43 | 44 | public function disable() { 45 | 46 | remove_action( 'template_redirect', array( $this, 'template_redirect' ) ); 47 | 48 | foreach ( $this->template_types as $type ) { 49 | remove_filter( "{$type}_template", array( $this, 'query_template' ) ); 50 | } 51 | 52 | if ( ! empty($this->mime_type) ) { 53 | remove_filter( "{$this->mime_type[0]}_template", array( $this, 'query_template' ) ); 54 | remove_filter( "{$this->mime_type[1]}_template", array( $this, 'query_template' ) ); 55 | remove_filter( "{$this->mime_type[0]}{$this->mime_type[1]}_template", array( $this, 'query_template' ) ); 56 | } 57 | } 58 | 59 | public function template_redirect() { 60 | 61 | if ( is_attachment() ) { 62 | global $posts; 63 | 64 | if ( ! empty( $posts ) && isset( $posts[0]->post_mime_type ) ) { 65 | $this->mime_type = explode( '/', $posts[0]->post_mime_type ); 66 | } 67 | 68 | add_filter( "{$this->mime_type[0]}_template", array( $this, 'query_template' ) ); 69 | add_filter( "{$this->mime_type[1]}_template", array( $this, 'query_template' ) ); 70 | add_filter( "{$this->mime_type[0]}{$this->mime_type[1]}_template", array( $this, 'query_template' ) ); 71 | } 72 | } 73 | 74 | /** 75 | * @param string $fallback 76 | * 77 | * @return string 78 | */ 79 | public function query_template( $fallback ) { 80 | 81 | $type = substr( current_filter(), 0, - 9 ); // trim '_template' from end 82 | $templates = array(); 83 | 84 | switch ( $type ) { 85 | case 'embed': 86 | 87 | $object = get_queried_object(); 88 | 89 | if ( ! empty( $object->post_type ) ) { 90 | 91 | $post_format = get_post_format( $object ); 92 | 93 | if ( $post_format ) { 94 | $templates[] = "embed-{$object->post_type}-{$post_format}.twig"; 95 | } 96 | 97 | $templates[] = "embed-{$object->post_type}.twig"; 98 | } 99 | 100 | $templates[] = 'embed.twig'; 101 | 102 | break; 103 | 104 | case 'taxonomy': 105 | $term = get_queried_object(); 106 | 107 | if ( $term ) { 108 | $taxonomy = $term->taxonomy; 109 | $templates[] = "taxonomy-{$taxonomy}-{$term->slug}.twig"; 110 | $templates[] = "taxonomy-{$taxonomy}.twig"; 111 | } 112 | 113 | $templates[] = 'taxonomy.twig'; 114 | break; 115 | 116 | case 'frontpage': 117 | $templates = array( 'front-page.twig' ); 118 | break; 119 | 120 | case 'home': 121 | $templates = array( 'home.twig', 'index.twig' ); 122 | break; 123 | 124 | case 'single': 125 | $object = get_queried_object(); 126 | 127 | if ( $object ) { 128 | $templates[] = "single-{$object->post_type}.twig"; 129 | } 130 | 131 | $templates[] = 'single.twig'; 132 | break; 133 | 134 | case 'page': 135 | $page_id = get_queried_object_id(); 136 | // $template = get_page_template_slug(); 137 | $pagename = get_query_var( 'pagename' ); 138 | 139 | if ( ! $pagename && $page_id ) { 140 | // If a static page is set as the front page, $pagename will not be set. Retrieve it from the queried object 141 | $post = get_queried_object(); 142 | $pagename = $post->post_name; 143 | } 144 | 145 | // TODO page templates 146 | // if ( $template && 0 === validate_file( $template ) ) 147 | // $templates[] = $template; 148 | 149 | if ( $pagename ) { 150 | $templates[] = "page-{$pagename}.twig"; 151 | } 152 | 153 | if ( $page_id ) { 154 | $templates[] = "page-{$page_id}.twig"; 155 | } 156 | 157 | $templates[] = 'page.twig'; 158 | break; 159 | 160 | case 'category': 161 | case 'tag': 162 | $term = get_queried_object(); 163 | 164 | if ( $term ) { 165 | $templates[] = "{$type}-{$term->slug}.twig"; 166 | $templates[] = "{$type}-{$term->term_id}.twig"; 167 | } 168 | 169 | $templates[] = "{$type}.twig"; 170 | break; 171 | 172 | case 'author': 173 | $author = get_queried_object(); 174 | 175 | if ( $author ) { 176 | $templates[] = "author-{$author->user_nicename}.twig"; 177 | $templates[] = "author-{$author->ID}.twig"; 178 | } 179 | 180 | $templates[] = 'author.twig'; 181 | break; 182 | 183 | case 'archive': 184 | $post_types = array_filter( (array) get_query_var( 'post_type' ) ); 185 | 186 | if ( \count( $post_types ) === 1 ) { 187 | $post_type = reset( $post_types ); 188 | $templates[] = "archive-{$post_type}.twig"; 189 | } 190 | 191 | $templates[] = 'archive.twig'; 192 | break; 193 | 194 | default: 195 | $templates = array( "{$type}.twig" ); 196 | } 197 | 198 | $template = $this->locate_template( $templates ); 199 | 200 | if ( empty( $template ) ) { 201 | $template = $fallback; 202 | } 203 | 204 | return apply_filters( 'meadow_query_template', $template, $type ); 205 | } 206 | 207 | /** 208 | * Broken out for easier inheritance to customize lookup logic. 209 | * 210 | * @param array|string $templates 211 | * 212 | * @return string 213 | */ 214 | public function locate_template( $templates ) { 215 | 216 | return locate_template( $templates ); 217 | } 218 | } 219 | -------------------------------------------------------------------------------- /src/Type_Template_Hierarchy.php: -------------------------------------------------------------------------------- 1 | template_types as $type ) { 39 | add_filter( "{$type}_template_hierarchy", array( $this, 'template_hierarchy' ) ); 40 | } 41 | } 42 | 43 | public function disable() { 44 | 45 | remove_filter( 'template_include', array( $this, 'template_include' ), 9 ); 46 | 47 | foreach ( $this->template_types as $type ) { 48 | remove_filter( "{$type}_template_hierarchy", array( $this, 'template_hierarchy' ) ); 49 | } 50 | } 51 | 52 | /** 53 | * @param string[] $templates Array of possible PHP templates, generated by WP core. 54 | * 55 | * @return string[] Array of templates, prepended with Twig versions. 56 | */ 57 | public function template_hierarchy( $templates ) { 58 | 59 | $this->type = substr( current_filter(), 0, - 19 ); // Trim '_template_hierarchy' from end. 60 | 61 | $twig_templates = []; 62 | 63 | foreach ( $templates as $php_template ) { 64 | if ( '.php' === substr( $php_template, - 4 ) ) { 65 | $twig_templates[] = substr( $php_template, 0, - 4 ) . '.twig'; 66 | } 67 | } 68 | 69 | return array_merge( $twig_templates, $templates ); 70 | } 71 | 72 | /** 73 | * @param string $template Template located by loader after going through hierarchy. 74 | * 75 | * @return string 76 | */ 77 | public function template_include( $template ) { 78 | 79 | return apply_filters( 'meadow_query_template', $template, $this->type ); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/blank.php: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 | 5 | 6 |
7 | --------------------------------------------------------------------------------