API Update template lookup to late resolution for performance reasons

API Update behaviour of form fields to use standard template lookup mechanism
API Support custom "type" parameter to template lookup
This commit is contained in:
Damian Mooyman 2016-08-23 14:32:26 +12:00
parent f40ed07dec
commit c9b6e9bac0
No known key found for this signature in database
GPG Key ID: 78B823A10DE27D1A
99 changed files with 322 additions and 187 deletions

View File

@ -15,6 +15,7 @@ use SilverStripe\ORM\ArrayList;
use SilverStripe\ORM\Queries\SQLSelect;
use SilverStripe\ORM\ManyManyList;
use SilverStripe\MSSQL\MSSQLDatabase;
use SSViewer;
use TemplateGlobalProvider;
use Deprecation;
use i18n;
@ -1541,8 +1542,10 @@ class Member extends DataObject implements TemplateGlobalProvider {
$dateFormatMap
)
);
$formatClass = get_class($dateFormatField);
$dateFormatField->setValue($self->DateFormat);
$dateFormatField->setDescriptionTemplate('forms/MemberDatetimeOptionsetField_description_date');
$dateTemplate = SSViewer::get_templates_by_class($formatClass, '_description_date', $formatClass);
$dateFormatField->setDescriptionTemplate($dateTemplate);
$defaultTimeFormat = Zend_Locale_Format::getTimeFormat(new Zend_Locale($self->Locale));
$timeFormatMap = array(
@ -1559,7 +1562,8 @@ class Member extends DataObject implements TemplateGlobalProvider {
)
);
$timeFormatField->setValue($self->TimeFormat);
$timeFormatField->setDescriptionTemplate('forms/MemberDatetimeOptionsetField_description_time');
$timeTemplate = SSViewer::get_templates_by_class($formatClass,'_description_time', $formatClass);
$timeFormatField->setDescriptionTemplate($timeTemplate);
});
return parent::getCMSFields();

View File

