diff --git a/security/Member.php b/security/Member.php index a83a9d20e..927ae14c0 100644 --- a/security/Member.php +++ b/security/Member.php @@ -1820,6 +1820,37 @@ class Member_GroupSet extends ManyManyList { } } + public function removeAll() { + $base = ClassInfo::baseDataClass($this->dataClass()); + + // Remove the join to the join table to avoid MySQL row locking issues. + $query = $this->dataQuery(); + $foreignFilter = $query->getQueryParam('Foreign.Filter'); + $query->removeFilterOn($foreignFilter); + + $selectQuery = $query->query(); + $selectQuery->setSelect("\"{$base}\".\"ID\""); + + $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 = new SQLDelete(); + $delete->setFrom("\"{$this->joinTable}\""); + // Use ManyManyList::foreignIDFilter() rather than the one in this class + // otherwise we end up selecting the wrong columns + $delete->addWhere(parent::foreignIDFilter()); + $subSelect = $selectQuery->sql($parameters); + $delete->addWhere(array( + "\"{$this->joinTable}\".\"{$this->localKey}\" IN ($subSelect)" => $parameters + )); + $delete->execute(); + } + /** * Determine if the following groups IDs can be added * diff --git a/tests/security/MemberTest.php b/tests/security/MemberTest.php index 342a2da71..22753c4e1 100644 --- a/tests/security/MemberTest.php +++ b/tests/security/MemberTest.php @@ -377,6 +377,35 @@ class MemberTest extends FunctionalTest { ); } + /** + * Assertions to check that Member_GroupSet is functionally equivalent to ManyManyList + */ + public function testRemoveGroups() + { + $staffmember = $this->objFromFixture('Member', 'staffmember'); + + $staffgroup = $this->objFromFixture('Group', 'staffgroup'); + $managementgroup = $this->objFromFixture('Group', 'managementgroup'); + + $this->assertTrue( + $staffmember->inGroups(array($staffgroup, $managementgroup)), + 'inGroups() succeeds if a membership is detected on one of many passed groups' + ); + + $staffmember->Groups()->remove($managementgroup); + $this->assertFalse( + $staffmember->inGroup($managementgroup), + 'member was not removed from group using ->Groups()->remove()' + ); + + $staffmember->Groups()->removeAll(); + $this->assertEquals( + 0, + $staffmember->Groups()->count(), + 'member was not removed from all groups using ->Groups()->removeAll()' + ); + } + public function testAddToGroupByCode() { $grouplessMember = $this->objFromFixture('Member', 'grouplessmember'); $memberlessGroup = $this->objFromFixture('Group','memberlessgroup');