BUG Fix SiteTree / SiteConfig permissions

This commit is contained in:
Damian Mooyman 2015-03-16 16:27:31 +13:00
parent a495385ee5
commit 64955e57d1
12 changed files with 712 additions and 347 deletions

View File

@ -46,6 +46,7 @@ class CMSMain extends LeftAndMain implements CurrentPageIdentifier, PermissionPr
'treeview', 'treeview',
'listview', 'listview',
'ListViewForm', 'ListViewForm',
'childfilter',
); );
public function init() { public function init() {
@ -379,55 +380,49 @@ class CMSMain extends LeftAndMain implements CurrentPageIdentifier, PermissionPr
$json = $cache->load($cacheKey); $json = $cache->load($cacheKey);
if(!$json) { if(!$json) {
$def['Root'] = array(); $def['Root'] = array();
$def['Root']['disallowedParents'] = array(); $def['Root']['disallowedChildren'] = array();
// Contains all possible classes to support UI controls listing them all,
// such as the "add page here" context menu.
$def['All'] = array();
// Identify disallows and set globals
foreach($classes as $class) { foreach($classes as $class) {
$obj = singleton($class); $obj = singleton($class);
if($obj instanceof HiddenClass) continue; if($obj instanceof HiddenClass) continue;
$allowedChildren = $obj->allowedChildren();
// SiteTree::allowedChildren() returns null rather than an empty array if SiteTree::allowed_chldren == 'none'
if($allowedChildren == null) $allowedChildren = array();
// Exclude SiteTree from possible Children
$possibleChildren = array_diff($allowedChildren, array("SiteTree"));
// Find i18n - names and build allowed children array // Name item
foreach($possibleChildren as $child) { $def['All'][$class] = array(
$instance = singleton($child); 'title' => $obj->i18n_singular_name()
);
if($instance instanceof HiddenClass) continue;
if(!array_key_exists($child, $cacheCanCreate) || !$cacheCanCreate[$child]) continue; // Check if can be created at the root
$needsPerm = $obj->stat('need_permission');
// skip this type if it is restricted if(
if($instance->stat('need_permission') && !$this->can(singleton($class)->stat('need_permission'))) continue; !$obj->stat('can_be_root')
|| (!array_key_exists($class, $cacheCanCreate) || !$cacheCanCreate[$class])
$title = $instance->i18n_singular_name(); || ($needsPerm && !$this->can($needsPerm))
) {
$def[$class]['allowedChildren'][] = array("ssclass" => $child, "ssname" => $title); $def['Root']['disallowedChildren'][] = $class;
} }
$allowedChildren = array_keys(array_diff($classes, $allowedChildren)); // Hint data specific to the class
if($allowedChildren) $def[$class]['disallowedChildren'] = $allowedChildren; $def[$class] = array();
$defaultChild = $obj->defaultChild(); $defaultChild = $obj->defaultChild();
if($defaultChild != 'Page' && $defaultChild != null) $def[$class]['defaultChild'] = $defaultChild; if($defaultChild !== 'Page' && $defaultChild !== null) {
$defaultParent = $obj->defaultParent(); $def[$class]['defaultChild'] = $defaultChild;
$parent = SiteTree::get_by_link($defaultParent); }
$id = $parent ? $parent->id : null;
if ($defaultParent != 1 && $defaultParent != null) $def[$class]['defaultParent'] = $defaultParent; $defaultParent = $obj->defaultParent();
if(isset($def[$class]['disallowedChildren'])) { if ($defaultParent !== 1 && $defaultParent !== null) {
foreach($def[$class]['disallowedChildren'] as $disallowedChild) { $def[$class]['defaultParent'] = $defaultParent;
$def[$disallowedChild]['disallowedParents'][] = $class;
}
} }
// Are any classes allowed to be parents of root?
$def['Root']['disallowedParents'][] = $class;
} }
$json = Convert::raw2xml(Convert::raw2json($def)); $this->extend('updateSiteTreeHints', $def);
$json = Convert::raw2json($def);
$cache->save($json, $cacheKey); $cache->save($json, $cacheKey);
} }
return $json; return $json;
@ -490,8 +485,6 @@ class CMSMain extends LeftAndMain implements CurrentPageIdentifier, PermissionPr
if($instance instanceof HiddenClass) continue; if($instance instanceof HiddenClass) continue;
if(!$instance->canCreate()) continue;
// skip this type if it is restricted // skip this type if it is restricted
if($instance->stat('need_permission') && !$this->can(singleton($class)->stat('need_permission'))) continue; if($instance->stat('need_permission') && !$this->can(singleton($class)->stat('need_permission'))) continue;
@ -674,6 +667,38 @@ class CMSMain extends LeftAndMain implements CurrentPageIdentifier, PermissionPr
public function listview($request) { public function listview($request) {
return $this->renderWith($this->getTemplatesWithSuffix('_ListView')); return $this->renderWith($this->getTemplatesWithSuffix('_ListView'));
} }
/**
* Callback to request the list of page types allowed under a given page instance.
* Provides a slower but more precise response over SiteTreeHints
*
* @param SS_HTTPRequest $request
* @return SS_HTTPResponse
*/
public function childfilter($request) {
// Check valid parent specified
$parentID = $request->requestVar('ParentID');
$parent = SiteTree::get()->byID($parentID);
if(!$parent || !$parent->exists()) return $this->httpError(404);
// Build hints specific to this class
// Identify disallows and set globals
$classes = SiteTree::page_type_classes();
$disallowedChildren = array();
foreach($classes as $class) {
$obj = singleton($class);
if($obj instanceof HiddenClass) continue;
if(!$obj->canCreate(null, array('Parent' => $parent))) {
$disallowedChildren[] = $class;
}
}
$this->extend('updateChildFilter', $disallowedChildren, $parentID);
$this->response->addHeader('Content-Type', 'application/json; charset=utf-8');
$this->response->setBody(Convert::raw2json($disallowedChildren));
return $this->response;
}
/** /**
* Returns the pages meet a certain criteria as {@see CMSSiteTreeFilter} or the subpages of a parent page * Returns the pages meet a certain criteria as {@see CMSSiteTreeFilter} or the subpages of a parent page

View File

@ -21,7 +21,7 @@ class CMSPageAddController extends CMSPageEditController {
$pageTypes = array(); $pageTypes = array();
foreach($this->PageTypes() as $type) { foreach($this->PageTypes() as $type) {
$html = sprintf('<span class="page-icon class-%s"></span><strong class="title">%s</strong><span class="description">%s</span>', $html = sprintf('<span class="page-icon class-%s"></span><strong class="title">%s</strong><span class="description">%s</span>',
$type->getField('Title'), $type->getField('ClassName'),
$type->getField('AddAction'), $type->getField('AddAction'),
$type->getField('Description') $type->getField('Description')
); );
@ -39,9 +39,6 @@ class CMSPageAddController extends CMSPageEditController {
$childTitle = _t('CMSPageAddController.ParentMode_child', 'Under another page'); $childTitle = _t('CMSPageAddController.ParentMode_child', 'Under another page');
$fields = new FieldList( $fields = new FieldList(
// new HiddenField("ParentID", false, ($this->parentRecord) ? $this->parentRecord->ID : null),
// TODO Should be part of the form attribute, but not possible in current form API
$hintsField = new LiteralField('Hints', sprintf('<span class="hints" data-hints="%s"></span>', $this->SiteTreeHints())),
new LiteralField('PageModeHeader', sprintf($numericLabelTmpl, 1, _t('CMSMain.ChoosePageParentMode', 'Choose where to create this page'))), new LiteralField('PageModeHeader', sprintf($numericLabelTmpl, 1, _t('CMSMain.ChoosePageParentMode', 'Choose where to create this page'))),
$parentModeField = new SelectionGroup( $parentModeField = new SelectionGroup(
@ -87,6 +84,8 @@ class CMSPageAddController extends CMSPageEditController {
$this->extend('updatePageOptions', $fields); $this->extend('updatePageOptions', $fields);
$form = new Form($this, "AddForm", $fields, $actions); $form = new Form($this, "AddForm", $fields, $actions);
$form->setAttribute('data-hints', $this->SiteTreeHints());
$form->setAttribute('data-childfilter', $this->Link('childfilter'));
$form->addExtraClass('cms-add-form stacked cms-content center cms-edit-form ' . $this->BaseCSSClasses()); $form->addExtraClass('cms-add-form stacked cms-content center cms-edit-form ' . $this->BaseCSSClasses());
$form->setTemplate($this->getTemplatesWithSuffix('_EditForm')); $form->setTemplate($this->getTemplatesWithSuffix('_EditForm'));
@ -113,12 +112,8 @@ class CMSPageAddController extends CMSPageEditController {
if(!$parentObj || !$parentObj->ID) $parentID = 0; if(!$parentObj || !$parentObj->ID) $parentID = 0;
if($parentObj) { if(!singleton($className)->canCreate(Member::currentUser(), array('Parent' => $parentObj))) {
if(!$parentObj->canAddChildren()) return Security::permissionFailure($this); return Security::permissionFailure($this);
if(!singleton($className)->canCreate()) return Security::permissionFailure($this);
} else {
if(!SiteConfig::current_site_config()->canCreateTopLevel())
return Security::permissionFailure($this);
} }
$record = $this->getNewItem("new-$className-$parentID".$suffix, false); $record = $this->getNewItem("new-$className-$parentID".$suffix, false);

View File

@ -21,8 +21,21 @@ class SiteConfig extends DataObject implements PermissionProvider {
"EditorGroups" => "Group", "EditorGroups" => "Group",
"CreateTopLevelGroups" => "Group" "CreateTopLevelGroups" => "Group"
); );
static $defaults = array(
"CanViewType" => "Anyone",
"CanEditType" => "LoggedInUsers",
"CanCreateTopLevelType" => "LoggedInUsers",
);
protected static $disabled_themes = array(); protected static $disabled_themes = array();
/**
* Default permission to check for 'LoggedInUsers' to create or edit pages
*
* @var array
*/
static $required_permission = array('CMS_ACCESS_CMSMain', 'CMS_ACCESS_LeftAndMain');
static public function disable_theme($theme) { static public function disable_theme($theme) {
self::$disabled_themes[$theme] = $theme; self::$disabled_themes[$theme] = $theme;
@ -192,48 +205,70 @@ class SiteConfig extends DataObject implements PermissionProvider {
* called if a page is set to Inherit, but there is nothing * called if a page is set to Inherit, but there is nothing
* to inherit from. * to inherit from.
* *
* @param mixed $member * @param Member $member
* @return boolean * @return boolean
*/ */
public function canView($member = null) { public function canViewPages($member = null) {
if(!$member) $member = Member::currentUserID(); if(!$member) $member = Member::currentUserID();
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 ($member && Permission::checkMember($member, "ADMIN")) return true; if ($member && Permission::checkMember($member, "ADMIN")) return true;
$extended = $this->extendedCan('canViewPages', $member);
if($extended !== null) return $extended;
if (!$this->CanViewType || $this->CanViewType == 'Anyone') return true; if (!$this->CanViewType || $this->CanViewType == 'Anyone') return true;
// check for any logged-in users // check for any logged-in users
if($this->CanViewType == 'LoggedInUsers' && $member) return true; if($this->CanViewType === 'LoggedInUsers' && $member) return true;
// check for specific groups // check for specific groups
if($this->CanViewType == 'OnlyTheseUsers' && $member && $member->inGroups($this->ViewerGroups())) return true; if($this->CanViewType === 'OnlyTheseUsers' && $member && $member->inGroups($this->ViewerGroups())) return true;
return false; return false;
} }
/** /**
* Can a user edit pages on this site? This method is only * Can a user edit pages on this site? This method is only
* called if a page is set to Inherit, but there is nothing * called if a page is set to Inherit, but there is nothing
* to inherit from. * to inherit from, or on new records without a parent.
* *
* @param mixed $member * @param Member $member
* @return boolean * @return boolean
*/ */
public function canEdit($member = null) { public function canEditPages($member = null) {
if(!$member) $member = Member::currentUserID(); if(!$member) $member = Member::currentUserID();
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 ($member && Permission::checkMember($member, "ADMIN")) return true; if ($member && Permission::checkMember($member, "ADMIN")) return true;
// check for any logged-in users $extended = $this->extendedCan('canEditPages', $member);
if(!$this->CanEditType || $this->CanEditType == 'LoggedInUsers' && $member) return true; if($extended !== null) return $extended;
// check for any logged-in users with CMS access
if( $this->CanEditType === 'LoggedInUsers'
&& Permission::checkMember($member, $this->config()->required_permission)
) {
return true;
}
// check for specific groups // check for specific groups
if($this->CanEditType == 'OnlyTheseUsers' && $member && $member->inGroups($this->EditorGroups())) return true; if($this->CanEditType === 'OnlyTheseUsers' && $member && $member->inGroups($this->EditorGroups())) {
return true;
}
return false; return false;
} }
public function canEdit($member = null) {
if(!$member) $member = Member::currentUserID();
if($member && is_numeric($member)) $member = DataObject::get_by_id('Member', $member);
$extended = $this->extendedCan('canEdit', $member);
if($extended !== null) return $extended;
return Permission::checkMember($member, "EDIT_SITECONFIG");
}
public function providePermissions() { public function providePermissions() {
return array( return array(
@ -249,25 +284,32 @@ class SiteConfig extends DataObject implements PermissionProvider {
/** /**
* Can a user create pages in the root of this site? * Can a user create pages in the root of this site?
* *
* @param mixed $member * @param Member $member
* @return boolean * @return boolean
*/ */
public function canCreateTopLevel($member = null) { public function canCreateTopLevel($member = null) {
if(!$member || !(is_a($member, 'Member')) || is_numeric($member)) { if(!$member) $member = Member::currentUserID();
$member = Member::currentUserID(); if($member && is_numeric($member)) $member = DataObject::get_by_id('Member', $member);
}
if (Permission::check('ADMIN')) return true;
if ($member && Permission::checkMember($member, "ADMIN")) return true; if ($member && Permission::checkMember($member, "ADMIN")) return true;
$extended = $this->extendedCan('canCreateTopLevel', $member);
if($extended !== null) return $extended;
// check for any logged-in users // check for any logged-in users with CMS permission
if($this->CanCreateTopLevelType == 'LoggedInUsers' && $member) return true; if( $this->CanCreateTopLevelType === 'LoggedInUsers'
&& Permission::checkMember($member, $this->config()->required_permission)
) {
return true;
}
// check for specific groups // check for specific groups
if($member && is_numeric($member)) $member = DataObject::get_by_id('Member', $member); if( $this->CanCreateTopLevelType === 'OnlyTheseUsers'
if($this->CanCreateTopLevelType == 'OnlyTheseUsers' && $member && $member->inGroups($this->CreateTopLevelGroups())) return true; && $member
&& $member->inGroups($this->CreateTopLevelGroups())
) {
return true;
}
return false; return false;
} }

View File

@ -815,7 +815,7 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
// check for inherit // check for inherit
if($this->CanViewType == 'Inherit') { if($this->CanViewType == 'Inherit') {
if($this->ParentID) return $this->Parent()->canView($member); if($this->ParentID) return $this->Parent()->canView($member);
else return $this->getSiteConfig()->canView($member); else return $this->getSiteConfig()->canViewPages($member);
} }
// check for any logged-in users // check for any logged-in users
@ -896,12 +896,12 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
/** /**
* This function should return true if the current user can create new * This function should return true if the current user can create new
* pages of this class. It can be overloaded to customise the security model for an * pages of this class, regardless of context. It can be overloaded
* application. * to customise the security model for an application.
* *
* Denies permission if any of the following conditions is TRUE: * By default, permission to create at the root level is based on the SiteConfig
* - canCreate() returns FALSE on any extension * configuration, and permission to create beneath a parent is based on the
* - $can_create is set to FALSE and the site is not in "dev mode" * 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.
* *
@ -909,6 +909,9 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
* @uses DataExtension->canCreate() * @uses DataExtension->canCreate()
* *
* @param Member $member * @param Member $member
* @param array $context Optional array which may contain array('Parent' => $parentObj)
* If a parent page is known, it will be checked for validity.
* If omitted, it will be assumed this is to be created as a top level page.
* @return boolean True if the current user can create pages on this class. * @return boolean True if the current user can create pages on this class.
*/ */
public function canCreate($member = null) { public function canCreate($member = null) {
@ -916,15 +919,30 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
$member = Member::currentUserID(); $member = Member::currentUserID();
} }
if($member && Permission::checkMember($member, "ADMIN")) return true; // Check parent (custom canCreate option for SiteTree)
// Block children not allowed for this parent type
// Standard mechanism for accepting permission changes from extensions $context = func_num_args() > 1 ? func_get_arg(1) : array();
$extended = $this->extendedCan('canCreate', $member); $parent = isset($context['Parent']) ? $context['Parent'] : null;
if($extended !== null) return $extended; if($parent && !in_array(get_class($this), $parent->allowedChildren())) return false;
return $this->stat('can_create') != false || Director::isDev();
}
// Check permission
if($member && Permission::checkMember($member, "ADMIN")) return true;
// Standard mechanism for accepting permission changes from extensions
$results = $this->extend('canCreate', $member, $parent);
if(is_array($results) && ($results = array_filter($results, function($v) {return $v !== null;}))) {
return min($results);
}
// Fall over to inherited permissions
if($parent) {
return $parent->canAddChildren($member);
} else {
// This doesn't necessarily mean we are creating a root page, but that
// we don't know if there is a parent, so default to this permission
return SiteConfig::current_site_config()->canCreateTopLevel($member);
}
}
/** /**
* This function should return true if the current user can edit this * This function should return true if the current user can edit this
@ -966,7 +984,7 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
// Default for unsaved pages // Default for unsaved pages
} else { } else {
return $this->getSiteConfig()->canEdit($member); return $this->getSiteConfig()->canEditPages($member);
} }
} }
@ -1207,7 +1225,7 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
* page can be edited. * page can be edited.
*/ */
static public function can_edit_multiple($ids, $memberID, $useCached = true) { static public function can_edit_multiple($ids, $memberID, $useCached = true) {
return self::batch_permission_check($ids, $memberID, 'CanEditType', 'SiteTree_EditorGroups', 'canEdit', 'CMS_ACCESS_CMSMain', $useCached); return self::batch_permission_check($ids, $memberID, 'CanEditType', 'SiteTree_EditorGroups', 'canEditPages', 'CMS_ACCESS_CMSMain', $useCached);
} }
/** /**
@ -2574,9 +2592,20 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
* @return string a html string ready to be directly used in a template * @return string a html string ready to be directly used in a template
*/ */
public function getTreeTitle() { public function getTreeTitle() {
// Build the list of candidate children
$children = array();
$candidates = static::page_type_classes();
foreach($this->allowedChildren() as $childClass) {
if(!in_array($childClass, $candidates)) continue;
$child = singleton($childClass);
if($child->canCreate(null, array('Parent' => $this))) {
$children[$childClass] = $child->i18n_singular_name();
}
}
$flags = $this->getStatusFlags(); $flags = $this->getStatusFlags();
$treeTitle = sprintf( $treeTitle = sprintf(
"<span class=\"jstree-pageicon\"></span><span class=\"item\">%s</span>", "<span class=\"jstree-pageicon\"></span><span class=\"item\" data-allowedchildren=\"%s\">%s</span>",
Convert::raw2att(Convert::raw2json($children)),
Convert::raw2xml(str_replace(array("\n","\r"),"",$this->MenuTitle)) Convert::raw2xml(str_replace(array("\n","\r"),"",$this->MenuTitle))
); );
foreach($flags as $class => $data) { foreach($flags as $class => $data) {

View File

@ -15,37 +15,107 @@
}); });
$(".cms-add-form").entwine({ $(".cms-add-form").entwine({
onmatch: function() { ParentID: 0, // Last selected parentID
ParentCache: {}, // Cache allowed children for each selected page
onadd: function() {
var self = this; var self = this;
this.find('#ParentID .TreeDropdownField').bind('change', function() { this.find('#ParentID .TreeDropdownField').bind('change', function() {
self.updateTypeList(); self.updateTypeList();
}); });
this.find(".SelectionGroup.parent-mode").bind('change', function() {
self.updateTypeList();
});
this.updateTypeList(); this.updateTypeList();
this._super();
}, },
onunmatch: function() { loadCachedChildren: function(parentID) {
this._super(); var cache = this.getParentCache();
if(typeof cache[parentID] !== 'undefined') return cache[parentID];
else return null;
},
saveCachedChildren: function(parentID, children) {
var cache = this.getParentCache();
cache[parentID] = children;
this.setParentCache(cache);
}, },
/** /**
* Limit page type selection based on parent class. * Limit page type selection based on parent selection.
* Select of root classes is pre-computed, but selections with a given parent
* are updated on-demand.
* Similar implementation to LeftAndMain.Tree.js. * Similar implementation to LeftAndMain.Tree.js.
*/ */
updateTypeList: function() { updateTypeList: function() {
var hints = this.find('.hints').data('hints'), var hints = this.data('hints'),
metadata = this.find('#ParentID .TreeDropdownField').data('metadata'), parentTree = this.find('#ParentID .TreeDropdownField'),
id = this.find('#ParentID .TreeDropdownField').getValue(), parentMode = this.find("input[name=ParentModeField]:checked").val(),
metadata = parentTree.data('metadata'),
id = (metadata && parentMode === 'child')
? (parentTree.getValue() || this.getParentID())
: null,
newClassName = metadata ? metadata.ClassName : null, newClassName = metadata ? metadata.ClassName : null,
hintKey = newClassName ? newClassName : 'Root', hintKey = (newClassName && parentMode === 'child')
hint = (typeof hints[hintKey] != 'undefined') ? hints[hintKey] : null; ? newClassName
: 'Root',
var disallowedChildren = (hint && typeof hint.disallowedChildren != 'undefined') ? hint.disallowedChildren : [], hint = (typeof hints[hintKey] !== 'undefined') ? hints[hintKey] : null,
defaultChildClass = (hint && typeof hint.defaultChild != 'undefined') ? hint.defaultChild : null; self = this,
defaultChildClass = (hint && typeof hint.defaultChild !== 'undefined')
? hint.defaultChild
: null,
disallowedChildren = [];
if(id) {
// Prevent interface operations
if(this.hasClass('loading')) return;
this.addClass('loading');
// Enable last parent ID to be re-selected from memory
this.setParentID(id);
if(!parentTree.getValue()) parentTree.setValue(id);
// Use cached data if available
disallowedChildren = this.loadCachedChildren(id);
if(disallowedChildren !== null) {
this.updateSelectionFilter(disallowedChildren, defaultChildClass);
this.removeClass('loading');
return;
}
$.ajax({
url: self.data('childfilter'),
data: {'ParentID': id},
success: function(data) {
// reload current form and tree
self.saveCachedChildren(id, data);
self.updateSelectionFilter(data, defaultChildClass);
},
complete: function() {
self.removeClass('loading');
}
});
return false;
} else {
disallowedChildren = (hint && typeof hint.disallowedChildren !== 'undefined')
? hint.disallowedChildren
: [],
this.updateSelectionFilter(disallowedChildren, defaultChildClass);
}
},
/**
* Update the selection filter with the given blacklist and default selection
*
* @param array disallowedChildren
* @param string defaultChildClass
*/
updateSelectionFilter: function(disallowedChildren, defaultChildClass) {
// Limit selection // Limit selection
var allAllowed = null; // troolian
this.find('#PageType li').each(function() { this.find('#PageType li').each(function() {
var className = $(this).find('input').val(), isAllowed = ($.inArray(className, disallowedChildren) == -1); var className = $(this).find('input').val(),
isAllowed = ($.inArray(className, disallowedChildren) === -1);
$(this).setEnabled(isAllowed); $(this).setEnabled(isAllowed);
if(!isAllowed) $(this).setSelected(false);
if(allAllowed === null) allAllowed = isAllowed;
else allAllowed = allAllowed && isAllowed;
}); });
// Set default child selection, or fall back to first available option // Set default child selection, or fall back to first available option
@ -69,10 +139,13 @@
}, },
setSelected: function(bool) { setSelected: function(bool) {
var input = this.find('input'); var input = this.find('input');
this.toggleClass('selected', bool);
if(bool && !input.is(':disabled')) { if(bool && !input.is(':disabled')) {
this.siblings().setSelected(false); this.siblings().setSelected(false);
input.attr('checked', 'checked'); this.toggleClass('selected', true);
input.prop('checked', true);
} else {
this.toggleClass('selected', false);
input.prop('checked', false);
} }
}, },
setEnabled: function(bool) { setEnabled: function(bool) {

View File

@ -9,23 +9,23 @@
'items': function(node) { 'items': function(node) {
// Build a list for allowed children as submenu entries // Build a list for allowed children as submenu entries
var pagetype = node.data('pagetype');
var id = node.data('id'); var id = node.data('id');
var allowedChildrenClasses = node.find('>a .item').data('allowedchildren');
var allowedChildren = new Object; var allowedChildren = new Object;
$(hints[pagetype].allowedChildren).each( var hasAllowedChildren = false;
function(key, val){ $.each(allowedChildrenClasses, function(klass, title) {
allowedChildren["allowedchildren-" + key ] = { hasAllowedChildren = true;
'label': '<span class="jstree-pageicon"></span>' + val.ssname, allowedChildren["allowedchildren-" + klass ] = {
'_class': 'class-' + val.ssclass, 'label': '<span class="jstree-pageicon"></span>' + title,
'action': function(obj) { '_class': 'class-' + klass,
$('.cms-container').entwine('.ss').loadPanel(ss.i18n.sprintf( 'action': function(obj) {
self.data('urlAddpage'), id, val.ssclass $('.cms-container').entwine('.ss').loadPanel(ss.i18n.sprintf(
)); self.data('urlAddpage'), id, klass
} ));
}; }
} };
); });
var menuitems = var menuitems =
{ {
'edit': { 'edit': {
@ -38,7 +38,7 @@
} }
}; };
// Test if there are any allowed Children and thus the possibility of adding some // Test if there are any allowed Children and thus the possibility of adding some
if(allowedChildren.hasOwnProperty('allowedchildren-0')) { if(hasAllowedChildren) {
menuitems['addsubpage'] = { menuitems['addsubpage'] = {
'label': ss.i18n._t('Tree.AddSubPage', 'Add page under this page', 100, 'Used in the context menu when right-clicking on a page node in the CMS tree'), 'label': ss.i18n._t('Tree.AddSubPage', 'Add page under this page', 100, 'Used in the context menu when right-clicking on a page node in the CMS tree'),
'submenu': allowedChildren 'submenu': allowedChildren

View File

@ -19,7 +19,7 @@ $ExtraTreeTools
</div> </div>
<% end_if %> <% end_if %>
<div class="cms-tree" data-url-tree="$Link(getsubtree)" data-url-savetreenode="$Link(savetreenode)" data-url-updatetreenodes="$Link(updatetreenodes)" data-url-addpage="{$LinkPageAdd('AddForm/?action_doAdd=1')}&amp;ParentID=%s&amp;PageType=%s&amp;SecurityID=$SecurityID" data-url-editpage="$LinkPageEdit('%s')" data-hints="$SiteTreeHints"> <div class="cms-tree" data-url-tree="$Link(getsubtree)" data-url-savetreenode="$Link(savetreenode)" data-url-updatetreenodes="$Link(updatetreenodes)" data-url-addpage="{$LinkPageAdd('AddForm/?action_doAdd=1')}&amp;ParentID=%s&amp;PageType=%s&amp;SecurityID=$SecurityID" data-url-editpage="$LinkPageEdit('%s')" data-hints="$SiteTreeHints.ATT" data-childfilter="$Link('childfilter')">
$SiteTreeAsUL $SiteTreeAsUL
</div> </div>
</div> </div>

View File

@ -8,7 +8,7 @@ class CMSMainTest extends FunctionalTest {
static $fixture_file = 'CMSMainTest.yml'; static $fixture_file = 'CMSMainTest.yml';
static protected $orig = array(); static protected $orig = array();
public function setUpOnce() { public function setUpOnce() {
self::$orig['CMSBatchActionHandler_batch_actions'] = CMSBatchActionHandler::$batch_actions; self::$orig['CMSBatchActionHandler_batch_actions'] = CMSBatchActionHandler::$batch_actions;
CMSBatchActionHandler::$batch_actions = array( CMSBatchActionHandler::$batch_actions = array(
@ -19,12 +19,84 @@ class CMSMainTest extends FunctionalTest {
parent::setUpOnce(); parent::setUpOnce();
} }
public function tearDownOnce() { public function tearDownOnce() {
CMSBatchActionHandler::$batch_actions = self::$orig['CMSBatchActionHandler_batch_actions']; CMSBatchActionHandler::$batch_actions = self::$orig['CMSBatchActionHandler_batch_actions'];
parent::tearDownOnce(); parent::tearDownOnce();
} }
function testSiteTreeHints() {
$cache = SS_Cache::factory('CMSMain_SiteTreeHints');
// Login as user with root creation privileges
$user = $this->objFromFixture('Member', 'rootedituser');
$user->logIn();
$cache->clean(Zend_Cache::CLEANING_MODE_ALL);
$rawHints = singleton('CMSMain')->SiteTreeHints();
$this->assertNotNull($rawHints);
$rawHints = preg_replace('/^"(.*)"$/', '$1', Convert::xml2raw($rawHints));
$hints = Convert::json2array($rawHints);
$this->assertArrayHasKey('Root', $hints);
$this->assertArrayHasKey('Page', $hints);
$this->assertArrayHasKey('All', $hints);
$this->assertArrayHasKey(
'CMSMainTest_ClassA',
$hints['All'],
'Global list shows allowed classes'
);
$this->assertArrayNotHasKey(
'CMSMainTest_HiddenClass',
$hints['All'],
'Global list does not list hidden classes'
);
$this->assertNotContains(
'CMSMainTest_ClassA',
$hints['Root']['disallowedChildren'],
'Limits root classes'
);
$this->assertContains(
'CMSMainTest_NotRoot',
$hints['Root']['disallowedChildren'],
'Limits root classes'
);
}
public function testChildFilter() {
$this->logInWithPermission('ADMIN');
// Check page A
$pageA = new CMSMainTest_ClassA();
$pageA->write();
$pageB = new CMSMainTest_ClassB();
$pageB->write();
// Check query
$response = $this->get('CMSMain/childfilter?ParentID='.$pageA->ID);
$children = json_decode($response->getBody());
$this->assertFalse($response->isError());
// Page A can't have unrelated children
$this->assertContains(
'Page',
$children,
'Limited parent lists disallowed classes'
);
// But it can create a ClassB
$this->assertNotContains(
'CMSMainTest_ClassB',
$children,
'Limited parent omits explicitly allowed classes in disallowedChildren'
);
}
/** /**
* @todo Test the results of a publication better * @todo Test the results of a publication better
@ -256,11 +328,7 @@ class CMSMainTest extends FunctionalTest {
'admin/pages/add/AddForm', 'admin/pages/add/AddForm',
array('ParentID' => $newPageId, 'PageType' => 'Page', 'Locale' => 'en_US', 'action_doAdd' => 1) array('ParentID' => $newPageId, 'PageType' => 'Page', 'Locale' => 'en_US', 'action_doAdd' => 1)
); );
$this->assertFalse($response->isError()); $this->assertEquals(403, $response->getStatusCode(), 'Add disallowed child should fail');
$this->assertContains(
htmlentities(_t('SiteTree.PageTypeNotAllowed', array('type' => 'Page'))),
$response->getBody()
);
$this->session()->inst_set('loggedInAs', NULL); $this->session()->inst_set('loggedInAs', NULL);
@ -314,3 +382,11 @@ class CMSMainTest_ClassA extends Page implements TestOnly {
class CMSMainTest_ClassB extends Page implements TestOnly { class CMSMainTest_ClassB extends Page implements TestOnly {
} }
class CMSMainTest_NotRoot extends Page implements TestOnly {
static $can_be_root = false;
}
class CMSMainTest_HiddenClass extends Page implements TestOnly, HiddenClass {
}

View File

@ -7,13 +7,15 @@
* SiteTreePermissionsTest * SiteTreePermissionsTest
*/ */
class SiteConfigTest extends SapphireTest { class SiteConfigTest extends SapphireTest {
static $fixture_file = 'SiteConfigTest.yml';
protected $illegalExtensions = array( protected $illegalExtensions = array(
'SiteTree' => array('SiteTreeSubsites') 'SiteTree' => array('SiteTreeSubsites')
); );
public function testAvailableThemes() { public function testAvailableThemes() {
$config = SiteConfig::current_site_config(); $config = $this->objFromFixture('SiteConfig', 'default');
$ds = DIRECTORY_SEPARATOR; $ds = DIRECTORY_SEPARATOR;
$testThemeBaseDir = TEMP_FOLDER . $ds . 'test-themes'; $testThemeBaseDir = TEMP_FOLDER . $ds . 'test-themes';
@ -34,5 +36,50 @@ class SiteConfigTest extends SapphireTest {
Filesystem::removeFolder($testThemeBaseDir); Filesystem::removeFolder($testThemeBaseDir);
} }
public function testCanCreateRootPages() {
$config = $this->objFromFixture('SiteConfig', 'default');
// Log in without pages admin access
$this->logInWithPermission('CMS_ACCESS_AssetAdmin');
$this->assertFalse($config->canCreateTopLevel());
// Login with necessary edit permission
$perms = SiteConfig::config()->required_permission;
$this->logInWithPermission(reset($perms));
$this->assertTrue($config->canCreateTopLevel());
}
public function testCanViewPages() {
$config = $this->objFromFixture('SiteConfig', 'default');
$this->assertTrue($config->canViewPages());
}
public function testCanEdit() {
$config = $this->objFromFixture('SiteConfig', 'default');
// Unrelated permissions don't allow siteconfig
$this->logInWithPermission('CMS_ACCESS_AssetAdmin');
$this->assertFalse($config->canEdit());
// Only those with edit permission can do this
$this->logInWithPermission('EDIT_SITECONFIG');
$this->assertTrue($config->canEdit());
}
public function testCanEditPages() {
$config = $this->objFromFixture('SiteConfig', 'default');
// Log in without pages admin access
$this->logInWithPermission('CMS_ACCESS_AssetAdmin');
$this->assertFalse($config->canEditPages());
// Login with necessary edit permission
$perms = SiteConfig::config()->required_permission;
$this->logInWithPermission(reset($perms));
$this->assertTrue($config->canEditPages());
}
} }

View File

@ -0,0 +1,7 @@
SiteConfig:
default:
Title: My test site
Tagline: Default site config
CanViewType: Anyone
CanEditType: LoggedInUsers
CanCreateTopLevelType: LoggedInUsers

View File

@ -19,24 +19,31 @@ class SiteTreeTest extends SapphireTest {
'SiteTreeTest_NotRoot', 'SiteTreeTest_NotRoot',
'SiteTreeTest_StageStatusInherit', 'SiteTreeTest_StageStatusInherit',
); );
/**
* Ensure any current member is logged out
*/
public function logOut() {
if($member = Member::currentUser()) $member->logOut();
}
public function testCreateDefaultpages() { public function testCreateDefaultpages() {
$remove = DataObject::get('SiteTree'); $remove = DataObject::get('SiteTree');
if($remove) foreach($remove as $page) $page->delete(); if($remove) foreach($remove as $page) $page->delete();
// Make sure the table is empty // Make sure the table is empty
$this->assertEquals(DB::query('SELECT COUNT("ID") FROM "SiteTree"')->value(), 0); $this->assertEquals(DB::query('SELECT COUNT("ID") FROM "SiteTree"')->value(), 0);
// Disable the creation // Disable the creation
SiteTree::set_create_default_pages(false); SiteTree::set_create_default_pages(false);
singleton('SiteTree')->requireDefaultRecords(); singleton('SiteTree')->requireDefaultRecords();
// The table should still be empty // The table should still be empty
$this->assertEquals(DB::query('SELECT COUNT("ID") FROM "SiteTree"')->value(), 0); $this->assertEquals(DB::query('SELECT COUNT("ID") FROM "SiteTree"')->value(), 0);
// Enable the creation // Enable the creation
SiteTree::set_create_default_pages(true); SiteTree::set_create_default_pages(true);
singleton('SiteTree')->requireDefaultRecords(); singleton('SiteTree')->requireDefaultRecords();
// The table should now have three rows (home, about-us, contact-us) // The table should now have three rows (home, about-us, contact-us)
$this->assertEquals(DB::query('SELECT COUNT("ID") FROM "SiteTree"')->value(), 3); $this->assertEquals(DB::query('SELECT COUNT("ID") FROM "SiteTree"')->value(), 3);
} }
@ -62,30 +69,30 @@ class SiteTreeTest extends SapphireTest {
'controller' => 'controller-2', 'controller' => 'controller-2',
'numericonly' => '1930', 'numericonly' => '1930',
); );
foreach($expectedURLs as $fixture => $urlSegment) { foreach($expectedURLs as $fixture => $urlSegment) {
$obj = $this->objFromFixture('Page', $fixture); $obj = $this->objFromFixture('Page', $fixture);
$this->assertEquals($urlSegment, $obj->URLSegment); $this->assertEquals($urlSegment, $obj->URLSegment);
} }
} }
/** /**
* Test that publication copies data to SiteTree_Live * Test that publication copies data to SiteTree_Live
*/ */
public function testPublishCopiesToLiveTable() { public function testPublishCopiesToLiveTable() {
$obj = $this->objFromFixture('Page','about'); $obj = $this->objFromFixture('Page','about');
$obj->publish('Stage', 'Live'); $obj->publish('Stage', 'Live');
$createdID = DB::query("SELECT \"ID\" FROM \"SiteTree_Live\" WHERE \"URLSegment\" = '$obj->URLSegment'")->value(); $createdID = DB::query("SELECT \"ID\" FROM \"SiteTree_Live\" WHERE \"URLSegment\" = '$obj->URLSegment'")->value();
$this->assertEquals($obj->ID, $createdID); $this->assertEquals($obj->ID, $createdID);
} }
/** /**
* Test that field which are set and then cleared are also transferred to the published site. * Test that field which are set and then cleared are also transferred to the published site.
*/ */
public function testPublishDeletedFields() { public function testPublishDeletedFields() {
$this->logInWithPermission('ADMIN'); $this->logInWithPermission('ADMIN');
$obj = $this->objFromFixture('Page', 'about'); $obj = $this->objFromFixture('Page', 'about');
$obj->MetaTitle = "asdfasdf"; $obj->MetaTitle = "asdfasdf";
$obj->write(); $obj->write();
@ -98,28 +105,27 @@ class SiteTreeTest extends SapphireTest {
$this->assertTrue($obj->doPublish()); $this->assertTrue($obj->doPublish());
$this->assertNull(DB::query("SELECT \"MetaTitle\" FROM \"SiteTree_Live\" WHERE \"ID\" = '$obj->ID'")->value()); $this->assertNull(DB::query("SELECT \"MetaTitle\" FROM \"SiteTree_Live\" WHERE \"ID\" = '$obj->ID'")->value());
} }
public function testParentNodeCachedInMemory() { public function testParentNodeCachedInMemory() {
$parent = new SiteTree(); $parent = new SiteTree();
$parent->Title = 'Section Title'; $parent->Title = 'Section Title';
$child = new SiteTree(); $child = new SiteTree();
$child->Title = 'Page Title'; $child->Title = 'Page Title';
$child->setParent($parent); $child->setParent($parent);
$this->assertInstanceOf("SiteTree", $child->Parent); $this->assertInstanceOf("SiteTree", $child->Parent);
$this->assertEquals("Section Title", $child->Parent->Title); $this->assertEquals("Section Title", $child->Parent->Title);
} }
public function testParentModelReturnType() { public function testParentModelReturnType() {
$parent = new SiteTreeTest_PageNode(); $parent = new SiteTreeTest_PageNode();
$child = new SiteTreeTest_PageNode(); $child = new SiteTreeTest_PageNode();
$child->setParent($parent); $child->setParent($parent);
$this->assertInstanceOf('SiteTreeTest_PageNode', $child->Parent); $this->assertInstanceOf('SiteTreeTest_PageNode', $child->Parent);
} }
/** /**
* Confirm that DataObject::get_one() gets records from SiteTree_Live * Confirm that DataObject::get_one() gets records from SiteTree_Live
*/ */
@ -131,41 +137,41 @@ class SiteTreeTest extends SapphireTest {
$s->publish("Stage", "Live"); $s->publish("Stage", "Live");
$s->Title = "V2"; $s->Title = "V2";
$s->write(); $s->write();
$oldMode = Versioned::get_reading_mode(); $oldMode = Versioned::get_reading_mode();
Versioned::reading_stage('Live'); Versioned::reading_stage('Live');
$checkSiteTree = DataObject::get_one("SiteTree", "\"URLSegment\" = 'get-one-test-page'"); $checkSiteTree = DataObject::get_one("SiteTree", "\"URLSegment\" = 'get-one-test-page'");
$this->assertEquals("V1", $checkSiteTree->Title); $this->assertEquals("V1", $checkSiteTree->Title);
Versioned::set_reading_mode($oldMode); Versioned::set_reading_mode($oldMode);
} }
public function testChidrenOfRootAreTopLevelPages() { public function testChidrenOfRootAreTopLevelPages() {
$pages = DataObject::get("SiteTree"); $pages = DataObject::get("SiteTree");
foreach($pages as $page) $page->publish('Stage', 'Live'); foreach($pages as $page) $page->publish('Stage', 'Live');
unset($pages); unset($pages);
/* If we create a new SiteTree object with ID = 0 */ /* If we create a new SiteTree object with ID = 0 */
$obj = new SiteTree(); $obj = new SiteTree();
/* Then its children should be the top-level pages */ /* Then its children should be the top-level pages */
$stageChildren = $obj->stageChildren()->map('ID','Title'); $stageChildren = $obj->stageChildren()->map('ID','Title');
$liveChildren = $obj->liveChildren()->map('ID','Title'); $liveChildren = $obj->liveChildren()->map('ID','Title');
$allChildren = $obj->AllChildrenIncludingDeleted()->map('ID','Title'); $allChildren = $obj->AllChildrenIncludingDeleted()->map('ID','Title');
$this->assertContains('Home', $stageChildren); $this->assertContains('Home', $stageChildren);
$this->assertContains('Products', $stageChildren); $this->assertContains('Products', $stageChildren);
$this->assertNotContains('Staff', $stageChildren); $this->assertNotContains('Staff', $stageChildren);
$this->assertContains('Home', $liveChildren); $this->assertContains('Home', $liveChildren);
$this->assertContains('Products', $liveChildren); $this->assertContains('Products', $liveChildren);
$this->assertNotContains('Staff', $liveChildren); $this->assertNotContains('Staff', $liveChildren);
$this->assertContains('Home', $allChildren); $this->assertContains('Home', $allChildren);
$this->assertContains('Products', $allChildren); $this->assertContains('Products', $allChildren);
$this->assertNotContains('Staff', $allChildren); $this->assertNotContains('Staff', $allChildren);
} }
public function testCanSaveBlankToHasOneRelations() { public function testCanSaveBlankToHasOneRelations() {
/* DataObject::write() should save to a has_one relationship if you set a field called (relname)ID */ /* DataObject::write() should save to a has_one relationship if you set a field called (relname)ID */
$page = new SiteTree(); $page = new SiteTree();
@ -173,13 +179,13 @@ class SiteTreeTest extends SapphireTest {
$page->ParentID = $parentID; $page->ParentID = $parentID;
$page->write(); $page->write();
$this->assertEquals($parentID, DB::query("SELECT \"ParentID\" FROM \"SiteTree\" WHERE \"ID\" = $page->ID")->value()); $this->assertEquals($parentID, DB::query("SELECT \"ParentID\" FROM \"SiteTree\" WHERE \"ID\" = $page->ID")->value());
/* You should then be able to save a null/0/'' value to the relation */ /* You should then be able to save a null/0/'' value to the relation */
$page->ParentID = null; $page->ParentID = null;
$page->write(); $page->write();
$this->assertEquals(0, DB::query("SELECT \"ParentID\" FROM \"SiteTree\" WHERE \"ID\" = $page->ID")->value()); $this->assertEquals(0, DB::query("SELECT \"ParentID\" FROM \"SiteTree\" WHERE \"ID\" = $page->ID")->value());
} }
public function testStageStates() { public function testStageStates() {
// newly created page // newly created page
$createdPage = new SiteTree(); $createdPage = new SiteTree();
@ -187,15 +193,15 @@ class SiteTreeTest extends SapphireTest {
$this->assertFalse($createdPage->IsDeletedFromStage); $this->assertFalse($createdPage->IsDeletedFromStage);
$this->assertTrue($createdPage->IsAddedToStage); $this->assertTrue($createdPage->IsAddedToStage);
$this->assertTrue($createdPage->IsModifiedOnStage); $this->assertTrue($createdPage->IsModifiedOnStage);
// published page // published page
$publishedPage = new SiteTree(); $publishedPage = new SiteTree();
$publishedPage->write(); $publishedPage->write();
$publishedPage->publish('Stage','Live'); $publishedPage->publish('Stage','Live');
$this->assertFalse($publishedPage->IsDeletedFromStage); $this->assertFalse($publishedPage->IsDeletedFromStage);
$this->assertFalse($publishedPage->IsAddedToStage); $this->assertFalse($publishedPage->IsAddedToStage);
$this->assertFalse($publishedPage->IsModifiedOnStage); $this->assertFalse($publishedPage->IsModifiedOnStage);
// published page, deleted from stage // published page, deleted from stage
$deletedFromDraftPage = new SiteTree(); $deletedFromDraftPage = new SiteTree();
$deletedFromDraftPage->write(); $deletedFromDraftPage->write();
@ -205,7 +211,7 @@ class SiteTreeTest extends SapphireTest {
$this->assertTrue($deletedFromDraftPage->IsDeletedFromStage); $this->assertTrue($deletedFromDraftPage->IsDeletedFromStage);
$this->assertFalse($deletedFromDraftPage->IsAddedToStage); $this->assertFalse($deletedFromDraftPage->IsAddedToStage);
$this->assertFalse($deletedFromDraftPage->IsModifiedOnStage); $this->assertFalse($deletedFromDraftPage->IsModifiedOnStage);
// published page, deleted from live // published page, deleted from live
$deletedFromLivePage = new SiteTree(); $deletedFromLivePage = new SiteTree();
$deletedFromLivePage->write(); $deletedFromLivePage->write();
@ -215,7 +221,7 @@ class SiteTreeTest extends SapphireTest {
$this->assertTrue($deletedFromLivePage->IsDeletedFromStage); $this->assertTrue($deletedFromLivePage->IsDeletedFromStage);
$this->assertFalse($deletedFromLivePage->IsAddedToStage); $this->assertFalse($deletedFromLivePage->IsAddedToStage);
$this->assertFalse($deletedFromLivePage->IsModifiedOnStage); $this->assertFalse($deletedFromLivePage->IsModifiedOnStage);
// published page, modified // published page, modified
$modifiedOnDraftPage = new SiteTree(); $modifiedOnDraftPage = new SiteTree();
$modifiedOnDraftPage->write(); $modifiedOnDraftPage->write();
@ -226,7 +232,7 @@ class SiteTreeTest extends SapphireTest {
$this->assertFalse($modifiedOnDraftPage->IsAddedToStage); $this->assertFalse($modifiedOnDraftPage->IsAddedToStage);
$this->assertTrue($modifiedOnDraftPage->IsModifiedOnStage); $this->assertTrue($modifiedOnDraftPage->IsModifiedOnStage);
} }
/** /**
* Test that a page can be completely deleted and restored to the stage site * Test that a page can be completely deleted and restored to the stage site
*/ */
@ -235,23 +241,23 @@ class SiteTreeTest extends SapphireTest {
$pageID = $page->ID; $pageID = $page->ID;
$page->delete(); $page->delete();
$this->assertTrue(!DataObject::get_by_id("Page", $pageID)); $this->assertTrue(!DataObject::get_by_id("Page", $pageID));
$deletedPage = Versioned::get_latest_version('SiteTree', $pageID); $deletedPage = Versioned::get_latest_version('SiteTree', $pageID);
$resultPage = $deletedPage->doRestoreToStage(); $resultPage = $deletedPage->doRestoreToStage();
$requeriedPage = DataObject::get_by_id("Page", $pageID); $requeriedPage = DataObject::get_by_id("Page", $pageID);
$this->assertEquals($pageID, $resultPage->ID); $this->assertEquals($pageID, $resultPage->ID);
$this->assertEquals($pageID, $requeriedPage->ID); $this->assertEquals($pageID, $requeriedPage->ID);
$this->assertEquals('About Us', $requeriedPage->Title); $this->assertEquals('About Us', $requeriedPage->Title);
$this->assertEquals('Page', $requeriedPage->class); $this->assertEquals('Page', $requeriedPage->class);
$page2 = $this->objFromFixture('Page', 'products'); $page2 = $this->objFromFixture('Page', 'products');
$page2ID = $page2->ID; $page2ID = $page2->ID;
$page2->doUnpublish(); $page2->doUnpublish();
$page2->delete(); $page2->delete();
// Check that if we restore while on the live site that the content still gets pushed to // Check that if we restore while on the live site that the content still gets pushed to
// stage // stage
Versioned::reading_stage('Live'); Versioned::reading_stage('Live');
@ -263,16 +269,16 @@ class SiteTreeTest extends SapphireTest {
$requeriedPage = DataObject::get_by_id("Page", $page2ID); $requeriedPage = DataObject::get_by_id("Page", $page2ID);
$this->assertEquals('Products', $requeriedPage->Title); $this->assertEquals('Products', $requeriedPage->Title);
$this->assertEquals('Page', $requeriedPage->class); $this->assertEquals('Page', $requeriedPage->class);
} }
public function testGetByLink() { public function testGetByLink() {
$home = $this->objFromFixture('Page', 'home'); $home = $this->objFromFixture('Page', 'home');
$about = $this->objFromFixture('Page', 'about'); $about = $this->objFromFixture('Page', 'about');
$staff = $this->objFromFixture('Page', 'staff'); $staff = $this->objFromFixture('Page', 'staff');
$product = $this->objFromFixture('Page', 'product1'); $product = $this->objFromFixture('Page', 'product1');
$notFound = $this->objFromFixture('ErrorPage', '404'); $notFound = $this->objFromFixture('ErrorPage', '404');
SiteTree::disable_nested_urls(); SiteTree::disable_nested_urls();
$this->assertEquals($home->ID, SiteTree::get_by_link('/', false)->ID); $this->assertEquals($home->ID, SiteTree::get_by_link('/', false)->ID);
@ -290,12 +296,12 @@ class SiteTreeTest extends SapphireTest {
$this->assertEquals($staff->ID, SiteTree::get_by_link($staff->Link(), false)->ID); $this->assertEquals($staff->ID, SiteTree::get_by_link($staff->Link(), false)->ID);
$this->assertEquals($product->ID, SiteTree::get_by_link($product->Link(), false)->ID); $this->assertEquals($product->ID, SiteTree::get_by_link($product->Link(), false)->ID);
$this->assertEquals($notFound->ID, SiteTree::get_by_link($notFound->Link(), false)->ID); $this->assertEquals($notFound->ID, SiteTree::get_by_link($notFound->Link(), false)->ID);
$this->assertEquals ( $this->assertEquals (
$staff->ID, SiteTree::get_by_link('/my-staff/', false)->ID, 'Assert a unique URLSegment can be used for b/c.' $staff->ID, SiteTree::get_by_link('/my-staff/', false)->ID, 'Assert a unique URLSegment can be used for b/c.'
); );
} }
public function testRelativeLink() { public function testRelativeLink() {
$about = $this->objFromFixture('Page', 'about'); $about = $this->objFromFixture('Page', 'about');
$staff = $this->objFromFixture('Page', 'staff'); $staff = $this->objFromFixture('Page', 'staff');
@ -320,52 +326,52 @@ class SiteTreeTest extends SapphireTest {
$parent->publish('Stage', 'Live'); $parent->publish('Stage', 'Live');
$parent->URLSegment = 'changed-on-draft'; $parent->URLSegment = 'changed-on-draft';
$parent->write(); $parent->write();
$this->assertStringEndsWith('changed-on-live/my-staff/', $child->getAbsoluteLiveLink(false)); $this->assertStringEndsWith('changed-on-live/my-staff/', $child->getAbsoluteLiveLink(false));
$this->assertStringEndsWith('changed-on-live/my-staff/?stage=Live', $child->getAbsoluteLiveLink()); $this->assertStringEndsWith('changed-on-live/my-staff/?stage=Live', $child->getAbsoluteLiveLink());
} }
public function testDeleteFromStageOperatesRecursively() { public function testDeleteFromStageOperatesRecursively() {
SiteTree::set_enforce_strict_hierarchy(false); SiteTree::set_enforce_strict_hierarchy(false);
$pageAbout = $this->objFromFixture('Page', 'about'); $pageAbout = $this->objFromFixture('Page', 'about');
$pageStaff = $this->objFromFixture('Page', 'staff'); $pageStaff = $this->objFromFixture('Page', 'staff');
$pageStaffDuplicate = $this->objFromFixture('Page', 'staffduplicate'); $pageStaffDuplicate = $this->objFromFixture('Page', 'staffduplicate');
$pageAbout->delete(); $pageAbout->delete();
$this->assertFalse(DataObject::get_by_id('Page', $pageAbout->ID)); $this->assertFalse(DataObject::get_by_id('Page', $pageAbout->ID));
$this->assertTrue(DataObject::get_by_id('Page', $pageStaff->ID) instanceof Page); $this->assertTrue(DataObject::get_by_id('Page', $pageStaff->ID) instanceof Page);
$this->assertTrue(DataObject::get_by_id('Page', $pageStaffDuplicate->ID) instanceof Page); $this->assertTrue(DataObject::get_by_id('Page', $pageStaffDuplicate->ID) instanceof Page);
SiteTree::set_enforce_strict_hierarchy(true); SiteTree::set_enforce_strict_hierarchy(true);
} }
public function testDeleteFromStageOperatesRecursivelyStrict() { public function testDeleteFromStageOperatesRecursivelyStrict() {
$pageAbout = $this->objFromFixture('Page', 'about'); $pageAbout = $this->objFromFixture('Page', 'about');
$pageStaff = $this->objFromFixture('Page', 'staff'); $pageStaff = $this->objFromFixture('Page', 'staff');
$pageStaffDuplicate = $this->objFromFixture('Page', 'staffduplicate'); $pageStaffDuplicate = $this->objFromFixture('Page', 'staffduplicate');
$pageAbout->delete(); $pageAbout->delete();
$this->assertFalse(DataObject::get_by_id('Page', $pageAbout->ID)); $this->assertFalse(DataObject::get_by_id('Page', $pageAbout->ID));
$this->assertFalse(DataObject::get_by_id('Page', $pageStaff->ID)); $this->assertFalse(DataObject::get_by_id('Page', $pageStaff->ID));
$this->assertFalse(DataObject::get_by_id('Page', $pageStaffDuplicate->ID)); $this->assertFalse(DataObject::get_by_id('Page', $pageStaffDuplicate->ID));
} }
public function testDeleteFromLiveOperatesRecursively() { public function testDeleteFromLiveOperatesRecursively() {
SiteTree::set_enforce_strict_hierarchy(false); SiteTree::set_enforce_strict_hierarchy(false);
$this->logInWithPermission('ADMIN'); $this->logInWithPermission('ADMIN');
$pageAbout = $this->objFromFixture('Page', 'about'); $pageAbout = $this->objFromFixture('Page', 'about');
$pageAbout->doPublish(); $pageAbout->doPublish();
$pageStaff = $this->objFromFixture('Page', 'staff'); $pageStaff = $this->objFromFixture('Page', 'staff');
$pageStaff->doPublish(); $pageStaff->doPublish();
$pageStaffDuplicate = $this->objFromFixture('Page', 'staffduplicate'); $pageStaffDuplicate = $this->objFromFixture('Page', 'staffduplicate');
$pageStaffDuplicate->doPublish(); $pageStaffDuplicate->doPublish();
$parentPage = $this->objFromFixture('Page', 'about'); $parentPage = $this->objFromFixture('Page', 'about');
$parentPage->doDeleteFromLive(); $parentPage->doDeleteFromLive();
Versioned::reading_stage('Live'); Versioned::reading_stage('Live');
$this->assertFalse(DataObject::get_by_id('Page', $pageAbout->ID)); $this->assertFalse(DataObject::get_by_id('Page', $pageAbout->ID));
@ -374,21 +380,21 @@ class SiteTreeTest extends SapphireTest {
Versioned::reading_stage('Stage'); Versioned::reading_stage('Stage');
SiteTree::set_enforce_strict_hierarchy(true); SiteTree::set_enforce_strict_hierarchy(true);
} }
public function testUnpublishDoesNotDeleteChildrenWithLooseHierachyOn() { public function testUnpublishDoesNotDeleteChildrenWithLooseHierachyOn() {
SiteTree::set_enforce_strict_hierarchy(false); SiteTree::set_enforce_strict_hierarchy(false);
$this->logInWithPermission('ADMIN'); $this->logInWithPermission('ADMIN');
$pageAbout = $this->objFromFixture('Page', 'about'); $pageAbout = $this->objFromFixture('Page', 'about');
$pageAbout->doPublish(); $pageAbout->doPublish();
$pageStaff = $this->objFromFixture('Page', 'staff'); $pageStaff = $this->objFromFixture('Page', 'staff');
$pageStaff->doPublish(); $pageStaff->doPublish();
$pageStaffDuplicate = $this->objFromFixture('Page', 'staffduplicate'); $pageStaffDuplicate = $this->objFromFixture('Page', 'staffduplicate');
$pageStaffDuplicate->doPublish(); $pageStaffDuplicate->doPublish();
$parentPage = $this->objFromFixture('Page', 'about'); $parentPage = $this->objFromFixture('Page', 'about');
$parentPage->doUnpublish(); $parentPage->doUnpublish();
Versioned::reading_stage('Live'); Versioned::reading_stage('Live');
$this->assertFalse(DataObject::get_by_id('Page', $pageAbout->ID)); $this->assertFalse(DataObject::get_by_id('Page', $pageAbout->ID));
$this->assertTrue(DataObject::get_by_id('Page', $pageStaff->ID) instanceof Page); $this->assertTrue(DataObject::get_by_id('Page', $pageStaff->ID) instanceof Page);
@ -396,28 +402,28 @@ class SiteTreeTest extends SapphireTest {
Versioned::reading_stage('Stage'); Versioned::reading_stage('Stage');
SiteTree::set_enforce_strict_hierarchy(true); SiteTree::set_enforce_strict_hierarchy(true);
} }
public function testDeleteFromLiveOperatesRecursivelyStrict() { public function testDeleteFromLiveOperatesRecursivelyStrict() {
$this->logInWithPermission('ADMIN'); $this->logInWithPermission('ADMIN');
$pageAbout = $this->objFromFixture('Page', 'about'); $pageAbout = $this->objFromFixture('Page', 'about');
$pageAbout->doPublish(); $pageAbout->doPublish();
$pageStaff = $this->objFromFixture('Page', 'staff'); $pageStaff = $this->objFromFixture('Page', 'staff');
$pageStaff->doPublish(); $pageStaff->doPublish();
$pageStaffDuplicate = $this->objFromFixture('Page', 'staffduplicate'); $pageStaffDuplicate = $this->objFromFixture('Page', 'staffduplicate');
$pageStaffDuplicate->doPublish(); $pageStaffDuplicate->doPublish();
$parentPage = $this->objFromFixture('Page', 'about'); $parentPage = $this->objFromFixture('Page', 'about');
$parentPage->doDeleteFromLive(); $parentPage->doDeleteFromLive();
Versioned::reading_stage('Live'); Versioned::reading_stage('Live');
$this->assertFalse(DataObject::get_by_id('Page', $pageAbout->ID)); $this->assertFalse(DataObject::get_by_id('Page', $pageAbout->ID));
$this->assertFalse(DataObject::get_by_id('Page', $pageStaff->ID)); $this->assertFalse(DataObject::get_by_id('Page', $pageStaff->ID));
$this->assertFalse(DataObject::get_by_id('Page', $pageStaffDuplicate->ID)); $this->assertFalse(DataObject::get_by_id('Page', $pageStaffDuplicate->ID));
Versioned::reading_stage('Stage'); Versioned::reading_stage('Stage');
} }
/** /**
* Simple test to confirm that querying from a particular archive date doesn't throw * Simple test to confirm that querying from a particular archive date doesn't throw
* an error * an error
@ -429,27 +435,70 @@ class SiteTreeTest extends SapphireTest {
Versioned::reading_archived_date(null); Versioned::reading_archived_date(null);
} }
public function testEditPermissions() { public function testEditPermissions() {
$editor = $this->objFromFixture("Member", "editor"); $editor = $this->objFromFixture("Member", "editor");
$home = $this->objFromFixture("Page", "home"); $home = $this->objFromFixture("Page", "home");
$staff = $this->objFromFixture("Page", "staff");
$products = $this->objFromFixture("Page", "products"); $products = $this->objFromFixture("Page", "products");
$product1 = $this->objFromFixture("Page", "product1"); $product1 = $this->objFromFixture("Page", "product1");
$product4 = $this->objFromFixture("Page", "product4"); $product4 = $this->objFromFixture("Page", "product4");
// Test logged out users cannot edit
$this->logOut();
$this->assertFalse($staff->canEdit());
// Can't edit a page that is locked to admins // Can't edit a page that is locked to admins
$this->assertFalse($home->canEdit($editor)); $this->assertFalse($home->canEdit($editor));
// Can edit a page that is locked to editors // Can edit a page that is locked to editors
$this->assertTrue($products->canEdit($editor)); $this->assertTrue($products->canEdit($editor));
// Can edit a child of that page that inherits // Can edit a child of that page that inherits
$this->assertTrue($product1->canEdit($editor)); $this->assertTrue($product1->canEdit($editor));
// Can't edit a child of that page that has its permissions overridden // Can't edit a child of that page that has its permissions overridden
$this->assertFalse($product4->canEdit($editor)); $this->assertFalse($product4->canEdit($editor));
} }
public function testCanEditWithAccessToAllSections() {
$page = new Page();
$page->write();
$allSectionMember = $this->objFromFixture('Member', 'allsections');
$securityAdminMember = $this->objFromFixture('Member', 'securityadmin');
$this->assertTrue(SiteConfig::current_site_config()->canEditPages($allSectionMember));
$this->assertTrue($page->canEdit($allSectionMember));
$this->assertFalse($page->canEdit($securityAdminMember));
}
public function testCreatePermissions() {
// Test logged out users cannot create
$this->logOut();
$this->assertFalse(singleton('SiteTree')->canCreate());
// Login with another permission
$this->logInWithPermission('DUMMY');
$this->assertFalse(singleton('SiteTree')->canCreate());
// Login with basic CMS permission
$perms = SiteConfig::config()->required_permission;
$this->logInWithPermission(reset($perms));
$this->assertTrue(singleton('SiteTree')->canCreate());
// Test creation underneath a parent which this user doesn't have access to
$parent = $this->objFromFixture('Page', 'about');
$this->assertFalse(singleton('SiteTree')->canCreate(null, array('Parent' => $parent)));
// Test creation underneath a parent which doesn't allow a certain child
$parentB = new SiteTreeTest_ClassB();
$parentB->Title = 'Only Allows SiteTreeTest_ClassC';
$parentB->write();
$this->assertTrue(singleton('SiteTreeTest_ClassA')->canCreate(null));
$this->assertFalse(singleton('SiteTreeTest_ClassA')->canCreate(null, array('Parent' => $parentB)));
$this->assertTrue(singleton('SiteTreeTest_ClassC')->canCreate(null, array('Parent' => $parentB)));
}
public function testEditPermissionsOnDraftVsLive() { public function testEditPermissionsOnDraftVsLive() {
// Create an inherit-permission page // Create an inherit-permission page
@ -458,17 +507,17 @@ class SiteTreeTest extends SapphireTest {
$page->CanEditType = "Inherit"; $page->CanEditType = "Inherit";
$page->doPublish(); $page->doPublish();
$pageID = $page->ID; $pageID = $page->ID;
// Lock down the site config // Lock down the site config
$sc = $page->SiteConfig; $sc = $page->SiteConfig;
$sc->CanEditType = 'OnlyTheseUsers'; $sc->CanEditType = 'OnlyTheseUsers';
$sc->EditorGroups()->add($this->idFromFixture('Group', 'admins')); $sc->EditorGroups()->add($this->idFromFixture('Group', 'admins'));
$sc->write(); $sc->write();
// Confirm that Member.editor can't edit the page // Confirm that Member.editor can't edit the page
$this->objFromFixture('Member','editor')->logIn(); $this->objFromFixture('Member','editor')->logIn();
$this->assertFalse($page->canEdit()); $this->assertFalse($page->canEdit());
// Change the page to be editable by Group.editors, but do not publish // Change the page to be editable by Group.editors, but do not publish
$this->objFromFixture('Member','admin')->logIn(); $this->objFromFixture('Member','admin')->logIn();
$page->CanEditType = 'OnlyTheseUsers'; $page->CanEditType = 'OnlyTheseUsers';
@ -476,25 +525,25 @@ class SiteTreeTest extends SapphireTest {
$page->write(); $page->write();
// Clear permission cache // Clear permission cache
SiteTree::on_db_reset(); SiteTree::on_db_reset();
// Confirm that Member.editor can now edit the page // Confirm that Member.editor can now edit the page
$this->objFromFixture('Member','editor')->logIn(); $this->objFromFixture('Member','editor')->logIn();
$this->assertTrue($page->canEdit()); $this->assertTrue($page->canEdit());
// Publish the changes to the page // Publish the changes to the page
$this->objFromFixture('Member','admin')->logIn(); $this->objFromFixture('Member','admin')->logIn();
$page->doPublish(); $page->doPublish();
// Confirm that Member.editor can still edit the page // Confirm that Member.editor can still edit the page
$this->objFromFixture('Member','editor')->logIn(); $this->objFromFixture('Member','editor')->logIn();
$this->assertTrue($page->canEdit()); $this->assertTrue($page->canEdit());
} }
public function testCompareVersions() { public function testCompareVersions() {
// Necessary to avoid // Necessary to avoid
$oldCleanerClass = Diff::$html_cleaner_class; $oldCleanerClass = Diff::$html_cleaner_class;
Diff::$html_cleaner_class = 'SiteTreeTest_NullHtmlCleaner'; Diff::$html_cleaner_class = 'SiteTreeTest_NullHtmlCleaner';
$page = new Page(); $page = new Page();
$page->write(); $page->write();
$this->assertEquals(1, $page->Version); $this->assertEquals(1, $page->Version);
@ -505,14 +554,14 @@ class SiteTreeTest extends SapphireTest {
$page->Content = "<span>This is a test</span>"; $page->Content = "<span>This is a test</span>";
$page->write(); $page->write();
$this->assertEquals(2, $page->Version); $this->assertEquals(2, $page->Version);
$diff = $page->compareVersions(1, 2); $diff = $page->compareVersions(1, 2);
$processedContent = trim($diff->Content); $processedContent = trim($diff->Content);
$processedContent = preg_replace('/\s*</','<',$processedContent); $processedContent = preg_replace('/\s*</','<',$processedContent);
$processedContent = preg_replace('/>\s*/','>',$processedContent); $processedContent = preg_replace('/>\s*/','>',$processedContent);
$this->assertEquals("<ins><span>This is a test</span></ins>", $processedContent); $this->assertEquals("<ins><span>This is a test</span></ins>", $processedContent);
Diff::$html_cleaner_class = $oldCleanerClass; Diff::$html_cleaner_class = $oldCleanerClass;
} }
@ -530,51 +579,51 @@ class SiteTreeTest extends SapphireTest {
$about = $this->objFromFixture('Page','about'); $about = $this->objFromFixture('Page','about');
$about->Title = "Another title"; $about->Title = "Another title";
$about->write(); $about->write();
// Check the version created // Check the version created
$savedVersion = DB::query("SELECT \"AuthorID\", \"PublisherID\" FROM \"SiteTree_versions\" $savedVersion = DB::query("SELECT \"AuthorID\", \"PublisherID\" FROM \"SiteTree_versions\"
WHERE \"RecordID\" = $about->ID ORDER BY \"Version\" DESC")->first(); WHERE \"RecordID\" = $about->ID ORDER BY \"Version\" DESC")->first();
$this->assertEquals($memberID, $savedVersion['AuthorID']); $this->assertEquals($memberID, $savedVersion['AuthorID']);
$this->assertEquals(0, $savedVersion['PublisherID']); $this->assertEquals(0, $savedVersion['PublisherID']);
// Publish the page // Publish the page
$about->doPublish(); $about->doPublish();
$publishedVersion = DB::query("SELECT \"AuthorID\", \"PublisherID\" FROM \"SiteTree_versions\" $publishedVersion = DB::query("SELECT \"AuthorID\", \"PublisherID\" FROM \"SiteTree_versions\"
WHERE \"RecordID\" = $about->ID ORDER BY \"Version\" DESC")->first(); WHERE \"RecordID\" = $about->ID ORDER BY \"Version\" DESC")->first();
// Check the version created // Check the version created
$this->assertEquals($memberID, $publishedVersion['AuthorID']); $this->assertEquals($memberID, $publishedVersion['AuthorID']);
$this->assertEquals($memberID, $publishedVersion['PublisherID']); $this->assertEquals($memberID, $publishedVersion['PublisherID']);
} }
public function testLinkShortcodeHandler() { public function testLinkShortcodeHandler() {
$aboutPage = $this->objFromFixture('Page', 'about'); $aboutPage = $this->objFromFixture('Page', 'about');
$errorPage = $this->objFromFixture('ErrorPage', '404'); $errorPage = $this->objFromFixture('ErrorPage', '404');
$parser = new ShortcodeParser(); $parser = new ShortcodeParser();
$parser->register('sitetree_link', array('SiteTree', 'link_shortcode_handler')); $parser->register('sitetree_link', array('SiteTree', 'link_shortcode_handler'));
$aboutShortcode = sprintf('[sitetree_link,id=%d]', $aboutPage->ID); $aboutShortcode = sprintf('[sitetree_link,id=%d]', $aboutPage->ID);
$aboutEnclosed = sprintf('[sitetree_link,id=%d]Example Content[/sitetree_link]', $aboutPage->ID); $aboutEnclosed = sprintf('[sitetree_link,id=%d]Example Content[/sitetree_link]', $aboutPage->ID);
$aboutShortcodeExpected = $aboutPage->Link(); $aboutShortcodeExpected = $aboutPage->Link();
$aboutEnclosedExpected = sprintf('<a href="%s">Example Content</a>', $aboutPage->Link()); $aboutEnclosedExpected = sprintf('<a href="%s">Example Content</a>', $aboutPage->Link());
$this->assertEquals($aboutShortcodeExpected, $parser->parse($aboutShortcode), 'Test that simple linking works.'); $this->assertEquals($aboutShortcodeExpected, $parser->parse($aboutShortcode), 'Test that simple linking works.');
$this->assertEquals($aboutEnclosedExpected, $parser->parse($aboutEnclosed), 'Test enclosed content is linked.'); $this->assertEquals($aboutEnclosedExpected, $parser->parse($aboutEnclosed), 'Test enclosed content is linked.');
$aboutPage->delete(); $aboutPage->delete();
$this->assertEquals($aboutShortcodeExpected, $parser->parse($aboutShortcode), 'Test that deleted pages still link.'); $this->assertEquals($aboutShortcodeExpected, $parser->parse($aboutShortcode), 'Test that deleted pages still link.');
$this->assertEquals($aboutEnclosedExpected, $parser->parse($aboutEnclosed)); $this->assertEquals($aboutEnclosedExpected, $parser->parse($aboutEnclosed));
$aboutShortcode = '[sitetree_link,id="-1"]'; $aboutShortcode = '[sitetree_link,id="-1"]';
$aboutEnclosed = '[sitetree_link,id="-1"]Example Content[/sitetree_link]'; $aboutEnclosed = '[sitetree_link,id="-1"]Example Content[/sitetree_link]';
$aboutShortcodeExpected = $errorPage->Link(); $aboutShortcodeExpected = $errorPage->Link();
$aboutEnclosedExpected = sprintf('<a href="%s">Example Content</a>', $errorPage->Link()); $aboutEnclosedExpected = sprintf('<a href="%s">Example Content</a>', $errorPage->Link());
$this->assertEquals($aboutShortcodeExpected, $parser->parse($aboutShortcode), 'Test link to 404 page if no suitable matches.'); $this->assertEquals($aboutShortcodeExpected, $parser->parse($aboutShortcode), 'Test link to 404 page if no suitable matches.');
$this->assertEquals($aboutEnclosedExpected, $parser->parse($aboutEnclosed)); $this->assertEquals($aboutEnclosedExpected, $parser->parse($aboutEnclosed));
@ -582,50 +631,50 @@ class SiteTreeTest extends SapphireTest {
$this->assertEquals('', $parser->parse('[sitetree_link,id="text"]')); $this->assertEquals('', $parser->parse('[sitetree_link,id="text"]'));
$this->assertEquals('', $parser->parse('[sitetree_link]Example Content[/sitetree_link]')); $this->assertEquals('', $parser->parse('[sitetree_link]Example Content[/sitetree_link]'));
} }
public function testIsCurrent() { public function testIsCurrent() {
$aboutPage = $this->objFromFixture('Page', 'about'); $aboutPage = $this->objFromFixture('Page', 'about');
$errorPage = $this->objFromFixture('ErrorPage', '404'); $errorPage = $this->objFromFixture('ErrorPage', '404');
Director::set_current_page($aboutPage); Director::set_current_page($aboutPage);
$this->assertTrue($aboutPage->isCurrent(), 'Assert that basic isSection checks works.'); $this->assertTrue($aboutPage->isCurrent(), 'Assert that basic isSection checks works.');
$this->assertFalse($errorPage->isCurrent()); $this->assertFalse($errorPage->isCurrent());
Director::set_current_page($errorPage); Director::set_current_page($errorPage);
$this->assertTrue($errorPage->isCurrent(), 'Assert isSection works on error pages.'); $this->assertTrue($errorPage->isCurrent(), 'Assert isSection works on error pages.');
$this->assertFalse($aboutPage->isCurrent()); $this->assertFalse($aboutPage->isCurrent());
Director::set_current_page($aboutPage); Director::set_current_page($aboutPage);
$this->assertTrue ( $this->assertTrue (
DataObject::get_one('SiteTree', '"Title" = \'About Us\'')->isCurrent(), DataObject::get_one('SiteTree', '"Title" = \'About Us\'')->isCurrent(),
'Assert that isCurrent works on another instance with the same ID.' 'Assert that isCurrent works on another instance with the same ID.'
); );
Director::set_current_page($newPage = new SiteTree()); Director::set_current_page($newPage = new SiteTree());
$this->assertTrue($newPage->isCurrent(), 'Assert that isCurrent works on unsaved pages.'); $this->assertTrue($newPage->isCurrent(), 'Assert that isCurrent works on unsaved pages.');
} }
public function testIsSection() { public function testIsSection() {
$about = $this->objFromFixture('Page', 'about'); $about = $this->objFromFixture('Page', 'about');
$staff = $this->objFromFixture('Page', 'staff'); $staff = $this->objFromFixture('Page', 'staff');
$ceo = $this->objFromFixture('Page', 'ceo'); $ceo = $this->objFromFixture('Page', 'ceo');
Director::set_current_page($about); Director::set_current_page($about);
$this->assertTrue($about->isSection()); $this->assertTrue($about->isSection());
$this->assertFalse($staff->isSection()); $this->assertFalse($staff->isSection());
$this->assertFalse($ceo->isSection()); $this->assertFalse($ceo->isSection());
Director::set_current_page($staff); Director::set_current_page($staff);
$this->assertTrue($about->isSection()); $this->assertTrue($about->isSection());
$this->assertTrue($staff->isSection()); $this->assertTrue($staff->isSection());
$this->assertFalse($ceo->isSection()); $this->assertFalse($ceo->isSection());
Director::set_current_page($ceo); Director::set_current_page($ceo);
$this->assertTrue($about->isSection()); $this->assertTrue($about->isSection());
$this->assertTrue($staff->isSection()); $this->assertTrue($staff->isSection());
$this->assertTrue($ceo->isSection()); $this->assertTrue($ceo->isSection());
} }
/** /**
* @covers SiteTree::validURLSegment * @covers SiteTree::validURLSegment
*/ */
@ -637,7 +686,7 @@ class SiteTreeTest extends SapphireTest {
$this->assertFalse($sitetree->validURLSegment(), 'URLSegment conflicts are recognised'); $this->assertFalse($sitetree->validURLSegment(), 'URLSegment conflicts are recognised');
$sitetree->URLSegment = 'home-noconflict'; $sitetree->URLSegment = 'home-noconflict';
$this->assertTrue($sitetree->validURLSegment()); $this->assertTrue($sitetree->validURLSegment());
$sitetree->ParentID = $this->idFromFixture('Page', 'about'); $sitetree->ParentID = $this->idFromFixture('Page', 'about');
$sitetree->URLSegment = 'home'; $sitetree->URLSegment = 'home';
$this->assertFalse($sitetree->validURLSegment(), 'Conflicts are still recognised with a ParentID value'); $this->assertFalse($sitetree->validURLSegment(), 'Conflicts are still recognised with a ParentID value');
@ -647,26 +696,26 @@ class SiteTreeTest extends SapphireTest {
$sitetree->ParentID = 0; $sitetree->ParentID = 0;
$sitetree->URLSegment = 'home'; $sitetree->URLSegment = 'home';
$this->assertFalse($sitetree->validURLSegment(), 'URLSegment conflicts are recognised'); $this->assertFalse($sitetree->validURLSegment(), 'URLSegment conflicts are recognised');
$sitetree->ParentID = $this->idFromFixture('Page', 'about'); $sitetree->ParentID = $this->idFromFixture('Page', 'about');
$this->assertTrue($sitetree->validURLSegment(), 'URLSegments can be the same across levels'); $this->assertTrue($sitetree->validURLSegment(), 'URLSegments can be the same across levels');
$sitetree->URLSegment = 'my-staff'; $sitetree->URLSegment = 'my-staff';
$this->assertFalse($sitetree->validURLSegment(), 'Nested URLSegment conflicts are recognised'); $this->assertFalse($sitetree->validURLSegment(), 'Nested URLSegment conflicts are recognised');
$sitetree->URLSegment = 'my-staff-noconflict'; $sitetree->URLSegment = 'my-staff-noconflict';
$this->assertTrue($sitetree->validURLSegment()); $this->assertTrue($sitetree->validURLSegment());
} }
/** /**
* @covers SiteTree::validURLSegment * @covers SiteTree::validURLSegment
*/ */
public function testValidURLSegmentClassNameConflicts() { public function testValidURLSegmentClassNameConflicts() {
$sitetree = new SiteTree(); $sitetree = new SiteTree();
$sitetree->URLSegment = 'Controller'; $sitetree->URLSegment = 'Controller';
$this->assertFalse($sitetree->validURLSegment(), 'Class name conflicts are recognised'); $this->assertFalse($sitetree->validURLSegment(), 'Class name conflicts are recognised');
} }
/** /**
* @covers SiteTree::validURLSegment * @covers SiteTree::validURLSegment
*/ */
@ -675,16 +724,16 @@ class SiteTreeTest extends SapphireTest {
$sitetree = new SiteTree(); $sitetree = new SiteTree();
$sitetree->ParentID = $this->idFromFixture('SiteTreeTest_Conflicted', 'parent'); $sitetree->ParentID = $this->idFromFixture('SiteTreeTest_Conflicted', 'parent');
$sitetree->URLSegment = 'index'; $sitetree->URLSegment = 'index';
$this->assertFalse($sitetree->validURLSegment(), 'index is not a valid URLSegment'); $this->assertFalse($sitetree->validURLSegment(), 'index is not a valid URLSegment');
$sitetree->URLSegment = 'conflicted-action'; $sitetree->URLSegment = 'conflicted-action';
$this->assertFalse($sitetree->validURLSegment(), 'allowed_actions conflicts are recognised'); $this->assertFalse($sitetree->validURLSegment(), 'allowed_actions conflicts are recognised');
$sitetree->URLSegment = 'conflicted-template'; $sitetree->URLSegment = 'conflicted-template';
$this->assertFalse($sitetree->validURLSegment(), 'Action-specific template conflicts are recognised'); $this->assertFalse($sitetree->validURLSegment(), 'Action-specific template conflicts are recognised');
$sitetree->URLSegment = 'valid'; $sitetree->URLSegment = 'valid';
$this->assertTrue($sitetree->validURLSegment(), 'Valid URLSegment values are allowed'); $this->assertTrue($sitetree->validURLSegment(), 'Valid URLSegment values are allowed');
} }
@ -708,13 +757,13 @@ class SiteTreeTest extends SapphireTest {
URLSegmentFilter::$default_allow_multibyte = $origAllow; URLSegmentFilter::$default_allow_multibyte = $origAllow;
} }
public function testVersionsAreCreated() { public function testVersionsAreCreated() {
$p = new Page(); $p = new Page();
$p->Content = "one"; $p->Content = "one";
$p->write(); $p->write();
$this->assertEquals(1, $p->Version); $this->assertEquals(1, $p->Version);
// No changes don't bump version // No changes don't bump version
$p->write(); $p->write();
$this->assertEquals(1, $p->Version); $this->assertEquals(1, $p->Version);
@ -735,45 +784,45 @@ class SiteTreeTest extends SapphireTest {
$this->assertEquals(3, $p->Version); $this->assertEquals(3, $p->Version);
} }
public function testPageTypeClasses() { public function testPageTypeClasses() {
$classes = SiteTree::page_type_classes(); $classes = SiteTree::page_type_classes();
$this->assertNotContains('SiteTree', $classes, 'Page types do not include base class'); $this->assertNotContains('SiteTree', $classes, 'Page types do not include base class');
$this->assertContains('Page', $classes, 'Page types do contain subclasses'); $this->assertContains('Page', $classes, 'Page types do contain subclasses');
} }
public function testAllowedChildren() { public function testAllowedChildren() {
$page = new SiteTree(); $page = new SiteTree();
$this->assertContains( $this->assertContains(
'VirtualPage', 'VirtualPage',
$page->allowedChildren(), $page->allowedChildren(),
'Includes core subclasses by default' 'Includes core subclasses by default'
); );
$classA = new SiteTreeTest_ClassA(); $classA = new SiteTreeTest_ClassA();
$this->assertEquals( $this->assertEquals(
array('SiteTreeTest_ClassB'), array('SiteTreeTest_ClassB'),
$classA->allowedChildren(), $classA->allowedChildren(),
'Direct setting of allowed children' 'Direct setting of allowed children'
); );
$classB = new SiteTreeTest_ClassB(); $classB = new SiteTreeTest_ClassB();
$this->assertEquals( $this->assertEquals(
array('SiteTreeTest_ClassC', 'SiteTreeTest_ClassCext'), array('SiteTreeTest_ClassC', 'SiteTreeTest_ClassCext'),
$classB->allowedChildren(), $classB->allowedChildren(),
'Includes subclasses' 'Includes subclasses'
); );
$classD = new SiteTreeTest_ClassD(); $classD = new SiteTreeTest_ClassD();
$this->assertEquals( $this->assertEquals(
array('SiteTreeTest_ClassC'), array('SiteTreeTest_ClassC'),
$classD->allowedChildren(), $classD->allowedChildren(),
'Excludes subclasses if class is prefixed by an asterisk' 'Excludes subclasses if class is prefixed by an asterisk'
); );
$classC = new SiteTreeTest_ClassC(); $classC = new SiteTreeTest_ClassC();
$this->assertEquals( $this->assertEquals(
array(), array(),
$classC->allowedChildren(), $classC->allowedChildren(),
'Null setting' 'Null setting'
); );
@ -792,11 +841,11 @@ class SiteTreeTest extends SapphireTest {
$classD->write(); $classD->write();
$classCext = new SiteTreeTest_ClassCext(); $classCext = new SiteTreeTest_ClassCext();
$classCext->write(); $classCext->write();
$classB->ParentID = $page->ID; $classB->ParentID = $page->ID;
$valid = $classB->validate(); $valid = $classB->validate();
$this->assertTrue($valid->valid(), "Does allow children on unrestricted parent"); $this->assertTrue($valid->valid(), "Does allow children on unrestricted parent");
$classB->ParentID = $classA->ID; $classB->ParentID = $classA->ID;
$valid = $classB->validate(); $valid = $classB->validate();
$this->assertTrue($valid->valid(), "Does allow child specifically allowed by parent"); $this->assertTrue($valid->valid(), "Does allow child specifically allowed by parent");
@ -804,20 +853,20 @@ class SiteTreeTest extends SapphireTest {
$classC->ParentID = $classA->ID; $classC->ParentID = $classA->ID;
$valid = $classC->validate(); $valid = $classC->validate();
$this->assertFalse($valid->valid(), "Doesnt allow child on parents specifically restricting children"); $this->assertFalse($valid->valid(), "Doesnt allow child on parents specifically restricting children");
$classB->ParentID = $classC->ID; $classB->ParentID = $classC->ID;
$valid = $classB->validate(); $valid = $classB->validate();
$this->assertFalse($valid->valid(), "Doesnt allow child on parents disallowing all children"); $this->assertFalse($valid->valid(), "Doesnt allow child on parents disallowing all children");
$classB->ParentID = $classC->ID; $classB->ParentID = $classC->ID;
$valid = $classB->validate(); $valid = $classB->validate();
$this->assertFalse($valid->valid(), "Doesnt allow child on parents disallowing all children"); $this->assertFalse($valid->valid(), "Doesnt allow child on parents disallowing all children");
$classCext->ParentID = $classD->ID; $classCext->ParentID = $classD->ID;
$valid = $classCext->validate(); $valid = $classCext->validate();
$this->assertFalse($valid->valid(), "Doesnt allow child where only parent class is allowed on parent node, and asterisk prefixing is used"); $this->assertFalse($valid->valid(), "Doesnt allow child where only parent class is allowed on parent node, and asterisk prefixing is used");
} }
public function testClassDropdown() { public function testClassDropdown() {
$sitetree = new SiteTree(); $sitetree = new SiteTree();
$method = new ReflectionMethod($sitetree, 'getClassDropdown'); $method = new ReflectionMethod($sitetree, 'getClassDropdown');
@ -825,13 +874,13 @@ class SiteTreeTest extends SapphireTest {
Session::set("loggedInAs", null); Session::set("loggedInAs", null);
$this->assertArrayNotHasKey('SiteTreeTest_ClassA', $method->invoke($sitetree)); $this->assertArrayNotHasKey('SiteTreeTest_ClassA', $method->invoke($sitetree));
$this->loginWithPermission('ADMIN'); $this->loginWithPermission('ADMIN');
$this->assertArrayHasKey('SiteTreeTest_ClassA', $method->invoke($sitetree)); $this->assertArrayHasKey('SiteTreeTest_ClassA', $method->invoke($sitetree));
$this->loginWithPermission('CMS_ACCESS_CMSMain'); $this->loginWithPermission('CMS_ACCESS_CMSMain');
$this->assertArrayHasKey('SiteTreeTest_ClassA', $method->invoke($sitetree)); $this->assertArrayHasKey('SiteTreeTest_ClassA', $method->invoke($sitetree));
Session::set("loggedInAs", null); Session::set("loggedInAs", null);
} }
@ -844,14 +893,14 @@ class SiteTreeTest extends SapphireTest {
$notRootPage->ParentID = 0; $notRootPage->ParentID = 0;
$isDetected = false; $isDetected = false;
try { try {
$notRootPage->write(); $notRootPage->write();
} catch(ValidationException $e) { } catch(ValidationException $e) {
$this->assertContains('is not allowed on the root level', $e->getMessage()); $this->assertContains('is not allowed on the root level', $e->getMessage());
$isDetected = true; $isDetected = true;
} }
if(!$isDetected) $this->fail('Fails validation with $can_be_root=false'); if(!$isDetected) $this->fail('Fails validation with $can_be_root=false');
} }
public function testModifyStatusFlagByInheritance(){ public function testModifyStatusFlagByInheritance(){
$node = new SiteTreeTest_StageStatusInherit(); $node = new SiteTreeTest_StageStatusInherit();
@ -865,7 +914,7 @@ class SiteTreeTest extends SapphireTest {
$page->Title = 'orig'; $page->Title = 'orig';
$page->MenuTitle = 'orig'; $page->MenuTitle = 'orig';
$page->write(); $page->write();
// change menu title // change menu title
$page->MenuTitle = 'changed'; $page->MenuTitle = 'changed';
$page->write(); $page->write();
@ -879,7 +928,7 @@ class SiteTreeTest extends SapphireTest {
$this->assertEquals(null, $page->getField('MenuTitle')); $this->assertEquals(null, $page->getField('MenuTitle'));
} }
} }
/**#@+ /**#@+
* @ignore * @ignore

View File

@ -1,82 +1,104 @@
SiteConfig:
default:
Title: My test site
Tagline: Default site config
CanViewType: Anyone
CanEditType: LoggedInUsers
CanCreateTopLevelType: LoggedInUsers
Group: Group:
editors: editors:
Title: Editors Title: Editors
admins: admins:
Title: Administrators Title: Administrators
allsections:
Title: All Section Editors
securityadmins:
Title: Security Admins
Permission: Permission:
admins: admins:
Code: ADMIN Code: ADMIN
Group: =>Group.admins Group: =>Group.admins
editors: editors:
Code: CMS_ACCESS_CMSMain Code: CMS_ACCESS_CMSMain
Group: =>Group.editors Group: =>Group.editors
allsections:
Code: CMS_ACCESS_CMSMain
Group: =>Group.allsections
securityadmins:
Code: CMS_ACCESS_SecurityAdmin
Group: =>Group.securityadmins
Member: Member:
editor: editor:
FirstName: Test FirstName: Test
Surname: Editor Surname: Editor
Groups: =>Group.editors Groups: =>Group.editors
admin: admin:
FirstName: Test FirstName: Test
Surname: Administrator Surname: Administrator
Groups: =>Group.admins Groups: =>Group.admins
allsections:
Groups: =>Group.allsections
securityadmin:
Groups: =>Group.securityadmins
Page: Page:
home: home:
Title: Home Title: Home
CanEditType: OnlyTheseUsers CanEditType: OnlyTheseUsers
EditorGroups: =>Group.admins EditorGroups: =>Group.admins
about: about:
Title: About Us Title: About Us
CanEditType: OnlyTheseUsers CanEditType: OnlyTheseUsers
EditorGroups: =>Group.admins EditorGroups: =>Group.admins
staff: staff:
Title: Staff Title: Staff
URLSegment: my-staff URLSegment: my-staff
Parent: =>Page.about Parent: =>Page.about
ceo: ceo:
Title: CEO Title: CEO
Parent: =>Page.staff Parent: =>Page.staff
staffduplicate: staffduplicate:
Title: Staff Title: Staff
URLSegment: my-staff URLSegment: my-staff
Parent: =>Page.about Parent: =>Page.about
products: products:
Title: Products Title: Products
CanEditType: OnlyTheseUsers CanEditType: OnlyTheseUsers
EditorGroups: =>Group.editors EditorGroups: =>Group.editors
product1: product1:
Title: 1.1 Test Product Title: 1.1 Test Product
Parent: =>Page.products Parent: =>Page.products
CanEditType: Inherit CanEditType: Inherit
product2: product2:
Title: Another Product Title: Another Product
Parent: =>Page.products Parent: =>Page.products
CanEditType: Inherit CanEditType: Inherit
product3: product3:
Title: Another Product Title: Another Product
Parent: =>Page.products Parent: =>Page.products
CanEditType: Inherit CanEditType: Inherit
product4: product4:
Title: Another Product Title: Another Product
Parent: =>Page.products Parent: =>Page.products
CanEditType: OnlyTheseUsers CanEditType: OnlyTheseUsers
EditorGroups: =>Group.admins EditorGroups: =>Group.admins
contact: contact:
Title: Contact Us Title: Contact Us
object: object:
Title: Object Title: Object
controller: controller:
Title: Controller Title: Controller
numericonly: numericonly:
Title: 1930 Title: 1930
SiteTreeTest_Conflicted: SiteTreeTest_Conflicted:
parent: parent:
Title: Parent Title: Parent
ErrorPage: ErrorPage:
404: 404:
Title: Page not Found Title: Page not Found
ErrorCode: 404 ErrorCode: 404