@ -972,7 +972,8 @@ class LeftAndMain extends Controller implements PermissionProvider {
* @return array
*/
public function getTemplatesWithSuffix($suffix) {
return SSViewer::get_templates_by_class(get_class($this), $suffix, 'SilverStripe\\Admin\\LeftAndMain');
$templates = SSViewer::get_templates_by_class(get_class($this), $suffix, __CLASS__);
return SSViewer::chooseTemplate($templates);
}
public function Content() {

View File

@ -165,7 +165,7 @@ class SecurityAdmin extends LeftAndMain implements PermissionProvider {
// Add import capabilities. Limit to admin since the import logic can affect assigned permissions
if(Permission::check('ADMIN')) {
$fields->addFieldsToTab('Root.Users', array(
new HeaderField(_t('SecurityAdmin.IMPORTUSERS', 'Import users'), 3),
new HeaderField('ImportUsersHeader', _t('SecurityAdmin.IMPORTUSERS', 'Import users'), 3),
new LiteralField(
'MemberImportFormIframe',
sprintf(
@ -176,7 +176,7 @@ class SecurityAdmin extends LeftAndMain implements PermissionProvider {
)
));
$fields->addFieldsToTab('Root.Groups', array(
new HeaderField(_t('SecurityAdmin.IMPORTGROUPS', 'Import groups'), 3),
new HeaderField('ImportGroupsHeader', _t('SecurityAdmin.IMPORTGROUPS', 'Import groups'), 3),
new LiteralField(
'GroupImportFormIframe',
sprintf(

View File

@ -1,12 +1,10 @@
<?php
use SilverStripe\ORM\SS_List;
use SilverStripe\ORM\ArrayList;
use SilverStripe\ORM\FieldType\DBField;
use SilverStripe\ORM\FieldType\DBHTMLText;
/**
* RSSFeed class
*
@ -91,9 +89,11 @@ class RSSFeed extends ViewableData {
protected $etag;
/**
* Custom template
*
* @var string
*/
protected $template = 'RSSFeed';
protected $template = null;
/**
* Constructor
@ -219,7 +219,7 @@ class RSSFeed extends ViewableData {
Config::inst()->update('SSViewer', 'source_file_comments', $prevState);
return $this->renderWith($this->getTemplate());
return $this->renderWith($this->getTemplates());
}
/**
@ -240,6 +240,21 @@ class RSSFeed extends ViewableData {
public function getTemplate() {
return $this->template;
}
/**
* Returns the ordered list of preferred templates for rendering this object.
* Will prioritise any custom template first, and then templates based on class hiearchy next.
*
* @return array
*/
public function getTemplates() {
$templates = SSViewer::get_templates_by_class(get_class($this), '', __CLASS__);
// Prefer any custom template
if($this->getTemplate()) {
array_unshift($templates, $this->getTemplate());
}
return $templates;
}
}
/**

View File

@ -87,15 +87,17 @@ class ClassInfo {
*
* @todo Move this into {@see DataObjectSchema}
*
* @param string|object $class
* @param string|object $nameOrObject Class or object instance
* @return array
*/
public static function dataClassesFor($class) {
if(is_string($class) && !class_exists($class)) return array();
public static function dataClassesFor($nameOrObject) {
if(is_string($nameOrObject) && !class_exists($nameOrObject)) {
return array();
}
$result = array();
$class = self::class_name($class);
$class = self::class_name($nameOrObject);
$classes = array_merge(
self::ancestry($class),
@ -134,17 +136,17 @@ class ClassInfo {
* )
* </code>
*
* @param mixed $class string of the classname or instance of the class
* @param string|object $nameOrObject The classname or object
* @return array Names of all subclasses as an associative array.
*/
public static function subclassesFor($class) {
if(is_string($class) && !class_exists($class)) {
public static function subclassesFor($nameOrObject) {
if(is_string($nameOrObject) && !class_exists($nameOrObject)) {
return [];
}
//normalise class case
$className = self::class_name($class);
$descendants = SS_ClassLoader::instance()->getManifest()->getDescendantsOf($class);
$className = self::class_name($nameOrObject);
$descendants = SS_ClassLoader::instance()->getManifest()->getDescendantsOf($className);
$result = array($className => $className);
if ($descendants) {
@ -174,14 +176,16 @@ class ClassInfo {
* Returns the passed class name along with all its parent class names in an
* array, sorted with the root class first.
*
* @param string $class
* @param bool $tablesOnly Only return classes that have a table in the db.
* @param string|object $nameOrObject Class or object instance
* @param bool $tablesOnly Only return classes that have a table in the db.
* @return array
*/
public static function ancestry($class, $tablesOnly = false) {
if(is_string($class) && !class_exists($class)) return array();
public static function ancestry($nameOrObject, $tablesOnly = false) {
if(is_string($nameOrObject) && !class_exists($nameOrObject)) {
return array();
}
$class = self::class_name($class);
$class = self::class_name($nameOrObject);
$lClass = strtolower($class);

View File

@ -201,11 +201,13 @@ You can use inequalities like `<`, `<=`, `>`, `>=` to compare numbers.
## Includes
Within SilverStripe templates we have the ability to include other templates using the `<% include %>` tag. The includes
will be searched for using the same filename look-up rules as a regular template, so this will include
`templates/Includes/Sidebar.ss`
will be searched for using the same filename look-up rules as a regular template. However in the case of the include tag
an additional `Includes` directory will be inserted into the resolved path just prior to the filename.
:::ss
<% include Includes\SideBar %>
E.g.
* `<% include SideBar %>` will include `templates/Includes/Sidebar.ss`
* `<% include MyNamespace/SideBar %>` will include `templates/MyNamespace/Includes/Sidebar.ss`
Note that in SilverStripe 3, you didn't have to specify a namespace in your `include` tag, as the template engine didn't
use namespaces. As of SilverStripe 4, the template namespaces need to match the folder structure of your template files.

View File

@ -63,6 +63,7 @@
* Search filter classes (e.g. `ExactMatchFilter`) are now registered with `Injector`
via a new `DataListFilter.` prefix convention.
see [search filter documentation](/developer_guides/model/searchfilters) for more information.
* FormField templates no longer look in the 'forms' folder for templates.
## New API
@ -126,6 +127,9 @@
* `PopoverField` added to provide popup-menu behaviour in react forms (currently not available for
non-react forms).
* Admin URL can now be configured via custom Director routing rule
* Templates now use a standard template lookup system via `SSViewer::get_templates_by_class`
which builds a candidate list for a given class. Actual resolution of existing templates
for any list of candidates is actually performed by `SSViewer::chooseTemplate`
### Front-end build tooling for CMS interface
@ -415,8 +419,14 @@ require manual intervention, please see the below upgrading notes.
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.
Either include the folder in the template name (`renderWith('MyEmail.ss')` => `renderWith('emails/MyEmail.ss')`),
move the template into the correct directory, or both.
When using `<% include %>` template tag you should continue to leave out the `Includes` folder.
`<% include Sidebar %>` will match only match `Includes/Sidebar.ss`, not `Sidebar.ss`.
Please refer to our [template syntax](/developer_guides/templates/syntax) for details.
The `forms` template folder is no longer used to lookup templates for `FormField` instances.
### Update code that uses SQLQuery

View File

@ -479,9 +479,11 @@ class Email extends ViewableData {
if($this->ss_template && !$isPlain) {
// Requery data so that updated versions of To, From, Subject, etc are included
$data = $this->templateData();
$template = new SSViewer('email/'.$this->ss_template);
$candidateTemplates = [
$this->ss_template,
[ 'type' => 'email', $this->ss_template ]
];
$template = new SSViewer($candidateTemplates);
if($template->exists()) {
$fullBody = $template->process($data);
}

View File

@ -1025,14 +1025,27 @@ class Form extends RequestHandler {
/**
* Return the template to render this form with.
* If the template isn't set, then default to the
* form class name e.g "Form".
*
* @return string
*/
public function getTemplate() {
if($this->template) return $this->template;
else return $this->class;
return $this->template;
}
/**
* Returs the ordered list of preferred templates for rendering this form
* If the template isn't set, then default to the
* form class name e.g "Form".
*
* @return array
*/
public function getTemplates() {
$templates = SSViewer::get_templates_by_class(get_class($this), '', __CLASS__);
// Prefer any custom template
if($this->getTemplate()) {
array_unshift($templates, $this->getTemplate());
}
return $templates;
}
/**
@ -1639,10 +1652,7 @@ class Form extends RequestHandler {
* @return DBHTMLText
*/
public function forTemplate() {
$return = $this->renderWith(array_merge(
(array)$this->getTemplate(),
array('Includes/Form')
));
$return = $this->renderWith($this->getTemplates());
// Now that we're rendered, clear message
$this->clearMessage();
@ -1742,7 +1752,7 @@ class Form extends RequestHandler {
/**
* Get a list of all actions, including those in the main "fields" FieldList
*
*
* @return array
*/
protected function getAllActions() {

View File

@ -761,7 +761,6 @@ class FormField extends RequestHandler {
*
* @param mixed $value
* @param null|array|DataObject $data {@see Form::loadDataFrom}
*
* @return $this
*/
public function setValue($value) {
@ -1048,22 +1047,14 @@ class FormField extends RequestHandler {
*
* @return array
*/
private function _templates($customTemplate = null, $customTemplateSuffix = null) {
$matches = array();
foreach(array_reverse(ClassInfo::ancestry($this)) as $className) {
$matches[] = 'forms/'. $className . $customTemplateSuffix;
if($className == "FormField") {
break;
}
}
protected function _templates($customTemplate = null, $customTemplateSuffix = null) {
$templates = SSViewer::get_templates_by_class(get_class($this), $customTemplateSuffix, __CLASS__);
// Prefer any custom template
if($customTemplate) {
array_unshift($matches, 'forms/'.$customTemplate);
// Prioritise direct template
array_unshift($templates, $customTemplate);
}
return $matches;
return $templates;
}
/**

View File

@ -25,13 +25,6 @@ use SilverStripe\ORM\DataList;
class GridFieldAddExistingAutocompleter
implements GridField_HTMLProvider, GridField_ActionProvider, GridField_DataManipulator, GridField_URLHandler {
/**
* Which template to use for rendering
*
* @var string $itemClass
*/
protected $itemClass = 'GridFieldAddExistingAutocompleter';
/**
* The HTML fragment to write this component into
*/
@ -84,6 +77,7 @@ class GridFieldAddExistingAutocompleter
/**
*
* @param string $targetFragment
* @param array $searchFields Which fields on the object in the list should be searched
*/
public function __construct($targetFragment = 'before', $searchFields = null) {
@ -97,7 +91,7 @@ class GridFieldAddExistingAutocompleter
* @return string[] - HTML
*/
public function getHTMLFragments($gridField) {
$dataClass = $gridField->getList()->dataClass();
$dataClass = $gridField->getModelClass();
$forTemplate = new ArrayData(array());
$forTemplate->Fields = new FieldList();
@ -130,8 +124,9 @@ class GridFieldAddExistingAutocompleter
$forTemplate->Fields->setForm($form);
}
$template = SSViewer::get_templates_by_class($this, '', __CLASS__);
return array(
$this->targetFragment => $forTemplate->renderWith('Includes/'.$this->itemClass)
$this->targetFragment => $forTemplate->renderWith($template)
);
}
@ -174,7 +169,7 @@ class GridFieldAddExistingAutocompleter
if(empty($objectID)) {
return $dataList;
}
$object = DataObject::get_by_id($dataList->dataclass(), $objectID);
$object = DataObject::get_by_id($gridField->getModelClass(), $objectID);
if($object) {
$dataList->add($object);
}
@ -198,9 +193,10 @@ class GridFieldAddExistingAutocompleter
*
* @param GridField $gridField
* @param SS_HTTPRequest $request
* @return string
*/
public function doSearch($gridField, $request) {
$dataClass = $gridField->getList()->dataClass();
$dataClass = $gridField->getModelClass();
$allList = $this->searchList ? $this->searchList : DataList::create($dataClass);
$searchFields = ($this->getSearchFields())
@ -269,6 +265,7 @@ class GridFieldAddExistingAutocompleter
/**
* @param array $fields
* @return $this
*/
public function setSearchFields($fields) {
$this->searchFields = $fields;

View File

@ -43,8 +43,9 @@ class GridFieldAddNewButton implements GridField_HTMLProvider {
'ButtonName' => $this->buttonName,
));
$templates = SSViewer::get_templates_by_class($this, '', __CLASS__);
return array(
$this->targetFragment => $data->renderWith('Includes/GridFieldAddNewButton'),
$this->targetFragment => $data->renderWith($templates),
);
}

View File

@ -26,8 +26,9 @@ class GridFieldButtonRow implements GridField_HTMLProvider {
"RightFragment" => "\$DefineFragment(buttons-{$this->targetFragment}-right)",
));
$templates = SSViewer::get_templates_by_class($this, '', __CLASS__);
return array(
$this->targetFragment => $data->renderWith('Includes/GridFieldButtonRow')
$this->targetFragment => $data->renderWith($templates)
);
}
}

View File

@ -127,7 +127,7 @@ class GridFieldDetailForm implements GridField_URLHandler {
$class,
array($gridField, $this, $record, $requestHandler, $this->name)
);
$handler->setTemplate($this->template);
$handler->setTemplate($this->getTemplate());
$this->extend('updateItemRequestHandler', $handler);
return $handler;
}
@ -214,7 +214,7 @@ class GridFieldDetailForm implements GridField_URLHandler {
} else if(ClassInfo::exists(get_class($this) . "_ItemRequest")) {
return get_class($this) . "_ItemRequest";
} else {
return 'GridFieldDetailForm_ItemRequest';
return __CLASS__ . '_ItemRequest';
}
}
@ -256,7 +256,7 @@ class GridFieldDetailForm_ItemRequest extends RequestHandler {
/**
*
* @var GridField_URLHandler
* @var GridFieldDetailForm
*/
protected $component;
@ -283,7 +283,7 @@ class GridFieldDetailForm_ItemRequest extends RequestHandler {
/**
* @var String
*/
protected $template = 'GridFieldItemEditView';
protected $template = null;
private static $url_handlers = array(
'$Action!' => '$Action',
@ -293,7 +293,7 @@ class GridFieldDetailForm_ItemRequest extends RequestHandler {
/**
*
* @param GridFIeld $gridField
* @param GridField_URLHandler $component
* @param GridFieldDetailForm $component
* @param DataObject $record
* @param RequestHandler $requestHandler
* @param string $popupFormName
@ -319,14 +319,14 @@ class GridFieldDetailForm_ItemRequest extends RequestHandler {
$controller = $this->getToplevelController();
$form = $this->ItemEditForm($this->gridField, $request);
$form = $this->ItemEditForm();
$form->makeReadonly();
$data = new ArrayData(array(
'Backlink' => $controller->Link(),
'ItemEditForm' => $form
));
$return = $data->renderWith($this->template);
$return = $data->renderWith($this->getTemplates());
if($request->isAjax()) {
return $return;
@ -337,12 +337,12 @@ class GridFieldDetailForm_ItemRequest extends RequestHandler {
public function edit($request) {
$controller = $this->getToplevelController();
$form = $this->ItemEditForm($this->gridField, $request);
$form = $this->ItemEditForm();
$return = $this->customise(array(
'Backlink' => $controller->hasMethod('Backlink') ? $controller->Backlink() : $controller->Link(),
'ItemEditForm' => $form,
))->renderWith($this->template);
))->renderWith($this->getTemplates());
if($request->isAjax()) {
return $return;
@ -751,6 +751,21 @@ class GridFieldDetailForm_ItemRequest extends RequestHandler {
return $this->template;
}
/**
* Get list of templates to use
*
* @return array
*/
public function getTemplates()
{
$templates = SSViewer::get_templates_by_class($this, '', __CLASS__);
// Prefer any custom template
if($this->getTemplate()) {
array_unshift($templates, $this->getTemplate());
}
return $templates;
}
/**
* @return Controller
*/

View File

@ -49,6 +49,7 @@ class GridFieldEditButton implements GridField_ColumnProvider {
if($columnName == 'Actions') {
return array('title' => '');
}
return [];
}
/**
@ -75,8 +76,7 @@ class GridFieldEditButton implements GridField_ColumnProvider {
* @param GridField $gridField
* @param DataObject $record
* @param string $columnName
*
* @return string - the HTML for the column
* @return string The HTML for the column
*/
public function getColumnContent($gridField, $record, $columnName) {
// No permission checks, handled through GridFieldDetailForm,
@ -86,7 +86,8 @@ class GridFieldEditButton implements GridField_ColumnProvider {
'Link' => Controller::join_links($gridField->Link('item'), $record->ID, 'edit')
));
return $data->renderWith('Includes/GridFieldEditButton');
$template = SSViewer::get_templates_by_class($this, '', __CLASS__);
return $data->renderWith($template);
}
/**

View File

@ -161,8 +161,9 @@ class GridFieldFilterHeader implements GridField_HTMLProvider, GridField_DataMan
$forTemplate->Fields->push($fields);
}
$templates = SSViewer::get_templates_by_class($this, '_Row', __CLASS__);
return array(
'header' => $forTemplate->renderWith('Includes/GridFieldFilterHeader_Row'),
'header' => $forTemplate->renderWith($templates),
);
}
}

View File

@ -25,7 +25,8 @@ class GridFieldFooter implements GridField_HTMLProvider {
/**
*
* @param string $message - a message to display in the footer
* @param string $message A message to display in the footer
* @param bool $showrecordcount
*/
public function __construct($message = null, $showrecordcount = true) {
if($message) {
@ -46,9 +47,10 @@ class GridFieldFooter implements GridField_HTMLProvider {
'NumRecords' => $count
));
$template = SSViewer::get_templates_by_class($this, '', __CLASS__);
return array(
'footer' => $forTemplate->renderWith(
'Includes/GridFieldFooter',
$template,
array(
'Colspan' => count($gridField->getColumns())
)

View File

@ -35,40 +35,48 @@ class GridFieldLevelup extends Object implements GridField_HTMLProvider {
* @param integer $currentID - The ID of the current item; this button will find that item's parent
*/
public function __construct($currentID) {
if($currentID && is_numeric($currentID)) $this->currentID = $currentID;
parent::__construct();
if($currentID && is_numeric($currentID)) {
$this->currentID = $currentID;
}
}
public function getHTMLFragments($gridField) {
$modelClass = $gridField->getModelClass();
$parentID = 0;
if($this->currentID) {
$modelObj = DataObject::get_by_id($modelClass, $this->currentID);
if($modelObj->hasMethod('getParent')) {
$parent = $modelObj->getParent();
} elseif($modelObj->ParentID) {
$parent = $modelObj->Parent();
}
if($parent) $parentID = $parent->ID;
// Attributes
$attrs = array_merge($this->attributes, array(
'href' => sprintf($this->linkSpec, $parentID),
'class' => 'cms-panel-link ss-ui-button font-icon-level-up no-text grid-levelup'
));
$attrsStr = '';
foreach($attrs as $k => $v) $attrsStr .= " $k=\"" . Convert::raw2att($v) . "\"";
$forTemplate = new ArrayData(array(
'UpLink' => DBField::create_field('HTMLFragment', sprintf('<a%s></a>', $attrsStr))
));
return array(
'before' => $forTemplate->renderWith('Includes/GridFieldLevelup'),
);
if(!$this->currentID) {
return null;
}
$modelObj = DataObject::get_by_id($modelClass, $this->currentID);
$parent = null;
if($modelObj->hasMethod('getParent')) {
$parent = $modelObj->getParent();
} elseif($modelObj->ParentID) {
$parent = $modelObj->Parent();
}
if ($parent) {
$parentID = $parent->ID;
}
// Attributes
$attrs = array_merge($this->attributes, array(
'href' => sprintf($this->linkSpec, $parentID),
'class' => 'cms-panel-link ss-ui-button font-icon-level-up no-text grid-levelup'
));
$linkTag = FormField::create_tag('a', $attrs);
$forTemplate = new ArrayData(array(
'UpLink' => DBField::create_field('HTMLFragment', $linkTag)
));
$template = SSViewer::get_templates_by_class($this, '', __CLASS__);
return array(
'before' => $forTemplate->renderWith($template),
);
}
public function setAttributes($attrs) {

View File

@ -16,14 +16,7 @@ class GridFieldPageCount implements GridField_HTMLProvider {
protected $targetFragment;
/**
* Which template to use for rendering
*
* @var string
*/
protected $itemClass = 'GridFieldPageCount';
/**
* @param string $targetFrament The fragment indicating the placement of this page count
* @param string $targetFragment The fragment indicating the placement of this page count
*/
public function __construct($targetFragment = 'before') {
$this->targetFragment = $targetFragment;
@ -62,14 +55,16 @@ class GridFieldPageCount implements GridField_HTMLProvider {
* @return array
*/
public function getHTMLFragments($gridField) {
// Retrieve paging parameters from the directing paginator component
$paginator = $this->getPaginator($gridField);
if ($paginator && ($forTemplate = $paginator->getTemplateParameters($gridField))) {
$template = SSViewer::get_templates_by_class($this, '', __CLASS__);
return array(
$this->targetFragment => $forTemplate->renderWith('Includes/'.$this->itemClass)
$this->targetFragment => $forTemplate->renderWith($template)
);
}
return null;
}
}

View File

@ -25,13 +25,6 @@ class GridFieldPaginator implements GridField_HTMLProvider, GridField_DataManipu
*/
protected $itemsPerPage;
/**
* Which template to use for rendering
*
* @var string
*/
protected $itemClass = 'GridFieldPaginator_Row';
/**
* See {@link setThrowExceptionOnBadDataType()}
*/
@ -255,18 +248,22 @@ class GridFieldPaginator implements GridField_HTMLProvider, GridField_DataManipu
* @return array
*/
public function getHTMLFragments($gridField) {
$forTemplate = $this->getTemplateParameters($gridField);
if($forTemplate) {
return array(
'footer' => $forTemplate->renderWith('Includes/'.$this->itemClass,
array('Colspan'=>count($gridField->getColumns())))
);
if(!$forTemplate) {
return null;
}
$template = SSViewer::get_templates_by_class($this, '_Row', __CLASS__);
return array(
'footer' => $forTemplate->renderWith(
$template,
array('Colspan' => count($gridField->getColumns()))
)
);
}
/**
* @param Int
* @param int $num
* @return $this
*/
public function setItemsPerPage($num) {
$this->itemsPerPage = $num;

View File

@ -121,8 +121,10 @@ class GridFieldPrintButton implements GridField_HTMLProvider, GridField_ActionPr
Requirements::css(FRAMEWORK_DIR . '/client/dist/styles/GridField_print.css');
if($data = $this->generatePrintData($gridField)){
return $data->renderWith("GridField_print");
return $data->renderWith(get_class($gridField)."_print");
}
return null;
}
/**

View File

@ -163,8 +163,9 @@ class GridFieldSortableHeader implements GridField_HTMLProvider, GridField_DataM
$forTemplate->Fields->push($field);
}
$template = SSViewer::get_templates_by_class($this, '_Row', __CLASS__);
return array(
'header' => $forTemplate->renderWith('Includes/GridFieldSortableHeader_Row'),
'header' => $forTemplate->renderWith($template),
);
}

View File

@ -11,9 +11,14 @@
*/
class GridFieldToolbarHeader implements GridField_HTMLProvider {
public function getHTMLFragments( $gridField) {
/**
* @param GridField $gridField
* @return array
*/
public function getHTMLFragments($gridField) {
$templates = SSViewer::get_templates_by_class($this, '', __CLASS__);
return array(
'header' => $gridField->renderWith('Includes/GridFieldToolbarHeader')
'header' => $gridField->renderWith($templates)
);
}
}

View File

@ -10,7 +10,9 @@
class GridFieldViewButton implements GridField_ColumnProvider {
public function augmentColumns($field, &$cols) {
if(!in_array('Actions', $cols)) $cols[] = 'Actions';
if(!in_array('Actions', $cols)) {
$cols[] = 'Actions';
}
}
public function getColumnsHandled($field) {
@ -18,12 +20,14 @@ class GridFieldViewButton implements GridField_ColumnProvider {
}
public function getColumnContent($field, $record, $col) {
if($record->canView()) {
$data = new ArrayData(array(
'Link' => Controller::join_links($field->Link('item'), $record->ID, 'view')
));
return $data->renderWith('Includes/GridFieldViewButton');
if(!$record->canView()) {
return null;
}
$data = new ArrayData(array(
'Link' => Controller::join_links($field->Link('item'), $record->ID, 'view')
));
$template = SSViewer::get_templates_by_class($this, '', __CLASS__);
return $data->renderWith($template);
}
public function getColumnAttributes($field, $record, $col) {

View File

@ -1,5 +0,0 @@
<% if Backlink %>
<a href="$Backlink"><%t GridFieldItemEditView.Go_back 'Go back' %></a>
<% end_if %>
$ItemEditForm

View File

@ -76,7 +76,7 @@ class RSSFeedTest extends SapphireTest {
$content = $rssFeed->outputToBrowser();
$this->assertContains('<title>Test Custom Template</title>', $content);
$rssFeed->setTemplate('RSSFeed');
$rssFeed->setTemplate(null);
$content = $rssFeed->outputToBrowser();
$this->assertNotContains('<title>Test Custom Template</title>', $content);
}

View File

@ -123,6 +123,31 @@ class ThemeResourceLoaderTest extends SapphireTest {
);
}
public function testFindTemplatesByPath() {
// Items given as full paths are returned directly
$this->assertEquals(
"$this->base/themes/theme/templates/Page.ss",
$this->loader->findTemplate("$this->base/themes/theme/templates/Page.ss", ['theme'])
);
$this->assertEquals(
"$this->base/themes/theme/templates/Page.ss",
$this->loader->findTemplate([
"$this->base/themes/theme/templates/Page.ss",
"Page"
], ['theme'])
);
// Ensure checks for file_exists
$this->assertEquals(
"$this->base/themes/theme/templates/Page.ss",
$this->loader->findTemplate([
"$this->base/themes/theme/templates/NotAPage.ss",
"$this->base/themes/theme/templates/Page.ss",
], ['theme'])
);
}
/**
* Test that 'main' and 'Layout' templates are loaded from set theme
*/

View File

@ -110,7 +110,7 @@ class MemberDatetimeOptionsetFieldTest extends SapphireTest {
$field->setDescription('Test description');
$this->assertEquals('Test description', $field->getDescription());
$field->setDescriptionTemplate('forms/MemberDatetimeOptionsetField_description_time');
$field->setDescriptionTemplate('MemberDatetimeOptionsetField_description_time');
$this->assertNotEmpty($field->getDescription());
$this->assertNotEquals('Test description', $field->getDescription());
}

View File

@ -1154,22 +1154,57 @@ after')
$this->useTestTheme(dirname(__FILE__), 'layouttest', function() use ($self) {
// Test passing a string
$templates = SSViewer::get_templates_by_class(
'TestNamespace\SSViewerTestModel_Controller',
'TestNamespace\\SSViewerTestModel_Controller',
'',
'Controller'
);
$self->assertEquals([
'TestNamespace\SSViewerTestModel_Controller',
'TestNamespace\\SSViewerTestModel_Controller',
[
'type' => 'Includes',
'TestNamespace\\SSViewerTestModel_Controller',
],
'TestNamespace\\SSViewerTestModel',
'Controller',
[
'type' => 'Includes',
'Controller',
],
], $templates);
// Test to ensure we're stopping at the base class.
$templates = SSViewer::get_templates_by_class('TestNamespace\SSViewerTestModel_Controller', '', 'TestNamespace\SSViewerTestModel_Controller');
$self->assertCount(1, $templates);
$templates = SSViewer::get_templates_by_class(
'TestNamespace\SSViewerTestModel_Controller',
'',
'TestNamespace\SSViewerTestModel_Controller'
);
$self->assertEquals([
'TestNamespace\\SSViewerTestModel_Controller',
[
'type' => 'Includes',
'TestNamespace\\SSViewerTestModel_Controller',
],
'TestNamespace\\SSViewerTestModel',
], $templates);
// Make sure we can filter our templates by suffix.
$templates = SSViewer::get_templates_by_class('TestNamespace\SSViewerTestModel', '_Controller');
$self->assertCount(1, $templates);
// Make sure we can search templates by suffix.
$templates = SSViewer::get_templates_by_class(
'TestNamespace\\SSViewerTestModel',
'_Controller',
'SilverStripe\\ORM\\DataObject'
);
$self->assertEquals([
'TestNamespace\\SSViewerTestModel_Controller',
[
'type' => 'Includes',
'TestNamespace\\SSViewerTestModel_Controller',
],
'SilverStripe\\ORM\\DataObject_Controller',
[
'type' => 'Includes',
'SilverStripe\\ORM\\DataObject_Controller',
],
], $templates);
// Let's throw something random in there.
$self->setExpectedException('InvalidArgumentException');

View File

@ -859,51 +859,52 @@ class SSViewer implements Flushable {
}
/**
* Traverses the given the given class context looking for templates with the relevant name.
*
* @param $className string - valid class name
* @param $suffix string
* @param $baseClass string
* Traverses the given the given class context looking for candidate template names
* which match each item in the class hierarchy. The resulting list of template candidates
* may or may not exist, but you can invoke {@see SSViewer::chooseTemplate} on any list
* to determine the best candidate based on the current themes.
*
* @param string|object $classOrObject Valid class name, or object
* @param string $suffix
* @param string $baseClass Class to halt ancestry search at
* @return array
*/
public static function get_templates_by_class($className, $suffix = '', $baseClass = null) {
public static function get_templates_by_class($classOrObject, $suffix = '', $baseClass = null) {
// Figure out the class name from the supplied context.
if(!is_string($className) || !class_exists($className)) {
if (!is_object($classOrObject) && !(
is_string($classOrObject) && class_exists($classOrObject)
)) {
throw new InvalidArgumentException(
'SSViewer::get_templates_by_class() expects a valid class name as its first parameter.'
);
}
$templates = array();
$classes = array_reverse(ClassInfo::ancestry($className));
$classes = array_reverse(ClassInfo::ancestry($classOrObject));
foreach($classes as $class) {
$template = $class . $suffix;
if(SSViewer::hasTemplate($template)) {
$templates[] = $template;
} elseif(SSViewer::hasTemplate($template = ['type' => 'Includes', $template])) {
$templates[] = $template;
}
$templates[] = $template;
$templates[] = ['type' => 'Includes', $template];
// If the class is "Page_Controller", look for Page.ss
if(stripos($class,'_controller') !== false) {
$template = str_ireplace('_controller','',$class) . $suffix;
if(SSViewer::hasTemplate($template)) {
$templates[] = $template;
}
if (stripos($class, '_controller') !== false) {
$templates[] = str_ireplace('_controller', '', $class) . $suffix;
}
if($baseClass && $class == $baseClass) break;
if($baseClass && $class == $baseClass) {
break;
}
}
return $templates;
}
/**
* @param string|array $templateList If passed as a string with .ss extension, used as the "main" template.
* @param string|array $templates If passed as a string with .ss extension, used as the "main" template.
* If passed as an array, it can be used for template inheritance (first found template "wins").
* Usually the array values are PHP class names, which directly correlate to template names.
* <code>
* array('MySpecificPage', 'MyPage', 'Page')
* </code>
* @param TemplateParser $parser
*/
public function __construct($templates, TemplateParser $parser = null) {
if ($parser) {
@ -929,10 +930,20 @@ class SSViewer implements Flushable {
public function setTemplate($templates) {
$this->templates = $templates;
$this->chosen = ThemeResourceLoader::instance()->findTemplate($templates, self::get_themes());
$this->chosen = $this->chooseTemplate($templates);
$this->subTemplates = [];
}
/**
* Find the template to use for a given list
*
* @param array|string $templates
* @return string
*/
public static function chooseTemplate($templates) {
return ThemeResourceLoader::instance()->findTemplate($templates, self::get_themes());
}
/**
* Set the template parser that will be used in template generation
* @param \TemplateParser $parser

View File

@ -155,13 +155,6 @@ class ThemeResourceLoader {
$templateList = array($template);
}
// If we have an .ss extension, this is a path, not a template name. We should
// pass in templates without extensions in order for template manifest to find
// files dynamically.
if(count($templateList) == 1 && is_string($templateList[0]) && substr($templateList[0], -3) == '.ss') {
return $templateList[0];
}
foreach($templateList as $i => $template) {
// Check if passed list of templates in array format
if (is_array($template)) {
@ -172,6 +165,13 @@ class ThemeResourceLoader {
continue;
}
// If we have an .ss extension, this is a path, not a template name. We should
// pass in templates without extensions in order for template manifest to find
// files dynamically.
if(substr($template, -3) == '.ss' && file_exists($template)) {
return $template;
}
// Check string template identifier
$template = str_replace('\\', '/', $template);
$parts = explode('/', $template);