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);
$stageURLField->setValue(Controller::join_links($record->getStageURLSegment(), '?stage=Stage'));
$form->disableDefaultAction();
$form->addExtraClass('cms-edit-form');
$form->setTemplate($this->getTemplatesWithSuffix('_EditForm'));
// TODO Can't merge $FormAttributes in template at the moment
$form->addExtraClass('center ss-tabset ' . $this->BaseCSSClasses());
if($form->Fields()->hasTabset()) $form->Fields()->findOrMakeTab('Root')->setTemplate('CMSTabSet');
if(!$record->canEdit() || $record->IsDeletedFromStage) {
$readonlyFields = $form->Fields()->makeReadonly();
$form->setFields($readonlyFields);
}
$this->extend('updateEditForm', $form);
return $form;
} else if($id) {
return new Form($this, "EditForm", new FieldList(
new LabelField('PageDoesntExistLabel',_t('CMSMain.PAGENOTEXISTS',"This page doesn't exist"))), new FieldList()
);
}
}
public function currentPageID() {
$id = parent::currentPageID();
// Fall back to homepage record
if(!$id) {
$homepageSegment = RootURLController::get_homepage_link();
$homepageRecord = DataObject::get_one('SiteTree', sprintf('"URLSegment" = \'%s\'', $homepageSegment));
if($homepageRecord) $id = $homepageRecord->ID;
}
return $id;
}
//------------------------------------------------------------------------------------------//
// 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);
}
// TODO Coupling to SiteTree
$record->HasBrokenLink = 0;
$record->HasBrokenFile = 0;
$record->writeWithoutVersion();
// 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) {
$record->doPublish();
// Update classname with original and get new instance (see above for explanation)
if(isset($data['ClassName'])) {
$record->setClassName($data['ClassName']);
$publishedRecord = $record->newClassInstance($record->ClassName);
}
$this->response->addHeader(
'X-Status',
sprintf(
_t(
'LeftAndMain.STATUSPUBLISHEDSUCCESS',
"Published '%s' successfully",
PR_MEDIUM,
'Status message after publishing a page, showing the page title'
),
$record->Title
)
);
// Reload form, data and actions might have changed
$form = $this->getEditForm($record->ID);
} else {
$this->response->addHeader('X-Status', _t('LeftAndMain.SAVEDUP'));
// Reload form, data and actions might have changed
$form = $this->getEditForm($record->ID);
}
return $form->forTemplate();
}
public function doAdd($data, $form) {
$className = isset($data['PageType']) ? $data['PageType'] : "Page";
$parentMode = isset($data['ParentModeField']) ? $data['ParentModeField'] : "top";
$parentID = isset($data['ParentID']) ? (int)$data['ParentID'] : 0;
if ($parentMode == "top") $parentID = 0;
$suffix = isset($data['Suffix']) ? "-" . $data['Suffix'] : null;
if(!$parentID && isset($data['Parent'])) {
$page = SiteTree:: get_by_link(Convert::raw2sql($data['Parent']));
if($page) $parentID = $page->ID;
}
if(is_numeric($parentID) && $parentID > 0) $parentObj = DataObject::get_by_id("SiteTree", $parentID);
else $parentObj = null;
if(!$parentObj || !$parentObj->ID) $parentID = 0;
if($parentObj) {
if(!$parentObj->canAddChildren()) return Security::permissionFailure($this);
if(!singleton($className)->canCreate()) return Security::permissionFailure($this);
} else {
if(!SiteConfig::current_site_config()->canCreateTopLevel())
return Security::permissionFailure($this);
}
$record = $this->getNewItem("new-$className-$parentID".$suffix, false);
if(class_exists('Translatable') && $record->hasExtension('Translatable')) $record->Locale = $data['Locale'];
$record->write();
$form = $this->getEditForm($record->ID);
$link = Controller::join_links(singleton('CMSPageEditController')->Link('show'), $record->ID);
$this->getResponse()->addHeader('X-ControllerURL', $link);
if(isset($data['returnID'])) {
return $record->ID;
} else if(Director::is_ajax()) {
$form = $this->getEditForm($record->ID);
return $form->forTemplate();
} else {
return $this->redirect($link);
}
}
/**
* @uses LeftAndMainExtension->augmentNewSiteTreeItem()
*/
public function getNewItem($id, $setID = true) {
list($dummy, $className, $parentID, $suffix) = array_pad(explode('-',$id),4,null);
$newItem = new $className();
if( !$suffix ) {
$sessionTag = "NewItems." . $parentID . "." . $className;
if(Session::get($sessionTag)) {
$suffix = '-' . Session::get($sessionTag);
Session::set($sessionTag, Session::get($sessionTag) + 1);
}
else
Session::set($sessionTag, 1);
$id = $id . $suffix;
}
$newItem->Title = _t('CMSMain.NEW',"New ",PR_MEDIUM,'"New " followed by a className').$className;
$newItem->URLSegment = "new-" . strtolower($className);
$newItem->ClassName = $className;
$newItem->ParentID = $parentID;
// DataObject::fieldExists only checks the current class, not the hierarchy
// This allows the CMS to set the correct sort value
if($newItem->castingHelper('Sort')) {
$newItem->Sort = DB::query("SELECT MAX(\"Sort\") FROM \"SiteTree\" WHERE \"ParentID\" = '" . Convert::raw2sql($parentID) . "'")->value() + 1;
}
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('augmentNewSiteTreeItem', $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;
// before deleting the records, get the descendants of this tree
if($record) {
$descendantIDs = $record->getDescendantIDList();
// then delete them from the live site too
$descendantsRemoved = 0;
foreach( $descendantIDs as $descID )
if( $descendant = DataObject::get_by_id('SiteTree', $descID) ) {
$descendant->doDeleteFromLive();
$descendantsRemoved++;
}
// delete the record
$record->doDeleteFromLive();
}
Versioned::reading_stage('Stage');
if(isset($descendantsRemoved)) {
$descRemoved = " and $descendantsRemoved descendants";
$descRemoved = sprintf(' '._t('CMSMain.DESCREMOVED', 'and %s descendants'), $descendantsRemoved);
} else {
$descRemoved = '';
}
$this->response->addHeader(
'X-Status',
sprintf(
_t('CMSMain.REMOVED', 'Deleted \'%s\'%s from live site'),
$record->Title,
$descRemoved
)
);
// nothing to return
return '';
}
/**
* Actually perform the publication step
*/
public function performPublish($record) {
if($record && !$record->canPublish()) return Security::permissionFailure($this);
$record->doPublish();
}
/**
* 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("SiteTree", $id);
if(!$restoredPage) return new SS_HTTPResponse("SiteTree #$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',
sprintf(
_t('CMSMain.RESTORED',"Restored '%s' successfully",PR_MEDIUM,'Param %s is a title'),
$record->Title
)
);
$form = $this->getEditForm($record->ID);
return $form->forTemplate();
}
/**
* Delete the current page from draft stage.
* @see deletefromlive()
*/
public function delete($data, $form) {
$id = Convert::raw2sql($data['ID']);
$record = DataObject::get_one(
"SiteTree",
sprintf("\"SiteTree\".\"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',
sprintf(
_t('CMSMain.REMOVEDPAGEFROMDRAFT',"Removed '%s' from the draft site"),
$record->Title
)
);
if($this->isAjax()) {
// need a valid ID value even if the record doesn't have one in the database
// (its still present in the live tables)
$liveRecord = Versioned::get_one_by_stage(
'SiteTree',
'Live',
"\"SiteTree_Live\".\"ID\" = $recordID"
);
return ($liveRecord) ? $form->forTemplate() : "";
} else {
$this->redirectBack();
}
}
function publish($data, $form) {
$data['publish'] = '1';
return $this->save($data, $form);
}
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',
sprintf(_t('CMSMain.REMOVEDPAGE',"Removed '%s' from the published site"),$record->Title)
);
// Reload form, data and actions might have changed
$form = $this->getEditForm($record->ID);
return $form->forTemplate();
}
/**
* Batch Actions Handler
*/
function batchactions() {
return new CMSBatchActionHandler($this, 'batchactions');
}
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 .= "$html
\n\n";
}
return new LiteralField("BatchActionParameters", ''.$pageHtml.'
');
}
/**
* Returns a list of batch actions
*/
function BatchActionList() {
return $this->batchactions()->batchActionList();
}
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 "$page->Title (link:$page->HasBrokenLink, file:$page->HasBrokenFile)";
$page->writeWithoutVersion();
$page->destroy();
$newPageSet[$i] = null;
}
}
/**
* @return Form
*/
function AddForm() {
$record = $this->currentPage();
$pageTypes = array();
foreach($this->PageTypes() as $type) {
$html = sprintf('%s %s ',
$type->getField('ClassName'),
$type->getField('AddAction'),
$type->getField('Description')
);
$pageTypes[$type->getField('ClassName')] = $html;
}
// Ensure generic page type shows on top
if(isset($pageTypes['Page'])) {
$pageTitle = $pageTypes['Page'];
$pageTypes = array_merge(array('Page' => $pageTitle), $pageTypes);
}
$numericLabelTmpl = '%d %s ';
$fields = new FieldList(
// new HiddenField("ParentID", false, ($this->parentRecord) ? $this->parentRecord->ID : null),
// TODO Should be part of the form attribute, but not possible in current form API
$hintsField = new LiteralField('Hints', sprintf(' ', $this->SiteTreeHints())),
new LiteralField('PageModeHeader', sprintf($numericLabelTmpl, 1, _t('CMSMain.ChoosePageParentMode', 'Choose where to create this page'))),
$parentModeField = new SelectionGroup(
"ParentModeField",
array(
"top//Top level" => null, //new LiteralField("Dummy", ''),
"child//Under another page" => $parentField = new TreeDropdownField(
"ParentID",
"",
'SiteTree'
)
)
),
$typeField = new OptionsetField(
"PageType",
sprintf($numericLabelTmpl, 2, _t('CMSMain.ChoosePageType', 'Choose page type')),
$pageTypes,
'Page'
)
);
$parentField->setShowSearch(true);
$parentModeField->setValue("child");
// CMSMain->currentPageID() automatically sets the homepage,
// which we need to counteract in the default selection (which should default to root, ID=0)
$homepageSegment = RootURLController::get_homepage_link();
if($record && $record->URLSegment != $homepageSegment) {
$parentField->setValue($record->ID);
}
$actions = new FieldList(
// $resetAction = new ResetFormAction('doCancel', _t('CMSMain.Cancel', 'Cancel')),
FormAction::create("doAdd", _t('CMSMain.Create',"Create"))
->addExtraClass('ss-ui-action-constructive')->setAttribute('data-icon', 'accept')
->setUseButtonTag(true)
);
$this->extend('updatePageOptions', $fields);
$form = new Form($this, "AddForm", $fields, $actions);
$form->addExtraClass('cms-add-form stacked');
return $form;
}
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 .= " $count ";
}
if($pages->Count() > 29) {
$start += 30;
$pages = DataObject::get("SiteTree", "", "", "", "$start,30");
} else {
break;
}
}
$response .= sprintf(_t('CMSMain.PUBPAGES',"Done: Published %d pages"), $count);
} else {
$token = SecurityToken::inst();
$fields = new FieldList();
$token->updateFieldSet($fields);
$tokenField = $fields->First();
$tokenHtml = ($tokenField) ? $tokenField->FieldHolder() : '';
$response .= '' . _t('CMSMain.PUBALLFUN','"Publish All" functionality') . '
' . _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.') . '
';
}
return $response;
}
/**
* Restore a completely deleted page from the SiteTree_versions table.
*/
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("SiteTree", $id);
if(!$restoredPage) return new SS_HTTPResponse("SiteTree #$id not found", 400);
$restoredPage = $restoredPage->doRestoreToStage();
$this->response->addHeader(
'X-Status',
sprintf(
_t('CMSMain.RESTORED',"Restored '%s' successfully",PR_MEDIUM,'Param %s is a title'),
$restoredPage->TreeTitle
)
);
// Reload form, data and actions might have changed
$form = $this->getEditForm($restoredPage->ID);
return $form->forTemplate();
}
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();
} else {
user_error("CMSMain::duplicate() Bad ID: '$id'", E_USER_WARNING);
}
}
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();
} else {
user_error("CMSMain::duplicate() Bad ID: '$id'", E_USER_WARNING);
}
}
/**
* Return the version number of this application.
* Uses the subversion path information in /silverstripe_version
* (automacially replaced by build scripts).
*
* @return string
*/
public function CMSVersion() {
$cmsVersion = file_get_contents(BASE_PATH . '/cms/silverstripe_version');
if(!$cmsVersion) $cmsVersion = _t('LeftAndMain.VersionUnknown');
$sapphireVersion = file_get_contents(BASE_PATH . '/cms/silverstripe_version');
if(!$sapphireVersion) $sapphireVersion = _t('LeftAndMain.VersionUnknown');
return sprintf(
"cms: %s, sapphire: %s",
$cmsVersion,
$sapphireVersion
);
}
function providePermissions() {
$title = _t("CMSPagesController.MENUTITLE", LeftAndMain::menu_title_for_class('CMSPagesController'));
return array(
"CMS_ACCESS_CMSMain" => array(
'name' => sprintf(_t('CMSMain.ACCESS', "Access to '%s' section"), $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
)
);
}
}