silverstripe-framework/src/Security/Member_GroupSet.php

147 lines
4.5 KiB
PHP

<?php
namespace SilverStripe\Security;
use SilverStripe\ORM\DataObject;
use SilverStripe\ORM\DB;
use SilverStripe\ORM\ManyManyList;
use SilverStripe\ORM\Queries\SQLDelete;
use SilverStripe\ORM\Queries\SQLSelect;
/**
* Represents a set of Groups attached to a member.
* Handles the hierarchy logic.
*/
class Member_GroupSet extends ManyManyList
{
protected function linkJoinTable()
{
// Do not join the table directly
if ($this->extraFields) {
throw new \BadMethodCallException('Member_GroupSet does not support many_many_extraFields');
}
}
/**
* Link this group set to a specific member.
*
* Recursively selects all groups applied to this member, as well as any
* parent groups of any applied groups
*
* @param array|int|string|null $id (optional) An ID or an array of IDs - if not provided, will use the current
* ids as per getForeignID
* @return array Condition In array(SQL => parameters format)
*/
public function foreignIDFilter($id = null)
{
if ($id === null) {
$id = $this->getForeignID();
}
// Find directly applied groups
$manyManyFilter = parent::foreignIDFilter($id);
$query = SQLSelect::create('"Group_Members"."GroupID"', '"Group_Members"', $manyManyFilter);
$groupIDs = $query->execute()->column();
// Get all ancestors, iteratively merging these into the master set
$allGroupIDs = [];
while ($groupIDs) {
$allGroupIDs = array_merge($allGroupIDs, $groupIDs);
$groupIDs = DataObject::get(Group::class)->byIDs($groupIDs)->column("ParentID");
$groupIDs = array_filter($groupIDs ?? []);
}
// Add a filter to this DataList
if (!empty($allGroupIDs)) {
$allGroupIDsPlaceholders = DB::placeholders($allGroupIDs);
return ["\"Group\".\"ID\" IN ($allGroupIDsPlaceholders)" => $allGroupIDs];
}
return ['"Group"."ID"' => 0];
}
public function foreignIDWriteFilter($id = null)
{
// Use the ManyManyList::foreignIDFilter rather than the one
// in this class, otherwise we end up selecting all inherited groups
return parent::foreignIDFilter($id);
}
public function add($item, $extraFields = null)
{
// Get Group.ID
$itemID = null;
if (is_numeric($item)) {
$itemID = $item;
} else {
if ($item instanceof Group) {
$itemID = $item->ID;
}
}
// Check if this group is allowed to be added
if ($this->canAddGroups([$itemID])) {
parent::add($item, $extraFields);
}
}
public function removeAll()
{
// Remove the join to the join table to avoid MySQL row locking issues.
$query = $this->dataQuery();
$foreignFilter = $query->getQueryParam('Foreign.Filter');
$query->removeFilterOn($foreignFilter);
// Select ID column
$selectQuery = $query->query();
$dataClassIDColumn = DataObject::getSchema()->sqlColumnForField($this->dataClass(), 'ID');
$selectQuery->setSelect($dataClassIDColumn);
$from = $selectQuery->getFrom();
unset($from[$this->joinTable]);
$selectQuery->setFrom($from);
$selectQuery->setOrderBy(); // ORDER BY in subselects breaks MS SQL Server and is not necessary here
$selectQuery->setDistinct(false);
// Use a sub-query as SQLite does not support setting delete targets in
// joined queries.
$delete = SQLDelete::create();
$delete->setFrom("\"{$this->joinTable}\"");
$delete->addWhere(parent::foreignIDFilter());
$subSelect = $selectQuery->sql($parameters);
$delete->addWhere([
"\"{$this->joinTable}\".\"{$this->localKey}\" IN ($subSelect)" => $parameters
]);
$delete->execute();
}
/**
* Determine if the following groups IDs can be added
*
* @param array $itemIDs
* @return boolean
*/
protected function canAddGroups($itemIDs)
{
if (empty($itemIDs)) {
return true;
}
$member = $this->getMember();
return empty($member) || $member->onChangeGroups($itemIDs);
}
/**
* Get foreign member record for this relation
*
* @return Member
*/
protected function getMember()
{
$id = $this->getForeignID();
if ($id) {
return DataObject::get_by_id(Member::class, $id);
}
}
}