silverstripe-framework/core/model/VirtualPage.php
Sam Minnee 332ddb700a BUGFIX: Virtual pages can be deleted from the live site even when the're not publishable. (from r93319)
git-svn-id: svn://svn.silverstripe.com/silverstripe/open/modules/sapphire/branches/2.4@96758 467b73ca-7a2a-4603-9d3b-597d59a354a9
2011-02-02 14:18:06 +13:00

356 lines
10 KiB
PHP
Executable File

<?php
/**
* Virtual Page creates an instance of a page, with the same fields that the original page had, but readonly.
* This allows you can have a page in mulitple places in the site structure, with different children without duplicating the content
* Note: This Only duplicates $db fields and not the $has_one etc..
* @package cms
*/
class VirtualPage extends Page {
static $icon = array("cms/images/treeicons/page-shortcut-gold","file");
public static $virtualFields;
static $has_one = array(
"CopyContentFrom" => "SiteTree",
);
static $db = array(
"VersionID" => "Int",
);
/**
* Generates the array of fields required for the page type.
*/
function getVirtualFields() {
$nonVirtualFields = array(
"SecurityTypeID",
"OwnerID",
"URLSegment",
"Sort",
"Status",
'ShowInMenus',
'ShowInSearch'
);
$allFields = $this->db();
if($hasOne = $this->has_one()) foreach($hasOne as $link) $allFields[$link . 'ID'] = "Int";
foreach($allFields as $field => $type) {
if(!in_array($field, $nonVirtualFields)) $virtualFields[] = $field;
}
return $virtualFields;
}
function ContentSource() {
return $this->CopyContentFrom();
}
function allowedChildren() {
if($this->CopyContentFrom()) {
return $this->CopyContentFrom()->allowedChildren();
}
}
public function syncLinkTracking() {
if($this->CopyContentFromID) {
$this->HasBrokenLink = !(bool) DataObject::get_by_id('SiteTree', $this->CopyContentFromID);
}
}
/**
* We can only publish the page if there is a published source page
*/
public function canPublish($member = null) {
return $this->isPublishable() && parent::canPublish($member);
}
/**
* Return true if we can delete this page from the live site, which is different from can
* we publish it.
*/
public function canDeleteFromLive($member = null) {
return parent::canPublish($member);
}
/**
* Returns true if is page is publishable by anyone at all
* Return false if the source page isn't published yet.
*
* Note that isPublishable doesn't affect ete from live, only publish.
*/
public function isPublishable() {
// No source
if(!$this->CopyContentFrom()) {
return false;
}
// Unpublished source
if(!Versioned::get_versionnumber_by_stage('SiteTree', 'Live', $this->CopyContentFromID)) {
return false;
}
// Default - publishable
return true;
}
/**
* Generate the CMS fields from the fields from the original page.
*/
function getCMSFields($cms = null) {
$fields = parent::getCMSFields($cms);
// Setup the linking to the original page.
$copyContentFromField = new TreeDropdownField(
"CopyContentFromID",
_t('VirtualPage.CHOOSE', "Choose a page to link to"),
"SiteTree"
);
$copyContentFromField->setFilterFunction(create_function('$item', 'return $item->ClassName != "VirtualPage";'));
// Setup virtual fields
if($virtualFields = $this->getVirtualFields()) {
$roTransformation = new ReadonlyTransformation();
foreach($virtualFields as $virtualField) {
if($fields->dataFieldByName($virtualField))
$fields->replaceField($virtualField, $fields->dataFieldByName($virtualField)->transform($roTransformation));
}
}
// Add fields to the tab
$fields->addFieldToTab("Root.Content.Main",
new HeaderField('VirtualPageHeader',_t('VirtualPage.HEADER', "This is a virtual page")),
"Title"
);
$fields->addFieldToTab("Root.Content.Main", $copyContentFromField, "Title");
// Create links back to the original object in the CMS
if($this->CopyContentFromID) {
$linkToContent = "<a class=\"cmsEditlink\" href=\"admin/show/$this->CopyContentFromID\">" .
_t('VirtualPage.EDITCONTENT', 'click here to edit the content') . "</a>";
$fields->addFieldToTab("Root.Content.Main",
$linkToContentLabelField = new LabelField('VirtualPageContentLinkLabel', $linkToContent),
"Title"
);
$linkToContentLabelField->setAllowHTML(true);
}
return $fields;
}
/**
* We have to change it to copy all the content from the original page first.
*/
function onBeforeWrite() {
// On regular write, this will copy from published source. This happens on every publish
if($this->extension_instances['Versioned']->migratingVersion
&& Versioned::current_stage() == 'Live') {
$performCopyFrom = true;
// On regular write, this will copy from draft source. This is only executed when the source
// page changeds
} else {
$performCopyFrom = $this->isChanged('CopyContentFromID') && $this->CopyContentFromID != 0;
}
// On publish, this will copy from published source
if($performCopyFrom && $this instanceof VirtualPage) {
// This flush is needed because the get_one cache doesn't respect site version :-(
singleton('SiteTree')->flushCache();
$source = DataObject::get_one("SiteTree",sprintf('"SiteTree"."ID" = %d', $this->CopyContentFromID));
// Leave the updating of image tracking until after write, in case its a new record
$this->copyFrom($source, false);
$this->URLSegment = $source->URLSegment . '-' . $this->ID;
}
parent::onBeforeWrite();
}
function onAfterWrite() {
parent::onAfterWrite();
// Don't do this stuff when we're publishing
if(!$this->extension_instances['Versioned']->migratingVersion) {
if(
$this->isChanged('CopyContentFromID')
&& $this->CopyContentFromID != 0
&& $this instanceof VirtualPage
) {
$this->updateImageTracking();
}
}
}
/**
* Ensure we have an up-to-date version of everything.
*/
function copyFrom($source, $updateImageTracking = true) {
if($source) {
foreach($this->getVirtualFields() as $virtualField) {
$this->$virtualField = $source->$virtualField;
}
// We also want to copy ShowInMenus, but only if we're copying the
// source page for the first time.
if($this->isChanged('CopyContentFromID')) {
$this->ShowInMenus = $source->ShowInMenus;
}
if($updateImageTracking) $this->updateImageTracking();
}
}
function updateImageTracking() {
// Doesn't work on unsaved records
if(!$this->ID) return;
// Remove CopyContentFrom() from the cache
unset($this->components['CopyContentFrom']);
// Update ImageTracking
$this->ImageTracking()->setByIdList($this->CopyContentFrom()->ImageTracking()->column('ID'));
}
/**
* Allow attributes on the master page to pass
* through to the virtual page
*
* @param string $field
* @return mixed
*/
function __get($field) {
$return = parent::__get($field);
if ($return === null) {
if($this->copyContentFrom()->hasMethod($funcName = "get$field")) {
$return = $this->copyContentFrom()->$funcName();
} else if($this->copyContentFrom()->hasField($field)) {
$return = $this->copyContentFrom()->getField($field);
}
}
return $return;
}
/**
* Pass unrecognized method calls on to the original data object
*
* @param string $method
* @param string $args
*/
function __call($method, $args) {
try {
return parent::__call($method, $args);
} catch (Exception $e) {
// Hack... detect exception type. We really should use exception subclasses.
// if the exception isn't a 'no method' error, rethrow it
if ($e->getCode() !== 2175) throw $e;
$original = $this->copyContentFrom();
return call_user_func_array(array($original, $method), $args);
}
}
/**
* Overwrite to also check for method on the original data object
*
* @param string $method
* @return bool
*/
function hasMethod($method) {
$haveIt = parent::hasMethod($method);
if (!$haveIt) $haveIt = $this->copyContentFrom()->hasMethod($method);
return $haveIt;
}
}
/**
* Controller for the virtual page.
* @package cms
*/
class VirtualPage_Controller extends Page_Controller {
static $allowed_actions = array(
'loadcontentall' => 'ADMIN',
);
/**
* Reloads the content if the version is different ;-)
*/
function reloadContent() {
$this->failover->copyFrom($this->failover->CopyContentFrom());
$this->failover->write();
return;
}
function getViewer($action) {
$originalClass = get_class($this->CopyContentFrom());
if ($originalClass == 'SiteTree') $name = 'Page_Controller';
else $name = $originalClass."_Controller";
$controller = new $name();
return $controller->getViewer($action);
}
/**
* When the virtualpage is loaded, check to see if the versions are the same
* if not, reload the content.
* NOTE: Virtual page must have a container object of subclass of sitetree.
* We can't load the content without an ID or record to copy it from.
*/
function init(){
if(isset($this->record) && $this->record->ID){
if($this->record->VersionID != $this->failover->CopyContentFrom()->Version){
$this->reloadContent();
$this->VersionID = $this->failover->CopyContentFrom()->VersionID;
}
}
parent::init();
}
function loadcontentall() {
$pages = DataObject::get("VirtualPage");
foreach($pages as $page) {
$page->copyFrom($page->CopyContentFrom());
$page->write();
$page->publish("Stage", "Live");
echo "<li>Published $page->URLSegment";
}
}
/**
* Also check the original objects' original controller for the method
*
* @param string $method
* @return bool
*/
function hasMethod($method) {
$haveIt = parent::hasMethod($method);
if (!$haveIt) {
$originalClass = get_class($this->CopyContentFrom());
if ($originalClass == 'SiteTree') $name = 'ContentController';
else $name = $originalClass."_Controller";
$controller = new $name($this->dataRecord->copyContentFrom());
$haveIt = $controller->hasMethod($method);
}
return $haveIt;
}
/**
* Pass unrecognized method calls on to the original controller
*
* @param string $method
* @param string $args
*/
function __call($method, $args) {
try {
return parent::__call($method, $args);
} catch (Exception $e) {
// Hack... detect exception type. We really should use exception subclasses.
// if the exception isn't a 'no method' error, rethrow it
if ($e->getCode() !== 2175) throw $e;
$original = $this->copyContentFrom();
return call_user_func_array(array($original, $method), $args);
}
}
}
?>