Complex classes like LeftAndMain often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use LeftAndMain, and based on these observations, apply Extract Interface, too.
| 1 | <?php |
||
| 20 | class LeftAndMain extends Controller implements PermissionProvider { |
||
| 21 | |||
| 22 | /** |
||
| 23 | * The 'base' url for CMS administration areas. |
||
| 24 | * Note that if this is changed, many javascript |
||
| 25 | * behaviours need to be updated with the correct url |
||
| 26 | * |
||
| 27 | * @config |
||
| 28 | * @var string $url_base |
||
| 29 | */ |
||
| 30 | private static $url_base = "admin"; |
||
| 31 | |||
| 32 | /** |
||
| 33 | * The current url segment attached to the LeftAndMain instance |
||
| 34 | * |
||
| 35 | * @config |
||
| 36 | * @var string |
||
| 37 | */ |
||
| 38 | private static $url_segment; |
||
| 39 | |||
| 40 | /** |
||
| 41 | * @config |
||
| 42 | * @var string |
||
| 43 | */ |
||
| 44 | private static $url_rule = '/$Action/$ID/$OtherID'; |
||
| 45 | |||
| 46 | /** |
||
| 47 | * @config |
||
| 48 | * @var string |
||
| 49 | */ |
||
| 50 | private static $menu_title; |
||
| 51 | |||
| 52 | /** |
||
| 53 | * @config |
||
| 54 | * @var string |
||
| 55 | */ |
||
| 56 | private static $menu_icon; |
||
| 57 | |||
| 58 | /** |
||
| 59 | * @config |
||
| 60 | * @var int |
||
| 61 | */ |
||
| 62 | private static $menu_priority = 0; |
||
| 63 | |||
| 64 | /** |
||
| 65 | * @config |
||
| 66 | * @var int |
||
| 67 | */ |
||
| 68 | private static $url_priority = 50; |
||
| 69 | |||
| 70 | /** |
||
| 71 | * A subclass of {@link DataObject}. |
||
| 72 | * |
||
| 73 | * Determines what is managed in this interface, through |
||
| 74 | * {@link getEditForm()} and other logic. |
||
| 75 | * |
||
| 76 | * @config |
||
| 77 | * @var string |
||
| 78 | */ |
||
| 79 | private static $tree_class = null; |
||
| 80 | |||
| 81 | /** |
||
| 82 | * The url used for the link in the Help tab in the backend |
||
| 83 | * |
||
| 84 | * @config |
||
| 85 | * @var string |
||
| 86 | */ |
||
| 87 | private static $help_link = '//userhelp.silverstripe.org/framework/en/3.3'; |
||
| 88 | |||
| 89 | /** |
||
| 90 | * @var array |
||
| 91 | */ |
||
| 92 | private static $allowed_actions = [ |
||
| 93 | 'index', |
||
| 94 | 'save', |
||
| 95 | 'savetreenode', |
||
| 96 | 'getsubtree', |
||
| 97 | 'updatetreenodes', |
||
| 98 | 'printable', |
||
| 99 | 'show', |
||
| 100 | 'EditorToolbar', |
||
| 101 | 'EditForm', |
||
| 102 | 'AddForm', |
||
| 103 | 'batchactions', |
||
| 104 | 'BatchActionsForm', |
||
| 105 | 'schema', |
||
| 106 | ]; |
||
| 107 | |||
| 108 | private static $url_handlers = [ |
||
| 109 | 'GET schema/$FormName/$RecordType/$ItemID' => 'schema' |
||
| 110 | ]; |
||
| 111 | |||
| 112 | private static $dependencies = [ |
||
| 113 | 'schema' => '%$FormSchema' |
||
| 114 | ]; |
||
| 115 | |||
| 116 | /** |
||
| 117 | * @config |
||
| 118 | * @var Array Codes which are required from the current user to view this controller. |
||
| 119 | * If multiple codes are provided, all of them are required. |
||
| 120 | * All CMS controllers require "CMS_ACCESS_LeftAndMain" as a baseline check, |
||
| 121 | * and fall back to "CMS_ACCESS_<class>" if no permissions are defined here. |
||
| 122 | * See {@link canView()} for more details on permission checks. |
||
| 123 | */ |
||
| 124 | private static $required_permission_codes; |
||
| 125 | |||
| 126 | /** |
||
| 127 | * @config |
||
| 128 | * @var String Namespace for session info, e.g. current record. |
||
| 129 | * Defaults to the current class name, but can be amended to share a namespace in case |
||
| 130 | * controllers are logically bundled together, and mainly separated |
||
| 131 | * to achieve more flexible templating. |
||
| 132 | */ |
||
| 133 | private static $session_namespace; |
||
| 134 | |||
| 135 | /** |
||
| 136 | * Register additional requirements through the {@link Requirements} class. |
||
| 137 | * Used mainly to work around the missing "lazy loading" functionality |
||
| 138 | * for getting css/javascript required after an ajax-call (e.g. loading the editform). |
||
| 139 | * |
||
| 140 | * YAML configuration example: |
||
| 141 | * <code> |
||
| 142 | * LeftAndMain: |
||
| 143 | * extra_requirements_javascript: |
||
| 144 | * mysite/javascript/myscript.js: |
||
| 145 | * </code> |
||
| 146 | * |
||
| 147 | * @config |
||
| 148 | * @var array |
||
| 149 | */ |
||
| 150 | private static $extra_requirements_javascript = array(); |
||
| 151 | |||
| 152 | /** |
||
| 153 | * YAML configuration example: |
||
| 154 | * <code> |
||
| 155 | * LeftAndMain: |
||
| 156 | * extra_requirements_css: |
||
| 157 | * mysite/css/mystyle.css: |
||
| 158 | * media: screen |
||
| 159 | * </code> |
||
| 160 | * |
||
| 161 | * @config |
||
| 162 | * @var array See {@link extra_requirements_javascript} |
||
| 163 | */ |
||
| 164 | private static $extra_requirements_css = array(); |
||
| 165 | |||
| 166 | /** |
||
| 167 | * @config |
||
| 168 | * @var array See {@link extra_requirements_javascript} |
||
| 169 | */ |
||
| 170 | private static $extra_requirements_themedCss = array(); |
||
| 171 | |||
| 172 | /** |
||
| 173 | * If true, call a keepalive ping every 5 minutes from the CMS interface, |
||
| 174 | * to ensure that the session never dies. |
||
| 175 | * |
||
| 176 | * @config |
||
| 177 | * @var boolean |
||
| 178 | */ |
||
| 179 | private static $session_keepalive_ping = true; |
||
| 180 | |||
| 181 | /** |
||
| 182 | * @var PjaxResponseNegotiator |
||
| 183 | */ |
||
| 184 | protected $responseNegotiator; |
||
| 185 | |||
| 186 | /** |
||
| 187 | * Gets the combined configuration of all LeafAndMain subclasses required by the client app. |
||
| 188 | * |
||
| 189 | * @return array |
||
| 190 | * |
||
| 191 | * WARNING: Experimental API |
||
| 192 | */ |
||
| 193 | public function getCombinedClientConfig() { |
||
| 210 | |||
| 211 | /** |
||
| 212 | * Returns configuration required by the client app. |
||
| 213 | * |
||
| 214 | * @return array |
||
| 215 | * |
||
| 216 | * WARNING: Experimental API |
||
| 217 | */ |
||
| 218 | public function getClientConfig() { |
||
| 223 | |||
| 224 | /** |
||
| 225 | * Gets a JSON schema representing the current edit form. |
||
| 226 | * |
||
| 227 | * WARNING: Experimental API. |
||
| 228 | * |
||
| 229 | * @return SS_HTTPResponse |
||
| 230 | */ |
||
| 231 | public function schema($request) { |
||
| 232 | $response = $this->getResponse(); |
||
| 233 | $formName = $request->param('FormName'); |
||
| 234 | $recordType = $request->param('RecordType'); |
||
| 235 | $itemID = $request->param('ItemID'); |
||
| 236 | |||
| 237 | if (!$formName || !$recordType) { |
||
| 238 | return (new SS_HTTPResponse('Missing request params', 400)); |
||
| 239 | } |
||
| 240 | |||
| 241 | if(!$this->hasMethod("get{$formName}")) { |
||
| 242 | return (new SS_HTTPResponse('Form not found', 404)); |
||
| 243 | } |
||
| 244 | |||
| 245 | if(!$this->hasAction($formName)) { |
||
| 246 | return (new SS_HTTPResponse('Form not accessible', 401)); |
||
| 247 | } |
||
| 248 | |||
| 249 | $form = $this->{"get{$formName}"}($itemID); |
||
| 250 | |||
| 251 | if($itemID) { |
||
| 252 | $record = $recordType::get()->byId($itemID); |
||
| 253 | if(!$record) { |
||
| 254 | return (new SS_HTTPResponse('Record not found', 404)); |
||
| 255 | } |
||
| 256 | if(!$record->canView()) { |
||
| 257 | return (new SS_HTTPResponse('Record not accessible', 403)); |
||
| 258 | } |
||
| 259 | $form->loadDataFrom($record); |
||
| 260 | } |
||
| 261 | |||
| 262 | $response->addHeader('Content-Type', 'application/json'); |
||
| 263 | $response->setBody(Convert::raw2json($this->getSchemaForForm($form))); |
||
| 264 | |||
| 265 | return $response; |
||
| 266 | } |
||
| 267 | |||
| 268 | /** |
||
| 269 | * Given a form, generate a response containing the requested form |
||
| 270 | * schema if X-Formschema-Request header is set. |
||
| 271 | * |
||
| 272 | * @param Form $form |
||
| 273 | * @return SS_HTTPResponse |
||
| 274 | */ |
||
| 275 | protected function getSchemaResponse($form) { |
||
| 285 | |||
| 286 | /** |
||
| 287 | * Returns a representation of the provided {@link Form} as structured data, |
||
| 288 | * based on the request data. |
||
| 289 | * |
||
| 290 | * @param Form $form |
||
| 291 | * @return array |
||
| 292 | */ |
||
| 293 | protected function getSchemaForForm(Form $form) { |
||
| 321 | |||
| 322 | /** |
||
| 323 | * @param Member $member |
||
| 324 | * @return boolean |
||
| 325 | */ |
||
| 326 | public function canView($member = null) { |
||
| 352 | |||
| 353 | /** |
||
| 354 | * @uses LeftAndMainExtension->init() |
||
| 355 | * @uses LeftAndMainExtension->accessedCMS() |
||
| 356 | * @uses CMSMenu |
||
| 357 | */ |
||
| 358 | public function init() { |
||
|
|
|||
| 359 | parent::init(); |
||
| 360 | |||
| 361 | Config::inst()->update('SSViewer', 'rewrite_hash_links', false); |
||
| 362 | Config::inst()->update('ContentNegotiator', 'enabled', false); |
||
| 363 | |||
| 364 | // set language |
||
| 365 | $member = Member::currentUser(); |
||
| 366 | if(!empty($member->Locale)) i18n::set_locale($member->Locale); |
||
| 367 | if(!empty($member->DateFormat)) i18n::config()->date_format = $member->DateFormat; |
||
| 368 | if(!empty($member->TimeFormat)) i18n::config()->time_format = $member->TimeFormat; |
||
| 369 | |||
| 370 | // can't be done in cms/_config.php as locale is not set yet |
||
| 371 | CMSMenu::add_link( |
||
| 372 | 'Help', |
||
| 373 | _t('LeftAndMain.HELP', 'Help', 'Menu title'), |
||
| 374 | $this->config()->help_link, |
||
| 375 | -2, |
||
| 376 | array( |
||
| 377 | 'target' => '_blank' |
||
| 378 | ) |
||
| 379 | ); |
||
| 380 | |||
| 381 | // Allow customisation of the access check by a extension |
||
| 382 | // Also all the canView() check to execute Controller::redirect() |
||
| 383 | if(!$this->canView() && !$this->getResponse()->isFinished()) { |
||
| 384 | // When access /admin/, we should try a redirect to another part of the admin rather than be locked out |
||
| 385 | $menu = $this->MainMenu(); |
||
| 386 | foreach($menu as $candidate) { |
||
| 387 | if( |
||
| 388 | $candidate->Link && |
||
| 389 | $candidate->Link != $this->Link() |
||
| 390 | && $candidate->MenuItem->controller |
||
| 391 | && singleton($candidate->MenuItem->controller)->canView() |
||
| 392 | ) { |
||
| 393 | return $this->redirect($candidate->Link); |
||
| 394 | } |
||
| 395 | } |
||
| 396 | |||
| 397 | if(Member::currentUser()) { |
||
| 398 | Session::set("BackURL", null); |
||
| 399 | } |
||
| 400 | |||
| 401 | // if no alternate menu items have matched, return a permission error |
||
| 402 | $messageSet = array( |
||
| 403 | 'default' => _t( |
||
| 404 | 'LeftAndMain.PERMDEFAULT', |
||
| 405 | "You must be logged in to access the administration area; please enter your credentials below." |
||
| 406 | ), |
||
| 407 | 'alreadyLoggedIn' => _t( |
||
| 408 | 'LeftAndMain.PERMALREADY', |
||
| 409 | "I'm sorry, but you can't access that part of the CMS. If you want to log in as someone else, do" |
||
| 410 | . " so below." |
||
| 411 | ), |
||
| 412 | 'logInAgain' => _t( |
||
| 413 | 'LeftAndMain.PERMAGAIN', |
||
| 414 | "You have been logged out of the CMS. If you would like to log in again, enter a username and" |
||
| 415 | . " password below." |
||
| 416 | ), |
||
| 417 | ); |
||
| 418 | |||
| 419 | return Security::permissionFailure($this, $messageSet); |
||
| 420 | } |
||
| 421 | |||
| 422 | // Don't continue if there's already been a redirection request. |
||
| 423 | if($this->redirectedTo()) return; |
||
| 424 | |||
| 425 | // Audit logging hook |
||
| 426 | if(empty($_REQUEST['executeForm']) && !$this->getRequest()->isAjax()) $this->extend('accessedCMS'); |
||
| 427 | |||
| 428 | // Set the members html editor config |
||
| 429 | if(Member::currentUser()) { |
||
| 430 | HtmlEditorConfig::set_active_identifier(Member::currentUser()->getHtmlEditorConfigForCMS()); |
||
| 431 | } |
||
| 432 | |||
| 433 | // Set default values in the config if missing. These things can't be defined in the config |
||
| 434 | // file because insufficient information exists when that is being processed |
||
| 435 | $htmlEditorConfig = HtmlEditorConfig::get_active(); |
||
| 436 | $htmlEditorConfig->setOption('language', i18n::get_tinymce_lang()); |
||
| 437 | if(!$htmlEditorConfig->getOption('content_css')) { |
||
| 438 | $cssFiles = array(); |
||
| 439 | $cssFiles[] = FRAMEWORK_ADMIN_DIR . '/client/dist/styles/editor.css'; |
||
| 440 | |||
| 441 | // Use theme from the site config |
||
| 442 | if(class_exists('SiteConfig') && ($config = SiteConfig::current_site_config()) && $config->Theme) { |
||
| 443 | $theme = $config->Theme; |
||
| 444 | } elseif(Config::inst()->get('SSViewer', 'theme_enabled') && Config::inst()->get('SSViewer', 'theme')) { |
||
| 445 | $theme = Config::inst()->get('SSViewer', 'theme'); |
||
| 446 | } else { |
||
| 447 | $theme = false; |
||
| 448 | } |
||
| 449 | |||
| 450 | if($theme) $cssFiles[] = THEMES_DIR . "/{$theme}/css/editor.css"; |
||
| 451 | else if(project()) $cssFiles[] = project() . '/css/editor.css'; |
||
| 452 | |||
| 453 | // Remove files that don't exist |
||
| 454 | foreach($cssFiles as $k => $cssFile) { |
||
| 455 | if(!file_exists(BASE_PATH . '/' . $cssFile)) unset($cssFiles[$k]); |
||
| 456 | } |
||
| 457 | |||
| 458 | $htmlEditorConfig->setOption('content_css', implode(',', $cssFiles)); |
||
| 459 | } |
||
| 460 | |||
| 461 | Requirements::customScript(" |
||
| 462 | window.ss = window.ss || {}; |
||
| 463 | window.ss.config = " . $this->getCombinedClientConfig() . "; |
||
| 464 | "); |
||
| 465 | |||
| 466 | Requirements::javascript(FRAMEWORK_ADMIN_DIR . '/client/dist/js/bundle-lib.js', [ |
||
| 467 | 'provides' => [ |
||
| 468 | THIRDPARTY_DIR . '/jquery/jquery.js', |
||
| 469 | THIRDPARTY_DIR . '/jquery-ui/jquery-ui.js', |
||
| 470 | THIRDPARTY_DIR . '/jquery-entwine/dist/jquery.entwine-dist.js', |
||
| 471 | THIRDPARTY_DIR . '/jquery-cookie/jquery.cookie.js', |
||
| 472 | THIRDPARTY_DIR . '/jquery-query/jquery.query.js', |
||
| 473 | THIRDPARTY_DIR . '/jquery-form/jquery.form.js', |
||
| 474 | THIRDPARTY_DIR . '/jquery-ondemand/jquery.ondemand.js', |
||
| 475 | THIRDPARTY_DIR . '/jquery-changetracker/lib/jquery.changetracker.js', |
||
| 476 | THIRDPARTY_DIR . '/jstree/jquery.jstree.js', |
||
| 477 | FRAMEWORK_ADMIN_DIR . '/thirdparty/jquery-notice/jquery.notice.js', |
||
| 478 | FRAMEWORK_ADMIN_DIR . '/thirdparty/jsizes/lib/jquery.sizes.js', |
||
| 479 | FRAMEWORK_ADMIN_DIR . '/thirdparty/jlayout/lib/jlayout.border.js', |
||
| 480 | FRAMEWORK_ADMIN_DIR . '/thirdparty/jlayout/lib/jquery.jlayout.js', |
||
| 481 | FRAMEWORK_ADMIN_DIR . '/thirdparty/chosen/chosen/chosen.jquery.js', |
||
| 482 | FRAMEWORK_ADMIN_DIR . '/thirdparty/jquery-hoverIntent/jquery.hoverIntent.js', |
||
| 483 | FRAMEWORK_DIR . '/client/dist/js/TreeDropdownField.js', |
||
| 484 | FRAMEWORK_DIR . '/client/dist/js/DateField.js', |
||
| 485 | FRAMEWORK_DIR . '/client/dist/js/HtmlEditorField.js', |
||
| 486 | FRAMEWORK_DIR . '/client/dist/js/TabSet.js', |
||
| 487 | FRAMEWORK_DIR . '/client/dist/js/GridField.js', |
||
| 488 | FRAMEWORK_DIR . '/client/dist/js/i18n.js', |
||
| 489 | FRAMEWORK_ADMIN_DIR . '/client/dist/js/sspath.js', |
||
| 490 | FRAMEWORK_ADMIN_DIR . '/client/dist/js/ssui.core.js' |
||
| 491 | ] |
||
| 492 | ]); |
||
| 493 | |||
| 494 | Requirements::javascript(FRAMEWORK_ADMIN_DIR . '/client/dist/js/bundle-legacy.js', [ |
||
| 495 | 'provides' => [ |
||
| 496 | FRAMEWORK_ADMIN_DIR . '/client/dist/js/LeftAndMain.Layout.js', |
||
| 497 | FRAMEWORK_ADMIN_DIR . '/client/dist/js/LeftAndMain.js', |
||
| 498 | FRAMEWORK_ADMIN_DIR . '/client/dist/js/LeftAndMain.ActionTabSet.js', |
||
| 499 | FRAMEWORK_ADMIN_DIR . '/client/dist/js/LeftAndMain.Panel.js', |
||
| 500 | FRAMEWORK_ADMIN_DIR . '/client/dist/js/LeftAndMain.Tree.js', |
||
| 501 | FRAMEWORK_ADMIN_DIR . '/client/dist/js/LeftAndMain.Content.js', |
||
| 502 | FRAMEWORK_ADMIN_DIR . '/client/dist/js/LeftAndMain.EditForm.js', |
||
| 503 | FRAMEWORK_ADMIN_DIR . '/client/dist/js/LeftAndMain.Menu.js', |
||
| 504 | FRAMEWORK_ADMIN_DIR . '/client/dist/js/LeftAndMain.Preview.js', |
||
| 505 | FRAMEWORK_ADMIN_DIR . '/client/dist/js/LeftAndMain.BatchActions.js', |
||
| 506 | FRAMEWORK_ADMIN_DIR . '/client/dist/js/LeftAndMain.FieldHelp.js', |
||
| 507 | FRAMEWORK_ADMIN_DIR . '/client/dist/js/LeftAndMain.FieldDescriptionToggle.js', |
||
| 508 | FRAMEWORK_ADMIN_DIR . '/client/dist/js/LeftAndMain.TreeDropdownField.js', |
||
| 509 | FRAMEWORK_ADMIN_DIR . '/client/dist/js/AddToCampaignForm.js' |
||
| 510 | ] |
||
| 511 | ]); |
||
| 512 | |||
| 513 | Requirements::add_i18n_javascript(FRAMEWORK_DIR . '/javascript/lang', false, true); |
||
| 514 | Requirements::add_i18n_javascript(FRAMEWORK_ADMIN_DIR . '/client/lang', false, true); |
||
| 515 | |||
| 516 | if ($this->config()->session_keepalive_ping) { |
||
| 517 | Requirements::javascript(FRAMEWORK_ADMIN_DIR . '/client/dist/js/LeftAndMain.Ping.js'); |
||
| 518 | } |
||
| 519 | |||
| 520 | if (Director::isDev()) { |
||
| 521 | // TODO Confuses jQuery.ondemand through document.write() |
||
| 522 | Requirements::javascript(THIRDPARTY_DIR . '/jquery-entwine/src/jquery.entwine.inspector.js'); |
||
| 523 | Requirements::javascript(FRAMEWORK_ADMIN_DIR . '/client/dist/js/leaktools.js'); |
||
| 524 | } |
||
| 525 | |||
| 526 | Requirements::javascript(FRAMEWORK_ADMIN_DIR . '/client/dist/js/bundle-framework.js'); |
||
| 527 | |||
| 528 | Requirements::css(FRAMEWORK_ADMIN_DIR . '/thirdparty/jquery-notice/jquery.notice.css'); |
||
| 529 | Requirements::css(THIRDPARTY_DIR . '/jquery-ui-themes/smoothness/jquery-ui.css'); |
||
| 530 | Requirements::css(THIRDPARTY_DIR . '/jstree/themes/apple/style.css'); |
||
| 531 | Requirements::css(FRAMEWORK_DIR . '/client/dist/styles/TreeDropdownField.css'); |
||
| 532 | Requirements::css(FRAMEWORK_ADMIN_DIR . '/client/dist/styles/bundle.css'); |
||
| 533 | Requirements::css(FRAMEWORK_DIR . '/client/dist/styles/GridField.css'); |
||
| 534 | |||
| 535 | // Custom requirements |
||
| 536 | $extraJs = $this->stat('extra_requirements_javascript'); |
||
| 537 | |||
| 538 | if($extraJs) { |
||
| 539 | foreach($extraJs as $file => $config) { |
||
| 540 | if(is_numeric($file)) { |
||
| 541 | $file = $config; |
||
| 542 | } |
||
| 543 | |||
| 544 | Requirements::javascript($file); |
||
| 545 | } |
||
| 546 | } |
||
| 547 | |||
| 548 | $extraCss = $this->stat('extra_requirements_css'); |
||
| 549 | |||
| 550 | if($extraCss) { |
||
| 551 | foreach($extraCss as $file => $config) { |
||
| 552 | if(is_numeric($file)) { |
||
| 553 | $file = $config; |
||
| 554 | $config = array(); |
||
| 555 | } |
||
| 556 | |||
| 557 | Requirements::css($file, isset($config['media']) ? $config['media'] : null); |
||
| 558 | } |
||
| 559 | } |
||
| 560 | |||
| 561 | $extraThemedCss = $this->stat('extra_requirements_themedCss'); |
||
| 562 | |||
| 563 | if($extraThemedCss) { |
||
| 564 | foreach ($extraThemedCss as $file => $config) { |
||
| 565 | if(is_numeric($file)) { |
||
| 566 | $file = $config; |
||
| 567 | $config = array(); |
||
| 568 | } |
||
| 569 | |||
| 570 | Requirements::themedCSS($file, isset($config['media']) ? $config['media'] : null); |
||
| 571 | } |
||
| 572 | } |
||
| 573 | |||
| 574 | $dummy = null; |
||
| 575 | $this->extend('init', $dummy); |
||
| 576 | |||
| 577 | // The user's theme shouldn't affect the CMS, if, for example, they have |
||
| 578 | // replaced TableListField.ss or Form.ss. |
||
| 579 | Config::inst()->update('SSViewer', 'theme_enabled', false); |
||
| 580 | |||
| 581 | //set the reading mode for the admin to stage |
||
| 582 | Versioned::set_stage(Versioned::DRAFT); |
||
| 583 | } |
||
| 584 | |||
| 585 | public function handleRequest(SS_HTTPRequest $request, DataModel $model = null) { |
||
| 611 | |||
| 612 | /** |
||
| 613 | * Overloaded redirection logic to trigger a fake redirect on ajax requests. |
||
| 614 | * While this violates HTTP principles, its the only way to work around the |
||
| 615 | * fact that browsers handle HTTP redirects opaquely, no intervention via JS is possible. |
||
| 616 | * In isolation, that's not a problem - but combined with history.pushState() |
||
| 617 | * it means we would request the same redirection URL twice if we want to update the URL as well. |
||
| 618 | * See LeftAndMain.js for the required jQuery ajaxComplete handlers. |
||
| 619 | */ |
||
| 620 | public function redirect($url, $code=302) { |
||
| 642 | |||
| 643 | public function index($request) { |
||
| 646 | |||
| 647 | /** |
||
| 648 | * If this is set to true, the "switchView" context in the |
||
| 649 | * template is shown, with links to the staging and publish site. |
||
| 650 | * |
||
| 651 | * @return boolean |
||
| 652 | */ |
||
| 653 | public function ShowSwitchView() { |
||
| 656 | |||
| 657 | |||
| 658 | //------------------------------------------------------------------------------------------// |
||
| 659 | // Main controllers |
||
| 660 | |||
| 661 | /** |
||
| 662 | * You should implement a Link() function in your subclass of LeftAndMain, |
||
| 663 | * to point to the URL of that particular controller. |
||
| 664 | * |
||
| 665 | * @return string |
||
| 666 | */ |
||
| 667 | public function Link($action = null) { |
||
| 684 | |||
| 685 | /** |
||
| 686 | * @deprecated 5.0 |
||
| 687 | */ |
||
| 688 | public static function menu_title_for_class($class) { |
||
| 692 | |||
| 693 | /** |
||
| 694 | * Get menu title for this section (translated) |
||
| 695 | * |
||
| 696 | * @param string $class Optional class name if called on LeftAndMain directly |
||
| 697 | * @param bool $localise Determine if menu title should be localised via i18n. |
||
| 698 | * @return string Menu title for the given class |
||
| 699 | */ |
||
| 700 | public static function menu_title($class = null, $localise = true) { |
||
| 721 | |||
| 722 | /** |
||
| 723 | * Return styling for the menu icon, if a custom icon is set for this class |
||
| 724 | * |
||
| 725 | * Example: static $menu-icon = '/path/to/image/'; |
||
| 726 | * @param string $class |
||
| 727 | * @return string |
||
| 728 | */ |
||
| 729 | public static function menu_icon_for_class($class) { |
||
| 737 | |||
| 738 | public function show($request) { |
||
| 743 | |||
| 744 | /** |
||
| 745 | * Caution: Volatile API. |
||
| 746 | * |
||
| 747 | * @return PjaxResponseNegotiator |
||
| 748 | */ |
||
| 749 | public function getResponseNegotiator() { |
||
| 772 | |||
| 773 | //------------------------------------------------------------------------------------------// |
||
| 774 | // Main UI components |
||
| 775 | |||
| 776 | /** |
||
| 777 | * Returns the main menu of the CMS. This is also used by init() |
||
| 778 | * to work out which sections the user has access to. |
||
| 779 | * |
||
| 780 | * @param Boolean |
||
| 781 | * @return SS_List |
||
| 782 | */ |
||
| 783 | public function MainMenu($cached = true) { |
||
| 861 | |||
| 862 | public function Menu() { |
||
| 865 | |||
| 866 | /** |
||
| 867 | * @todo Wrap in CMSMenu instance accessor |
||
| 868 | * @return ArrayData A single menu entry (see {@link MainMenu}) |
||
| 869 | */ |
||
| 870 | public function MenuCurrentItem() { |
||
| 874 | |||
| 875 | /** |
||
| 876 | * Return a list of appropriate templates for this class, with the given suffix using |
||
| 877 | * {@link SSViewer::get_templates_by_class()} |
||
| 878 | * |
||
| 879 | * @return array |
||
| 880 | */ |
||
| 881 | public function getTemplatesWithSuffix($suffix) { |
||
| 884 | |||
| 885 | public function Content() { |
||
| 888 | |||
| 889 | public function getRecord($id) { |
||
| 901 | |||
| 902 | /** |
||
| 903 | * @return ArrayList |
||
| 904 | */ |
||
| 905 | public function Breadcrumbs($unlinked = false) { |
||
| 934 | |||
| 935 | /** |
||
| 936 | * @return String HTML |
||
| 937 | */ |
||
| 938 | public function SiteTreeAsUL() { |
||
| 943 | |||
| 944 | /** |
||
| 945 | * Gets the current search filter for this request, if available |
||
| 946 | * |
||
| 947 | * @throws InvalidArgumentException |
||
| 948 | * @return LeftAndMain_SearchFilter |
||
| 949 | */ |
||
| 950 | protected function getSearchFilter() { |
||
| 966 | |||
| 967 | /** |
||
| 968 | * Get a site tree HTML listing which displays the nodes under the given criteria. |
||
| 969 | * |
||
| 970 | * @param $className The class of the root object |
||
| 971 | * @param $rootID The ID of the root object. If this is null then a complete tree will be |
||
| 972 | * shown |
||
| 973 | * @param $childrenMethod The method to call to get the children of the tree. For example, |
||
| 974 | * Children, AllChildrenIncludingDeleted, or AllHistoricalChildren |
||
| 975 | * @return String Nested unordered list with links to each page |
||
| 976 | */ |
||
| 977 | public function getSiteTreeFor($className, $rootID = null, $childrenMethod = null, $numChildrenMethod = null, |
||
| 1101 | |||
| 1102 | /** |
||
| 1103 | * Get a subtree underneath the request param 'ID'. |
||
| 1104 | * If ID = 0, then get the whole tree. |
||
| 1105 | */ |
||
| 1106 | public function getsubtree($request) { |
||
| 1122 | |||
| 1123 | /** |
||
| 1124 | * Allows requesting a view update on specific tree nodes. |
||
| 1125 | * Similar to {@link getsubtree()}, but doesn't enforce loading |
||
| 1126 | * all children with the node. Useful to refresh views after |
||
| 1127 | * state modifications, e.g. saving a form. |
||
| 1128 | * |
||
| 1129 | * @return String JSON |
||
| 1130 | */ |
||
| 1131 | public function updatetreenodes($request) { |
||
| 1175 | |||
| 1176 | /** |
||
| 1177 | * Save handler |
||
| 1178 | * |
||
| 1179 | * @param array $data |
||
| 1180 | * @param Form $form |
||
| 1181 | * @return SS_HTTPResponse |
||
| 1182 | */ |
||
| 1183 | public function save($data, $form) { |
||
| 1223 | |||
| 1224 | /** |
||
| 1225 | * Create new item. |
||
| 1226 | * |
||
| 1227 | * @param string|int $id |
||
| 1228 | * @param bool $setID |
||
| 1229 | * @return DataObject |
||
| 1230 | */ |
||
| 1231 | public function getNewItem($id, $setID = true) { |
||
| 1239 | |||
| 1240 | public function delete($data, $form) { |
||
| 1256 | |||
| 1257 | /** |
||
| 1258 | * Update the position and parent of a tree node. |
||
| 1259 | * Only saves the node if changes were made. |
||
| 1260 | * |
||
| 1261 | * Required data: |
||
| 1262 | * - 'ID': The moved node |
||
| 1263 | * - 'ParentID': New parent relation of the moved node (0 for root) |
||
| 1264 | * - 'SiblingIDs': Array of all sibling nodes to the moved node (incl. the node itself). |
||
| 1265 | * In case of a 'ParentID' change, relates to the new siblings under the new parent. |
||
| 1266 | * |
||
| 1267 | * @return SS_HTTPResponse JSON string with a |
||
| 1268 | */ |
||
| 1269 | public function savetreenode($request) { |
||
| 1363 | |||
| 1364 | public function CanOrganiseSitetree() { |
||
| 1367 | |||
| 1368 | /** |
||
| 1369 | * Retrieves an edit form, either for display, or to process submitted data. |
||
| 1370 | * Also used in the template rendered through {@link Right()} in the $EditForm placeholder. |
||
| 1371 | * |
||
| 1372 | * This is a "pseudo-abstract" methoed, usually connected to a {@link getEditForm()} |
||
| 1373 | * method in an entwine subclass. This method can accept a record identifier, |
||
| 1374 | * selected either in custom logic, or through {@link currentPageID()}. |
||
| 1375 | * The form usually construct itself from {@link DataObject->getCMSFields()} |
||
| 1376 | * for the specific managed subclass defined in {@link LeftAndMain::$tree_class}. |
||
| 1377 | * |
||
| 1378 | * @param HTTPRequest $request Optionally contains an identifier for the |
||
| 1379 | * record to load into the form. |
||
| 1380 | * @return Form Should return a form regardless wether a record has been found. |
||
| 1381 | * Form might be readonly if the current user doesn't have the permission to edit |
||
| 1382 | * the record. |
||
| 1383 | */ |
||
| 1384 | /** |
||
| 1385 | * @return Form |
||
| 1386 | */ |
||
| 1387 | public function EditForm($request = null) { |
||
| 1390 | |||
| 1391 | /** |
||
| 1392 | * Calls {@link SiteTree->getCMSFields()} |
||
| 1393 | * |
||
| 1394 | * @param Int $id |
||
| 1395 | * @param FieldList $fields |
||
| 1396 | * @return Form |
||
| 1397 | */ |
||
| 1398 | public function getEditForm($id = null, $fields = null) { |
||
| 1527 | |||
| 1528 | /** |
||
| 1529 | * Returns a placeholder form, used by {@link getEditForm()} if no record is selected. |
||
| 1530 | * Our javascript logic always requires a form to be present in the CMS interface. |
||
| 1531 | * |
||
| 1532 | * @return Form |
||
| 1533 | */ |
||
| 1534 | public function EmptyForm() { |
||
| 1562 | |||
| 1563 | /** |
||
| 1564 | * Return the CMS's HTML-editor toolbar |
||
| 1565 | */ |
||
| 1566 | public function EditorToolbar() { |
||
| 1569 | |||
| 1570 | /** |
||
| 1571 | * Renders a panel containing tools which apply to all displayed |
||
| 1572 | * "content" (mostly through {@link EditForm()}), for example a tree navigation or a filter panel. |
||
| 1573 | * Auto-detects applicable templates by naming convention: "<controller classname>_Tools.ss", |
||
| 1574 | * and takes the most specific template (see {@link getTemplatesWithSuffix()}). |
||
| 1575 | * To explicitly disable the panel in the subclass, simply create a more specific, empty template. |
||
| 1576 | * |
||
| 1577 | * @return String HTML |
||
| 1578 | */ |
||
| 1579 | public function Tools() { |
||
| 1588 | |||
| 1589 | /** |
||
| 1590 | * Renders a panel containing tools which apply to the currently displayed edit form. |
||
| 1591 | * The main difference to {@link Tools()} is that the panel is displayed within |
||
| 1592 | * the element structure of the form panel (rendered through {@link EditForm}). |
||
| 1593 | * This means the panel will be loaded alongside new forms, and refreshed upon save, |
||
| 1594 | * which can mean a performance hit, depending on how complex your panel logic gets. |
||
| 1595 | * Any form fields contained in the returned markup will also be submitted with the main form, |
||
| 1596 | * which might be desired depending on the implementation details. |
||
| 1597 | * |
||
| 1598 | * @return String HTML |
||
| 1599 | */ |
||
| 1600 | public function EditFormTools() { |
||
| 1609 | |||
| 1610 | /** |
||
| 1611 | * Batch Actions Handler |
||
| 1612 | */ |
||
| 1613 | public function batchactions() { |
||
| 1616 | |||
| 1617 | /** |
||
| 1618 | * @return Form |
||
| 1619 | */ |
||
| 1620 | public function BatchActionsForm() { |
||
| 1651 | |||
| 1652 | public function printable() { |
||
| 1665 | |||
| 1666 | /** |
||
| 1667 | * Used for preview controls, mainly links which switch between different states of the page. |
||
| 1668 | * |
||
| 1669 | * @return ArrayData |
||
| 1670 | */ |
||
| 1671 | public function getSilverStripeNavigator() { |
||
| 1680 | |||
| 1681 | /** |
||
| 1682 | * Identifier for the currently shown record, |
||
| 1683 | * in most cases a database ID. Inspects the following |
||
| 1684 | * sources (in this order): |
||
| 1685 | * - GET/POST parameter named 'ID' |
||
| 1686 | * - URL parameter named 'ID' |
||
| 1687 | * - Session value namespaced by classname, e.g. "CMSMain.currentPage" |
||
| 1688 | * |
||
| 1689 | * @return int |
||
| 1690 | */ |
||
| 1691 | public function currentPageID() { |
||
| 1702 | |||
| 1703 | /** |
||
| 1704 | * Forces the current page to be set in session, |
||
| 1705 | * which can be retrieved later through {@link currentPageID()}. |
||
| 1706 | * Keep in mind that setting an ID through GET/POST or |
||
| 1707 | * as a URL parameter will overrule this value. |
||
| 1708 | * |
||
| 1709 | * @param int $id |
||
| 1710 | */ |
||
| 1711 | public function setCurrentPageID($id) { |
||
| 1714 | |||
| 1715 | /** |
||
| 1716 | * Uses {@link getRecord()} and {@link currentPageID()} |
||
| 1717 | * to get the currently selected record. |
||
| 1718 | * |
||
| 1719 | * @return DataObject |
||
| 1720 | */ |
||
| 1721 | public function currentPage() { |
||
| 1724 | |||
| 1725 | /** |
||
| 1726 | * Compares a given record to the currently selected one (if any). |
||
| 1727 | * Used for marking the current tree node. |
||
| 1728 | * |
||
| 1729 | * @return boolean |
||
| 1730 | */ |
||
| 1731 | public function isCurrentPage(DataObject $record) { |
||
| 1734 | |||
| 1735 | /** |
||
| 1736 | * @return String |
||
| 1737 | */ |
||
| 1738 | protected function sessionNamespace() { |
||
| 1742 | |||
| 1743 | /** |
||
| 1744 | * URL to a previewable record which is shown through this controller. |
||
| 1745 | * The controller might not have any previewable content, in which case |
||
| 1746 | * this method returns FALSE. |
||
| 1747 | * |
||
| 1748 | * @return String|boolean |
||
| 1749 | */ |
||
| 1750 | public function LinkPreview() { |
||
| 1753 | |||
| 1754 | /** |
||
| 1755 | * Return the version number of this application. |
||
| 1756 | * Uses the number in <mymodule>/silverstripe_version |
||
| 1757 | * (automatically replaced by build scripts). |
||
| 1758 | * If silverstripe_version is empty, |
||
| 1759 | * then attempts to get it from composer.lock |
||
| 1760 | * |
||
| 1761 | * @return string |
||
| 1762 | */ |
||
| 1763 | public function CMSVersion() { |
||
| 1822 | |||
| 1823 | /** |
||
| 1824 | * @return array |
||
| 1825 | */ |
||
| 1826 | public function SwitchView() { |
||
| 1832 | |||
| 1833 | /** |
||
| 1834 | * @return SiteConfig |
||
| 1835 | */ |
||
| 1836 | public function SiteConfig() { |
||
| 1839 | |||
| 1840 | /** |
||
| 1841 | * The href for the anchor on the Silverstripe logo. |
||
| 1842 | * Set by calling LeftAndMain::set_application_link() |
||
| 1843 | * |
||
| 1844 | * @config |
||
| 1845 | * @var String |
||
| 1846 | */ |
||
| 1847 | private static $application_link = '//www.silverstripe.org/'; |
||
| 1848 | |||
| 1849 | /** |
||
| 1850 | * Sets the href for the anchor on the Silverstripe logo in the menu |
||
| 1851 | * |
||
| 1852 | * @deprecated since version 4.0 |
||
| 1853 | * |
||
| 1854 | * @param String $link |
||
| 1855 | */ |
||
| 1856 | public static function set_application_link($link) { |
||
| 1860 | |||
| 1861 | /** |
||
| 1862 | * @return String |
||
| 1863 | */ |
||
| 1864 | public function ApplicationLink() { |
||
| 1867 | |||
| 1868 | /** |
||
| 1869 | * The application name. Customisable by calling |
||
| 1870 | * LeftAndMain::setApplicationName() - the first parameter. |
||
| 1871 | * |
||
| 1872 | * @config |
||
| 1873 | * @var String |
||
| 1874 | */ |
||
| 1875 | private static $application_name = 'SilverStripe'; |
||
| 1876 | |||
| 1877 | /** |
||
| 1878 | * @param String $name |
||
| 1879 | * @deprecated since version 4.0 |
||
| 1880 | */ |
||
| 1881 | public static function setApplicationName($name) { |
||
| 1885 | |||
| 1886 | /** |
||
| 1887 | * Get the application name. |
||
| 1888 | * |
||
| 1889 | * @return string |
||
| 1890 | */ |
||
| 1891 | public function getApplicationName() { |
||
| 1894 | |||
| 1895 | /** |
||
| 1896 | * @return string |
||
| 1897 | */ |
||
| 1898 | public function Title() { |
||
| 1903 | |||
| 1904 | /** |
||
| 1905 | * Return the title of the current section. Either this is pulled from |
||
| 1906 | * the current panel's menu_title or from the first active menu |
||
| 1907 | * |
||
| 1908 | * @return string |
||
| 1909 | */ |
||
| 1910 | public function SectionTitle() { |
||
| 1922 | |||
| 1923 | /** |
||
| 1924 | * Same as {@link ViewableData->CSSClasses()}, but with a changed name |
||
| 1925 | * to avoid problems when using {@link ViewableData->customise()} |
||
| 1926 | * (which always returns "ArrayData" from the $original object). |
||
| 1927 | * |
||
| 1928 | * @return String |
||
| 1929 | */ |
||
| 1930 | public function BaseCSSClasses() { |
||
| 1933 | |||
| 1934 | /** |
||
| 1935 | * @return String |
||
| 1936 | */ |
||
| 1937 | public function Locale() { |
||
| 1940 | |||
| 1941 | public function providePermissions() { |
||
| 1971 | |||
| 1972 | /** |
||
| 1973 | * Register the given javascript file as required in the CMS. |
||
| 1974 | * Filenames should be relative to the base, eg, FRAMEWORK_DIR . '/client/dist/js/loader.js' |
||
| 1975 | * |
||
| 1976 | * @deprecated since version 4.0 |
||
| 1977 | */ |
||
| 1978 | public static function require_javascript($file) { |
||
| 1982 | |||
| 1983 | /** |
||
| 1984 | * Register the given stylesheet file as required. |
||
| 1985 | * @deprecated since version 4.0 |
||
| 1986 | * |
||
| 1987 | * @param $file String Filenames should be relative to the base, eg, THIRDPARTY_DIR . '/tree/tree.css' |
||
| 1988 | * @param $media String Comma-separated list of media-types (e.g. "screen,projector") |
||
| 1989 | * @see http://www.w3.org/TR/REC-CSS2/media.html |
||
| 1990 | */ |
||
| 1991 | public static function require_css($file, $media = null) { |
||
| 1995 | |||
| 1996 | /** |
||
| 1997 | * Register the given "themeable stylesheet" as required. |
||
| 1998 | * Themeable stylesheets have globally unique names, just like templates and PHP files. |
||
| 1999 | * Because of this, they can be replaced by similarly named CSS files in the theme directory. |
||
| 2000 | * |
||
| 2001 | * @deprecated since version 4.0 |
||
| 2002 | * |
||
| 2003 | * @param $name String The identifier of the file. For example, css/MyFile.css would have the identifier "MyFile" |
||
| 2004 | * @param $media String Comma-separated list of media-types (e.g. "screen,projector") |
||
| 2005 | */ |
||
| 2006 | public static function require_themed_css($name, $media = null) { |
||
| 2010 | |||
| 2011 | } |
||
| 2012 | |||
| 2287 |
Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable: