diff -r 000000000000 -r 7f95f8617b0b vendor/symfony/src/Symfony/Component/Security/Acl/Dbal/MutableAclProvider.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vendor/symfony/src/Symfony/Component/Security/Acl/Dbal/MutableAclProvider.php Sat Sep 24 15:40:41 2011 +0200 @@ -0,0 +1,890 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Acl\Dbal; + +use Doctrine\Common\PropertyChangedListener; +use Doctrine\DBAL\Driver\Connection; +use Symfony\Component\Security\Acl\Domain\RoleSecurityIdentity; +use Symfony\Component\Security\Acl\Domain\UserSecurityIdentity; +use Symfony\Component\Security\Acl\Exception\AclAlreadyExistsException; +use Symfony\Component\Security\Acl\Exception\ConcurrentModificationException; +use Symfony\Component\Security\Acl\Exception\Exception; +use Symfony\Component\Security\Acl\Model\AclCacheInterface; +use Symfony\Component\Security\Acl\Model\AclInterface; +use Symfony\Component\Security\Acl\Model\EntryInterface; +use Symfony\Component\Security\Acl\Model\MutableAclInterface; +use Symfony\Component\Security\Acl\Model\MutableAclProviderInterface; +use Symfony\Component\Security\Acl\Model\ObjectIdentityInterface; +use Symfony\Component\Security\Acl\Model\PermissionGrantingStrategyInterface; +use Symfony\Component\Security\Acl\Model\SecurityIdentityInterface; + +/** + * An implementation of the MutableAclProviderInterface using Doctrine DBAL. + * + * @author Johannes M. Schmitt + */ +class MutableAclProvider extends AclProvider implements MutableAclProviderInterface, PropertyChangedListener +{ + private $propertyChanges; + + /** + * {@inheritDoc} + */ + public function __construct(Connection $connection, PermissionGrantingStrategyInterface $permissionGrantingStrategy, array $options, AclCacheInterface $cache = null) + { + parent::__construct($connection, $permissionGrantingStrategy, $options, $cache); + + $this->propertyChanges = new \SplObjectStorage(); + } + + /** + * {@inheritDoc} + */ + public function createAcl(ObjectIdentityInterface $oid) + { + if (false !== $this->retrieveObjectIdentityPrimaryKey($oid)) { + throw new AclAlreadyExistsException(sprintf('%s is already associated with an ACL.', $oid)); + } + + $this->connection->beginTransaction(); + try { + $this->createObjectIdentity($oid); + + $pk = $this->retrieveObjectIdentityPrimaryKey($oid); + $this->connection->executeQuery($this->getInsertObjectIdentityRelationSql($pk, $pk)); + + $this->connection->commit(); + } catch (\Exception $failed) { + $this->connection->rollBack(); + + throw $failed; + } + + // re-read the ACL from the database to ensure proper caching, etc. + return $this->findAcl($oid); + } + + /** + * {@inheritDoc} + */ + public function deleteAcl(ObjectIdentityInterface $oid) + { + $this->connection->beginTransaction(); + try { + foreach ($this->findChildren($oid, true) as $childOid) { + $this->deleteAcl($childOid); + } + + $oidPK = $this->retrieveObjectIdentityPrimaryKey($oid); + + $this->deleteAccessControlEntries($oidPK); + $this->deleteObjectIdentityRelations($oidPK); + $this->deleteObjectIdentity($oidPK); + + $this->connection->commit(); + } catch (\Exception $failed) { + $this->connection->rollBack(); + + throw $failed; + } + + // evict the ACL from the in-memory identity map + if (isset($this->loadedAcls[$oid->getType()][$oid->getIdentifier()])) { + $this->propertyChanges->offsetUnset($this->loadedAcls[$oid->getType()][$oid->getIdentifier()]); + unset($this->loadedAcls[$oid->getType()][$oid->getIdentifier()]); + } + + // evict the ACL from any caches + if (null !== $this->cache) { + $this->cache->evictFromCacheByIdentity($oid); + } + } + + /** + * {@inheritDoc} + */ + public function findAcls(array $oids, array $sids = array()) + { + $result = parent::findAcls($oids, $sids); + + foreach ($result as $oid) { + $acl = $result->offsetGet($oid); + + if (false === $this->propertyChanges->contains($acl) && $acl instanceof MutableAclInterface) { + $acl->addPropertyChangedListener($this); + $this->propertyChanges->attach($acl, array()); + } + + $parentAcl = $acl->getParentAcl(); + while (null !== $parentAcl) { + if (false === $this->propertyChanges->contains($parentAcl) && $acl instanceof MutableAclInterface) { + $parentAcl->addPropertyChangedListener($this); + $this->propertyChanges->attach($parentAcl, array()); + } + + $parentAcl = $parentAcl->getParentAcl(); + } + } + + return $result; + } + + /** + * Implementation of PropertyChangedListener + * + * This allows us to keep track of which values have been changed, so we don't + * have to do a full introspection when ->updateAcl() is called. + * + * @param mixed $sender + * @param string $propertyName + * @param mixed $oldValue + * @param mixed $newValue + * @return void + */ + public function propertyChanged($sender, $propertyName, $oldValue, $newValue) + { + if (!$sender instanceof MutableAclInterface && !$sender instanceof EntryInterface) { + throw new \InvalidArgumentException('$sender must be an instance of MutableAclInterface, or EntryInterface.'); + } + + if ($sender instanceof EntryInterface) { + if (null === $sender->getId()) { + return; + } + + $ace = $sender; + $sender = $ace->getAcl(); + } else { + $ace = null; + } + + if (false === $this->propertyChanges->contains($sender)) { + throw new \InvalidArgumentException('$sender is not being tracked by this provider.'); + } + + $propertyChanges = $this->propertyChanges->offsetGet($sender); + if (null === $ace) { + if (isset($propertyChanges[$propertyName])) { + $oldValue = $propertyChanges[$propertyName][0]; + if ($oldValue === $newValue) { + unset($propertyChanges[$propertyName]); + } else { + $propertyChanges[$propertyName] = array($oldValue, $newValue); + } + } else { + $propertyChanges[$propertyName] = array($oldValue, $newValue); + } + } else { + if (!isset($propertyChanges['aces'])) { + $propertyChanges['aces'] = new \SplObjectStorage(); + } + + $acePropertyChanges = $propertyChanges['aces']->contains($ace)? $propertyChanges['aces']->offsetGet($ace) : array(); + + if (isset($acePropertyChanges[$propertyName])) { + $oldValue = $acePropertyChanges[$propertyName][0]; + if ($oldValue === $newValue) { + unset($acePropertyChanges[$propertyName]); + } else { + $acePropertyChanges[$propertyName] = array($oldValue, $newValue); + } + } else { + $acePropertyChanges[$propertyName] = array($oldValue, $newValue); + } + + if (count($acePropertyChanges) > 0) { + $propertyChanges['aces']->offsetSet($ace, $acePropertyChanges); + } else { + $propertyChanges['aces']->offsetUnset($ace); + + if (0 === count($propertyChanges['aces'])) { + unset($propertyChanges['aces']); + } + } + } + + $this->propertyChanges->offsetSet($sender, $propertyChanges); + } + + /** + * {@inheritDoc} + */ + public function updateAcl(MutableAclInterface $acl) + { + if (!$this->propertyChanges->contains($acl)) { + throw new \InvalidArgumentException('$acl is not tracked by this provider.'); + } + + $propertyChanges = $this->propertyChanges->offsetGet($acl); + // check if any changes were made to this ACL + if (0 === count($propertyChanges)) { + return; + } + + $sets = $sharedPropertyChanges = array(); + + $this->connection->beginTransaction(); + try { + if (isset($propertyChanges['entriesInheriting'])) { + $sets[] = 'entries_inheriting = '.$this->connection->getDatabasePlatform()->convertBooleans($propertyChanges['entriesInheriting'][1]); + } + + if (isset($propertyChanges['parentAcl'])) { + if (null === $propertyChanges['parentAcl'][1]) { + $sets[] = 'parent_object_identity_id = NULL'; + } else { + $sets[] = 'parent_object_identity_id = '.intval($propertyChanges['parentAcl'][1]->getId()); + } + + $this->regenerateAncestorRelations($acl); + $childAcls = $this->findAcls($this->findChildren($acl->getObjectIdentity(), false)); + foreach ($childAcls as $childOid) { + $this->regenerateAncestorRelations($childAcls[$childOid]); + } + } + + // this includes only updates of existing ACEs, but neither the creation, nor + // the deletion of ACEs; these are tracked by changes to the ACL's respective + // properties (classAces, classFieldAces, objectAces, objectFieldAces) + if (isset($propertyChanges['aces'])) { + $this->updateAces($propertyChanges['aces']); + } + + // check properties for deleted, and created ACEs + if (isset($propertyChanges['classAces'])) { + $this->updateAceProperty('classAces', $propertyChanges['classAces']); + $sharedPropertyChanges['classAces'] = $propertyChanges['classAces']; + } + if (isset($propertyChanges['classFieldAces'])) { + $this->updateFieldAceProperty('classFieldAces', $propertyChanges['classFieldAces']); + $sharedPropertyChanges['classFieldAces'] = $propertyChanges['classFieldAces']; + } + if (isset($propertyChanges['objectAces'])) { + $this->updateAceProperty('objectAces', $propertyChanges['objectAces']); + } + if (isset($propertyChanges['objectFieldAces'])) { + $this->updateFieldAceProperty('objectFieldAces', $propertyChanges['objectFieldAces']); + } + + // if there have been changes to shared properties, we need to synchronize other + // ACL instances for object identities of the same type that are already in-memory + if (count($sharedPropertyChanges) > 0) { + $classAcesProperty = new \ReflectionProperty('Symfony\Component\Security\Acl\Domain\Acl', 'classAces'); + $classAcesProperty->setAccessible(true); + $classFieldAcesProperty = new \ReflectionProperty('Symfony\Component\Security\Acl\Domain\Acl', 'classFieldAces'); + $classFieldAcesProperty->setAccessible(true); + + foreach ($this->loadedAcls[$acl->getObjectIdentity()->getType()] as $sameTypeAcl) { + if (isset($sharedPropertyChanges['classAces'])) { + if ($acl !== $sameTypeAcl && $classAcesProperty->getValue($sameTypeAcl) !== $sharedPropertyChanges['classAces'][0]) { + throw new ConcurrentModificationException('The "classAces" property has been modified concurrently.'); + } + + $classAcesProperty->setValue($sameTypeAcl, $sharedPropertyChanges['classAces'][1]); + } + + if (isset($sharedPropertyChanges['classFieldAces'])) { + if ($acl !== $sameTypeAcl && $classFieldAcesProperty->getValue($sameTypeAcl) !== $sharedPropertyChanges['classFieldAces'][0]) { + throw new ConcurrentModificationException('The "classFieldAces" property has been modified concurrently.'); + } + + $classFieldAcesProperty->setValue($sameTypeAcl, $sharedPropertyChanges['classFieldAces'][1]); + } + } + } + + // persist any changes to the acl_object_identities table + if (count($sets) > 0) { + $this->connection->executeQuery($this->getUpdateObjectIdentitySql($acl->getId(), $sets)); + } + + $this->connection->commit(); + } catch (\Exception $failed) { + $this->connection->rollBack(); + + throw $failed; + } + + $this->propertyChanges->offsetSet($acl, array()); + + if (null !== $this->cache) { + if (count($sharedPropertyChanges) > 0) { + // FIXME: Currently, there is no easy way to clear the cache for ACLs + // of a certain type. The problem here is that we need to make + // sure to clear the cache of all child ACLs as well, and these + // child ACLs might be of a different class type. + $this->cache->clearCache(); + } else { + // if there are no shared property changes, it's sufficient to just delete + // the cache for this ACL + $this->cache->evictFromCacheByIdentity($acl->getObjectIdentity()); + + foreach ($this->findChildren($acl->getObjectIdentity()) as $childOid) { + $this->cache->evictFromCacheByIdentity($childOid); + } + } + } + } + + /** + * Constructs the SQL for deleting access control entries. + * + * @param integer $oidPK + * @return string + */ + protected function getDeleteAccessControlEntriesSql($oidPK) + { + return sprintf( + 'DELETE FROM %s WHERE object_identity_id = %d', + $this->options['entry_table_name'], + $oidPK + ); + } + + /** + * Constructs the SQL for deleting a specific ACE. + * + * @param integer $acePK + * @return string + */ + protected function getDeleteAccessControlEntrySql($acePK) + { + return sprintf( + 'DELETE FROM %s WHERE id = %d', + $this->options['entry_table_name'], + $acePK + ); + } + + /** + * Constructs the SQL for deleting an object identity. + * + * @param integer $pk + * @return string + */ + protected function getDeleteObjectIdentitySql($pk) + { + return sprintf( + 'DELETE FROM %s WHERE id = %d', + $this->options['oid_table_name'], + $pk + ); + } + + /** + * Constructs the SQL for deleting relation entries. + * + * @param integer $pk + * @return string + */ + protected function getDeleteObjectIdentityRelationsSql($pk) + { + return sprintf( + 'DELETE FROM %s WHERE object_identity_id = %d', + $this->options['oid_ancestors_table_name'], + $pk + ); + } + + /** + * Constructs the SQL for inserting an ACE. + * + * @param integer $classId + * @param integer|null $objectIdentityId + * @param string|null $field + * @param integer $aceOrder + * @param integer $securityIdentityId + * @param string $strategy + * @param integer $mask + * @param Boolean $granting + * @param Boolean $auditSuccess + * @param Boolean $auditFailure + * @return string + */ + protected function getInsertAccessControlEntrySql($classId, $objectIdentityId, $field, $aceOrder, $securityIdentityId, $strategy, $mask, $granting, $auditSuccess, $auditFailure) + { + $query = <<options['entry_table_name'], + $classId, + null === $objectIdentityId? 'NULL' : intval($objectIdentityId), + null === $field? 'NULL' : $this->connection->quote($field), + $aceOrder, + $securityIdentityId, + $mask, + $this->connection->getDatabasePlatform()->convertBooleans($granting), + $this->connection->quote($strategy), + $this->connection->getDatabasePlatform()->convertBooleans($auditSuccess), + $this->connection->getDatabasePlatform()->convertBooleans($auditFailure) + ); + } + + /** + * Constructs the SQL for inserting a new class type. + * + * @param string $classType + * @return string + */ + protected function getInsertClassSql($classType) + { + return sprintf( + 'INSERT INTO %s (class_type) VALUES (%s)', + $this->options['class_table_name'], + $this->connection->quote($classType) + ); + } + + /** + * Constructs the SQL for inserting a relation entry. + * + * @param integer $objectIdentityId + * @param integer $ancestorId + * @return string + */ + protected function getInsertObjectIdentityRelationSql($objectIdentityId, $ancestorId) + { + return sprintf( + 'INSERT INTO %s (object_identity_id, ancestor_id) VALUES (%d, %d)', + $this->options['oid_ancestors_table_name'], + $objectIdentityId, + $ancestorId + ); + } + + /** + * Constructs the SQL for inserting an object identity. + * + * @param string $identifier + * @param integer $classId + * @param Boolean $entriesInheriting + * @return string + */ + protected function getInsertObjectIdentitySql($identifier, $classId, $entriesInheriting) + { + $query = <<options['oid_table_name'], + $classId, + $this->connection->quote($identifier), + $this->connection->getDatabasePlatform()->convertBooleans($entriesInheriting) + ); + } + + /** + * Constructs the SQL for inserting a security identity. + * + * @param SecurityIdentityInterface $sid + * @throws \InvalidArgumentException + * @return string + */ + protected function getInsertSecurityIdentitySql(SecurityIdentityInterface $sid) + { + if ($sid instanceof UserSecurityIdentity) { + $identifier = $sid->getClass().'-'.$sid->getUsername(); + $username = true; + } else if ($sid instanceof RoleSecurityIdentity) { + $identifier = $sid->getRole(); + $username = false; + } else { + throw new \InvalidArgumentException('$sid must either be an instance of UserSecurityIdentity, or RoleSecurityIdentity.'); + } + + return sprintf( + 'INSERT INTO %s (identifier, username) VALUES (%s, %s)', + $this->options['sid_table_name'], + $this->connection->quote($identifier), + $this->connection->getDatabasePlatform()->convertBooleans($username) + ); + } + + /** + * Constructs the SQL for selecting an ACE. + * + * @param integer $classId + * @param integer $oid + * @param string $field + * @param integer $order + * @return string + */ + protected function getSelectAccessControlEntryIdSql($classId, $oid, $field, $order) + { + return sprintf( + 'SELECT id FROM %s WHERE class_id = %d AND %s AND %s AND ace_order = %d', + $this->options['entry_table_name'], + $classId, + null === $oid ? + $this->connection->getDatabasePlatform()->getIsNullExpression('object_identity_id') + : 'object_identity_id = '.intval($oid), + null === $field ? + $this->connection->getDatabasePlatform()->getIsNullExpression('field_name') + : 'field_name = '.$this->connection->quote($field), + $order + ); + } + + /** + * Constructs the SQL for selecting the primary key associated with + * the passed class type. + * + * @param string $classType + * @return string + */ + protected function getSelectClassIdSql($classType) + { + return sprintf( + 'SELECT id FROM %s WHERE class_type = %s', + $this->options['class_table_name'], + $this->connection->quote($classType) + ); + } + + /** + * Constructs the SQL for selecting the primary key of a security identity. + * + * @param SecurityIdentityInterface $sid + * @throws \InvalidArgumentException + * @return string + */ + protected function getSelectSecurityIdentityIdSql(SecurityIdentityInterface $sid) + { + if ($sid instanceof UserSecurityIdentity) { + $identifier = $sid->getClass().'-'.$sid->getUsername(); + $username = true; + } else if ($sid instanceof RoleSecurityIdentity) { + $identifier = $sid->getRole(); + $username = false; + } else { + throw new \InvalidArgumentException('$sid must either be an instance of UserSecurityIdentity, or RoleSecurityIdentity.'); + } + + return sprintf( + 'SELECT id FROM %s WHERE identifier = %s AND username = %s', + $this->options['sid_table_name'], + $this->connection->quote($identifier), + $this->connection->getDatabasePlatform()->convertBooleans($username) + ); + } + + /** + * Constructs the SQL for updating an object identity. + * + * @param integer $pk + * @param array $changes + * @throws \InvalidArgumentException + * @return string + */ + protected function getUpdateObjectIdentitySql($pk, array $changes) + { + if (0 === count($changes)) { + throw new \InvalidArgumentException('There are no changes.'); + } + + return sprintf( + 'UPDATE %s SET %s WHERE id = %d', + $this->options['oid_table_name'], + implode(', ', $changes), + $pk + ); + } + + /** + * Constructs the SQL for updating an ACE. + * + * @param integer $pk + * @param array $sets + * @throws \InvalidArgumentException + * @return string + */ + protected function getUpdateAccessControlEntrySql($pk, array $sets) + { + if (0 === count($sets)) { + throw new \InvalidArgumentException('There are no changes.'); + } + + return sprintf( + 'UPDATE %s SET %s WHERE id = %d', + $this->options['entry_table_name'], + implode(', ', $sets), + $pk + ); + } + + /** + * Creates the ACL for the passed object identity + * + * @param ObjectIdentityInterface $oid + * @return void + */ + private function createObjectIdentity(ObjectIdentityInterface $oid) + { + $classId = $this->createOrRetrieveClassId($oid->getType()); + + $this->connection->executeQuery($this->getInsertObjectIdentitySql($oid->getIdentifier(), $classId, true)); + } + + /** + * Returns the primary key for the passed class type. + * + * If the type does not yet exist in the database, it will be created. + * + * @param string $classType + * @return integer + */ + private function createOrRetrieveClassId($classType) + { + if (false !== $id = $this->connection->executeQuery($this->getSelectClassIdSql($classType))->fetchColumn()) { + return $id; + } + + $this->connection->executeQuery($this->getInsertClassSql($classType)); + + return $this->connection->executeQuery($this->getSelectClassIdSql($classType))->fetchColumn(); + } + + /** + * Returns the primary key for the passed security identity. + * + * If the security identity does not yet exist in the database, it will be + * created. + * + * @param SecurityIdentityInterface $sid + * @return integer + */ + private function createOrRetrieveSecurityIdentityId(SecurityIdentityInterface $sid) + { + if (false !== $id = $this->connection->executeQuery($this->getSelectSecurityIdentityIdSql($sid))->fetchColumn()) { + return $id; + } + + $this->connection->executeQuery($this->getInsertSecurityIdentitySql($sid)); + + return $this->connection->executeQuery($this->getSelectSecurityIdentityIdSql($sid))->fetchColumn(); + } + + /** + * Deletes all ACEs for the given object identity primary key. + * + * @param integer $oidPK + * @return void + */ + private function deleteAccessControlEntries($oidPK) + { + $this->connection->executeQuery($this->getDeleteAccessControlEntriesSql($oidPK)); + } + + /** + * Deletes the object identity from the database. + * + * @param integer $pk + * @return void + */ + private function deleteObjectIdentity($pk) + { + $this->connection->executeQuery($this->getDeleteObjectIdentitySql($pk)); + } + + /** + * Deletes all entries from the relations table from the database. + * + * @param integer $pk + * @return void + */ + private function deleteObjectIdentityRelations($pk) + { + $this->connection->executeQuery($this->getDeleteObjectIdentityRelationsSql($pk)); + } + + /** + * This regenerates the ancestor table which is used for fast read access. + * + * @param AclInterface $acl + * @return void + */ + private function regenerateAncestorRelations(AclInterface $acl) + { + $pk = $acl->getId(); + $this->connection->executeQuery($this->getDeleteObjectIdentityRelationsSql($pk)); + $this->connection->executeQuery($this->getInsertObjectIdentityRelationSql($pk, $pk)); + + $parentAcl = $acl->getParentAcl(); + while (null !== $parentAcl) { + $this->connection->executeQuery($this->getInsertObjectIdentityRelationSql($pk, $parentAcl->getId())); + + $parentAcl = $parentAcl->getParentAcl(); + } + } + + /** + * This processes changes on an ACE related property (classFieldAces, or objectFieldAces). + * + * @param string $name + * @param array $changes + * @return void + */ + private function updateFieldAceProperty($name, array $changes) + { + $sids = new \SplObjectStorage(); + $classIds = new \SplObjectStorage(); + $currentIds = array(); + foreach ($changes[1] as $field => $new) { + for ($i=0,$c=count($new); $i<$c; $i++) { + $ace = $new[$i]; + + if (null === $ace->getId()) { + if ($sids->contains($ace->getSecurityIdentity())) { + $sid = $sids->offsetGet($ace->getSecurityIdentity()); + } else { + $sid = $this->createOrRetrieveSecurityIdentityId($ace->getSecurityIdentity()); + } + + $oid = $ace->getAcl()->getObjectIdentity(); + if ($classIds->contains($oid)) { + $classId = $classIds->offsetGet($oid); + } else { + $classId = $this->createOrRetrieveClassId($oid->getType()); + } + + $objectIdentityId = $name === 'classFieldAces' ? null : $ace->getAcl()->getId(); + + $this->connection->executeQuery($this->getInsertAccessControlEntrySql($classId, $objectIdentityId, $field, $i, $sid, $ace->getStrategy(), $ace->getMask(), $ace->isGranting(), $ace->isAuditSuccess(), $ace->isAuditFailure())); + $aceId = $this->connection->executeQuery($this->getSelectAccessControlEntryIdSql($classId, $objectIdentityId, $field, $i))->fetchColumn(); + $this->loadedAces[$aceId] = $ace; + + $aceIdProperty = new \ReflectionProperty('Symfony\Component\Security\Acl\Domain\Entry', 'id'); + $aceIdProperty->setAccessible(true); + $aceIdProperty->setValue($ace, intval($aceId)); + } else { + $currentIds[$ace->getId()] = true; + } + } + } + + foreach ($changes[0] as $old) { + for ($i=0,$c=count($old); $i<$c; $i++) { + $ace = $old[$i]; + + if (!isset($currentIds[$ace->getId()])) { + $this->connection->executeQuery($this->getDeleteAccessControlEntrySql($ace->getId())); + unset($this->loadedAces[$ace->getId()]); + } + } + } + } + + /** + * This processes changes on an ACE related property (classAces, or objectAces). + * + * @param string $name + * @param array $changes + * @return void + */ + private function updateAceProperty($name, array $changes) + { + list($old, $new) = $changes; + + $sids = new \SplObjectStorage(); + $classIds = new \SplObjectStorage(); + $currentIds = array(); + for ($i=0,$c=count($new); $i<$c; $i++) { + $ace = $new[$i]; + + if (null === $ace->getId()) { + if ($sids->contains($ace->getSecurityIdentity())) { + $sid = $sids->offsetGet($ace->getSecurityIdentity()); + } else { + $sid = $this->createOrRetrieveSecurityIdentityId($ace->getSecurityIdentity()); + } + + $oid = $ace->getAcl()->getObjectIdentity(); + if ($classIds->contains($oid)) { + $classId = $classIds->offsetGet($oid); + } else { + $classId = $this->createOrRetrieveClassId($oid->getType()); + } + + $objectIdentityId = $name === 'classAces' ? null : $ace->getAcl()->getId(); + + $this->connection->executeQuery($this->getInsertAccessControlEntrySql($classId, $objectIdentityId, null, $i, $sid, $ace->getStrategy(), $ace->getMask(), $ace->isGranting(), $ace->isAuditSuccess(), $ace->isAuditFailure())); + $aceId = $this->connection->executeQuery($this->getSelectAccessControlEntryIdSql($classId, $objectIdentityId, null, $i))->fetchColumn(); + $this->loadedAces[$aceId] = $ace; + + $aceIdProperty = new \ReflectionProperty($ace, 'id'); + $aceIdProperty->setAccessible(true); + $aceIdProperty->setValue($ace, intval($aceId)); + } else { + $currentIds[$ace->getId()] = true; + } + } + + for ($i=0,$c=count($old); $i<$c; $i++) { + $ace = $old[$i]; + + if (!isset($currentIds[$ace->getId()])) { + $this->connection->executeQuery($this->getDeleteAccessControlEntrySql($ace->getId())); + unset($this->loadedAces[$ace->getId()]); + } + } + } + + /** + * Persists the changes which were made to ACEs to the database. + * + * @param \SplObjectStorage $aces + * @return void + */ + private function updateAces(\SplObjectStorage $aces) + { + foreach ($aces as $ace) { + $propertyChanges = $aces->offsetGet($ace); + $sets = array(); + + if (isset($propertyChanges['mask'])) { + $sets[] = sprintf('mask = %d', $propertyChanges['mask'][1]); + } + if (isset($propertyChanges['strategy'])) { + $sets[] = sprintf('granting_strategy = %s', $this->connection->quote($propertyChanges['strategy'])); + } + if (isset($propertyChanges['aceOrder'])) { + $sets[] = sprintf('ace_order = %d', $propertyChanges['aceOrder'][1]); + } + if (isset($propertyChanges['auditSuccess'])) { + $sets[] = sprintf('audit_success = %s', $this->connection->getDatabasePlatform()->convertBooleans($propertyChanges['auditSuccess'][1])); + } + if (isset($propertyChanges['auditFailure'])) { + $sets[] = sprintf('audit_failure = %s', $this->connection->getDatabasePlatform()->convertBooleans($propertyChanges['auditFailure'][1])); + } + + $this->connection->executeQuery($this->getUpdateAccessControlEntrySql($ace->getId(), $sets)); + } + } +}