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 @@
+
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));
+ }
}