Skip to content
Permalink
Browse files

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
  • Loading branch information...
Damian Mooyman
Damian Mooyman committed Aug 23, 2016
1 parent f40ed07 commit c9b6e9bac0aaf78e3b2963c9e6ad38ad27fa46f1
Showing with 317 additions and 182 deletions.
  1. +6 −2 Security/Member.php
  2. +2 −1 admin/code/LeftAndMain.php
  3. +2 −2 admin/code/SecurityAdmin.php
  4. 0 admin/themes/cms-forms/templates/{forms → }/CMSTabSet.ss
  5. 0 admin/themes/cms-forms/templates/{forms → }/CheckboxField_holder.ss
  6. 0 admin/themes/cms-forms/templates/{forms → }/CheckboxField_holder_small.ss
  7. 0 admin/themes/cms-forms/templates/{forms → }/CheckboxSetField.ss
  8. 0 admin/themes/cms-forms/templates/{forms → }/CompositeField.ss
  9. 0 admin/themes/cms-forms/templates/{forms → }/CompositeField_holder.ss
  10. 0 admin/themes/cms-forms/templates/{forms → }/CompositeField_holder_small.ss
  11. 0 admin/themes/cms-forms/templates/{forms → }/CreditCardField.ss
  12. 0 admin/themes/cms-forms/templates/{forms → }/DatetimeField.ss
  13. 0 admin/themes/cms-forms/templates/{forms → }/DatetimeField_holder.ss
  14. 0 admin/themes/cms-forms/templates/{forms → }/FieldGroup.ss
  15. 0 admin/themes/cms-forms/templates/{forms → }/FieldGroup_DefaultFieldHolder.ss
  16. 0 admin/themes/cms-forms/templates/{forms → }/FieldGroup_holder.ss
  17. 0 admin/themes/cms-forms/templates/{forms → }/FormAction_holder_small.ss
  18. 0 admin/themes/cms-forms/templates/{forms → }/FormField.ss
  19. 0 admin/themes/cms-forms/templates/{forms → }/FormField_holder.ss
  20. 0 admin/themes/cms-forms/templates/{forms → }/FormField_holder_small.ss
  21. 0 admin/themes/cms-forms/templates/{forms → }/LabelField.ss
  22. 0 admin/themes/cms-forms/templates/{forms → }/LookupField.ss
  23. 0 admin/themes/cms-forms/templates/{forms → }/MemberDatetimeOptionsetField.ss
  24. 0 admin/themes/cms-forms/templates/{forms → }/MoneyField.ss
  25. 0 admin/themes/cms-forms/templates/{forms → }/OptionsetField.ss
  26. 0 admin/themes/cms-forms/templates/{forms → }/OptionsetField_holder.ss
  27. 0 admin/themes/cms-forms/templates/{forms → }/PhoneNumberField.ss
  28. 0 admin/themes/cms-forms/templates/{forms → }/ReadonlyField.ss
  29. 0 admin/themes/cms-forms/templates/{forms → }/SelectionGroup.ss
  30. +19 −4 api/RSSFeed.php
  31. +18 −14 core/ClassInfo.php
  32. +6 −4 docs/en/02_Developer_Guides/01_Templates/01_Syntax.md
  33. +12 −2 docs/en/04_Changelogs/4.0.0.md
  34. +5 −3 email/Email.php
  35. +19 −9 forms/Form.php
  36. +6 −15 forms/FormField.php
  37. +8 −11 forms/gridfield/GridFieldAddExistingAutocompleter.php
  38. +2 −1 forms/gridfield/GridFieldAddNewButton.php
  39. +2 −1 forms/gridfield/GridFieldButtonRow.php
  40. +24 −9 forms/gridfield/GridFieldDetailForm.php
  41. +4 −3 forms/gridfield/GridFieldEditButton.php
  42. +2 −1 forms/gridfield/GridFieldFilterHeader.php
  43. +4 −2 forms/gridfield/GridFieldFooter.php
  44. +30 −22 forms/gridfield/GridFieldLevelup.php
  45. +5 −10 forms/gridfield/GridFieldPageCount.php
  46. +11 −14 forms/gridfield/GridFieldPaginator.php
  47. +3 −1 forms/gridfield/GridFieldPrintButton.php
  48. +2 −1 forms/gridfield/GridFieldSortableHeader.php
  49. +7 −2 forms/gridfield/GridFieldToolbarHeader.php
  50. +10 −6 forms/gridfield/GridFieldViewButton.php
  51. 0 templates/{forms → }/AssetField.ss
  52. 0 templates/{forms → }/AssetUploadField.ss
  53. 0 templates/{forms → }/CheckboxField.ss
  54. 0 templates/{forms → }/CheckboxField_holder.ss
  55. 0 templates/{forms → }/CheckboxField_holder_small.ss
  56. 0 templates/{forms → }/CheckboxSetField.ss
  57. 0 templates/{forms → }/CompositeField.ss
  58. 0 templates/{forms → }/CompositeField_holder.ss
  59. 0 templates/{forms → }/CompositeField_holder_small.ss
  60. 0 templates/{forms → }/CreditCardField.ss
  61. 0 templates/{forms → }/DatetimeField.ss
  62. 0 templates/{forms → }/DropdownField.ss
  63. 0 templates/{forms → }/FieldGroup.ss
  64. 0 templates/{forms → }/FieldGroup_DefaultFieldHolder.ss
  65. 0 templates/{forms → }/FieldGroup_holder.ss
  66. 0 templates/{forms → }/FileField.ss
  67. 0 templates/{forms → }/FormAction.ss
  68. 0 templates/{forms → }/FormField.ss
  69. 0 templates/{forms → }/FormField_holder.ss
  70. 0 templates/{forms → }/FormField_holder_small.ss
  71. 0 templates/{forms → }/GroupedDropdownField.ss
  72. 0 templates/{forms → }/HTMLEditorField_UploadField.ss
  73. 0 templates/{forms → }/HTMLReadonlyField.ss
  74. 0 templates/{forms → }/HeaderField.ss
  75. 0 templates/{forms → }/HiddenField.ss
  76. +0 −5 templates/Includes/GridFieldItemEditView.ss
  77. 0 templates/{forms → }/LabelField.ss
  78. 0 templates/{forms → }/ListboxField.ss
  79. 0 templates/{forms → }/LookupField.ss
  80. 0 templates/{forms → }/MemberDatetimeOptionsetField.ss
  81. 0 templates/{forms → }/MemberDatetimeOptionsetField_description_date.ss
  82. 0 templates/{forms → }/MemberDatetimeOptionsetField_description_time.ss
  83. 0 templates/{forms → }/MoneyField.ss
  84. 0 templates/{forms → }/OptionsetField.ss
  85. 0 templates/{forms → }/OptionsetField_holder.ss
  86. 0 templates/{forms → }/ReadonlyField.ss
  87. 0 templates/{forms → }/SelectionGroup.ss
  88. 0 templates/{forms → }/TabSet.ss
  89. 0 templates/{forms → }/TextField.ss
  90. 0 templates/{forms → }/TextareaField.ss
  91. 0 templates/{forms → }/ToggleCompositeField.ss
  92. 0 templates/{forms → }/TreeDropdownField.ss
  93. 0 templates/{forms → }/UploadField.ss
  94. +1 −1 tests/api/RSSFeedTest.php
  95. +25 −0 tests/core/manifest/ThemeResourceLoaderTest.php
  96. +1 −1 tests/forms/MemberDatetimeOptionsetFieldTest.php
  97. +42 −7 tests/view/SSViewerTest.php
  98. +32 −21 view/SSViewer.php
  99. +7 −7 view/ThemeResourceLoader.php
@@ -15,6 +15,7 @@
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 @@ public function getCMSFields() {
$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 @@ public function getCMSFields() {
)
);
$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();
@@ -972,7 +972,8 @@ public function MenuCurrentItem() {
* @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() {
@@ -165,7 +165,7 @@ public function getEditForm($id = null, $fields = null) {
// 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 @@ public function getEditForm($id = null, $fields = null) {
)
));
$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(
@@ -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 @@ public function outputToBrowser() {
Config::inst()->update('SSViewer', 'source_file_comments', $prevState);
return $this->renderWith($this->getTemplate());
return $this->renderWith($this->getTemplates());
}
/**
@@ -240,6 +240,21 @@ public function setTemplate($template) {
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;
}
}
/**
@@ -87,15 +87,17 @@ public static function getValidSubClasses($class = 'SilverStripe\\CMS\\Model\\Si
*
* @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 @@ public static function baseDataClass($class) {
* )
* </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 @@ public static function class_name($nameOrObject) {
* 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);
@@ -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.
@@ -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

@@ -479,9 +479,11 @@ protected function parseVariables($isPlain = false) {
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);
}
@@ -1025,14 +1025,27 @@ public function setTemplate($template) {
/**
* 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 @@ public function callfieldmethod($data) {
* @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 @@ public function buttonClicked() {
/**
* Get a list of all actions, including those in the main "fields" FieldList
*
*
* @return array
*/
protected function getAllActions() {
@@ -761,7 +761,6 @@ public function attrValue() {
*
* @param mixed $value
* @param null|array|DataObject $data {@see Form::loadDataFrom}
*
* @return $this
*/
public function setValue($value) {
@@ -1048,22 +1047,14 @@ public function getSmallFieldHolderTemplates() {
*
* @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;
}
/**

0 comments on commit c9b6e9b

Please sign in to comment.
You can’t perform that action at this time.