Source of file SinglePageAdmin.php
Size: 19,275 Bytes - Last Modified: 2021-12-23T10:02:22+00:00
/var/www/docs.ssmods.com/process/src/src/SinglePageAdmin.php
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594 | <?php namespace LittleGiant\SinglePageAdmin; use SilverStripe\Admin\LeftAndMain; use SilverStripe\CampaignAdmin\AddToCampaignHandler_FormAction; use SilverStripe\CMS\Controllers\RootURLController; use SilverStripe\CMS\Model\SiteTree; use SilverStripe\Control\Controller; use SilverStripe\Control\Director; use SilverStripe\Control\HTTPResponse; use SilverStripe\Core\ClassInfo; use SilverStripe\Dev\TestOnly; use SilverStripe\Forms\CompositeField; use SilverStripe\Forms\FieldList; use SilverStripe\Forms\FormAction; use SilverStripe\Forms\HiddenField; use SilverStripe\Forms\LiteralField; use SilverStripe\Forms\Tab; use SilverStripe\Forms\TabSet; use SilverStripe\ORM\DataObject; use SilverStripe\ORM\ValidationResult; use SilverStripe\Security\Permission; use SilverStripe\Security\PermissionProvider; use SilverStripe\Security\Security; use SilverStripe\Versioned\Versioned; use SilverStripe\View\Requirements; /** * Defines the Single Page Administration interface for the CMS * * @package SinglePageAdmin * @author Stevie Mayhew */ class SinglePageAdmin extends LeftAndMain implements PermissionProvider { /** * As of 4.0 all subclasses of LeftAndMain have to have a * $url_segment as a result of this, we need to hide the * item from the cms menu */ private static $url_segment = 'little-giant/single-page-admin'; /** * @var string */ private static $menu_title = 'Single Page Admin'; /** * @var string */ private static $url_rule = '/$Action/$ID/$OtherID'; /** * @var string */ private static $menu_icon_class = 'font-icon-edit-list'; /** * @var array */ private static $allowed_actions = [ 'EditForm', ]; /** * It may be desirable to have subclasses of the "single page", but pick up the declared tree_class in the single * page admin as the page instance to be edited. This is probably the desired behaviour by default, but is defaulted * to false to prevent a breaking change to existing code. * @config * @var bool */ private static $ignore_tree_class_subclasses = false; /** * 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; /** * A cached reference to the page record * @var SiteTree */ protected $page = null; /** * Initialize requirements for this view */ public function init() { parent::init(); Requirements::javascript('silverstripe/cms: client/dist/js/bundle.js'); Requirements::javascript('silverstripe/cms: client/dist/js/SilverStripeNavigator.js'); Requirements::css('silverstripe/cms: client/dist/styles/bundle.css'); Requirements::add_i18n_javascript('silverstripe/cms: client/lang', false, true); } /** * Helper function for getting the single page instance, existing or created * @return SiteTree */ protected function findOrMakePage() { if ($this->page !== null) { return $this->page; } $currentStage = Versioned::get_stage(); Versioned::set_stage(Versioned::DRAFT); /** @var \SilverStripe\CMS\Model\SiteTree $treeClass */ $treeClass = static::config()->get('tree_class'); $treeObjects = DataObject::get($treeClass); if (static::config()->get('ignore_tree_class_subclasses')) { $treeObjects = $treeObjects->filter('ClassName', $treeClass); } /** @var \SilverStripe\CMS\Model\SiteTree|null $page */ $page = $treeObjects->first(); if ($page === null) { $page = $treeClass::create(); if (empty($page->Title)) { $page->Title = str_replace('Page', '', ClassInfo::shortName($treeClass)); } $page->write(); } Versioned::set_stage($currentStage); $this->page = $page; return $page; } /** * @param null $member * @return bool|int */ public function canView($member = null) { if (!$member && $member !== false) { $member = Security::getCurrentUser(); } $codes = []; $extraCodes = $this->config()->get('required_permission_codes'); if ($extraCodes !== false) { // allow explicit FALSE to disable subclass check if ($extraCodes) { $codes = array_merge($codes, (array)$extraCodes); } else { $class = static::class; $codes[] = "CMS_ACCESS_$class"; } } foreach ($codes as $code) { if (!Permission::checkMember($member, $code)) { return false; } } return parent::canView($member); } /** * @return array */ public function providePermissions() { $perms = []; // Add any custom SinglePageAdmin subclasses. foreach (ClassInfo::subclassesFor(SinglePageAdmin::class) as $i => $class) { if ($class === SinglePageAdmin::class || ClassInfo::classImplements($class, TestOnly::class)) { continue; } $title = _t("{$class}.MENUTITLE", LeftAndMain::menu_title($class)); $perms["CMS_ACCESS_" . $class] = [ 'name' => _t( 'CMSMain.ACCESS', "Access to '{title}' section", "Item in permission selection identifying the admin section. Example: Access to 'Files & Images'", ['title' => $title] ), 'category' => _t('Permission.CMS_ACCESS_CATEGORY', 'CMS Access'), ]; } return $perms; } /** * @param null $id * @param null $fields * @return $this|null|\SilverStripe\Forms\Form */ public function getEditForm($id = null, $fields = null) { $page = $this->findOrMakePage(); $fields = $page->getCMSFields(); $fields->push(new HiddenField('PreviewURL', 'Preview URL', RootURLController::get_homepage_link())); $fields->push($navField = new LiteralField('SilverStripeNavigator', $this->getSilverStripeNavigator())); $navField->setAllowHTML(true); $currentStage = Versioned::get_stage(); Versioned::set_stage(Versioned::DRAFT); // Check record exists if (!$page) { return $this->EmptyForm(); } // Check if this record is viewable if ($page && !$page->canView()) { $response = Security::permissionFailure($this); $this->setResponse($response); return null; } $negotiator = $this->getResponseNegotiator(); $form = SinglePageCMSForm::create( $this, "EditForm", $fields, $this->getCMSActions() )->setHTMLID('Form_EditForm'); $form->addExtraClass('cms-edit-form fill-height flexbox-area-grow'); $form->loadDataFrom($page); $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, [ 'CurrentForm' => function () use ($result) { return $result; }, ]); } return null; }); $this->extend('updateEditForm', $form); Versioned::set_stage($currentStage); return $form; } /** * @param null $request * @return null|\SilverStripe\Forms\Form|SinglePageAdmin */ public function EditForm($request = null) { return $this->getEditForm(); } /** * @desc This function is necessary for for some module functionality that relies on the controller having the current page ID implemented * @return mixed */ public function currentPageID() { return $this->findOrMakePage()->ID; } /** * @desc Used for preview controls, mainly links which switch between different states of the page. * @return bool */ public function getSilverStripeNavigator() { return false; } /** * @return mixed */ public function getResponseNegotiator() { $neg = parent::getResponseNegotiator(); $controller = $this; $neg->setCallback('CurrentForm', function () use (&$controller) { error_log($controller->renderWith($this->getTemplatesWithSuffix('_EditForm'))); return $controller->renderWith($this->getTemplatesWithSuffix('_EditForm')); }); return $neg; } /** * @return mixed */ public function LinkPreview() { $page = $this->findOrMakePage(); $baseLink = ($page && $page instanceof SiteTree) ? $page->Link('?stage=Stage') : Director::absoluteBaseURL(); return $baseLink; } /** * @return FieldList */ protected function getCMSActions() { $page = $this->findOrMakePage(); // Get status of page $isPublished = $page->isPublished(); $isOnDraft = $page->isOnDraft(); $stagesDiffer = $page->stagesDiffer('Stage', 'Live'); // Check permissions $canPublish = $page->canPublish(); $canUnpublish = $page->canUnpublish(); $canEdit = $page->canEdit(); // Major actions appear as buttons immediately visible as page actions. $majorActions = CompositeField::create()->setName('MajorActions'); $majorActions->setFieldHolderTemplate(get_class($majorActions) . '_holder_buttongroup'); // "save", supports an alternate state that is still clickable, but notifies the user that the action is not needed. $noChangesClasses = 'btn-outline-primary font-icon-tick'; if ($canEdit && $isOnDraft) { $majorActions->push( FormAction::create('save', _t(__CLASS__ . '.BUTTONSAVED', 'Saved')) ->addExtraClass($noChangesClasses) ->setAttribute('data-btn-alternate-add', 'btn-primary font-icon-save') ->setAttribute('data-btn-alternate-remove', $noChangesClasses) ->setUseButtonTag(true) ->setAttribute('data-text-alternate', _t('SilverStripe\\CMS\\Controllers\\CMSMain.SAVEDRAFT', 'Save draft')) ); } // "publish", as with "save", it supports an alternate state to show when action is needed. if ($canPublish && $isOnDraft) { $majorActions->push( $publish = FormAction::create('publish', _t(__CLASS__ . '.BUTTONPUBLISHED', 'Published')) ->addExtraClass($noChangesClasses) ->setAttribute('data-btn-alternate-add', 'btn-primary font-icon-rocket') ->setAttribute('data-btn-alternate-remove', $noChangesClasses) ->setUseButtonTag(true) ->setAttribute('data-text-alternate', _t(__CLASS__ . '.BUTTONSAVEPUBLISH', 'Save & publish')) ); // Set up the initial state of the button to reflect the state of the underlying SiteTree object. if ($stagesDiffer) { $publish->addExtraClass('btn-primary font-icon-rocket'); $publish->setTitle(_t(__CLASS__ . '.BUTTONSAVEPUBLISH', 'Save & publish')); $publish->removeExtraClass($noChangesClasses); } } // Minor options are hidden behind a drop-up and appear as links (although they are still FormActions). $rootTabSet = new TabSet('ActionMenus'); $moreOptions = new Tab( 'MoreOptions', _t(__CLASS__ . '.MoreOptions', 'More options', 'Expands a view for more buttons') ); $moreOptions->addExtraClass('popover-actions-simulate'); $rootTabSet->push($moreOptions); $rootTabSet->addExtraClass('ss-ui-action-tabset action-menus noborder'); // Add to campaign option if not-archived and has publish permission if (($isPublished || $isOnDraft) && $canPublish) { $moreOptions->push( AddToCampaignHandler_FormAction::create() ->removeExtraClass('btn-primary') ->addExtraClass('btn-secondary') ); } // Rollback if ($isOnDraft && $isPublished && $canEdit && $stagesDiffer) { $moreOptions->push( FormAction::create('rollback', _t(__CLASS__ . '.BUTTONCANCELDRAFT', 'Cancel draft changes')) ->setDescription(_t( 'SilverStripe\\CMS\\Model\\SiteTree.BUTTONCANCELDRAFTDESC', 'Delete your draft and revert to the currently published page' )) ->addExtraClass('btn-secondary') ); } // Unpublish if ($isPublished && $canPublish && $isOnDraft && $canUnpublish) { $moreOptions->push( FormAction::create('unpublish', _t(__CLASS__ . '.BUTTONUNPUBLISH', 'Unpublish'), 'delete') ->setDescription(_t(__CLASS__ . '.BUTTONUNPUBLISHDESC', 'Remove this page from the published site')) ->addExtraClass('btn-secondary') ); } $actions = new FieldList([$majorActions, $rootTabSet]); $this->extend('updateCMSActions', $actions); return $actions; } /** * @param array $data * @param \SilverStripe\Forms\Form $form * @return mixed */ public function save($data, $form) { $currentStage = Versioned::get_stage(); Versioned::set_stage(Versioned::DRAFT); $value = $this->doSave($data, $form); Versioned::set_stage($currentStage); return $value; } /** * @param $data * @param $form * @return mixed */ public function publish($data, $form) { $data['__publish__'] = '1'; return $this->doSave($data, $form); } /** * @desc Save the page * @param $data * @param $form * @return mixed|HTTPResponse */ public function doSave($data, $form) { $page = $this->findOrMakePage(); $controller = Controller::curr(); $publish = isset($data['__publish__']); // Check publishing permissions $doPublish = !empty($publish); if ($page && $doPublish && !$page->canPublish()) { return Security::permissionFailure($this); } // save form data into record $form->saveInto($page, true); $page->write(); $this->extend('onAfterSave', $page); // Set the response message // If the 'Save & Publish' button was clicked, also publish the page if ($doPublish) { $page->publishRecursive(); $message = _t( 'SilverStripe\\CMS\\Controllers\\CMSMain.PUBLISHED', "Published '{title}' successfully.", ['title' => $page->Title] ); } else { $message = _t( 'SilverStripe\\CMS\\Controllers\\CMSMain.SAVED', "Saved '{title}' successfully.", ['title' => $page->Title] ); } $this->getResponse()->addHeader('X-Status', rawurlencode($message)); return $this->edit($controller->getRequest()); } /** * @desc Unpublish the page * @return mixed */ public function unPublish() { $currentStage = Versioned::get_stage(); Versioned::set_stage(Versioned::LIVE); $page = $this->findOrMakePage(); // This way our ID won't be unset $clone = clone $page; $clone->delete(); Versioned::set_stage($currentStage); return $this->edit(Controller::curr()->getRequest()); } /** * @param $data * @param $form * @return HTTPResponse */ public function rollback($data, $form) { $page = $this->findOrMakePage(); $page->extend('onBeforeRollback', $page->ID, $page->Version); $id = (isset($page->ID)) ? (int)$page->ID : null; $version = (isset($page->Version)) ? (int)$page->Version : null; /** @var DataObject|Versioned $record */ $record = Versioned::get_latest_version($this->config()->get('tree_class'), $id); if ($record && !$record->canEdit()) { return Security::permissionFailure($this); } if ($version) { $record->doRollbackTo($version); $message = _t( __CLASS__ . '.ROLLEDBACKVERSIONv2', "Rolled back to version #{version}.", ['version' => $page->Version] ); } else { $record->doRevertToLive(); $message = _t( __CLASS__ . '.ROLLEDBACKPUBv2', "Rolled back to published version." ); } $this->getResponse()->addHeader('X-Status', rawurlencode($message)); // Can be used in different contexts: In normal page edit view, in which case the redirect won't have any effect. // Or in history view, in which case a revert causes the CMS to re-load the edit view. // The X-Pjax header forces a "full" content refresh on redirect. $url = Controller::curr()->getRequest(); $this->getResponse()->addHeader('X-ControllerURL', $url->getURL()); // @TODO: Redirect to the base url of the form - 24/11/17 Ryan Potter $this->getRequest()->addHeader('X-Pjax', 'Content'); $this->getResponse()->addHeader('X-Pjax', 'Content'); return $this->getResponseNegotiator()->respond($this->getRequest()); } /** * @param $request * @return mixed */ public function edit($request) { $controller = Controller::curr(); $form = $this->EditForm($request); $return = $this->customise([ 'Backlink' => $controller->hasMethod('Backlink') ? $controller->Backlink() : $controller->Link(), 'EditForm' => $form, ])->renderWith($this->getTemplatesWithSuffix('_Content')); if ($request->isAjax()) { return $return; } else { return $controller->customise([ 'Content' => $return, ]); } } /** * @return string */ public function Backlink() { return $this->Link(); } } |