diff --git a/admin/code/LeftAndMain.php b/admin/code/LeftAndMain.php index 750153883..25189dbc0 100644 --- a/admin/code/LeftAndMain.php +++ b/admin/code/LeftAndMain.php @@ -378,29 +378,58 @@ class LeftAndMain extends Controller implements PermissionProvider { $ie = isset($_SERVER['HTTP_USER_AGENT']) ? strpos($_SERVER['HTTP_USER_AGENT'], 'MSIE') : false; if($ie) { $version = substr($_SERVER['HTTP_USER_AGENT'], $ie + 5, 3); - if($version == 7) Requirements::css(FRAMEWORK_ADMIN_DIR . '/css/ie7.css'); - else if($version == 8) Requirements::css(FRAMEWORK_ADMIN_DIR . '/css/ie8.css'); + + if($version == 7) { + Requirements::css(FRAMEWORK_ADMIN_DIR . '/css/ie7.css'); + } else if($version == 8) { + Requirements::css(FRAMEWORK_ADMIN_DIR . '/css/ie8.css'); + } } // Custom requirements $extraJs = $this->stat('extra_requirements_javascript'); - if($extraJs) foreach($extraJs as $file => $config) { - Requirements::javascript($file); + if($extraJs) { + foreach($extraJs as $file => $config) { + if(is_numeric($file)) { + $file = $config; + } + + Requirements::javascript($file); + } } + $extraCss = $this->stat('extra_requirements_css'); - if($extraCss) foreach($extraCss as $file => $config) { - Requirements::css($file, isset($config['media']) ? $config['media'] : null); + + if($extraCss) { + foreach($extraCss as $file => $config) { + if(is_numeric($file)) { + $file = $config; + $config = array(); + } + + Requirements::css($file, isset($config['media']) ? $config['media'] : null); + } } + $extraThemedCss = $this->stat('extra_requirements_themedCss'); - if($extraThemedCss) foreach ($extraThemedCss as $file => $config) { - Requirements::themedCSS($file, isset($config['media']) ? $config['media'] : null); + + if($extraThemedCss) { + foreach ($extraThemedCss as $file => $config) { + if(is_numeric($file)) { + $file = $config; + $config = array(); + } + + Requirements::themedCSS($file, isset($config['media']) ? $config['media'] : null); + } } $dummy = null; $this->extend('init', $dummy); - // The user's theme shouldn't affect the CMS, if, for example, they have replaced TableListField.ss or Form.ss. + // The user's theme shouldn't affect the CMS, if, for example, they have + // replaced TableListField.ss or Form.ss. Config::inst()->update('SSViewer', 'theme_enabled', false); } diff --git a/admin/tests/LeftAndMainTest.php b/admin/tests/LeftAndMainTest.php index 2b971a335..23ae15fb9 100644 --- a/admin/tests/LeftAndMainTest.php +++ b/admin/tests/LeftAndMainTest.php @@ -1,6 +1,7 @@ autoFollowRedirection = false; CMSMenu::populate_menu(); + + $this->backupCss = Config::inst()->get('LeftAndMain', 'extra_requirements_css'); + $this->backupJs = Config::inst()->get('LeftAndMain', 'extra_requirements_javascript'); + $this->backupCombined = Requirements::get_combined_files_enabled(); + + Config::inst()->update('LeftAndMain', 'extra_requirements_css', array( + FRAMEWORK_DIR . '/tests/assets/LeftAndMainTest.css' + )); + + Config::inst()->update('LeftAndMain', 'extra_requirements_javascript', array( + FRAMEWORK_DIR . '/tests/assets/LeftAndMainTest.js' + )); + + Requirements::set_combined_files_enabled(false); + } + + public function tearDown() { + parent::tearDown(); + + Config::inst()->update('LeftAndMain', 'extra_requirements_css', $this->backupCss); + Config::inst()->update('LeftAndMain', 'extra_requirements_javascript', $this->backupJs); + + Requirements::set_combined_files_enabled($this->backupCombined); } + + public function testExtraCssAndJavascript() { + $admin = $this->objFromFixture('Member', 'admin'); + $this->session()->inst_set('loggedInAs', $admin->ID); + $response = $this->get('LeftAndMainTest_Controller'); + + $this->assertRegExp('/tests\/assets\/LeftAndMainTest.css/i', $response->getBody(), "body should contain custom css"); + $this->assertRegExp('/tests\/assets\/LeftAndMainTest.js/i', $response->getBody(), "body should contain custom js"); + } + /** * Note: This test would typically rely on SiteTree and CMSMain, but is mocked by * LeftAndMain_Controller and LeftAndMain_Object here to remove this dependency. @@ -158,15 +194,22 @@ class LeftAndMainTest extends FunctionalTest { $this->session()->inst_set('loggedInAs', null); } - } +/** + * @package framework + * @subpackage tests + */ class LeftAndMainTest_Controller extends LeftAndMain implements TestOnly { protected $template = 'BlankPage'; private static $tree_class = 'LeftAndMainTest_Object'; } +/** + * @package framework + * @subpackage tests + */ class LeftAndMainTest_Object extends DataObject implements TestOnly { private static $db = array( diff --git a/docs/en/howto/extend-cms-interface.md b/docs/en/howto/extend-cms-interface.md index 5460583d6..9001b971f 100644 --- a/docs/en/howto/extend-cms-interface.md +++ b/docs/en/howto/extend-cms-interface.md @@ -2,28 +2,31 @@ ## Introduction ## -The CMS interface works just like any other part of your website: It consists of PHP controllers, -templates, CSS stylesheets and JavaScript. Because it uses the same base elements, -it is relatively easy to extend. -As an example, we're going to add a permanent "bookmarks" bar to popular pages at the bottom of the CMS. -A page can be bookmarked by a CMS author through a simple checkbox. +The CMS interface works just like any other part of your website: It consists of +PHP controllers, templates, CSS stylesheets and JavaScript. Because it uses the +same base elements, it is relatively easy to extend. + +As an example, we're going to add a permanent "bookmarks" bar to popular pages +at the bottom of the CMS. A page can be bookmarked by a CMS author through a +simple checkbox. For a deeper introduction to the inner workings of the CMS, please refer to our guide on [CMS Architecture](../reference/cms-architecture). ## Overload a CMS template ## -If you place a template with an identical name into your application template directory -(usually `mysite/templates/`), it'll take priority over the built-in one. +If you place a template with an identical name into your application template +directory (usually `mysite/templates/`), it'll take priority over the built-in +one. CMS templates are inherited based on their controllers, similar to subclasses of the common `Page` object (a new PHP class `MyPage` will look for a `MyPage.ss` template). We can use this to create a different base template with `LeftAndMain.ss` (which corresponds to the `LeftAndMain` PHP controller class). -Copy the template markup of the base implementation at `framework/admin/templates/LeftAndMain.ss` into -`mysite/templates/LeftAndMain.ss`. It will automatically be picked up by the CMS logic. Add a new section after the -`$Content` tag: +Copy the template markup of the base implementation at `framework/admin/templates/LeftAndMain.ss` +into `mysite/templates/LeftAndMain.ss`. It will automatically be picked up by +the CMS logic. Add a new section after the `$Content` tag: :::ss ... @@ -39,21 +42,24 @@ Copy the template markup of the base implementation at `framework/admin/template ... -Refresh the CMS interface with `admin/?flush=all`, and you should see the new bottom bar with some hardcoded links. -We'll make these dynamic further down. +Refresh the CMS interface with `admin/?flush=all`, and you should see the new +bottom bar with some hardcoded links. We'll make these dynamic further down. -You might have noticed that we didn't write any JavaScript to add our layout manager. -The important piece of information is the `south` class in our new `
` structure, -plus the height value in our CSS. It instructs the existing parent layout how to render the element. -This layout manager ([jLayout](http://www.bramstein.com/projects/jlayout/)) -allows us to build complex layouts with minimal JavaScript configuration. +You might have noticed that we didn't write any JavaScript to add our layout +manager. The important piece of information is the `south` class in our new +`
` structure, plus the height value in our CSS. It instructs the existing +parent layout how to render the element. This layout manager +([jLayout](http://www.bramstein.com/projects/jlayout/)) allows us to build +complex layouts with minimal JavaScript configuration. -See [layout reference](../reference/layout) for more specific information on CMS layouting. +See [layout reference](../reference/layout) for more specific information on +CMS layouting. ## Include custom CSS in the CMS -In order to show the links in one line, we'll add some CSS, and get it to load with the CMS interface. -Paste the following content into a new file called `mysite/css/BookmarkedPages.css`: +In order to show the links in one line, we'll add some CSS, and get it to load +with the CMS interface. Paste the following content into a new file called +`mysite/css/BookmarkedPages.css`: :::css .cms-bottom-bar {height: 20px; padding: 5px; background: #C6D7DF;} @@ -67,18 +73,23 @@ Load the new CSS file into the CMS, by setting the `LeftAndMain.extra_requiremen :::yml LeftAndMain: extra_requirements_css: - mysite/css/BookmarkedPages.css: + - mysite/css/BookmarkedPages.css: ## Create a "bookmark" flag on pages ## -Now we'll define which pages are actually bookmarked, a flag that is stored in the database. -For this we need to decorate the page record with a `DataExtension`. -Create a new file called `mysite/code/BookmarkedPageExtension.php` and insert the following code. +Now we'll define which pages are actually bookmarked, a flag that is stored in +the database. For this we need to decorate the page record with a +`DataExtension`. Create a new file called `mysite/code/BookmarkedPageExtension.php` +and insert the following code. :::php 'Boolean'); + + private static $db = array( + 'IsBookmarked' => 'Boolean' + ); public function updateCMSFields(FieldList $fields) { $fields->addFieldToTab('Root.Main', @@ -100,14 +111,17 @@ Refresh the CMS, open a page for editing and you should see the new checkbox. ## Retrieve the list of bookmarks from the database One piece in the puzzle is still missing: How do we get the list of bookmarked -pages from the datbase into the template we've already created (with hardcoded links)? -Again, we extend a core class: The main CMS controller called `LeftAndMain`. +pages from the database into the template we've already created (with hardcoded +links)? Again, we extend a core class: The main CMS controller called +`LeftAndMain`. Add the following code to a new file `mysite/code/BookmarkedLeftAndMainExtension.php`; :::php filter("IsBookmarked", 1); } @@ -133,39 +147,51 @@ and replace it with the following: ## Extending the CMS actions -CMS actions follow a principle similar to the CMS fields: they are built in the backend with the help of `FormFields` -and `FormActions`, and the frontend is responsible for applying a consistent styling. +CMS actions follow a principle similar to the CMS fields: they are built in the +backend with the help of `FormFields` and `FormActions`, and the frontend is +responsible for applying a consistent styling. The following conventions apply: -* New actions can be added by redefining `getCMSActions`, or adding an extension with `updateCMSActions`. -* It is required the actions are contained in a `FieldSet` (`getCMSActions` returns this already). -* Standalone buttons are created by adding a top-level `FormAction` (no such button is added by default). -* Button groups are created by adding a top-level `CompositeField` with `FormActions` in it. +* New actions can be added by redefining `getCMSActions`, or adding an extension +with `updateCMSActions`. +* It is required the actions are contained in a `FieldSet` (`getCMSActions` +returns this already). +* Standalone buttons are created by adding a top-level `FormAction` (no such +button is added by default). +* Button groups are created by adding a top-level `CompositeField` with +`FormActions` in it. * A `MajorActions` button group is already provided as a default. -* Drop ups with additional actions that appear as links are created via a `TabSet` and `Tabs` with `FormActions` inside. -* A `ActionMenus.MoreOptions` tab is already provided as a default and contains some minor actions. -* You can override the actions completely by providing your own `getAllCMSFields`. +* Drop ups with additional actions that appear as links are created via a +`TabSet` and `Tabs` with `FormActions` inside. +* A `ActionMenus.MoreOptions` tab is already provided as a default and contains +some minor actions. +* You can override the actions completely by providing your own +`getAllCMSFields`. Let's walk through a couple of examples of adding new CMS actions in `getCMSActions`. -First of all we can add a regular standalone button anywhere in the set. Here we are inserting it in the front of all -other actions. We could also add a button group (`CompositeField`) in a similar fashion. +First of all we can add a regular standalone button anywhere in the set. Here +we are inserting it in the front of all other actions. We could also add a +button group (`CompositeField`) in a similar fashion. :::php $fields->unshift(FormAction::create('normal', 'Normal button')); -We can affect the existing button group by manipulating the `CompositeField` already present in the `FieldList`. +We can affect the existing button group by manipulating the `CompositeField` +already present in the `FieldList`. :::php $fields->fieldByName('MajorActions')->push(FormAction::create('grouped', 'New group button')); -Another option is adding actions into the drop-up - best place for placing infrequently used minor actions. +Another option is adding actions into the drop-up - best place for placing +infrequently used minor actions. :::php $fields->addFieldToTab('ActionMenus.MoreOptions', FormAction::create('minor', 'Minor action')); -We can also easily create new drop-up menus by defining new tabs within the `TabSet`. +We can also easily create new drop-up menus by defining new tabs within the +`TabSet`. :::php $fields->addFieldToTab('ActionMenus.MyDropUp', FormAction::create('minor', 'Minor action in a new drop-up')); @@ -174,15 +200,18 @@ We can also easily create new drop-up menus by defining new tabs within the `Tab Empty tabs will be automatically removed from the `FieldList` to prevent clutter.
-New actions will need associated controller handlers to work. You can use a `LeftAndMainExtension` to provide one. Refer -to [Controller documentation](../topics/controller) for instructions on setting up handlers. +New actions will need associated controller handlers to work. You can use a +`LeftAndMainExtension` to provide one. Refer to [Controller documentation](../topics/controller) +for instructions on setting up handlers. -To make the actions more user-friendly you can also use alternating buttons as detailed in the [CMS Alternating -Button](../reference/cms-alternating-button) how-to. +To make the actions more user-friendly you can also use alternating buttons as +detailed in the [CMS Alternating Button](../reference/cms-alternating-button) +how-to. ## Summary In a few lines of code, we've customized the look and feel of the CMS. + While this example is only scratching the surface, it includes most building blocks and concepts for more complex extensions as well. diff --git a/docs/en/reference/preview.md b/docs/en/reference/preview.md index fde31481d..9a48a7111 100644 --- a/docs/en/reference/preview.md +++ b/docs/en/reference/preview.md @@ -2,31 +2,37 @@ ## Overview -With the addition of side-by-side editing, the preview has the ability to appear within the CMS window when editing -content in the _Pages_ section of the CMS. The site is rendered into an iframe. It will update itself whenever the -content is saved, and relevant pages will be loaded for editing when the user navigates around in the preview. +With the addition of side-by-side editing, the preview has the ability to appear +within the CMS window when editing content in the _Pages_ section of the CMS. +The site is rendered into an iframe. It will update itself whenever the content +is saved, and relevant pages will be loaded for editing when the user navigates +around in the preview. -The root element for preview is `.cms-preview` which maintains the internal states neccessary for rendering within the -entwine properties. It provides function calls for transitioning between these states and has the ability to update the -appearance of the option selectors. +The root element for preview is `.cms-preview` which maintains the internal +states necessary for rendering within the entwine properties. It provides +function calls for transitioning between these states and has the ability to +update the appearance of the option selectors. -In terms of backend support, it relies on `SilverStripeNavigator` to be rendered into the `.cms-edit-form`. -_LeftAndMain_ will automatically take care of generating it as long as the `*_SilverStripeNavigator` template is found - -first segment has to match current _LeftAndMain_-derived class (e.g. `LeftAndMain_SilverStripeNavigator`). +In terms of backend support, it relies on `SilverStripeNavigator` to be rendered +into the `.cms-edit-form`. _LeftAndMain_ will automatically take care of +generating it as long as the `*_SilverStripeNavigator` template is found - +first segment has to match current _LeftAndMain_-derived class (e.g. +`LeftAndMain_SilverStripeNavigator`). We use `ss.preview` entwine namespace for all preview-related entwines.
-Caveat: `SilverStripeNavigator` and `CMSPreviewable` interface currently only support SiteTree objects that are -_Versioned_. They are not general enough for using on any other DataObject. That pretty much limits the extendability -of the feature. +Caveat: `SilverStripeNavigator` and `CMSPreviewable` interface currently only +support SiteTree objects that are _Versioned_. They are not general enough for +using on any other DataObject. That pretty much limits the extendability of the +feature.
## Configuration and Defaults Like most of the CMS, the preview UI is powered by -[jQuery entwine](https://github.com/hafriedlander/jquery.entwine). -This means its defaults are configured through JavaScript, by setting entwine properties. +[jQuery entwine](https://github.com/hafriedlander/jquery.entwine). This means +its defaults are configured through JavaScript, by setting entwine properties. In order to achieve this, create a new file `mysite/javascript/MyLeftAndMain.Preview.js`. In the following example we configure three aspects: @@ -69,7 +75,7 @@ to the `LeftAndMain.extra_requirements_javascript` [configuration value](/topics :::yml LeftAndMain extra_requirements_javascript: - mysite/javascript/MyLeftAndMain.Preview.js: + - mysite/javascript/MyLeftAndMain.Preview.js: In order to find out which configuration values are available, the source code is your best reference at the moment - have a look in `framework/admin/javascript/LeftAndMain.Preview.js`. @@ -78,30 +84,33 @@ To understand how layouts are handled in the CMS UI, have a look at the ## Enabling preview -The frontend decides on the preview being enabled or disabled based on the presnce of the `.cms-previewable` class. If -this class is not found the preview will remain hidden, and the layout will stay in the _content_ mode. +The frontend decides on the preview being enabled or disabled based on the +presence of the `.cms-previewable` class. If this class is not found the preview +will remain hidden, and the layout will stay in the _content_ mode. -If the class is found, frontend looks for the `SilverStripeNavigator` structure and moves it to the -`.cms-preview-control` panel at the bottom of the preview. This structure supplies preview options such as state -selector. +If the class is found, frontend looks for the `SilverStripeNavigator` structure +and moves it to the `.cms-preview-control` panel at the bottom of the preview. +This structure supplies preview options such as state selector. -If the navigator is not found, the preview appears in the GUI, but is shown as "blocked" - i.e. displaying the "preview -unavailable" overlay. +If the navigator is not found, the preview appears in the GUI, but is shown as +"blocked" - i.e. displaying the "preview unavailable" overlay. -The preview can be affected by calling `enablePreview` and `disablePreview`. You can check if the preview is active by -inspecting the `IsPreviewEnabled` entwine property. +The preview can be affected by calling `enablePreview` and `disablePreview`. You +can check if the preview is active by inspecting the `IsPreviewEnabled` entwine +property. ## Preview states -States are the site stages: _live_, _stage_ etc. Preview states are picked up from the `SilverStripeNavigator`. -You can invoke the state change by calling: +States are the site stages: _live_, _stage_ etc. Preview states are picked up +from the `SilverStripeNavigator`. You can invoke the state change by calling: ```js $('.cms-preview').entwine('.ss.preview').changeState('StageLink'); ``` -Note the state names come from `SilverStripeNavigatorItems` class names - thus the _Link_ in their names. This call will -also redraw the state selector to fit with the internal state. See `AllowedStates` in `.cms-preview` entwine for the +Note the state names come from `SilverStripeNavigatorItems` class names - thus +the _Link_ in their names. This call will also redraw the state selector to fit +with the internal state. See `AllowedStates` in `.cms-preview` entwine for the list of supported states. You can get the current state by calling: @@ -112,16 +121,18 @@ You can get the current state by calling: ## Preview sizes -This selector defines how the preview iframe is rendered, and try to emulate different device sizes. The options are -hardcoded. The option names map directly to CSS classes applied to the `.cms-preview` and are as follows: +This selector defines how the preview iframe is rendered, and try to emulate +different device sizes. The options are hardcoded. The option names map directly +to CSS classes applied to the `.cms-preview` and are as follows: * _auto_: responsive layout * _desktop_ * _tablet_ * _mobile_ -You can switch between different types of display sizes programmatically, which has the benefit of redrawing the -related selector and maintaining a consistent internal state: +You can switch between different types of display sizes programmatically, which +has the benefit of redrawing the related selector and maintaining a consistent +internal state: ```js $('.cms-preview').entwine('.ss.preview').changeSize('auto'); @@ -135,25 +146,27 @@ You can find out current size by calling: ## Preview modes -Preview modes map to the modes supported by the _threeColumnCompressor_ layout algorithm, see -[layout reference](../reference/layout) for more details. You can change modes by calling: +Preview modes map to the modes supported by the _threeColumnCompressor_ layout +algorithm, see [layout reference](../reference/layout) for more details. You +can change modes by calling: ```js $('.cms-preview').entwine('.ss.preview').changeMode('preview'); ``` -Currently active mode is stored on the `.cms-container` along with related internal states of the layout. You can reach -it by calling: +Currently active mode is stored on the `.cms-container` along with related +internal states of the layout. You can reach it by calling: ```js $('.cms-container').entwine('.ss').getLayoutOptions().mode; ```
-Caveat: the `.preview-mode-selector` appears twice, once in the preview and second time in the CMS actions area as -`#preview-mode-dropdown-in-cms`. This is done because the user should still have access to the mode selector even if -preview is not visible. Currently CMS Actions are a separate area to the preview option selectors, even if they try -to appear as one horizontal bar. +Caveat: the `.preview-mode-selector` appears twice, once in the preview and +second time in the CMS actions area as `#preview-mode-dropdown-in-cms`. This is +done because the user should still have access to the mode selector even if +preview is not visible. Currently CMS Actions are a separate area to the preview +option selectors, even if they try to appear as one horizontal bar.
## Preview API diff --git a/tests/assets/LeftAndMainTest.css b/tests/assets/LeftAndMainTest.css new file mode 100644 index 000000000..e69de29bb diff --git a/tests/assets/LeftAndMainTest.js b/tests/assets/LeftAndMainTest.js new file mode 100644 index 000000000..e69de29bb