From 533d5dbd4b79c307d8b6289f8c6fb28ae743a9ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thierry=20Fran=C3=A7ois?= Date: Sat, 5 Jul 2014 14:13:01 +0300 Subject: [PATCH] NEW Admin url can now be customized The default 'admin/' base URL can now be changed with Director rules --- _config/routes.yml | 2 +- admin/code/AdminRootController.php | 57 ++++++++++++++++--- admin/code/CMSMenu.php | 4 +- admin/code/LeftAndMain.php | 15 +---- admin/javascript/LeftAndMain.BatchActions.js | 8 +-- admin/templates/Includes/LeftAndMain_Menu.ss | 2 +- control/Director.php | 4 +- core/Core.php | 1 - .../05_CMS_Architecture.md | 56 ++++++++++++++---- .../How_Tos/Customise_CMS_Tree.md | 2 +- .../How_Tos/Extend_CMS_Interface.md | 6 +- docs/en/04_Changelogs/3.2.0.md | 9 +++ filesystem/File.php | 2 +- 13 files changed, 123 insertions(+), 45 deletions(-) diff --git a/_config/routes.yml b/_config/routes.yml index 77971cd7c..3242d45ee 100644 --- a/_config/routes.yml +++ b/_config/routes.yml @@ -31,4 +31,4 @@ After: --- Director: rules: - 'admin': 'AdminRootController' + 'admin': 'AdminRootController' \ No newline at end of file diff --git a/admin/code/AdminRootController.php b/admin/code/AdminRootController.php index 77a394ce6..25af0dca5 100644 --- a/admin/code/AdminRootController.php +++ b/admin/code/AdminRootController.php @@ -4,15 +4,47 @@ * @package framework * @subpackage admin */ -class AdminRootController extends Controller { +class AdminRootController extends Controller implements TemplateGlobalProvider { /** - * @var string - * @config - * The url base that all LeftAndMain derived panels will live under - * Won't automatically update the base route if you change this - that has to be done seperately + * Convenience function to return the admin route config. + * Looks for the {@link Director::$rules} for the current admin Controller. */ - private static $url_base = 'admin'; + public static function get_admin_route() { + if (Controller::has_curr()) { + $routeParams = Controller::curr()->getRequest()->routeParams(); + $adminControllerClass = isset($routeParams['Controller']) ? $routeParams['Controller'] : get_called_class(); + } + else { + $adminControllerClass = get_called_class(); + } + + $rules = Config::inst()->get('Director', 'rules'); + $adminRoute = array_search($adminControllerClass, $rules); + return $adminRoute ? $adminRoute : ''; + } + + /** + * Returns the root admin URL for the site with trailing slash + * + * @return string + * @uses get_admin_route() + */ + public static function admin_url() { + return self::get_admin_route() . '/'; + } + + /** + * Includes the adminURL JavaScript config in the ss namespace + */ + public static function include_js() { + $js = "(function(root) { + root.ss = root.ss || {}; + root.ss.config = root.ss.config || {}; + root.ss.config.adminURL = '".self::admin_url()."' + }(window));"; + Requirements::customScript($js, 'adminURLConfig'); + } /** * @var string @@ -74,11 +106,10 @@ class AdminRootController extends Controller { public function handleRequest(SS_HTTPRequest $request, DataModel $model) { // If this is the final portion of the request (i.e. the URL is just /admin), direct to the default panel if ($request->allParsed()) { - $base = $this->config()->url_base; $segment = Config::inst()->get($this->config()->default_panel, 'url_segment'); $this->response = new SS_HTTPResponse(); - $this->redirect(Controller::join_links($base, $segment)); + $this->redirect(Controller::join_links(self::admin_url(), $segment)); return $this->response; } @@ -97,4 +128,14 @@ class AdminRootController extends Controller { return $this->httpError(404, 'Not found'); } + + /** + * @return array Returns an array of strings of the method names of methods on the call that should be exposed + * as global variables in the templates. + */ + public static function get_template_global_variables() { + return array( + 'adminURL' => 'admin_url' + ); + } } diff --git a/admin/code/CMSMenu.php b/admin/code/CMSMenu.php index b3f01cd9d..3f48b62ed 100644 --- a/admin/code/CMSMenu.php +++ b/admin/code/CMSMenu.php @@ -57,8 +57,8 @@ class CMSMenu extends Object implements IteratorAggregate, i18nEntityProvider { * Return a CMSMenuItem to add the given controller to the CMSMenu */ protected static function menuitem_for_controller($controllerClass) { - $urlBase = Config::inst()->get($controllerClass, 'url_base', Config::FIRST_SET); - $urlSegment = Config::inst()->get($controllerClass, 'url_segment', Config::FIRST_SET); + $urlBase = AdminRootController::admin_url(); + $urlSegment = Config::inst()->get($controllerClass, 'url_segment', Config::FIRST_SET); $menuPriority = Config::inst()->get($controllerClass, 'menu_priority', Config::FIRST_SET); // Don't add menu items defined the old way diff --git a/admin/code/LeftAndMain.php b/admin/code/LeftAndMain.php index d15c46596..9853765b3 100644 --- a/admin/code/LeftAndMain.php +++ b/admin/code/LeftAndMain.php @@ -11,16 +11,6 @@ */ class LeftAndMain extends Controller implements PermissionProvider { - /** - * The 'base' url for CMS administration areas. - * Note that if this is changed, many javascript - * behaviours need to be updated with the correct url - * - * @config - * @var string $url_base - */ - private static $url_base = "admin"; - /** * The current url segment attached to the LeftAndMain instance * @@ -340,6 +330,7 @@ class LeftAndMain extends Controller implements PermissionProvider { if (Director::isDev()) Requirements::javascript(FRAMEWORK_ADMIN_DIR . '/javascript/leaktools.js'); HTMLEditorField::include_js(); + AdminRootController::include_js(); $leftAndMainIncludes = array_unique(array_merge( array( @@ -527,7 +518,7 @@ class LeftAndMain extends Controller implements PermissionProvider { }; $link = Controller::join_links( - $this->stat('url_base', true), + AdminRootController::admin_url(), $segment, '/', // trailing slash needed if $action is null! "$action" @@ -642,7 +633,7 @@ class LeftAndMain extends Controller implements PermissionProvider { // default menu is the one with a blank {@link url_segment} } else if(singleton($menuItem->controller)->stat('url_segment') == '') { - if($this->Link() == $this->stat('url_base').'/') { + if($this->Link() == AdminRootController::admin_url()) { $linkingmode = "current"; } diff --git a/admin/javascript/LeftAndMain.BatchActions.js b/admin/javascript/LeftAndMain.BatchActions.js index 5e4cb741e..af370a2c6 100644 --- a/admin/javascript/LeftAndMain.BatchActions.js +++ b/admin/javascript/LeftAndMain.BatchActions.js @@ -333,7 +333,7 @@ /** * Publish selected pages action */ - $('#Form_BatchActionsForm').register('admin/batchactions/publish', function(ids) { + $('#Form_BatchActionsForm').register(ss.config.adminURL+'batchactions/publish', function(ids) { var confirmed = confirm( "You have " + ids.length + " pages selected.\n\n" + "Do your really want to publish?" @@ -344,7 +344,7 @@ /** * Unpublish selected pages action */ - $('#Form_BatchActionsForm').register('admin/batchactions/unpublish', function(ids) { + $('#Form_BatchActionsForm').register(ss.config.adminURL+'batchactions/unpublish', function(ids) { var confirmed = confirm( "You have " + ids.length + " pages selected.\n\n" + "Do your really want to unpublish?" @@ -355,7 +355,7 @@ /** * Delete selected pages action */ - $('#Form_BatchActionsForm').register('admin/batchactions/delete', function(ids) { + $('#Form_BatchActionsForm').register(ss.config.adminURL+'batchactions/delete', function(ids) { var confirmed = confirm( "You have " + ids.length + " pages selected.\n\n" + "Do your really want to delete?" @@ -366,7 +366,7 @@ /** * Delete selected pages from live action */ - $('#Form_BatchActionsForm').register('admin/batchactions/deletefromlive', function(ids) { + $('#Form_BatchActionsForm').register(ss.config.adminURL+'batchactions/deletefromlive', function(ids) { var confirmed = confirm( "You have " + ids.length + " pages selected.\n\n" + "Do your really want to delete these pages from live?" diff --git a/admin/templates/Includes/LeftAndMain_Menu.ss b/admin/templates/Includes/LeftAndMain_Menu.ss index 305589e6b..97c36b079 100644 --- a/admin/templates/Includes/LeftAndMain_Menu.ss +++ b/admin/templates/Includes/LeftAndMain_Menu.ss @@ -12,7 +12,7 @@ <% with $CurrentMember %> <% _t('LeftAndMain_Menu_ss.Hello','Hi') %> - + <% if $FirstName && $Surname %>$FirstName $Surname<% else_if $FirstName %>$FirstName<% else %>$Email<% end_if %> diff --git a/control/Director.php b/control/Director.php index 865f52ce4..b8d6a0f5d 100644 --- a/control/Director.php +++ b/control/Director.php @@ -351,12 +351,14 @@ class Director implements TemplateGlobalProvider { if(isset($_REQUEST['debug'])) Debug::show($rules); foreach($rules as $pattern => $controllerOptions) { - if(is_string($controllerOptions)) { + if(is_string($controllerOptions) && $controllerOptions) { if(substr($controllerOptions,0,2) == '->') { $controllerOptions = array('Redirect' => substr($controllerOptions,2)); } else { $controllerOptions = array('Controller' => $controllerOptions); } + } else { + continue; } if(($arguments = $request->match($pattern, true)) !== false) { diff --git a/core/Core.php b/core/Core.php index e55706d86..4868de4e8 100644 --- a/core/Core.php +++ b/core/Core.php @@ -135,7 +135,6 @@ if(Director::isLive()) { */ Debug::loadErrorHandlers(); - /////////////////////////////////////////////////////////////////////////////// // HELPER FUNCTIONS diff --git a/docs/en/02_Developer_Guides/15_Customising_the_Admin_Interface/05_CMS_Architecture.md b/docs/en/02_Developer_Guides/15_Customising_the_Admin_Interface/05_CMS_Architecture.md index 11ac98f58..3bb18a749 100644 --- a/docs/en/02_Developer_Guides/15_Customising_the_Admin_Interface/05_CMS_Architecture.md +++ b/docs/en/02_Developer_Guides/15_Customising_the_Admin_Interface/05_CMS_Architecture.md @@ -15,6 +15,42 @@ with some special conventions. For a more practical-oriented approach to CMS customizations, refer to the [Howto: Extend the CMS Interface](../how_tos/extend_cms_interface) which builds +## The Admin URL + +The CMS interface can be accessed by default through the `admin/` URL. You can change this by setting your own [Director routing rule](director#routing-rules) to the `[api:AdminRootController]` and clear the old rule like in the example below. + + :::yml + --- + Name: myadmin + After: + - '#adminroutes' + --- + Director: + rule: + 'admin': '' + 'newAdmin': 'AdminRootController' + --- + +When extending the CMS or creating modules, you can take advantage of various functions that will return the configured admin URL (by default 'admin/' is returned): + +In PHP you should use: + + :::php + AdminRootController::admin_url() + +When writing templates use: + + :::ss + $AdminURL + +And in JavaScript, this is avaible through the `ss` namespace + + :::js + ss.config.adminURL + +### Multiple Admin URL and overrides + +You can also create your own classes that extend the `[api:AdminRootController]` to create multiple or custom admin areas, with a `Director.rules` for each one. ## Markup and Style Conventions @@ -257,7 +293,7 @@ in a single Ajax request. <% include CMSBreadcrumbs %>
Static content (not affected by update)
<% include MyRecordInfo %> - + Update record info @@ -285,7 +321,7 @@ On the client, you can set your preference through the `data-pjax-target` attrib on links or through the `X-Pjax` header. For firing off an Ajax request that is tracked in the browser history, use the `pjax` attribute on the state data. - $('.cms-container').loadPanel('admin/pages', null, {pjax: 'Content'}); + $('.cms-container').loadPanel(ss.config.adminURL+'pages', null, {pjax: 'Content'}); ## Loading lightweight PJAX fragments @@ -301,16 +337,16 @@ unrelated to the main flow. In this case you can use the `loadFragment` call supplied by `LeftAndMain.js`. You can trigger as many of these in parallel as you want. This will not disturb the main navigation. - $('.cms-container').loadFragment('admin/foobar/', 'Fragment1'); - $('.cms-container').loadFragment('admin/foobar/', 'Fragment2'); - $('.cms-container').loadFragment('admin/foobar/', 'Fragment3'); + $('.cms-container').loadFragment(ss.config.adminURL+'foobar/', 'Fragment1'); + $('.cms-container').loadFragment(ss.config.adminURL+'foobar/', 'Fragment2'); + $('.cms-container').loadFragment(ss.config.adminURL+'foobar/', 'Fragment3'); The ongoing requests are tracked by the PJAX fragment name (Fragment1, 2, and 3 above) - resubmission will result in the prior request for this fragment to be aborted. Other parallel requests will continue undisturbed. You can also load multiple fragments in one request, as long as they are to the same controller (i.e. URL): - $('.cms-container').loadFragment('admin/foobar/', 'Fragment2,Fragment3'); + $('.cms-container').loadFragment(ss.config.adminURL+'foobar/', 'Fragment2,Fragment3'); This counts as a separate request type from the perspective of the request tracking, so will not abort the singular `Fragment2` nor `Fragment3`. @@ -331,7 +367,7 @@ You can hook up a response handler that obtains all the details of the XHR reque Alternatively you can use the jQuery deferred API: $('.cms-container') - .loadFragment('admin/foobar/', 'Fragment1') + .loadFragment(ss.config.adminURL+'foobar/', 'Fragment1') .success(function(data, status, xhr) { // Say 'success'! alert(status); @@ -519,19 +555,19 @@ and load the HTML content into the main view. Example:
-The URL endpoints `admin/mytabs/tab1` and `admin/mytabs/tab2` +The URL endpoints `{$AdminURL}mytabs/tab1` and `{$AdminURL}mytabs/tab2` should return HTML fragments suitable for inserting into the content area, through the `PjaxResponseNegotiator` class (see above). diff --git a/docs/en/02_Developer_Guides/15_Customising_the_Admin_Interface/How_Tos/Customise_CMS_Tree.md b/docs/en/02_Developer_Guides/15_Customising_the_Admin_Interface/How_Tos/Customise_CMS_Tree.md index 46193e87e..aad83595e 100644 --- a/docs/en/02_Developer_Guides/15_Customising_the_Admin_Interface/How_Tos/Customise_CMS_Tree.md +++ b/docs/en/02_Developer_Guides/15_Customising_the_Admin_Interface/How_Tos/Customise_CMS_Tree.md @@ -26,7 +26,7 @@ code like this: ...
  •   - +     diff --git a/docs/en/02_Developer_Guides/15_Customising_the_Admin_Interface/How_Tos/Extend_CMS_Interface.md b/docs/en/02_Developer_Guides/15_Customising_the_Admin_Interface/How_Tos/Extend_CMS_Interface.md index c26fd24ef..57adcd0b0 100644 --- a/docs/en/02_Developer_Guides/15_Customising_the_Admin_Interface/How_Tos/Extend_CMS_Interface.md +++ b/docs/en/02_Developer_Guides/15_Customising_the_Admin_Interface/How_Tos/Extend_CMS_Interface.md @@ -33,10 +33,10 @@ the CMS logic. Add a new section into the ` diff --git a/docs/en/04_Changelogs/3.2.0.md b/docs/en/04_Changelogs/3.2.0.md index 8658bc785..2d7f169d1 100644 --- a/docs/en/04_Changelogs/3.2.0.md +++ b/docs/en/04_Changelogs/3.2.0.md @@ -17,6 +17,7 @@ * `Mailer` no longer calls `xml2raw` on all email subject line, and now must be passed in via plain text. * `ErrorControlChain` now supports reload on exceptions * `FormField::validate` now requires an instance of `Validator` + * Admin URL can now be configured via custom Director routing rule #### Deprecated classes/methods removed @@ -124,6 +125,14 @@ languages like JavaScript won't be able to read them. To set it back to be non-HTTP only, you need to set the `$httpOnly` argument to false when calling `Cookie::set()`. +### Admin URL can now be configured via custom Director routing rule + +The default `admin/` URL to access the CMS interface can now be changed +via a custom Director routing rule for `AdminRootController`. If your website or module +has hard coded `admin` URLs in PHP, templates or JavaScript, make sure to update those +with the appropriate function or config call. +See [CMS architecture](/developer_guides/customising_the_admin_interface/cms-architecture#the-admin-url) for language specific functions. + ### Bugfixes * Migration of code to use new parameterised framework diff --git a/filesystem/File.php b/filesystem/File.php index ba12d4014..6d8f19116 100644 --- a/filesystem/File.php +++ b/filesystem/File.php @@ -748,7 +748,7 @@ class File extends DataObject { * @todo Coupling with cms module, remove this method. */ public function DeleteLink() { - return Director::absoluteBaseURL()."admin/assets/removefile/".$this->ID; + return Director::absoluteBaseURL().AdminRootController::admin_url()."assets/removefile/".$this->ID; } public function getFilename() {