2007-07-19 12:40:28 +02:00
|
|
|
<?php
|
2016-06-15 06:03:16 +02:00
|
|
|
|
2016-06-23 01:37:22 +02:00
|
|
|
namespace SilverStripe\Security;
|
|
|
|
|
2016-08-11 01:40:23 +02:00
|
|
|
use SilverStripe\Admin\SecurityAdmin;
|
2016-08-19 00:51:35 +02:00
|
|
|
use SilverStripe\Core\Convert;
|
|
|
|
use SilverStripe\Forms\DropdownField;
|
|
|
|
use SilverStripe\Forms\FieldList;
|
2017-06-09 05:07:35 +02:00
|
|
|
use SilverStripe\Forms\Form;
|
|
|
|
use SilverStripe\Forms\GridField\GridField;
|
|
|
|
use SilverStripe\Forms\GridField\GridFieldAddExistingAutocompleter;
|
2016-08-19 00:51:35 +02:00
|
|
|
use SilverStripe\Forms\GridField\GridFieldButtonRow;
|
2017-06-09 05:07:35 +02:00
|
|
|
use SilverStripe\Forms\GridField\GridFieldConfig_RelationEditor;
|
2017-10-26 23:49:38 +02:00
|
|
|
use SilverStripe\Forms\GridField\GridFieldDeleteAction;
|
2017-06-09 05:07:35 +02:00
|
|
|
use SilverStripe\Forms\GridField\GridFieldDetailForm;
|
2016-08-19 00:51:35 +02:00
|
|
|
use SilverStripe\Forms\GridField\GridFieldExportButton;
|
2017-10-26 23:49:38 +02:00
|
|
|
use SilverStripe\Forms\GridField\GridFieldGroupDeleteAction;
|
|
|
|
use SilverStripe\Forms\GridField\GridFieldPageCount;
|
2016-08-19 00:51:35 +02:00
|
|
|
use SilverStripe\Forms\GridField\GridFieldPrintButton;
|
2017-06-09 05:07:35 +02:00
|
|
|
use SilverStripe\Forms\HiddenField;
|
|
|
|
use SilverStripe\Forms\HTMLEditor\HTMLEditorConfig;
|
|
|
|
use SilverStripe\Forms\ListboxField;
|
|
|
|
use SilverStripe\Forms\LiteralField;
|
|
|
|
use SilverStripe\Forms\Tab;
|
|
|
|
use SilverStripe\Forms\TabSet;
|
|
|
|
use SilverStripe\Forms\TextareaField;
|
|
|
|
use SilverStripe\Forms\TextField;
|
2016-06-15 06:03:16 +02:00
|
|
|
use SilverStripe\ORM\ArrayList;
|
|
|
|
use SilverStripe\ORM\DataObject;
|
2016-08-19 00:51:35 +02:00
|
|
|
use SilverStripe\ORM\DataQuery;
|
2016-06-23 01:37:22 +02:00
|
|
|
use SilverStripe\ORM\HasManyList;
|
2016-08-19 00:51:35 +02:00
|
|
|
use SilverStripe\ORM\Hierarchy\Hierarchy;
|
2016-06-23 01:37:22 +02:00
|
|
|
use SilverStripe\ORM\ManyManyList;
|
2016-06-15 06:03:16 +02:00
|
|
|
use SilverStripe\ORM\UnsavedRelationList;
|
2016-06-23 01:37:22 +02:00
|
|
|
|
2008-02-25 03:10:37 +01:00
|
|
|
/**
|
|
|
|
* A security group.
|
2014-08-15 08:53:05 +02:00
|
|
|
*
|
2017-06-15 04:20:12 +02:00
|
|
|
* @property string $Title Name of the group
|
|
|
|
* @property string $Description Description of the group
|
|
|
|
* @property string $Code Group code
|
|
|
|
* @property string $Locked Boolean indicating whether group is locked in security panel
|
|
|
|
* @property int $Sort
|
2014-01-26 04:17:17 +01:00
|
|
|
* @property string HtmlEditorConfig
|
|
|
|
*
|
2017-06-15 04:20:12 +02:00
|
|
|
* @property int $ParentID ID of parent group
|
2014-01-26 04:17:17 +01:00
|
|
|
*
|
|
|
|
* @method Group Parent() Return parent group
|
|
|
|
* @method HasManyList Permissions() List of group permissions
|
|
|
|
* @method HasManyList Groups() List of child groups
|
|
|
|
* @method ManyManyList Roles() List of PermissionRoles
|
2016-08-19 00:51:35 +02:00
|
|
|
* @mixin Hierarchy
|
2008-02-25 03:10:37 +01:00
|
|
|
*/
|
2016-11-29 00:31:16 +01:00
|
|
|
class Group extends DataObject
|
|
|
|
{
|
|
|
|
|
2016-11-23 06:09:10 +01:00
|
|
|
private static $db = array(
|
|
|
|
"Title" => "Varchar(255)",
|
|
|
|
"Description" => "Text",
|
|
|
|
"Code" => "Varchar(255)",
|
|
|
|
"Locked" => "Boolean",
|
|
|
|
"Sort" => "Int",
|
|
|
|
"HtmlEditorConfig" => "Text"
|
|
|
|
);
|
|
|
|
|
|
|
|
private static $has_one = array(
|
2017-05-11 11:07:27 +02:00
|
|
|
"Parent" => Group::class,
|
2016-11-23 06:09:10 +01:00
|
|
|
);
|
|
|
|
|
|
|
|
private static $has_many = array(
|
2017-05-11 11:07:27 +02:00
|
|
|
"Permissions" => Permission::class,
|
|
|
|
"Groups" => Group::class,
|
2016-11-23 06:09:10 +01:00
|
|
|
);
|
|
|
|
|
|
|
|
private static $many_many = array(
|
2017-05-11 11:07:27 +02:00
|
|
|
"Members" => Member::class,
|
|
|
|
"Roles" => PermissionRole::class,
|
2016-11-23 06:09:10 +01:00
|
|
|
);
|
|
|
|
|
|
|
|
private static $extensions = array(
|
2017-05-11 11:07:27 +02:00
|
|
|
Hierarchy::class,
|
2016-11-23 06:09:10 +01:00
|
|
|
);
|
|
|
|
|
|
|
|
private static $table_name = "Group";
|
2016-11-29 00:31:16 +01:00
|
|
|
|
|
|
|
public function populateDefaults()
|
|
|
|
{
|
2016-11-23 06:09:10 +01:00
|
|
|
parent::populateDefaults();
|
2016-11-29 00:31:16 +01:00
|
|
|
|
|
|
|
if (!$this->Title) {
|
2018-01-16 19:39:30 +01:00
|
|
|
$this->Title = _t(__CLASS__ . '.NEWGROUP', "New Group");
|
2016-11-29 00:31:16 +01:00
|
|
|
}
|
2016-11-23 06:09:10 +01:00
|
|
|
}
|
2016-11-29 00:31:16 +01:00
|
|
|
|
|
|
|
public function getAllChildren()
|
|
|
|
{
|
2016-11-23 06:09:10 +01:00
|
|
|
$doSet = new ArrayList();
|
|
|
|
|
|
|
|
$children = Group::get()->filter("ParentID", $this->ID);
|
2017-06-09 05:07:35 +02:00
|
|
|
/** @var Group $child */
|
2016-11-23 06:09:10 +01:00
|
|
|
foreach ($children as $child) {
|
|
|
|
$doSet->push($child);
|
|
|
|
$doSet->merge($child->getAllChildren());
|
|
|
|
}
|
|
|
|
|
|
|
|
return $doSet;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Caution: Only call on instances, not through a singleton.
|
|
|
|
* The "root group" fields will be created through {@link SecurityAdmin->EditForm()}.
|
|
|
|
*
|
2017-07-03 02:21:27 +02:00
|
|
|
* @skipUpgrade
|
2016-11-23 06:09:10 +01:00
|
|
|
* @return FieldList
|
|
|
|
*/
|
2016-11-29 00:31:16 +01:00
|
|
|
public function getCMSFields()
|
|
|
|
{
|
2016-11-23 06:09:10 +01:00
|
|
|
$fields = new FieldList(
|
2016-11-29 00:31:16 +01:00
|
|
|
new TabSet(
|
|
|
|
"Root",
|
|
|
|
new Tab(
|
|
|
|
'Members',
|
2018-01-16 19:39:30 +01:00
|
|
|
_t(__CLASS__ . '.MEMBERS', 'Members'),
|
2016-11-23 06:09:10 +01:00
|
|
|
new TextField("Title", $this->fieldLabel('Title')),
|
2016-11-29 00:31:16 +01:00
|
|
|
$parentidfield = DropdownField::create(
|
|
|
|
'ParentID',
|
2016-11-23 06:09:10 +01:00
|
|
|
$this->fieldLabel('Parent'),
|
|
|
|
Group::get()->exclude('ID', $this->ID)->map('ID', 'Breadcrumbs')
|
|
|
|
)->setEmptyString(' '),
|
|
|
|
new TextareaField('Description', $this->fieldLabel('Description'))
|
|
|
|
),
|
2016-11-29 00:31:16 +01:00
|
|
|
$permissionsTab = new Tab(
|
|
|
|
'Permissions',
|
2018-01-16 19:39:30 +01:00
|
|
|
_t(__CLASS__ . '.PERMISSIONS', 'Permissions'),
|
2016-11-23 06:09:10 +01:00
|
|
|
$permissionsField = new PermissionCheckboxSetField(
|
|
|
|
'Permissions',
|
|
|
|
false,
|
2017-05-11 11:07:27 +02:00
|
|
|
Permission::class,
|
2016-11-23 06:09:10 +01:00
|
|
|
'GroupID',
|
|
|
|
$this
|
|
|
|
)
|
|
|
|
)
|
|
|
|
)
|
|
|
|
);
|
|
|
|
|
|
|
|
$parentidfield->setDescription(
|
2017-04-20 03:15:24 +02:00
|
|
|
_t('SilverStripe\\Security\\Group.GroupReminder', 'If you choose a parent group, this group will take all it\'s roles')
|
2016-11-23 06:09:10 +01:00
|
|
|
);
|
|
|
|
|
|
|
|
if ($this->ID) {
|
|
|
|
$group = $this;
|
|
|
|
$config = GridFieldConfig_RelationEditor::create();
|
|
|
|
$config->addComponent(new GridFieldButtonRow('after'));
|
|
|
|
$config->addComponents(new GridFieldExportButton('buttons-after-left'));
|
|
|
|
$config->addComponents(new GridFieldPrintButton('buttons-after-left'));
|
2017-10-26 23:49:38 +02:00
|
|
|
$config->removeComponentsByType(GridFieldDeleteAction::class);
|
|
|
|
$config->addComponent(new GridFieldGroupDeleteAction($this->ID), GridFieldPageCount::class);
|
|
|
|
|
2016-11-23 06:09:10 +01:00
|
|
|
/** @var GridFieldAddExistingAutocompleter $autocompleter */
|
2017-07-03 02:21:27 +02:00
|
|
|
$autocompleter = $config->getComponentByType(GridFieldAddExistingAutocompleter::class);
|
2016-11-23 06:09:10 +01:00
|
|
|
/** @skipUpgrade */
|
|
|
|
$autocompleter
|
|
|
|
->setResultsFormat('$Title ($Email)')
|
|
|
|
->setSearchFields(array('FirstName', 'Surname', 'Email'));
|
|
|
|
/** @var GridFieldDetailForm $detailForm */
|
2017-05-11 11:07:27 +02:00
|
|
|
$detailForm = $config->getComponentByType(GridFieldDetailForm::class);
|
2016-11-23 06:09:10 +01:00
|
|
|
$detailForm
|
|
|
|
->setValidator(Member_Validator::create())
|
2017-06-09 05:07:35 +02:00
|
|
|
->setItemEditFormCallback(function ($form) use ($group) {
|
2016-11-23 06:09:10 +01:00
|
|
|
/** @var Form $form */
|
|
|
|
$record = $form->getRecord();
|
|
|
|
$groupsField = $form->Fields()->dataFieldByName('DirectGroups');
|
|
|
|
if ($groupsField) {
|
|
|
|
// If new records are created in a group context,
|
|
|
|
// set this group by default.
|
|
|
|
if ($record && !$record->ID) {
|
|
|
|
$groupsField->setValue($group->ID);
|
|
|
|
} elseif ($record && $record->ID) {
|
|
|
|
// TODO Mark disabled once chosen.js supports it
|
|
|
|
// $groupsField->setDisabledItems(array($group->ID));
|
2016-11-29 00:31:16 +01:00
|
|
|
$form->Fields()->replaceField(
|
|
|
|
'DirectGroups',
|
|
|
|
$groupsField->performReadonlyTransformation()
|
|
|
|
);
|
2016-11-23 06:09:10 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
$memberList = GridField::create('Members', false, $this->DirectMembers(), $config)
|
|
|
|
->addExtraClass('members_grid');
|
|
|
|
// @todo Implement permission checking on GridField
|
|
|
|
//$memberList->setPermissions(array('edit', 'delete', 'export', 'add', 'inlineadd'));
|
|
|
|
$fields->addFieldToTab('Root.Members', $memberList);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Only add a dropdown for HTML editor configurations if more than one is available.
|
|
|
|
// Otherwise Member->getHtmlEditorConfigForCMS() will default to the 'cms' configuration.
|
|
|
|
$editorConfigMap = HTMLEditorConfig::get_available_configs_map();
|
|
|
|
if (count($editorConfigMap) > 1) {
|
2016-11-29 00:31:16 +01:00
|
|
|
$fields->addFieldToTab(
|
|
|
|
'Root.Permissions',
|
2016-11-23 06:09:10 +01:00
|
|
|
new DropdownField(
|
|
|
|
'HtmlEditorConfig',
|
|
|
|
'HTML Editor Configuration',
|
|
|
|
$editorConfigMap
|
|
|
|
),
|
|
|
|
'Permissions'
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!Permission::check('EDIT_PERMISSIONS')) {
|
|
|
|
$fields->removeFieldFromTab('Root', 'Permissions');
|
|
|
|
}
|
|
|
|
|
|
|
|
// Only show the "Roles" tab if permissions are granted to edit them,
|
|
|
|
// and at least one role exists
|
2017-03-10 04:43:10 +01:00
|
|
|
if (Permission::check('APPLY_ROLES') &&
|
|
|
|
PermissionRole::get()->count() &&
|
|
|
|
class_exists(SecurityAdmin::class)
|
|
|
|
) {
|
2018-01-16 19:39:30 +01:00
|
|
|
$fields->findOrMakeTab('Root.Roles', _t(__CLASS__ . '.ROLES', 'Roles'));
|
2016-11-29 00:31:16 +01:00
|
|
|
$fields->addFieldToTab(
|
|
|
|
'Root.Roles',
|
2016-11-23 06:09:10 +01:00
|
|
|
new LiteralField(
|
|
|
|
"",
|
|
|
|
"<p>" .
|
|
|
|
_t(
|
2018-01-16 19:39:30 +01:00
|
|
|
__CLASS__ . '.ROLESDESCRIPTION',
|
2016-11-23 06:09:10 +01:00
|
|
|
"Roles are predefined sets of permissions, and can be assigned to groups.<br />"
|
|
|
|
. "They are inherited from parent groups if required."
|
|
|
|
) . '<br />' .
|
|
|
|
sprintf(
|
|
|
|
'<a href="%s" class="add-role">%s</a>',
|
|
|
|
SecurityAdmin::singleton()->Link('show/root#Root_Roles'),
|
|
|
|
// TODO This should include #Root_Roles to switch directly to the tab,
|
|
|
|
// but tabstrip.js doesn't display tabs when directly adressed through a URL pragma
|
2017-04-20 03:15:24 +02:00
|
|
|
_t('SilverStripe\\Security\\Group.RolesAddEditLink', 'Manage roles')
|
2016-11-23 06:09:10 +01:00
|
|
|
) .
|
|
|
|
"</p>"
|
|
|
|
)
|
|
|
|
);
|
|
|
|
|
|
|
|
// Add roles (and disable all checkboxes for inherited roles)
|
|
|
|
$allRoles = PermissionRole::get();
|
|
|
|
if (!Permission::check('ADMIN')) {
|
|
|
|
$allRoles = $allRoles->filter("OnlyAdminCanApply", 0);
|
|
|
|
}
|
|
|
|
if ($this->ID) {
|
|
|
|
$groupRoles = $this->Roles();
|
|
|
|
$inheritedRoles = new ArrayList();
|
|
|
|
$ancestors = $this->getAncestors();
|
|
|
|
foreach ($ancestors as $ancestor) {
|
|
|
|
$ancestorRoles = $ancestor->Roles();
|
2016-11-29 00:31:16 +01:00
|
|
|
if ($ancestorRoles) {
|
|
|
|
$inheritedRoles->merge($ancestorRoles);
|
2016-11-23 06:09:10 +01:00
|
|
|
}
|
2016-11-29 00:31:16 +01:00
|
|
|
}
|
2016-11-23 06:09:10 +01:00
|
|
|
$groupRoleIDs = $groupRoles->column('ID') + $inheritedRoles->column('ID');
|
|
|
|
$inheritedRoleIDs = $inheritedRoles->column('ID');
|
|
|
|
} else {
|
|
|
|
$groupRoleIDs = array();
|
|
|
|
$inheritedRoleIDs = array();
|
|
|
|
}
|
|
|
|
|
|
|
|
$rolesField = ListboxField::create('Roles', false, $allRoles->map()->toArray())
|
|
|
|
->setDefaultItems($groupRoleIDs)
|
2017-04-20 03:15:24 +02:00
|
|
|
->setAttribute('data-placeholder', _t('SilverStripe\\Security\\Group.AddRole', 'Add a role for this group'))
|
2016-11-23 06:09:10 +01:00
|
|
|
->setDisabledItems($inheritedRoleIDs);
|
|
|
|
if (!$allRoles->count()) {
|
2017-04-20 03:15:24 +02:00
|
|
|
$rolesField->setAttribute('data-placeholder', _t('SilverStripe\\Security\\Group.NoRoles', 'No roles found'));
|
2016-11-23 06:09:10 +01:00
|
|
|
}
|
|
|
|
$fields->addFieldToTab('Root.Roles', $rolesField);
|
|
|
|
}
|
|
|
|
|
|
|
|
$fields->push($idField = new HiddenField("ID"));
|
|
|
|
|
|
|
|
$this->extend('updateCMSFields', $fields);
|
|
|
|
|
|
|
|
return $fields;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param bool $includerelations Indicate if the labels returned include relation fields
|
|
|
|
* @return array
|
2017-07-03 02:21:27 +02:00
|
|
|
* @skipUpgrade
|
2016-11-23 06:09:10 +01:00
|
|
|
*/
|
2016-11-29 00:31:16 +01:00
|
|
|
public function fieldLabels($includerelations = true)
|
|
|
|
{
|
2016-11-23 06:09:10 +01:00
|
|
|
$labels = parent::fieldLabels($includerelations);
|
2018-01-16 19:39:30 +01:00
|
|
|
$labels['Title'] = _t(__CLASS__ . '.GROUPNAME', 'Group name');
|
2017-04-20 03:15:24 +02:00
|
|
|
$labels['Description'] = _t('SilverStripe\\Security\\Group.Description', 'Description');
|
|
|
|
$labels['Code'] = _t('SilverStripe\\Security\\Group.Code', 'Group Code', 'Programmatical code identifying a group');
|
|
|
|
$labels['Locked'] = _t('SilverStripe\\Security\\Group.Locked', 'Locked?', 'Group is locked in the security administration area');
|
|
|
|
$labels['Sort'] = _t('SilverStripe\\Security\\Group.Sort', 'Sort Order');
|
2016-11-23 06:09:10 +01:00
|
|
|
if ($includerelations) {
|
2017-04-20 03:15:24 +02:00
|
|
|
$labels['Parent'] = _t('SilverStripe\\Security\\Group.Parent', 'Parent Group', 'One group has one parent group');
|
|
|
|
$labels['Permissions'] = _t('SilverStripe\\Security\\Group.has_many_Permissions', 'Permissions', 'One group has many permissions');
|
|
|
|
$labels['Members'] = _t('SilverStripe\\Security\\Group.many_many_Members', 'Members', 'One group has many members');
|
2016-11-23 06:09:10 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
return $labels;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get many-many relation to {@link Member},
|
|
|
|
* including all members which are "inherited" from children groups of this record.
|
|
|
|
* See {@link DirectMembers()} for retrieving members without any inheritance.
|
|
|
|
*
|
2017-12-11 05:49:23 +01:00
|
|
|
* @param string $filter
|
2016-11-23 06:09:10 +01:00
|
|
|
* @return ManyManyList
|
|
|
|
*/
|
2016-11-29 00:31:16 +01:00
|
|
|
public function Members($filter = '')
|
|
|
|
{
|
2016-11-23 06:09:10 +01:00
|
|
|
// First get direct members as a base result
|
|
|
|
$result = $this->DirectMembers();
|
2016-11-29 00:31:16 +01:00
|
|
|
|
2016-11-23 06:09:10 +01:00
|
|
|
// Unsaved group cannot have child groups because its ID is still 0.
|
2016-11-29 00:31:16 +01:00
|
|
|
if (!$this->exists()) {
|
|
|
|
return $result;
|
|
|
|
}
|
|
|
|
|
2016-11-23 06:09:10 +01:00
|
|
|
// Remove the default foreign key filter in prep for re-applying a filter containing all children groups.
|
|
|
|
// Filters are conjunctive in DataQuery by default, so this filter would otherwise overrule any less specific
|
|
|
|
// ones.
|
|
|
|
if (!($result instanceof UnsavedRelationList)) {
|
|
|
|
$result = $result->alterDataQuery(function ($query) {
|
|
|
|
/** @var DataQuery $query */
|
|
|
|
$query->removeFilterOn('Group_Members');
|
|
|
|
});
|
|
|
|
}
|
2017-12-04 01:12:13 +01:00
|
|
|
|
2016-11-23 06:09:10 +01:00
|
|
|
// Now set all children groups as a new foreign key
|
2017-12-04 01:12:13 +01:00
|
|
|
$familyIDs = $this->collateFamilyIDs();
|
2017-12-11 05:49:23 +01:00
|
|
|
$result = $result->forForeignID($familyIDs);
|
|
|
|
|
|
|
|
return $result->where($filter);
|
2016-11-23 06:09:10 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Return only the members directly added to this group
|
|
|
|
*/
|
2016-11-29 00:31:16 +01:00
|
|
|
public function DirectMembers()
|
|
|
|
{
|
2016-11-23 06:09:10 +01:00
|
|
|
return $this->getManyManyComponents('Members');
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Return a set of this record's "family" of IDs - the IDs of
|
|
|
|
* this record and all its descendants.
|
|
|
|
*
|
|
|
|
* @return array
|
|
|
|
*/
|
2016-11-29 00:31:16 +01:00
|
|
|
public function collateFamilyIDs()
|
|
|
|
{
|
2016-11-23 06:09:10 +01:00
|
|
|
if (!$this->exists()) {
|
|
|
|
throw new \InvalidArgumentException("Cannot call collateFamilyIDs on unsaved Group.");
|
|
|
|
}
|
2016-11-29 00:31:16 +01:00
|
|
|
|
2016-11-23 06:09:10 +01:00
|
|
|
$familyIDs = array();
|
|
|
|
$chunkToAdd = array($this->ID);
|
2016-11-29 00:31:16 +01:00
|
|
|
|
2016-11-23 06:09:10 +01:00
|
|
|
while ($chunkToAdd) {
|
|
|
|
$familyIDs = array_merge($familyIDs, $chunkToAdd);
|
2016-11-29 00:31:16 +01:00
|
|
|
|
2016-11-23 06:09:10 +01:00
|
|
|
// Get the children of *all* the groups identified in the previous chunk.
|
|
|
|
// This minimises the number of SQL queries necessary
|
|
|
|
$chunkToAdd = Group::get()->filter("ParentID", $chunkToAdd)->column('ID');
|
|
|
|
}
|
2016-11-29 00:31:16 +01:00
|
|
|
|
2016-11-23 06:09:10 +01:00
|
|
|
return $familyIDs;
|
|
|
|
}
|
2016-11-29 00:31:16 +01:00
|
|
|
|
2016-11-23 06:09:10 +01:00
|
|
|
/**
|
|
|
|
* Returns an array of the IDs of this group and all its parents
|
|
|
|
*
|
|
|
|
* @return array
|
|
|
|
*/
|
2016-11-29 00:31:16 +01:00
|
|
|
public function collateAncestorIDs()
|
|
|
|
{
|
2016-11-23 06:09:10 +01:00
|
|
|
$parent = $this;
|
|
|
|
$items = [];
|
2017-06-09 05:07:35 +02:00
|
|
|
while ($parent instanceof Group) {
|
2016-11-23 06:09:10 +01:00
|
|
|
$items[] = $parent->ID;
|
2017-06-09 05:07:35 +02:00
|
|
|
$parent = $parent->getParent();
|
2016-11-23 06:09:10 +01:00
|
|
|
}
|
|
|
|
return $items;
|
|
|
|
}
|
|
|
|
|
2017-06-21 17:13:55 +02:00
|
|
|
/**
|
|
|
|
* Check if the group is a child of the given group or any parent groups
|
|
|
|
*
|
|
|
|
* @param string|int|Group $group Group instance, Group Code or ID
|
|
|
|
* @return bool Returns TRUE if the Group is a child of the given group, otherwise FALSE
|
|
|
|
*/
|
|
|
|
public function inGroup($group)
|
|
|
|
{
|
|
|
|
return in_array($this->identifierToGroupID($group), $this->collateAncestorIDs());
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Check if the group is a child of the given groups or any parent groups
|
|
|
|
*
|
|
|
|
* @param (string|int|Group)[] $groups
|
|
|
|
* @param bool $requireAll set to TRUE if must be in ALL groups, or FALSE if must be in ANY
|
|
|
|
* @return bool Returns TRUE if the Group is a child of any of the given groups, otherwise FALSE
|
|
|
|
*/
|
|
|
|
public function inGroups($groups, $requireAll = false)
|
|
|
|
{
|
|
|
|
$ancestorIDs = $this->collateAncestorIDs();
|
|
|
|
$candidateIDs = [];
|
|
|
|
foreach ($groups as $group) {
|
|
|
|
$groupID = $this->identifierToGroupID($group);
|
|
|
|
if ($groupID) {
|
|
|
|
$candidateIDs[] = $groupID;
|
|
|
|
} elseif ($requireAll) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (empty($candidateIDs)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
$matches = array_intersect($candidateIDs, $ancestorIDs);
|
|
|
|
if ($requireAll) {
|
|
|
|
return count($candidateIDs) === count($matches);
|
|
|
|
}
|
|
|
|
return !empty($matches);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Turn a string|int|Group into a GroupID
|
|
|
|
*
|
|
|
|
* @param string|int|Group $groupID Group instance, Group Code or ID
|
|
|
|
* @return int|null the Group ID or NULL if not found
|
|
|
|
*/
|
|
|
|
protected function identifierToGroupID($groupID)
|
|
|
|
{
|
|
|
|
if (is_numeric($groupID) && Group::get()->byID($groupID)) {
|
|
|
|
return $groupID;
|
|
|
|
} elseif (is_string($groupID) && $groupByCode = Group::get()->filter(['Code' => $groupID])->first()) {
|
|
|
|
return $groupByCode->ID;
|
|
|
|
} elseif ($groupID instanceof Group && $groupID->exists()) {
|
|
|
|
return $groupID->ID;
|
|
|
|
}
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2016-11-23 06:09:10 +01:00
|
|
|
/**
|
2017-12-11 05:49:23 +01:00
|
|
|
* This isn't a descendant of SiteTree, but needs this in case
|
2016-11-23 06:09:10 +01:00
|
|
|
* the group is "reorganised";
|
|
|
|
*/
|
2016-11-29 00:31:16 +01:00
|
|
|
public function cmsCleanup_parentChanged()
|
|
|
|
{
|
2016-11-23 06:09:10 +01:00
|
|
|
}
|
2016-11-29 00:31:16 +01:00
|
|
|
|
2016-11-23 06:09:10 +01:00
|
|
|
/**
|
|
|
|
* Override this so groups are ordered in the CMS
|
|
|
|
*/
|
2016-11-29 00:31:16 +01:00
|
|
|
public function stageChildren()
|
|
|
|
{
|
2016-11-23 06:09:10 +01:00
|
|
|
return Group::get()
|
|
|
|
->filter("ParentID", $this->ID)
|
|
|
|
->exclude("ID", $this->ID)
|
|
|
|
->sort('"Sort"');
|
|
|
|
}
|
2016-11-29 00:31:16 +01:00
|
|
|
|
2017-06-09 05:07:35 +02:00
|
|
|
/**
|
|
|
|
* @return string
|
|
|
|
*/
|
2016-11-29 00:31:16 +01:00
|
|
|
public function getTreeTitle()
|
|
|
|
{
|
2017-06-09 05:07:35 +02:00
|
|
|
$title = htmlspecialchars($this->Title, ENT_QUOTES);
|
|
|
|
$this->extend('updateTreeTitle', $title);
|
|
|
|
return $title;
|
2016-11-23 06:09:10 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Overloaded to ensure the code is always descent.
|
|
|
|
*
|
|
|
|
* @param string
|
|
|
|
*/
|
2016-11-29 00:31:16 +01:00
|
|
|
public function setCode($val)
|
|
|
|
{
|
2016-11-23 06:09:10 +01:00
|
|
|
$this->setField("Code", Convert::raw2url($val));
|
|
|
|
}
|
2016-11-29 00:31:16 +01:00
|
|
|
|
|
|
|
public function validate()
|
|
|
|
{
|
2016-11-23 06:09:10 +01:00
|
|
|
$result = parent::validate();
|
|
|
|
|
|
|
|
// Check if the new group hierarchy would add certain "privileged permissions",
|
|
|
|
// and require an admin to perform this change in case it does.
|
|
|
|
// This prevents "sub-admin" users with group editing permissions to increase their privileges.
|
|
|
|
if ($this->Parent()->exists() && !Permission::check('ADMIN')) {
|
|
|
|
$inheritedCodes = Permission::get()
|
|
|
|
->filter('GroupID', $this->Parent()->collateAncestorIDs())
|
|
|
|
->column('Code');
|
2017-05-11 11:07:27 +02:00
|
|
|
$privilegedCodes = Permission::config()->get('privileged_permissions');
|
2016-11-23 06:09:10 +01:00
|
|
|
if (array_intersect($inheritedCodes, $privilegedCodes)) {
|
2017-02-04 20:41:31 +01:00
|
|
|
$result->addError(
|
2016-11-23 06:09:10 +01:00
|
|
|
_t(
|
2017-04-20 03:15:24 +02:00
|
|
|
'SilverStripe\\Security\\Group.HierarchyPermsError',
|
2017-02-04 20:41:31 +01:00
|
|
|
'Can\'t assign parent group "{group}" with privileged permissions (requires ADMIN access)',
|
|
|
|
['group' => $this->Parent()->Title]
|
|
|
|
)
|
|
|
|
);
|
2016-11-23 06:09:10 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return $result;
|
|
|
|
}
|
2016-11-29 00:31:16 +01:00
|
|
|
|
|
|
|
public function onBeforeWrite()
|
|
|
|
{
|
2016-11-23 06:09:10 +01:00
|
|
|
parent::onBeforeWrite();
|
2016-11-29 00:31:16 +01:00
|
|
|
|
2016-11-23 06:09:10 +01:00
|
|
|
// Only set code property when the group has a custom title, and no code exists.
|
|
|
|
// The "Code" attribute is usually treated as a more permanent identifier than database IDs
|
|
|
|
// in custom application logic, so can't be changed after its first set.
|
2018-01-16 19:39:30 +01:00
|
|
|
if (!$this->Code && $this->Title != _t(__CLASS__ . '.NEWGROUP', "New Group")) {
|
2016-11-23 06:09:10 +01:00
|
|
|
$this->setCode($this->Title);
|
|
|
|
}
|
|
|
|
}
|
2016-11-29 00:31:16 +01:00
|
|
|
|
|
|
|
public function onBeforeDelete()
|
|
|
|
{
|
2016-11-23 06:09:10 +01:00
|
|
|
parent::onBeforeDelete();
|
|
|
|
|
|
|
|
// if deleting this group, delete it's children as well
|
|
|
|
foreach ($this->Groups() as $group) {
|
|
|
|
$group->delete();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Delete associated permissions
|
|
|
|
foreach ($this->Permissions() as $permission) {
|
|
|
|
$permission->delete();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Checks for permission-code CMS_ACCESS_SecurityAdmin.
|
|
|
|
* If the group has ADMIN permissions, it requires the user to have ADMIN permissions as well.
|
|
|
|
*
|
2017-05-11 11:07:27 +02:00
|
|
|
* @param Member $member Member
|
2016-11-23 06:09:10 +01:00
|
|
|
* @return boolean
|
|
|
|
*/
|
2016-11-29 00:31:16 +01:00
|
|
|
public function canEdit($member = null)
|
|
|
|
{
|
2017-05-11 11:07:27 +02:00
|
|
|
if (!$member) {
|
2017-05-20 06:32:25 +02:00
|
|
|
$member = Security::getCurrentUser();
|
2016-11-29 00:31:16 +01:00
|
|
|
}
|
|
|
|
|
2016-11-23 06:09:10 +01:00
|
|
|
// extended access checks
|
|
|
|
$results = $this->extend('canEdit', $member);
|
2016-11-29 00:31:16 +01:00
|
|
|
if ($results && is_array($results)) {
|
|
|
|
if (!min($results)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (// either we have an ADMIN
|
2016-11-23 06:09:10 +01:00
|
|
|
(bool)Permission::checkMember($member, "ADMIN")
|
|
|
|
|| (
|
|
|
|
// or a privileged CMS user and a group without ADMIN permissions.
|
|
|
|
// without this check, a user would be able to add himself to an administrators group
|
|
|
|
// with just access to the "Security" admin interface
|
|
|
|
Permission::checkMember($member, "CMS_ACCESS_SecurityAdmin") &&
|
|
|
|
!Permission::get()->filter(array('GroupID' => $this->ID, 'Code' => 'ADMIN'))->exists()
|
|
|
|
)
|
|
|
|
) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Checks for permission-code CMS_ACCESS_SecurityAdmin.
|
|
|
|
*
|
2017-05-11 11:07:27 +02:00
|
|
|
* @param Member $member
|
2016-11-23 06:09:10 +01:00
|
|
|
* @return boolean
|
|
|
|
*/
|
2016-11-29 00:31:16 +01:00
|
|
|
public function canView($member = null)
|
|
|
|
{
|
2017-05-11 11:07:27 +02:00
|
|
|
if (!$member) {
|
2017-05-20 06:32:25 +02:00
|
|
|
$member = Security::getCurrentUser();
|
2016-11-29 00:31:16 +01:00
|
|
|
}
|
|
|
|
|
2016-11-23 06:09:10 +01:00
|
|
|
// extended access checks
|
|
|
|
$results = $this->extend('canView', $member);
|
2016-11-29 00:31:16 +01:00
|
|
|
if ($results && is_array($results)) {
|
|
|
|
if (!min($results)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-11-23 06:09:10 +01:00
|
|
|
// user needs access to CMS_ACCESS_SecurityAdmin
|
2016-11-29 00:31:16 +01:00
|
|
|
if (Permission::checkMember($member, "CMS_ACCESS_SecurityAdmin")) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2016-11-23 06:09:10 +01:00
|
|
|
return false;
|
|
|
|
}
|
2016-11-29 00:31:16 +01:00
|
|
|
|
|
|
|
public function canDelete($member = null)
|
|
|
|
{
|
2017-05-11 11:07:27 +02:00
|
|
|
if (!$member) {
|
2017-05-20 06:32:25 +02:00
|
|
|
$member = Security::getCurrentUser();
|
2016-11-29 00:31:16 +01:00
|
|
|
}
|
|
|
|
|
2016-11-23 06:09:10 +01:00
|
|
|
// extended access checks
|
|
|
|
$results = $this->extend('canDelete', $member);
|
2016-11-29 00:31:16 +01:00
|
|
|
if ($results && is_array($results)) {
|
|
|
|
if (!min($results)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-11-23 06:09:10 +01:00
|
|
|
return $this->canEdit($member);
|
|
|
|
}
|
2016-11-29 00:31:16 +01:00
|
|
|
|
2016-11-23 06:09:10 +01:00
|
|
|
/**
|
|
|
|
* Returns all of the children for the CMS Tree.
|
|
|
|
* Filters to only those groups that the current user can edit
|
2017-12-11 05:49:23 +01:00
|
|
|
*
|
|
|
|
* @return ArrayList
|
2016-11-23 06:09:10 +01:00
|
|
|
*/
|
2016-11-29 00:31:16 +01:00
|
|
|
public function AllChildrenIncludingDeleted()
|
|
|
|
{
|
2017-10-05 06:23:02 +02:00
|
|
|
$children = parent::AllChildrenIncludingDeleted();
|
2016-11-29 00:31:16 +01:00
|
|
|
|
2016-11-23 06:09:10 +01:00
|
|
|
$filteredChildren = new ArrayList();
|
2016-11-29 00:31:16 +01:00
|
|
|
|
|
|
|
if ($children) {
|
|
|
|
foreach ($children as $child) {
|
2017-12-11 05:49:23 +01:00
|
|
|
/** @var DataObject $child */
|
2016-11-29 00:31:16 +01:00
|
|
|
if ($child->canView()) {
|
|
|
|
$filteredChildren->push($child);
|
|
|
|
}
|
|
|
|
}
|
2016-11-23 06:09:10 +01:00
|
|
|
}
|
2016-11-29 00:31:16 +01:00
|
|
|
|
2016-11-23 06:09:10 +01:00
|
|
|
return $filteredChildren;
|
|
|
|
}
|
2016-11-29 00:31:16 +01:00
|
|
|
|
2016-11-23 06:09:10 +01:00
|
|
|
/**
|
|
|
|
* Add default records to database.
|
|
|
|
*
|
|
|
|
* This function is called whenever the database is built, after the
|
|
|
|
* database tables have all been created.
|
|
|
|
*/
|
2016-11-29 00:31:16 +01:00
|
|
|
public function requireDefaultRecords()
|
|
|
|
{
|
2016-11-23 06:09:10 +01:00
|
|
|
parent::requireDefaultRecords();
|
|
|
|
|
|
|
|
// Add default author group if no other group exists
|
2017-05-11 11:07:27 +02:00
|
|
|
$allGroups = Group::get();
|
2016-11-23 06:09:10 +01:00
|
|
|
if (!$allGroups->count()) {
|
|
|
|
$authorGroup = new Group();
|
|
|
|
$authorGroup->Code = 'content-authors';
|
2017-02-04 20:41:31 +01:00
|
|
|
$authorGroup->Title = _t(__CLASS__ . '.DefaultGroupTitleContentAuthors', 'Content Authors');
|
2016-11-23 06:09:10 +01:00
|
|
|
$authorGroup->Sort = 1;
|
|
|
|
$authorGroup->write();
|
|
|
|
Permission::grant($authorGroup->ID, 'CMS_ACCESS_CMSMain');
|
|
|
|
Permission::grant($authorGroup->ID, 'CMS_ACCESS_AssetAdmin');
|
|
|
|
Permission::grant($authorGroup->ID, 'CMS_ACCESS_ReportAdmin');
|
|
|
|
Permission::grant($authorGroup->ID, 'SITETREE_REORGANISE');
|
|
|
|
}
|
|
|
|
|
|
|
|
// Add default admin group if none with permission code ADMIN exists
|
|
|
|
$adminGroups = Permission::get_groups_by_permission('ADMIN');
|
|
|
|
if (!$adminGroups->count()) {
|
|
|
|
$adminGroup = new Group();
|
|
|
|
$adminGroup->Code = 'administrators';
|
2017-02-04 20:41:31 +01:00
|
|
|
$adminGroup->Title = _t(__CLASS__ . '.DefaultGroupTitleAdministrators', 'Administrators');
|
2016-11-23 06:09:10 +01:00
|
|
|
$adminGroup->Sort = 0;
|
|
|
|
$adminGroup->write();
|
|
|
|
Permission::grant($adminGroup->ID, 'ADMIN');
|
|
|
|
}
|
|
|
|
|
|
|
|
// Members are populated through Member->requireDefaultRecords()
|
|
|
|
}
|
2012-05-23 12:45:04 +02:00
|
|
|
}
|