mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-10-22 14:05:37 +02:00
Merge pull request #5804 from open-sausages/feature/themestack
API Theme stacking
This commit is contained in:
commit
5c98d331a3
@ -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();
|
||||
|
@ -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'));
|
||||
|
@ -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(
|
||||
|
@ -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
|
||||
));
|
||||
|
||||
|
@ -1,87 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* Handles finding templates from a stack of template manifest objects.
|
||||
*
|
||||
* @package framework
|
||||
* @subpackage manifest
|
||||
*/
|
||||
class SS_TemplateLoader {
|
||||
|
||||
/**
|
||||
* @var SS_TemplateLoader
|
||||
*/
|
||||
private static $instance;
|
||||
|
||||
/**
|
||||
* @var SS_TemplateManifest[]
|
||||
*/
|
||||
protected $manifests = array();
|
||||
|
||||
/**
|
||||
* @return SS_TemplateLoader
|
||||
*/
|
||||
public static function instance() {
|
||||
return self::$instance ? self::$instance : self::$instance = new self();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the currently active template manifest instance.
|
||||
*
|
||||
* @return SS_TemplateManifest
|
||||
*/
|
||||
public function getManifest() {
|
||||
return $this->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;
|
||||
}
|
||||
|
||||
}
|
@ -1,271 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* A class which builds a manifest of all templates present in a directory,
|
||||
* in both modules and themes.
|
||||
*
|
||||
* @package framework
|
||||
* @subpackage manifest
|
||||
*/
|
||||
class SS_TemplateManifest {
|
||||
|
||||
const TEMPLATES_DIR = 'templates';
|
||||
|
||||
protected $base;
|
||||
protected $tests;
|
||||
protected $cache;
|
||||
protected $cacheKey;
|
||||
protected $project;
|
||||
protected $inited;
|
||||
protected $templates = array();
|
||||
|
||||
/**
|
||||
* Constructs a new template manifest. The manifest is not actually built
|
||||
* or loaded from cache until needed.
|
||||
*
|
||||
* @param string $base The base path.
|
||||
* @param string $project Path to application code
|
||||
*
|
||||
* @param bool $includeTests Include tests in the manifest.
|
||||
* @param bool $forceRegen Force the manifest to be regenerated.
|
||||
*/
|
||||
public function __construct($base, $project, $includeTests = false, $forceRegen = false) {
|
||||
$this->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:
|
||||
*
|
||||
* <code>
|
||||
* 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'
|
||||
* )
|
||||
* )
|
||||
* )
|
||||
* )
|
||||
* </code>
|
||||
*
|
||||
* @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();
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
@ -397,6 +397,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.
|
||||
|
@ -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);
|
||||
|
@ -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"
|
||||
)
|
||||
);
|
||||
|
||||
|
@ -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"
|
||||
)
|
||||
);
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -1052,7 +1052,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;
|
||||
@ -1060,7 +1060,7 @@ class FormField extends RequestHandler {
|
||||
}
|
||||
|
||||
if($customTemplate) {
|
||||
array_unshift($matches, $customTemplate);
|
||||
array_unshift($matches, 'forms/'.$customTemplate);
|
||||
}
|
||||
|
||||
return $matches;
|
||||
|
@ -44,6 +44,7 @@ class MemberDatetimeOptionsetField extends OptionsetField {
|
||||
'Options' => new ArrayList($options)
|
||||
));
|
||||
|
||||
|
||||
return $this->customise($properties)->renderWith(
|
||||
$this->getTemplates()
|
||||
);
|
||||
|
@ -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
|
||||
|
@ -131,7 +131,7 @@ class GridFieldAddExistingAutocompleter
|
||||
}
|
||||
|
||||
return array(
|
||||
$this->targetFragment => $forTemplate->renderWith($this->itemClass)
|
||||
$this->targetFragment => $forTemplate->renderWith('Includes/'.$this->itemClass)
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -44,7 +44,7 @@ class GridFieldAddNewButton implements GridField_HTMLProvider {
|
||||
));
|
||||
|
||||
return array(
|
||||
$this->targetFragment => $data->renderWith('GridFieldAddNewbutton'),
|
||||
$this->targetFragment => $data->renderWith('Includes/GridFieldAddNewButton'),
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -27,7 +27,7 @@ class GridFieldButtonRow implements GridField_HTMLProvider {
|
||||
));
|
||||
|
||||
return array(
|
||||
$this->targetFragment => $data->renderWith('GridFieldButtonRow')
|
||||
$this->targetFragment => $data->renderWith('Includes/GridFieldButtonRow')
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -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()) {
|
||||
|
@ -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');
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -162,7 +162,7 @@ class GridFieldFilterHeader implements GridField_HTMLProvider, GridField_DataMan
|
||||
}
|
||||
|
||||
return array(
|
||||
'header' => $forTemplate->renderWith('GridFieldFilterHeader_Row'),
|
||||
'header' => $forTemplate->renderWith('Includes/GridFieldFilterHeader_Row'),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -48,7 +48,7 @@ class GridFieldFooter implements GridField_HTMLProvider {
|
||||
|
||||
return array(
|
||||
'footer' => $forTemplate->renderWith(
|
||||
'GridFieldFooter',
|
||||
'Includes/GridFieldFooter',
|
||||
array(
|
||||
'Colspan' => count($gridField->getColumns())
|
||||
)
|
||||
|
@ -66,7 +66,7 @@ class GridFieldLevelup extends Object implements GridField_HTMLProvider {
|
||||
));
|
||||
|
||||
return array(
|
||||
'before' => $forTemplate->renderWith('GridFieldLevelup'),
|
||||
'before' => $forTemplate->renderWith('Includes/GridFieldLevelup'),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -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())))
|
||||
);
|
||||
}
|
||||
|
@ -164,7 +164,7 @@ class GridFieldSortableHeader implements GridField_HTMLProvider, GridField_DataM
|
||||
}
|
||||
|
||||
return array(
|
||||
'header' => $forTemplate->renderWith('GridFieldSortableHeader_Row'),
|
||||
'header' => $forTemplate->renderWith('Includes/GridFieldSortableHeader_Row'),
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -13,7 +13,7 @@ class GridFieldToolbarHeader implements GridField_HTMLProvider {
|
||||
|
||||
public function getHTMLFragments( $gridField) {
|
||||
return array(
|
||||
'header' => $gridField->renderWith('GridFieldToolbarHeader')
|
||||
'header' => $gridField->renderWith('Includes/GridFieldToolbarHeader')
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -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');
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -181,7 +181,7 @@ class HTMLEditorField_Toolbar extends RequestHandler {
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $templateViewFile = 'HTMLEditorField_viewfile';
|
||||
protected $templateViewFile = 'Includes/HTMLEditorField_viewfile';
|
||||
|
||||
protected $controller, $name;
|
||||
|
||||
|
@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
use SilverStripe\View\TemplateLoader;
|
||||
|
||||
/**
|
||||
* Default configuration for HtmlEditor specific to tinymce
|
||||
*/
|
||||
@ -419,13 +421,18 @@ class TinyMCEConfig extends HTMLEditorConfig {
|
||||
Director::absoluteBaseURL(),
|
||||
FRAMEWORK_ADMIN_DIR . '/client/dist/styles/editor.css'
|
||||
);
|
||||
if($theme = SSViewer::get_theme_folder()) {
|
||||
$editorDir = $theme . '/css/editor.css';
|
||||
|
||||
foreach(SSViewer::get_themes() as $theme) {
|
||||
$path = TemplateLoader::instance()->getPath($theme);
|
||||
$editorDir = $path . '/css/editor.css';;
|
||||
|
||||
if(file_exists(BASE_PATH . '/' . $editorDir)) {
|
||||
$editor[] = Controller::join_links(
|
||||
Director::absoluteBaseURL(),
|
||||
$editorDir
|
||||
);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
return $editor;
|
||||
|
@ -1,6 +1,10 @@
|
||||
<?php
|
||||
|
||||
use SilverStripe\View\TemplateLoader;
|
||||
use SilverStripe\View\ThemeManifest;
|
||||
|
||||
/**
|
||||
* Tests for the {@link SS_TemplateLoader} class.
|
||||
* Tests for the {@link TemplateLoader} class.
|
||||
*
|
||||
* @package framework
|
||||
* @subpackage tests
|
||||
@ -16,162 +20,82 @@ class TemplateLoaderTest extends SapphireTest {
|
||||
*/
|
||||
public function setUp() {
|
||||
parent::setUp();
|
||||
// Fake project root
|
||||
$this->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) {
|
||||
|
@ -1,141 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* Tests for the template manifest.
|
||||
*
|
||||
* @package framework
|
||||
* @subpackage tests
|
||||
*/
|
||||
class TemplateManifestTest extends SapphireTest {
|
||||
|
||||
protected $base;
|
||||
protected $manifest;
|
||||
protected $manifestTests;
|
||||
|
||||
public function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
$this->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'));
|
||||
}
|
||||
|
||||
}
|
@ -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());
|
||||
}
|
||||
|
@ -1,9 +1,11 @@
|
||||
<?php
|
||||
|
||||
use SilverStripe\View\TemplateLoader;
|
||||
|
||||
/**
|
||||
* @package framework
|
||||
* @subpackage i18n
|
||||
*/
|
||||
|
||||
class i18nSSLegacyAdapterTest extends SapphireTest {
|
||||
|
||||
public function setUp() {
|
||||
@ -14,10 +16,10 @@ class i18nSSLegacyAdapterTest extends SapphireTest {
|
||||
Filesystem::makeFolder($this->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);
|
||||
|
@ -1,6 +1,9 @@
|
||||
<?php
|
||||
|
||||
use SilverStripe\ORM\DataObject;
|
||||
use SilverStripe\View\TemplateLoader;
|
||||
use SilverStripe\View\ThemeManifest;
|
||||
|
||||
require_once 'Zend/Translate.php';
|
||||
|
||||
/**
|
||||
@ -35,10 +38,13 @@ class i18nTest extends SapphireTest {
|
||||
FileSystem::makeFolder($this->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);
|
||||
|
@ -1,4 +1,7 @@
|
||||
<?php
|
||||
|
||||
use SilverStripe\View\TemplateLoader;
|
||||
|
||||
/**
|
||||
* @package framework
|
||||
* @subpackage tests
|
||||
@ -34,13 +37,13 @@ class i18nTextCollectorTest extends SapphireTest {
|
||||
$this->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();
|
||||
|
@ -2,6 +2,5 @@
|
||||
<% include SSViewerTestProcessHead %>
|
||||
|
||||
<body>
|
||||
<% include SSViewerTestCommentsWithInclude %>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -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;
|
||||
"<!-- template $f/SSViewerTestCommentsWithInclude.ss -->"
|
||||
. "<div class='typography'>"
|
||||
. "<!-- include 'SSViewerTestCommentsInclude' -->"
|
||||
. "<!-- template $f/SSViewerTestCommentsInclude.ss -->"
|
||||
. "<!-- template $i/SSViewerTestCommentsInclude.ss -->"
|
||||
. "Included"
|
||||
. "<!-- end template $f/SSViewerTestCommentsInclude.ss -->"
|
||||
. "<!-- end template $i/SSViewerTestCommentsInclude.ss -->"
|
||||
. "<!-- end include 'SSViewerTestCommentsInclude' -->"
|
||||
. "</div>"
|
||||
. "<!-- end template $f/SSViewerTestCommentsWithInclude.ss -->",
|
||||
),
|
||||
);
|
||||
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);
|
||||
|
@ -2,7 +2,12 @@
|
||||
|
||||
namespace TestNamespace;
|
||||
|
||||
class SSViewerTest_Controller extends \Controller
|
||||
{
|
||||
use SilverStripe\ORM\DataObject;
|
||||
|
||||
class SSViewerTestModel extends DataObject {
|
||||
|
||||
}
|
||||
|
||||
class SSViewerTestModel_Controller extends \Controller {
|
||||
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
<?php
|
||||
|
||||
use SilverStripe\Filesystem\Storage\GeneratedAssetHandler;
|
||||
use SilverStripe\View\TemplateLoader;
|
||||
|
||||
/**
|
||||
* Requirements tracker for JavaScript and CSS.
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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')
|
||||
* </code>
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
118
view/TemplateLoader.php
Normal file
118
view/TemplateLoader.php
Normal file
@ -0,0 +1,118 @@
|
||||
<?php
|
||||
|
||||
namespace SilverStripe\View;
|
||||
|
||||
/**
|
||||
* Handles finding templates from a stack of template manifest objects.
|
||||
*
|
||||
* @package framework
|
||||
* @subpackage view
|
||||
*/
|
||||
class TemplateLoader {
|
||||
|
||||
/**
|
||||
* @var TemplateLoader
|
||||
*/
|
||||
private static $instance;
|
||||
|
||||
protected $base;
|
||||
|
||||
protected $sets = [];
|
||||
|
||||
public static function instance() {
|
||||
return self::$instance ? self::$instance : self::$instance = new self();
|
||||
}
|
||||
|
||||
public static function set_instance(TemplateLoader $instance) {
|
||||
self::$instance = $instance;
|
||||
}
|
||||
|
||||
public function __construct($base = null) {
|
||||
$this->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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
140
view/ThemeManifest.php
Normal file
140
view/ThemeManifest.php
Normal file
@ -0,0 +1,140 @@
|
||||
<?php
|
||||
|
||||
namespace SilverStripe\View;
|
||||
|
||||
use \ManifestFileFinder;
|
||||
|
||||
/**
|
||||
* A class which builds a manifest of all themes (which is really just a directory called "templates")
|
||||
*
|
||||
* @package framework
|
||||
* @subpackage manifest
|
||||
*/
|
||||
class ThemeManifest {
|
||||
|
||||
const TEMPLATES_DIR = 'templates';
|
||||
|
||||
protected $base;
|
||||
protected $tests;
|
||||
protected $project;
|
||||
|
||||
protected $cache;
|
||||
protected $cacheKey;
|
||||
|
||||
protected $themes = null;
|
||||
|
||||
/**
|
||||
* Constructs a new template manifest. The manifest is not actually built
|
||||
* or loaded from cache until needed.
|
||||
*
|
||||
* @param string $base The base path.
|
||||
* @param string $project Path to application code
|
||||
*
|
||||
* @param bool $includeTests Include tests in the manifest.
|
||||
* @param bool $forceRegen Force the manifest to be regenerated.
|
||||
*/
|
||||
public function __construct($base, $project, $includeTests = false, $forceRegen = false) {
|
||||
$this->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:
|
||||
*
|
||||
* <code>
|
||||
* [
|
||||
* 'mysite',
|
||||
* 'framework',
|
||||
* 'framework/admin'
|
||||
* ]
|
||||
* </code>
|
||||
*
|
||||
* @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();
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user