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 |
||
| 66 | class LeftAndMain extends Controller implements PermissionProvider { |
||
| 67 | |||
| 68 | /** |
||
| 69 | * Form schema header identifier |
||
| 70 | */ |
||
| 71 | const SCHEMA_HEADER = 'X-Formschema-Request'; |
||
| 72 | |||
| 73 | /** |
||
| 74 | * Enable front-end debugging (increases verbosity) in dev mode. |
||
| 75 | * Will be ignored in live environments. |
||
| 76 | * |
||
| 77 | * @var bool |
||
| 78 | */ |
||
| 79 | private static $client_debugging = true; |
||
| 80 | |||
| 81 | /** |
||
| 82 | * The current url segment attached to the LeftAndMain instance |
||
| 83 | * |
||
| 84 | * @config |
||
| 85 | * @var string |
||
| 86 | */ |
||
| 87 | private static $url_segment; |
||
| 88 | |||
| 89 | /** |
||
| 90 | * @config |
||
| 91 | * @var string |
||
| 92 | */ |
||
| 93 | private static $url_rule = '/$Action/$ID/$OtherID'; |
||
| 94 | |||
| 95 | /** |
||
| 96 | * @config |
||
| 97 | * @var string |
||
| 98 | */ |
||
| 99 | private static $menu_title; |
||
| 100 | |||
| 101 | /** |
||
| 102 | * @config |
||
| 103 | * @var string |
||
| 104 | */ |
||
| 105 | private static $menu_icon; |
||
| 106 | |||
| 107 | /** |
||
| 108 | * @config |
||
| 109 | * @var int |
||
| 110 | */ |
||
| 111 | private static $menu_priority = 0; |
||
| 112 | |||
| 113 | /** |
||
| 114 | * @config |
||
| 115 | * @var int |
||
| 116 | */ |
||
| 117 | private static $url_priority = 50; |
||
| 118 | |||
| 119 | /** |
||
| 120 | * A subclass of {@link DataObject}. |
||
| 121 | * |
||
| 122 | * Determines what is managed in this interface, through |
||
| 123 | * {@link getEditForm()} and other logic. |
||
| 124 | * |
||
| 125 | * @config |
||
| 126 | * @var string |
||
| 127 | */ |
||
| 128 | private static $tree_class = null; |
||
| 129 | |||
| 130 | /** |
||
| 131 | * The url used for the link in the Help tab in the backend |
||
| 132 | * |
||
| 133 | * @config |
||
| 134 | * @var string |
||
| 135 | */ |
||
| 136 | private static $help_link = '//userhelp.silverstripe.org/framework/en/3.3'; |
||
| 137 | |||
| 138 | /** |
||
| 139 | * @var array |
||
| 140 | */ |
||
| 141 | private static $allowed_actions = [ |
||
| 142 | 'index', |
||
| 143 | 'save', |
||
| 144 | 'savetreenode', |
||
| 145 | 'getsubtree', |
||
| 146 | 'updatetreenodes', |
||
| 147 | 'printable', |
||
| 148 | 'show', |
||
| 149 | 'EditorToolbar', |
||
| 150 | 'EditForm', |
||
| 151 | 'AddForm', |
||
| 152 | 'batchactions', |
||
| 153 | 'BatchActionsForm', |
||
| 154 | 'schema', |
||
| 155 | ]; |
||
| 156 | |||
| 157 | private static $url_handlers = [ |
||
| 158 | 'GET schema/$FormName/$ItemID/$OtherItemID' => 'schema' |
||
| 159 | ]; |
||
| 160 | |||
| 161 | private static $dependencies = [ |
||
| 162 | 'FormSchema' => '%$FormSchema' |
||
| 163 | ]; |
||
| 164 | |||
| 165 | /** |
||
| 166 | * Current form schema helper |
||
| 167 | * |
||
| 168 | * @var FormSchema |
||
| 169 | */ |
||
| 170 | protected $schema = null; |
||
| 171 | |||
| 172 | /** |
||
| 173 | * Assign themes to use for cms |
||
| 174 | * |
||
| 175 | * @config |
||
| 176 | * @var array |
||
| 177 | */ |
||
| 178 | private static $admin_themes = [ |
||
| 179 | 'silverstripe/framework:/admin/themes/cms-forms', |
||
| 180 | SSViewer::DEFAULT_THEME, |
||
| 181 | ]; |
||
| 182 | |||
| 183 | /** |
||
| 184 | * Codes which are required from the current user to view this controller. |
||
| 185 | * If multiple codes are provided, all of them are required. |
||
| 186 | * All CMS controllers require "CMS_ACCESS_LeftAndMain" as a baseline check, |
||
| 187 | * and fall back to "CMS_ACCESS_<class>" if no permissions are defined here. |
||
| 188 | * See {@link canView()} for more details on permission checks. |
||
| 189 | * |
||
| 190 | * @config |
||
| 191 | * @var array |
||
| 192 | */ |
||
| 193 | private static $required_permission_codes; |
||
| 194 | |||
| 195 | /** |
||
| 196 | * @config |
||
| 197 | * @var String Namespace for session info, e.g. current record. |
||
| 198 | * Defaults to the current class name, but can be amended to share a namespace in case |
||
| 199 | * controllers are logically bundled together, and mainly separated |
||
| 200 | * to achieve more flexible templating. |
||
| 201 | */ |
||
| 202 | private static $session_namespace; |
||
| 203 | |||
| 204 | /** |
||
| 205 | * Register additional requirements through the {@link Requirements} class. |
||
| 206 | * Used mainly to work around the missing "lazy loading" functionality |
||
| 207 | * for getting css/javascript required after an ajax-call (e.g. loading the editform). |
||
| 208 | * |
||
| 209 | * YAML configuration example: |
||
| 210 | * <code> |
||
| 211 | * LeftAndMain: |
||
| 212 | * extra_requirements_javascript: |
||
| 213 | * - mysite/javascript/myscript.js |
||
| 214 | * </code> |
||
| 215 | * |
||
| 216 | * @config |
||
| 217 | * @var array |
||
| 218 | */ |
||
| 219 | private static $extra_requirements_javascript = array(); |
||
| 220 | |||
| 221 | /** |
||
| 222 | * YAML configuration example: |
||
| 223 | * <code> |
||
| 224 | * LeftAndMain: |
||
| 225 | * extra_requirements_css: |
||
| 226 | * - mysite/css/mystyle.css: |
||
| 227 | * media: screen |
||
| 228 | * </code> |
||
| 229 | * |
||
| 230 | * @config |
||
| 231 | * @var array See {@link extra_requirements_javascript} |
||
| 232 | */ |
||
| 233 | private static $extra_requirements_css = array(); |
||
| 234 | |||
| 235 | /** |
||
| 236 | * @config |
||
| 237 | * @var array See {@link extra_requirements_javascript} |
||
| 238 | */ |
||
| 239 | private static $extra_requirements_themedCss = array(); |
||
| 240 | |||
| 241 | /** |
||
| 242 | * If true, call a keepalive ping every 5 minutes from the CMS interface, |
||
| 243 | * to ensure that the session never dies. |
||
| 244 | * |
||
| 245 | * @config |
||
| 246 | * @var boolean |
||
| 247 | */ |
||
| 248 | private static $session_keepalive_ping = true; |
||
| 249 | |||
| 250 | /** |
||
| 251 | * Value of X-Frame-Options header |
||
| 252 | * |
||
| 253 | * @config |
||
| 254 | * @var string |
||
| 255 | */ |
||
| 256 | private static $frame_options = 'SAMEORIGIN'; |
||
| 257 | |||
| 258 | /** |
||
| 259 | * @var PjaxResponseNegotiator |
||
| 260 | */ |
||
| 261 | protected $responseNegotiator; |
||
| 262 | |||
| 263 | /** |
||
| 264 | * Gets the combined configuration of all LeafAndMain subclasses required by the client app. |
||
| 265 | * |
||
| 266 | * @return array |
||
| 267 | * |
||
| 268 | * WARNING: Experimental API |
||
| 269 | */ |
||
| 270 | public function getCombinedClientConfig() { |
||
| 271 | $combinedClientConfig = ['sections' => []]; |
||
| 272 | $cmsClassNames = CMSMenu::get_cms_classes('SilverStripe\\Admin\\LeftAndMain', true, CMSMenu::URL_PRIORITY); |
||
| 273 | |||
| 274 | foreach ($cmsClassNames as $className) { |
||
| 275 | $combinedClientConfig['sections'][$className] = Injector::inst()->get($className)->getClientConfig(); |
||
| 276 | } |
||
| 277 | |||
| 278 | // Pass in base url (absolute and relative) |
||
| 279 | $combinedClientConfig['baseUrl'] = Director::baseURL(); |
||
| 280 | $combinedClientConfig['absoluteBaseUrl'] = Director::absoluteBaseURL(); |
||
| 281 | $combinedClientConfig['adminUrl'] = AdminRootController::admin_url(); |
||
| 282 | |||
| 283 | // Get "global" CSRF token for use in JavaScript |
||
| 284 | $token = SecurityToken::inst(); |
||
| 285 | $combinedClientConfig[$token->getName()] = $token->getValue(); |
||
| 286 | |||
| 287 | // Set env |
||
| 288 | $combinedClientConfig['environment'] = Director::get_environment_type(); |
||
| 289 | $combinedClientConfig['debugging'] = $this->config()->client_debugging; |
||
|
|
|||
| 290 | |||
| 291 | return Convert::raw2json($combinedClientConfig); |
||
| 292 | } |
||
| 293 | |||
| 294 | /** |
||
| 295 | * Returns configuration required by the client app. |
||
| 296 | * |
||
| 297 | * @return array |
||
| 298 | * |
||
| 299 | * WARNING: Experimental API |
||
| 300 | */ |
||
| 301 | public function getClientConfig() { |
||
| 302 | return [ |
||
| 303 | // Trim leading/trailing slash to make it easier to concatenate URL |
||
| 304 | // and use in routing definitions. |
||
| 305 | 'url' => trim($this->Link(), '/'), |
||
| 306 | ]; |
||
| 307 | } |
||
| 308 | |||
| 309 | /** |
||
| 310 | * Get form schema helper |
||
| 311 | * |
||
| 312 | * @return FormSchema |
||
| 313 | */ |
||
| 314 | public function getFormSchema() { |
||
| 315 | return $this->schema; |
||
| 316 | } |
||
| 317 | |||
| 318 | /** |
||
| 319 | * Set form schema helper for this controller |
||
| 320 | * |
||
| 321 | * @param FormSchema $schema |
||
| 322 | * @return $this |
||
| 323 | */ |
||
| 324 | public function setFormSchema(FormSchema $schema) { |
||
| 325 | $this->schema = $schema; |
||
| 326 | return $this; |
||
| 327 | } |
||
| 328 | |||
| 329 | /** |
||
| 330 | * Gets a JSON schema representing the current edit form. |
||
| 331 | * |
||
| 332 | * WARNING: Experimental API. |
||
| 333 | * |
||
| 334 | * @param HTTPRequest $request |
||
| 335 | * @return HTTPResponse |
||
| 336 | */ |
||
| 337 | public function schema($request) { |
||
| 338 | $formName = $request->param('FormName'); |
||
| 339 | $itemID = $request->param('ItemID'); |
||
| 340 | |||
| 341 | if (!$formName) { |
||
| 342 | return (new HTTPResponse('Missing request params', 400)); |
||
| 343 | } |
||
| 344 | |||
| 345 | if(!$this->hasMethod("get{$formName}")) { |
||
| 346 | return (new HTTPResponse('Form not found', 404)); |
||
| 347 | } |
||
| 348 | |||
| 349 | if(!$this->hasAction($formName)) { |
||
| 350 | return (new HTTPResponse('Form not accessible', 401)); |
||
| 351 | } |
||
| 352 | |||
| 353 | $form = $this->{"get{$formName}"}($itemID); |
||
| 354 | $schemaID = $request->getURL(); |
||
| 355 | return $this->getSchemaResponse($schemaID, $form); |
||
| 356 | } |
||
| 357 | |||
| 358 | /** |
||
| 359 | * Check if the current request has a X-Formschema-Request header set. |
||
| 360 | * Used by conditional logic that responds to validation results |
||
| 361 | * |
||
| 362 | * @return bool |
||
| 363 | */ |
||
| 364 | protected function getSchemaRequested() { |
||
| 365 | $parts = $this->getRequest()->getHeader(static::SCHEMA_HEADER); |
||
| 366 | return !empty($parts); |
||
| 367 | } |
||
| 368 | |||
| 369 | /** |
||
| 370 | * Generate schema for the given form based on the X-Formschema-Request header value |
||
| 371 | * |
||
| 372 | * @param string $schemaID ID for this schema. Required. |
||
| 373 | * @param Form $form Required for 'state' or 'schema' response |
||
| 374 | * @param ValidationResult $errors Required for 'error' response |
||
| 375 | * @param array $extraData Any extra data to be merged with the schema response |
||
| 376 | * @return HTTPResponse |
||
| 377 | */ |
||
| 378 | protected function getSchemaResponse($schemaID, $form = null, ValidationResult $errors = null, $extraData = []) { |
||
| 379 | $parts = $this->getRequest()->getHeader(static::SCHEMA_HEADER); |
||
| 380 | $data = $this |
||
| 381 | ->getFormSchema() |
||
| 382 | ->getMultipartSchema($parts, $schemaID, $form, $errors); |
||
| 383 | |||
| 384 | if ($extraData) { |
||
| 385 | $data = array_merge($data, $extraData); |
||
| 386 | } |
||
| 387 | |||
| 388 | $response = new HTTPResponse(Convert::raw2json($data)); |
||
| 389 | $response->addHeader('Content-Type', 'application/json'); |
||
| 390 | return $response; |
||
| 391 | } |
||
| 392 | |||
| 393 | /** |
||
| 394 | * Get link to schema url for a given form |
||
| 395 | * |
||
| 396 | * @param Form $form |
||
| 397 | * @return string |
||
| 398 | */ |
||
| 399 | protected function getSchemaLinkForForm(Form $form) { |
||
| 408 | |||
| 409 | /** |
||
| 410 | * @param Member $member |
||
| 411 | * @return boolean |
||
| 412 | */ |
||
| 413 | public function canView($member = null) { |
||
| 445 | |||
| 446 | /** |
||
| 447 | * Get list of required permissions |
||
| 448 | * |
||
| 449 | * @return array|string|bool Code, array of codes, or false if no permission required |
||
| 450 | */ |
||
| 451 | public static function getRequiredPermissions() { |
||
| 462 | |||
| 463 | /** |
||
| 464 | * @uses LeftAndMainExtension->init() |
||
| 465 | * @uses LeftAndMainExtension->accessedCMS() |
||
| 466 | * @uses CMSMenu |
||
| 467 | */ |
||
| 468 | protected function init() { |
||
| 628 | |||
| 629 | public function handleRequest(HTTPRequest $request, DataModel $model = null) { |
||
| 655 | |||
| 656 | /** |
||
| 657 | * Overloaded redirection logic to trigger a fake redirect on ajax requests. |
||
| 658 | * While this violates HTTP principles, its the only way to work around the |
||
| 659 | * fact that browsers handle HTTP redirects opaquely, no intervention via JS is possible. |
||
| 660 | * In isolation, that's not a problem - but combined with history.pushState() |
||
| 661 | * it means we would request the same redirection URL twice if we want to update the URL as well. |
||
| 662 | * See LeftAndMain.js for the required jQuery ajaxComplete handlers. |
||
| 663 | * |
||
| 664 | * @param string $url |
||
| 665 | * @param int $code |
||
| 666 | * @return HTTPResponse|string |
||
| 667 | */ |
||
| 668 | public function redirect($url, $code=302) { |
||
| 690 | |||
| 691 | /** |
||
| 692 | * @param HTTPRequest $request |
||
| 693 | * @return HTTPResponse |
||
| 694 | */ |
||
| 695 | public function index($request) { |
||
| 698 | |||
| 699 | /** |
||
| 700 | * If this is set to true, the "switchView" context in the |
||
| 701 | * template is shown, with links to the staging and publish site. |
||
| 702 | * |
||
| 703 | * @return boolean |
||
| 704 | */ |
||
| 705 | public function ShowSwitchView() { |
||
| 708 | |||
| 709 | |||
| 710 | //------------------------------------------------------------------------------------------// |
||
| 711 | // Main controllers |
||
| 712 | |||
| 713 | /** |
||
| 714 | * You should implement a Link() function in your subclass of LeftAndMain, |
||
| 715 | * to point to the URL of that particular controller. |
||
| 716 | * |
||
| 717 | * @param string $action |
||
| 718 | * @return string |
||
| 719 | */ |
||
| 720 | public function Link($action = null) { |
||
| 737 | |||
| 738 | /** |
||
| 739 | * @deprecated 5.0 |
||
| 740 | */ |
||
| 741 | public static function menu_title_for_class($class) { |
||
| 745 | |||
| 746 | /** |
||
| 747 | * Get menu title for this section (translated) |
||
| 748 | * |
||
| 749 | * @param string $class Optional class name if called on LeftAndMain directly |
||
| 750 | * @param bool $localise Determine if menu title should be localised via i18n. |
||
| 751 | * @return string Menu title for the given class |
||
| 752 | */ |
||
| 753 | public static function menu_title($class = null, $localise = true) { |
||
| 774 | |||
| 775 | /** |
||
| 776 | * Return styling for the menu icon, if a custom icon is set for this class |
||
| 777 | * |
||
| 778 | * Example: static $menu-icon = '/path/to/image/'; |
||
| 779 | * @param string $class |
||
| 780 | * @return string |
||
| 781 | */ |
||
| 782 | public static function menu_icon_for_class($class) { |
||
| 790 | |||
| 791 | /** |
||
| 792 | * @param HTTPRequest $request |
||
| 793 | * @return HTTPResponse |
||
| 794 | * @throws HTTPResponse_Exception |
||
| 795 | */ |
||
| 796 | public function show($request) { |
||
| 801 | |||
| 802 | /** |
||
| 803 | * Caution: Volatile API. |
||
| 804 | * |
||
| 805 | * @return PjaxResponseNegotiator |
||
| 806 | */ |
||
| 807 | public function getResponseNegotiator() { |
||
| 833 | |||
| 834 | //------------------------------------------------------------------------------------------// |
||
| 835 | // Main UI components |
||
| 836 | |||
| 837 | /** |
||
| 838 | * Returns the main menu of the CMS. This is also used by init() |
||
| 839 | * to work out which sections the user has access to. |
||
| 840 | * |
||
| 841 | * @param bool $cached |
||
| 842 | * @return SS_List |
||
| 843 | */ |
||
| 844 | public function MainMenu($cached = true) { |
||
| 925 | |||
| 926 | public function Menu() { |
||
| 929 | |||
| 930 | /** |
||
| 931 | * @todo Wrap in CMSMenu instance accessor |
||
| 932 | * @return ArrayData A single menu entry (see {@link MainMenu}) |
||
| 933 | */ |
||
| 934 | public function MenuCurrentItem() { |
||
| 938 | |||
| 939 | /** |
||
| 940 | * Return a list of appropriate templates for this class, with the given suffix using |
||
| 941 | * {@link SSViewer::get_templates_by_class()} |
||
| 942 | * |
||
| 943 | * @param string $suffix |
||
| 944 | * @return array |
||
| 945 | */ |
||
| 946 | public function getTemplatesWithSuffix($suffix) { |
||
| 950 | |||
| 951 | public function Content() { |
||
| 954 | |||
| 955 | /** |
||
| 956 | * Render $PreviewPanel content |
||
| 957 | * |
||
| 958 | * @return DBHTMLText |
||
| 959 | */ |
||
| 960 | public function PreviewPanel() { |
||
| 967 | |||
| 968 | public function getRecord($id) { |
||
| 980 | |||
| 981 | /** |
||
| 982 | * @param bool $unlinked |
||
| 983 | * @return ArrayList |
||
| 984 | */ |
||
| 985 | public function Breadcrumbs($unlinked = false) { |
||
| 1014 | |||
| 1015 | /** |
||
| 1016 | * @return String HTML |
||
| 1017 | */ |
||
| 1018 | public function SiteTreeAsUL() { |
||
| 1023 | |||
| 1024 | /** |
||
| 1025 | * Gets the current search filter for this request, if available |
||
| 1026 | * |
||
| 1027 | * @throws InvalidArgumentException |
||
| 1028 | * @return LeftAndMain_SearchFilter |
||
| 1029 | */ |
||
| 1030 | protected function getSearchFilter() { |
||
| 1046 | |||
| 1047 | /** |
||
| 1048 | * Get a site tree HTML listing which displays the nodes under the given criteria. |
||
| 1049 | * |
||
| 1050 | * @param string $className The class of the root object |
||
| 1051 | * @param string $rootID The ID of the root object. If this is null then a complete tree will be |
||
| 1052 | * shown |
||
| 1053 | * @param string $childrenMethod The method to call to get the children of the tree. For example, |
||
| 1054 | * Children, AllChildrenIncludingDeleted, or AllHistoricalChildren |
||
| 1055 | * @param string $numChildrenMethod |
||
| 1056 | * @param callable $filterFunction |
||
| 1057 | * @param int $nodeCountThreshold |
||
| 1058 | * @return string Nested unordered list with links to each page |
||
| 1059 | */ |
||
| 1060 | public function getSiteTreeFor($className, $rootID = null, $childrenMethod = null, $numChildrenMethod = null, |
||
| 1193 | |||
| 1194 | /** |
||
| 1195 | * Get a subtree underneath the request param 'ID'. |
||
| 1196 | * If ID = 0, then get the whole tree. |
||
| 1197 | * |
||
| 1198 | * @param HTTPRequest $request |
||
| 1199 | * @return string |
||
| 1200 | */ |
||
| 1201 | public function getsubtree($request) { |
||
| 1217 | |||
| 1218 | /** |
||
| 1219 | * Allows requesting a view update on specific tree nodes. |
||
| 1220 | * Similar to {@link getsubtree()}, but doesn't enforce loading |
||
| 1221 | * all children with the node. Useful to refresh views after |
||
| 1222 | * state modifications, e.g. saving a form. |
||
| 1223 | * |
||
| 1224 | * @param HTTPRequest $request |
||
| 1225 | * @return string JSON |
||
| 1226 | */ |
||
| 1227 | public function updatetreenodes($request) { |
||
| 1271 | |||
| 1272 | /** |
||
| 1273 | * Save handler |
||
| 1274 | * |
||
| 1275 | * @param array $data |
||
| 1276 | * @param Form $form |
||
| 1277 | * @return HTTPResponse |
||
| 1278 | */ |
||
| 1279 | public function save($data, $form) { |
||
| 1280 | $request = $this->getRequest(); |
||
| 1281 | $className = $this->stat('tree_class'); |
||
| 1282 | |||
| 1283 | // Existing or new record? |
||
| 1284 | $id = $data['ID']; |
||
| 1285 | if(is_numeric($id) && $id > 0) { |
||
| 1286 | $record = DataObject::get_by_id($className, $id); |
||
| 1287 | if($record && !$record->canEdit()) { |
||
| 1288 | return Security::permissionFailure($this); |
||
| 1289 | } |
||
| 1290 | if(!$record || !$record->ID) { |
||
| 1291 | $this->httpError(404, "Bad record ID #" . (int)$id); |
||
| 1292 | } |
||
| 1293 | } else { |
||
| 1294 | if(!singleton($this->stat('tree_class'))->canCreate()) { |
||
| 1295 | return Security::permissionFailure($this); |
||
| 1296 | } |
||
| 1297 | $record = $this->getNewItem($id, false); |
||
| 1298 | } |
||
| 1299 | |||
| 1300 | // save form data into record |
||
| 1301 | $form->saveInto($record, true); |
||
| 1302 | $record->write(); |
||
| 1303 | $this->extend('onAfterSave', $record); |
||
| 1304 | $this->setCurrentPageID($record->ID); |
||
| 1305 | |||
| 1306 | $message = _t('LeftAndMain.SAVEDUP', 'Saved.'); |
||
| 1307 | if($this->getSchemaRequested()) { |
||
| 1308 | $schemaId = Controller::join_links($this->Link('schema/DetailEditForm'), $id); |
||
| 1309 | // Ensure that newly created records have all their data loaded back into the form. |
||
| 1310 | $form->loadDataFrom($record); |
||
| 1311 | $form->setMessage($message, 'good'); |
||
| 1312 | $response = $this->getSchemaResponse($schemaId, $form); |
||
| 1313 | } else { |
||
| 1314 | $response = $this->getResponseNegotiator()->respond($request); |
||
| 1315 | } |
||
| 1316 | |||
| 1317 | $response->addHeader('X-Status', rawurlencode($message)); |
||
| 1318 | return $response; |
||
| 1319 | } |
||
| 1320 | |||
| 1321 | /** |
||
| 1322 | * Create new item. |
||
| 1323 | * |
||
| 1324 | * @param string|int $id |
||
| 1325 | * @param bool $setID |
||
| 1326 | * @return DataObject |
||
| 1327 | */ |
||
| 1328 | public function getNewItem($id, $setID = true) { |
||
| 1329 | $class = $this->stat('tree_class'); |
||
| 1330 | $object = Injector::inst()->create($class); |
||
| 1331 | if($setID) { |
||
| 1332 | $object->ID = $id; |
||
| 1333 | } |
||
| 1334 | return $object; |
||
| 1335 | } |
||
| 1336 | |||
| 1337 | public function delete($data, $form) { |
||
| 1338 | $className = $this->stat('tree_class'); |
||
| 1339 | |||
| 1340 | $id = $data['ID']; |
||
| 1341 | $record = DataObject::get_by_id($className, $id); |
||
| 1342 | if($record && !$record->canDelete()) return Security::permissionFailure(); |
||
| 1343 | if(!$record || !$record->ID) $this->httpError(404, "Bad record ID #" . (int)$id); |
||
| 1344 | |||
| 1345 | $record->delete(); |
||
| 1346 | |||
| 1347 | $this->getResponse()->addHeader('X-Status', rawurlencode(_t('LeftAndMain.DELETED', 'Deleted.'))); |
||
| 1348 | return $this->getResponseNegotiator()->respond( |
||
| 1349 | $this->getRequest(), |
||
| 1350 | array('currentform' => array($this, 'EmptyForm')) |
||
| 1351 | ); |
||
| 1352 | } |
||
| 1353 | |||
| 1354 | /** |
||
| 1355 | * Update the position and parent of a tree node. |
||
| 1356 | * Only saves the node if changes were made. |
||
| 1357 | * |
||
| 1358 | * Required data: |
||
| 1359 | * - 'ID': The moved node |
||
| 1360 | * - 'ParentID': New parent relation of the moved node (0 for root) |
||
| 1361 | * - 'SiblingIDs': Array of all sibling nodes to the moved node (incl. the node itself). |
||
| 1362 | * In case of a 'ParentID' change, relates to the new siblings under the new parent. |
||
| 1363 | * |
||
| 1364 | * @param HTTPRequest $request |
||
| 1365 | * @return HTTPResponse JSON string with a |
||
| 1366 | * @throws HTTPResponse_Exception |
||
| 1367 | */ |
||
| 1368 | public function savetreenode($request) { |
||
| 1369 | if (!SecurityToken::inst()->checkRequest($request)) { |
||
| 1370 | return $this->httpError(400); |
||
| 1371 | } |
||
| 1372 | if (!Permission::check('SITETREE_REORGANISE') && !Permission::check('ADMIN')) { |
||
| 1373 | $this->getResponse()->setStatusCode( |
||
| 1374 | 403, |
||
| 1375 | _t('LeftAndMain.CANT_REORGANISE', |
||
| 1376 | "You do not have permission to rearange the site tree. Your change was not saved.") |
||
| 1377 | ); |
||
| 1378 | return; |
||
| 1379 | } |
||
| 1380 | |||
| 1381 | $className = $this->stat('tree_class'); |
||
| 1382 | $statusUpdates = array('modified'=>array()); |
||
| 1383 | $id = $request->requestVar('ID'); |
||
| 1384 | $parentID = $request->requestVar('ParentID'); |
||
| 1385 | |||
| 1386 | if($className == 'SilverStripe\\CMS\\Model\\SiteTree' && $page = DataObject::get_by_id('Page', $id)){ |
||
| 1387 | $root = $page->getParentType(); |
||
| 1388 | if(($parentID == '0' || $root == 'root') && !SiteConfig::current_site_config()->canCreateTopLevel()){ |
||
| 1389 | $this->getResponse()->setStatusCode( |
||
| 1390 | 403, |
||
| 1391 | _t('LeftAndMain.CANT_REORGANISE', |
||
| 1392 | "You do not have permission to alter Top level pages. Your change was not saved.") |
||
| 1393 | ); |
||
| 1394 | return; |
||
| 1395 | } |
||
| 1396 | } |
||
| 1397 | |||
| 1398 | $siblingIDs = $request->requestVar('SiblingIDs'); |
||
| 1399 | $statusUpdates = array('modified'=>array()); |
||
| 1400 | if(!is_numeric($id) || !is_numeric($parentID)) throw new InvalidArgumentException(); |
||
| 1401 | |||
| 1402 | $node = DataObject::get_by_id($className, $id); |
||
| 1403 | if($node && !$node->canEdit()) return Security::permissionFailure($this); |
||
| 1404 | |||
| 1405 | if(!$node) { |
||
| 1406 | $this->getResponse()->setStatusCode( |
||
| 1407 | 500, |
||
| 1408 | _t('LeftAndMain.PLEASESAVE', |
||
| 1409 | "Please Save Page: This page could not be updated because it hasn't been saved yet." |
||
| 1410 | ) |
||
| 1411 | ); |
||
| 1412 | return; |
||
| 1413 | } |
||
| 1414 | |||
| 1415 | // Update hierarchy (only if ParentID changed) |
||
| 1416 | if($node->ParentID != $parentID) { |
||
| 1417 | $node->ParentID = (int)$parentID; |
||
| 1418 | $node->write(); |
||
| 1419 | |||
| 1420 | $statusUpdates['modified'][$node->ID] = array( |
||
| 1421 | 'TreeTitle'=>$node->TreeTitle |
||
| 1422 | ); |
||
| 1423 | |||
| 1424 | // Update all dependent pages |
||
| 1425 | if(class_exists('SilverStripe\\CMS\\Model\\VirtualPage')) { |
||
| 1426 | $virtualPages = VirtualPage::get()->filter("CopyContentFromID", $node->ID); |
||
| 1427 | foreach($virtualPages as $virtualPage) { |
||
| 1428 | $statusUpdates['modified'][$virtualPage->ID] = array( |
||
| 1429 | 'TreeTitle' => $virtualPage->TreeTitle() |
||
| 1430 | ); |
||
| 1431 | } |
||
| 1432 | } |
||
| 1433 | |||
| 1434 | $this->getResponse()->addHeader('X-Status', |
||
| 1435 | rawurlencode(_t('LeftAndMain.REORGANISATIONSUCCESSFUL', 'Reorganised the site tree successfully.'))); |
||
| 1436 | } |
||
| 1437 | |||
| 1438 | // Update sorting |
||
| 1439 | if(is_array($siblingIDs)) { |
||
| 1440 | $counter = 0; |
||
| 1441 | foreach($siblingIDs as $id) { |
||
| 1442 | if($id == $node->ID) { |
||
| 1443 | $node->Sort = ++$counter; |
||
| 1444 | $node->write(); |
||
| 1445 | $statusUpdates['modified'][$node->ID] = array( |
||
| 1446 | 'TreeTitle' => $node->TreeTitle |
||
| 1447 | ); |
||
| 1448 | } else if(is_numeric($id)) { |
||
| 1449 | // Nodes that weren't "actually moved" shouldn't be registered as |
||
| 1450 | // having been edited; do a direct SQL update instead |
||
| 1451 | ++$counter; |
||
| 1452 | $table = DataObject::getSchema()->baseDataTable($className); |
||
| 1453 | DB::prepared_query( |
||
| 1454 | "UPDATE \"$table\" SET \"Sort\" = ? WHERE \"ID\" = ?", |
||
| 1455 | array($counter, $id) |
||
| 1456 | ); |
||
| 1457 | } |
||
| 1458 | } |
||
| 1459 | |||
| 1460 | $this->getResponse()->addHeader('X-Status', |
||
| 1461 | rawurlencode(_t('LeftAndMain.REORGANISATIONSUCCESSFUL', 'Reorganised the site tree successfully.'))); |
||
| 1462 | } |
||
| 1463 | |||
| 1464 | return Convert::raw2json($statusUpdates); |
||
| 1465 | } |
||
| 1466 | |||
| 1467 | public function CanOrganiseSitetree() { |
||
| 1470 | |||
| 1471 | /** |
||
| 1472 | * Retrieves an edit form, either for display, or to process submitted data. |
||
| 1473 | * Also used in the template rendered through {@link Right()} in the $EditForm placeholder. |
||
| 1474 | * |
||
| 1475 | * This is a "pseudo-abstract" methoed, usually connected to a {@link getEditForm()} |
||
| 1476 | * method in an entwine subclass. This method can accept a record identifier, |
||
| 1477 | * selected either in custom logic, or through {@link currentPageID()}. |
||
| 1478 | * The form usually construct itself from {@link DataObject->getCMSFields()} |
||
| 1479 | * for the specific managed subclass defined in {@link LeftAndMain::$tree_class}. |
||
| 1480 | * |
||
| 1481 | * @param HTTPRequest $request Optionally contains an identifier for the |
||
| 1482 | * record to load into the form. |
||
| 1483 | * @return Form Should return a form regardless wether a record has been found. |
||
| 1484 | * Form might be readonly if the current user doesn't have the permission to edit |
||
| 1485 | * the record. |
||
| 1486 | */ |
||
| 1487 | /** |
||
| 1488 | * @return Form |
||
| 1489 | */ |
||
| 1490 | public function EditForm($request = null) { |
||
| 1493 | |||
| 1494 | /** |
||
| 1495 | * Calls {@link SiteTree->getCMSFields()} |
||
| 1496 | * |
||
| 1497 | * @param Int $id |
||
| 1498 | * @param FieldList $fields |
||
| 1499 | * @return Form |
||
| 1500 | */ |
||
| 1501 | public function getEditForm($id = null, $fields = null) { |
||
| 1502 | if(!$id) $id = $this->currentPageID(); |
||
| 1503 | |||
| 1504 | if(is_object($id)) { |
||
| 1636 | |||
| 1637 | /** |
||
| 1638 | * Returns a placeholder form, used by {@link getEditForm()} if no record is selected. |
||
| 1639 | * Our javascript logic always requires a form to be present in the CMS interface. |
||
| 1640 | * |
||
| 1641 | * @return Form |
||
| 1642 | */ |
||
| 1643 | public function EmptyForm() { |
||
| 1671 | |||
| 1672 | /** |
||
| 1673 | * Return the CMS's HTML-editor toolbar |
||
| 1674 | */ |
||
| 1675 | public function EditorToolbar() { |
||
| 1678 | |||
| 1679 | /** |
||
| 1680 | * Renders a panel containing tools which apply to all displayed |
||
| 1681 | * "content" (mostly through {@link EditForm()}), for example a tree navigation or a filter panel. |
||
| 1682 | * Auto-detects applicable templates by naming convention: "<controller classname>_Tools.ss", |
||
| 1683 | * and takes the most specific template (see {@link getTemplatesWithSuffix()}). |
||
| 1684 | * To explicitly disable the panel in the subclass, simply create a more specific, empty template. |
||
| 1685 | * |
||
| 1686 | * @return String HTML |
||
| 1687 | */ |
||
| 1688 | public function Tools() { |
||
| 1697 | |||
| 1698 | /** |
||
| 1699 | * Renders a panel containing tools which apply to the currently displayed edit form. |
||
| 1700 | * The main difference to {@link Tools()} is that the panel is displayed within |
||
| 1701 | * the element structure of the form panel (rendered through {@link EditForm}). |
||
| 1702 | * This means the panel will be loaded alongside new forms, and refreshed upon save, |
||
| 1703 | * which can mean a performance hit, depending on how complex your panel logic gets. |
||
| 1704 | * Any form fields contained in the returned markup will also be submitted with the main form, |
||
| 1705 | * which might be desired depending on the implementation details. |
||
| 1706 | * |
||
| 1707 | * @return String HTML |
||
| 1708 | */ |
||
| 1709 | public function EditFormTools() { |
||
| 1718 | |||
| 1719 | /** |
||
| 1720 | * Batch Actions Handler |
||
| 1721 | */ |
||
| 1722 | public function batchactions() { |
||
| 1725 | |||
| 1726 | /** |
||
| 1727 | * @return Form |
||
| 1728 | */ |
||
| 1729 | public function BatchActionsForm() { |
||
| 1760 | |||
| 1761 | public function printable() { |
||
| 1774 | |||
| 1775 | /** |
||
| 1776 | * Used for preview controls, mainly links which switch between different states of the page. |
||
| 1777 | * |
||
| 1778 | * @return DBHTMLText |
||
| 1779 | */ |
||
| 1780 | public function getSilverStripeNavigator() { |
||
| 1788 | |||
| 1789 | /** |
||
| 1790 | * Identifier for the currently shown record, |
||
| 1791 | * in most cases a database ID. Inspects the following |
||
| 1792 | * sources (in this order): |
||
| 1793 | * - GET/POST parameter named 'ID' |
||
| 1794 | * - URL parameter named 'ID' |
||
| 1795 | * - Session value namespaced by classname, e.g. "CMSMain.currentPage" |
||
| 1796 | * |
||
| 1797 | * @return int |
||
| 1798 | */ |
||
| 1799 | public function currentPageID() { |
||
| 1813 | |||
| 1814 | /** |
||
| 1815 | * Forces the current page to be set in session, |
||
| 1816 | * which can be retrieved later through {@link currentPageID()}. |
||
| 1817 | * Keep in mind that setting an ID through GET/POST or |
||
| 1818 | * as a URL parameter will overrule this value. |
||
| 1819 | * |
||
| 1820 | * @param int $id |
||
| 1821 | */ |
||
| 1822 | public function setCurrentPageID($id) { |
||
| 1826 | |||
| 1827 | /** |
||
| 1828 | * Uses {@link getRecord()} and {@link currentPageID()} |
||
| 1829 | * to get the currently selected record. |
||
| 1830 | * |
||
| 1831 | * @return DataObject |
||
| 1832 | */ |
||
| 1833 | public function currentPage() { |
||
| 1836 | |||
| 1837 | /** |
||
| 1838 | * Compares a given record to the currently selected one (if any). |
||
| 1839 | * Used for marking the current tree node. |
||
| 1840 | * |
||
| 1841 | * @param DataObject $record |
||
| 1842 | * @return bool |
||
| 1843 | */ |
||
| 1844 | public function isCurrentPage(DataObject $record) { |
||
| 1847 | |||
| 1848 | /** |
||
| 1849 | * @return String |
||
| 1850 | */ |
||
| 1851 | protected function sessionNamespace() { |
||
| 1855 | |||
| 1856 | /** |
||
| 1857 | * URL to a previewable record which is shown through this controller. |
||
| 1858 | * The controller might not have any previewable content, in which case |
||
| 1859 | * this method returns FALSE. |
||
| 1860 | * |
||
| 1861 | * @return String|boolean |
||
| 1862 | */ |
||
| 1863 | public function LinkPreview() { |
||
| 1866 | |||
| 1867 | /** |
||
| 1868 | * Return the version number of this application. |
||
| 1869 | * Uses the number in <mymodule>/silverstripe_version |
||
| 1870 | * (automatically replaced by build scripts). |
||
| 1871 | * If silverstripe_version is empty, |
||
| 1872 | * then attempts to get it from composer.lock |
||
| 1873 | * |
||
| 1874 | * @return string |
||
| 1875 | */ |
||
| 1876 | public function CMSVersion() { |
||
| 1935 | |||
| 1936 | /** |
||
| 1937 | * @return array |
||
| 1938 | */ |
||
| 1939 | public function SwitchView() { |
||
| 1945 | |||
| 1946 | /** |
||
| 1947 | * @return SiteConfig |
||
| 1948 | */ |
||
| 1949 | public function SiteConfig() { |
||
| 1952 | |||
| 1953 | /** |
||
| 1954 | * The href for the anchor on the Silverstripe logo. |
||
| 1955 | * Set by calling LeftAndMain::set_application_link() |
||
| 1956 | * |
||
| 1957 | * @config |
||
| 1958 | * @var String |
||
| 1959 | */ |
||
| 1960 | private static $application_link = '//www.silverstripe.org/'; |
||
| 1961 | |||
| 1962 | /** |
||
| 1963 | * @return String |
||
| 1964 | */ |
||
| 1965 | public function ApplicationLink() { |
||
| 1968 | |||
| 1969 | /** |
||
| 1970 | * The application name. Customisable by calling |
||
| 1971 | * LeftAndMain::setApplicationName() - the first parameter. |
||
| 1972 | * |
||
| 1973 | * @config |
||
| 1974 | * @var String |
||
| 1975 | */ |
||
| 1976 | private static $application_name = 'SilverStripe'; |
||
| 1977 | |||
| 1978 | /** |
||
| 1979 | * Get the application name. |
||
| 1980 | * |
||
| 1981 | * @return string |
||
| 1982 | */ |
||
| 1983 | public function getApplicationName() { |
||
| 1986 | |||
| 1987 | /** |
||
| 1988 | * @return string |
||
| 1989 | */ |
||
| 1990 | public function Title() { |
||
| 1995 | |||
| 1996 | /** |
||
| 1997 | * Return the title of the current section. Either this is pulled from |
||
| 1998 | * the current panel's menu_title or from the first active menu |
||
| 1999 | * |
||
| 2000 | * @return string |
||
| 2001 | */ |
||
| 2002 | public function SectionTitle() { |
||
| 2014 | |||
| 2015 | /** |
||
| 2016 | * Same as {@link ViewableData->CSSClasses()}, but with a changed name |
||
| 2017 | * to avoid problems when using {@link ViewableData->customise()} |
||
| 2018 | * (which always returns "ArrayData" from the $original object). |
||
| 2019 | * |
||
| 2020 | * @return String |
||
| 2021 | */ |
||
| 2022 | public function BaseCSSClasses() { |
||
| 2025 | |||
| 2026 | /** |
||
| 2027 | * @return String |
||
| 2028 | */ |
||
| 2029 | public function Locale() { |
||
| 2032 | |||
| 2033 | public function providePermissions() { |
||
| 2078 | |||
| 2079 | } |
||
| 2080 |
Since your code implements the magic setter
_set, this function will be called for any write access on an undefined variable. You can add the@propertyannotation to your class or interface to document the existence of this variable.Since the property has write access only, you can use the @property-write annotation instead.
Of course, you may also just have mistyped another name, in which case you should fix the error.
See also the PhpDoc documentation for @property.