From c9b6e9bac0aaf78e3b2963c9e6ad38ad27fa46f1 Mon Sep 17 00:00:00 2001 From: Damian Mooyman Date: Tue, 23 Aug 2016 14:32:26 +1200 Subject: [PATCH] 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 --- Security/Member.php | 8 ++- admin/code/LeftAndMain.php | 3 +- admin/code/SecurityAdmin.php | 4 +- .../templates/{forms => }/CMSTabSet.ss | 0 .../{forms => }/CheckboxField_holder.ss | 0 .../{forms => }/CheckboxField_holder_small.ss | 0 .../templates/{forms => }/CheckboxSetField.ss | 0 .../templates/{forms => }/CompositeField.ss | 0 .../{forms => }/CompositeField_holder.ss | 0 .../CompositeField_holder_small.ss | 0 .../templates/{forms => }/CreditCardField.ss | 0 .../templates/{forms => }/DatetimeField.ss | 0 .../{forms => }/DatetimeField_holder.ss | 0 .../templates/{forms => }/FieldGroup.ss | 0 .../FieldGroup_DefaultFieldHolder.ss | 0 .../{forms => }/FieldGroup_holder.ss | 0 .../{forms => }/FormAction_holder_small.ss | 0 .../templates/{forms => }/FormField.ss | 0 .../templates/{forms => }/FormField_holder.ss | 0 .../{forms => }/FormField_holder_small.ss | 0 .../templates/{forms => }/LabelField.ss | 0 .../templates/{forms => }/LookupField.ss | 0 .../MemberDatetimeOptionsetField.ss | 0 .../templates/{forms => }/MoneyField.ss | 0 .../templates/{forms => }/OptionsetField.ss | 0 .../{forms => }/OptionsetField_holder.ss | 0 .../templates/{forms => }/PhoneNumberField.ss | 0 .../templates/{forms => }/ReadonlyField.ss | 0 .../templates/{forms => }/SelectionGroup.ss | 0 api/RSSFeed.php | 23 +++++-- core/ClassInfo.php | 32 +++++----- .../01_Templates/01_Syntax.md | 10 +-- docs/en/04_Changelogs/4.0.0.md | 14 ++++- email/Email.php | 8 ++- forms/Form.php | 28 ++++++--- forms/FormField.php | 21 ++----- .../GridFieldAddExistingAutocompleter.php | 19 +++--- forms/gridfield/GridFieldAddNewButton.php | 3 +- forms/gridfield/GridFieldButtonRow.php | 3 +- forms/gridfield/GridFieldDetailForm.php | 33 +++++++--- forms/gridfield/GridFieldEditButton.php | 7 ++- forms/gridfield/GridFieldFilterHeader.php | 3 +- forms/gridfield/GridFieldFooter.php | 6 +- forms/gridfield/GridFieldLevelup.php | 62 +++++++++++-------- forms/gridfield/GridFieldPageCount.php | 15 ++--- forms/gridfield/GridFieldPaginator.php | 25 ++++---- forms/gridfield/GridFieldPrintButton.php | 4 +- forms/gridfield/GridFieldSortableHeader.php | 3 +- forms/gridfield/GridFieldToolbarHeader.php | 9 ++- forms/gridfield/GridFieldViewButton.php | 16 +++-- templates/{forms => }/AssetField.ss | 0 templates/{forms => }/AssetUploadField.ss | 0 templates/{forms => }/CheckboxField.ss | 0 templates/{forms => }/CheckboxField_holder.ss | 0 .../{forms => }/CheckboxField_holder_small.ss | 0 templates/{forms => }/CheckboxSetField.ss | 0 templates/{forms => }/CompositeField.ss | 0 .../{forms => }/CompositeField_holder.ss | 0 .../CompositeField_holder_small.ss | 0 templates/{forms => }/CreditCardField.ss | 0 templates/{forms => }/DatetimeField.ss | 0 templates/{forms => }/DropdownField.ss | 0 templates/{forms => }/FieldGroup.ss | 0 .../FieldGroup_DefaultFieldHolder.ss | 0 templates/{forms => }/FieldGroup_holder.ss | 0 templates/{forms => }/FileField.ss | 0 templates/{forms => }/FormAction.ss | 0 templates/{forms => }/FormField.ss | 0 templates/{forms => }/FormField_holder.ss | 0 .../{forms => }/FormField_holder_small.ss | 0 templates/{forms => }/GroupedDropdownField.ss | 0 .../HTMLEditorField_UploadField.ss | 0 templates/{forms => }/HTMLReadonlyField.ss | 0 templates/{forms => }/HeaderField.ss | 0 templates/{forms => }/HiddenField.ss | 0 templates/Includes/GridFieldItemEditView.ss | 5 -- templates/{forms => }/LabelField.ss | 0 templates/{forms => }/ListboxField.ss | 0 templates/{forms => }/LookupField.ss | 0 .../MemberDatetimeOptionsetField.ss | 0 ...DatetimeOptionsetField_description_date.ss | 0 ...DatetimeOptionsetField_description_time.ss | 0 templates/{forms => }/MoneyField.ss | 0 templates/{forms => }/OptionsetField.ss | 0 .../{forms => }/OptionsetField_holder.ss | 0 templates/{forms => }/ReadonlyField.ss | 0 templates/{forms => }/SelectionGroup.ss | 0 templates/{forms => }/TabSet.ss | 0 templates/{forms => }/TextField.ss | 0 templates/{forms => }/TextareaField.ss | 0 templates/{forms => }/ToggleCompositeField.ss | 0 templates/{forms => }/TreeDropdownField.ss | 0 templates/{forms => }/UploadField.ss | 0 tests/api/RSSFeedTest.php | 2 +- .../core/manifest/ThemeResourceLoaderTest.php | 25 ++++++++ .../MemberDatetimeOptionsetFieldTest.php | 2 +- tests/view/SSViewerTest.php | 49 ++++++++++++--- view/SSViewer.php | 53 +++++++++------- view/ThemeResourceLoader.php | 14 ++--- 99 files changed, 322 insertions(+), 187 deletions(-) rename admin/themes/cms-forms/templates/{forms => }/CMSTabSet.ss (100%) rename admin/themes/cms-forms/templates/{forms => }/CheckboxField_holder.ss (100%) rename admin/themes/cms-forms/templates/{forms => }/CheckboxField_holder_small.ss (100%) rename admin/themes/cms-forms/templates/{forms => }/CheckboxSetField.ss (100%) rename admin/themes/cms-forms/templates/{forms => }/CompositeField.ss (100%) rename admin/themes/cms-forms/templates/{forms => }/CompositeField_holder.ss (100%) rename admin/themes/cms-forms/templates/{forms => }/CompositeField_holder_small.ss (100%) rename admin/themes/cms-forms/templates/{forms => }/CreditCardField.ss (100%) rename admin/themes/cms-forms/templates/{forms => }/DatetimeField.ss (100%) rename admin/themes/cms-forms/templates/{forms => }/DatetimeField_holder.ss (100%) rename admin/themes/cms-forms/templates/{forms => }/FieldGroup.ss (100%) rename admin/themes/cms-forms/templates/{forms => }/FieldGroup_DefaultFieldHolder.ss (100%) rename admin/themes/cms-forms/templates/{forms => }/FieldGroup_holder.ss (100%) rename admin/themes/cms-forms/templates/{forms => }/FormAction_holder_small.ss (100%) rename admin/themes/cms-forms/templates/{forms => }/FormField.ss (100%) rename admin/themes/cms-forms/templates/{forms => }/FormField_holder.ss (100%) rename admin/themes/cms-forms/templates/{forms => }/FormField_holder_small.ss (100%) rename admin/themes/cms-forms/templates/{forms => }/LabelField.ss (100%) rename admin/themes/cms-forms/templates/{forms => }/LookupField.ss (100%) rename admin/themes/cms-forms/templates/{forms => }/MemberDatetimeOptionsetField.ss (100%) rename admin/themes/cms-forms/templates/{forms => }/MoneyField.ss (100%) rename admin/themes/cms-forms/templates/{forms => }/OptionsetField.ss (100%) rename admin/themes/cms-forms/templates/{forms => }/OptionsetField_holder.ss (100%) rename admin/themes/cms-forms/templates/{forms => }/PhoneNumberField.ss (100%) rename admin/themes/cms-forms/templates/{forms => }/ReadonlyField.ss (100%) rename admin/themes/cms-forms/templates/{forms => }/SelectionGroup.ss (100%) rename templates/{forms => }/AssetField.ss (100%) rename templates/{forms => }/AssetUploadField.ss (100%) rename templates/{forms => }/CheckboxField.ss (100%) rename templates/{forms => }/CheckboxField_holder.ss (100%) rename templates/{forms => }/CheckboxField_holder_small.ss (100%) rename templates/{forms => }/CheckboxSetField.ss (100%) rename templates/{forms => }/CompositeField.ss (100%) rename templates/{forms => }/CompositeField_holder.ss (100%) rename templates/{forms => }/CompositeField_holder_small.ss (100%) rename templates/{forms => }/CreditCardField.ss (100%) rename templates/{forms => }/DatetimeField.ss (100%) rename templates/{forms => }/DropdownField.ss (100%) rename templates/{forms => }/FieldGroup.ss (100%) rename templates/{forms => }/FieldGroup_DefaultFieldHolder.ss (100%) rename templates/{forms => }/FieldGroup_holder.ss (100%) rename templates/{forms => }/FileField.ss (100%) rename templates/{forms => }/FormAction.ss (100%) rename templates/{forms => }/FormField.ss (100%) rename templates/{forms => }/FormField_holder.ss (100%) rename templates/{forms => }/FormField_holder_small.ss (100%) rename templates/{forms => }/GroupedDropdownField.ss (100%) rename templates/{forms => }/HTMLEditorField_UploadField.ss (100%) rename templates/{forms => }/HTMLReadonlyField.ss (100%) rename templates/{forms => }/HeaderField.ss (100%) rename templates/{forms => }/HiddenField.ss (100%) delete mode 100644 templates/Includes/GridFieldItemEditView.ss rename templates/{forms => }/LabelField.ss (100%) rename templates/{forms => }/ListboxField.ss (100%) rename templates/{forms => }/LookupField.ss (100%) rename templates/{forms => }/MemberDatetimeOptionsetField.ss (100%) rename templates/{forms => }/MemberDatetimeOptionsetField_description_date.ss (100%) rename templates/{forms => }/MemberDatetimeOptionsetField_description_time.ss (100%) rename templates/{forms => }/MoneyField.ss (100%) rename templates/{forms => }/OptionsetField.ss (100%) rename templates/{forms => }/OptionsetField_holder.ss (100%) rename templates/{forms => }/ReadonlyField.ss (100%) rename templates/{forms => }/SelectionGroup.ss (100%) rename templates/{forms => }/TabSet.ss (100%) rename templates/{forms => }/TextField.ss (100%) rename templates/{forms => }/TextareaField.ss (100%) rename templates/{forms => }/ToggleCompositeField.ss (100%) rename templates/{forms => }/TreeDropdownField.ss (100%) rename templates/{forms => }/UploadField.ss (100%) diff --git a/Security/Member.php b/Security/Member.php index b2f149b63..6ae597b13 100644 --- a/Security/Member.php +++ b/Security/Member.php @@ -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(); diff --git a/admin/code/LeftAndMain.php b/admin/code/LeftAndMain.php index 6b10f7984..ff3e41603 100644 --- a/admin/code/LeftAndMain.php +++ b/admin/code/LeftAndMain.php @@ -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() { diff --git a/admin/code/SecurityAdmin.php b/admin/code/SecurityAdmin.php index 06b39a3d6..8b4d4c688 100755 --- a/admin/code/SecurityAdmin.php +++ b/admin/code/SecurityAdmin.php @@ -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( diff --git a/admin/themes/cms-forms/templates/forms/CMSTabSet.ss b/admin/themes/cms-forms/templates/CMSTabSet.ss similarity index 100% rename from admin/themes/cms-forms/templates/forms/CMSTabSet.ss rename to admin/themes/cms-forms/templates/CMSTabSet.ss diff --git a/admin/themes/cms-forms/templates/forms/CheckboxField_holder.ss b/admin/themes/cms-forms/templates/CheckboxField_holder.ss similarity index 100% rename from admin/themes/cms-forms/templates/forms/CheckboxField_holder.ss rename to admin/themes/cms-forms/templates/CheckboxField_holder.ss diff --git a/admin/themes/cms-forms/templates/forms/CheckboxField_holder_small.ss b/admin/themes/cms-forms/templates/CheckboxField_holder_small.ss similarity index 100% rename from admin/themes/cms-forms/templates/forms/CheckboxField_holder_small.ss rename to admin/themes/cms-forms/templates/CheckboxField_holder_small.ss diff --git a/admin/themes/cms-forms/templates/forms/CheckboxSetField.ss b/admin/themes/cms-forms/templates/CheckboxSetField.ss similarity index 100% rename from admin/themes/cms-forms/templates/forms/CheckboxSetField.ss rename to admin/themes/cms-forms/templates/CheckboxSetField.ss diff --git a/admin/themes/cms-forms/templates/forms/CompositeField.ss b/admin/themes/cms-forms/templates/CompositeField.ss similarity index 100% rename from admin/themes/cms-forms/templates/forms/CompositeField.ss rename to admin/themes/cms-forms/templates/CompositeField.ss diff --git a/admin/themes/cms-forms/templates/forms/CompositeField_holder.ss b/admin/themes/cms-forms/templates/CompositeField_holder.ss similarity index 100% rename from admin/themes/cms-forms/templates/forms/CompositeField_holder.ss rename to admin/themes/cms-forms/templates/CompositeField_holder.ss diff --git a/admin/themes/cms-forms/templates/forms/CompositeField_holder_small.ss b/admin/themes/cms-forms/templates/CompositeField_holder_small.ss similarity index 100% rename from admin/themes/cms-forms/templates/forms/CompositeField_holder_small.ss rename to admin/themes/cms-forms/templates/CompositeField_holder_small.ss diff --git a/admin/themes/cms-forms/templates/forms/CreditCardField.ss b/admin/themes/cms-forms/templates/CreditCardField.ss similarity index 100% rename from admin/themes/cms-forms/templates/forms/CreditCardField.ss rename to admin/themes/cms-forms/templates/CreditCardField.ss diff --git a/admin/themes/cms-forms/templates/forms/DatetimeField.ss b/admin/themes/cms-forms/templates/DatetimeField.ss similarity index 100% rename from admin/themes/cms-forms/templates/forms/DatetimeField.ss rename to admin/themes/cms-forms/templates/DatetimeField.ss diff --git a/admin/themes/cms-forms/templates/forms/DatetimeField_holder.ss b/admin/themes/cms-forms/templates/DatetimeField_holder.ss similarity index 100% rename from admin/themes/cms-forms/templates/forms/DatetimeField_holder.ss rename to admin/themes/cms-forms/templates/DatetimeField_holder.ss diff --git a/admin/themes/cms-forms/templates/forms/FieldGroup.ss b/admin/themes/cms-forms/templates/FieldGroup.ss similarity index 100% rename from admin/themes/cms-forms/templates/forms/FieldGroup.ss rename to admin/themes/cms-forms/templates/FieldGroup.ss diff --git a/admin/themes/cms-forms/templates/forms/FieldGroup_DefaultFieldHolder.ss b/admin/themes/cms-forms/templates/FieldGroup_DefaultFieldHolder.ss similarity index 100% rename from admin/themes/cms-forms/templates/forms/FieldGroup_DefaultFieldHolder.ss rename to admin/themes/cms-forms/templates/FieldGroup_DefaultFieldHolder.ss diff --git a/admin/themes/cms-forms/templates/forms/FieldGroup_holder.ss b/admin/themes/cms-forms/templates/FieldGroup_holder.ss similarity index 100% rename from admin/themes/cms-forms/templates/forms/FieldGroup_holder.ss rename to admin/themes/cms-forms/templates/FieldGroup_holder.ss diff --git a/admin/themes/cms-forms/templates/forms/FormAction_holder_small.ss b/admin/themes/cms-forms/templates/FormAction_holder_small.ss similarity index 100% rename from admin/themes/cms-forms/templates/forms/FormAction_holder_small.ss rename to admin/themes/cms-forms/templates/FormAction_holder_small.ss diff --git a/admin/themes/cms-forms/templates/forms/FormField.ss b/admin/themes/cms-forms/templates/FormField.ss similarity index 100% rename from admin/themes/cms-forms/templates/forms/FormField.ss rename to admin/themes/cms-forms/templates/FormField.ss diff --git a/admin/themes/cms-forms/templates/forms/FormField_holder.ss b/admin/themes/cms-forms/templates/FormField_holder.ss similarity index 100% rename from admin/themes/cms-forms/templates/forms/FormField_holder.ss rename to admin/themes/cms-forms/templates/FormField_holder.ss diff --git a/admin/themes/cms-forms/templates/forms/FormField_holder_small.ss b/admin/themes/cms-forms/templates/FormField_holder_small.ss similarity index 100% rename from admin/themes/cms-forms/templates/forms/FormField_holder_small.ss rename to admin/themes/cms-forms/templates/FormField_holder_small.ss diff --git a/admin/themes/cms-forms/templates/forms/LabelField.ss b/admin/themes/cms-forms/templates/LabelField.ss similarity index 100% rename from admin/themes/cms-forms/templates/forms/LabelField.ss rename to admin/themes/cms-forms/templates/LabelField.ss diff --git a/admin/themes/cms-forms/templates/forms/LookupField.ss b/admin/themes/cms-forms/templates/LookupField.ss similarity index 100% rename from admin/themes/cms-forms/templates/forms/LookupField.ss rename to admin/themes/cms-forms/templates/LookupField.ss diff --git a/admin/themes/cms-forms/templates/forms/MemberDatetimeOptionsetField.ss b/admin/themes/cms-forms/templates/MemberDatetimeOptionsetField.ss similarity index 100% rename from admin/themes/cms-forms/templates/forms/MemberDatetimeOptionsetField.ss rename to admin/themes/cms-forms/templates/MemberDatetimeOptionsetField.ss diff --git a/admin/themes/cms-forms/templates/forms/MoneyField.ss b/admin/themes/cms-forms/templates/MoneyField.ss similarity index 100% rename from admin/themes/cms-forms/templates/forms/MoneyField.ss rename to admin/themes/cms-forms/templates/MoneyField.ss diff --git a/admin/themes/cms-forms/templates/forms/OptionsetField.ss b/admin/themes/cms-forms/templates/OptionsetField.ss similarity index 100% rename from admin/themes/cms-forms/templates/forms/OptionsetField.ss rename to admin/themes/cms-forms/templates/OptionsetField.ss diff --git a/admin/themes/cms-forms/templates/forms/OptionsetField_holder.ss b/admin/themes/cms-forms/templates/OptionsetField_holder.ss similarity index 100% rename from admin/themes/cms-forms/templates/forms/OptionsetField_holder.ss rename to admin/themes/cms-forms/templates/OptionsetField_holder.ss diff --git a/admin/themes/cms-forms/templates/forms/PhoneNumberField.ss b/admin/themes/cms-forms/templates/PhoneNumberField.ss similarity index 100% rename from admin/themes/cms-forms/templates/forms/PhoneNumberField.ss rename to admin/themes/cms-forms/templates/PhoneNumberField.ss diff --git a/admin/themes/cms-forms/templates/forms/ReadonlyField.ss b/admin/themes/cms-forms/templates/ReadonlyField.ss similarity index 100% rename from admin/themes/cms-forms/templates/forms/ReadonlyField.ss rename to admin/themes/cms-forms/templates/ReadonlyField.ss diff --git a/admin/themes/cms-forms/templates/forms/SelectionGroup.ss b/admin/themes/cms-forms/templates/SelectionGroup.ss similarity index 100% rename from admin/themes/cms-forms/templates/forms/SelectionGroup.ss rename to admin/themes/cms-forms/templates/SelectionGroup.ss diff --git a/api/RSSFeed.php b/api/RSSFeed.php index f3023eb4a..294647426 100644 --- a/api/RSSFeed.php +++ b/api/RSSFeed.php @@ -1,12 +1,10 @@ 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; + } } /** diff --git a/core/ClassInfo.php b/core/ClassInfo.php index aa923a559..902211172 100644 --- a/core/ClassInfo.php +++ b/core/ClassInfo.php @@ -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 { * ) * * - * @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); diff --git a/docs/en/02_Developer_Guides/01_Templates/01_Syntax.md b/docs/en/02_Developer_Guides/01_Templates/01_Syntax.md index 36c7a21ed..2062878f8 100644 --- a/docs/en/02_Developer_Guides/01_Templates/01_Syntax.md +++ b/docs/en/02_Developer_Guides/01_Templates/01_Syntax.md @@ -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. diff --git a/docs/en/04_Changelogs/4.0.0.md b/docs/en/04_Changelogs/4.0.0.md index ed378305d..955ff7fd5 100644 --- a/docs/en/04_Changelogs/4.0.0.md +++ b/docs/en/04_Changelogs/4.0.0.md @@ -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 diff --git a/email/Email.php b/email/Email.php index 807eff6b9..25063f12c 100644 --- a/email/Email.php +++ b/email/Email.php @@ -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); } diff --git a/forms/Form.php b/forms/Form.php index 06bde80c7..462f92ae9 100644 --- a/forms/Form.php +++ b/forms/Form.php @@ -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() { diff --git a/forms/FormField.php b/forms/FormField.php index 3b18a2fdc..944d25c21 100644 --- a/forms/FormField.php +++ b/forms/FormField.php @@ -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; } /** diff --git a/forms/gridfield/GridFieldAddExistingAutocompleter.php b/forms/gridfield/GridFieldAddExistingAutocompleter.php index ebda46f2b..03bd78901 100644 --- a/forms/gridfield/GridFieldAddExistingAutocompleter.php +++ b/forms/gridfield/GridFieldAddExistingAutocompleter.php @@ -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; diff --git a/forms/gridfield/GridFieldAddNewButton.php b/forms/gridfield/GridFieldAddNewButton.php index ca83deb05..740f9d6ef 100644 --- a/forms/gridfield/GridFieldAddNewButton.php +++ b/forms/gridfield/GridFieldAddNewButton.php @@ -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), ); } diff --git a/forms/gridfield/GridFieldButtonRow.php b/forms/gridfield/GridFieldButtonRow.php index 58feae9b4..141d6555d 100644 --- a/forms/gridfield/GridFieldButtonRow.php +++ b/forms/gridfield/GridFieldButtonRow.php @@ -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) ); } } diff --git a/forms/gridfield/GridFieldDetailForm.php b/forms/gridfield/GridFieldDetailForm.php index 3dc46c2ec..2aeaf25a8 100644 --- a/forms/gridfield/GridFieldDetailForm.php +++ b/forms/gridfield/GridFieldDetailForm.php @@ -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 */ diff --git a/forms/gridfield/GridFieldEditButton.php b/forms/gridfield/GridFieldEditButton.php index f8178ccd5..6ecfcde78 100644 --- a/forms/gridfield/GridFieldEditButton.php +++ b/forms/gridfield/GridFieldEditButton.php @@ -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); } /** diff --git a/forms/gridfield/GridFieldFilterHeader.php b/forms/gridfield/GridFieldFilterHeader.php index d6086e2e7..462761b8d 100755 --- a/forms/gridfield/GridFieldFilterHeader.php +++ b/forms/gridfield/GridFieldFilterHeader.php @@ -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), ); } } diff --git a/forms/gridfield/GridFieldFooter.php b/forms/gridfield/GridFieldFooter.php index e360ded96..572be8e95 100644 --- a/forms/gridfield/GridFieldFooter.php +++ b/forms/gridfield/GridFieldFooter.php @@ -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()) ) diff --git a/forms/gridfield/GridFieldLevelup.php b/forms/gridfield/GridFieldLevelup.php index 4894f1cbe..d83e395ac 100644 --- a/forms/gridfield/GridFieldLevelup.php +++ b/forms/gridfield/GridFieldLevelup.php @@ -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('', $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) { diff --git a/forms/gridfield/GridFieldPageCount.php b/forms/gridfield/GridFieldPageCount.php index 380dc2490..26a305b33 100755 --- a/forms/gridfield/GridFieldPageCount.php +++ b/forms/gridfield/GridFieldPageCount.php @@ -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; } } diff --git a/forms/gridfield/GridFieldPaginator.php b/forms/gridfield/GridFieldPaginator.php index c3efe381b..9f1b89d39 100755 --- a/forms/gridfield/GridFieldPaginator.php +++ b/forms/gridfield/GridFieldPaginator.php @@ -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; diff --git a/forms/gridfield/GridFieldPrintButton.php b/forms/gridfield/GridFieldPrintButton.php index 0b0ef58d2..ba2a741c8 100644 --- a/forms/gridfield/GridFieldPrintButton.php +++ b/forms/gridfield/GridFieldPrintButton.php @@ -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; } /** diff --git a/forms/gridfield/GridFieldSortableHeader.php b/forms/gridfield/GridFieldSortableHeader.php index 1078104dd..b8d4a6fa2 100644 --- a/forms/gridfield/GridFieldSortableHeader.php +++ b/forms/gridfield/GridFieldSortableHeader.php @@ -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), ); } diff --git a/forms/gridfield/GridFieldToolbarHeader.php b/forms/gridfield/GridFieldToolbarHeader.php index 3f8296d98..d8a5586ab 100644 --- a/forms/gridfield/GridFieldToolbarHeader.php +++ b/forms/gridfield/GridFieldToolbarHeader.php @@ -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) ); } } diff --git a/forms/gridfield/GridFieldViewButton.php b/forms/gridfield/GridFieldViewButton.php index f4494f6b3..eb2e1a437 100644 --- a/forms/gridfield/GridFieldViewButton.php +++ b/forms/gridfield/GridFieldViewButton.php @@ -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) { diff --git a/templates/forms/AssetField.ss b/templates/AssetField.ss similarity index 100% rename from templates/forms/AssetField.ss rename to templates/AssetField.ss diff --git a/templates/forms/AssetUploadField.ss b/templates/AssetUploadField.ss similarity index 100% rename from templates/forms/AssetUploadField.ss rename to templates/AssetUploadField.ss diff --git a/templates/forms/CheckboxField.ss b/templates/CheckboxField.ss similarity index 100% rename from templates/forms/CheckboxField.ss rename to templates/CheckboxField.ss diff --git a/templates/forms/CheckboxField_holder.ss b/templates/CheckboxField_holder.ss similarity index 100% rename from templates/forms/CheckboxField_holder.ss rename to templates/CheckboxField_holder.ss diff --git a/templates/forms/CheckboxField_holder_small.ss b/templates/CheckboxField_holder_small.ss similarity index 100% rename from templates/forms/CheckboxField_holder_small.ss rename to templates/CheckboxField_holder_small.ss diff --git a/templates/forms/CheckboxSetField.ss b/templates/CheckboxSetField.ss similarity index 100% rename from templates/forms/CheckboxSetField.ss rename to templates/CheckboxSetField.ss diff --git a/templates/forms/CompositeField.ss b/templates/CompositeField.ss similarity index 100% rename from templates/forms/CompositeField.ss rename to templates/CompositeField.ss diff --git a/templates/forms/CompositeField_holder.ss b/templates/CompositeField_holder.ss similarity index 100% rename from templates/forms/CompositeField_holder.ss rename to templates/CompositeField_holder.ss diff --git a/templates/forms/CompositeField_holder_small.ss b/templates/CompositeField_holder_small.ss similarity index 100% rename from templates/forms/CompositeField_holder_small.ss rename to templates/CompositeField_holder_small.ss diff --git a/templates/forms/CreditCardField.ss b/templates/CreditCardField.ss similarity index 100% rename from templates/forms/CreditCardField.ss rename to templates/CreditCardField.ss diff --git a/templates/forms/DatetimeField.ss b/templates/DatetimeField.ss similarity index 100% rename from templates/forms/DatetimeField.ss rename to templates/DatetimeField.ss diff --git a/templates/forms/DropdownField.ss b/templates/DropdownField.ss similarity index 100% rename from templates/forms/DropdownField.ss rename to templates/DropdownField.ss diff --git a/templates/forms/FieldGroup.ss b/templates/FieldGroup.ss similarity index 100% rename from templates/forms/FieldGroup.ss rename to templates/FieldGroup.ss diff --git a/templates/forms/FieldGroup_DefaultFieldHolder.ss b/templates/FieldGroup_DefaultFieldHolder.ss similarity index 100% rename from templates/forms/FieldGroup_DefaultFieldHolder.ss rename to templates/FieldGroup_DefaultFieldHolder.ss diff --git a/templates/forms/FieldGroup_holder.ss b/templates/FieldGroup_holder.ss similarity index 100% rename from templates/forms/FieldGroup_holder.ss rename to templates/FieldGroup_holder.ss diff --git a/templates/forms/FileField.ss b/templates/FileField.ss similarity index 100% rename from templates/forms/FileField.ss rename to templates/FileField.ss diff --git a/templates/forms/FormAction.ss b/templates/FormAction.ss similarity index 100% rename from templates/forms/FormAction.ss rename to templates/FormAction.ss diff --git a/templates/forms/FormField.ss b/templates/FormField.ss similarity index 100% rename from templates/forms/FormField.ss rename to templates/FormField.ss diff --git a/templates/forms/FormField_holder.ss b/templates/FormField_holder.ss similarity index 100% rename from templates/forms/FormField_holder.ss rename to templates/FormField_holder.ss diff --git a/templates/forms/FormField_holder_small.ss b/templates/FormField_holder_small.ss similarity index 100% rename from templates/forms/FormField_holder_small.ss rename to templates/FormField_holder_small.ss diff --git a/templates/forms/GroupedDropdownField.ss b/templates/GroupedDropdownField.ss similarity index 100% rename from templates/forms/GroupedDropdownField.ss rename to templates/GroupedDropdownField.ss diff --git a/templates/forms/HTMLEditorField_UploadField.ss b/templates/HTMLEditorField_UploadField.ss similarity index 100% rename from templates/forms/HTMLEditorField_UploadField.ss rename to templates/HTMLEditorField_UploadField.ss diff --git a/templates/forms/HTMLReadonlyField.ss b/templates/HTMLReadonlyField.ss similarity index 100% rename from templates/forms/HTMLReadonlyField.ss rename to templates/HTMLReadonlyField.ss diff --git a/templates/forms/HeaderField.ss b/templates/HeaderField.ss similarity index 100% rename from templates/forms/HeaderField.ss rename to templates/HeaderField.ss diff --git a/templates/forms/HiddenField.ss b/templates/HiddenField.ss similarity index 100% rename from templates/forms/HiddenField.ss rename to templates/HiddenField.ss diff --git a/templates/Includes/GridFieldItemEditView.ss b/templates/Includes/GridFieldItemEditView.ss deleted file mode 100644 index 4e8a1b99b..000000000 --- a/templates/Includes/GridFieldItemEditView.ss +++ /dev/null @@ -1,5 +0,0 @@ -<% if Backlink %> - <%t GridFieldItemEditView.Go_back 'Go back' %> -<% end_if %> - -$ItemEditForm diff --git a/templates/forms/LabelField.ss b/templates/LabelField.ss similarity index 100% rename from templates/forms/LabelField.ss rename to templates/LabelField.ss diff --git a/templates/forms/ListboxField.ss b/templates/ListboxField.ss similarity index 100% rename from templates/forms/ListboxField.ss rename to templates/ListboxField.ss diff --git a/templates/forms/LookupField.ss b/templates/LookupField.ss similarity index 100% rename from templates/forms/LookupField.ss rename to templates/LookupField.ss diff --git a/templates/forms/MemberDatetimeOptionsetField.ss b/templates/MemberDatetimeOptionsetField.ss similarity index 100% rename from templates/forms/MemberDatetimeOptionsetField.ss rename to templates/MemberDatetimeOptionsetField.ss diff --git a/templates/forms/MemberDatetimeOptionsetField_description_date.ss b/templates/MemberDatetimeOptionsetField_description_date.ss similarity index 100% rename from templates/forms/MemberDatetimeOptionsetField_description_date.ss rename to templates/MemberDatetimeOptionsetField_description_date.ss diff --git a/templates/forms/MemberDatetimeOptionsetField_description_time.ss b/templates/MemberDatetimeOptionsetField_description_time.ss similarity index 100% rename from templates/forms/MemberDatetimeOptionsetField_description_time.ss rename to templates/MemberDatetimeOptionsetField_description_time.ss diff --git a/templates/forms/MoneyField.ss b/templates/MoneyField.ss similarity index 100% rename from templates/forms/MoneyField.ss rename to templates/MoneyField.ss diff --git a/templates/forms/OptionsetField.ss b/templates/OptionsetField.ss similarity index 100% rename from templates/forms/OptionsetField.ss rename to templates/OptionsetField.ss diff --git a/templates/forms/OptionsetField_holder.ss b/templates/OptionsetField_holder.ss similarity index 100% rename from templates/forms/OptionsetField_holder.ss rename to templates/OptionsetField_holder.ss diff --git a/templates/forms/ReadonlyField.ss b/templates/ReadonlyField.ss similarity index 100% rename from templates/forms/ReadonlyField.ss rename to templates/ReadonlyField.ss diff --git a/templates/forms/SelectionGroup.ss b/templates/SelectionGroup.ss similarity index 100% rename from templates/forms/SelectionGroup.ss rename to templates/SelectionGroup.ss diff --git a/templates/forms/TabSet.ss b/templates/TabSet.ss similarity index 100% rename from templates/forms/TabSet.ss rename to templates/TabSet.ss diff --git a/templates/forms/TextField.ss b/templates/TextField.ss similarity index 100% rename from templates/forms/TextField.ss rename to templates/TextField.ss diff --git a/templates/forms/TextareaField.ss b/templates/TextareaField.ss similarity index 100% rename from templates/forms/TextareaField.ss rename to templates/TextareaField.ss diff --git a/templates/forms/ToggleCompositeField.ss b/templates/ToggleCompositeField.ss similarity index 100% rename from templates/forms/ToggleCompositeField.ss rename to templates/ToggleCompositeField.ss diff --git a/templates/forms/TreeDropdownField.ss b/templates/TreeDropdownField.ss similarity index 100% rename from templates/forms/TreeDropdownField.ss rename to templates/TreeDropdownField.ss diff --git a/templates/forms/UploadField.ss b/templates/UploadField.ss similarity index 100% rename from templates/forms/UploadField.ss rename to templates/UploadField.ss diff --git a/tests/api/RSSFeedTest.php b/tests/api/RSSFeedTest.php index 2e593659f..23179ef87 100644 --- a/tests/api/RSSFeedTest.php +++ b/tests/api/RSSFeedTest.php @@ -76,7 +76,7 @@ class RSSFeedTest extends SapphireTest { $content = $rssFeed->outputToBrowser(); $this->assertContains('Test Custom Template', $content); - $rssFeed->setTemplate('RSSFeed'); + $rssFeed->setTemplate(null); $content = $rssFeed->outputToBrowser(); $this->assertNotContains('Test Custom Template', $content); } diff --git a/tests/core/manifest/ThemeResourceLoaderTest.php b/tests/core/manifest/ThemeResourceLoaderTest.php index f8df70355..ed99d82c0 100644 --- a/tests/core/manifest/ThemeResourceLoaderTest.php +++ b/tests/core/manifest/ThemeResourceLoaderTest.php @@ -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 */ diff --git a/tests/forms/MemberDatetimeOptionsetFieldTest.php b/tests/forms/MemberDatetimeOptionsetFieldTest.php index 59e5fb0b7..cefcafc66 100644 --- a/tests/forms/MemberDatetimeOptionsetFieldTest.php +++ b/tests/forms/MemberDatetimeOptionsetFieldTest.php @@ -110,7 +110,7 @@ class MemberDatetimeOptionsetFieldTest extends SapphireTest { $field->setDescription('Test description'); $this->assertEquals('Test description', $field->getDescription()); - $field->setDescriptionTemplate('forms/MemberDatetimeOptionsetField_description_time'); + $field->setDescriptionTemplate('MemberDatetimeOptionsetField_description_time'); $this->assertNotEmpty($field->getDescription()); $this->assertNotEquals('Test description', $field->getDescription()); } diff --git a/tests/view/SSViewerTest.php b/tests/view/SSViewerTest.php index c6602e861..f5ec8230e 100644 --- a/tests/view/SSViewerTest.php +++ b/tests/view/SSViewerTest.php @@ -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'); diff --git a/view/SSViewer.php b/view/SSViewer.php index 818baa4ee..377cdb0ec 100644 --- a/view/SSViewer.php +++ b/view/SSViewer.php @@ -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. * * array('MySpecificPage', 'MyPage', 'Page') * + * @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 diff --git a/view/ThemeResourceLoader.php b/view/ThemeResourceLoader.php index 2dfbe7500..0098712d7 100644 --- a/view/ThemeResourceLoader.php +++ b/view/ThemeResourceLoader.php @@ -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);