├── .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 | [](https://scrutinizer-ci.com/g/Rarst/meadow/?branch=master)
6 | [](https://packagist.org/packages/rarst/meadow)
7 | [](https://packagist.org/packages/rarst/meadow)
8 | [](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 |
82 | {{ the_content() }}
83 | {% endloop %}
84 | ```
85 |
86 | ### Secondary Loop
87 |
88 | ```twig
89 | {% loop { 'post_type' : 'book', 'orderby' : 'title' } %} {# expression for arguments #}
90 |
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 | {{ __('Search for:') }}
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------