Source of file LeftAndMain.php
Size: 64,187 Bytes - Last Modified: 2021-12-23T10:27:20+00:00
/var/www/docs.ssmods.com/process/src/code/LeftAndMain.php
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989 | <?php namespace SilverStripe\Admin; use BadMethodCallException; use InvalidArgumentException; use LogicException; use ReflectionClass; use SilverStripe\CMS\Controllers\SilverStripeNavigator; use SilverStripe\Control\ContentNegotiator; use SilverStripe\Control\Controller; use SilverStripe\Control\Director; use SilverStripe\Control\HTTPRequest; use SilverStripe\Control\HTTPResponse; use SilverStripe\Control\HTTPResponse_Exception; use SilverStripe\Control\Middleware\HTTPCacheControlMiddleware; use SilverStripe\Control\PjaxResponseNegotiator; use SilverStripe\Core\ClassInfo; use SilverStripe\Core\Config\Config; use SilverStripe\Core\Convert; use SilverStripe\Core\Injector\Injector; use SilverStripe\Core\Manifest\ModuleResourceLoader; use SilverStripe\Core\Manifest\VersionProvider; use SilverStripe\Dev\Deprecation; use SilverStripe\Dev\TestOnly; use SilverStripe\Forms\DropdownField; use SilverStripe\Forms\FieldList; use SilverStripe\Forms\Form; use SilverStripe\Forms\FormAction; use SilverStripe\Forms\HiddenField; use SilverStripe\Forms\HTMLEditor\HTMLEditorConfig; use SilverStripe\Forms\HTMLEditor\TinyMCEConfig; use SilverStripe\Forms\LiteralField; use SilverStripe\Forms\PrintableTransformation; use SilverStripe\Forms\Schema\FormSchema; use SilverStripe\i18n\i18n; use SilverStripe\ORM\ArrayList; use SilverStripe\ORM\CMSPreviewable; use SilverStripe\ORM\DataObject; use SilverStripe\ORM\FieldType\DBField; use SilverStripe\ORM\FieldType\DBHTMLText; use SilverStripe\ORM\Hierarchy\Hierarchy; use SilverStripe\ORM\SS_List; use SilverStripe\ORM\ValidationException; use SilverStripe\ORM\ValidationResult; use SilverStripe\Security\Member; use SilverStripe\Security\Permission; use SilverStripe\Security\PermissionProvider; use SilverStripe\Security\Security; use SilverStripe\Security\SecurityToken; use SilverStripe\SiteConfig\SiteConfig; use SilverStripe\Versioned\Versioned; use SilverStripe\View\ArrayData; use SilverStripe\View\Requirements; use SilverStripe\View\SSViewer; /** * LeftAndMain is the parent class of all the two-pane views in the CMS. * If you are wanting to add more areas to the CMS, you can do it by subclassing LeftAndMain. * * This is essentially an abstract class which should be subclassed. * See {@link CMSMain} for a good example. */ class LeftAndMain extends Controller implements PermissionProvider { /** * Form schema header identifier */ const SCHEMA_HEADER = 'X-Formschema-Request'; /** * Enable front-end debugging (increases verbosity) in dev mode. * Will be ignored in live environments. * * @var bool */ private static $client_debugging = true; /** * The current url segment attached to the LeftAndMain instance * * @config * @var string */ private static $url_segment = null; /** * @config * @var string Used by {@link AdminRootController} to augment Director route rules for sub-classes of LeftAndMain */ private static $url_rule = '/$Action/$ID/$OtherID'; /** * @config * @var string */ private static $menu_title; /** * @config * @var string */ private static $menu_icon; /** * @config * @var int */ private static $menu_priority = 0; /** * @config * @var int */ private static $url_priority = 50; /** * A subclass of {@link DataObject}. * * Determines what is managed in this interface, through * {@link getEditForm()} and other logic. * * @config * @var string */ private static $tree_class = null; /** * @deprecated 5.0 use $help_links instead * * @config * @var string */ private static $help_link = ''; /** * @var array */ private static $allowed_actions = [ 'index', 'save', 'printable', 'show', 'Modals', 'EditForm', 'AddForm', 'batchactions', 'BatchActionsForm', 'schema', 'methodSchema', ]; private static $url_handlers = [ 'GET schema/$FormName/$ItemID/$OtherItemID' => 'schema', 'GET methodSchema/$Method/$FormName/$ItemID' => 'methodSchema', ]; private static $dependencies = [ 'FormSchema' => '%$' . FormSchema::class, 'VersionProvider' => '%$' . VersionProvider::class, ]; /** * Current form schema helper * * @var FormSchema */ protected $schema = null; /** * Current pageID for this request * * @var null */ protected $pageID = null; /** * Assign themes to use for cms * * @config * @var array */ private static $admin_themes = [ 'silverstripe/admin:cms-forms', SSViewer::DEFAULT_THEME, ]; /** * Codes which are required from the current user to view this controller. * If multiple codes are provided, all of them are required. * All CMS controllers require "CMS_ACCESS_LeftAndMain" as a baseline check, * and fall back to "CMS_ACCESS_<class>" if no permissions are defined here. * See {@link canView()} for more details on permission checks. * * @config * @var array */ private static $required_permission_codes; /** * Namespace for session info, e.g. current record. * Defaults to the current class name, but can be amended to share a namespace in case * controllers are logically bundled together, and mainly separated * to achieve more flexible templating. * * @config * @var string */ private static $session_namespace; /** * Register additional requirements through the {@link Requirements} class. * Used mainly to work around the missing "lazy loading" functionality * for getting css/javascript required after an ajax-call (e.g. loading the editform). * * YAML configuration example: * <code> * LeftAndMain: * extra_requirements_javascript: * - mysite/javascript/myscript.js * </code> * * @config * @var array */ private static $extra_requirements_javascript = array(); /** * YAML configuration example: * <code> * LeftAndMain: * extra_requirements_css: * mysite/css/mystyle.css: * media: screen * </code> * * @config * @var array See {@link extra_requirements_javascript} */ private static $extra_requirements_css = array(); /** * @config * @var array See {@link extra_requirements_javascript} */ private static $extra_requirements_themedCss = array(); /** * If true, call a keepalive ping every 5 minutes from the CMS interface, * to ensure that the session never dies. * * @config * @var bool */ private static $session_keepalive_ping = true; /** * Value of X-Frame-Options header * * @config * @var string */ private static $frame_options = 'SAMEORIGIN'; /** * The configuration passed to the supporting JS for each CMS section includes a 'name' key * that by default matches the FQCN of the current class. This setting allows you to change * the key if necessary (for example, if you are overloading CMSMain or another core class * and want to keep the core JS - which depends on the core class names - functioning, you * would need to set this to the FQCN of the class you are overloading). * * @config * @var string|null */ private static $section_name = null; /** * @var PjaxResponseNegotiator */ protected $responseNegotiator; /** * @var VersionProvider */ protected $versionProvider; /** * Gets the combined configuration of all LeftAndMain subclasses required by the client app. * * @return string * * WARNING: Experimental API */ public function getCombinedClientConfig() { $combinedClientConfig = ['sections' => []]; $cmsClassNames = CMSMenu::get_cms_classes(LeftAndMain::class, true, CMSMenu::URL_PRIORITY); // append LeftAndMain to the list as well $cmsClassNames[] = LeftAndMain::class; foreach ($cmsClassNames as $className) { $combinedClientConfig['sections'][] = Injector::inst()->get($className)->getClientConfig(); } // Pass in base url (absolute and relative) $combinedClientConfig['baseUrl'] = Director::baseURL(); $combinedClientConfig['absoluteBaseUrl'] = Director::absoluteBaseURL(); $combinedClientConfig['adminUrl'] = AdminRootController::admin_url(); // Get "global" CSRF token for use in JavaScript $token = SecurityToken::inst(); $combinedClientConfig[$token->getName()] = $token->getValue(); // Set env $combinedClientConfig['environment'] = Director::get_environment_type(); $combinedClientConfig['debugging'] = LeftAndMain::config()->uninherited('client_debugging'); return json_encode($combinedClientConfig); } /** * Returns configuration required by the client app. * * @return array * * WARNING: Experimental API */ public function getClientConfig() { // Allows the section name to be overridden in config $name = $this->config()->get('section_name'); if (!$name) { $name = static::class; } $clientConfig = [ // Trim leading/trailing slash to make it easier to concatenate URL // and use in routing definitions. 'name' => $name, 'url' => trim($this->Link(), '/'), 'form' => [ 'EditorExternalLink' => [ 'schemaUrl' => $this->Link('methodSchema/Modals/EditorExternalLink'), ], 'EditorEmailLink' => [ 'schemaUrl' => $this->Link('methodSchema/Modals/EditorEmailLink'), ], ], ]; $this->extend('updateClientConfig', $clientConfig); return $clientConfig; } /** * Get form schema helper * * @return FormSchema */ public function getFormSchema() { return $this->schema; } /** * Set form schema helper for this controller * * @param FormSchema $schema * @return $this */ public function setFormSchema(FormSchema $schema) { $this->schema = $schema; return $this; } /** * Gets a JSON schema representing the current edit form. * * WARNING: Experimental API. * * @param HTTPRequest $request * @return HTTPResponse */ public function schema($request) { $formName = $request->param('FormName'); $itemID = $request->param('ItemID'); if (!$formName) { $this->jsonError(400, 'Missing request params'); return null; } $formMethod = "get{$formName}"; if (!$this->hasMethod($formMethod)) { $this->jsonError(404, 'Form not found'); return null; } if (!$this->hasAction($formName)) { $this->jsonError(401, 'Form not accessible'); return null; } if ($itemID) { $form = $this->{$formMethod}($itemID); } else { $form = $this->{$formMethod}(); } $schemaID = $request->getURL(); return $this->getSchemaResponse($schemaID, $form); } /** * Return an error HTTPResponse encoded as json * * @param int $errorCode * @param string $errorMessage * @return HTTPResponse * @throws HTTPResponse_Exception */ public function jsonError($errorCode, $errorMessage = null) { // Build error from message $error = [ 'type' => 'error', 'code' => $errorCode, ]; if ($errorMessage) { $error['value'] = $errorMessage; } // Support explicit error handling with status = error, or generic message handling // with a message of type = error $result = [ 'status' => 'error', 'errors' => [$error] ]; $response = HTTPResponse::create(json_encode($result), $errorCode) ->addHeader('Content-Type', 'application/json'); // Call a handler method such as onBeforeHTTPError404 $this->extend("onBeforeJSONError{$errorCode}", $request, $response); // Call a handler method such as onBeforeHTTPError, passing 404 as the first arg $this->extend('onBeforeJSONError', $errorCode, $request, $response); // Throw a new exception throw new HTTPResponse_Exception($response); } /** * @param HTTPRequest $request * @return HTTPResponse */ public function methodSchema($request) { $method = $request->param('Method'); $formName = $request->param('FormName'); $itemID = $request->param('ItemID'); if (!$formName || !$method) { $this->jsonError(400, 'Missing request params'); return null; } if (!$this->hasMethod($method)) { $this->jsonError(404, 'Method not found'); return null; } if (!$this->hasAction($method)) { $this->jsonError(401, 'Method not accessible'); return null; } $methodItem = $this->{$method}(); if (!$methodItem->hasMethod($formName)) { $this->jsonError(404, 'Form not found'); return null; } if (!$methodItem->hasAction($formName)) { $this->jsonError(401, 'Form not accessible'); return null; } $form = $methodItem->{$formName}($itemID); $schemaID = $request->getURL(); return $this->getSchemaResponse($schemaID, $form); } /** * Check if the current request has a X-Formschema-Request header set. * Used by conditional logic that responds to validation results * * @return bool */ protected function getSchemaRequested() { $parts = $this->getRequest()->getHeader(static::SCHEMA_HEADER); return !empty($parts); } /** * Generate schema for the given form based on the X-Formschema-Request header value * * @param string $schemaID ID for this schema. Required. * @param Form $form Required for 'state' or 'schema' response * @param ValidationResult $errors Required for 'error' response * @param array $extraData Any extra data to be merged with the schema response * @return HTTPResponse */ protected function getSchemaResponse($schemaID, $form = null, ValidationResult $errors = null, $extraData = []) { $parts = $this->getRequest()->getHeader(static::SCHEMA_HEADER); $data = $this ->getFormSchema() ->getMultipartSchema($parts, $schemaID, $form, $errors); if ($extraData) { $data = array_merge($data, $extraData); } $response = new HTTPResponse(json_encode($data)); $response->addHeader('Content-Type', 'application/json'); return $response; } /** * @param Member $member * @return bool */ public function canView($member = null) { if (!$member && $member !== false) { $member = Security::getCurrentUser(); } // cms menus only for logged-in members if (!$member) { return false; } // alternative extended checks if ($this->hasMethod('alternateAccessCheck')) { $alternateAllowed = $this->alternateAccessCheck($member); if ($alternateAllowed === false) { return false; } } // Check for "CMS admin" permission if (Permission::checkMember($member, "CMS_ACCESS_LeftAndMain")) { return true; } // Check for LeftAndMain sub-class permissions $codes = $this->getRequiredPermissions(); if ($codes === false) { // allow explicit FALSE to disable subclass check return true; } foreach ((array)$codes as $code) { if (!Permission::checkMember($member, $code)) { return false; } } return true; } /** * Get list of required permissions * * @return array|string|bool Code, array of codes, or false if no permission required */ public static function getRequiredPermissions() { $class = get_called_class(); // If the user is accessing LeftAndMain directly, only generic permissions are required. if ($class === self::class) { return 'CMS_ACCESS'; } $code = Config::inst()->get($class, 'required_permission_codes'); if ($code === false) { return false; } if ($code) { return $code; } return 'CMS_ACCESS_' . $class; } /** * @uses LeftAndMainExtension->init() * @uses LeftAndMainExtension->accessedCMS() * @uses CMSMenu */ protected function init() { parent::init(); HTTPCacheControlMiddleware::singleton()->disableCache(); SSViewer::setRewriteHashLinksDefault(false); ContentNegotiator::setEnabled(false); // set language $member = Security::getCurrentUser(); if (!empty($member->Locale)) { i18n::set_locale($member->Locale); } // Allow customisation of the access check by a extension // Also all the canView() check to execute Controller::redirect() if (!$this->canView() && !$this->getResponse()->isFinished()) { // When access /admin/, we should try a redirect to another part of the admin rather than be locked out $menu = $this->MainMenu(); foreach ($menu as $candidate) { if ($candidate->Link && $candidate->Link != $this->Link() && $candidate->MenuItem->controller && singleton($candidate->MenuItem->controller)->canView() ) { $this->redirect($candidate->Link); return; } } if (Security::getCurrentUser()) { $this->getRequest()->getSession()->clear("BackURL"); } // if no alternate menu items have matched, return a permission error $messageSet = array( 'default' => _t( __CLASS__ . '.PERMDEFAULT', "You must be logged in to access the administration area; please enter your credentials below." ), 'alreadyLoggedIn' => _t( __CLASS__ . '.PERMALREADY', "I'm sorry, but you can't access that part of the CMS. If you want to log in as someone else, do" . " so below." ), 'logInAgain' => _t( __CLASS__ . '.PERMAGAIN', "You have been logged out of the CMS. If you would like to log in again, enter a username and" . " password below." ), ); Security::permissionFailure($this, $messageSet); return; } // Don't continue if there's already been a redirection request. if ($this->redirectedTo()) { return; } // Audit logging hook if (empty($_REQUEST['executeForm']) && !$this->getRequest()->isAjax()) { $this->extend('accessedCMS'); } // Set the members html editor config if (Security::getCurrentUser()) { HTMLEditorConfig::set_active_identifier(Security::getCurrentUser()->getHtmlEditorConfigForCMS()); } // Set default values in the config if missing. These things can't be defined in the config // file because insufficient information exists when that is being processed $htmlEditorConfig = HTMLEditorConfig::get_active(); $htmlEditorConfig->setOption('language', TinyMCEConfig::get_tinymce_lang()); Requirements::customScript(" window.ss = window.ss || {}; window.ss.config = " . $this->getCombinedClientConfig() . "; "); Requirements::javascript('silverstripe/admin: client/dist/js/vendor.js'); Requirements::javascript('silverstripe/admin: client/dist/js/bundle.js'); // Bootstrap components Requirements::javascript('silverstripe/admin: thirdparty/popper/popper.min.js'); Requirements::javascript('silverstripe/admin: thirdparty/bootstrap/js/dist/util.js'); Requirements::javascript('silverstripe/admin: thirdparty/bootstrap/js/dist/collapse.js'); Requirements::javascript('silverstripe/admin: thirdparty/bootstrap/js/dist/tooltip.js'); Requirements::customScript( "window.jQuery('body').tooltip({ selector: '[data-toggle=tooltip]' });", 'bootstrap.tooltip-boot' ); Requirements::css('silverstripe/admin: client/dist/styles/bundle.css'); Requirements::add_i18n_javascript('silverstripe/admin:client/lang'); Requirements::add_i18n_javascript('silverstripe/admin:client/dist/moment-locales', false, false, true); if (LeftAndMain::config()->uninherited('session_keepalive_ping')) { Requirements::javascript('silverstripe/admin: client/dist/js/LeftAndMain.Ping.js'); } // Custom requirements $extraJs = $this->config()->get('extra_requirements_javascript'); if ($extraJs) { foreach ($extraJs as $file => $config) { if (is_numeric($file)) { $file = $config; } Requirements::javascript($file); } } $extraCss = $this->config()->get('extra_requirements_css'); if ($extraCss) { foreach ($extraCss as $file => $config) { if (is_numeric($file)) { $file = $config; $config = array(); } Requirements::css($file, isset($config['media']) ? $config['media'] : null); } } $extraThemedCss = $this->config()->get('extra_requirements_themedCss'); if ($extraThemedCss) { foreach ($extraThemedCss as $file => $config) { if (is_numeric($file)) { $file = $config; $config = array(); } Requirements::themedCSS($file, isset($config['media']) ? $config['media'] : null); } } $this->extend('init'); // Load the editor with original user themes before overwriting // them with admin themes $themes = HTMLEditorConfig::getThemes(); if (empty($themes)) { HTMLEditorConfig::setThemes(SSViewer::get_themes()); } // Assign default cms theme and replace user-specified themes SSViewer::set_themes(LeftAndMain::config()->uninherited('admin_themes')); // Set the current reading mode Versioned::set_stage(Versioned::DRAFT); // Set default reading mode to suppress ?stage=Stage querystring params in CMS Versioned::set_default_reading_mode(Versioned::get_reading_mode()); } public function handleRequest(HTTPRequest $request) { try { $response = parent::handleRequest($request); } catch (ValidationException $e) { // Nicer presentation of model-level validation errors $msgs = _t(__CLASS__ . '.ValidationError', 'Validation error') . ': ' . $e->getMessage(); $e = new HTTPResponse_Exception($msgs, 403); $errorResponse = $e->getResponse(); $errorResponse->addHeader('Content-Type', 'text/plain'); $errorResponse->addHeader('X-Status', rawurlencode($msgs)); $e->setResponse($errorResponse); throw $e; } $title = $this->Title(); if (!$response->getHeader('X-Controller')) { $response->addHeader('X-Controller', static::class); } if (!$response->getHeader('X-Title')) { $response->addHeader('X-Title', urlencode($title)); } // Prevent clickjacking, see https://developer.mozilla.org/en-US/docs/HTTP/X-Frame-Options $originalResponse = $this->getResponse(); $originalResponse->addHeader('X-Frame-Options', LeftAndMain::config()->uninherited('frame_options')); $originalResponse->addHeader('Vary', 'X-Requested-With'); return $response; } /** * Overloaded redirection logic to trigger a fake redirect on ajax requests. * While this violates HTTP principles, its the only way to work around the * fact that browsers handle HTTP redirects opaquely, no intervention via JS is possible. * In isolation, that's not a problem - but combined with history.pushState() * it means we would request the same redirection URL twice if we want to update the URL as well. * See LeftAndMain.js for the required jQuery ajaxComplete handlers. * * @param string $url * @param int $code * @return HTTPResponse|string */ public function redirect($url, $code = 302) { if ($this->getRequest()->isAjax()) { $response = $this->getResponse(); $response->addHeader('X-ControllerURL', $url); if ($this->getRequest()->getHeader('X-Pjax') && !$response->getHeader('X-Pjax')) { $response->addHeader('X-Pjax', $this->getRequest()->getHeader('X-Pjax')); } $newResponse = new LeftAndMain_HTTPResponse( $response->getBody(), $response->getStatusCode(), $response->getStatusDescription() ); foreach ($response->getHeaders() as $k => $v) { $newResponse->addHeader($k, $v); } $newResponse->setIsFinished(true); $this->setResponse($newResponse); return ''; // Actual response will be re-requested by client } else { return parent::redirect($url, $code); } } /** * @param HTTPRequest $request * @return HTTPResponse */ public function index($request) { return $this->getResponseNegotiator()->respond($request); } /** * If this is set to true, the "switchView" context in the * template is shown, with links to the staging and publish site. * * @return bool */ public function ShowSwitchView() { return false; } //------------------------------------------------------------------------------------------// // Main controllers /** * You should implement a Link() function in your subclass of LeftAndMain, * to point to the URL of that particular controller. * * @param string $action * @return string */ public function Link($action = null) { // LeftAndMain methods have a top-level uri access if (static::class === LeftAndMain::class) { $segment = ''; } else { // Get url_segment $segment = $this->config()->get('url_segment'); if (!$segment) { throw new BadMethodCallException( sprintf('LeftAndMain subclasses (%s) must have url_segment', static::class) ); } } $link = Controller::join_links( AdminRootController::admin_url(), $segment, '/', // trailing slash needed if $action is null! "$action" ); $this->extend('updateLink', $link); return $link; } /** * @deprecated 5.0 */ public static function menu_title_for_class($class) { Deprecation::notice('5.0', 'Use menu_title() instead'); return static::menu_title($class, false); } /** * Get menu title for this section (translated) * * @param string $class Optional class name if called on LeftAndMain directly * @param bool $localise Determine if menu title should be localised via i18n. * @return string Menu title for the given class */ public static function menu_title($class = null, $localise = true) { if ($class && is_subclass_of($class, __CLASS__)) { // Respect oveloading of menu_title() in subclasses return $class::menu_title(null, $localise); } if (!$class) { $class = get_called_class(); } // Get default class title $title = static::config()->get('menu_title'); if (!$title) { $title = preg_replace('/Admin$/', '', $class); } // Check localisation if (!$localise) { return $title; } return i18n::_t("{$class}.MENUTITLE", $title); } /** * Return styling for the menu icon, if a custom icon is set for this class * * Example: static $menu-icon = '/path/to/image/'; * @param string $class * @return string */ public static function menu_icon_for_class($class) { $icon = Config::inst()->get($class, 'menu_icon'); if (!empty($icon)) { $iconURL = ModuleResourceLoader::resourceURL($icon); $class = strtolower(Convert::raw2htmlname(str_replace('\\', '-', $class))); return ".icon.icon-16.icon-{$class} { background-image: url('{$iconURL}'); } "; } return ''; } /** * Return the web font icon class name for this interface icon. Uses the * built in SilveStripe webfont. {@see menu_icon_for_class()} for providing * a background image. * * @param string $class . * @return string */ public static function menu_icon_class_for_class($class) { return Config::inst()->get($class, 'menu_icon_class'); } /** * @param HTTPRequest $request * @return HTTPResponse * @throws HTTPResponse_Exception */ public function show($request) { // TODO Necessary for TableListField URLs to work properly if ($request->param('ID')) { $this->setCurrentPageID($request->param('ID')); } return $this->getResponseNegotiator()->respond($request); } /** * Caution: Volatile API. * * @return PjaxResponseNegotiator */ public function getResponseNegotiator() { if (!$this->responseNegotiator) { $this->responseNegotiator = new PjaxResponseNegotiator( array( 'CurrentForm' => function () { return $this->getEditForm()->forTemplate(); }, 'Content' => function () { return $this->renderWith($this->getTemplatesWithSuffix('_Content')); }, 'Breadcrumbs' => function () { return $this->renderWith([ 'type' => 'Includes', 'SilverStripe\\Admin\\CMSBreadcrumbs' ]); }, 'default' => function () { return $this->renderWith($this->getViewer('show')); } ), $this->getResponse() ); } return $this->responseNegotiator; } //------------------------------------------------------------------------------------------// // Main UI components /** * Returns the main menu of the CMS. This is also used by init() * to work out which sections the user has access to. * * @param bool $cached * @return SS_List */ public function MainMenu($cached = true) { if (!isset($this->_cache_MainMenu) || !$cached) { // Don't accidentally return a menu if you're not logged in - it's used to determine access. if (!Security::getCurrentUser()) { return new ArrayList(); } // Encode into DO set $menu = new ArrayList(); $menuItems = CMSMenu::get_viewable_menu_items(); // extra styling for custom menu-icons $menuIconStyling = ''; if ($menuItems) { /** @var CMSMenuItem $menuItem */ foreach ($menuItems as $code => $menuItem) { // alternate permission checks (in addition to LeftAndMain->canView()) if (isset($menuItem->controller) && $this->hasMethod('alternateMenuDisplayCheck') && !$this->alternateMenuDisplayCheck($menuItem->controller) ) { continue; } $linkingmode = "link"; if ($menuItem->controller && get_class($this) == $menuItem->controller) { $linkingmode = "current"; } elseif (strpos($this->Link(), $menuItem->url) !== false) { if ($this->Link() == $menuItem->url) { $linkingmode = "current"; // default menu is the one with a blank {@link url_segment} } elseif (singleton($menuItem->controller)->config()->get('url_segment') == '') { if ($this->Link() == AdminRootController::admin_url()) { $linkingmode = "current"; } } else { $linkingmode = "current"; } } // already set in CMSMenu::populate_menu(), but from a static pre-controller // context, so doesn't respect the current user locale in _t() calls - as a workaround, // we simply call LeftAndMain::menu_title() again // if we're dealing with a controller if ($menuItem->controller) { $title = LeftAndMain::menu_title($menuItem->controller); } else { $title = $menuItem->title; } // Provide styling for custom $menu-icon. Done here instead of in // CMSMenu::populate_menu(), because the icon is part of // the CMS right pane for the specified class as well... $iconClass = ''; $hasCSSIcon = false; if ($menuItem->controller) { $menuIcon = LeftAndMain::menu_icon_for_class($menuItem->controller); if (!empty($menuIcon)) { $menuIconStyling .= $menuIcon; $hasCSSIcon = true; } else { $iconClass = LeftAndMain::menu_icon_class_for_class($menuItem->controller); } } else { $iconClass = $menuItem->iconClass; } $menu->push(new ArrayData(array( "MenuItem" => $menuItem, "AttributesHTML" => $menuItem->getAttributesHTML(), "Title" => $title, "Code" => $code, "Icon" => strtolower($code), "IconClass" => $iconClass, "HasCSSIcon" => $hasCSSIcon, "Link" => $menuItem->url, "LinkingMode" => $linkingmode ))); } } if ($menuIconStyling) { Requirements::customCSS($menuIconStyling); } $this->_cache_MainMenu = $menu; } return $this->_cache_MainMenu; } public function Menu() { return $this->renderWith($this->getTemplatesWithSuffix('_Menu')); } /** * @todo Wrap in CMSMenu instance accessor * @return ArrayData A single menu entry (see {@link MainMenu}) */ public function MenuCurrentItem() { $items = $this->MainMenu(); return $items->find('LinkingMode', 'current'); } /** * Return appropriate template(s) for this class, with the given suffix using * {@link SSViewer::get_templates_by_class()} * * @param string $suffix * @return string|array */ public function getTemplatesWithSuffix($suffix) { $templates = SSViewer::get_templates_by_class(get_class($this), $suffix, __CLASS__); return SSViewer::chooseTemplate($templates); } public function Content() { return $this->renderWith($this->getTemplatesWithSuffix('_Content')); } /** * Render $PreviewPanel content * * @return DBHTMLText */ public function PreviewPanel() { $template = $this->getTemplatesWithSuffix('_PreviewPanel'); // Only render sections with preview panel if ($template) { return $this->renderWith($template); } return null; } /** * Get dataobject from the current ID * * @param int|DataObject $id ID or object * @return DataObject */ public function getRecord($id) { $className = $this->config()->get('tree_class'); if (!$className) { return null; } if ($id instanceof $className) { return $id; } if ($id === 'root') { return DataObject::singleton($className); } if (is_numeric($id)) { return DataObject::get_by_id($className, $id); } return null; } /** * @param bool $unlinked * @return ArrayList */ public function Breadcrumbs($unlinked = false) { $items = new ArrayList(array( new ArrayData(array( 'Title' => $this->menu_title(), 'Link' => ($unlinked) ? false : $this->Link() )) )); return $items; } /** * Cached search filter instance for current search * * @var LeftAndMain_SearchFilter */ protected $searchFilterCache = null; /** * Gets the current search filter for this request, if available * * @throws InvalidArgumentException * @return LeftAndMain_SearchFilter */ protected function getSearchFilter() { if ($this->searchFilterCache) { return $this->searchFilterCache; } // Check for given FilterClass $params = $this->getRequest()->getVar('q'); if (empty($params['FilterClass'])) { return null; } // Validate classname $filterClass = $params['FilterClass']; $filterInfo = new ReflectionClass($filterClass); if (!$filterInfo->implementsInterface(LeftAndMain_SearchFilter::class)) { throw new InvalidArgumentException(sprintf('Invalid filter class passed: %s', $filterClass)); } return $this->searchFilterCache = Injector::inst()->createWithArgs($filterClass, array($params)); } /** * Save handler * * @param array $data * @param Form $form * @return HTTPResponse */ public function save($data, $form) { $request = $this->getRequest(); $className = $this->config()->get('tree_class'); // Existing or new record? $id = $data['ID']; if (is_numeric($id) && $id > 0) { $record = DataObject::get_by_id($className, $id); if ($record && !$record->canEdit()) { return Security::permissionFailure($this); } if (!$record || !$record->ID) { $this->httpError(404, "Bad record ID #" . (int)$id); } } else { if (!singleton($this->config()->get('tree_class'))->canCreate()) { return Security::permissionFailure($this); } $record = $this->getNewItem($id, false); } // save form data into record $form->saveInto($record, true); $record->write(); $this->extend('onAfterSave', $record); $this->setCurrentPageID($record->ID); $message = _t(__CLASS__ . '.SAVEDUP', 'Saved.'); if ($this->getSchemaRequested()) { $schemaId = Controller::join_links($this->Link('schema/DetailEditForm'), $id); // Ensure that newly created records have all their data loaded back into the form. $form->loadDataFrom($record); $form->setMessage($message, 'good'); $response = $this->getSchemaResponse($schemaId, $form); } else { $response = $this->getResponseNegotiator()->respond($request); } $response->addHeader('X-Status', rawurlencode($message)); return $response; } /** * Create new item. * * @param string|int $id * @param bool $setID * @return DataObject */ public function getNewItem($id, $setID = true) { $class = $this->config()->get('tree_class'); $object = Injector::inst()->create($class); if ($setID) { $object->ID = $id; } return $object; } public function delete($data, $form) { $className = $this->config()->get('tree_class'); $id = $data['ID']; $record = DataObject::get_by_id($className, $id); if ($record && !$record->canDelete()) { return Security::permissionFailure(); } if (!$record || !$record->ID) { $this->httpError(404, "Bad record ID #" . (int)$id); } $record->delete(); $this->getResponse()->addHeader( 'X-Status', rawurlencode(_t(__CLASS__ . '.DELETED', 'Deleted.')) ); return $this->getResponseNegotiator()->respond( $this->getRequest(), array('currentform' => array($this, 'EmptyForm')) ); } /** * Retrieves an edit form, either for display, or to process submitted data. * Also used in the template rendered through {@link Right()} in the $EditForm placeholder. * * This is a "pseudo-abstract" methoed, usually connected to a {@link getEditForm()} * method in an entwine subclass. This method can accept a record identifier, * selected either in custom logic, or through {@link currentPageID()}. * The form usually construct itself from {@link DataObject->getCMSFields()} * for the specific managed subclass defined in {@link LeftAndMain::$tree_class}. * * @param HTTPRequest $request Passed if executing a HTTPRequest directly on the form. * If empty, this is invoked as $EditForm in the template * @return Form Should return a form regardless wether a record has been found. * Form might be readonly if the current user doesn't have the permission to edit * the record. */ public function EditForm($request = null) { return $this->getEditForm(); } /** * Calls {@link SiteTree->getCMSFields()} by default to determine the form fields to display. * * @param int $id * @param FieldList $fields * @return Form */ public function getEditForm($id = null, $fields = null) { if (!$id) { $id = $this->currentPageID(); } // Check record exists $record = $this->getRecord($id); if (!$record) { return $this->EmptyForm(); } // Check if this record is viewable if ($record && !$record->canView()) { $response = Security::permissionFailure($this); $this->setResponse($response); return null; } $fields = $fields ?: $record->getCMSFields(); if (!$fields) { throw new LogicException( "getCMSFields() returned null - it should return a FieldList object. Perhaps you forgot to put a return statement at the end of your method?" ); } // Add hidden fields which are required for saving the record // and loading the UI state if (!$fields->dataFieldByName('ClassName')) { $fields->push(new HiddenField('ClassName')); } $tree_class = $this->config()->get('tree_class'); if ($tree_class::has_extension(Hierarchy::class) && !$fields->dataFieldByName('ParentID') ) { $fields->push(new HiddenField('ParentID')); } // Added in-line to the form, but plucked into different view by frontend scripts. if ($record instanceof CMSPreviewable) { /** @skipUpgrade */ $navField = new LiteralField('SilverStripeNavigator', $this->getSilverStripeNavigator()); $navField->setAllowHTML(true); $fields->push($navField); } if ($record->hasMethod('getAllCMSActions')) { $actions = $record->getAllCMSActions(); } else { $actions = $record->getCMSActions(); // add default actions if none are defined if (!$actions || !$actions->count()) { if ($record->hasMethod('canEdit') && $record->canEdit()) { $actions->push( FormAction::create('save', _t('SilverStripe\\CMS\\Controllers\\CMSMain.SAVE', 'Save')) ->addExtraClass('btn btn-primary') ->addExtraClass('font-icon-add-circle') ); } if ($record->hasMethod('canDelete') && $record->canDelete()) { $actions->push( FormAction::create('delete', _t('SilverStripe\\Admin\\ModelAdmin.DELETE', 'Delete')) ->addExtraClass('btn btn-secondary') ); } } } $negotiator = $this->getResponseNegotiator(); $form = Form::create( $this, "EditForm", $fields, $actions )->setHTMLID('Form_EditForm'); $form->addExtraClass('cms-edit-form'); $form->loadDataFrom($record); $form->setTemplate($this->getTemplatesWithSuffix('_EditForm')); $form->setAttribute('data-pjax-fragment', 'CurrentForm'); $form->setValidationResponseCallback(function (ValidationResult $errors) use ($negotiator, $form) { $request = $this->getRequest(); if ($request->isAjax() && $negotiator) { $result = $form->forTemplate(); return $negotiator->respond($request, array( 'CurrentForm' => function () use ($result) { return $result; } )); } return null; }); // Announce the capability so the frontend can decide whether to allow preview or not. if ($record instanceof CMSPreviewable) { $form->addExtraClass('cms-previewable'); } $form->addExtraClass('fill-height'); if ($record->hasMethod('getCMSCompositeValidator')) { // As of framework v4.7, a CompositeValidator is always available form a DataObject, but it may be // empty (which is fine) $form->setValidator($record->getCMSCompositeValidator()); } elseif ($record->hasMethod('getCMSValidator')) { // BC support for framework < v4.7 $validator = $record->getCMSValidator(); // The clientside (mainly LeftAndMain*.js) rely on ajax responses // which can be evaluated as javascript, hence we need // to override any global changes to the validation handler. if ($validator) { $form->setValidator($validator); } } else { $form->unsetValidator(); } // Check if this form is readonly if (!$record->canEdit()) { $readonlyFields = $form->Fields()->makeReadonly(); $form->setFields($readonlyFields); } return $form; } /** * Returns a placeholder form, used by {@link getEditForm()} if no record is selected. * Our javascript logic always requires a form to be present in the CMS interface. * * @return Form */ public function EmptyForm() { $form = Form::create( $this, "EditForm", new FieldList(), new FieldList() )->setHTMLID('Form_EditForm'); $form->unsetValidator(); $form->addExtraClass('cms-edit-form'); $form->addExtraClass('root-form'); $form->setTemplate($this->getTemplatesWithSuffix('_EditForm')); $form->setAttribute('data-pjax-fragment', 'CurrentForm'); return $form; } /** * Handler for all global modals * * @return ModalController */ public function Modals() { return ModalController::create($this, "Modals"); } /** * Renders a panel containing tools which apply to all displayed * "content" (mostly through {@link EditForm()}), for example a tree navigation or a filter panel. * Auto-detects applicable templates by naming convention: "<controller classname>_Tools.ss", * and takes the most specific template (see {@link getTemplatesWithSuffix()}). * To explicitly disable the panel in the subclass, simply create a more specific, empty template. * * @return string HTML */ public function Tools() { $templates = $this->getTemplatesWithSuffix('_Tools'); if ($templates) { $viewer = SSViewer::create($templates); return $viewer->process($this); } else { return false; } } /** * Renders a panel containing tools which apply to the currently displayed edit form. * The main difference to {@link Tools()} is that the panel is displayed within * the element structure of the form panel (rendered through {@link EditForm}). * This means the panel will be loaded alongside new forms, and refreshed upon save, * which can mean a performance hit, depending on how complex your panel logic gets. * Any form fields contained in the returned markup will also be submitted with the main form, * which might be desired depending on the implementation details. * * @return string HTML */ public function EditFormTools() { $templates = $this->getTemplatesWithSuffix('_EditFormTools'); if ($templates) { $viewer = SSViewer::create($templates); return $viewer->process($this); } else { return false; } } /** * Batch Actions Handler */ public function batchactions() { return new CMSBatchActionHandler($this, 'batchactions', $this->config()->get('tree_class')); } /** * @return Form */ public function BatchActionsForm() { $actions = $this->batchactions()->batchActionList(); // Placeholder action $actionsMap = ['-1' => _t(__CLASS__ . '.DropdownBatchActionsDefault', 'Choose an action...')]; foreach ($actions as $action) { $actionsMap[$action->Link] = $action->Title; } $form = new Form( $this, 'BatchActionsForm', new FieldList( new HiddenField('csvIDs'), DropdownField::create( 'Action', false, $actionsMap ) ->setAttribute('autocomplete', 'off') ->setAttribute( 'data-placeholder', _t(__CLASS__ . '.DropdownBatchActionsDefault', 'Choose an action...') ) ), new FieldList( FormAction::create('submit', _t(__CLASS__ . '.SUBMIT_BUTTON_LABEL', "Go")) ->addExtraClass('btn-outline-secondary') ) ); $form->addExtraClass('cms-batch-actions form--no-dividers'); $form->unsetValidator(); $this->extend('updateBatchActionsForm', $form); return $form; } public function printable() { $form = $this->getEditForm($this->currentPageID()); if (!$form) { return false; } $form->transform(new PrintableTransformation()); $form->setActions(null); Requirements::clear(); Requirements::css('silverstripe/admin: dist/css/LeftAndMain_printable.css'); return array( "PrintForm" => $form ); } /** * Used for preview controls, mainly links which switch between different states of the page. * * @return DBHTMLText */ public function getSilverStripeNavigator() { $page = $this->currentPage(); if ($page instanceof CMSPreviewable) { $navigator = new SilverStripeNavigator($page); return $navigator->renderWith($this->getTemplatesWithSuffix('_SilverStripeNavigator')); } return null; } /** * Identifier for the currently shown record, * in most cases a database ID. Inspects the following * sources (in this order): * - GET/POST parameter named 'ID' * - URL parameter named 'ID' * - Session value namespaced by classname, e.g. "CMSMain.currentPage" * * @return int */ public function currentPageID() { if ($this->pageID) { return $this->pageID; } if ($this->getRequest()->requestVar('ID') && is_numeric($this->getRequest()->requestVar('ID'))) { return $this->getRequest()->requestVar('ID'); } if ($this->getRequest()->requestVar('CMSMainCurrentPageID') && is_numeric($this->getRequest()->requestVar('CMSMainCurrentPageID'))) { // see GridFieldDetailForm::ItemEditForm return $this->getRequest()->requestVar('CMSMainCurrentPageID'); } if (isset($this->urlParams['ID']) && is_numeric($this->urlParams['ID'])) { return $this->urlParams['ID']; } if (is_numeric($this->getRequest()->param('ID'))) { return $this->getRequest()->param('ID'); } /** @deprecated */ $session = $this->getRequest()->getSession(); return $session->get($this->sessionNamespace() . ".currentPage") ?: null; } /** * Forces the current page to be set in session, * which can be retrieved later through {@link currentPageID()}. * Keep in mind that setting an ID through GET/POST or * as a URL parameter will overrule this value. * * @param int $id */ public function setCurrentPageID($id) { $this->pageID = $id; $id = (int)$id; /** @deprecated */ $this->getRequest()->getSession()->set($this->sessionNamespace() . ".currentPage", $id); } /** * Uses {@link getRecord()} and {@link currentPageID()} * to get the currently selected record. * * @return DataObject */ public function currentPage() { return $this->getRecord($this->currentPageID()); } /** * Compares a given record to the currently selected one (if any). * Used for marking the current tree node. * * @param DataObject $record * @return bool */ public function isCurrentPage(DataObject $record) { return ($record->ID == $this->currentPageID()); } /** * @return string */ protected function sessionNamespace() { $override = $this->config()->get('session_namespace'); return $override ? $override : static::class; } /** * URL to a previewable record which is shown through this controller. * The controller might not have any previewable content, in which case * this method returns FALSE. * * @return string|bool */ public function LinkPreview() { return false; } /** * Return the version number of this application, ie. 'CMS: 4.2.1' * * @return string */ public function CMSVersion() { return $this->getVersionProvider()->getVersion(); } /** * Return the version number of the CMS in the 'major.minor' format, e.g. '4.2' * Will handle 4.10.x-dev by removing .x-dev * * @return string */ public function CMSVersionNumber() { $moduleName = array_keys($this->getVersionProvider()->getModules())[0]; $lockModules = $this->getVersionProvider()->getModuleVersionFromComposer([$moduleName]); if (!isset($lockModules[$moduleName])) { return ''; } $version = $lockModules[$moduleName]; if (preg_match('#^([0-9]+)\.([0-9]+)#', $version, $m)) { return $m[1] . '.' . $m[2]; } return $version; } /** * @return array */ public function SwitchView() { $page = $this->currentPage(); if (!$page) { return null; } $nav = SilverStripeNavigator::get_for_record($page); return $nav['items']; } /** * @return SiteConfig */ public function SiteConfig() { return class_exists(SiteConfig::class) ? SiteConfig::current_site_config() : null; } /** * The urls used for the links in the Help dropdown in the backend * * @config * @var array */ private static $help_links = [ 'CMS User help' => 'https://userhelp.silverstripe.org/en/4', 'Developer docs' => 'https://docs.silverstripe.org/en/4/', 'Community' => 'https://www.silverstripe.org/', 'Feedback' => 'https://www.silverstripe.org/give-feedback/', ]; /** * Returns help_links in a format readable by a template * @return ArrayList */ public function getHelpLinks() { $helpLinks = $this->config()->get('help_links'); $formattedLinks = []; $helpLink = $this->config()->get('help_link'); if ($helpLink) { Deprecation::notice('5.0', 'Use $help_links instead of $help_link'); $helpLinks['CMS User help'] = $helpLink; } foreach ($helpLinks as $key => $value) { $translationKey = str_replace(' ', '', $key); $formattedLinks[] = [ 'Title' => _t(__CLASS__ . '.' . $translationKey, $key), 'URL' => $value ]; } return ArrayList::create($formattedLinks); } /** * The href for the anchor on the Silverstripe logo * * @config * @var string */ private static $application_link = '//www.silverstripe.org/'; /** * @return string */ public function ApplicationLink() { return $this->config()->get('application_link'); } /** * The application name * * @config * @var string */ private static $application_name = 'Silverstripe'; /** * Get the application name. * * @return string */ public function getApplicationName() { return $this->config()->get('application_name'); } /** * @return string */ public function Title() { $app = $this->getApplicationName(); return ($section = $this->SectionTitle()) ? sprintf('%s - %s', $app, $section) : $app; } /** * Return the title of the current section. Either this is pulled from * the current panel's menu_title or from the first active menu * * @return string */ public function SectionTitle() { $title = $this->menu_title(); if ($title) { return $title; } foreach ($this->MainMenu() as $menuItem) { if ($menuItem->LinkingMode != 'link') { return $menuItem->Title; } } return null; } /** * Generate a logout url with BackURL to the CMS * * @return string */ public function LogoutURL() { return Controller::join_links(Security::logout_url(), '?' . http_build_query([ 'BackURL' => AdminRootController::admin_url(), ])); } /** * Same as {@link ViewableData->CSSClasses()}, but with a changed name * to avoid problems when using {@link ViewableData->customise()} * (which always returns "ArrayData" from the $original object). * * @return string */ public function BaseCSSClasses() { return $this->CSSClasses(Controller::class); } /** * @return string */ public function Locale() { return DBField::create_field('Locale', i18n::get_locale()); } public function providePermissions() { $perms = array( "CMS_ACCESS_LeftAndMain" => array( 'name' => _t(__CLASS__ . '.ACCESSALLINTERFACES', 'Access to all CMS sections'), 'category' => _t('SilverStripe\\Security\\Permission.CMS_ACCESS_CATEGORY', 'CMS Access'), 'help' => _t(__CLASS__ . '.ACCESSALLINTERFACESHELP', 'Overrules more specific access settings.'), 'sort' => -100 ) ); // Add any custom ModelAdmin subclasses. Can't put this on ModelAdmin itself // since its marked abstract, and needs to be singleton instanciated. foreach (ClassInfo::subclassesFor(ModelAdmin::class) as $i => $class) { if ($class === ModelAdmin::class) { continue; } if (ClassInfo::classImplements($class, TestOnly::class)) { continue; } // Check if modeladmin has explicit required_permission_codes option. // If a modeladmin is namespaced you can apply this config to override // the default permission generation based on fully qualified class name. $code = $class::getRequiredPermissions(); if (!$code) { continue; } // Get first permission if multiple specified if (is_array($code)) { $code = reset($code); } $title = LeftAndMain::menu_title($class); $perms[$code] = array( 'name' => _t( 'SilverStripe\\CMS\\Controllers\\CMSMain.ACCESS', "Access to '{title}' section", "Item in permission selection identifying the admin section. Example: Access to 'Files & Images'", array('title' => $title) ), 'category' => _t('SilverStripe\\Security\\Permission.CMS_ACCESS_CATEGORY', 'CMS Access') ); } return $perms; } /** * Set the SilverStripe version provider to use * * @param VersionProvider $provider * @return $this */ public function setVersionProvider(VersionProvider $provider) { $this->versionProvider = $provider; return $this; } /** * Get the SilverStripe version provider * * @return VersionProvider */ public function getVersionProvider() { return $this->versionProvider; } } |