Source of file ContentModuleMain.php
Size: 48,822 Bytes - Last Modified: 2021-12-23T10:59:17+00:00
/var/www/docs.ssmods.com/process/src/code/admin/ContentModuleMain.php
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449 | <?php /** * The main "content" area of the CMS. * * This class creates a 2-frame layout - left-tree and right-form - to sit beneath the main * admin menu. * * @package inpage-modules */ class ContentModuleMain extends LeftAndMain implements PermissionProvider { private static $url_segment = 'content-modules'; private static $url_rule = '/$Action/$ID/$OtherID'; // Maintain a lower priority than other administration sections // so that Director does not think they are actions of CMSMain private static $url_priority = 30; private static $menu_title = 'Edit Module'; private static $menu_icon = 'inpage-modules/images/icon.png'; private static $tree_class = 'ContentModule'; private static $menu_priority = 50; private static $page_id = 0; private static $subitem_class = "Member"; private static $allowed_actions = array( 'deleteitems', 'DeleteItemsForm', 'dialog', 'duplicate', 'PublishItemsForm', 'submit', 'EditForm', 'SearchForm', 'batchactions', 'listview', 'ListViewForm', 'publish', 'delete' ); public function init() { // set reading lang if (Object::has_extension('ContentModule', 'Translatable') && !$this->request->isAjax()) { Translatable::choose_site_locale(array_keys(Translatable::get_existing_content_languages('ContentModule'))); } parent::init(); Versioned::reading_stage("Stage"); Requirements::css(CMS_DIR . '/css/screen.css'); Requirements::css(INPAGE_MODULES_DIR . '/css/ContentModule_Admin.css'); Requirements::combine_files( 'contentmodulemain.js', array_merge( array( //INPAGE_MODULES_DIR . '/javascript/CMSMain.js', //INPAGE_MODULES_DIR . '/javascript/CMSMain.EditForm.js', //INPAGE_MODULES_DIR . '/javascript/CMSMain.AddForm.js', CMS_DIR . '/javascript/CMSPageHistoryController.js', //INPAGE_MODULES_DIR . '/javascript/SilverStripeNavigator.js', ) //Requirements::add_i18n_javascript(INPAGE_MODULES_DIR . '/javascript/lang', true, true) ) ); //CMSBatchActionHandler::register('publish', 'CMSBatchAction_Publish'); //CMSBatchActionHandler::register('unpublish', 'CMSBatchAction_Unpublish'); //CMSBatchActionHandler::register('delete', 'CMSBatchAction_Delete'); //CMSBatchActionHandler::register('deletefromlive', 'CMSBatchAction_DeleteFromLive'); if (isset($_REQUEST['ID'])) { $this->setCurrentPageID($_REQUEST['ID']); } } public function index($request) { // In case we're not showing a specific record, explicitly remove any session state, // to avoid it being highlighted in the tree, and causing an edit form to show. if (!$request->param('Action')) { $this->setCurrentPageId(null); } return parent::index($request); } public function getResponseNegotiator() { $negotiator = parent::getResponseNegotiator(); $controller = $this; $negotiator->setCallback( 'ListViewForm', function () use (&$controller) { return $controller->ListViewForm()->forTemplate()->RAW(); } ); return $negotiator; } /** * If this is set to true, the "switchView" context in the * template is shown, with links to the staging and publish site. * * @return boolean */ public function ShowSwitchView() { return true; } /** * Overloads the LeftAndMain::ShowView. Allows to pass a page as a parameter, so we are able * to switch view also for archived versions. */ public function SwitchView($page = null) { if (!$page) { $page = $this->currentModule(); } if ($page) { $nav = SilverStripeNavigator::get_for_record($page); return $nav['items']; } } //------------------------------------------------------------------------------------------// // Main controllers //------------------------------------------------------------------------------------------// // Main UI components /** * Override {@link LeftAndMain} Link to allow blank URL segment * * @return string */ public function Link($action = null) { $link = Controller::join_links( $this->stat('url_base', true), $this->stat('url_segment', true), // in case we want to change the segment '/', // trailing slash needed if $action is null! "$action" ); $this->extend('updateLink', $link); return $link; } public function LinkModules() { return singleton('ContentModulePagesController')->Link(); } public function LinkModulesWithSearch() { return $this->LinkWithSearch($this->LinkModules()); } public function LinkModuleEdit($id = null) { if (!$id) { $id = $this->currentPageID(); } $base = singleton('ContentModuleEditController')->Link('show'); $query = ''; if (stripos($base, '?') !== false) { $parts = explode('?', $base); $base = $parts[0]; $query = $parts[1]; } return $this->LinkWithSearch( Controller::join_links($base, $id), $query ); } public function LinkModuleSettings() { if ($id = $this->currentPageID()) { return $this->LinkWithSearch( Controller::join_links(singleton('ContentModuleSettingsController')->Link('show'), $id) ); } } public function LinkModuleHistory() { if ($id = $this->currentPageID()) { return $this->LinkWithSearch( Controller::join_links(singleton('ContentModuleHistoryController')->Link('show'), $id) ); } } protected function LinkWithSearch($link, $query = '') { // Whitelist to avoid side effects $params = array( 'q' => (array)$this->request->getVar('q'), 'PageID' => $this->request->getVar('PageID') ); //prepend ?/& to $query if needed if ($query && array_filter(array_values($params))) { $query = '&' . $query; } elseif ($query) { $query = '?' . $query; } $link = Controller::join_links( $link, array_filter(array_values($params)) ? '?' . http_build_query($params) : null ); $this->extend('updateLinkWithSearch', $link); return $link; } public function LinkModuleAdd($extraArguments = null) { $link = singleton("ContentModuleAddController")->Link(); $this->extend('updateLinkModuleAdd', $link); if ($extraArguments) { $link = Controller::join_links($link, $extraArguments); } return $link; } /** * Disable this for now, need to get it working * @return string */ public function LinkPreview() { $record = $this->getRecord($this->currentPageID()); $baseLink = ($record && $record instanceof Page) ? $record->Link('?stage=Stage') : Director::absoluteBaseURL(); return false; } /** * Returns a Form for page searching for use in templates. * * Can be modified from a decorator by a 'updateSearchForm' method * * @return Form */ public function SearchForm() { // Create the fields $content = new TextField('q[Title]', _t('CMSSearch.FILTERTITLEHEADING', 'Module Name')); $dateHeader = new HeaderField('q[Date]', _t('CMSSearch.FILTERDATEHEADING', 'Date'), 4); $dateFrom = new DateField( 'q[LastEditedFrom]', _t('CMSSearch.FILTERDATEFROM', 'From') ); $dateFrom->setConfig('showcalendar', true); $dateTo = new DateField( 'q[LastEditedTo]', _t('CMSSearch.FILTERDATETO', 'To') ); $dateTo->setConfig('showcalendar', true); $pageClasses = new DropdownField( 'q[Module]', _t('ContentModule.MODULETYPEOPT', 'Module Type', 'Dropdown for limiting search to a module type'), $this->getModuleTypes() ); $pageClasses->setEmptyString(_t('ContentModule.MODULETYPEANYOPT', 'Any')); // Group the Datefields $dateGroup = new FieldGroup( $dateHeader, $dateFrom, $dateTo ); $dateGroup->setFieldHolderTemplate('FieldGroup_DefaultFieldHolder')->addExtraClass('stacked'); // Create the Field list $fields = new FieldList( $content, $dateGroup, $pageClasses ); // Create the Search and Reset action $actions = new FieldList( FormAction::create('doSearch', _t('CMSMain_left.ss.APPLY FILTER', 'Apply Filter')) ->addExtraClass('ss-ui-action-constructive'), Object::create('ResetFormAction', 'clear', _t('CMSMain_left.ss.RESET', 'Reset')) ); // Use <button> to allow full jQuery UI styling on the all of the Actions foreach ($actions->dataFields() as $action) { $action->setUseButtonTag(true); } // Create the form $form = Form::create($this, 'SearchForm', $fields, $actions) ->addExtraClass('cms-search-form') ->setFormMethod('GET') ->setFormAction($this->Link()) ->disableSecurityToken() ->unsetValidator(); // Load the form with previously sent search data $form->loadDataFrom($this->request->getVars()); // Allow decorators to modify the form $this->extend('updateSearchForm', $form); return $form; } /** * Returns a sorted array suitable for a dropdown with moduletypes and their translated name * * @return array */ protected function getModuleTypes() { $pageTypes = array(); foreach (ContentModule::content_module_types() as $pageTypeClass) { $pageTypes[$pageTypeClass->ClassName] = $pageTypeClass->i18n_singular_name(); } ksort($pageTypes); return $pageTypes; } public function doSearch($data, $form) { return $this->getsubtree($this->request); } /** * @return ArrayList */ public function Breadcrumbs($unlinked = false) { $items = parent::Breadcrumbs($unlinked); // The root element should point to the pages tree view, // rather than the actual controller (which would just show an empty edit form) $defaultTitle = self::menu_title_for_class('ContentModulesController'); $items[0]->Title = _t("{$this->class}.MENUTITLE", $defaultTitle); $items[0]->Link = singleton('ContentModulesController')->Link(); return $items; } /** * Populates an array of classes in the CMS * which allows the user to change the page type. * * @return SS_List */ public function ModuleTypesList() { $modules = ContentModule::content_module_types(); $result = new ArrayList(); foreach ($modules as $instance) { if (!$instance->canCreate()) { continue; } // skip this type if it is restricted if ($instance->stat('need_permission') && !$this->can($instance->stat('need_permission'))) { continue; } $addAction = $instance->i18n_singular_name(); // Get description (convert 'Page' to 'SiteTree' for correct localization lookups) $description = _t($instance->class . '.DESCRIPTION'); if (!$description) { $description = $instance->uninherited('description'); } $instance->update( array( 'ClassName' => $instance->class, 'AddAction' => $addAction, 'Description' => $description, // TODO Sprite support 'IconURL' => $instance->stat('icon'), 'Title' => $instance->i18n_singular_name(), 'Total' => DataList::create($instance->class)->count(), 'LastUpdate' => DataList::create($instance->class)->max('LastEdited') ) ); $result->push($instance); } $result = $result->sort('AddAction'); return $result; } /** * Get a database record to be managed by the CMS. * * @param int $id Record ID * @param int $versionID optional Version id of the given record */ public function getRecord($id, $versionID = null) { $treeClass = $this->stat('tree_class'); if ($id instanceof $treeClass) { return $id; } elseif ($id && is_numeric($id)) { if ($this->request->getVar('Version')) { $versionID = (int)$this->request->getVar('Version'); } if ($versionID) { $record = Versioned::get_version($treeClass, $id, $versionID); } else { $record = DataObject::get_one($treeClass, "\"$treeClass\".\"ID\" = $id"); } // Then, try getting a record from the live site if (!$record) { // $record = Versioned::get_one_by_stage($treeClass, "Live", "\"$treeClass\".\"ID\" = $id"); Versioned::reading_stage('Live'); singleton($treeClass)->flushCache(); $record = DataObject::get_one($treeClass, "\"$treeClass\".\"ID\" = $id"); if ($record) { Versioned::set_reading_mode(''); } } // Then, try getting a deleted record if (!$record) { $record = Versioned::get_latest_version($treeClass, $id); } // Don't open a page from a different locale /** The record's Locale is saved in database in 2.4, and not related with Session, * we should not check their locale matches the Translatable::get_current_locale, * here as long as we all the HTTPRequest is init with right locale. * This bit breaks the all FileIFrameField functions if the field is used in CMS * and its relevent ajax calles, like loading the tree dropdown for TreeSelectorField. */ /* if($record && Object::has_extension('SiteTree', 'Translatable') && $record->Locale && $record->Locale != Translatable::get_current_locale()) { $record = null; }*/ return $record; } elseif (substr($id, 0, 3) == 'new') { return $this->getNewItem($id); } } /** * @param Int $id * @param FieldList $fields * @return Form */ public function getEditForm($id = null, $fields = null) { if (!$id) { $id = $this->currentPageID(); } $form = parent::getEditForm($id); // TODO Duplicate record fetching (see parent implementation) $record = $this->getRecord($id); if ($record && !$record->canView()) { return Security::permissionFailure($this); } if (!$fields) { $fields = $form->Fields(); } $actions = $form->Actions(); if ($record) { $deletedFromStage = $record->IsDeletedFromStage; $deleteFromLive = !$record->ExistsOnLive; $fields->push($idField = new HiddenField("ID", false, $id)); // Necessary for different subsites $fields->push($liveLinkField = new HiddenField("AbsoluteLink", false, $record->AbsoluteLink())); $fields->push($liveLinkField = new HiddenField("LiveLink")); $fields->push($stageLinkField = new HiddenField("StageLink")); if ($record->ID && is_numeric($record->ID)) { $liveLink = $record->getAbsoluteLiveLink(); if ($liveLink) { $liveLinkField->setValue($liveLink); } if (!$deletedFromStage) { $stageLink = Controller::join_links($record->AbsoluteLink(), '?stage=Stage'); if ($stageLink) { $stageLinkField->setValue($stageLink); } } } // Added in-line to the form, but plucked into different view by LeftAndMain.Preview.js upon load /*if(in_array('CMSPreviewable', class_implements($record)) && !$fields->fieldByName('SilverStripeNavigator')) { $navField = new LiteralField('SilverStripeNavigator', $this->getSilverStripeNavigator()); $navField->setAllowHTML(true); $fields->push($navField); }*/ // getAllCMSActions can be used to completely redefine the action list if ($record->hasMethod('getAllCMSActions')) { $actions = $record->getAllCMSActions(); } else { $actions = $record->getCMSActions(); } // Use <button> to allow full jQuery UI styling $actionsFlattened = $actions->dataFields(); if ($actionsFlattened) { foreach ($actionsFlattened as $action) { $action->setUseButtonTag(true); } } if ($record->hasMethod('getCMSValidator')) { $validator = $record->getCMSValidator(); } else { $validator = new RequiredFields(); } $form = new Form($this, "EditForm", $fields, $actions, $validator); $form->loadDataFrom($record); $form->disableDefaultAction(); $form->addExtraClass('cms-edit-form content-module'); $form->setTemplate($this->getTemplatesWithSuffix('_EditForm')); // TODO Can't merge $FormAttributes in template at the moment $form->addExtraClass('center ' . $this->BaseCSSClasses()); $form->setAttribute('data-pjax-fragment', 'CurrentForm'); if (!$record->canEdit() || $deletedFromStage) { $readonlyFields = $form->Fields()->makeReadonly(); $form->setFields($readonlyFields); } $this->extend('updateEditForm', $form); return $form; } elseif ($id) { return new Form( $this, "EditForm", new FieldList( new LabelField( 'ModuleDoesntExistLabel', _t('ContentModule.MODULENOTEXISTS', "This module doesn't exist") ) ), new FieldList() ); } return $this->ListViewForm(); } public function listview($request) { return $this->renderWith($this->getTemplatesWithSuffix('_ListView')); } /** * Returns the pages meet a certain criteria as {@see CMSSiteTreeFilter} or the subpages of a parent page * defaulting to no filter and show all pages in first level. * Doubles as search results, if any search parameters are set through {@link SearchForm()}. * * @param Array Search filter criteria * @param Int Optional module parameter filters by the ClassName of the ContentModule * @return SS_List */ public function getList($params, $module = '') { $list = new DataList($this->stat('tree_class')); $filter = null; $ids = array(); if (isset($params['FilterClass']) && $filterClass = $params['FilterClass']) { if (!is_subclass_of($filterClass, 'CMSSiteTreeFilter')) { throw new Exception(sprintf('Invalid filter class passed: %s', $filterClass)); } $filter = new $filterClass($params); $filterOn = true; foreach ($pages = $filter->pagesIncluded() as $pageMap) { $ids[] = $pageMap['ID']; } if (count($ids)) { $list = $list->where('"' . $this->stat('tree_class') . '"."ID" IN (' . implode(",", $ids) . ')'); } } elseif ($params) { $filter = array(); if (!empty($params["Title"])) { $filter["Title:PartialMatch"] = $params["Title"]; } if (!empty($params["LastEditedFrom"])) { $filter["LastEdited:GreaterThan"] = $params["LastEditedFrom"]; } if (!empty($params["LastEditedTo"])) { $filter["LastEdited:LessThan"] = $params["LastEditedTo"]; } if (!empty($params["Module"])) { $filter['ClassName'] = $params['Module']; } if (!empty($params['Locale'])) { $filter['Locale'] = $params['Locale']; } $list = $list->filter($filter); } else { $list = $list->filter("ClassName", $module ? $module : ''); } return $list; } public function getModulesGridField($params, $module) { $list = $this->getList($params, $module); $gridFieldConfig = GridFieldConfig::create()->addComponents( new GridFieldSortableHeader(), new GridFieldDataColumns(), new GridFieldPaginator(30) ); if ($module) { $gridFieldConfig->addComponent( GridFieldLevelup::create($module) ->setLinkSpec('?Module=%s') ->setAttributes(array('data-pjax' => 'ListViewForm,Breadcrumbs')) ); } $gridField = new GridField('Module', 'Modules', $list, $gridFieldConfig); $columns = $gridField->getConfig()->getComponentByType('GridFieldDataColumns'); // Don't allow navigating into children nodes on filtered lists $fields = array( 'Title' => _t('ContentModule.MODULETITLE', 'Module Title'), 'Created' => _t('SiteTree.CREATED', 'Date Created'), 'LastEdited' => _t('SiteTree.LASTUPDATED', 'Last Updated'), ); $gridField->getConfig()->getComponentByType('GridFieldSortableHeader')->setFieldSorting( array('Title' => 'Title') ); $columns->setDisplayFields($fields); $columns->setFieldCasting( array( 'Created' => 'Datetime->Ago', 'LastEdited' => 'Datetime->Ago', 'Title' => 'HTMLText' ) ); $controller = $this; $columns->setFieldFormatting( array( 'Title' => function ($value, &$item) use ($controller) { return '<a class="action-detail" href="' . $controller->LinkModuleEdit($item->ID) . '">' . $item->Title . '</a>'; } ) ); return $gridField; } public function getModuleTypesGridField() { $list = $this->ModuleTypesList(); $gridFieldConfig = GridFieldConfig::create()->addComponents( new GridFieldSortableHeader(), new GridFieldDataColumns(), new GridFieldPaginator(30) ); $gridField = new GridField('ModuleTypes', 'Module Types', $list, $gridFieldConfig); $columns = $gridField->getConfig()->getComponentByType('GridFieldDataColumns'); // Don't allow navigating into children nodes on filtered lists $fields = array( 'Title' => _t('ContentModule.MODULETYPE', 'Module Type'), 'Total' => _t('ContentModule.MODULETYPETOTAL', 'Total'), 'LastUpdate' => _t('ContentModule.MODULETYPELASTUPDATE', 'Last Update'), ); $gridField->getConfig()->getComponentByType('GridFieldSortableHeader')->setFieldSorting( array('Title' => 'Title') ); //$fields = array_merge(array('listChildrenLink' => ''), $fields); $columns->setDisplayFields($fields); $columns->setFieldCasting( array( 'Title' => 'HTMLText', 'LastUpdate' => 'SS_Datetime' ) ); $controller = $this; $columns->setFieldFormatting( array( /*'listChildrenLink' => function($value, &$item) use($controller) { return sprintf( '<a class="cms-panel-link list-children-link" data-pjax-target="ListViewForm,Breadcrumbs" href="%s">></a>', Controller::join_links($controller->Link() . "?Module={$item->ClassName}") ); },*/ 'Title' => function ($value, &$item) use ($controller) { return $item->Total ? sprintf( '<a class="action-detail" href="%s">%s</a>', Controller::join_links($controller->Link(), '?Module=' . $item->ClassName), $item->Title ) : $item->Title; } ) ); return $gridField; } public function ListViewForm() { $params = $this->request->requestVar('q'); if (($module = $this->request->requestVar('Module')) || (!empty($params))) { $gridField = $this->getModulesGridField($params, $module); } else { $gridField = $this->getModuleTypesGridField(); } $listview = new Form( $this, 'ListViewForm', new FieldList($gridField), new FieldList() ); //$listview->addExtraClass('cms-edit-form'); $listview->setTemplate($this->getTemplatesWithSuffix('_ListViewForm')); // TODO Can't merge $FormAttributes in template at the moment $listview->addExtraClass($this->BaseCSSClasses()); $listview->setAttribute('data-pjax-fragment', 'ListViewForm'); $this->extend('updateListView', $listview); $listview->disableSecurityToken(); return $listview; } /*public function currentPageID() { $id = self::$page_id; //fallback to first //if ($module = ContentModule::get()->first()) { // $id = $module->ID; //} return $id; } public function setCurrentPageID($id) { self::$page_id = $id; return $this; }*/ //------------------------------------------------------------------------------------------// // Data saving handlers /** * Save and Publish page handler */ public function save($data, $form) { $className = $this->stat('tree_class'); // Existing or new record? $SQL_id = Convert::raw2sql($data['ID']); if (substr($SQL_id, 0, 3) != 'new') { $record = DataObject::get_by_id($className, $SQL_id); if ($record && !$record->canEdit()) { return Security::permissionFailure($this); } if (!$record || !$record->ID) { throw new SS_HTTPResponse_Exception("Bad record ID #$SQL_id", 404); } } else { if (!singleton($this->stat('tree_class'))->canCreate()) { return Security::permissionFailure($this); } $record = $this->getNewItem($SQL_id, false); } // Update the class instance if necessary if (isset($data['ClassName']) && $data['ClassName'] != $record->ClassName) { $newClassName = $record->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 $record->setClassName($data['ClassName']); // Replace $record with a new instance $record = $record->newClassInstance($newClassName); } // save form data into record $form->saveInto($record); $record->write(); // If the 'Save & Publish' button was clicked, also publish the page if (isset($data['publish']) && $data['publish'] == 1) { $response = $record->doPublish(); $this->response->addHeader( 'X-Status', rawurlencode( _t( 'LeftAndMain.STATUSPUBLISHEDSUCCESS', "Published '{title}' successfully", 'Status message after publishing a module, showing the module title', array('title' => $record->Title) ) ) ); } else { $this->response->addHeader('X-Status', rawurlencode(_t('LeftAndMain.SAVEDUP', 'Saved.'))); } return $this->getResponseNegotiator()->respond($this->request); } /** * @uses LeftAndMainExtension->augmentNewModuleItem() */ public function getNewItem($id, $setID = true) { list($dummy, $className, $suffix) = array_pad(explode('-', $id), 3, null); $newItem = new $className(); $newItem->ClassName = $className; if ($setID) { $newItem->ID = $id; } # Some modules like subsites add extra fields that need to be set when the new item is created $this->extend('augmentNewModuleItem', $newItem); return $newItem; } /** * Delete the page from live. This means a page in draft mode might still exist. * * @see delete() */ public function deletefromlive($data, $form) { Versioned::reading_stage('Live'); $record = DataObject::get_by_id("SiteTree", $data['ID']); if ($record && !($record->canDelete() && $record->canDeleteFromLive())) { return Security::permissionFailure($this); } $descRemoved = ''; $descendantsRemoved = 0; $recordTitle = $record->Title; $recordID = $record->ID; Versioned::reading_stage('Stage'); $this->response->addHeader( 'X-Status', rawurlencode( _t( 'CMSMain.REMOVED', 'Deleted \'{title}\'{description} from live site', array('title' => $recordTitle, 'description' => $descRemoved) ) ) ); // Even if the record has been deleted from stage and live, it can be viewed in "archive mode" return $this->getResponseNegotiator()->respond($this->request); } /** * Actually perform the publication step */ public function performPublish($record) { if ($record && !$record->canPublish()) { return Security::permissionFailure($this); } $record->doPublish(); } public function canView($member = null) { return true; } /** * Reverts a page by publishing it to live. * Use {@link restorepage()} if you want to restore a page * which was deleted from draft without publishing. * * @uses SiteTree->doRevertToLive() */ public function revert($data, $form) { if (!isset($data['ID'])) { return new SS_HTTPResponse("Please pass an ID in the form content", 400); } $id = (int)$data['ID']; $restoredPage = Versioned::get_latest_version("ContentModule", $id); if (!$restoredPage) { return new SS_HTTPResponse("ContentModule #$id not found", 400); } $record = Versioned::get_one_by_stage( 'SiteTree', 'Live', sprintf("\"SiteTree_Live\".\"ID\" = '%d'", (int)$data['ID']) ); // a user can restore a page without publication rights, as it just adds a new draft state // (this action should just be available when page has been "deleted from draft") if ($record && !$record->canEdit()) { return Security::permissionFailure($this); } if (!$record || !$record->ID) { throw new SS_HTTPResponse_Exception("Bad record ID #$id", 404); } $record->doRevertToLive(); $this->response->addHeader( 'X-Status', rawurlencode( _t( 'CMSMain.RESTORED', "Restored '{title}' successfully", 'Param %s is a title', array('title' => $record->Title) ) ) ); return $this->getResponseNegotiator()->respond($this->request); } /** * Delete the current page from draft stage. * @see deletefromlive() */ public function delete($data, $form) { $id = Convert::raw2sql($data['ID']); $record = DataObject::get_one( "ContentModule", sprintf("\"ContentModule\".\"ID\" = %d", $id) ); if ($record && !$record->canDelete()) { return Security::permissionFailure(); } if (!$record || !$record->ID) { throw new SS_HTTPResponse_Exception("Bad record ID #$id", 404); } // save ID and delete record $recordID = $record->ID; $record->delete(); $this->response->addHeader( 'X-Status', rawurlencode( sprintf(_t('CMSMain.REMOVEDPAGEFROMDRAFT', "Removed '%s' from the draft site"), $record->Title) ) ); // Even if the record has been deleted from stage and live, it can be viewed in "archive mode" return $this->getResponseNegotiator()->respond($this->request); } public function publish($data, $form) { $data['publish'] = '1'; return $this->save($data, $form); } public function unpublish($data, $form) { $className = $this->stat('tree_class'); $record = DataObject::get_by_id($className, $data['ID']); if ($record && !$record->canDeleteFromLive()) { return Security::permissionFailure($this); } if (!$record || !$record->ID) { throw new SS_HTTPResponse_Exception("Bad record ID #" . (int)$data['ID'], 404); } $record->doUnpublish(); $this->response->addHeader( 'X-Status', rawurlencode( _t('CMSMain.REMOVEDPAGE', "Removed '{title}' from the published site", array('title' => $record->Title)) ) ); return $this->getResponseNegotiator()->respond($this->request); } /** * @return array */ public function rollback() { return $this->doRollback( array( 'ID' => $this->currentPageID(), 'Version' => $this->request->param('VersionID') ), null ); } /** * Rolls a site back to a given version ID * * @param array * @param Form * * @return html */ public function doRollback($data, $form) { $this->extend('onBeforeRollback', $data['ID']); $id = (isset($data['ID'])) ? (int)$data['ID'] : null; $version = (isset($data['Version'])) ? (int)$data['Version'] : null; $record = DataObject::get_by_id($this->stat('tree_class'), $id); if ($record && !$record->canEdit()) { return Security::permissionFailure($this); } if ($version) { $record->doRollbackTo($version); $message = _t( 'CMSMain.ROLLEDBACKVERSION', "Rolled back to version #%d. New version number is #%d", array('version' => $data['Version'], 'versionnew' => $record->Version) ); } else { $record->doRollbackTo('Live'); $message = _t( 'CMSMain.ROLLEDBACKPUB', "Rolled back to published version. New version number is #{version}", array('version' => $record->Version) ); } $this->response->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::join_links(singleton('CMSPageEditController')->Link('show'), $record->ID); $this->response->addHeader('X-ControllerURL', $url); $this->request->addHeader('X-Pjax', 'Content'); $this->response->addHeader('X-Pjax', 'Content'); return $this->getResponseNegotiator()->respond($this->request); } /** * Batch Actions Handler */ public function batchactions() { return new CMSBatchActionHandler($this, 'batchactions'); } public function BatchActionParameters() { $batchActions = CMSBatchActionHandler::$batch_actions; $forms = array(); foreach ($batchActions as $urlSegment => $batchAction) { $SNG_action = singleton($batchAction); if ($SNG_action->canView() && $fieldset = $SNG_action->getParameterFields()) { $formHtml = ''; foreach ($fieldset as $field) { $formHtml .= $field->Field(); } $forms[$urlSegment] = $formHtml; } } $pageHtml = ''; foreach ($forms as $urlSegment => $html) { $pageHtml .= "<div class=\"params\" id=\"BatchActionParameters_$urlSegment\">$html</div>\n\n"; } return new LiteralField( "BatchActionParameters", '<div id="BatchActionParameters" style="display:none">' . $pageHtml . '</div>' ); } /** * Returns a list of batch actions */ public function BatchActionList() { return $this->batchactions()->batchActionList(); } public function buildbrokenlinks($request) { // Protect against CSRF on destructive action if (!SecurityToken::inst()->checkRequest($request)) { return $this->httpError(400); } increase_time_limit_to(); increase_memory_limit_to(); if ($this->urlParams['ID']) { $newPageSet[] = DataObject::get_by_id("Page", $this->urlParams['ID']); } else { $pages = DataObject::get("Page"); foreach ($pages as $page) { $newPageSet[] = $page; } $pages = null; } $content = new HtmlEditorField('Content'); $download = new HtmlEditorField('Download'); foreach ($newPageSet as $i => $page) { $page->HasBrokenLink = 0; $page->HasBrokenFile = 0; $content->setValue($page->Content); $content->saveInto($page); $download->setValue($page->Download); $download->saveInto($page); echo "<li>$page->Title (link:$page->HasBrokenLink, file:$page->HasBrokenFile)"; $page->writeWithoutVersion(); $page->destroy(); $newPageSet[$i] = null; } } public function publishall($request) { if (!Permission::check('ADMIN')) { return Security::permissionFailure($this); } increase_time_limit_to(); increase_memory_limit_to(); $response = ""; if (isset($this->requestParams['confirm'])) { // Protect against CSRF on destructive action if (!SecurityToken::inst()->checkRequest($request)) { return $this->httpError(400); } $start = 0; $pages = DataObject::get("SiteTree", "", "", "", "$start,30"); $count = 0; while ($pages) { foreach ($pages as $page) { if ($page && !$page->canPublish()) { return Security::permissionFailure($this); } $page->doPublish(); $page->destroy(); unset($page); $count++; $response .= "<li>$count</li>"; } if ($pages->Count() > 29) { $start += 30; $pages = DataObject::get("SiteTree", "", "", "", "$start,30"); } else { break; } } $response .= _t('CMSMain.PUBPAGES', "Done: Published {count} pages", array('count' => $count)); } else { $token = SecurityToken::inst(); $fields = new FieldList(); $token->updateFieldSet($fields); $tokenField = $fields->First(); $tokenHtml = ($tokenField) ? $tokenField->FieldHolder() : ''; $response .= '<h1>' . _t('CMSMain.PUBALLFUN', '"Publish All" functionality') . '</h1> <p>' . _t( 'CMSMain.PUBALLFUN2', 'Pressing this button will do the equivalent of going to every page and pressing "publish". It\'s intended to be used after there have been massive edits of the content, such as when the site was first built.' ) . '</p> <form method="post" action="publishall"> <input type="submit" name="confirm" value="' . _t( 'CMSMain.PUBALLCONFIRM', "Please publish every page in the site, copying content stage to live", 'Confirmation button' ) . '" />' . $tokenHtml . '</form>'; } return $response; } /** * Restore a completely deleted page from the SiteTree_versions table. */ public function restore($data, $form) { if (!isset($data['ID']) || !is_numeric($data['ID'])) { return new SS_HTTPResponse("Please pass an ID in the form content", 400); } $id = (int)$data['ID']; $restoredPage = Versioned::get_latest_version("ContentModule", $id); if (!$restoredPage) { return new SS_HTTPResponse("ContentModule #$id not found", 400); } $restoredPage = $restoredPage->doRestoreToStage(); $this->response->addHeader( 'X-Status', rawurlencode( _t( 'CMSMain.RESTORED', "Restored '{title}' successfully", array('title' => $restoredPage->Title) ) ) ); return $this->getResponseNegotiator()->respond($this->request); } public function duplicate($request) { // Protect against CSRF on destructive action if (!SecurityToken::inst()->checkRequest($request)) { return $this->httpError(400); } if (($id = $this->urlParams['ID']) && is_numeric($id)) { $page = DataObject::get_by_id("SiteTree", $id); if ($page && (!$page->canEdit() || !$page->canCreate())) { return Security::permissionFailure($this); } if (!$page || !$page->ID) { throw new SS_HTTPResponse_Exception("Bad record ID #$id", 404); } $newPage = $page->duplicate(); // ParentID can be hard-set in the URL. This is useful for pages with multiple parents if ($_GET['parentID'] && is_numeric($_GET['parentID'])) { $newPage->ParentID = $_GET['parentID']; $newPage->write(); } // Reload form, data and actions might have changed $form = $this->getEditForm($newPage->ID); return $form->forTemplate()->RAW(); } else { user_error("CMSMain::duplicate() Bad ID: '$id'", E_USER_WARNING); } } public function duplicatewithchildren($request) { // Protect against CSRF on destructive action if (!SecurityToken::inst()->checkRequest($request)) { return $this->httpError(400); } if (($id = $this->urlParams['ID']) && is_numeric($id)) { $page = DataObject::get_by_id("SiteTree", $id); if ($page && (!$page->canEdit() || !$page->canCreate())) { return Security::permissionFailure($this); } if (!$page || !$page->ID) { throw new SS_HTTPResponse_Exception("Bad record ID #$id", 404); } $newPage = $page->duplicateWithChildren(); // Reload form, data and actions might have changed $form = $this->getEditForm($newPage->ID); return $form->forTemplate()->RAW(); } else { user_error("CMSMain::duplicate() Bad ID: '$id'", E_USER_WARNING); } } /** * Return the version number of this application. * Uses the subversion path information in <mymodule>/silverstripe_version * (automacially replaced by build scripts). * * @return string */ public function CMSVersion() { $cmsVersion = file_get_contents(CMS_PATH . '/silverstripe_version'); if (!$cmsVersion) { $cmsVersion = _t('LeftAndMain.VersionUnknown', 'Unknown'); } $frameworkVersion = file_get_contents(FRAMEWORK_PATH . '/silverstripe_version'); if (!$frameworkVersion) { $frameworkVersion = _t('LeftAndMain.VersionUnknown', 'Unknown'); } return sprintf( "CMS: %s Framework: %s", $cmsVersion, $frameworkVersion ); } public function providePermissions() { $title = _t("CMSPagesController.MENUTITLE", LeftAndMain::menu_title_for_class('CMSPagesController')); return array( "CMS_ACCESS_CMSMain" => array( 'name' => _t('CMSMain.ACCESS', "Access to '{title}' section", array('title' => $title)), 'category' => _t('Permission.CMS_ACCESS_CATEGORY', 'CMS Access'), 'help' => _t( 'CMSMain.ACCESS_HELP', 'Allow viewing of the section containing page tree and content. View and edit permissions can be handled through page specific dropdowns, as well as the separate "Content permissions".' ), 'sort' => -99 // below "CMS_ACCESS_LeftAndMain", but above everything else ) ); } /** * Populates an array of classes in the CMS * which allows the user to change the page type. * * @return SS_List */ public function ModuleTypes() { $classes = ContentModule::content_module_types(); $result = new ArrayList(); foreach ($classes as $instance) { $class = $instance->class; if ($instance instanceof HiddenClass) { continue; } if (!$instance->canCreate()) { continue; } // skip this type if it is restricted if ($instance->stat('need_permission') && !$this->can(singleton($class)->stat('need_permission'))) { continue; } $addAction = $instance->i18n_singular_name(); // Get description (convert 'Page' to 'SiteTree' for correct localization lookups) $description = _t($class . '.DESCRIPTION'); if (!$description) { $description = $instance->uninherited('description'); } $result->push( new ArrayData( array( 'ClassName' => $class, 'AddAction' => $addAction, 'Description' => $description, // TODO Sprite support 'IconURL' => $instance->stat('icon'), 'Title' => singleton($class)->i18n_singular_name(), ) ) ); } $result = $result->sort('AddAction'); return $result; } } |