FIX: content authors unable to duplicate top-level pages (fixes #1685)

This commit is contained in:
Loz Calver 2016-11-22 15:55:28 +00:00
parent 12c0f3c1c7
commit b2503ac004
2 changed files with 284 additions and 280 deletions

View File

@ -3,7 +3,7 @@
* Basic data-object representing all pages within the site tree. All page types that live within the hierarchy should * Basic data-object representing all pages within the site tree. All page types that live within the hierarchy should
* inherit from this. In addition, it contains a number of static methods for querying the site tree and working with * inherit from this. In addition, it contains a number of static methods for querying the site tree and working with
* draft and published states. * draft and published states.
* *
* <h2>URLs</h2> * <h2>URLs</h2>
* A page is identified during request handling via its "URLSegment" database column. As pages can be nested, the full * A page is identified during request handling via its "URLSegment" database column. As pages can be nested, the full
* path of a URL might contain multiple segments. Each segment is stored in its filtered representation (through * path of a URL might contain multiple segments. Each segment is stored in its filtered representation (through
@ -44,7 +44,7 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
* class is allowed - no subclasses. Otherwise, the class and all its * class is allowed - no subclasses. Otherwise, the class and all its
* subclasses are allowed. * subclasses are allowed.
* To control allowed children on root level (no parent), use {@link $can_be_root}. * To control allowed children on root level (no parent), use {@link $can_be_root}.
* *
* Note that this setting is cached when used in the CMS, use the "flush" query parameter to clear it. * Note that this setting is cached when used in the CMS, use the "flush" query parameter to clear it.
* *
* @config * @config
@ -168,13 +168,13 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
/** /**
* Icon to use in the CMS page tree. This should be the full filename, relative to the webroot. * Icon to use in the CMS page tree. This should be the full filename, relative to the webroot.
* Also supports custom CSS rule contents (applied to the correct selector for the tree UI implementation). * Also supports custom CSS rule contents (applied to the correct selector for the tree UI implementation).
* *
* @see CMSMain::generateTreeStylingCSS() * @see CMSMain::generateTreeStylingCSS()
* @config * @config
* @var string * @var string
*/ */
private static $icon = null; private static $icon = null;
/** /**
* @config * @config
* @var string Description of the class functionality, typically shown to a user * @var string Description of the class functionality, typically shown to a user
@ -187,7 +187,7 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
"Versioned('Stage', 'Live')", "Versioned('Stage', 'Live')",
"SiteTreeLinkTracking" "SiteTreeLinkTracking"
); );
private static $searchable_fields = array( private static $searchable_fields = array(
'Title', 'Title',
'Content', 'Content',
@ -196,22 +196,22 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
private static $field_labels = array( private static $field_labels = array(
'URLSegment' => 'URL' 'URLSegment' => 'URL'
); );
/** /**
* @config * @config
*/ */
private static $nested_urls = true; private static $nested_urls = true;
/** /**
* @config * @config
*/ */
private static $create_default_pages = true; private static $create_default_pages = true;
/** /**
* This controls whether of not extendCMSFields() is called by getCMSFields. * This controls whether of not extendCMSFields() is called by getCMSFields.
*/ */
private static $runCMSFieldsExtensions = true; private static $runCMSFieldsExtensions = true;
/** /**
* Cache for canView/Edit/Publish/Delete permissions. * Cache for canView/Edit/Publish/Delete permissions.
* Keyed by permission type (e.g. 'edit'), with an array * Keyed by permission type (e.g. 'edit'), with an array
@ -235,12 +235,12 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
private static $meta_generator = 'SilverStripe - http://silverstripe.org'; private static $meta_generator = 'SilverStripe - http://silverstripe.org';
protected $_cache_statusFlags = null; protected $_cache_statusFlags = null;
/** /**
* Determines if the system should avoid orphaned pages * Determines if the system should avoid orphaned pages
* by deleting all children when the their parent is deleted (TRUE), * by deleting all children when the their parent is deleted (TRUE),
* or rather preserve this data even if its not reachable through any navigation path (FALSE). * or rather preserve this data even if its not reachable through any navigation path (FALSE).
* *
* @deprecated 4.0 Use the "SiteTree.enforce_strict_hierarchy" config setting instead * @deprecated 4.0 Use the "SiteTree.enforce_strict_hierarchy" config setting instead
* @param boolean * @param boolean
*/ */
@ -248,7 +248,7 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
Deprecation::notice('4.0', 'Use the "SiteTree.enforce_strict_hierarchy" config setting instead'); Deprecation::notice('4.0', 'Use the "SiteTree.enforce_strict_hierarchy" config setting instead');
Config::inst()->update('SiteTree', 'enforce_strict_hierarchy', $to); Config::inst()->update('SiteTree', 'enforce_strict_hierarchy', $to);
} }
/** /**
* @deprecated 4.0 Use the "SiteTree.enforce_strict_hierarchy" config setting instead * @deprecated 4.0 Use the "SiteTree.enforce_strict_hierarchy" config setting instead
* @return boolean * @return boolean
@ -268,7 +268,7 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
Deprecation::notice('4.0', 'Use the "SiteTree.nested_urls" config setting instead'); Deprecation::notice('4.0', 'Use the "SiteTree.nested_urls" config setting instead');
return Config::inst()->get('SiteTree', 'nested_urls'); return Config::inst()->get('SiteTree', 'nested_urls');
} }
/** /**
* @deprecated 4.0 Use the "SiteTree.nested_urls" config setting instead * @deprecated 4.0 Use the "SiteTree.nested_urls" config setting instead
*/ */
@ -276,7 +276,7 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
Deprecation::notice('4.0', 'Use the "SiteTree.nested_urls" config setting instead'); Deprecation::notice('4.0', 'Use the "SiteTree.nested_urls" config setting instead');
Config::inst()->update('SiteTree', 'nested_urls', true); Config::inst()->update('SiteTree', 'nested_urls', true);
} }
/** /**
* @deprecated 4.0 Use the "SiteTree.nested_urls" config setting instead * @deprecated 4.0 Use the "SiteTree.nested_urls" config setting instead
*/ */
@ -284,7 +284,7 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
Deprecation::notice('4.0', 'Use the "SiteTree.nested_urls" config setting instead'); Deprecation::notice('4.0', 'Use the "SiteTree.nested_urls" config setting instead');
Config::inst()->update('SiteTree', 'nested_urls', false); Config::inst()->update('SiteTree', 'nested_urls', false);
} }
/** /**
* Set the (re)creation of default pages on /dev/build * Set the (re)creation of default pages on /dev/build
* *
@ -306,7 +306,7 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
Deprecation::notice('4.0', 'Use the "SiteTree.create_default_pages" config setting instead'); Deprecation::notice('4.0', 'Use the "SiteTree.create_default_pages" config setting instead');
return Config::inst()->get('SiteTree', 'create_default_pages'); return Config::inst()->get('SiteTree', 'create_default_pages');
} }
/** /**
* Fetches the {@link SiteTree} object that maps to a link. * Fetches the {@link SiteTree} object that maps to a link.
* *
@ -326,9 +326,9 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
} else { } else {
$link = RootURLController::get_homepage_link(); $link = RootURLController::get_homepage_link();
} }
$parts = preg_split('|/+|', $link); $parts = preg_split('|/+|', $link);
// Grab the initial root level page to traverse down from. // Grab the initial root level page to traverse down from.
$URLSegment = array_shift($parts); $URLSegment = array_shift($parts);
$conditions = array('"SiteTree"."URLSegment"' => rawurlencode($URLSegment)); $conditions = array('"SiteTree"."URLSegment"' => rawurlencode($URLSegment));
@ -336,7 +336,7 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
$conditions[] = array('"SiteTree"."ParentID"' => 0); $conditions[] = array('"SiteTree"."ParentID"' => 0);
} }
$sitetree = DataObject::get_one('SiteTree', $conditions, $cache); $sitetree = DataObject::get_one('SiteTree', $conditions, $cache);
/// Fall back on a unique URLSegment for b/c. /// Fall back on a unique URLSegment for b/c.
if( !$sitetree if( !$sitetree
&& self::config()->nested_urls && self::config()->nested_urls
@ -346,47 +346,47 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
) { ) {
return $page; return $page;
} }
// Attempt to grab an alternative page from extensions. // Attempt to grab an alternative page from extensions.
if(!$sitetree) { if(!$sitetree) {
$parentID = self::config()->nested_urls ? 0 : null; $parentID = self::config()->nested_urls ? 0 : null;
if($alternatives = singleton('SiteTree')->extend('alternateGetByLink', $URLSegment, $parentID)) { if($alternatives = singleton('SiteTree')->extend('alternateGetByLink', $URLSegment, $parentID)) {
foreach($alternatives as $alternative) if($alternative) $sitetree = $alternative; foreach($alternatives as $alternative) if($alternative) $sitetree = $alternative;
} }
if(!$sitetree) return false; if(!$sitetree) return false;
} }
// Check if we have any more URL parts to parse. // Check if we have any more URL parts to parse.
if(!self::config()->nested_urls || !count($parts)) return $sitetree; if(!self::config()->nested_urls || !count($parts)) return $sitetree;
// Traverse down the remaining URL segments and grab the relevant SiteTree objects. // Traverse down the remaining URL segments and grab the relevant SiteTree objects.
foreach($parts as $segment) { foreach($parts as $segment) {
$next = DataObject::get_one('SiteTree', array( $next = DataObject::get_one('SiteTree', array(
'"SiteTree"."URLSegment"' => $segment, '"SiteTree"."URLSegment"' => $segment,
'"SiteTree"."ParentID"' => $sitetree->ID '"SiteTree"."ParentID"' => $sitetree->ID
), ),
$cache $cache
); );
if(!$next) { if(!$next) {
$parentID = (int) $sitetree->ID; $parentID = (int) $sitetree->ID;
if($alternatives = singleton('SiteTree')->extend('alternateGetByLink', $segment, $parentID)) { if($alternatives = singleton('SiteTree')->extend('alternateGetByLink', $segment, $parentID)) {
foreach($alternatives as $alternative) if($alternative) $next = $alternative; foreach($alternatives as $alternative) if($alternative) $next = $alternative;
} }
if(!$next) return false; if(!$next) return false;
} }
$sitetree->destroy(); $sitetree->destroy();
$sitetree = $next; $sitetree = $next;
} }
return $sitetree; return $sitetree;
} }
/** /**
* Return a subclass map of SiteTree that shouldn't be hidden through {@link SiteTree::$hide_ancestor} * Return a subclass map of SiteTree that shouldn't be hidden through {@link SiteTree::$hide_ancestor}
* *
@ -424,7 +424,7 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
return $classes; return $classes;
} }
/** /**
* Replace a "[sitetree_link id=n]" shortcode with a link to the page with the corresponding ID. * Replace a "[sitetree_link id=n]" shortcode with a link to the page with the corresponding ID.
* *
@ -435,7 +435,7 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
*/ */
static public function link_shortcode_handler($arguments, $content = null, $parser = null) { static public function link_shortcode_handler($arguments, $content = null, $parser = null) {
if(!isset($arguments['id']) || !is_numeric($arguments['id'])) return; if(!isset($arguments['id']) || !is_numeric($arguments['id'])) return;
if ( if (
!($page = DataObject::get_by_id('SiteTree', $arguments['id'])) // Get the current page by ID. !($page = DataObject::get_by_id('SiteTree', $arguments['id'])) // Get the current page by ID.
&& !($page = Versioned::get_latest_version('SiteTree', $arguments['id'])) // Attempt link to old version. && !($page = Versioned::get_latest_version('SiteTree', $arguments['id'])) // Attempt link to old version.
@ -445,7 +445,7 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
} }
$link = Convert::raw2att($page->Link()); $link = Convert::raw2att($page->Link());
if($content) { if($content) {
return sprintf('<a href="%s">%s</a>', $link, $parser->parse($content)); return sprintf('<a href="%s">%s</a>', $link, $parser->parse($content));
} else { } else {
@ -465,7 +465,7 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
public function Link($action = null) { public function Link($action = null) {
return Controller::join_links(Director::baseURL(), $this->RelativeLink($action)); return Controller::join_links(Director::baseURL(), $this->RelativeLink($action));
} }
/** /**
* Get the absolute URL for this page, including protocol and host. * Get the absolute URL for this page, including protocol and host.
* *
@ -479,11 +479,11 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
return Director::absoluteURL($this->Link($action)); return Director::absoluteURL($this->Link($action));
} }
} }
/** /**
* Base link used for previewing. Defaults to absolute URL, in order to account for domain changes, e.g. on multi * Base link used for previewing. Defaults to absolute URL, in order to account for domain changes, e.g. on multi
* site setups. Does not contain hints about the stage, see {@link SilverStripeNavigator} for details. * site setups. Does not contain hints about the stage, see {@link SilverStripeNavigator} for details.
* *
* @param string $action See {@link Link()} * @param string $action See {@link Link()}
* @return string * @return string
*/ */
@ -494,7 +494,7 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
return $this->AbsoluteLink($action); return $this->AbsoluteLink($action);
} }
} }
/** /**
* Return the link for this {@link SiteTree} object relative to the SilverStripe root. * Return the link for this {@link SiteTree} object relative to the SilverStripe root.
* *
@ -503,7 +503,7 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
* and returned in its full form. * and returned in its full form.
* *
* @uses RootURLController::get_homepage_link() * @uses RootURLController::get_homepage_link()
* *
* @param string $action See {@link Link()} * @param string $action See {@link Link()}
* @return string * @return string
*/ */
@ -517,15 +517,15 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
$base = $parent->RelativeLink($this->URLSegment); $base = $parent->RelativeLink($this->URLSegment);
} elseif(!$action && $this->URLSegment == RootURLController::get_homepage_link()) { } elseif(!$action && $this->URLSegment == RootURLController::get_homepage_link()) {
// Unset base for root-level homepages. // Unset base for root-level homepages.
// Note: Homepages with action parameters (or $action === true) // Note: Homepages with action parameters (or $action === true)
// need to retain their URLSegment. // need to retain their URLSegment.
$base = null; $base = null;
} else { } else {
$base = $this->URLSegment; $base = $this->URLSegment;
} }
$this->extend('updateRelativeLink', $base, $action); $this->extend('updateRelativeLink', $base, $action);
// Legacy support: If $action === true, retain URLSegment for homepages, // Legacy support: If $action === true, retain URLSegment for homepages,
// but don't append any action // but don't append any action
if($action === true) $action = null; if($action === true) $action = null;
@ -555,7 +555,7 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
Versioned::reading_stage($oldStage); Versioned::reading_stage($oldStage);
return $link; return $link;
} }
/** /**
* Generates a link to edit this page in the CMS. * Generates a link to edit this page in the CMS.
* *
@ -564,8 +564,8 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
public function CMSEditLink() { public function CMSEditLink() {
return Controller::join_links(singleton('CMSPageEditController')->Link('show'), $this->ID); return Controller::join_links(singleton('CMSPageEditController')->Link('show'), $this->ID);
} }
/** /**
* Return a CSS identifier generated from this page's link. * Return a CSS identifier generated from this page's link.
* *
@ -574,7 +574,7 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
public function ElementName() { public function ElementName() {
return str_replace('/', '-', trim($this->RelativeLink(true), '/')); return str_replace('/', '-', trim($this->RelativeLink(true), '/'));
} }
/** /**
* Returns true if this is the currently active page being used to handle this request. * Returns true if this is the currently active page being used to handle this request.
* *
@ -583,7 +583,7 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
public function isCurrent() { public function isCurrent() {
return $this->ID ? $this->ID == Director::get_current_page()->ID : $this === Director::get_current_page(); return $this->ID ? $this->ID == Director::get_current_page()->ID : $this === Director::get_current_page();
} }
/** /**
* Check if this page is in the currently active section (e.g. it is either current or one of its children is * Check if this page is in the currently active section (e.g. it is either current or one of its children is
* currently being viewed). * currently being viewed).
@ -595,23 +595,23 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
Director::get_current_page() instanceof SiteTree && in_array($this->ID, Director::get_current_page()->getAncestors()->column()) Director::get_current_page() instanceof SiteTree && in_array($this->ID, Director::get_current_page()->getAncestors()->column())
); );
} }
/** /**
* Check if the parent of this page has been removed (or made otherwise unavailable), and is still referenced by * Check if the parent of this page has been removed (or made otherwise unavailable), and is still referenced by
* this child. Any such orphaned page may still require access via the CMS, but should not be shown as accessible * this child. Any such orphaned page may still require access via the CMS, but should not be shown as accessible
* to external users. * to external users.
* *
* @return bool * @return bool
*/ */
public function isOrphaned() { public function isOrphaned() {
// Always false for root pages // Always false for root pages
if(empty($this->ParentID)) return false; if(empty($this->ParentID)) return false;
// Parent must exist and not be an orphan itself // Parent must exist and not be an orphan itself
$parent = $this->Parent(); $parent = $this->Parent();
return !$parent || !$parent->exists() || $parent->isOrphaned(); return !$parent || !$parent->exists() || $parent->isOrphaned();
} }
/** /**
* Return "link" or "current" depending on if this is the {@link SiteTree::isCurrent()} current page. * Return "link" or "current" depending on if this is the {@link SiteTree::isCurrent()} current page.
* *
@ -620,7 +620,7 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
public function LinkOrCurrent() { public function LinkOrCurrent() {
return $this->isCurrent() ? 'current' : 'link'; return $this->isCurrent() ? 'current' : 'link';
} }
/** /**
* Return "link" or "section" depending on if this is the {@link SiteTree::isSeciton()} current section. * Return "link" or "section" depending on if this is the {@link SiteTree::isSeciton()} current section.
* *
@ -629,7 +629,7 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
public function LinkOrSection() { public function LinkOrSection() {
return $this->isSection() ? 'section' : 'link'; return $this->isSection() ? 'section' : 'link';
} }
/** /**
* Return "link", "current" or "section" depending on if this page is the current page, or not on the current page * Return "link", "current" or "section" depending on if this page is the current page, or not on the current page
* but in the current section. * but in the current section.
@ -645,7 +645,7 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
return 'link'; return 'link';
} }
} }
/** /**
* Check if this page is in the given current section. * Check if this page is in the given current section.
* *
@ -670,18 +670,18 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
* @return self The duplicated object * @return self The duplicated object
*/ */
public function duplicate($doWrite = true) { public function duplicate($doWrite = true) {
$page = parent::duplicate(false); $page = parent::duplicate(false);
$page->Sort = 0; $page->Sort = 0;
$this->invokeWithExtensions('onBeforeDuplicate', $page); $this->invokeWithExtensions('onBeforeDuplicate', $page);
if($doWrite) { if($doWrite) {
$page->write(); $page->write();
$page = $this->duplicateManyManyRelations($this, $page); $page = $this->duplicateManyManyRelations($this, $page);
} }
$this->invokeWithExtensions('onAfterDuplicate', $page); $this->invokeWithExtensions('onAfterDuplicate', $page);
return $page; return $page;
} }
@ -718,7 +718,7 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
$newSiteTree->Sort = 0; $newSiteTree->Sort = 0;
$newSiteTree->write(); $newSiteTree->write();
} }
/** /**
* Return a breadcrumb trail to this page. Excludes "hidden" pages (with ShowInMenus=0) by default. * Return a breadcrumb trail to this page. Excludes "hidden" pages (with ShowInMenus=0) by default.
* *
@ -750,16 +750,16 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
public function getBreadcrumbItems($maxDepth = 20, $stopAtPageType = false, $showHidden = false) { public function getBreadcrumbItems($maxDepth = 20, $stopAtPageType = false, $showHidden = false) {
$page = $this; $page = $this;
$pages = array(); $pages = array();
while( while(
$page $page
&& (!$maxDepth || count($pages) < $maxDepth) && (!$maxDepth || count($pages) < $maxDepth)
&& (!$stopAtPageType || $page->ClassName != $stopAtPageType) && (!$stopAtPageType || $page->ClassName != $stopAtPageType)
) { ) {
if($showHidden || $page->ShowInMenus || ($page->ID == $this->ID)) { if($showHidden || $page->ShowInMenus || ($page->ID == $this->ID)) {
$pages[] = $page; $pages[] = $page;
} }
$page = $page->Parent; $page = $page->Parent;
} }
@ -769,7 +769,7 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
/** /**
* Make this page a child of another page. * Make this page a child of another page.
* *
* If the parent page does not exist, resolve it to a valid ID before updating this page's reference. * If the parent page does not exist, resolve it to a valid ID before updating this page's reference.
* *
* @param SiteTree|int $item Either the parent object, or the parent ID * @param SiteTree|int $item Either the parent object, or the parent ID
@ -782,7 +782,7 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
$this->setField("ParentID", $item); $this->setField("ParentID", $item);
} }
} }
/** /**
* Get the parent of this page. * Get the parent of this page.
* *
@ -814,7 +814,7 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
/** /**
* This function should return true if the current user can execute this action. It can be overloaded to customise * This function should return true if the current user can execute this action. It can be overloaded to customise
* the security model for an application. * the security model for an application.
* *
* Slightly altered from parent behaviour in {@link DataObject->can()}: * Slightly altered from parent behaviour in {@link DataObject->can()}:
* - Checks for existence of a method named "can<$perm>()" on the object * - Checks for existence of a method named "can<$perm>()" on the object
* - Calls decorators and only returns for FALSE "vetoes" * - Calls decorators and only returns for FALSE "vetoes"
@ -833,12 +833,12 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
} }
if($member && Permission::checkMember($member, "ADMIN")) return true; if($member && Permission::checkMember($member, "ADMIN")) return true;
if(is_string($perm) && method_exists($this, 'can' . ucfirst($perm))) { if(is_string($perm) && method_exists($this, 'can' . ucfirst($perm))) {
$method = 'can' . ucfirst($perm); $method = 'can' . ucfirst($perm);
return $this->$method($member); return $this->$method($member);
} }
$results = $this->extend('can', $member); $results = $this->extend('can', $member);
if($results && is_array($results)) if(!min($results)) return false; if($results && is_array($results)) if(!min($results)) return false;
@ -848,12 +848,12 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
/** /**
* This function should return true if the current user can add children to this page. It can be overloaded to * This function should return true if the current user can add children to this page. It can be overloaded to
* customise the security model for an application. * customise the security model for an application.
* *
* Denies permission if any of the following conditions is true: * Denies permission if any of the following conditions is true:
* - alternateCanAddChildren() on a extension returns false * - alternateCanAddChildren() on a extension returns false
* - canEdit() is not granted * - canEdit() is not granted
* - There are no classes defined in {@link $allowed_children} * - There are no classes defined in {@link $allowed_children}
* *
* @uses SiteTreeExtension->canAddChildren() * @uses SiteTreeExtension->canAddChildren()
* @uses canEdit() * @uses canEdit()
* @uses $allowed_children * @uses $allowed_children
@ -872,18 +872,18 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
} }
if($member && Permission::checkMember($member, "ADMIN")) return true; if($member && Permission::checkMember($member, "ADMIN")) return true;
// Standard mechanism for accepting permission changes from extensions // Standard mechanism for accepting permission changes from extensions
$extended = $this->extendedCan('canAddChildren', $member); $extended = $this->extendedCan('canAddChildren', $member);
if($extended !== null) return $extended; if($extended !== null) return $extended;
return $this->canEdit($member) && $this->stat('allowed_children') != 'none'; return $this->canEdit($member) && $this->stat('allowed_children') != 'none';
} }
/** /**
* This function should return true if the current user can view this page. It can be overloaded to customise the * This function should return true if the current user can view this page. It can be overloaded to customise the
* security model for an application. * security model for an application.
* *
* Denies permission if any of the following conditions is true: * Denies permission if any of the following conditions is true:
* - canView() on any extension returns false * - canView() on any extension returns false
* - "CanViewType" directive is set to "Inherit" and any parent page return false for canView() * - "CanViewType" directive is set to "Inherit" and any parent page return false for canView()
@ -920,14 +920,14 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
// check to make sure this version is the live version and so can be viewed // check to make sure this version is the live version and so can be viewed
if (Versioned::get_versionnumber_by_stage($this->class, 'Live', $this->ID) != $this->Version) return false; if (Versioned::get_versionnumber_by_stage($this->class, 'Live', $this->ID) != $this->Version) return false;
} }
// Orphaned pages (in the current stage) are unavailable, except for admins via the CMS // Orphaned pages (in the current stage) are unavailable, except for admins via the CMS
if($this->isOrphaned()) return false; if($this->isOrphaned()) return false;
// Standard mechanism for accepting permission changes from extensions // Standard mechanism for accepting permission changes from extensions
$extended = $this->extendedCan('canView', $member); $extended = $this->extendedCan('canView', $member);
if($extended !== null) return $extended; if($extended !== null) return $extended;
// check for empty spec // check for empty spec
if(!$this->CanViewType || $this->CanViewType == 'Anyone') return true; if(!$this->CanViewType || $this->CanViewType == 'Anyone') return true;
@ -936,29 +936,29 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
if($this->ParentID) return $this->Parent()->canView($member); if($this->ParentID) return $this->Parent()->canView($member);
else return $this->getSiteConfig()->canViewPages($member); else return $this->getSiteConfig()->canViewPages($member);
} }
// check for any logged-in users // check for any logged-in users
if($this->CanViewType == 'LoggedInUsers' && $member) { if($this->CanViewType == 'LoggedInUsers' && $member) {
return true; return true;
} }
// check for specific groups // check for specific groups
if($member && is_numeric($member)) $member = DataObject::get_by_id('Member', $member); if($member && is_numeric($member)) $member = DataObject::get_by_id('Member', $member);
if( if(
$this->CanViewType == 'OnlyTheseUsers' $this->CanViewType == 'OnlyTheseUsers'
&& $member && $member
&& $member->inGroups($this->ViewerGroups()) && $member->inGroups($this->ViewerGroups())
) return true; ) return true;
return false; return false;
} }
/** /**
* Determines canView permissions for the latest version of this Page on a specific stage (see {@link Versioned}). * Determines canView permissions for the latest version of this Page on a specific stage (see {@link Versioned}).
* Usually the stage is read from {@link Versioned::current_stage()}. * Usually the stage is read from {@link Versioned::current_stage()}.
* *
* @todo Implement in CMS UI. * @todo Implement in CMS UI.
* *
* @param string $stage * @param string $stage
* @param Member $member * @param Member $member
* @return bool * @return bool
@ -968,7 +968,7 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
Versioned::reading_stage($stage); Versioned::reading_stage($stage);
$versionFromStage = DataObject::get($this->class)->byID($this->ID); $versionFromStage = DataObject::get($this->class)->byID($this->ID);
Versioned::set_reading_mode($oldMode); Versioned::set_reading_mode($oldMode);
return $versionFromStage ? $versionFromStage->canView($member) : false; return $versionFromStage ? $versionFromStage->canView($member) : false;
} }
@ -976,12 +976,12 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
/** /**
* This function should return true if the current user can delete this page. It can be overloaded to customise the * This function should return true if the current user can delete this page. It can be overloaded to customise the
* security model for an application. * security model for an application.
* *
* Denies permission if any of the following conditions is true: * Denies permission if any of the following conditions is true:
* - canDelete() returns false on any extension * - canDelete() returns false on any extension
* - canEdit() returns false * - canEdit() returns false
* - any descendant page returns false for canDelete() * - any descendant page returns false for canDelete()
* *
* @uses canDelete() * @uses canDelete()
* @uses SiteTreeExtension->canDelete() * @uses SiteTreeExtension->canDelete()
* @uses canEdit() * @uses canEdit()
@ -993,32 +993,32 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
if($member instanceof Member) $memberID = $member->ID; if($member instanceof Member) $memberID = $member->ID;
else if(is_numeric($member)) $memberID = $member; else if(is_numeric($member)) $memberID = $member;
else $memberID = Member::currentUserID(); else $memberID = Member::currentUserID();
if($memberID && Permission::checkMember($memberID, array("ADMIN", "SITETREE_EDIT_ALL"))) { if($memberID && Permission::checkMember($memberID, array("ADMIN", "SITETREE_EDIT_ALL"))) {
return true; return true;
} }
// Standard mechanism for accepting permission changes from extensions // Standard mechanism for accepting permission changes from extensions
$extended = $this->extendedCan('canDelete', $memberID); $extended = $this->extendedCan('canDelete', $memberID);
if($extended !== null) return $extended; if($extended !== null) return $extended;
// Regular canEdit logic is handled by can_edit_multiple // Regular canEdit logic is handled by can_edit_multiple
$results = self::can_delete_multiple(array($this->ID), $memberID); $results = self::can_delete_multiple(array($this->ID), $memberID);
// If this page no longer exists in stage/live results won't contain the page. // If this page no longer exists in stage/live results won't contain the page.
// Fail-over to false // Fail-over to false
return isset($results[$this->ID]) ? $results[$this->ID] : false; return isset($results[$this->ID]) ? $results[$this->ID] : false;
} }
/** /**
* This function should return true if the current user can create new pages of this class, regardless of class. It * This function should return true if the current user can create new pages of this class, regardless of class. It
* can be overloaded to customise the security model for an application. * can be overloaded to customise the security model for an application.
* *
* By default, permission to create at the root level is based on the SiteConfig configuration, and permission to * By default, permission to create at the root level is based on the SiteConfig configuration, and permission to
* create beneath a parent is based on the ability to edit that parent page. * create beneath a parent is based on the ability to edit that parent page.
* *
* Use {@link canAddChildren()} to control behaviour of creating children under this page. * Use {@link canAddChildren()} to control behaviour of creating children under this page.
* *
* @uses $can_create * @uses $can_create
* @uses DataExtension->canCreate() * @uses DataExtension->canCreate()
* *
@ -1049,7 +1049,7 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
} }
// Fall over to inherited permissions // Fall over to inherited permissions
if($parent) { if($parent && $parent->exists()) {
return $parent->canAddChildren($member); return $parent->canAddChildren($member);
} else { } else {
// This doesn't necessarily mean we are creating a root page, but that // This doesn't necessarily mean we are creating a root page, but that
@ -1061,7 +1061,7 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
/** /**
* This function should return true if the current user can edit this page. It can be overloaded to customise the * This function should return true if the current user can edit this page. It can be overloaded to customise the
* security model for an application. * security model for an application.
* *
* Denies permission if any of the following conditions is true: * Denies permission if any of the following conditions is true:
* - canEdit() on any extension returns false * - canEdit() on any extension returns false
* - canView() return false * - canView() return false
@ -1069,7 +1069,7 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
* - "CanEditType" directive is set to "LoggedInUsers" and no user is logged in or doesn't have the * - "CanEditType" directive is set to "LoggedInUsers" and no user is logged in or doesn't have the
* CMS_Access_CMSMAIN permission code * CMS_Access_CMSMAIN permission code
* - "CanEditType" directive is set to "OnlyTheseUsers" and user is not in the given groups * - "CanEditType" directive is set to "OnlyTheseUsers" and user is not in the given groups
* *
* @uses canView() * @uses canView()
* @uses EditorGroups() * @uses EditorGroups()
* @uses DataExtension->canEdit() * @uses DataExtension->canEdit()
@ -1082,9 +1082,9 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
if($member instanceof Member) $memberID = $member->ID; if($member instanceof Member) $memberID = $member->ID;
else if(is_numeric($member)) $memberID = $member; else if(is_numeric($member)) $memberID = $member;
else $memberID = Member::currentUserID(); else $memberID = Member::currentUserID();
if($memberID && Permission::checkMember($memberID, array("ADMIN", "SITETREE_EDIT_ALL"))) return true; if($memberID && Permission::checkMember($memberID, array("ADMIN", "SITETREE_EDIT_ALL"))) return true;
// Standard mechanism for accepting permission changes from extensions // Standard mechanism for accepting permission changes from extensions
$extended = $this->extendedCan('canEdit', $memberID); $extended = $this->extendedCan('canEdit', $memberID);
if($extended !== null) return $extended; if($extended !== null) return $extended;
@ -1096,7 +1096,7 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
// If this page no longer exists in stage/live results won't contain the page. // If this page no longer exists in stage/live results won't contain the page.
// Fail-over to false // Fail-over to false
return isset($results[$this->ID]) ? $results[$this->ID] : false; return isset($results[$this->ID]) ? $results[$this->ID] : false;
// Default for unsaved pages // Default for unsaved pages
} else { } else {
return $this->getSiteConfig()->canEditPages($member); return $this->getSiteConfig()->canEditPages($member);
@ -1106,11 +1106,11 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
/** /**
* This function should return true if the current user can publish this page. It can be overloaded to customise * This function should return true if the current user can publish this page. It can be overloaded to customise
* the security model for an application. * the security model for an application.
* *
* Denies permission if any of the following conditions is true: * Denies permission if any of the following conditions is true:
* - canPublish() on any extension returns false * - canPublish() on any extension returns false
* - canEdit() returns false * - canEdit() returns false
* *
* @uses SiteTreeExtension->canPublish() * @uses SiteTreeExtension->canPublish()
* *
* @param Member $member * @param Member $member
@ -1118,7 +1118,7 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
*/ */
public function canPublish($member = null) { public function canPublish($member = null) {
if(!$member || !(is_a($member, 'Member')) || is_numeric($member)) $member = Member::currentUser(); if(!$member || !(is_a($member, 'Member')) || is_numeric($member)) $member = Member::currentUser();
if($member && Permission::checkMember($member, "ADMIN")) return true; if($member && Permission::checkMember($member, "ADMIN")) return true;
// Standard mechanism for accepting permission changes from extensions // Standard mechanism for accepting permission changes from extensions
@ -1128,7 +1128,7 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
// Normal case - fail over to canEdit() // Normal case - fail over to canEdit()
return $this->canEdit($member); return $this->canEdit($member);
} }
public function canDeleteFromLive($member = null) { public function canDeleteFromLive($member = null) {
// Standard mechanism for accepting permission changes from extensions // Standard mechanism for accepting permission changes from extensions
$extended = $this->extendedCan('canDeleteFromLive', $member); $extended = $this->extendedCan('canDeleteFromLive', $member);
@ -1136,19 +1136,19 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
return $this->canPublish($member); return $this->canPublish($member);
} }
/** /**
* Stub method to get the site config, unless the current class can provide an alternate. * Stub method to get the site config, unless the current class can provide an alternate.
* *
* @return SiteConfig * @return SiteConfig
*/ */
public function getSiteConfig() { public function getSiteConfig() {
if($this->hasMethod('alternateSiteConfig')) { if($this->hasMethod('alternateSiteConfig')) {
$altConfig = $this->alternateSiteConfig(); $altConfig = $this->alternateSiteConfig();
if($altConfig) return $altConfig; if($altConfig) return $altConfig;
} }
return SiteConfig::current_site_config(); return SiteConfig::current_site_config();
} }
@ -1163,7 +1163,7 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
*/ */
static public function prepopulate_permission_cache($permission = 'CanEditType', $ids, $batchCallback = null) { static public function prepopulate_permission_cache($permission = 'CanEditType', $ids, $batchCallback = null) {
if(!$batchCallback) $batchCallback = "SiteTree::can_{$permission}_multiple"; if(!$batchCallback) $batchCallback = "SiteTree::can_{$permission}_multiple";
if(is_callable($batchCallback)) { if(is_callable($batchCallback)) {
call_user_func($batchCallback, $ids, Member::currentUserID(), false); call_user_func($batchCallback, $ids, Member::currentUserID(), false);
} else { } else {
@ -1196,7 +1196,7 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
// Sanitise the IDs // Sanitise the IDs
$ids = array_filter($ids, 'is_numeric'); $ids = array_filter($ids, 'is_numeric');
// This is the name used on the permission cache // This is the name used on the permission cache
// converts something like 'CanEditType' to 'edit'. // converts something like 'CanEditType' to 'edit'.
$cacheKey = strtolower(substr($typeField, 3, -4)) . "-$memberID"; $cacheKey = strtolower(substr($typeField, 3, -4)) . "-$memberID";
@ -1208,7 +1208,7 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
// Look in the cache for values // Look in the cache for values
if($useCached && isset(self::$cache_permissions[$cacheKey])) { if($useCached && isset(self::$cache_permissions[$cacheKey])) {
$cachedValues = array_intersect_key(self::$cache_permissions[$cacheKey], $result); $cachedValues = array_intersect_key(self::$cache_permissions[$cacheKey], $result);
// If we can't find everything in the cache, then look up the remainder separately // If we can't find everything in the cache, then look up the remainder separately
$uncachedValues = array_diff_key($result, self::$cache_permissions[$cacheKey]); $uncachedValues = array_diff_key($result, self::$cache_permissions[$cacheKey]);
if($uncachedValues) { if($uncachedValues) {
@ -1216,7 +1216,7 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
} }
return $cachedValues; return $cachedValues;
} }
// If a member doesn't have a certain permission then they can't edit anything // If a member doesn't have a certain permission then they can't edit anything
if(!$memberID || ($globalPermission && !Permission::checkMember($memberID, $globalPermission))) { if(!$memberID || ($globalPermission && !Permission::checkMember($memberID, $globalPermission))) {
return $result; return $result;
@ -1228,18 +1228,18 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
// If page can't be viewed, don't grant edit permissions to do - implement can_view_multiple(), so this can // If page can't be viewed, don't grant edit permissions to do - implement can_view_multiple(), so this can
// be enabled // be enabled
//$ids = array_keys(array_filter(self::can_view_multiple($ids, $memberID))); //$ids = array_keys(array_filter(self::can_view_multiple($ids, $memberID)));
// Get the groups that the given member belongs to // Get the groups that the given member belongs to
$groupIDs = DataObject::get_by_id('Member', $memberID)->Groups()->column("ID"); $groupIDs = DataObject::get_by_id('Member', $memberID)->Groups()->column("ID");
$SQL_groupList = implode(", ", $groupIDs); $SQL_groupList = implode(", ", $groupIDs);
if (!$SQL_groupList) $SQL_groupList = '0'; if (!$SQL_groupList) $SQL_groupList = '0';
$combinedStageResult = array(); $combinedStageResult = array();
foreach(array('Stage', 'Live') as $stage) { foreach(array('Stage', 'Live') as $stage) {
// Start by filling the array with the pages that actually exist // Start by filling the array with the pages that actually exist
$table = ($stage=='Stage') ? "SiteTree" : "SiteTree_$stage"; $table = ($stage=='Stage') ? "SiteTree" : "SiteTree_$stage";
if($ids) { if($ids) {
$idQuery = "SELECT \"ID\" FROM \"$table\" WHERE \"ID\" IN ($idPlaceholders)"; $idQuery = "SELECT \"ID\" FROM \"$table\" WHERE \"ID\" IN ($idPlaceholders)";
$stageIds = DB::prepared_query($idQuery, $ids)->column(); $stageIds = DB::prepared_query($idQuery, $ids)->column();
@ -1247,7 +1247,7 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
$stageIds = array(); $stageIds = array();
} }
$result = array_fill_keys($stageIds, false); $result = array_fill_keys($stageIds, false);
// Get the uninherited permissions // Get the uninherited permissions
$uninheritedPermissions = Versioned::get_by_stage("SiteTree", $stage) $uninheritedPermissions = Versioned::get_by_stage("SiteTree", $stage)
->where(array( ->where(array(
@ -1257,7 +1257,7 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
=> $ids => $ids
)) ))
->leftJoin($groupJoinTable, "\"$groupJoinTable\".\"SiteTreeID\" = \"SiteTree\".\"ID\" AND \"$groupJoinTable\".\"GroupID\" IN ($SQL_groupList)"); ->leftJoin($groupJoinTable, "\"$groupJoinTable\".\"SiteTreeID\" = \"SiteTree\".\"ID\" AND \"$groupJoinTable\".\"GroupID\" IN ($SQL_groupList)");
if($uninheritedPermissions) { if($uninheritedPermissions) {
// Set all the relevant items in $result to true // Set all the relevant items in $result to true
$result = array_fill_keys($uninheritedPermissions->column('ID'), true) + $result; $result = array_fill_keys($uninheritedPermissions->column('ID'), true) + $result;
@ -1266,7 +1266,7 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
// Get permissions that are inherited // Get permissions that are inherited
$potentiallyInherited = Versioned::get_by_stage( $potentiallyInherited = Versioned::get_by_stage(
"SiteTree", "SiteTree",
$stage, $stage,
array("\"$typeField\" = 'Inherit' AND \"SiteTree\".\"ID\" IN ($idPlaceholders)" => $ids) array("\"$typeField\" = 'Inherit' AND \"SiteTree\".\"ID\" IN ($idPlaceholders)" => $ids)
); );
@ -1297,9 +1297,9 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
} }
} }
} }
$combinedStageResult = $combinedStageResult + $result; $combinedStageResult = $combinedStageResult + $result;
} }
} }
@ -1307,7 +1307,7 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
// Cache the results // Cache the results
if(empty(self::$cache_permissions[$cacheKey])) self::$cache_permissions[$cacheKey] = array(); if(empty(self::$cache_permissions[$cacheKey])) self::$cache_permissions[$cacheKey] = array();
self::$cache_permissions[$cacheKey] = $combinedStageResult + self::$cache_permissions[$cacheKey]; self::$cache_permissions[$cacheKey] = $combinedStageResult + self::$cache_permissions[$cacheKey];
return $combinedStageResult; return $combinedStageResult;
} else { } else {
return array(); return array();
@ -1339,11 +1339,11 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
$deletable = array(); $deletable = array();
$result = array_fill_keys($ids, false); $result = array_fill_keys($ids, false);
$cacheKey = "delete-$memberID"; $cacheKey = "delete-$memberID";
// Look in the cache for values // Look in the cache for values
if($useCached && isset(self::$cache_permissions[$cacheKey])) { if($useCached && isset(self::$cache_permissions[$cacheKey])) {
$cachedValues = array_intersect_key(self::$cache_permissions[$cacheKey], $result); $cachedValues = array_intersect_key(self::$cache_permissions[$cacheKey], $result);
// If we can't find everything in the cache, then look up the remainder separately // If we can't find everything in the cache, then look up the remainder separately
$uncachedValues = array_diff_key($result, self::$cache_permissions[$cacheKey]); $uncachedValues = array_diff_key($result, self::$cache_permissions[$cacheKey]);
if($uncachedValues) { if($uncachedValues) {
@ -1356,7 +1356,7 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
// You can only delete pages that you can edit // You can only delete pages that you can edit
$editableIDs = array_keys(array_filter(self::can_edit_multiple($ids, $memberID))); $editableIDs = array_keys(array_filter(self::can_edit_multiple($ids, $memberID)));
if($editableIDs) { if($editableIDs) {
// You can only delete pages whose children you can delete // You can only delete pages whose children you can delete
$editablePlaceholders = DB::placeholders($editableIDs); $editablePlaceholders = DB::placeholders($editableIDs);
$childRecords = SiteTree::get()->where(array( $childRecords = SiteTree::get()->where(array(
@ -1367,7 +1367,7 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
// Find out the children that can be deleted // Find out the children that can be deleted
$deletableChildren = self::can_delete_multiple($children->keys(), $memberID); $deletableChildren = self::can_delete_multiple($children->keys(), $memberID);
// Get a list of all the parents that have no undeletable children // Get a list of all the parents that have no undeletable children
$deletableParents = array_fill_keys($editableIDs, true); $deletableParents = array_fill_keys($editableIDs, true);
foreach($deletableChildren as $id => $canDelete) { foreach($deletableChildren as $id => $canDelete) {
@ -1390,7 +1390,7 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
} else { } else {
$deletable = array(); $deletable = array();
} }
// Convert the array of deletable IDs into a map of the original IDs with true/false as the value // Convert the array of deletable IDs into a map of the original IDs with true/false as the value
return array_fill_keys($deletable, true) + array_fill_keys($ids, false); return array_fill_keys($deletable, true) + array_fill_keys($ids, false);
} }
@ -1439,10 +1439,10 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
if($this->MetaDescription) { if($this->MetaDescription) {
$tags .= "<meta name=\"description\" content=\"" . Convert::raw2att($this->MetaDescription) . "\" />\n"; $tags .= "<meta name=\"description\" content=\"" . Convert::raw2att($this->MetaDescription) . "\" />\n";
} }
if($this->ExtraMeta) { if($this->ExtraMeta) {
$tags .= $this->ExtraMeta . "\n"; $tags .= $this->ExtraMeta . "\n";
} }
if(Permission::check('CMS_ACCESS_CMSMain') if(Permission::check('CMS_ACCESS_CMSMain')
&& in_array('CMSPreviewable', class_implements($this)) && in_array('CMSPreviewable', class_implements($this))
&& !$this instanceof ErrorPage && !$this instanceof ErrorPage
@ -1477,7 +1477,7 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
*/ */
public function requireDefaultRecords() { public function requireDefaultRecords() {
parent::requireDefaultRecords(); parent::requireDefaultRecords();
// default pages // default pages
if($this->class == 'SiteTree' && $this->config()->create_default_pages) { if($this->class == 'SiteTree' && $this->config()->create_default_pages) {
if(!SiteTree::get_by_link(Config::inst()->get('RootURLController', 'default_homepage_link'))) { if(!SiteTree::get_by_link(Config::inst()->get('RootURLController', 'default_homepage_link'))) {
@ -1512,7 +1512,7 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
DB::alteration_message('Contact Us page created', 'created'); DB::alteration_message('Contact Us page created', 'created');
} }
} }
// schema migration // schema migration
// @todo Move to migration task once infrastructure is implemented // @todo Move to migration task once infrastructure is implemented
if($this->class == 'SiteTree') { if($this->class == 'SiteTree') {
@ -1532,7 +1532,7 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
if(!$this->Sort) { if(!$this->Sort) {
$parentID = ($this->ParentID) ? $this->ParentID : 0; $parentID = ($this->ParentID) ? $this->ParentID : 0;
$this->Sort = DB::prepared_query( $this->Sort = DB::prepared_query(
"SELECT MAX(\"Sort\") + 1 FROM \"SiteTree\" WHERE \"ParentID\" = ?", "SELECT MAX(\"Sort\") + 1 FROM \"SiteTree\" WHERE \"ParentID\" = ?",
array($parentID) array($parentID)
)->value(); )->value();
} }
@ -1552,7 +1552,7 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
// If after sanitising there is no URLSegment, give it a reasonable default // If after sanitising there is no URLSegment, give it a reasonable default
if(!$this->URLSegment) $this->URLSegment = "page-$this->ID"; if(!$this->URLSegment) $this->URLSegment = "page-$this->ID";
} }
// Ensure that this object has a non-conflicting URLSegment value. // Ensure that this object has a non-conflicting URLSegment value.
$count = 2; $count = 2;
while(!$this->validURLSegment()) { while(!$this->validURLSegment()) {
@ -1575,15 +1575,15 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
$this->migrateVersion($this->Version); $this->migrateVersion($this->Version);
} }
} }
public function syncLinkTracking() { public function syncLinkTracking() {
$this->extend('augmentSyncLinkTracking'); $this->extend('augmentSyncLinkTracking');
} }
public function onAfterWrite() { public function onAfterWrite() {
// Need to flush cache to avoid outdated versionnumber references // Need to flush cache to avoid outdated versionnumber references
$this->flushCache(); $this->flushCache();
$linkedPages = $this->VirtualPages(); $linkedPages = $this->VirtualPages();
if($linkedPages) { if($linkedPages) {
// The only way after a write() call to determine if it was triggered by a writeWithoutVersion(), // The only way after a write() call to determine if it was triggered by a writeWithoutVersion(),
@ -1596,13 +1596,13 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
else $page->write(); else $page->write();
} }
} }
parent::onAfterWrite(); parent::onAfterWrite();
} }
public function onBeforeDelete() { public function onBeforeDelete() {
parent::onBeforeDelete(); parent::onBeforeDelete();
// If deleting this page, delete all its children. // If deleting this page, delete all its children.
if(SiteTree::config()->enforce_strict_hierarchy && $children = $this->AllChildren()) { if(SiteTree::config()->enforce_strict_hierarchy && $children = $this->AllChildren()) {
foreach($children as $child) { foreach($children as $child) {
@ -1610,18 +1610,18 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
} }
} }
} }
public function onAfterDelete() { public function onAfterDelete() {
// Need to flush cache to avoid outdated versionnumber references // Need to flush cache to avoid outdated versionnumber references
$this->flushCache(); $this->flushCache();
// Need to mark pages depending to this one as broken // Need to mark pages depending to this one as broken
$dependentPages = $this->DependentPages(); $dependentPages = $this->DependentPages();
if($dependentPages) foreach($dependentPages as $page) { if($dependentPages) foreach($dependentPages as $page) {
// $page->write() calls syncLinkTracking, which does all the hard work for us. // $page->write() calls syncLinkTracking, which does all the hard work for us.
$page->write(); $page->write();
} }
parent::onAfterDelete(); parent::onAfterDelete();
} }
@ -1629,23 +1629,23 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
parent::flushCache($persistent); parent::flushCache($persistent);
$this->_cache_statusFlags = null; $this->_cache_statusFlags = null;
} }
protected function validate() { protected function validate() {
$result = parent::validate(); $result = parent::validate();
// Allowed children validation // Allowed children validation
$parent = $this->getParent(); $parent = $this->getParent();
if($parent && $parent->exists()) { if($parent && $parent->exists()) {
// No need to check for subclasses or instanceof, as allowedChildren() already // No need to check for subclasses or instanceof, as allowedChildren() already
// deconstructs any inheritance trees already. // deconstructs any inheritance trees already.
$allowed = $parent->allowedChildren(); $allowed = $parent->allowedChildren();
$subject = ($this instanceof VirtualPage && $this->CopyContentFromID) ? $this->CopyContentFrom() : $this; $subject = ($this instanceof VirtualPage && $this->CopyContentFromID) ? $this->CopyContentFrom() : $this;
if(!in_array($subject->ClassName, $allowed)) { if(!in_array($subject->ClassName, $allowed)) {
$result->error( $result->error(
_t( _t(
'SiteTree.PageTypeNotAllowed', 'SiteTree.PageTypeNotAllowed',
'Page type "{type}" not allowed as child of this parent page', 'Page type "{type}" not allowed as child of this parent page',
array('type' => $subject->i18n_singular_name()) array('type' => $subject->i18n_singular_name())
), ),
'ALLOWED_CHILDREN' 'ALLOWED_CHILDREN'
@ -1657,17 +1657,17 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
if(!$this->stat('can_be_root') && !$this->ParentID) { if(!$this->stat('can_be_root') && !$this->ParentID) {
$result->error( $result->error(
_t( _t(
'SiteTree.PageTypNotAllowedOnRoot', 'SiteTree.PageTypNotAllowedOnRoot',
'Page type "{type}" is not allowed on the root level', 'Page type "{type}" is not allowed on the root level',
array('type' => $this->i18n_singular_name()) array('type' => $this->i18n_singular_name())
), ),
'CAN_BE_ROOT' 'CAN_BE_ROOT'
); );
} }
return $result; return $result;
} }
/** /**
* Returns true if this object has a URLSegment value that does not conflict with any other objects. This method * Returns true if this object has a URLSegment value that does not conflict with any other objects. This method
* checks for: * checks for:
@ -1683,11 +1683,11 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
if($controller instanceof Controller && $controller->hasAction($this->URLSegment)) return false; if($controller instanceof Controller && $controller->hasAction($this->URLSegment)) return false;
} }
} }
if(!self::config()->nested_urls || !$this->ParentID) { if(!self::config()->nested_urls || !$this->ParentID) {
if(class_exists($this->URLSegment) && is_subclass_of($this->URLSegment, 'RequestHandler')) return false; if(class_exists($this->URLSegment) && is_subclass_of($this->URLSegment, 'RequestHandler')) return false;
} }
// Filters by url, id, and parent // Filters by url, id, and parent
$filter = array('"SiteTree"."URLSegment"' => $this->URLSegment); $filter = array('"SiteTree"."URLSegment"' => $this->URLSegment);
if($this->ID) { if($this->ID) {
@ -1696,9 +1696,9 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
if(self::config()->nested_urls) { if(self::config()->nested_urls) {
$filter['"SiteTree"."ParentID"'] = $this->ParentID ? $this->ParentID : 0; $filter['"SiteTree"."ParentID"'] = $this->ParentID ? $this->ParentID : 0;
} }
$votes = array_filter( $votes = array_filter(
(array)$this->extend('augmentValidURLSegment'), (array)$this->extend('augmentValidURLSegment'),
function($v) {return !is_null($v);} function($v) {return !is_null($v);}
); );
if($votes) { if($votes) {
@ -1711,7 +1711,7 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
return !($existingPage); return !($existingPage);
} }
/** /**
* Generate a URL segment based on the title provided. * Generate a URL segment based on the title provided.
* *
@ -1719,23 +1719,23 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
* updateURLSegment(&$url, $title). $url will be passed by reference and should be modified. $title will contain * updateURLSegment(&$url, $title). $url will be passed by reference and should be modified. $title will contain
* the title that was originally used as the source of this generated URL. This lets extensions either start from * the title that was originally used as the source of this generated URL. This lets extensions either start from
* scratch, or incrementally modify the generated URL. * scratch, or incrementally modify the generated URL.
* *
* @param string $title Page title * @param string $title Page title
* @return string Generated url segment * @return string Generated url segment
*/ */
public function generateURLSegment($title){ public function generateURLSegment($title){
$filter = URLSegmentFilter::create(); $filter = URLSegmentFilter::create();
$t = $filter->filter($title); $t = $filter->filter($title);
// Fallback to generic page name if path is empty (= no valid, convertable characters) // Fallback to generic page name if path is empty (= no valid, convertable characters)
if(!$t || $t == '-' || $t == '-1') $t = "page-$this->ID"; if(!$t || $t == '-' || $t == '-1') $t = "page-$this->ID";
// Hook for extensions // Hook for extensions
$this->extend('updateURLSegment', $t, $title); $this->extend('updateURLSegment', $t, $title);
return $t; return $t;
} }
/** /**
* Gets the URL segment for the latest draft version of this page. * Gets the URL segment for the latest draft version of this page.
* *
@ -1747,7 +1747,7 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
)); ));
return ($stageRecord) ? $stageRecord->URLSegment : null; return ($stageRecord) ? $stageRecord->URLSegment : null;
} }
/** /**
* Gets the URL segment for the currently published version of this page. * Gets the URL segment for the currently published version of this page.
* *
@ -1759,7 +1759,7 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
)); ));
return ($liveRecord) ? $liveRecord->URLSegment : null; return ($liveRecord) ? $liveRecord->URLSegment : null;
} }
/** /**
* Rewrite a file URL on this page, after its been renamed. Triggers the onRenameLinkedAsset action on extensions. * Rewrite a file URL on this page, after its been renamed. Triggers the onRenameLinkedAsset action on extensions.
*/ */
@ -1783,7 +1783,7 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
if($numReplaced) { if($numReplaced) {
$query = sprintf('UPDATE "%s" SET "%s" = ? WHERE "ID" = ?', $table, $fieldName); $query = sprintf('UPDATE "%s" SET "%s" = ? WHERE "ID" = ?', $table, $fieldName);
DB::prepared_query($query, array($published[$fieldName], $this->ID)); DB::prepared_query($query, array($published[$fieldName], $this->ID));
// Tell static caching to update itself // Tell static caching to update itself
if($table == 'SiteTree_Live') { if($table == 'SiteTree_Live') {
$publishedClass = $origPublished['ClassName']; $publishedClass = $origPublished['ClassName'];
@ -1795,10 +1795,10 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
} }
} }
} }
/** /**
* Returns the pages that depend on this page. This includes virtual pages, pages that link to it, etc. * Returns the pages that depend on this page. This includes virtual pages, pages that link to it, etc.
* *
* @param bool $includeVirtuals Set to false to exlcude virtual pages. * @param bool $includeVirtuals Set to false to exlcude virtual pages.
* @return ArrayList * @return ArrayList
*/ */
@ -1807,7 +1807,7 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
$origDisableSubsiteFilter = Subsite::$disable_subsite_filter; $origDisableSubsiteFilter = Subsite::$disable_subsite_filter;
Subsite::disable_subsite_filter(true); Subsite::disable_subsite_filter(true);
} }
// Content links // Content links
$items = new ArrayList(); $items = new ArrayList();
@ -1820,7 +1820,7 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
} }
$items->merge($linkList); $items->merge($linkList);
} }
// Virtual pages // Virtual pages
if($includeVirtuals) { if($includeVirtuals) {
$virtuals = $this->VirtualPages(); $virtuals = $this->VirtualPages();
@ -1849,7 +1849,7 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
} }
if(class_exists('Subsite')) Subsite::disable_subsite_filter($origDisableSubsiteFilter); if(class_exists('Subsite')) Subsite::disable_subsite_filter($origDisableSubsiteFilter);
return $items; return $items;
} }
@ -1859,10 +1859,10 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
* @return DataList * @return DataList
*/ */
public function VirtualPages() { public function VirtualPages() {
// Ignore new records // Ignore new records
if(!$this->ID) return null; if(!$this->ID) return null;
// Check subsite virtual pages // Check subsite virtual pages
// @todo Refactor out subsite module specific code // @todo Refactor out subsite module specific code
if(class_exists('Subsite')) { if(class_exists('Subsite')) {
@ -1870,14 +1870,14 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
'"VirtualPage"."CopyContentFromID"' => $this->ID '"VirtualPage"."CopyContentFromID"' => $this->ID
)); ));
} }
// Check existing virtualpages // Check existing virtualpages
if(class_exists('VirtualPage')) { if(class_exists('VirtualPage')) {
return VirtualPage::get()->where(array( return VirtualPage::get()->where(array(
'"VirtualPage"."CopyContentFromID"' => $this->ID '"VirtualPage"."CopyContentFromID"' => $this->ID
)); ));
} }
return null; return null;
} }
@ -1924,7 +1924,7 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
} }
$statusMessage[] = _t( $statusMessage[] = _t(
'SiteTree.APPEARSVIRTUALPAGES', 'SiteTree.APPEARSVIRTUALPAGES',
"This content also appears on the virtual pages in the {title} sections.", "This content also appears on the virtual pages in the {title} sections.",
array('title' => $parentList) array('title' => $parentList)
); );
@ -1937,7 +1937,7 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
$dependentNote = ''; $dependentNote = '';
$dependentTable = new LiteralField('DependentNote', '<p></p>'); $dependentTable = new LiteralField('DependentNote', '<p></p>');
// Create a table for showing pages linked to this one // Create a table for showing pages linked to this one
$dependentPages = $this->DependentPages(); $dependentPages = $this->DependentPages();
$dependentPagesCount = $dependentPages->Count(); $dependentPagesCount = $dependentPages->Count();
@ -1948,7 +1948,7 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
'DependentLinkType' => _t('SiteTree.DependtPageColumnLinkType', 'Link type'), 'DependentLinkType' => _t('SiteTree.DependtPageColumnLinkType', 'Link type'),
); );
if(class_exists('Subsite')) $dependentColumns['Subsite.Title'] = singleton('Subsite')->i18n_singular_name(); if(class_exists('Subsite')) $dependentColumns['Subsite.Title'] = singleton('Subsite')->i18n_singular_name();
$dependentNote = new LiteralField('DependentNote', '<p>' . _t('SiteTree.DEPENDENT_NOTE', 'The following pages depend on this page. This includes virtual pages, redirector pages, and pages with content links.') . '</p>'); $dependentNote = new LiteralField('DependentNote', '<p>' . _t('SiteTree.DEPENDENT_NOTE', 'The following pages depend on this page. This includes virtual pages, redirector pages, and pages with content links.') . '</p>');
$dependentTable = GridField::create( $dependentTable = GridField::create(
'DependentPages', 'DependentPages',
@ -1974,12 +1974,12 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
} }
)); ));
} }
$baseLink = Controller::join_links ( $baseLink = Controller::join_links (
Director::absoluteBaseURL(), Director::absoluteBaseURL(),
(self::config()->nested_urls && $this->ParentID ? $this->Parent()->RelativeLink(true) : null) (self::config()->nested_urls && $this->ParentID ? $this->Parent()->RelativeLink(true) : null)
); );
$urlsegment = SiteTreeURLSegmentField::create("URLSegment", $this->fieldLabel('URLSegment')) $urlsegment = SiteTreeURLSegmentField::create("URLSegment", $this->fieldLabel('URLSegment'))
->setURLPrefix($baseLink) ->setURLPrefix($baseLink)
->setDefaultURL($this->generateURLSegment(_t( ->setDefaultURL($this->generateURLSegment(_t(
@ -1992,7 +1992,7 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
$helpText .= _t('SiteTreeURLSegmentField.HelpChars', ' Special characters are automatically converted or removed.'); $helpText .= _t('SiteTreeURLSegmentField.HelpChars', ' Special characters are automatically converted or removed.');
} }
$urlsegment->setHelpText($helpText); $urlsegment->setHelpText($helpText);
$fields = new FieldList( $fields = new FieldList(
$rootTab = new TabSet("Root", $rootTab = new TabSet("Root",
$tabMain = new Tab('Main', $tabMain = new Tab('Main',
@ -2014,12 +2014,12 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
) )
); );
$htmlField->addExtraClass('stacked'); $htmlField->addExtraClass('stacked');
// Help text for MetaData on page content editor // Help text for MetaData on page content editor
$metaFieldDesc $metaFieldDesc
->setRightTitle( ->setRightTitle(
_t( _t(
'SiteTree.METADESCHELP', 'SiteTree.METADESCHELP',
"Search engines use this content for displaying search results (although it will not influence their ranking)." "Search engines use this content for displaying search results (although it will not influence their ranking)."
) )
) )
@ -2027,7 +2027,7 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
$metaFieldExtra $metaFieldExtra
->setRightTitle( ->setRightTitle(
_t( _t(
'SiteTree.METAEXTRAHELP', 'SiteTree.METAEXTRAHELP',
"HTML tags for additional meta information. For example &lt;meta name=\"customName\" content=\"your custom content here\" /&gt;" "HTML tags for additional meta information. For example &lt;meta name=\"customName\" content=\"your custom content here\" /&gt;"
) )
) )
@ -2036,7 +2036,7 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
// Conditional dependent pages tab // Conditional dependent pages tab
if($dependentPagesCount) $tabDependent->setTitle(_t('SiteTree.TABDEPENDENT', "Dependent pages") . " ($dependentPagesCount)"); if($dependentPagesCount) $tabDependent->setTitle(_t('SiteTree.TABDEPENDENT', "Dependent pages") . " ($dependentPagesCount)");
else $fields->removeFieldFromTab('Root', 'Dependent'); else $fields->removeFieldFromTab('Root', 'Dependent');
$tabMain->setTitle(_t('SiteTree.TABCONTENT', "Main Content")); $tabMain->setTitle(_t('SiteTree.TABCONTENT', "Main Content"));
if($this->ObsoleteClassName) { if($this->ObsoleteClassName) {
@ -2054,8 +2054,8 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
} }
if(file_exists(BASE_PATH . '/install.php')) { if(file_exists(BASE_PATH . '/install.php')) {
$fields->addFieldToTab("Root.Main", new LiteralField("InstallWarningHeader", $fields->addFieldToTab("Root.Main", new LiteralField("InstallWarningHeader",
"<p class=\"message warning\">" . _t("SiteTree.REMOVE_INSTALL_WARNING", "<p class=\"message warning\">" . _t("SiteTree.REMOVE_INSTALL_WARNING",
"Warning: You should remove install.php from this SilverStripe install for security reasons.") "Warning: You should remove install.php from this SilverStripe install for security reasons.")
. "</p>"), "Title"); . "</p>"), "Title");
} }
@ -2065,19 +2065,19 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
'/^Root\.Content\.Main$/' => 'Root.Main', '/^Root\.Content\.Main$/' => 'Root.Main',
'/^Root\.Content\.([^.]+)$/' => 'Root.\\1', '/^Root\.Content\.([^.]+)$/' => 'Root.\\1',
)); ));
if(self::$runCMSFieldsExtensions) { if(self::$runCMSFieldsExtensions) {
$this->extend('updateCMSFields', $fields); $this->extend('updateCMSFields', $fields);
} }
return $fields; return $fields;
} }
/** /**
* Returns fields related to configuration aspects on this record, e.g. access control. See {@link getCMSFields()} * Returns fields related to configuration aspects on this record, e.g. access control. See {@link getCMSFields()}
* for content-related fields. * for content-related fields.
* *
* @return FieldList * @return FieldList
*/ */
public function getSettingsFields() { public function getSettingsFields() {
@ -2087,13 +2087,13 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
$groupsMap[$group->ID] = $group->getBreadcrumbs(' > '); $groupsMap[$group->ID] = $group->getBreadcrumbs(' > ');
} }
asort($groupsMap); asort($groupsMap);
$fields = new FieldList( $fields = new FieldList(
$rootTab = new TabSet("Root", $rootTab = new TabSet("Root",
$tabBehaviour = new Tab('Settings', $tabBehaviour = new Tab('Settings',
new DropdownField( new DropdownField(
"ClassName", "ClassName",
$this->fieldLabel('ClassName'), $this->fieldLabel('ClassName'),
$this->getClassDropdown() $this->getClassDropdown()
), ),
$parentTypeSelector = new CompositeField( $parentTypeSelector = new CompositeField(
@ -2108,41 +2108,41 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
new CheckboxField("ShowInSearch", $this->fieldLabel('ShowInSearch')) new CheckboxField("ShowInSearch", $this->fieldLabel('ShowInSearch'))
), ),
$viewersOptionsField = new OptionsetField( $viewersOptionsField = new OptionsetField(
"CanViewType", "CanViewType",
_t('SiteTree.ACCESSHEADER', "Who can view this page?") _t('SiteTree.ACCESSHEADER', "Who can view this page?")
), ),
$viewerGroupsField = ListboxField::create("ViewerGroups", _t('SiteTree.VIEWERGROUPS', "Viewer Groups")) $viewerGroupsField = ListboxField::create("ViewerGroups", _t('SiteTree.VIEWERGROUPS', "Viewer Groups"))
->setMultiple(true) ->setMultiple(true)
->setSource($groupsMap) ->setSource($groupsMap)
->setAttribute( ->setAttribute(
'data-placeholder', 'data-placeholder',
_t('SiteTree.GroupPlaceholder', 'Click to select group') _t('SiteTree.GroupPlaceholder', 'Click to select group')
), ),
$editorsOptionsField = new OptionsetField( $editorsOptionsField = new OptionsetField(
"CanEditType", "CanEditType",
_t('SiteTree.EDITHEADER', "Who can edit this page?") _t('SiteTree.EDITHEADER', "Who can edit this page?")
), ),
$editorGroupsField = ListboxField::create("EditorGroups", _t('SiteTree.EDITORGROUPS', "Editor Groups")) $editorGroupsField = ListboxField::create("EditorGroups", _t('SiteTree.EDITORGROUPS', "Editor Groups"))
->setMultiple(true) ->setMultiple(true)
->setSource($groupsMap) ->setSource($groupsMap)
->setAttribute( ->setAttribute(
'data-placeholder', 'data-placeholder',
_t('SiteTree.GroupPlaceholder', 'Click to select group') _t('SiteTree.GroupPlaceholder', 'Click to select group')
) )
) )
) )
); );
$visibility->setTitle($this->fieldLabel('Visibility')); $visibility->setTitle($this->fieldLabel('Visibility'));
// This filter ensures that the ParentID dropdown selection does not show this node, // This filter ensures that the ParentID dropdown selection does not show this node,
// or its descendents, as this causes vanishing bugs // or its descendents, as this causes vanishing bugs
$parentIDField->setFilterFunction(create_function('$node', "return \$node->ID != {$this->ID};")); $parentIDField->setFilterFunction(create_function('$node', "return \$node->ID != {$this->ID};"));
$parentTypeSelector->addExtraClass('parentTypeSelector'); $parentTypeSelector->addExtraClass('parentTypeSelector');
$tabBehaviour->setTitle(_t('SiteTree.TABBEHAVIOUR', "Behavior")); $tabBehaviour->setTitle(_t('SiteTree.TABBEHAVIOUR', "Behavior"));
// Make page location fields read-only if the user doesn't have the appropriate permission // Make page location fields read-only if the user doesn't have the appropriate permission
if(!Permission::check("SITETREE_REORGANISE")) { if(!Permission::check("SITETREE_REORGANISE")) {
$fields->makeFieldReadonly('ParentType'); $fields->makeFieldReadonly('ParentType');
@ -2152,14 +2152,14 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
$fields->makeFieldReadonly('ParentID'); $fields->makeFieldReadonly('ParentID');
} }
} }
$viewersOptionsSource = array(); $viewersOptionsSource = array();
$viewersOptionsSource["Inherit"] = _t('SiteTree.INHERIT', "Inherit from parent page"); $viewersOptionsSource["Inherit"] = _t('SiteTree.INHERIT', "Inherit from parent page");
$viewersOptionsSource["Anyone"] = _t('SiteTree.ACCESSANYONE', "Anyone"); $viewersOptionsSource["Anyone"] = _t('SiteTree.ACCESSANYONE', "Anyone");
$viewersOptionsSource["LoggedInUsers"] = _t('SiteTree.ACCESSLOGGEDIN', "Logged-in users"); $viewersOptionsSource["LoggedInUsers"] = _t('SiteTree.ACCESSLOGGEDIN', "Logged-in users");
$viewersOptionsSource["OnlyTheseUsers"] = _t('SiteTree.ACCESSONLYTHESE', "Only these people (choose from list)"); $viewersOptionsSource["OnlyTheseUsers"] = _t('SiteTree.ACCESSONLYTHESE', "Only these people (choose from list)");
$viewersOptionsField->setSource($viewersOptionsSource); $viewersOptionsField->setSource($viewersOptionsSource);
$editorsOptionsSource = array(); $editorsOptionsSource = array();
$editorsOptionsSource["Inherit"] = _t('SiteTree.INHERIT', "Inherit from parent page"); $editorsOptionsSource["Inherit"] = _t('SiteTree.INHERIT', "Inherit from parent page");
$editorsOptionsSource["LoggedInUsers"] = _t('SiteTree.EDITANYONE', "Anyone who can log-in to the CMS"); $editorsOptionsSource["LoggedInUsers"] = _t('SiteTree.EDITANYONE', "Anyone who can log-in to the CMS");
@ -2173,7 +2173,7 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
} else { } else {
$fields->removeByName('ViewerGroups'); $fields->removeByName('ViewerGroups');
} }
$fields->makeFieldReadonly($editorsOptionsField); $fields->makeFieldReadonly($editorsOptionsField);
if($this->CanEditType == 'OnlyTheseUsers') { if($this->CanEditType == 'OnlyTheseUsers') {
$fields->makeFieldReadonly($editorGroupsField); $fields->makeFieldReadonly($editorGroupsField);
@ -2181,14 +2181,14 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
$fields->removeByName('EditorGroups'); $fields->removeByName('EditorGroups');
} }
} }
if(self::$runCMSFieldsExtensions) { if(self::$runCMSFieldsExtensions) {
$this->extend('updateSettingsFields', $fields); $this->extend('updateSettingsFields', $fields);
} }
return $fields; return $fields;
} }
/** /**
* @param bool $includerelations A boolean value to indicate if the labels returned should include relation fields * @param bool $includerelations A boolean value to indicate if the labels returned should include relation fields
* @return array * @return array
@ -2218,7 +2218,7 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
$labels['LinkChangeNote'] = _t ( $labels['LinkChangeNote'] = _t (
'SiteTree.LINKCHANGENOTE', 'Changing this page\'s link will also affect the links of all child pages.' 'SiteTree.LINKCHANGENOTE', 'Changing this page\'s link will also affect the links of all child pages.'
); );
if($includerelations){ if($includerelations){
$labels['Parent'] = _t('SiteTree.has_one_Parent', 'Parent Page', 'The parent page in the site hierarchy'); $labels['Parent'] = _t('SiteTree.has_one_Parent', 'Parent Page', 'The parent page in the site hierarchy');
$labels['LinkTracking'] = _t('SiteTree.many_many_LinkTracking', 'Link Tracking'); $labels['LinkTracking'] = _t('SiteTree.many_many_LinkTracking', 'Link Tracking');
@ -2253,7 +2253,7 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
// Minor options are hidden behind a drop-up and appear as links (although they are still FormActions). // Minor options are hidden behind a drop-up and appear as links (although they are still FormActions).
$rootTabSet = new TabSet('ActionMenus'); $rootTabSet = new TabSet('ActionMenus');
$moreOptions = new Tab( $moreOptions = new Tab(
'MoreOptions', 'MoreOptions',
_t('SiteTree.MoreOptions', 'More options', 'Expands a view for more buttons') _t('SiteTree.MoreOptions', 'More options', 'Expands a view for more buttons')
); );
$rootTabSet->push($moreOptions); $rootTabSet->push($moreOptions);
@ -2323,7 +2323,7 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
} else { } else {
// Determine if we should force a restore to root (where once it was a subpage) // Determine if we should force a restore to root (where once it was a subpage)
$restoreToRoot = $this->isParentArchived(); $restoreToRoot = $this->isParentArchived();
// "restore" // "restore"
$title = $restoreToRoot $title = $restoreToRoot
? _t('CMSMain.RESTORE_TO_ROOT','Restore draft at top level') ? _t('CMSMain.RESTORE_TO_ROOT','Restore draft at top level')
@ -2362,7 +2362,7 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
->addExtraClass('delete ss-ui-action-destructive') ->addExtraClass('delete ss-ui-action-destructive')
); );
} }
// "save", supports an alternate state that is still clickable, but notifies the user that the action is not needed. // "save", supports an alternate state that is still clickable, but notifies the user that the action is not needed.
$majorActions->push( $majorActions->push(
FormAction::create('save', _t('SiteTree.BUTTONSAVED', 'Saved')) FormAction::create('save', _t('SiteTree.BUTTONSAVED', 'Saved'))
@ -2387,25 +2387,25 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
$publish->addExtraClass('ss-ui-alternate'); $publish->addExtraClass('ss-ui-alternate');
} }
} }
$actions = new FieldList(array($majorActions, $rootTabSet)); $actions = new FieldList(array($majorActions, $rootTabSet));
// Hook for extensions to add/remove actions. // Hook for extensions to add/remove actions.
$this->extend('updateCMSActions', $actions); $this->extend('updateCMSActions', $actions);
return $actions; return $actions;
} }
/** /**
* Publish this page. * Publish this page.
* *
* @uses SiteTreeExtension->onBeforePublish() * @uses SiteTreeExtension->onBeforePublish()
* @uses SiteTreeExtension->onAfterPublish() * @uses SiteTreeExtension->onAfterPublish()
* @return bool True if published * @return bool True if published
*/ */
public function doPublish() { public function doPublish() {
if (!$this->canPublish()) return false; if (!$this->canPublish()) return false;
$original = Versioned::get_one_by_stage("SiteTree", "Live", array( $original = Versioned::get_one_by_stage("SiteTree", "Live", array(
'"SiteTree"."ID"' => $this->ID '"SiteTree"."ID"' => $this->ID
)); ));
@ -2422,7 +2422,7 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
WHERE EXISTS (SELECT "SiteTree"."Sort" FROM "SiteTree" WHERE "SiteTree_Live"."ID" = "SiteTree"."ID") AND "ParentID" = ?', WHERE EXISTS (SELECT "SiteTree"."Sort" FROM "SiteTree" WHERE "SiteTree_Live"."ID" = "SiteTree"."ID") AND "ParentID" = ?',
array($this->ParentID) array($this->ParentID)
); );
// Publish any virtual pages that might need publishing // Publish any virtual pages that might need publishing
$linkedPages = $this->VirtualPages(); $linkedPages = $this->VirtualPages();
if($linkedPages) foreach($linkedPages as $page) { if($linkedPages) foreach($linkedPages as $page) {
@ -2430,7 +2430,7 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
$page->write(); $page->write();
if($page->getExistsOnLive()) $page->doPublish(); if($page->getExistsOnLive()) $page->doPublish();
} }
// Need to update pages linking to this one as no longer broken, on the live site // Need to update pages linking to this one as no longer broken, on the live site
$origMode = Versioned::get_reading_mode(); $origMode = Versioned::get_reading_mode();
Versioned::reading_stage('Live'); Versioned::reading_stage('Live');
@ -2439,25 +2439,25 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
$page->write(); $page->write();
} }
Versioned::set_reading_mode($origMode); Versioned::set_reading_mode($origMode);
// Handle activities undertaken by extensions // Handle activities undertaken by extensions
$this->invokeWithExtensions('onAfterPublish', $original); $this->invokeWithExtensions('onAfterPublish', $original);
return true; return true;
} }
/** /**
* Unpublish this page - remove it from the live site * Unpublish this page - remove it from the live site
* *
* @uses SiteTreeExtension->onBeforeUnpublish() * @uses SiteTreeExtension->onBeforeUnpublish()
* @uses SiteTreeExtension->onAfterUnpublish() * @uses SiteTreeExtension->onAfterUnpublish()
*/ */
public function doUnpublish() { public function doUnpublish() {
if(!$this->canDeleteFromLive()) return false; if(!$this->canDeleteFromLive()) return false;
if(!$this->ID) return false; if(!$this->ID) return false;
$this->invokeWithExtensions('onBeforeUnpublish', $this); $this->invokeWithExtensions('onBeforeUnpublish', $this);
$origStage = Versioned::current_stage(); $origStage = Versioned::current_stage();
Versioned::reading_stage('Live'); Versioned::reading_stage('Live');
@ -2490,7 +2490,7 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
return true; return true;
} }
/** /**
* Revert the draft changes: replace the draft content with the content on live * Revert the draft changes: replace the draft content with the content on live
*/ */
@ -2508,7 +2508,7 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
// $page->write() calls syncLinkTracking, which does all the hard work for us. // $page->write() calls syncLinkTracking, which does all the hard work for us.
$page->write(); $page->write();
} }
$this->invokeWithExtensions('onAfterRevertToLive', $this); $this->invokeWithExtensions('onAfterRevertToLive', $this);
return true; return true;
} }
@ -2527,7 +2527,7 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
} }
return false; return false;
} }
/** /**
* Restore the content in the active copy of this SiteTree page to the stage site. * Restore the content in the active copy of this SiteTree page to the stage site.
* *
@ -2538,7 +2538,7 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
if($this->isParentArchived()) { if($this->isParentArchived()) {
$this->ParentID = 0; $this->ParentID = 0;
} }
// if no record can be found on draft stage (meaning it has been "deleted from draft" before), // if no record can be found on draft stage (meaning it has been "deleted from draft" before),
// create an empty record // create an empty record
if(!DB::prepared_query("SELECT \"ID\" FROM \"SiteTree\" WHERE \"ID\" = ?", array($this->ID))->value()) { if(!DB::prepared_query("SELECT \"ID\" FROM \"SiteTree\" WHERE \"ID\" = ?", array($this->ID))->value()) {
@ -2547,12 +2547,12 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
DB::prepared_query("INSERT INTO \"SiteTree\" (\"ID\") VALUES (?)", array($this->ID)); DB::prepared_query("INSERT INTO \"SiteTree\" (\"ID\") VALUES (?)", array($this->ID));
if(method_exists($conn, 'allowPrimaryKeyEditing')) $conn->allowPrimaryKeyEditing('SiteTree', false); if(method_exists($conn, 'allowPrimaryKeyEditing')) $conn->allowPrimaryKeyEditing('SiteTree', false);
} }
$oldStage = Versioned::current_stage(); $oldStage = Versioned::current_stage();
Versioned::reading_stage('Stage'); Versioned::reading_stage('Stage');
$this->forceChange(); $this->forceChange();
$this->write(); $this->write();
$result = DataObject::get_by_id($this->class, $this->ID); $result = DataObject::get_by_id($this->class, $this->ID);
// Need to update pages linking to this one as no longer broken // Need to update pages linking to this one as no longer broken
@ -2560,9 +2560,9 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
// $page->write() calls syncLinkTracking, which does all the hard work for us. // $page->write() calls syncLinkTracking, which does all the hard work for us.
$page->write(); $page->write();
} }
Versioned::reading_stage($oldStage); Versioned::reading_stage($oldStage);
return $result; return $result;
} }
@ -2590,7 +2590,7 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
if(!$member) { if(!$member) {
$member = Member::currentUser(); $member = Member::currentUser();
} }
// Standard mechanism for accepting permission changes from extensions // Standard mechanism for accepting permission changes from extensions
$extended = $this->extendedCan('canArchive', $member); $extended = $this->extendedCan('canArchive', $member);
if($extended !== null) { if($extended !== null) {
@ -2601,12 +2601,12 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
if(!$this->canDelete($member)) { if(!$this->canDelete($member)) {
return false; return false;
} }
// If published, check if we can delete from live // If published, check if we can delete from live
if($this->ExistsOnLive && !$this->canDeleteFromLive($member)) { if($this->ExistsOnLive && !$this->canDeleteFromLive($member)) {
return false; return false;
} }
return true; return true;
} }
@ -2660,7 +2660,7 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
$classes = self::page_type_classes(); $classes = self::page_type_classes();
$currentClass = null; $currentClass = null;
$result = array(); $result = array();
$result = array(); $result = array();
foreach($classes as $class) { foreach($classes as $class) {
$instance = singleton($class); $instance = singleton($class);
@ -2669,7 +2669,7 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
if ($this->ClassName != $instance->ClassName) { if ($this->ClassName != $instance->ClassName) {
if((($instance instanceof HiddenClass) || !$instance->canCreate())) continue; if((($instance instanceof HiddenClass) || !$instance->canCreate())) continue;
} }
if($perms = $instance->stat('need_permission')) { if($perms = $instance->stat('need_permission')) {
if(!$this->can($perms)) continue; if(!$this->can($perms)) continue;
} }
@ -2686,7 +2686,7 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
$result[$class] = $result[$class] . " ({$class})"; $result[$class] = $result[$class] . " ({$class})";
} }
} }
// sort alphabetically, and put current on top // sort alphabetically, and put current on top
asort($result); asort($result);
if($currentClass) { if($currentClass) {
@ -2696,7 +2696,7 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
$result[$currentClass] = $currentPageTypeName; $result[$currentClass] = $currentPageTypeName;
$result = array_reverse($result); $result = array_reverse($result);
} }
return $result; return $result;
} }
@ -2722,7 +2722,7 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
} }
} }
} }
return $allowedChildren; return $allowedChildren;
} }
@ -2777,17 +2777,17 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
$this->setField("MenuTitle", $value); $this->setField("MenuTitle", $value);
} }
} }
/** /**
* A flag provides the user with additional data about the current page status, for example a "removed from draft" * A flag provides the user with additional data about the current page status, for example a "removed from draft"
* status. Each page can have more than one status flag. Returns a map of a unique key to a (localized) title for * status. Each page can have more than one status flag. Returns a map of a unique key to a (localized) title for
* the flag. The unique key can be reused as a CSS class. Use the 'updateStatusFlags' extension point to customize * the flag. The unique key can be reused as a CSS class. Use the 'updateStatusFlags' extension point to customize
* the flags. * the flags.
* *
* Example (simple): * Example (simple):
* "deletedonlive" => "Deleted" * "deletedonlive" => "Deleted"
* *
* Example (with optional title attribute): * Example (with optional title attribute):
* "deletedonlive" => array('text' => "Deleted", 'title' => 'This page has been deleted') * "deletedonlive" => array('text' => "Deleted", 'title' => 'This page has been deleted')
* *
* @param bool $cached Whether to serve the fields from cache; false regenerate them * @param bool $cached Whether to serve the fields from cache; false regenerate them
@ -2824,7 +2824,7 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
$this->_cache_statusFlags = $flags; $this->_cache_statusFlags = $flags;
} }
return $this->_cache_statusFlags; return $this->_cache_statusFlags;
} }
@ -2861,7 +2861,7 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
Convert::raw2xml($data['text']) Convert::raw2xml($data['text'])
); );
} }
return $treeTitle; return $treeTitle;
} }
@ -2921,7 +2921,7 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
if(!$this->ShowInMenus) { if(!$this->ShowInMenus) {
$classes .= " notinmenu"; $classes .= " notinmenu";
} }
//TODO: Add integration //TODO: Add integration
/* /*
if($this->hasExtension('Translatable') && $controller->Locale != Translatable::default_locale() && !$this->isTranslation()) if($this->hasExtension('Translatable') && $controller->Locale != Translatable::default_locale() && !$this->isTranslation())
@ -2931,23 +2931,23 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
return $classes; return $classes;
} }
/** /**
* Compares current draft with live version, and returns true if no draft version of this page exists but the page * Compares current draft with live version, and returns true if no draft version of this page exists but the page
* is still published (eg, after triggering "Delete from draft site" in the CMS). * is still published (eg, after triggering "Delete from draft site" in the CMS).
* *
* @return bool * @return bool
*/ */
public function getIsDeletedFromStage() { public function getIsDeletedFromStage() {
if(!$this->ID) return true; if(!$this->ID) return true;
if($this->isNew()) return false; if($this->isNew()) return false;
$stageVersion = Versioned::get_versionnumber_by_stage('SiteTree', 'Stage', $this->ID); $stageVersion = Versioned::get_versionnumber_by_stage('SiteTree', 'Stage', $this->ID);
// Return true for both completely deleted pages and for pages just deleted from stage // Return true for both completely deleted pages and for pages just deleted from stage
return !($stageVersion); return !($stageVersion);
} }
/** /**
* Return true if this page exists on the live site * Return true if this page exists on the live site
* *
@ -2960,38 +2960,38 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
/** /**
* Compares current draft with live version, and returns true if these versions differ, meaning there have been * Compares current draft with live version, and returns true if these versions differ, meaning there have been
* unpublished changes to the draft site. * unpublished changes to the draft site.
* *
* @return bool * @return bool
*/ */
public function getIsModifiedOnStage() { public function getIsModifiedOnStage() {
// New unsaved pages could be never be published // New unsaved pages could be never be published
if($this->isNew()) return false; if($this->isNew()) return false;
$stageVersion = Versioned::get_versionnumber_by_stage('SiteTree', 'Stage', $this->ID); $stageVersion = Versioned::get_versionnumber_by_stage('SiteTree', 'Stage', $this->ID);
$liveVersion = Versioned::get_versionnumber_by_stage('SiteTree', 'Live', $this->ID); $liveVersion = Versioned::get_versionnumber_by_stage('SiteTree', 'Live', $this->ID);
$isModified = ($stageVersion && $stageVersion != $liveVersion); $isModified = ($stageVersion && $stageVersion != $liveVersion);
$this->extend('getIsModifiedOnStage', $isModified); $this->extend('getIsModifiedOnStage', $isModified);
return $isModified; return $isModified;
} }
/** /**
* Compares current draft with live version, and returns true if no live version exists, meaning the page was never * Compares current draft with live version, and returns true if no live version exists, meaning the page was never
* published. * published.
* *
* @return bool * @return bool
*/ */
public function getIsAddedToStage() { public function getIsAddedToStage() {
// New unsaved pages could be never be published // New unsaved pages could be never be published
if($this->isNew()) return false; if($this->isNew()) return false;
$stageVersion = Versioned::get_versionnumber_by_stage('SiteTree', 'Stage', $this->ID); $stageVersion = Versioned::get_versionnumber_by_stage('SiteTree', 'Stage', $this->ID);
$liveVersion = Versioned::get_versionnumber_by_stage('SiteTree', 'Live', $this->ID); $liveVersion = Versioned::get_versionnumber_by_stage('SiteTree', 'Live', $this->ID);
return ($stageVersion && !$liveVersion); return ($stageVersion && !$liveVersion);
} }
/** /**
* Stops extendCMSFields() being called on getCMSFields(). This is useful when you need access to fields added by * Stops extendCMSFields() being called on getCMSFields(). This is useful when you need access to fields added by
* subclasses of SiteTree in a extension. Call before calling parent::getCMSFields(), and reenable afterwards. * subclasses of SiteTree in a extension. Call before calling parent::getCMSFields(), and reenable afterwards.
@ -2999,7 +2999,7 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
static public function disableCMSFieldsExtensions() { static public function disableCMSFieldsExtensions() {
self::$runCMSFieldsExtensions = false; self::$runCMSFieldsExtensions = false;
} }
/** /**
* Reenables extendCMSFields() being called on getCMSFields() after it has been disabled by * Reenables extendCMSFields() being called on getCMSFields() after it has been disabled by
* disableCMSFieldsExtensions(). * disableCMSFieldsExtensions().
@ -3042,10 +3042,10 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
) )
); );
} }
/** /**
* Return the translated Singular name. * Return the translated Singular name.
* *
* @return string * @return string
*/ */
public function i18n_singular_name() { public function i18n_singular_name() {
@ -3053,7 +3053,7 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
$class = ($this->class == 'Page') ? 'SiteTree' : $this->class; $class = ($this->class == 'Page') ? 'SiteTree' : $this->class;
return _t($class.'.SINGULARNAME', $this->singular_name()); return _t($class.'.SINGULARNAME', $this->singular_name());
} }
/** /**
* Overloaded to also provide entities for 'Page' class which is usually located in custom code, hence textcollector * Overloaded to also provide entities for 'Page' class which is usually located in custom code, hence textcollector
* picks it up for the wrong folder. * picks it up for the wrong folder.
@ -3062,9 +3062,9 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
*/ */
public function provideI18nEntities() { public function provideI18nEntities() {
$entities = parent::provideI18nEntities(); $entities = parent::provideI18nEntities();
if(isset($entities['Page.SINGULARNAME'])) $entities['Page.SINGULARNAME'][3] = CMS_DIR; if(isset($entities['Page.SINGULARNAME'])) $entities['Page.SINGULARNAME'][3] = CMS_DIR;
if(isset($entities['Page.PLURALNAME'])) $entities['Page.PLURALNAME'][3] = CMS_DIR; if(isset($entities['Page.PLURALNAME'])) $entities['Page.PLURALNAME'][3] = CMS_DIR;
$entities[$this->class . '.DESCRIPTION'] = array( $entities[$this->class . '.DESCRIPTION'] = array(
$this->stat('description'), $this->stat('description'),
@ -3092,7 +3092,7 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
public static function reset() { public static function reset() {
self::$cache_permissions = array(); self::$cache_permissions = array();
} }
static public function on_db_reset() { static public function on_db_reset() {
self::$cache_permissions = array(); self::$cache_permissions = array();
} }

View File

@ -4,13 +4,13 @@
* @subpackage tests * @subpackage tests
*/ */
class SiteTreeTest extends SapphireTest { class SiteTreeTest extends SapphireTest {
protected static $fixture_file = 'SiteTreeTest.yml'; protected static $fixture_file = 'SiteTreeTest.yml';
protected $illegalExtensions = array( protected $illegalExtensions = array(
'SiteTree' => array('SiteTreeSubsites', 'Translatable') 'SiteTree' => array('SiteTreeSubsites', 'Translatable')
); );
protected $extraDataObjects = array( protected $extraDataObjects = array(
'SiteTreeTest_ClassA', 'SiteTreeTest_ClassA',
'SiteTreeTest_ClassB', 'SiteTreeTest_ClassB',
@ -27,7 +27,7 @@ class SiteTreeTest extends SapphireTest {
public function logOut() { public function logOut() {
if($member = Member::currentUser()) $member->logOut(); if($member = Member::currentUser()) $member->logOut();
} }
public function testCreateDefaultpages() { public function testCreateDefaultpages() {
$remove = SiteTree::get(); $remove = SiteTree::get();
if($remove) foreach($remove as $page) $page->delete(); if($remove) foreach($remove as $page) $page->delete();
@ -413,7 +413,7 @@ class SiteTreeTest extends SapphireTest {
Versioned::reading_stage('Stage'); Versioned::reading_stage('Stage');
Config::inst()->update('SiteTree', 'enforce_strict_hierarchy', true); Config::inst()->update('SiteTree', 'enforce_strict_hierarchy', true);
} }
public function testDeleteFromLiveOperatesRecursivelyStrict() { public function testDeleteFromLiveOperatesRecursivelyStrict() {
$this->logInWithPermission('ADMIN'); $this->logInWithPermission('ADMIN');
@ -512,8 +512,12 @@ class SiteTreeTest extends SapphireTest {
$this->assertTrue(singleton('SiteTreeTest_ClassA')->canCreate(null)); $this->assertTrue(singleton('SiteTreeTest_ClassA')->canCreate(null));
$this->assertFalse(singleton('SiteTreeTest_ClassA')->canCreate(null, array('Parent' => $parentB))); $this->assertFalse(singleton('SiteTreeTest_ClassA')->canCreate(null, array('Parent' => $parentB)));
$this->assertTrue(singleton('SiteTreeTest_ClassC')->canCreate(null, array('Parent' => $parentB))); $this->assertTrue(singleton('SiteTreeTest_ClassC')->canCreate(null, array('Parent' => $parentB)));
// Test creation underneath a parent which doesn't exist in the database. This should
// fall back to checking whether the user can create pages at the root of the site
$this->assertTrue(singleton('SiteTree')->canCreate(null, array('Parent' => singleton('SiteTree'))));
} }
public function testEditPermissionsOnDraftVsLive() { public function testEditPermissionsOnDraftVsLive() {
// Create an inherit-permission page // Create an inherit-permission page
$page = new Page(); $page = new Page();
@ -709,7 +713,7 @@ class SiteTreeTest extends SapphireTest {
$this->assertEquals($sitetree->URLSegment, 'new-page', $this->assertEquals($sitetree->URLSegment, 'new-page',
'Sets based on default title on first save' 'Sets based on default title on first save'
); );
$sitetree->Title = 'Changed'; $sitetree->Title = 'Changed';
$sitetree->write(); $sitetree->write();
$this->assertEquals($sitetree->URLSegment, 'changed', $this->assertEquals($sitetree->URLSegment, 'changed',
@ -1066,7 +1070,7 @@ class SiteTreeTest extends SapphireTest {
$this->assertEquals($breadcrumbs->first()->Title, "Breadcrumbs 4", "First item should be Breadrcumbs 4."); $this->assertEquals($breadcrumbs->first()->Title, "Breadcrumbs 4", "First item should be Breadrcumbs 4.");
$this->assertEquals($breadcrumbs->last()->Title, "Breadcrumbs 5", "Breadcrumbs 5 should be last."); $this->assertEquals($breadcrumbs->last()->Title, "Breadcrumbs 5", "Breadcrumbs 5 should be last.");
} }
/** /**
* Tests SiteTree::MetaTags * Tests SiteTree::MetaTags
* Note that this test makes no assumption on the closing of tags (other than <title></title>) * Note that this test makes no assumption on the closing of tags (other than <title></title>)
@ -1184,11 +1188,11 @@ class SiteTreeTest_PageNode_Controller extends Page_Controller implements TestOn
class SiteTreeTest_Conflicted extends Page implements TestOnly { } class SiteTreeTest_Conflicted extends Page implements TestOnly { }
class SiteTreeTest_Conflicted_Controller extends Page_Controller implements TestOnly { class SiteTreeTest_Conflicted_Controller extends Page_Controller implements TestOnly {
private static $allowed_actions = array ( private static $allowed_actions = array (
'conflicted-action' 'conflicted-action'
); );
public function hasActionTemplate($template) { public function hasActionTemplate($template) {
if($template == 'conflicted-template') { if($template == 'conflicted-template') {
return true; return true;
@ -1196,7 +1200,7 @@ class SiteTreeTest_Conflicted_Controller extends Page_Controller implements Test
return parent::hasActionTemplate($template); return parent::hasActionTemplate($template);
} }
} }
} }
class SiteTreeTest_NullHtmlCleaner extends HTMLCleaner { class SiteTreeTest_NullHtmlCleaner extends HTMLCleaner {
@ -1208,13 +1212,13 @@ class SiteTreeTest_NullHtmlCleaner extends HTMLCleaner {
class SiteTreeTest_ClassA extends Page implements TestOnly { class SiteTreeTest_ClassA extends Page implements TestOnly {
private static $need_permission = array('ADMIN', 'CMS_ACCESS_CMSMain'); private static $need_permission = array('ADMIN', 'CMS_ACCESS_CMSMain');
private static $allowed_children = array('SiteTreeTest_ClassB'); private static $allowed_children = array('SiteTreeTest_ClassB');
} }
class SiteTreeTest_ClassB extends Page implements TestOnly { class SiteTreeTest_ClassB extends Page implements TestOnly {
// Also allowed subclasses // Also allowed subclasses
private static $allowed_children = array('SiteTreeTest_ClassC'); private static $allowed_children = array('SiteTreeTest_ClassC');
} }
class SiteTreeTest_ClassC extends Page implements TestOnly { class SiteTreeTest_ClassC extends Page implements TestOnly {