Source of file GridFieldBetterButtonsItemRequest.php
Size: 20,091 Bytes - Last Modified: 2021-12-23T10:56:01+00:00
/var/www/docs.ssmods.com/process/src/src/Extensions/GridFieldBetterButtonsItemRequest.php
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597 | <?php namespace UncleCheese\BetterButtons\Extensions; use Exception; use SilverStripe\Admin\LeftAndMain; use SilverStripe\Control\Controller; use SilverStripe\Control\HTTPRequest; use SilverStripe\Control\PjaxResponseNegotiator; use SilverStripe\Core\Convert; use SilverStripe\Forms\FieldList; use SilverStripe\Forms\Form; use SilverStripe\Forms\GridField\GridFieldDetailForm_ItemRequest; use SilverStripe\Forms\TabSet; use SilverStripe\ORM\DataExtension; use SilverStripe\ORM\DataList; use SilverStripe\ORM\DataModel; use SilverStripe\ORM\DataObject; use SilverStripe\ORM\DB; use SilverStripe\ORM\ManyManyList; use SilverStripe\ORM\ValidationException; use SilverStripe\Versioned\Versioned; use SilverStripe\View\Requirements; use UncleCheese\BetterButtons\Controllers\BetterButtonsCustomActionRequest; use UncleCheese\BetterButtons\Controllers\BetterButtonsNestedFormRequest; use UncleCheese\BetterButtons\Interfaces\BetterButtonInterface; use UncleCheese\BetterButtons\Interfaces\BetterButton_Versioned; /** * Decorates {@link GridDetailForm_ItemRequest} to use new form actions and buttons. * * @author Uncle Cheese <unclecheese@leftandmain.com> * @package silverstripe-gridfield-betterbuttons */ class GridFieldBetterButtonsItemRequest extends DataExtension { /** * @var array Allowed controller actions */ private static $allowed_actions = array( 'addnew', 'edit', 'save', 'cancel', 'publish', 'rollback', 'unpublish', 'ItemEditForm', 'doNew', 'doSaveAndAdd', 'doSaveAndQuit', 'doPublishAndAdd', 'doPublishAndClose', 'doSaveAndNext', 'doSaveAndPrev', 'doDelete', 'customaction', 'nestedform', ); /** * Handles all custom action from DataObjects and hands them off to a sub-controller. * e.g. /customaction/mymethodname * * Can't handle the actions here because the url_param '$Action!' gets matched, and we don't * get to read anything after /customaction/ * * @param HTTPRequest $r * @return BetterButtonsCustomActionRequest */ public function customaction(HTTPRequest $r) { $req = new BetterButtonsCustomActionRequest($this, $this->owner, $this->owner->ItemEditForm()); return $req->handleRequest($r, DataModel::inst()); } /** * Handles all custom action from DataObjects and hands them off to a sub-controller. * e.g. /nestedform?action=myDataObjectAction * * @param HTTPRequest $r * @return BetterButtonsNestedFormRequest */ public function nestedform(HTTPRequest $r) { $req = new BetterButtonsNestedFormRequest($this, $this->owner, $this->owner->ItemEditForm()); return $req->handleRequest($r, DataModel::inst()); } /** * Redirecting to the current URL doesn't do anything, so this is just a dummy action * that gives the request somewhere to go in order to force a reload, and then just * redirects back to the original link. * * @param HTTPRequest The request object */ public function addnew(HTTPRequest $r) { return Controller::curr()->redirect(Controller::join_links($this->owner->gridField->Link("item"), "new")); } /** * Updates the detail form to include new form actions and buttons * * @param Form The ItemEditForm object */ public function updateItemEditForm($form) { if ($this->owner->record->stat('better_buttons_enabled') !== true) { return false; } Requirements::css(BETTER_BUTTONS_DIR.'/css/gridfield_betterbuttons.css'); Requirements::javascript(BETTER_BUTTONS_DIR.'/javascript/gridfield_betterbuttons.js'); $actions = $this->owner->record->getBetterButtonsActions(); $form->setActions($this->filterFieldList($form, $actions)); if ($form->Fields()->hasTabSet()) { $form->Fields()->findOrMakeTab('Root')->setTemplate(TabSet::class); $form->addExtraClass('cms-tabset'); } $utils = $this->owner->record->getBetterButtonsUtils(); $form->Utils = $this->filterFieldList($form, $utils); $form->setTemplate([ 'type' => 'Includes', 'BetterButtons_EditForm', ]); $form->addExtraClass('better-buttons-form'); } /** * Given a list of actions, remove anything that doesn't belong. * @param Form $form * @param FieldList $actions * @return FieldList */ protected function filterFieldList(Form $form, FieldList $actions) { $list = FieldList::create(); foreach ($actions as $a) { if (!$a instanceof BetterButtonInterface) { throw new Exception("{$buttonObj->class} must implement BetterButtonInterface"); } $a->bindGridField($form, $this->owner); if (!$a->shouldDisplay()) { continue; } if (($a instanceof BetterButton_Versioned) && !$this->owner->record->checkVersioned()) { continue; } $list->push($a); } return $list; } /** * Saves the form and forwards to a blank form to continue creating * * @param array The form data * @param Form The form object */ public function doSaveAndAdd($data, $form) { return $this->saveAndRedirect($data, $form, $this->owner->Link("addnew")); } /** * Saves the form and goes back to list view * * * @param array The form data * @param Form The form object */ public function doSaveAndQuit($data, $form) { Controller::curr()->getResponse()->addHeader("X-Pjax", "Content"); return $this->saveAndRedirect($data, $form, $this->getBackLink()); } /** * Publishes the record and goes to make a new record * @param array $data The form data * @param Form $form The Form object * @return HTTPResponse */ public function doPublishAndAdd($data, $form) { return $this->publish($data, $form, $this->owner, $this->owner->Link('addnew')); } /** * Publishes the record and closes the detail form * @param array $data The form data * @param Form $form The Form object * @return HTTPResponse */ public function doPublishAndClose($data, $form) { Controller::curr()->getResponse()->addHeader("X-Pjax", "Content"); return $this->publish($data, $form, $this->owner, $this->getBackLink()); } /** * Goes back to list view * * @param array The form data * @param Form The form object */ public function cancel() { Controller::curr()->getResponse()->addHeader("X-Pjax", "Content"); return Controller::curr()->redirect($this->getBackLink()); } /** * Saves the record and goes to the next one * @param arary $data The form data * @param Form $form The Form object * @return HTTPResponse */ public function doSaveAndNext($data, $form) { Controller::curr()->getResponse()->addHeader("X-Pjax", "Content"); $link = $this->getEditLink($this->getNextRecordID()); return $this->saveAndRedirect($data, $form, $link); } /** * Saves the record and goes to the previous one * @param arary $data The form data * @param Form $form The Form object * @return HTTPResponse */ public function doSaveAndPrev($data, $form) { Controller::curr()->getResponse()->addHeader("X-Pjax", "Content"); $link = $this->getEditLink($this->getPreviousRecordID()); return $this->saveAndRedirect($data, $form, $link); } /** * Gets the edit link for a record * @param int $id The ID of the record in the GridField * @return string */ public function getEditLink($id) { return Controller::join_links($this->owner->gridField->Link(), "item", $id); } /** * Creates a new record. If you're already creating a new record, * this forces the URL to change. Hacky UI workaround. * * @param arary $data The form data * @param Form $form The Form object * @return HTTPResponse */ public function doNew($data, $form) { return Controller::curr()->redirect($this->owner->Link('addnew')); } /** * Allows us to have our own configurable save button * @param arary $data The form data * @param Form $form The Form object * @return HTTPResponse */ public function save($data, $form) { $origStage = Versioned::get_stage(); Versioned::set_stage('Stage'); $action = $this->owner->doSave($data, $form); Versioned::set_stage($origStage); return $action; } /** * @param array $data * @param Form $form * @param HTTPRequest $request * @param string $redirectURL * @return HTMLText|HTTPResponse|ViewableData_Customised */ public function publish($data, $form, $request = null, $redirectURL = null) { $new_record = $this->owner->record->ID == 0; $controller = Controller::curr(); $list = $this->owner->gridField->getList(); if ($list instanceof ManyManyList) { // Data is escaped in ManyManyList->add() $extraData = (isset($data['ManyMany'])) ? $data['ManyMany'] : null; } else { $extraData = null; } if (isset($data['ClassName']) && $data['ClassName'] != $this->owner->record->ClassName) { $newClassName = $data['ClassName']; // The records originally saved attribute was overwritten by $form->saveInto($record) before. // This is necessary for newClassInstance() to work as expected, and trigger change detection // on the ClassName attribute $this->owner->record->setClassName($this->owner->record->ClassName); // Replace $record with a new instance $this->owner->record = $this->owner->record->newClassInstance($newClassName); } if (!$this->owner->record->canEdit()) { return $controller->httpError(403); } try { $this->save($data, $form); $list->add($this->owner->record, $extraData); $this->owner->record->invokeWithExtensions('onBeforePublish', $this->owner->record); $this->owner->record->publish('Stage', 'Live'); $this->owner->record->invokeWithExtensions('onAfterPublish', $this->owner->record); } catch (ValidationException $e) { $form->sessionMessage($e->getResult()->message(), 'bad'); $responseNegotiator = new PjaxResponseNegotiator(array( 'CurrentForm' => function () use (&$form) { return $form->forTemplate(); }, 'default' => function () use (&$controller) { return $controller->redirectBack(); } )); if ($controller->getRequest()->isAjax()) { $controller->getRequest()->addHeader('X-Pjax', 'CurrentForm'); } return $responseNegotiator->respond($controller->getRequest()); } // TODO Save this item into the given relationship if ($redirectURL) { return $controller->redirect($redirectURL); } $title = '"' . Convert::raw2xml($this->owner->record->Title) . '"'; $message = sprintf( 'Published %s %s', $this->owner->record->i18n_singular_name(), $title ); $form->sessionMessage($message, 'good'); if ($new_record) { return Controller::curr()->redirect($this->owner->Link()); } elseif ($this->owner->gridField->getList()->byId($this->owner->record->ID)) { // Return new view, as we can't do a "virtual redirect" via the CMS Ajax // to the same URL (it assumes that its content is already current, and doesn't reload) return $this->owner->edit(Controller::curr()->getRequest()); } else { // Changes to the record properties might've excluded the record from // a filtered list, so return back to the main view if it can't be found $noActionURL = $controller->removeAction($data['url']); $controller->getRequest()->addHeader('X-Pjax', 'Content'); return $controller->redirect($noActionURL, 302); } } /** * Unpublishes the record * * @return HTMLText|ViewableData_Customised */ public function unPublish() { $origStage = Versioned::get_stage(); Versioned::set_stage('Live'); // This way our ID won't be unset $clone = clone $this->owner->record; $clone->delete(); Versioned::set_stage($origStage); return $this->owner->edit(Controller::curr()->getRequest()); } /** * @param array $data * @param Form $form * @return HTMLText|ViewableData_Customised */ public function rollback($data, $form) { if (!$this->owner->record->canEdit()) { return Controller::curr()->httpError(403); } $this->owner->record->doRollbackTo('Live'); $this->owner->record = DataList::create($this->owner->record->class)->byID($this->owner->record->ID); $message = _t( 'CMSMain.ROLLEDBACKPUBv2', "Rolled back to published version." ); $form->sessionMessage($message, 'good'); return $this->owner->edit(Controller::curr()->getRequest()); } /** * Gets the top level controller. * * @return Controller * @todo This had to be directly copied from {@link GridFieldDetailForm_ItemRequest} * because it is a protected method and not visible to a decorator! */ protected function getToplevelController() { $c = $this->owner->getController(); while ($c && $c instanceof GridFieldDetailForm_ItemRequest) { $c = $c->getController(); } return $c; } /** * Gets the back link * * @return string * @todo This had to be directly copied from {@link GridFieldDetailForm_ItemRequest} * because it is a protected method and not visible to a decorator! */ public function getBackLink() { // TODO Coupling with CMS $backlink = ''; $toplevelController = $this->getToplevelController(); if ($toplevelController && $toplevelController instanceof LeftAndMain) { if ($toplevelController->hasMethod('Backlink')) { $backlink = $toplevelController->Backlink(); } elseif ($this->owner->getController()->hasMethod('Breadcrumbs')) { $parents = $this->owner->getController()->Breadcrumbs(false)->items; $backlink = array_pop($parents)->Link; } } if (!$backlink) { $backlink = $toplevelController->Link(); } return $backlink; } /** * Oh, the horror! DRY police be advised. This function is a serious offender. * Saves the form data and redirects to a given link * * @param array $data The form data * @param Form $form The form object * @param string $redirectLink The redirect link * @todo GridFieldDetailForm_ItemRequest::doSave is too monolithic, making overloading impossible. Most * of this code is a direct copy. * */ protected function saveAndRedirect($data, $form, $redirectLink) { $new_record = $this->owner->record->ID == 0; $controller = Controller::curr(); $list = $this->owner->gridField->getList(); if ($list instanceof ManyManyList) { // Data is escaped in ManyManyList->add() $extraData = (isset($data['ManyMany'])) ? $data['ManyMany'] : null; } else { $extraData = null; } if (!$this->owner->record->canEdit()) { return $controller->httpError(403); } try { $form->saveInto($this->owner->record); $this->owner->record->write(); $list->add($this->owner->record, $extraData); } catch (ValidationException $e) { $form->sessionMessage($e->getResult()->message(), 'bad'); $responseNegotiator = new PjaxResponseNegotiator(array( 'CurrentForm' => function () use ($form) { return $form->forTemplate(); }, 'default' => function () use ($controller) { return $controller->redirectBack(); } )); if ($controller->getRequest()->isAjax()) { $controller->getRequest()->addHeader('X-Pjax', 'CurrentForm'); } return $responseNegotiator->respond($controller->getRequest()); } return Controller::curr()->redirect($redirectLink); } /** * Gets the ID of the previous record in the list. * WARNING: This does not respect the mutated state of the list (e.g. sorting or filtering). * Currently the GridField API does not expose this in the detail form view. * * @todo This method is very inefficient. * @return int */ public function getPreviousRecordID() { $map = $this->owner->gridField->getManipulatedList()->limit(PHP_INT_MAX, 0)->column('ID'); $offset = array_search($this->owner->record->ID, $map); return isset($map[$offset-1]) ? $map[$offset-1] : false; } /** * Gets the ID of the next record in the list. * WARNING: This does not respect the mutated state of the list (e.g. sorting or filtering). * Currently the GridField API does not expose this in the detail form view. * * @todo This method is very inefficient. * @return int */ public function getNextRecordID() { $map = $this->owner->gridField->getManipulatedList()->limit(PHP_INT_MAX, 0)->column('ID'); // If there are a million results and they were paginated, this is going to be slow now // TODO: Search in the paginated list only somehow (grab the limit + offset and search from there?) $offset = array_search($this->owner->record->ID, $map); return isset($map[$offset+1]) ? $map[$offset+1] : false; } /** * Determines if the current record is published * @return boolean */ public function recordIsPublished() { if (!$this->owner->record->checkVersioned()) { return false; } if (!$this->owner->record->isInDB()) { return false; } $baseClass = DataObject::getSchema()->baseDataClass($this->owner->record); $stageTable = DataObject::getSchema()->tableName($baseClass) . '_Live'; return (bool) DB::query("SELECT \"ID\" FROM \"{$stageTable}\" WHERE \"ID\" = {$this->owner->record->ID}") ->value(); } /** * Determines if the current record is deleted from stage * @return boolean */ public function recordIsDeletedFromStage() { // for SiteTree records if ($this->owner->hasMethod('getIsDeletedFromStage')) { return $this->owner->IsDeletedFromStage; } if (!$this->owner->record->checkVersioned()) { return false; } if (!$this->owner->record->isInDB()) { return true; } $class = $this->owner->record->class; $stageVersion = Versioned::get_versionnumber_by_stage($class, 'Stage', $this->owner->record->ID); // Return true for both completely deleted pages and for pages just deleted from stage return !($stageVersion); } } |