diff --git a/Security/Member.php b/Security/Member.php index 85534a58f..f838bf49c 100644 --- a/Security/Member.php +++ b/Security/Member.php @@ -1509,7 +1509,7 @@ class Member extends DataObject implements TemplateGlobalProvider { ) ); $dateFormatField->setValue($self->DateFormat); - $dateFormatField->setDescriptionTemplate('MemberDatetimeOptionsetField_description_date'); + $dateFormatField->setDescriptionTemplate('forms/MemberDatetimeOptionsetField_description_date'); $defaultTimeFormat = Zend_Locale_Format::getTimeFormat(new Zend_Locale($self->Locale)); $timeFormatMap = array( @@ -1526,7 +1526,7 @@ class Member extends DataObject implements TemplateGlobalProvider { ) ); $timeFormatField->setValue($self->TimeFormat); - $timeFormatField->setDescriptionTemplate('MemberDatetimeOptionsetField_description_time'); + $timeFormatField->setDescriptionTemplate('forms/MemberDatetimeOptionsetField_description_time'); }); return parent::getCMSFields(); diff --git a/admin/code/LeftAndMain.php b/admin/code/LeftAndMain.php index 4114b2003..e25baccf0 100644 --- a/admin/code/LeftAndMain.php +++ b/admin/code/LeftAndMain.php @@ -783,7 +783,7 @@ class LeftAndMain extends Controller implements PermissionProvider { return $controller->renderWith($controller->getTemplatesWithSuffix('_Content')); }, 'Breadcrumbs' => function() use (&$controller) { - return $controller->renderWith('CMSBreadcrumbs'); + return $controller->renderWith('Includes/CMSBreadcrumbs'); }, 'default' => function() use(&$controller) { return $controller->renderWith($controller->getViewer('show')); diff --git a/admin/code/ModelAdmin.php b/admin/code/ModelAdmin.php index db54dcb50..63426a0bf 100644 --- a/admin/code/ModelAdmin.php +++ b/admin/code/ModelAdmin.php @@ -366,7 +366,7 @@ abstract class ModelAdmin extends LeftAndMain { 'ModelName' => Convert::raw2att($modelName), 'Fields' => $specFields, 'Relations' => $specRelations, - ))->renderWith('ModelAdmin_ImportSpec'); + ))->renderWith('Includes/ModelAdmin_ImportSpec'); $fields->push(new LiteralField("SpecFor{$modelName}", $specHTML)); $fields->push( diff --git a/admin/templates/CMSBreadcrumbs.ss b/admin/templates/Includes/CMSBreadcrumbs.ss similarity index 100% rename from admin/templates/CMSBreadcrumbs.ss rename to admin/templates/Includes/CMSBreadcrumbs.ss diff --git a/admin/templates/CMSTabSet.ss b/admin/templates/forms/CMSTabSet.ss similarity index 100% rename from admin/templates/CMSTabSet.ss rename to admin/templates/forms/CMSTabSet.ss diff --git a/core/Core.php b/core/Core.php index 66801e7dd..76e646012 100644 --- a/core/Core.php +++ b/core/Core.php @@ -77,8 +77,7 @@ require_once 'core/manifest/ConfigManifest.php'; require_once 'core/manifest/ConfigStaticManifest.php'; require_once 'core/manifest/ClassManifest.php'; require_once 'core/manifest/ManifestFileFinder.php'; -require_once 'core/manifest/TemplateLoader.php'; -require_once 'core/manifest/TemplateManifest.php'; +require_once 'view/TemplateLoader.php'; require_once 'core/manifest/TokenisedRegularExpression.php'; require_once 'control/injector/Injector.php'; @@ -113,7 +112,7 @@ $configManifest = new SS_ConfigManifest(BASE_PATH, false, $flush); Config::inst()->pushConfigYamlManifest($configManifest); // Load template manifest -SS_TemplateLoader::instance()->pushManifest(new SS_TemplateManifest( +SilverStripe\View\TemplateLoader::instance()->addSet('$default', new SilverStripe\View\ThemeManifest( BASE_PATH, project(), false, $flush )); diff --git a/core/manifest/TemplateLoader.php b/core/manifest/TemplateLoader.php deleted file mode 100644 index 5f9e7f58a..000000000 --- a/core/manifest/TemplateLoader.php +++ /dev/null @@ -1,87 +0,0 @@ -manifests[count($this->manifests) - 1]; - } - - /** - * @param SS_TemplateManifest $manifest - */ - public function pushManifest(SS_TemplateManifest $manifest) { - $this->manifests[] = $manifest; - } - - /** - * @return SS_TemplateManifest - */ - public function popManifest() { - return array_pop($this->manifests); - } - - /** - * Attempts to find possible candidate templates from a set of template - * names from modules, current theme directory and finally the application - * folder. - * - * The template names can be passed in as plain strings, or be in the - * format "type/name", where type is the type of template to search for - * (e.g. Includes, Layout). - * - * @param string|array $templates - * @param string $theme - * - * @return array - */ - public function findTemplates($templates, $theme = null) { - $result = array(); - - foreach ((array) $templates as $template) { - if (strpos($template, '/')) { - list($type, $template) = explode('/', $template, 2); - } else { - $type = null; - } - - if ($found = $this->getManifest()->getCandidateTemplate($template, $theme)) { - if ($type && isset($found[$type])) { - $found = array( - 'main' => $found[$type] - ); - } - $result = array_merge($found, $result); - } - } - - return $result; - } - -} diff --git a/core/manifest/TemplateManifest.php b/core/manifest/TemplateManifest.php deleted file mode 100644 index 4839a29a8..000000000 --- a/core/manifest/TemplateManifest.php +++ /dev/null @@ -1,271 +0,0 @@ -base = $base; - $this->tests = $includeTests; - - $this->project = $project; - - $cacheClass = defined('SS_MANIFESTCACHE') ? SS_MANIFESTCACHE : 'ManifestCache_File'; - - $this->cache = new $cacheClass('templatemanifest'.($includeTests ? '_tests' : '')); - $this->cacheKey = $this->getCacheKey($includeTests); - - if ($forceRegen) { - $this->regenerate(); - } - } - - /** - * @return string - */ - public function getBase() { - return $this->base; - } - - /** - * Generate a unique cache key to avoid manifest cache collisions. - * We compartmentalise based on the base path, the given project, and whether - * or not we intend to include tests. - * @param boolean $includeTests - * @return string - */ - public function getCacheKey($includeTests = false) { - return sha1(sprintf( - "manifest-%s-%s-%s", - $this->base, - $this->project, - (int) $includeTests // cast true to 1, false to 0 - ) - ); - } - - /** - * Returns a map of all template information. The map is in the following - * format: - * - * - * array( - * 'moduletemplate' => array( - * 'main' => '/path/to/module/templates/Main.ss' - * ), - * 'include' => array( - * 'include' => '/path/to/module/templates/Includes/Include.ss' - * ), - * 'page' => array( - * 'themes' => array( - * 'simple' => array( - * 'main' => '/path/to/theme/Page.ss' - * 'Layout' => '/path/to/theme/Layout/Page.ss' - * ) - * ) - * ) - * ) - * - * - * @return array - */ - public function getTemplates() { - if (!$this->inited) { - $this->init(); - } - - return $this->templates; - } - - /** - * Returns a set of possible candidate templates that match a certain - * template name. - * - * This is the same as extracting an individual array element from - * {@link SS_TemplateManifest::getTemplates()}. - * - * @param string $name - * @return array - */ - public function getTemplate($name) { - if (!$this->inited) { - $this->init(); - } - - $name = strtolower($name); - - if (array_key_exists($name, $this->templates)) { - return $this->templates[$name]; - } else { - return array(); - } - } - - /** - * Returns the correct candidate template. In order of importance, application - * project code, current theme and finally modules. - * - * @param string $name - * @param string $theme - theme name - * - * @return array - */ - public function getCandidateTemplate($name, $theme = null) { - $found = array(); - $candidates = $this->getTemplate($name); - - // theme overrides modules - if ($theme && isset($candidates['themes'][$theme])) { - $found = array_merge($candidates, $candidates['themes'][$theme]); - } - // project overrides theme - if ($this->project && isset($candidates[$this->project])) { - $found = array_merge($found, $candidates[$this->project]); - } - - $found = ($found) ? $found : $candidates; - - if (isset($found['themes'])) unset($found['themes']); - if (isset($found[$this->project])) unset($found[$this->project]); - - return $found; - } - - /** - * Regenerates the manifest by scanning the base path. - * - * @param bool $cache - */ - public function regenerate($cache = true) { - $finder = new ManifestFileFinder(); - $finder->setOptions(array( - 'name_regex' => '/\.ss$/', - 'include_themes' => true, - 'ignore_tests' => !$this->tests, - 'file_callback' => array($this, 'handleFile') - )); - - $finder->find($this->base); - - if ($cache) { - $this->cache->save($this->templates, $this->cacheKey); - } - - $this->inited = true; - } - - public function handleFile($basename, $pathname, $depth) - { - $projectFile = false; - $theme = null; - - // Template in theme - if (preg_match( - '#'.preg_quote($this->base.'/'.THEMES_DIR).'/([^/_]+)(_[^/]+)?/(.*)$#', - $pathname, - $matches - )) { - $theme = $matches[1]; - $relPath = $matches[3]; - - // Template in project - } elseif (preg_match( - '#'.preg_quote($this->base.'/'.$this->project).'/(.*)$#', - $pathname, - $matches - )) { - $projectFile = true; - $relPath = $matches[1]; - - // Template in module - } elseif (preg_match( - '#'.preg_quote($this->base).'/([^/]+)/(.*)$#', - $pathname, - $matches - )) { - $relPath = $matches[2]; - - } else { - throw new \LogicException("Can't determine meaning of path: $pathname"); - } - - // If a templates subfolder is used, ignore that - if (preg_match('#'.preg_quote(self::TEMPLATES_DIR).'/(.*)$#', $relPath, $matches)) { - $relPath = $matches[1]; - } - - // Layout and Content folders have special meaning - if (preg_match('#^(.*/)?(Layout|Content|Includes)/([^/]+)$#', $relPath, $matches)) { - $type = $matches[2]; - $relPath = "$matches[1]$matches[3]"; - } else { - $type = "main"; - } - - $name = strtolower(substr($relPath, 0, -3)); - $name = str_replace('/', '\\', $name); - - if ($theme) { - $this->templates[$name]['themes'][$theme][$type] = $pathname; - } else if ($projectFile) { - $this->templates[$name][$this->project][$type] = $pathname; - } else { - $this->templates[$name][$type] = $pathname; - } - - // If we've found a template in a subdirectory, then allow its use for a non-namespaced class - // as well. This was a common SilverStripe 3 approach, where templates were placed into - // subfolders to suit the whim of the developer. - if (strpos($name, '\\') !== false) { - $name2 = substr($name, strrpos($name, '\\') + 1); - // In of these cases, the template will only be provided if it isn't already set. This - // matches SilverStripe 3 prioritisation. - if ($theme) { - if (!isset($this->templates[$name2]['themes'][$theme][$type])) { - $this->templates[$name2]['themes'][$theme][$type] = $pathname; - } - } else if ($projectFile) { - if (!isset($this->templates[$name2][$this->project][$type])) { - $this->templates[$name2][$this->project][$type] = $pathname; - } - } else { - if (!isset($this->templates[$name2][$type])) { - $this->templates[$name2][$type] = $pathname; - } - } - } - } - - protected function init() { - if ($data = $this->cache->load($this->cacheKey)) { - $this->templates = $data; - $this->inited = true; - } else { - $this->regenerate(); - } - } -} diff --git a/dev/SapphireTest.php b/dev/SapphireTest.php index b9fdb9c77..9a7f50e28 100644 --- a/dev/SapphireTest.php +++ b/dev/SapphireTest.php @@ -13,8 +13,8 @@ use SilverStripe\Security\Member; use SilverStripe\Security\Security; use SilverStripe\Security\Group; use SilverStripe\Security\Permission; - - +use SilverStripe\View\TemplateLoader; +use SilverStripe\View\ThemeManifest; /** * Test case class for the Sapphire framework. @@ -848,7 +848,7 @@ class SapphireTest extends PHPUnit_Framework_TestCase { SS_ClassLoader::instance()->pushManifest($classManifest, false); SapphireTest::set_test_class_manifest($classManifest); - SS_TemplateLoader::instance()->pushManifest(new SS_TemplateManifest( + TemplateLoader::instance()->addSet('$default', new ThemeManifest( BASE_PATH, project(), true, $flush )); @@ -1054,22 +1054,15 @@ class SapphireTest extends PHPUnit_Framework_TestCase { */ protected function useTestTheme($themeBaseDir, $theme, $callback) { Config::nest(); - global $project; - $manifest = new SS_TemplateManifest($themeBaseDir, $project, true, true); - - SS_TemplateLoader::instance()->pushManifest($manifest); - - Config::inst()->update('SSViewer', 'theme', $theme); + if (strpos($themeBaseDir, BASE_PATH) === 0) $themeBaseDir = substr($themeBaseDir, strlen(BASE_PATH)); + SSViewer::set_themes([$themeBaseDir.'/themes/'.$theme, '$default']); $e = null; try { $callback(); } catch (Exception $e) { /* NOP for now, just save $e */ } - // Remove all the test themes we created - SS_TemplateLoader::instance()->popManifest(); - Config::unnest(); if ($e) throw $e; diff --git a/docs/en/04_Changelogs/4.0.0.md b/docs/en/04_Changelogs/4.0.0.md index c91773d09..ea3d58239 100644 --- a/docs/en/04_Changelogs/4.0.0.md +++ b/docs/en/04_Changelogs/4.0.0.md @@ -395,6 +395,14 @@ all changed project files. This will resolve the majority of upgrading work, but for specific changes that will require manual intervention, please see the below upgrading notes. +### Make sure templates are in correct locations + +Templates are now much more strict about their locations. You can no longer put a template in an arbitrary +folder and have it be found. Case is now also checked on case-sensitive filesystems. + +Either include the folder in the template name (`renderWith('Field.ss')` => `renderWith('forms/Field.ss')`), move the +template into the correct directory, or both. + ### Update code that uses SQLQuery Where your code once used SQLQuery you should now use SQLSelect in all cases, as this has been removed. diff --git a/email/Email.php b/email/Email.php index b35dcbabe..c0dbca55f 100644 --- a/email/Email.php +++ b/email/Email.php @@ -373,7 +373,7 @@ class Email extends ViewableData { // Requery data so that updated versions of To, From, Subject, etc are included $data = $this->templateData(); - $template = new SSViewer($this->ss_template); + $template = new SSViewer('email/'.$this->ss_template); if($template->exists()) { $fullBody = $template->process($data); diff --git a/filesystem/flysystem/ProtectedAssetAdapter.php b/filesystem/flysystem/ProtectedAssetAdapter.php index 1dc91b200..2ca223f55 100644 --- a/filesystem/flysystem/ProtectedAssetAdapter.php +++ b/filesystem/flysystem/ProtectedAssetAdapter.php @@ -16,10 +16,10 @@ class ProtectedAssetAdapter extends AssetAdapter implements ProtectedAdapter { private static $server_configuration = array( 'apache' => array( - '.htaccess' => "Protected_HTAccess" + '.htaccess' => "filesystem/Protected_HTAccess" ), 'microsoft-iis' => array( - 'web.config' => "Protected_WebConfig" + 'web.config' => "filesystem/Protected_WebConfig" ) ); diff --git a/filesystem/flysystem/PublicAssetAdapter.php b/filesystem/flysystem/PublicAssetAdapter.php index e6e27f024..d425a7439 100644 --- a/filesystem/flysystem/PublicAssetAdapter.php +++ b/filesystem/flysystem/PublicAssetAdapter.php @@ -21,10 +21,10 @@ class PublicAssetAdapter extends AssetAdapter implements PublicAdapter { */ private static $server_configuration = array( 'apache' => array( - '.htaccess' => "Assets_HTAccess" + '.htaccess' => "filesystem/Assets_HTAccess" ), 'microsoft-iis' => array( - 'web.config' => "Assets_WebConfig" + 'web.config' => "filesystem/Assets_WebConfig" ) ); diff --git a/forms/AssetField.php b/forms/AssetField.php index 9e983606d..7cbc13e7a 100644 --- a/forms/AssetField.php +++ b/forms/AssetField.php @@ -48,7 +48,7 @@ class AssetField extends FileField { * * @var string */ - protected $templateFileButtons = 'AssetField_FileButtons'; + protected $templateFileButtons = 'Includes/AssetField_FileButtons'; /** * Parent data record. Will be infered from parent form or controller if blank. The destination diff --git a/forms/Form.php b/forms/Form.php index 7edee9dd3..5dd5e9d3e 100644 --- a/forms/Form.php +++ b/forms/Form.php @@ -1632,7 +1632,7 @@ class Form extends RequestHandler { public function forTemplate() { $return = $this->renderWith(array_merge( (array)$this->getTemplate(), - array('Form') + array('Includes/Form') )); // Now that we're rendered, clear message diff --git a/forms/FormField.php b/forms/FormField.php index 37e43bc35..62ffefe55 100644 --- a/forms/FormField.php +++ b/forms/FormField.php @@ -1051,7 +1051,7 @@ class FormField extends RequestHandler { $matches = array(); foreach(array_reverse(ClassInfo::ancestry($this)) as $className) { - $matches[] = $className . $customTemplateSuffix; + $matches[] = 'forms/'. $className . $customTemplateSuffix; if($className == "FormField") { break; @@ -1059,7 +1059,7 @@ class FormField extends RequestHandler { } if($customTemplate) { - array_unshift($matches, $customTemplate); + array_unshift($matches, 'forms/'.$customTemplate); } return $matches; diff --git a/forms/MemberDatetimeOptionsetField.php b/forms/MemberDatetimeOptionsetField.php index a3d456247..67f011ff1 100644 --- a/forms/MemberDatetimeOptionsetField.php +++ b/forms/MemberDatetimeOptionsetField.php @@ -44,6 +44,7 @@ class MemberDatetimeOptionsetField extends OptionsetField { 'Options' => new ArrayList($options) )); + return $this->customise($properties)->renderWith( $this->getTemplates() ); diff --git a/forms/UploadField.php b/forms/UploadField.php index d0a582a8a..3f43f3f99 100644 --- a/forms/UploadField.php +++ b/forms/UploadField.php @@ -65,7 +65,7 @@ class UploadField extends FileField { * * @var string */ - protected $templateFileButtons = 'UploadField_FileButtons'; + protected $templateFileButtons = 'Includes/UploadField_FileButtons'; /** * Template to use for the edit form diff --git a/forms/gridfield/GridFieldAddExistingAutocompleter.php b/forms/gridfield/GridFieldAddExistingAutocompleter.php index 3796c1cd8..e9898d97c 100644 --- a/forms/gridfield/GridFieldAddExistingAutocompleter.php +++ b/forms/gridfield/GridFieldAddExistingAutocompleter.php @@ -131,7 +131,7 @@ class GridFieldAddExistingAutocompleter } return array( - $this->targetFragment => $forTemplate->renderWith($this->itemClass) + $this->targetFragment => $forTemplate->renderWith('Includes/'.$this->itemClass) ); } diff --git a/forms/gridfield/GridFieldAddNewButton.php b/forms/gridfield/GridFieldAddNewButton.php index 4c63e455b..ca83deb05 100644 --- a/forms/gridfield/GridFieldAddNewButton.php +++ b/forms/gridfield/GridFieldAddNewButton.php @@ -44,7 +44,7 @@ class GridFieldAddNewButton implements GridField_HTMLProvider { )); return array( - $this->targetFragment => $data->renderWith('GridFieldAddNewbutton'), + $this->targetFragment => $data->renderWith('Includes/GridFieldAddNewButton'), ); } diff --git a/forms/gridfield/GridFieldButtonRow.php b/forms/gridfield/GridFieldButtonRow.php index 5acb8c82a..58feae9b4 100644 --- a/forms/gridfield/GridFieldButtonRow.php +++ b/forms/gridfield/GridFieldButtonRow.php @@ -27,7 +27,7 @@ class GridFieldButtonRow implements GridField_HTMLProvider { )); return array( - $this->targetFragment => $data->renderWith('GridFieldButtonRow') + $this->targetFragment => $data->renderWith('Includes/GridFieldButtonRow') ); } } diff --git a/forms/gridfield/GridFieldDetailForm.php b/forms/gridfield/GridFieldDetailForm.php index 8f16724e9..c7a4084a3 100644 --- a/forms/gridfield/GridFieldDetailForm.php +++ b/forms/gridfield/GridFieldDetailForm.php @@ -441,7 +441,7 @@ class GridFieldDetailForm_ItemRequest extends RequestHandler { // Always show with base template (full width, no other panels), // regardless of overloaded CMS controller templates. // TODO Allow customization, e.g. to display an edit form alongside a search form from the CMS controller - $form->setTemplate('LeftAndMain_EditForm'); + $form->setTemplate('Includes/LeftAndMain_EditForm'); $form->addExtraClass('cms-content cms-edit-form center'); $form->setAttribute('data-pjax-fragment', 'CurrentForm Content'); if($form->Fields()->hasTabset()) { diff --git a/forms/gridfield/GridFieldEditButton.php b/forms/gridfield/GridFieldEditButton.php index 8f2b6dc66..9801e3b3f 100644 --- a/forms/gridfield/GridFieldEditButton.php +++ b/forms/gridfield/GridFieldEditButton.php @@ -86,7 +86,7 @@ class GridFieldEditButton implements GridField_ColumnProvider { 'Link' => Controller::join_links($gridField->Link('item'), $record->ID, 'edit') )); - return $data->renderWith('GridFieldEditButton'); + return $data->renderWith('Includes/GridFieldEditButton'); } /** diff --git a/forms/gridfield/GridFieldFilterHeader.php b/forms/gridfield/GridFieldFilterHeader.php index 02798da23..471f99700 100755 --- a/forms/gridfield/GridFieldFilterHeader.php +++ b/forms/gridfield/GridFieldFilterHeader.php @@ -162,7 +162,7 @@ class GridFieldFilterHeader implements GridField_HTMLProvider, GridField_DataMan } return array( - 'header' => $forTemplate->renderWith('GridFieldFilterHeader_Row'), + 'header' => $forTemplate->renderWith('Includes/GridFieldFilterHeader_Row'), ); } } diff --git a/forms/gridfield/GridFieldFooter.php b/forms/gridfield/GridFieldFooter.php index 1fba1c2a6..e360ded96 100644 --- a/forms/gridfield/GridFieldFooter.php +++ b/forms/gridfield/GridFieldFooter.php @@ -48,7 +48,7 @@ class GridFieldFooter implements GridField_HTMLProvider { return array( 'footer' => $forTemplate->renderWith( - 'GridFieldFooter', + 'Includes/GridFieldFooter', array( 'Colspan' => count($gridField->getColumns()) ) diff --git a/forms/gridfield/GridFieldLevelup.php b/forms/gridfield/GridFieldLevelup.php index 34149217b..4894f1cbe 100644 --- a/forms/gridfield/GridFieldLevelup.php +++ b/forms/gridfield/GridFieldLevelup.php @@ -66,7 +66,7 @@ class GridFieldLevelup extends Object implements GridField_HTMLProvider { )); return array( - 'before' => $forTemplate->renderWith('GridFieldLevelup'), + 'before' => $forTemplate->renderWith('Includes/GridFieldLevelup'), ); } } diff --git a/forms/gridfield/GridFieldPageCount.php b/forms/gridfield/GridFieldPageCount.php index e7175a71a..380dc2490 100755 --- a/forms/gridfield/GridFieldPageCount.php +++ b/forms/gridfield/GridFieldPageCount.php @@ -67,7 +67,7 @@ class GridFieldPageCount implements GridField_HTMLProvider { $paginator = $this->getPaginator($gridField); if ($paginator && ($forTemplate = $paginator->getTemplateParameters($gridField))) { return array( - $this->targetFragment => $forTemplate->renderWith($this->itemClass) + $this->targetFragment => $forTemplate->renderWith('Includes/'.$this->itemClass) ); } } diff --git a/forms/gridfield/GridFieldPaginator.php b/forms/gridfield/GridFieldPaginator.php index 5f298f43e..332746384 100755 --- a/forms/gridfield/GridFieldPaginator.php +++ b/forms/gridfield/GridFieldPaginator.php @@ -259,7 +259,7 @@ class GridFieldPaginator implements GridField_HTMLProvider, GridField_DataManipu $forTemplate = $this->getTemplateParameters($gridField); if($forTemplate) { return array( - 'footer' => $forTemplate->renderWith($this->itemClass, + 'footer' => $forTemplate->renderWith('Includes/'.$this->itemClass, array('Colspan'=>count($gridField->getColumns()))) ); } diff --git a/forms/gridfield/GridFieldSortableHeader.php b/forms/gridfield/GridFieldSortableHeader.php index a85a3c956..963e4c37c 100644 --- a/forms/gridfield/GridFieldSortableHeader.php +++ b/forms/gridfield/GridFieldSortableHeader.php @@ -164,7 +164,7 @@ class GridFieldSortableHeader implements GridField_HTMLProvider, GridField_DataM } return array( - 'header' => $forTemplate->renderWith('GridFieldSortableHeader_Row'), + 'header' => $forTemplate->renderWith('Includes/GridFieldSortableHeader_Row'), ); } diff --git a/forms/gridfield/GridFieldToolbarHeader.php b/forms/gridfield/GridFieldToolbarHeader.php index f4cab23b7..3f8296d98 100644 --- a/forms/gridfield/GridFieldToolbarHeader.php +++ b/forms/gridfield/GridFieldToolbarHeader.php @@ -13,7 +13,7 @@ class GridFieldToolbarHeader implements GridField_HTMLProvider { public function getHTMLFragments( $gridField) { return array( - 'header' => $gridField->renderWith('GridFieldToolbarHeader') + 'header' => $gridField->renderWith('Includes/GridFieldToolbarHeader') ); } } diff --git a/forms/gridfield/GridFieldViewButton.php b/forms/gridfield/GridFieldViewButton.php index c18190e3a..c65773376 100644 --- a/forms/gridfield/GridFieldViewButton.php +++ b/forms/gridfield/GridFieldViewButton.php @@ -22,7 +22,7 @@ class GridFieldViewButton implements GridField_ColumnProvider { $data = new ArrayData(array( 'Link' => Controller::join_links($field->Link('item'), $record->ID, 'view') )); - return $data->renderWith('GridFieldViewButton'); + return $data->renderWith('Includes/GridFieldViewButton'); } } diff --git a/forms/htmleditor/HTMLEditorField.php b/forms/htmleditor/HTMLEditorField.php index 8a6eca7bb..f7ab9ccde 100644 --- a/forms/htmleditor/HTMLEditorField.php +++ b/forms/htmleditor/HTMLEditorField.php @@ -181,7 +181,7 @@ class HTMLEditorField_Toolbar extends RequestHandler { /** * @var string */ - protected $templateViewFile = 'HTMLEditorField_viewfile'; + protected $templateViewFile = 'Includes/HTMLEditorField_viewfile'; protected $controller, $name; diff --git a/forms/htmleditor/TinyMCEConfig.php b/forms/htmleditor/TinyMCEConfig.php index fea517b8e..423ee7ca2 100644 --- a/forms/htmleditor/TinyMCEConfig.php +++ b/forms/htmleditor/TinyMCEConfig.php @@ -1,5 +1,7 @@ getPath($theme); + $editorDir = $path . '/css/editor.css';; + if(file_exists(BASE_PATH . '/' . $editorDir)) { $editor[] = Controller::join_links( Director::absoluteBaseURL(), $editorDir ); + + break; } } return $editor; diff --git a/templates/AssetField.ss b/templates/forms/AssetField.ss similarity index 100% rename from templates/AssetField.ss rename to templates/forms/AssetField.ss diff --git a/templates/AssetUploadField.ss b/templates/forms/AssetUploadField.ss similarity index 100% rename from templates/AssetUploadField.ss rename to templates/forms/AssetUploadField.ss diff --git a/templates/HTMLEditorField_UploadField.ss b/templates/forms/HTMLEditorField_UploadField.ss similarity index 100% rename from templates/HTMLEditorField_UploadField.ss rename to templates/forms/HTMLEditorField_UploadField.ss diff --git a/templates/UploadField.ss b/templates/forms/UploadField.ss similarity index 100% rename from templates/UploadField.ss rename to templates/forms/UploadField.ss diff --git a/tests/core/manifest/TemplateLoaderTest.php b/tests/core/manifest/TemplateLoaderTest.php index 3bffdd623..ebcb7cb0b 100644 --- a/tests/core/manifest/TemplateLoaderTest.php +++ b/tests/core/manifest/TemplateLoaderTest.php @@ -1,6 +1,10 @@ base = dirname(__FILE__) . '/fixtures/templatemanifest'; - $this->manifest = new SS_TemplateManifest($this->base, 'myproject', false, true); - $this->loader = new SS_TemplateLoader(); - $this->refreshLoader(); + // New ThemeManifest for that root + $this->manifest = new \SilverStripe\View\ThemeManifest($this->base, 'myproject', false, true); + // New Loader for that root + $this->loader = new \SilverStripe\View\TemplateLoader($this->base); + $this->loader->addSet('$default', $this->manifest); } /** * Test that 'main' and 'Layout' templates are loaded from module */ public function testFindTemplatesInModule() { - $expect = array( - 'main' => "$this->base/module/templates/Page.ss", - 'Layout' => "$this->base/module/templates/Layout/Page.ss" + $this->assertEquals( + "$this->base/module/templates/Page.ss", + $this->loader->findTemplate('Page', ['$default']) + ); + + $this->assertEquals( + "$this->base/module/templates/Layout/Page.ss", + $this->loader->findTemplate(['type' => 'Layout', 'Page'], ['$default']) ); - $this->assertEquals($expect, $this->loader->findTemplates('Page')); - $this->assertEquals($expect, $this->loader->findTemplates('PAGE')); - $this->assertEquals($expect, $this->loader->findTemplates(array('Foo', 'Page'))); } /** * Test that 'main' and 'Layout' templates are loaded from set theme */ public function testFindTemplatesInTheme() { - $expect = array( - 'main' => "$this->base/themes/theme/templates/Page.ss", - 'Layout' => "$this->base/themes/theme/templates/Layout/Page.ss" + $this->assertEquals( + "$this->base/themes/theme/templates/Page.ss", + $this->loader->findTemplate('Page', ['theme']) + ); + + $this->assertEquals( + "$this->base/themes/theme/templates/Layout/Page.ss", + $this->loader->findTemplate(['type' => 'Layout', 'Page'], ['theme']) ); - $this->assertEquals($expect, $this->loader->findTemplates('Page', 'theme')); - $this->assertEquals($expect, $this->loader->findTemplates('PAGE', 'theme')); - $this->assertEquals($expect, $this->loader->findTemplates(array('Foo', 'Page'), 'theme')); } /** * Test that 'main' and 'Layout' templates are loaded from project without a set theme */ public function testFindTemplatesInApplication() { + // TODO: replace with one that doesn't create temporary files (so bad) $templates = array( $this->base . '/myproject/templates/Page.ss', $this->base . '/myproject/templates/Layout/Page.ss' ); $this->createTestTemplates($templates); - $this->refreshLoader(); - $expect = array( - 'main' => "$this->base/myproject/templates/Page.ss", - 'Layout' => "$this->base/myproject/templates/Layout/Page.ss" + $this->assertEquals( + "$this->base/myproject/templates/Page.ss", + $this->loader->findTemplate('Page', ['$default']) + ); + + $this->assertEquals( + "$this->base/myproject/templates/Layout/Page.ss", + $this->loader->findTemplate(['type' => 'Layout', 'Page'], ['$default']) ); - $this->assertEquals($expect, $this->loader->findTemplates('Page')); - $this->assertEquals($expect, $this->loader->findTemplates('PAGE')); - $this->assertEquals($expect, $this->loader->findTemplates(array('Foo', 'Page'))); $this->removeTestTemplates($templates); } - /** - * Test that 'Layout' template is loaded from module - */ - public function testFindTemplatesInModuleLayout() { - $expect = array( - 'main' => "$this->base/module/templates/Layout/Page.ss" - ); - $this->assertEquals($expect, $this->loader->findTemplates('Layout/Page')); - } - - /** - * Test that 'Layout' template is loaded from theme - */ - public function testFindTemplatesInThemeLayout() { - $expect = array( - 'main' => "$this->base/themes/theme/templates/Layout/Page.ss" - ); - $this->assertEquals($expect, $this->loader->findTemplates('Layout/Page', 'theme')); - } - /** * Test that 'main' template is found in theme and 'Layout' is found in module */ public function testFindTemplatesMainThemeLayoutModule() { - $expect = array( - 'main' => "$this->base/themes/theme/templates/CustomThemePage.ss", - 'Layout' => "$this->base/module/templates/Layout/CustomThemePage.ss" + $this->assertEquals( + "$this->base/themes/theme/templates/CustomThemePage.ss", + $this->loader->findTemplate('CustomThemePage', ['theme', '$default']) ); - $this->assertEquals($expect, $this->loader->findTemplates(array('CustomThemePage', 'Page'), 'theme')); - } - /** - * Test that project template overrides module template of same name - */ - public function testFindTemplatesApplicationOverridesModule() { - $expect = array( - 'main' => "$this->base/myproject/templates/CustomTemplate.ss" + $this->assertEquals( + "$this->base/module/templates/Layout/CustomThemePage.ss", + $this->loader->findTemplate(['type' => 'Layout', 'CustomThemePage'], ['theme', '$default']) ); - $this->assertEquals($expect, $this->loader->findTemplates('CustomTemplate')); - } - - /** - * Test that project templates overrides theme templates - */ - public function testFindTemplatesApplicationOverridesTheme() { - $templates = array( - $this->base . '/myproject/templates/Page.ss', - $this->base . '/myproject/templates/Layout/Page.ss' - ); - $this->createTestTemplates($templates); - $this->refreshLoader(); - - $expect = array( - 'main' => "$this->base/myproject/templates/Page.ss", - 'Layout' => "$this->base/myproject/templates/Layout/Page.ss" - ); - $this->assertEquals($expect, $this->loader->findTemplates('Page'), 'theme'); - - $this->removeTestTemplates($templates); - } - - /** - * Test that project 'Layout' template overrides theme 'Layout' template - */ - public function testFindTemplatesApplicationLayoutOverridesThemeLayout() { - $templates = array( - $this->base . '/myproject/templates/Layout/Page.ss' - ); - $this->createTestTemplates($templates); - $this->refreshLoader(); - - $expect = array( - 'main' => "$this->base/themes/theme/templates/Page.ss", - 'Layout' => "$this->base/myproject/templates/Layout/Page.ss" - ); - $this->assertEquals($expect, $this->loader->findTemplates('Page', 'theme')); - - $this->removeTestTemplates($templates); - } - - /** - * Test that project 'main' template overrides theme 'main' template - */ - public function testFindTemplatesApplicationMainOverridesThemeMain() { - $templates = array( - $this->base . '/myproject/templates/Page.ss' - ); - $this->createTestTemplates($templates); - $this->refreshLoader(); - - $expect = array( - 'main' => "$this->base/myproject/templates/Page.ss", - 'Layout' => "$this->base/themes/theme/templates/Layout/Page.ss" - ); - $this->assertEquals($expect, $this->loader->findTemplates('Page', 'theme')); - - $this->removeTestTemplates($templates); - } - - protected function refreshLoader() { - $this->manifest->regenerate(false); - $this->loader->pushManifest($this->manifest); } protected function createTestTemplates($templates) { diff --git a/tests/core/manifest/TemplateManifestTest.php b/tests/core/manifest/TemplateManifestTest.php deleted file mode 100644 index 61e9af45c..000000000 --- a/tests/core/manifest/TemplateManifestTest.php +++ /dev/null @@ -1,141 +0,0 @@ -base = dirname(__FILE__) . '/fixtures/templatemanifest'; - $this->manifest = new SS_TemplateManifest($this->base, 'myproject'); - $this->manifestTests = new SS_TemplateManifest($this->base, 'myproject', true); - - $this->manifest->regenerate(false); - $this->manifestTests->regenerate(false); - } - - public function testGetTemplates() { - $expect = array( - 'root' => array( - 'main' => "{$this->base}/module/Root.ss" - ), - 'page' => array( - 'main' => "{$this->base}/module/templates/Page.ss", - 'Layout' => "{$this->base}/module/templates/Layout/Page.ss", - 'themes' => array('theme' => array( - 'main' => "{$this->base}/themes/theme/templates/Page.ss", - 'Layout' => "{$this->base}/themes/theme/templates/Layout/Page.ss" - )) - ), - 'custompage' => array( - 'Layout' => "{$this->base}/module/templates/Layout/CustomPage.ss" - ), - 'customtemplate' => array( - 'main' => "{$this->base}/module/templates/CustomTemplate.ss", - 'myproject' => array( - 'main' => "{$this->base}/myproject/templates/CustomTemplate.ss" - ) - ), - 'subfolder' => array( - 'main' => "{$this->base}/module/subfolder/templates/Subfolder.ss" - ), - 'customthemepage' => array ( - 'Layout' => "{$this->base}/module/templates/Layout/CustomThemePage.ss", - 'themes' => - array( - 'theme' => array('main' => "{$this->base}/themes/theme/templates/CustomThemePage.ss",) - ) - ), - 'mynamespace\myclass' => array( - 'main' => "{$this->base}/module/templates/MyNamespace/MyClass.ss", - 'Layout' => "{$this->base}/module/templates/MyNamespace/Layout/MyClass.ss", - 'themes' => array( - 'theme' => array( - 'main' => "{$this->base}/themes/theme/templates/MyNamespace/MyClass.ss", - ) - ), - ), - 'mynamespace\mysubnamespace\mysubclass' => array( - 'main' => "{$this->base}/module/templates/MyNamespace/MySubnamespace/MySubclass.ss", - ), - 'myclass' => array( - 'main' => "{$this->base}/module/templates/MyNamespace/MyClass.ss", - 'Layout' => "{$this->base}/module/templates/MyNamespace/Layout/MyClass.ss", - 'themes' => array( - 'theme' => array( - 'main' => "{$this->base}/themes/theme/templates/MyNamespace/MyClass.ss", - ) - ), - ), - 'mysubclass' => array( - 'main' => "{$this->base}/module/templates/MyNamespace/MySubnamespace/MySubclass.ss", - ), - 'include' => array('themes' => array( - 'theme' => array( - 'Includes' => "{$this->base}/themes/theme/templates/Includes/Include.ss" - ) - )) - ); - - $expectTests = $expect; - $expectTests['test'] = array( - 'main' => "{$this->base}/module/tests/templates/Test.ss" - ); - - $manifest = $this->manifest->getTemplates(); - $manifestTests = $this->manifestTests->getTemplates(); - - ksort($expect); - ksort($expectTests); - ksort($manifest); - ksort($manifestTests); - - $this->assertEquals( - $expect, $manifest, - 'All templates are correctly loaded in the manifest.' - ); - - $this->assertEquals( - $expectTests, $manifestTests, - 'The test manifest is the same, but includes test templates.' - ); - } - - public function testGetTemplate() { - $expectPage = array( - 'main' => "{$this->base}/module/templates/Page.ss", - 'Layout' => "{$this->base}/module/templates/Layout/Page.ss", - 'themes' => array('theme' => array( - 'main' => "{$this->base}/themes/theme/templates/Page.ss", - 'Layout' => "{$this->base}/themes/theme/templates/Layout/Page.ss" - )) - ); - - $expectTests = array( - 'main' => "{$this->base}/module/tests/templates/Test.ss" - ); - - $this->assertEquals($expectPage, $this->manifest->getTemplate('Page')); - $this->assertEquals($expectPage, $this->manifest->getTemplate('PAGE')); - $this->assertEquals($expectPage, $this->manifestTests->getTemplate('Page')); - $this->assertEquals($expectPage, $this->manifestTests->getTemplate('PAGE')); - - $this->assertEquals(array(), $this->manifest->getTemplate('Test')); - $this->assertEquals($expectTests, $this->manifestTests->getTemplate('Test')); - - $this->assertEquals(array( - 'main' => "{$this->base}/module/templates/CustomTemplate.ss", - 'myproject' => array( - 'main' => "{$this->base}/myproject/templates/CustomTemplate.ss" - )), $this->manifestTests->getTemplate('CustomTemplate')); - } - -} diff --git a/tests/forms/MemberDatetimeOptionsetFieldTest.php b/tests/forms/MemberDatetimeOptionsetFieldTest.php index 8d25221dd..7aa127726 100644 --- a/tests/forms/MemberDatetimeOptionsetFieldTest.php +++ b/tests/forms/MemberDatetimeOptionsetFieldTest.php @@ -110,7 +110,7 @@ class MemberDatetimeOptionsetFieldTest extends SapphireTest { $field->setDescription('Test description'); $this->assertEquals('Test description', $field->getDescription()); - $field->setDescriptionTemplate('MemberDatetimeOptionsetField_description_time'); + $field->setDescriptionTemplate('forms/MemberDatetimeOptionsetField_description_time'); $this->assertNotEmpty($field->getDescription()); $this->assertNotEquals('Test description', $field->getDescription()); } diff --git a/tests/i18n/i18nSSLegacyAdapterTest.php b/tests/i18n/i18nSSLegacyAdapterTest.php index 520eec3f9..475cba780 100644 --- a/tests/i18n/i18nSSLegacyAdapterTest.php +++ b/tests/i18n/i18nSSLegacyAdapterTest.php @@ -1,9 +1,11 @@ alternateBaseSavePath); Config::inst()->update('Director', 'alternate_base_folder', $this->alternateBasePath); - // Push a template loader running from the fake webroot onto the stack. - $templateManifest = new SS_TemplateManifest($this->alternateBasePath, null, false, true); - $templateManifest->regenerate(false); - SS_TemplateLoader::instance()->pushManifest($templateManifest); + // Replace old template loader with new one with alternate base path + $this->_oldLoader = TemplateLoader::instance(); + TemplateLoader::set_instance(new TemplateLoader($this->alternateBasePath)); + $this->_oldTheme = Config::inst()->get('SSViewer', 'theme'); Config::inst()->update('SSViewer', 'theme', 'testtheme1'); @@ -40,7 +42,7 @@ class i18nSSLegacyAdapterTest extends SapphireTest { } public function tearDown() { - SS_TemplateLoader::instance()->popManifest(); + TemplateLoader::set_instance($this->_oldLoader); SS_ClassLoader::instance()->popManifest(); i18n::set_locale($this->originalLocale); Config::inst()->update('Director', 'alternate_base_folder', null); diff --git a/tests/i18n/i18nTest.php b/tests/i18n/i18nTest.php index 3c0fb6209..3f774f5b2 100644 --- a/tests/i18n/i18nTest.php +++ b/tests/i18n/i18nTest.php @@ -1,6 +1,9 @@ alternateBaseSavePath); Config::inst()->update('Director', 'alternate_base_folder', $this->alternateBasePath); - // Push a template loader running from the fake webroot onto the stack. - $templateManifest = new SS_TemplateManifest($this->alternateBasePath, null, false, true); - $templateManifest->regenerate(false); - SS_TemplateLoader::instance()->pushManifest($templateManifest); + // Replace old template loader with new one with alternate base path + $this->_oldLoader = TemplateLoader::instance(); + TemplateLoader::set_instance($loader = new TemplateLoader($this->alternateBasePath)); + $loader->addSet('$default', new ThemeManifest( + $this->alternateBasePath, project(), false, true + )); + $this->_oldTheme = Config::inst()->get('SSViewer', 'theme'); Config::inst()->update('SSViewer', 'theme', 'testtheme1'); @@ -58,7 +64,7 @@ class i18nTest extends SapphireTest { } public function tearDown() { - SS_TemplateLoader::instance()->popManifest(); + TemplateLoader::set_instance($this->_oldLoader); i18n::set_locale($this->originalLocale); Config::inst()->update('Director', 'alternate_base_folder', null); Config::inst()->update('SSViewer', 'theme', $this->_oldTheme); diff --git a/tests/i18n/i18nTextCollectorTest.php b/tests/i18n/i18nTextCollectorTest.php index e6da0eda2..ca599b452 100644 --- a/tests/i18n/i18nTextCollectorTest.php +++ b/tests/i18n/i18nTextCollectorTest.php @@ -1,4 +1,7 @@ alternateBasePath, false, true, false ); - $manifest = new SS_TemplateManifest($this->alternateBasePath, null, false, true); - $manifest->regenerate(false); - SS_TemplateLoader::instance()->pushManifest($manifest); + // Replace old template loader with new one with alternate base path + $this->_oldLoader = TemplateLoader::instance(); + TemplateLoader::set_instance(new TemplateLoader($this->alternateBasePath)); } public function tearDown() { - SS_TemplateLoader::instance()->popManifest(); + TemplateLoader::set_instance($this->_oldLoader); // Pop if added during testing if(SS_ClassLoader::instance()->getManifest() === $this->manifest) { SS_ClassLoader::instance()->popManifest(); diff --git a/tests/templates/SSViewerTestComments/SSViewerTestCommentsInclude.ss b/tests/templates/Includes/SSViewerTestCommentsInclude.ss similarity index 100% rename from tests/templates/SSViewerTestComments/SSViewerTestCommentsInclude.ss rename to tests/templates/Includes/SSViewerTestCommentsInclude.ss diff --git a/tests/templates/SSViewerTestIncludeObjectArguments.ss b/tests/templates/Includes/SSViewerTestIncludeObjectArguments.ss similarity index 100% rename from tests/templates/SSViewerTestIncludeObjectArguments.ss rename to tests/templates/Includes/SSViewerTestIncludeObjectArguments.ss diff --git a/tests/templates/SSViewerTestIncludeScopeInheritanceInclude.ss b/tests/templates/Includes/SSViewerTestIncludeScopeInheritanceInclude.ss similarity index 100% rename from tests/templates/SSViewerTestIncludeScopeInheritanceInclude.ss rename to tests/templates/Includes/SSViewerTestIncludeScopeInheritanceInclude.ss diff --git a/tests/templates/SSViewerTestIncludeScopeInheritanceWithArgsInLoop.ss b/tests/templates/Includes/SSViewerTestIncludeScopeInheritanceWithArgsInLoop.ss similarity index 100% rename from tests/templates/SSViewerTestIncludeScopeInheritanceWithArgsInLoop.ss rename to tests/templates/Includes/SSViewerTestIncludeScopeInheritanceWithArgsInLoop.ss diff --git a/tests/templates/SSViewerTestIncludeScopeInheritanceWithArgsInNestedWith.ss b/tests/templates/Includes/SSViewerTestIncludeScopeInheritanceWithArgsInNestedWith.ss similarity index 100% rename from tests/templates/SSViewerTestIncludeScopeInheritanceWithArgsInNestedWith.ss rename to tests/templates/Includes/SSViewerTestIncludeScopeInheritanceWithArgsInNestedWith.ss diff --git a/tests/templates/SSViewerTestIncludeScopeInheritanceWithArgsInWith.ss b/tests/templates/Includes/SSViewerTestIncludeScopeInheritanceWithArgsInWith.ss similarity index 100% rename from tests/templates/SSViewerTestIncludeScopeInheritanceWithArgsInWith.ss rename to tests/templates/Includes/SSViewerTestIncludeScopeInheritanceWithArgsInWith.ss diff --git a/tests/templates/SSViewerTestIncludeScopeInheritanceWithUpAndTop.ss b/tests/templates/Includes/SSViewerTestIncludeScopeInheritanceWithUpAndTop.ss similarity index 100% rename from tests/templates/SSViewerTestIncludeScopeInheritanceWithUpAndTop.ss rename to tests/templates/Includes/SSViewerTestIncludeScopeInheritanceWithUpAndTop.ss diff --git a/tests/templates/SSViewerTestIncludeWIthArguments.ss b/tests/templates/Includes/SSViewerTestIncludeWithArguments.ss similarity index 100% rename from tests/templates/SSViewerTestIncludeWIthArguments.ss rename to tests/templates/Includes/SSViewerTestIncludeWithArguments.ss diff --git a/tests/templates/SSViewerTestProcessHead.ss b/tests/templates/Includes/SSViewerTestProcessHead.ss similarity index 100% rename from tests/templates/SSViewerTestProcessHead.ss rename to tests/templates/Includes/SSViewerTestProcessHead.ss diff --git a/tests/templates/SSViewerTestRecursiveInclude.ss b/tests/templates/Includes/SSViewerTestRecursiveInclude.ss similarity index 100% rename from tests/templates/SSViewerTestRecursiveInclude.ss rename to tests/templates/Includes/SSViewerTestRecursiveInclude.ss diff --git a/tests/templates/Namespace/NamespaceInclude.ss b/tests/templates/Namespace/Includes/NamespaceInclude.ss similarity index 100% rename from tests/templates/Namespace/NamespaceInclude.ss rename to tests/templates/Namespace/Includes/NamespaceInclude.ss diff --git a/tests/templates/SSViewerTestProcess.ss b/tests/templates/SSViewerTestProcess.ss index 8ab505478..60561e017 100644 --- a/tests/templates/SSViewerTestProcess.ss +++ b/tests/templates/SSViewerTestProcess.ss @@ -2,6 +2,5 @@ <% include SSViewerTestProcessHead %> - <% include SSViewerTestCommentsWithInclude %> diff --git a/tests/view/SSViewerTest.php b/tests/view/SSViewerTest.php index 87f6ae127..c6602e861 100644 --- a/tests/view/SSViewerTest.php +++ b/tests/view/SSViewerTest.php @@ -759,17 +759,11 @@ after') $this->render('tests:( <% include Namespace/NamespaceInclude %> )', $data), 'Forward slashes work for namespace references in includes' ); - - $this->assertEquals( - "tests:( NamespaceInclude\n )", - $this->render('tests:( <% include NamespaceInclude %> )', $data), - 'Namespace can be missed for a namespaed include' - ); } public function testRecursiveInclude() { - $view = new SSViewer(array('SSViewerTestRecursiveInclude')); + $view = new SSViewer(array('Includes/SSViewerTestRecursiveInclude')); $data = new ArrayData(array( 'Title' => 'A', @@ -1160,21 +1154,21 @@ after') $this->useTestTheme(dirname(__FILE__), 'layouttest', function() use ($self) { // Test passing a string $templates = SSViewer::get_templates_by_class( - 'TestNamespace\SSViewerTest_Controller', + 'TestNamespace\SSViewerTestModel_Controller', '', 'Controller' ); $self->assertEquals([ - 'TestNamespace\SSViewerTest_Controller', + 'TestNamespace\SSViewerTestModel_Controller', 'Controller', ], $templates); // Test to ensure we're stopping at the base class. - $templates = SSViewer::get_templates_by_class('TestNamespace\SSViewerTest_Controller', '', 'TestNamespace\SSViewerTest_Controller'); + $templates = SSViewer::get_templates_by_class('TestNamespace\SSViewerTestModel_Controller', '', 'TestNamespace\SSViewerTestModel_Controller'); $self->assertCount(1, $templates); // Make sure we can filter our templates by suffix. - $templates = SSViewer::get_templates_by_class('SSViewerTest', '_Controller'); + $templates = SSViewer::get_templates_by_class('TestNamespace\SSViewerTestModel', '_Controller'); $self->assertCount(1, $templates); // Let's throw something random in there. @@ -1183,38 +1177,6 @@ after') }); } - /** - * @covers SSViewer::get_themes() - */ - public function testThemeRetrieval() { - $ds = DIRECTORY_SEPARATOR; - $testThemeBaseDir = TEMP_FOLDER . $ds . 'test-themes'; - - if(file_exists($testThemeBaseDir)) Filesystem::removeFolder($testThemeBaseDir); - - mkdir($testThemeBaseDir); - mkdir($testThemeBaseDir . $ds . 'blackcandy'); - mkdir($testThemeBaseDir . $ds . 'blackcandy_blog'); - mkdir($testThemeBaseDir . $ds . 'darkshades'); - mkdir($testThemeBaseDir . $ds . 'darkshades_blog'); - - $this->assertEquals(array( - 'blackcandy' => 'blackcandy', - 'darkshades' => 'darkshades' - ), SSViewer::get_themes($testThemeBaseDir), 'Our test theme directory contains 2 themes'); - - $this->assertEquals(array( - 'blackcandy' => 'blackcandy', - 'blackcandy_blog' => 'blackcandy_blog', - 'darkshades' => 'darkshades', - 'darkshades_blog' => 'darkshades_blog' - ), SSViewer::get_themes($testThemeBaseDir, true), - 'Our test theme directory contains 2 themes and 2 sub-themes'); - - // Remove all the test themes we created - Filesystem::removeFolder($testThemeBaseDir); - } - public function testRewriteHashlinks() { $orig = Config::inst()->get('SSViewer', 'rewrite_hash_links'); Config::inst()->update('SSViewer', 'rewrite_hash_links', true); @@ -1327,6 +1289,7 @@ EOC; $origEnv = Config::inst()->get('Director', 'environment_type'); Config::inst()->update('Director', 'environment_type', 'dev'); Config::inst()->update('SSViewer', 'source_file_comments', true); + $i = FRAMEWORK_PATH . '/tests/templates/Includes'; $f = FRAMEWORK_PATH . '/tests/templates/SSViewerTestComments'; $templates = array( array( @@ -1398,16 +1361,16 @@ EOC; "" . "
" . "" - . "" + . "" . "Included" - . "" + . "" . "" . "
" . "", ), ); foreach ($templates as $template) { - $this->_renderWithSourceFileComments($template['name'], $template['expected']); + $this->_renderWithSourceFileComments('SSViewerTestComments/'.$template['name'], $template['expected']); } Config::inst()->update('SSViewer', 'source_file_comments', false); Config::inst()->update('Director', 'environment_type', $origEnv); diff --git a/tests/view/TestNamespace/SSViewerTest_Controller.php b/tests/view/TestNamespace/SSViewerTest_Controller.php index 6814768b6..882c2d56a 100644 --- a/tests/view/TestNamespace/SSViewerTest_Controller.php +++ b/tests/view/TestNamespace/SSViewerTest_Controller.php @@ -2,7 +2,12 @@ namespace TestNamespace; -class SSViewerTest_Controller extends \Controller -{ +use SilverStripe\ORM\DataObject; + +class SSViewerTestModel extends DataObject { + +} + +class SSViewerTestModel_Controller extends \Controller { } diff --git a/tests/view/themes/layouttest/Controller.ss b/tests/view/themes/layouttest/templates/Controller.ss similarity index 100% rename from tests/view/themes/layouttest/Controller.ss rename to tests/view/themes/layouttest/templates/Controller.ss diff --git a/tests/view/themes/layouttest/Layout/Page.ss b/tests/view/themes/layouttest/templates/Layout/Page.ss similarity index 100% rename from tests/view/themes/layouttest/Layout/Page.ss rename to tests/view/themes/layouttest/templates/Layout/Page.ss diff --git a/tests/view/themes/layouttest/Layout/Shortcodes.ss b/tests/view/themes/layouttest/templates/Layout/Shortcodes.ss similarity index 100% rename from tests/view/themes/layouttest/Layout/Shortcodes.ss rename to tests/view/themes/layouttest/templates/Layout/Shortcodes.ss diff --git a/tests/view/themes/layouttest/Page.ss b/tests/view/themes/layouttest/templates/Page.ss similarity index 100% rename from tests/view/themes/layouttest/Page.ss rename to tests/view/themes/layouttest/templates/Page.ss diff --git a/tests/view/themes/layouttest/TestNamespace/SSViewerTest_Controller.ss b/tests/view/themes/layouttest/templates/TestNamespace/SSViewerTestModel_Controller.ss similarity index 100% rename from tests/view/themes/layouttest/TestNamespace/SSViewerTest_Controller.ss rename to tests/view/themes/layouttest/templates/TestNamespace/SSViewerTestModel_Controller.ss diff --git a/view/Requirements.php b/view/Requirements.php index a3bf19e96..f4c778e27 100644 --- a/view/Requirements.php +++ b/view/Requirements.php @@ -1,6 +1,7 @@ javascript[$file]) && isset($this->javascript[$file]['async']) @@ -842,7 +843,7 @@ class Requirements_Backend if(isset($options['provides'])) { $this->providedJavascript[$file] = array_values($options['provides']); } - + } /** @@ -1798,21 +1799,27 @@ class Requirements_Backend * (e.g. 'screen,projector') */ public function themedCSS($name, $module = null, $media = null) { - $theme = SSViewer::get_theme_folder(); - $project = project(); - $absbase = BASE_PATH . DIRECTORY_SEPARATOR; - $abstheme = $absbase . $theme; - $absproject = $absbase . $project; $css = "/css/$name.css"; + $project = project(); + $absbase = BASE_PATH . DIRECTORY_SEPARATOR; + $absproject = $absbase . $project; + if(file_exists($absproject . $css)) { - $this->css($project . $css, $media); - } elseif($module && file_exists($abstheme . '_' . $module.$css)) { - $this->css($theme . '_' . $module . $css, $media); - } elseif(file_exists($abstheme . $css)) { - $this->css($theme . $css, $media); - } elseif($module) { - $this->css($module . $css, $media); + return $this->css($project . $css, $media); + } + + foreach(SSViewer::get_themes() as $theme) { + $path = TemplateLoader::instance()->getPath($theme); + $abspath = BASE_PATH . '/' . $path; + + if(file_exists($abspath . $css)) { + return $this->css($path . $css, $media); + } + } + + if($module) { + return $this->css($module . $css, $media); } } diff --git a/view/SSTemplateParser.php b/view/SSTemplateParser.php index cc48950c1..ae681bfd8 100644 --- a/view/SSTemplateParser.php +++ b/view/SSTemplateParser.php @@ -3382,7 +3382,7 @@ class SSTemplateParser extends Parser implements TemplateParser { $template = $res['template']; $arguments = $res['arguments']; - $res['php'] = '$val .= SSViewer::execute_template('.$template.', $scope->getItem(), array(' . + $res['php'] = '$val .= SSViewer::execute_template(["type" => "Includes", '.$template.'], $scope->getItem(), array(' . implode(',', $arguments)."), \$scope);\n"; if($this->includeDebuggingComments) { // Add include filename comments on dev sites diff --git a/view/SSTemplateParser.php.inc b/view/SSTemplateParser.php.inc index bc0b57d68..cad6e3fd3 100644 --- a/view/SSTemplateParser.php.inc +++ b/view/SSTemplateParser.php.inc @@ -830,7 +830,7 @@ class SSTemplateParser extends Parser implements TemplateParser { $template = $res['template']; $arguments = $res['arguments']; - $res['php'] = '$val .= SSViewer::execute_template('.$template.', $scope->getItem(), array(' . + $res['php'] = '$val .= SSViewer::execute_template(["type" => "Includes", '.$template.'], $scope->getItem(), array(' . implode(',', $arguments)."), \$scope);\n"; if($this->includeDebuggingComments) { // Add include filename comments on dev sites diff --git a/view/SSViewer.php b/view/SSViewer.php index 3ce381ebe..7f771f2fa 100644 --- a/view/SSViewer.php +++ b/view/SSViewer.php @@ -3,6 +3,7 @@ use SilverStripe\ORM\FieldType\DBField; use SilverStripe\ORM\FieldType\DBHTMLText; use SilverStripe\Security\Permission; +use SilverStripe\View\TemplateLoader; /** * This tracks the current scope for an SSViewer instance. It has three goals: @@ -723,10 +724,19 @@ class SSViewer implements Flushable { } /** - * @var array $chosenTemplates Associative array for the different - * template containers: "main" and "Layout". Values are absolute file paths to *.ss files. + * @var array $templates List of templates to select from */ - private $chosenTemplates = array(); + private $templates = null; + + /** + * @var string $chosen Absolute path to chosen template file + */ + private $chosen = null; + + /** + * @var array Templates to use when looking up 'Layout' or 'Content' + */ + private $subTemplates = null; /** * @var boolean @@ -735,9 +745,17 @@ class SSViewer implements Flushable { /** * @config - * @var string The used "theme", which usually consists of templates, images and stylesheets. + * @var string A list (highest priority first) of themes to use * Only used when {@link $theme_enabled} is set to TRUE. */ + private static $themes = []; + + /** + * @deprecated 4.0..5.0 + * @config + * @var string The used "theme", which usually consists of templates, images and stylesheets. + * Only used when {@link $theme_enabled} is set to TRUE, and $themes is empty + */ private static $theme = null; /** @@ -791,65 +809,33 @@ class SSViewer implements Flushable { return $viewer; } + public static function set_themes($themes = []) { + Config::inst()->remove('SSViewer', 'themes'); + Config::inst()->update('SSViewer', 'themes', $themes); + } + + public static function add_themes($themes = []) { + Config::inst()->update('SSViewer', 'themes', $themes); + } + + public static function get_themes() { + $res = ['$default']; + + if (Config::inst()->get('SSViewer', 'theme_enabled')) { + if ($list = Config::inst()->get('SSViewer', 'themes')) $res = $list; + elseif ($theme = Config::inst()->get('SSViewer', 'theme')) $res = [$theme, '$default']; + } + + return $res; + } + /** * @deprecated 4.0 Use the "SSViewer.theme" config setting instead * @param string $theme The "base theme" name (without underscores). */ public static function set_theme($theme) { - Deprecation::notice('4.0', 'Use the "SSViewer.theme" config setting instead'); - Config::inst()->update('SSViewer', 'theme', $theme); - } - - /** - * @deprecated 4.0 Use the "SSViewer.theme" config setting instead - * @return string - */ - public static function current_theme() { - Deprecation::notice('4.0', 'Use the "SSViewer.theme" config setting instead'); - return Config::inst()->get('SSViewer', 'theme'); - } - - /** - * Returns the path to the theme folder - * - * @return string - */ - public static function get_theme_folder() { - $theme = Config::inst()->get('SSViewer', 'theme'); - return $theme ? THEMES_DIR . "/" . $theme : project(); - } - - /** - * Returns an array of theme names present in a directory. - * - * @param string $path - * @param bool $subthemes Include subthemes (default false). - * @return array - */ - public static function get_themes($path = null, $subthemes = false) { - $path = rtrim($path ? $path : THEMES_PATH, '/'); - $themes = array(); - - if (!is_dir($path)) return $themes; - - foreach (scandir($path) as $item) { - if ($item[0] != '.' && is_dir("$path/$item")) { - if ($subthemes || strpos($item, '_') === false) { - $themes[$item] = $item; - } - } - } - - return $themes; - } - - /** - * @deprecated since version 4.0 - * @return string - */ - public static function current_custom_theme(){ - Deprecation::notice('4.0', 'Use the "SSViewer.theme" and "SSViewer.theme_enabled" config settings instead'); - return Config::inst()->get('SSViewer', 'theme_enabled') ? Config::inst()->get('SSViewer', 'theme') : null; + Deprecation::notice('4.0', 'Use the "SSViewer#set_themes" instead'); + self::set_themes([$theme]); } /** @@ -873,6 +859,7 @@ class SSViewer implements Flushable { foreach($classes as $class) { $template = $class . $suffix; if(SSViewer::hasTemplate($template)) $templates[] = $template; + elseif(SSViewer::hasTemplate('Includes/'.$template)) $templates[] = 'Includes/'.$template; // If the class is "Page_Controller", look for Page.ss if(stripos($class,'_controller') !== false) { @@ -893,38 +880,34 @@ class SSViewer implements Flushable { * array('MySpecificPage', 'MyPage', 'Page') * */ - public function __construct($templateList, TemplateParser $parser = null) { + public function __construct($templates, TemplateParser $parser = null) { if ($parser) { $this->setParser($parser); } - if(!is_array($templateList) && substr((string) $templateList,-3) == '.ss') { - $this->chosenTemplates['main'] = $templateList; - } else { - if(Config::inst()->get('SSViewer', 'theme_enabled')) { - $theme = Config::inst()->get('SSViewer', 'theme'); - } else { - $theme = null; - } - $this->chosenTemplates = SS_TemplateLoader::instance()->findTemplates( - $templateList, $theme - ); - } + $this->setTemplate($templates); - if(!$this->chosenTemplates) { - $templateList = (is_array($templateList)) ? $templateList : array($templateList); + if(!$this->chosen) { + $message = 'None of the following templates could be found: '; + $message .= print_r($templates, true); - $message = 'None of the following templates could be found'; - if(!$theme) { + $themes = self::get_themes(); + if(!$themes) { $message .= ' (no theme in use)'; } else { - $message .= ' in theme "' . $theme . '"'; + $message .= ' in themes "' . print_r($themes, true) . '"'; } - user_error($message . ': ' . implode(".ss, ", $templateList) . ".ss", E_USER_WARNING); + user_error($message, E_USER_WARNING); } } + public function setTemplate($templates) { + $this->templates = $templates; + $this->chosen = TemplateLoader::instance()->findTemplate($templates, self::get_themes()); + $this->subTemplates = []; + } + /** * Set the template parser that will be used in template generation * @param \TemplateParser $parser @@ -954,19 +937,7 @@ class SSViewer implements Flushable { * @return boolean */ public static function hasTemplate($templates) { - $manifest = SS_TemplateLoader::instance()->getManifest(); - - if(Config::inst()->get('SSViewer', 'theme_enabled')) { - $theme = Config::inst()->get('SSViewer', 'theme'); - } else { - $theme = null; - } - - foreach ((array) $templates as $template) { - if ($manifest->getCandidateTemplate($template, $theme)) return true; - } - - return false; + return (bool)TemplateLoader::instance()->findTemplate($templates, self::get_themes()); } /** @@ -1033,7 +1004,7 @@ class SSViewer implements Flushable { } public function exists() { - return $this->chosenTemplates; + return $this->chosen; } /** @@ -1043,21 +1014,7 @@ class SSViewer implements Flushable { * @return string Full system path to a template file */ public static function getTemplateFileByType($identifier, $type) { - $loader = SS_TemplateLoader::instance(); - if(Config::inst()->get('SSViewer', 'theme_enabled')) { - $theme = Config::inst()->get('SSViewer', 'theme'); - } else { - $theme = null; - } - $found = $loader->findTemplates("$type/$identifier", $theme); - - if (isset($found['main'])) { - return $found['main']; - } - else if (!empty($found)) { - $founds = array_values($found); - return $founds[0]; - } + return TemplateLoader::instance()->findTemplate(['type' => $type, $identifier], self::get_themes()); } /** @@ -1192,13 +1149,7 @@ class SSViewer implements Flushable { public function process($item, $arguments = null, $inheritedScope = null) { SSViewer::$topLevel[] = $item; - if(isset($this->chosenTemplates['main'])) { - $template = $this->chosenTemplates['main']; - } else { - $keys = array_keys($this->chosenTemplates); - $key = reset($keys); - $template = $this->chosenTemplates[$key]; - } + $template = $this->chosen; $cacheFile = TEMP_FOLDER . "/.cache" . str_replace(array('\\','/',':'), '.', Director::makeRelative(realpath($template))); @@ -1218,14 +1169,27 @@ class SSViewer implements Flushable { // Makes the rendered sub-templates available on the parent item, // through $Content and $Layout placeholders. foreach(array('Content', 'Layout') as $subtemplate) { - if(isset($this->chosenTemplates[$subtemplate])) { + $sub = null; + if(isset($this->subTemplates[$subtemplate])) { + $sub = $this->subTemplates[$subtemplate]; + } + elseif(!is_array($this->templates)) { + $sub = ['type' => $subtemplate, $this->templates]; + } + elseif(!array_key_exists('type', $this->templates) || !$this->templates['type']) { + $sub = array_merge($this->templates, ['type' => $subtemplate]); + } + + if ($sub) { $subtemplateViewer = clone $this; // Disable requirements - this will be handled by the parent template $subtemplateViewer->includeRequirements(false); - // The subtemplate is the only file we want to process, so set it as the "main" template file - $subtemplateViewer->chosenTemplates = array('main' => $this->chosenTemplates[$subtemplate]); + // Select the right template + $subtemplateViewer->setTemplate($sub); - $underlay[$subtemplate] = $subtemplateViewer->process($item, $arguments); + if ($subtemplateViewer->exists()) { + $underlay[$subtemplate] = $subtemplateViewer->process($item, $arguments); + } } } @@ -1301,7 +1265,7 @@ class SSViewer implements Flushable { * 'Content' & 'Layout', and will have to contain 'main' */ public function templates() { - return $this->chosenTemplates; + return array_merge(['main' => $this->chosen], $this->subTemplates); } /** @@ -1309,7 +1273,8 @@ class SSViewer implements Flushable { * @param string $file Full system path to the template file */ public function setTemplateFile($type, $file) { - $this->chosenTemplates[$type] = $file; + if (!$type || $type == 'main') $this->chosen = $file; + else $this->subTemplates[$type] = $file; } /** diff --git a/view/TemplateLoader.php b/view/TemplateLoader.php new file mode 100644 index 000000000..8a7a58695 --- /dev/null +++ b/view/TemplateLoader.php @@ -0,0 +1,118 @@ +base = $base ? $base : BASE_PATH; + } + + public function addSet($set, $manifest) { + $this->sets[$set] = $manifest; + } + + public function getPath($identifier) { + $slashPos = strpos($identifier, '/'); + + // If identifier starts with "/", it's a path from root + if ($slashPos === 0) { + return substr($identifier, 1); + } + // Otherwise if there is a "/", identifier is a vendor'ed module + elseif ($slashPos !== false) { + $parts = explode(':', $identifier, 2); + + list($vendor, $module) = explode('/', $parts[0], 2); + $theme = count($parts) > 1 ? $parts[1] : ''; + + $path = $module . ($theme ? '/themes/'.$theme : ''); + + // Right now we require $module to be a silverstripe module (in root) or theme (in themes dir) + // If both exist, we prefer theme + if (is_dir(THEMES_PATH . '/' .$path)) { + return THEMES_DIR . '/' . $path; + } + else { + return $path; + } + } + // Otherwise it's a (deprecated) old-style "theme" identifier + else { + return THEMES_DIR.'/'.$identifier; + } + } + + /** + * Attempts to find possible candidate templates from a set of template + * names from modules, current theme directory and finally the application + * folder. + * + * The template names can be passed in as plain strings, or be in the + * format "type/name", where type is the type of template to search for + * (e.g. Includes, Layout). + * + * @param string|array $templates + * @param string $theme + * + * @return array + */ + public function findTemplate($template, $themes = []) { + + if(is_array($template)) { + $type = array_key_exists('type', $template) ? $template['type'] : ''; + $templateList = array_key_exists('templates', $template) ? $template['templates'] : $template; + } + else { + $type = ''; + $templateList = array($template); + } + + if(count($templateList) == 1 && substr($templateList[0], -3) == '.ss') { + return $templateList[0]; + } + + foreach($templateList as $i => $template) { + $template = str_replace('\\', '/', $template); + $parts = explode('/', $template); + + $tail = array_pop($parts); + $head = implode('/', $parts); + + foreach($themes as $themename) { + $subthemes = isset($this->sets[$themename]) ? $this->sets[$themename]->getThemes() : [$themename]; + + foreach($subthemes as $theme) { + $themePath = $this->base . '/' . $this->getPath($theme); + + $path = $themePath . '/templates/' . implode('/', array_filter([$head, $type, $tail])) . '.ss'; + if (file_exists($path)) return $path; + } + } + } + } + +} diff --git a/view/ThemeManifest.php b/view/ThemeManifest.php new file mode 100644 index 000000000..1e17bedf0 --- /dev/null +++ b/view/ThemeManifest.php @@ -0,0 +1,140 @@ +base = $base; + $this->tests = $includeTests; + + $this->project = $project; + + $cacheClass = defined('SS_MANIFESTCACHE') ? SS_MANIFESTCACHE : 'ManifestCache_File'; + + $this->cache = new $cacheClass('thememanifest'.($includeTests ? '_tests' : '')); + $this->cacheKey = $this->getCacheKey(); + + if ($forceRegen) { + $this->regenerate(); + } + } + + /** + * @return string + */ + public function getBase() { + return $this->base; + } + + /** + * Generate a unique cache key to avoid manifest cache collisions. + * We compartmentalise based on the base path, the given project, and whether + * or not we intend to include tests. + * @return string + */ + public function getCacheKey() { + return sha1(sprintf( + "manifest-%s-%s-%u", + $this->base, + $this->project, + $this->tests + )); + } + + /** + * Returns a map of all themes information. The map is in the following format: + * + * + * [ + * 'mysite', + * 'framework', + * 'framework/admin' + * ] + * + * + * @return array + */ + public function getThemes() { + if ($this->themes === null) $this->init(); + return $this->themes; + } + + /** + * Regenerates the manifest by scanning the base path. + * + * @param bool $cache + */ + public function regenerate($cache = true) { + $finder = new ManifestFileFinder(); + $finder->setOptions(array( + 'include_themes' => false, + 'ignore_tests' => !$this->tests, + 'dir_callback' => array($this, 'handleDirectory') + )); + + $this->themes = []; + $finder->find($this->base); + + if ($cache) { + $this->cache->save($this->themes, $this->cacheKey); + } + } + + public function handleDirectory($basename, $pathname, $depth) + { + if ($basename == self::TEMPLATES_DIR) { + // We only want part of the full path, so split into directories + $parts = explode('/', $pathname); + // Take the end (the part relative to base), except the very last directory + $themeParts = array_slice($parts, -$depth, $depth-1); + // Then join again + $path = '/'.implode('/', $themeParts); + + // If this is in the project, add to beginning of list. Else add to end. + if ($themeParts[0] == $this->project) { + array_unshift($this->themes, $path); + } + else { + array_push($this->themes, $path); + } + } + } + + protected function init() { + if ($data = $this->cache->load($this->cacheKey)) { + $this->themes = $data; + } else { + $this->regenerate(); + } + } +}