diff --git a/bin/install-wp-tests.sh b/bin/install-wp-tests.sh old mode 100644 new mode 100755 diff --git a/docs/guides/template-locations.md b/docs/guides/template-locations.md index 187712e56..50b748ee8 100644 --- a/docs/guides/template-locations.md +++ b/docs/guides/template-locations.md @@ -23,6 +23,40 @@ Timber::$locations = array( ); ``` +## Register your own namespaces + +You can also use namespaces in your locations too, just define it as the value next to a path, for example: + +```php + 'styleguide' +); +``` + +In the example above the namespace is called `styleguide`. You must prefix this with `@` when using it in templates (that's how Twig can differentiate namespaces from regular paths). +Assuming you have a template called `menu.twig` within that namespace, you would call it like so: + +```twig +{% include '@styleguide/menu.twig' %} +``` + +You can also register multiple paths for the same namespace. Order is important as it will look top to bottom and return the first one it encounters, for example: + +```php + 'styleguide', + '/Users/jared/Sandbox/styleguide' => 'styleguide' +); +``` + You only need to do this once in your project (in `functions.php` of your theme). When you call one of the render or compile functions from a PHP file (say `single.php`), Timber will look for Twig files in these locations before it checks the child or parent theme. ## Changing the default folder for Twig files diff --git a/lib/Loader.php b/lib/Loader.php index 30455fdb5..cb05bf6cb 100644 --- a/lib/Loader.php +++ b/lib/Loader.php @@ -3,6 +3,7 @@ namespace Timber; use Timber\Cache\Cleaner; +use Twig\Loader\FilesystemLoader; class Loader { @@ -16,6 +17,9 @@ class Loader { const CACHE_SITE_TRANSIENT = 'site-transient'; const CACHE_USE_DEFAULT = 'default'; + /** Identifier of the main namespace. Will likely mirror Twig\Loader\FilesystemLoader::MAIN_NAMESPACE */ + const MAIN_NAMESPACE = '__main__'; + public static $cache_modes = array( self::CACHE_NONE, self::CACHE_OBJECT, @@ -130,16 +134,27 @@ protected function template_exists( $name ) { * @return \Twig_Loader_Filesystem */ public function get_loader() { - $open_basedir = ini_get('open_basedir'); - $paths = array_merge($this->locations, array($open_basedir ? ABSPATH : '/')); - $paths = apply_filters('timber/loader/paths', $paths); + $open_basedir = ini_get( 'open_basedir' ); + $paths = array_merge_recursive( + $this->locations, + array( self::MAIN_NAMESPACE => $open_basedir ? ABSPATH : '/' ) + ); + $paths = apply_filters( 'timber/loader/paths', $paths ); $rootPath = '/'; if ( $open_basedir ) { $rootPath = null; } - $fs = new \Twig_Loader_Filesystem($paths, $rootPath); - $fs = apply_filters('timber/loader/loader', $fs); + $fs = new \Twig_Loader_Filesystem( array(), $rootPath ); + foreach ( $paths as $namespace => $path_locations ) { + if ( is_array( $path_locations ) ) { + array_map( function ( $path ) use ( $fs, $namespace ) { + $fs->addPath( $path, $namespace ); + }, $path_locations ); + } + } + $fs = apply_filters( 'timber/loader/loader', $fs ); + return $fs; } diff --git a/lib/LocationManager.php b/lib/LocationManager.php index 62ffbe488..091e00e07 100644 --- a/lib/LocationManager.php +++ b/lib/LocationManager.php @@ -12,17 +12,26 @@ class LocationManager { public static function get_locations( $caller = false ) { //prioirty: user locations, caller (but not theme), child theme, parent theme, caller $locs = array(); - $locs = array_merge($locs, self::get_locations_user()); - $locs = array_merge($locs, self::get_locations_caller($caller)); + $locs = array_merge_recursive( $locs, self::get_locations_user() ); + $locs = array_merge_recursive( $locs, self::get_locations_caller( $caller ) ); //remove themes from caller - $locs = array_diff($locs, self::get_locations_theme()); - $locs = array_merge($locs, self::get_locations_theme()); - $locs = array_merge($locs, self::get_locations_caller($caller)); - $locs = array_unique($locs); + $locs = array_merge_recursive( $locs, self::get_locations_theme() ); + $locs = array_merge_recursive( $locs, self::get_locations_caller( $caller ) ); + $locs = array_map( 'array_unique', $locs ); + //now make sure theres a trailing slash on everything - $locs = array_map('trailingslashit', $locs); - $locs = apply_filters('timber_locations', $locs); - $locs = apply_filters('timber/locations', $locs); + $locs = array_map( function ( $loc ) { + return array_map( 'trailingslashit', $loc ); + }, $locs ); + + $locs = array_map( function ( $loc ) { + return apply_filters( 'timber_locations', $loc ); + }, $locs ); + + $locs = array_map( function ( $loc ) { + return apply_filters( 'timber/locations', $loc ); + }, $locs ); + return $locs; } @@ -33,19 +42,19 @@ public static function get_locations( $caller = false ) { protected static function get_locations_theme() { $theme_locs = array(); $theme_dirs = LocationManager::get_locations_theme_dir(); - $roots = array(get_stylesheet_directory(), get_template_directory()); - $roots = array_map('realpath', $roots); - $roots = array_unique($roots); + $roots = array( get_stylesheet_directory(), get_template_directory() ); + $roots = array_map( 'realpath', $roots ); + $roots = array_unique( $roots ); foreach ( $roots as $root ) { - if ( !is_dir($root) ) { + if ( ! is_dir( $root ) ) { continue; } - $theme_locs[] = $root; - $root = trailingslashit($root); + $theme_locs[ Loader::MAIN_NAMESPACE ][] = $root; + $root = trailingslashit( $root ); foreach ( $theme_dirs as $dirname ) { - $tloc = realpath($root.$dirname); - if ( is_dir($tloc) ) { - $theme_locs[] = $tloc; + $tloc = realpath( $root . $dirname ); + if ( is_dir( $tloc ) ) { + $theme_locs[ Loader::MAIN_NAMESPACE ][] = $tloc; } } } @@ -105,17 +114,29 @@ public static function get_locations_theme_dir() { */ protected static function get_locations_user() { $locs = array(); - if ( isset(Timber::$locations) ) { - if ( is_string(Timber::$locations) ) { - Timber::$locations = array(Timber::$locations); + if ( isset( Timber::$locations ) ) { + if ( is_string( Timber::$locations ) ) { + Timber::$locations = array( Timber::$locations ); } - foreach ( Timber::$locations as $tloc ) { - $tloc = realpath($tloc); - if ( is_dir($tloc) ) { - $locs[] = $tloc; + foreach ( Timber::$locations as $tloc => $namespace_or_tloc ) { + if ( is_string( $tloc ) ) { + $namespace = $namespace_or_tloc; + } else { + $tloc = $namespace_or_tloc; + $namespace = null; + } + + $tloc = realpath( $tloc ); + if ( is_dir( $tloc ) ) { + if ( ! is_string( $namespace ) ) { + $locs[ Loader::MAIN_NAMESPACE ][] = $tloc; + } else { + $locs[ $namespace ][] = $tloc; + } } } } + return $locs; } @@ -125,21 +146,22 @@ protected static function get_locations_user() { */ protected static function get_locations_caller( $caller = false ) { $locs = array(); - if ( $caller && is_string($caller) ) { - $caller = realpath($caller); - if ( is_dir($caller) ) { - $locs[] = $caller; + if ( $caller && is_string( $caller ) ) { + $caller = realpath( $caller ); + if ( is_dir( $caller ) ) { + $locs[ Loader::MAIN_NAMESPACE ][] = $caller; } - $caller = trailingslashit($caller); + $caller = trailingslashit( $caller ); foreach ( LocationManager::get_locations_theme_dir() as $dirname ) { - $caller_sub = realpath($caller.$dirname); - if ( is_dir($caller_sub) ) { - $locs[] = $caller_sub; + $caller_sub = realpath( $caller . $dirname ); + if ( is_dir( $caller_sub ) ) { + $locs[ Loader::MAIN_NAMESPACE ][] = $caller_sub; } } } + return $locs; } -} \ No newline at end of file +} diff --git a/lib/Timber.php b/lib/Timber.php index 89fd2a467..76666e1a2 100644 --- a/lib/Timber.php +++ b/lib/Timber.php @@ -439,22 +439,26 @@ public static function get_sidebar( $sidebar = 'sidebar.php', $data = array() ) * @return string */ public static function get_sidebar_from_php( $sidebar = '', $data ) { - $caller = LocationManager::get_calling_script_dir(1); - $uris = LocationManager::get_locations($caller); + $caller = LocationManager::get_calling_script_dir( 1 ); + $uris = LocationManager::get_locations( $caller ); ob_start(); $found = false; - foreach ( $uris as $uri ) { - if ( file_exists(trailingslashit($uri).$sidebar) ) { - include trailingslashit($uri).$sidebar; - $found = true; - break; + foreach ( $uris as $namespace => $uri_locations ) { + if ( is_array( $uri_locations ) ) { + foreach ( $uri_locations as $uri ) { + if ( file_exists( trailingslashit( $uri ) . $sidebar ) ) { + include trailingslashit( $uri ) . $sidebar; + $found = true; + } + } } } - if ( !$found ) { - Helper::error_log('error loading your sidebar, check to make sure the file exists'); + if ( ! $found ) { + Helper::error_log( 'error loading your sidebar, check to make sure the file exists' ); } $ret = ob_get_contents(); ob_end_clean(); + return $ret; } diff --git a/tests/namespaced/test-namespaced.twig b/tests/namespaced/test-namespaced.twig new file mode 100644 index 000000000..79e526307 --- /dev/null +++ b/tests/namespaced/test-namespaced.twig @@ -0,0 +1 @@ +This is a namespaced template. diff --git a/tests/namespaced/test-nested.twig b/tests/namespaced/test-nested.twig new file mode 100644 index 000000000..ae71e17bd --- /dev/null +++ b/tests/namespaced/test-nested.twig @@ -0,0 +1 @@ +{% include '@namespaced/test-namespaced.twig' %} diff --git a/tests/namespaced/thumb-test.twig b/tests/namespaced/thumb-test.twig new file mode 100644 index 000000000..ce129953d --- /dev/null +++ b/tests/namespaced/thumb-test.twig @@ -0,0 +1 @@ +namespaced diff --git a/tests/test-timber-loader.php b/tests/test-timber-loader.php index ac722a7a6..6f8aebc88 100644 --- a/tests/test-timber-loader.php +++ b/tests/test-timber-loader.php @@ -34,6 +34,7 @@ function testTwigPathFilterAdded() { function testTwigPathFilter() { $php_unit = $this; add_filter('timber/loader/paths', function($paths) use ($php_unit) { + $paths = call_user_func_array('array_merge', $paths); $count = count($paths); $php_unit->assertEquals(3, count($paths)); $pos = array_search('/', $paths); @@ -134,5 +135,65 @@ function testTwigLoadsFromLocation(){ $this->assertEquals('', trim($str)); } + function testTwigLoadsFromLocationWithNamespace(){ + Timber::$locations = array( __DIR__.'/assets' => 'assets' ); + $str = Timber::compile('@assets/thumb-test.twig'); + $this->assertEquals('', trim($str)); + } + + function testTwigLoadsFromLocationWithNestedNamespace(){ + Timber::$locations = array( __DIR__.'/namespaced' => 'namespaced' ); + $str = Timber::compile('@namespaced/test-nested.twig'); + $this->assertEquals('This is a namespaced template.', trim($str)); + } + + function testTwigLoadsFromLocationWithAndWithoutNamespaces(){ + Timber::$locations = array( __DIR__.'/namespaced' => 'namespaced', __DIR__ . '/assets' ); + + // Namespaced location + $str = Timber::compile('@namespaced/test-namespaced.twig'); + $this->assertEquals('This is a namespaced template.', trim($str)); + + // Non namespaced location + $str = Timber::compile('thumb-test.twig'); + $this->assertEquals('', trim($str)); + } + + function testTwigLoadsFromLocationWithAndWithoutNamespacesAndDirs(){ + Timber::$dirname = array('foo', 'views'); + Timber::$locations = array( __DIR__.'/namespaced' => 'namespaced', __DIR__ . '/assets' ); + + // Namespaced location + $str = Timber::compile('@namespaced/test-namespaced.twig'); + $this->assertEquals('This is a namespaced template.', trim($str)); + + // Non namespaced location + $str = Timber::compile('thumb-test.twig'); + $this->assertEquals('', trim($str)); + + if (!file_exists(get_template_directory().'/foo')) { + mkdir(get_template_directory().'/foo', 0777, true); + } + copy(__DIR__.'/assets/single-foo.twig', get_template_directory().'/foo/single-foo.twig'); + + // Dir + $str = Timber::compile('single-foo.twig'); + $this->assertEquals('I am single-foo', trim($str)); + } + + function testTwigLoadsFromMultipleLocationsWithNamespace(){ + Timber::$locations = array( __DIR__.'/assets' => 'assets', __DIR__ .'/namespaced' => 'assets' ); + $str = Timber::compile('@assets/thumb-test.twig'); + $this->assertEquals('', trim($str)); + + $str = Timber::compile('@assets/test-namespaced.twig'); + $this->assertEquals('This is a namespaced template.', trim($str)); + } + + function testTwigLoadsFirstTemplateWhenMultipleLocationsWithSameNamespace(){ + Timber::$locations = array( __DIR__.'/assets' => 'assets', __DIR__ .'/namespaced' => 'assets' ); + $str = Timber::compile('@assets/thumb-test.twig'); + $this->assertEquals('', trim($str)); + } }