From 647c24c3b4621438e017800d930d41b2d08f25a2 Mon Sep 17 00:00:00 2001 From: fogelito Date: Thu, 24 Jul 2025 16:42:01 +0300 Subject: [PATCH 1/8] $skipPermissions --- src/Database/Adapter.php | 3 +- src/Database/Adapter/MariaDB.php | 197 +++++++++++++++--------------- src/Database/Adapter/Pool.php | 2 +- src/Database/Adapter/Postgres.php | 9 +- src/Database/Adapter/SQLite.php | 4 +- src/Database/Database.php | 35 +++++- 6 files changed, 142 insertions(+), 108 deletions(-) diff --git a/src/Database/Adapter.php b/src/Database/Adapter.php index c20593ba1..7ff0770c4 100644 --- a/src/Database/Adapter.php +++ b/src/Database/Adapter.php @@ -697,10 +697,11 @@ abstract public function createDocuments(string $collection, array $documents): * @param string $collection * @param string $id * @param Document $document + * @param bool $skipPermissions * * @return Document */ - abstract public function updateDocument(string $collection, string $id, Document $document): Document; + abstract public function updateDocument(string $collection, string $id, Document $document, bool $skipPermissions): Document; /** * Update documents diff --git a/src/Database/Adapter/MariaDB.php b/src/Database/Adapter/MariaDB.php index c67d5114e..a76349209 100644 --- a/src/Database/Adapter/MariaDB.php +++ b/src/Database/Adapter/MariaDB.php @@ -926,13 +926,14 @@ public function createDocument(string $collection, Document $document): Document * @param string $collection * @param string $id * @param Document $document + * @param bool $skipPermissions * @return Document * @throws Exception * @throws PDOException * @throws DuplicateException * @throws \Throwable */ - public function updateDocument(string $collection, string $id, Document $document): Document + public function updateDocument(string $collection, string $id, Document $document, bool $skipPermissions): Document { try { $attributes = $document->getAttributes(); @@ -943,149 +944,151 @@ public function updateDocument(string $collection, string $id, Document $documen $name = $this->filter($collection); $columns = ''; - $sql = " + if (!$skipPermissions) { + $sql = " SELECT _type, _permission FROM {$this->getSQLTable($name . '_perms')} WHERE _document = :_uid {$this->getTenantQuery($collection)} "; - $sql = $this->trigger(Database::EVENT_PERMISSIONS_READ, $sql); + $sql = $this->trigger(Database::EVENT_PERMISSIONS_READ, $sql); - /** - * Get current permissions from the database - */ - $sqlPermissions = $this->getPDO()->prepare($sql); - $sqlPermissions->bindValue(':_uid', $document->getId()); + /** + * Get current permissions from the database + */ + $sqlPermissions = $this->getPDO()->prepare($sql); + $sqlPermissions->bindValue(':_uid', $document->getId()); - if ($this->sharedTables) { - $sqlPermissions->bindValue(':_tenant', $this->tenant); - } + if ($this->sharedTables) { + $sqlPermissions->bindValue(':_tenant', $this->tenant); + } - $sqlPermissions->execute(); - $permissions = $sqlPermissions->fetchAll(); - $sqlPermissions->closeCursor(); + $sqlPermissions->execute(); + $permissions = $sqlPermissions->fetchAll(); + $sqlPermissions->closeCursor(); - $initial = []; - foreach (Database::PERMISSIONS as $type) { - $initial[$type] = []; - } + $initial = []; + foreach (Database::PERMISSIONS as $type) { + $initial[$type] = []; + } - $permissions = array_reduce($permissions, function (array $carry, array $item) { - $carry[$item['_type']][] = $item['_permission']; + $permissions = array_reduce($permissions, function (array $carry, array $item) { + $carry[$item['_type']][] = $item['_permission']; - return $carry; - }, $initial); + return $carry; + }, $initial); - /** - * Get removed Permissions - */ - $removals = []; - foreach (Database::PERMISSIONS as $type) { - $diff = \array_diff($permissions[$type], $document->getPermissionsByType($type)); - if (!empty($diff)) { - $removals[$type] = $diff; + /** + * Get removed Permissions + */ + $removals = []; + foreach (Database::PERMISSIONS as $type) { + $diff = \array_diff($permissions[$type], $document->getPermissionsByType($type)); + if (!empty($diff)) { + $removals[$type] = $diff; + } } - } - /** - * Get added Permissions - */ - $additions = []; - foreach (Database::PERMISSIONS as $type) { - $diff = \array_diff($document->getPermissionsByType($type), $permissions[$type]); - if (!empty($diff)) { - $additions[$type] = $diff; + /** + * Get added Permissions + */ + $additions = []; + foreach (Database::PERMISSIONS as $type) { + $diff = \array_diff($document->getPermissionsByType($type), $permissions[$type]); + if (!empty($diff)) { + $additions[$type] = $diff; + } } - } - /** - * Query to remove permissions - */ - $removeQuery = ''; - if (!empty($removals)) { - $removeQuery = ' AND ('; - foreach ($removals as $type => $permissions) { - $removeQuery .= "( + /** + * Query to remove permissions + */ + $removeQuery = ''; + if (!empty($removals)) { + $removeQuery = ' AND ('; + foreach ($removals as $type => $permissions) { + $removeQuery .= "( _type = '{$type}' AND _permission IN (" . implode(', ', \array_map(fn (string $i) => ":_remove_{$type}_{$i}", \array_keys($permissions))) . ") )"; - if ($type !== \array_key_last($removals)) { - $removeQuery .= ' OR '; + if ($type !== \array_key_last($removals)) { + $removeQuery .= ' OR '; + } } } - } - if (!empty($removeQuery)) { - $removeQuery .= ')'; - $sql = " + if (!empty($removeQuery)) { + $removeQuery .= ')'; + $sql = " DELETE FROM {$this->getSQLTable($name . '_perms')} WHERE _document = :_uid {$this->getTenantQuery($collection)} "; - $removeQuery = $sql . $removeQuery; + $removeQuery = $sql . $removeQuery; - $removeQuery = $this->trigger(Database::EVENT_PERMISSIONS_DELETE, $removeQuery); + $removeQuery = $this->trigger(Database::EVENT_PERMISSIONS_DELETE, $removeQuery); - $stmtRemovePermissions = $this->getPDO()->prepare($removeQuery); - $stmtRemovePermissions->bindValue(':_uid', $document->getId()); + $stmtRemovePermissions = $this->getPDO()->prepare($removeQuery); + $stmtRemovePermissions->bindValue(':_uid', $document->getId()); - if ($this->sharedTables) { - $stmtRemovePermissions->bindValue(':_tenant', $this->tenant); - } + if ($this->sharedTables) { + $stmtRemovePermissions->bindValue(':_tenant', $this->tenant); + } - foreach ($removals as $type => $permissions) { - foreach ($permissions as $i => $permission) { - $stmtRemovePermissions->bindValue(":_remove_{$type}_{$i}", $permission); + foreach ($removals as $type => $permissions) { + foreach ($permissions as $i => $permission) { + $stmtRemovePermissions->bindValue(":_remove_{$type}_{$i}", $permission); + } } } - } - - /** - * Query to add permissions - */ - if (!empty($additions)) { - $values = []; - foreach ($additions as $type => $permissions) { - foreach ($permissions as $i => $_) { - $value = "( :_uid, '{$type}', :_add_{$type}_{$i}"; - if ($this->sharedTables) { - $value .= ", :_tenant)"; - } else { - $value .= ")"; + /** + * Query to add permissions + */ + if (!empty($additions)) { + $values = []; + foreach ($additions as $type => $permissions) { + foreach ($permissions as $i => $_) { + $value = "( :_uid, '{$type}', :_add_{$type}_{$i}"; + + if ($this->sharedTables) { + $value .= ", :_tenant)"; + } else { + $value .= ")"; + } + + $values[] = $value; } - - $values[] = $value; } - } - $sql = " + $sql = " INSERT INTO {$this->getSQLTable($name . '_perms')} (_document, _type, _permission "; - if ($this->sharedTables) { - $sql .= ', _tenant)'; - } else { - $sql .= ')'; - } + if ($this->sharedTables) { + $sql .= ', _tenant)'; + } else { + $sql .= ')'; + } - $sql .= " VALUES " . \implode(', ', $values); + $sql .= " VALUES " . \implode(', ', $values); - $sql = $this->trigger(Database::EVENT_PERMISSIONS_CREATE, $sql); + $sql = $this->trigger(Database::EVENT_PERMISSIONS_CREATE, $sql); - $stmtAddPermissions = $this->getPDO()->prepare($sql); + $stmtAddPermissions = $this->getPDO()->prepare($sql); - $stmtAddPermissions->bindValue(":_uid", $document->getId()); + $stmtAddPermissions->bindValue(":_uid", $document->getId()); - if ($this->sharedTables) { - $stmtAddPermissions->bindValue(":_tenant", $this->tenant); - } + if ($this->sharedTables) { + $stmtAddPermissions->bindValue(":_tenant", $this->tenant); + } - foreach ($additions as $type => $permissions) { - foreach ($permissions as $i => $permission) { - $stmtAddPermissions->bindValue(":_add_{$type}_{$i}", $permission); + foreach ($additions as $type => $permissions) { + foreach ($permissions as $i => $permission) { + $stmtAddPermissions->bindValue(":_add_{$type}_{$i}", $permission); + } } } } diff --git a/src/Database/Adapter/Pool.php b/src/Database/Adapter/Pool.php index e64db87ec..d255a1d1e 100644 --- a/src/Database/Adapter/Pool.php +++ b/src/Database/Adapter/Pool.php @@ -235,7 +235,7 @@ public function createDocuments(string $collection, array $documents): array return $this->delegate(__FUNCTION__, \func_get_args()); } - public function updateDocument(string $collection, string $id, Document $document): Document + public function updateDocument(string $collection, string $id, Document $document, bool $skipPermissions): Document { return $this->delegate(__FUNCTION__, \func_get_args()); } diff --git a/src/Database/Adapter/Postgres.php b/src/Database/Adapter/Postgres.php index 2fc4827b5..190d32987 100644 --- a/src/Database/Adapter/Postgres.php +++ b/src/Database/Adapter/Postgres.php @@ -1044,15 +1044,16 @@ public function createDocument(string $collection, Document $document): Document /** * Update Document * + * * @param string $collection * @param string $id * @param Document $document - * + * @param bool $skipPermissions * @return Document * @throws DatabaseException * @throws DuplicateException */ - public function updateDocument(string $collection, string $id, Document $document): Document + public function updateDocument(string $collection, string $id, Document $document, bool $skipPermissions): Document { $attributes = $document->getAttributes(); $attributes['_createdAt'] = $document->getCreatedAt(); @@ -1062,6 +1063,7 @@ public function updateDocument(string $collection, string $id, Document $documen $name = $this->filter($collection); $columns = ''; + if (!$skipPermissions) { $sql = " SELECT _type, _permission FROM {$this->getSQLTable($name . '_perms')} @@ -1127,7 +1129,7 @@ public function updateDocument(string $collection, string $id, Document $documen foreach ($removals as $type => $permissions) { $removeQuery .= "( _type = '{$type}' - AND _permission IN (" . implode(', ', \array_map(fn (string $i) => ":_remove_{$type}_{$i}", \array_keys($permissions))) . ") + AND _permission IN (" . implode(', ', \array_map(fn(string $i) => ":_remove_{$type}_{$i}", \array_keys($permissions))) . ") )"; if ($type !== \array_key_last($removals)) { $removeQuery .= ' OR '; @@ -1193,6 +1195,7 @@ public function updateDocument(string $collection, string $id, Document $documen } } } + } /** * Update Attributes diff --git a/src/Database/Adapter/SQLite.php b/src/Database/Adapter/SQLite.php index be2c4b5f7..6b41634fc 100644 --- a/src/Database/Adapter/SQLite.php +++ b/src/Database/Adapter/SQLite.php @@ -629,13 +629,15 @@ public function createDocument(string $collection, Document $document): Document * Update Document * * @param string $collection + * @param string $id * @param Document $document + * @param bool $skipPermissions * @return Document * @throws Exception * @throws PDOException * @throws Duplicate */ - public function updateDocument(string $collection, string $id, Document $document): Document + public function updateDocument(string $collection, string $id, Document $document, bool $skipPermissions): Document { $attributes = $document->getAttributes(); $attributes['_createdAt'] = $document->getCreatedAt(); diff --git a/src/Database/Database.php b/src/Database/Database.php index f5276c787..c909d0818 100644 --- a/src/Database/Database.php +++ b/src/Database/Database.php @@ -3590,6 +3590,10 @@ public function createDocument(string $collection, Document $document): Document ->setAttribute('$createdAt', empty($createdAt) || !$this->preserveDates ? $time : $createdAt) ->setAttribute('$updatedAt', empty($updatedAt) || !$this->preserveDates ? $time : $updatedAt); + if (!$document->offsetExists('$permissions')){ + $document->setAttribute('$permissions', []); + } + if ($this->adapter->getSharedTables()) { if ($this->adapter->getTenantPerDocument()) { if ( @@ -4091,6 +4095,18 @@ public function updateDocument(string $collection, string $id, Document $documen fn () => $this->getDocument($collection->getId(), $id, forUpdate: true) )); + $skipPermissionsUpdate = false; + + if ($document->offsetExists('$permissions')){ + $originalPermissions = $old->getPermissions(); + $currentPermissions = $document->getPermissions(); + + sort($originalPermissions); + sort($currentPermissions); + + $skipPermissionsUpdate = ($originalPermissions === $currentPermissions); + } + $document = \array_merge($old->getArrayCopy(), $document->getArrayCopy()); $document['$collection'] = $old->getAttribute('$collection'); // Make sure user doesn't switch collection ID $document['$createdAt'] = $old->getCreatedAt(); // Make sure user doesn't switch createdAt @@ -4247,7 +4263,7 @@ public function updateDocument(string $collection, string $id, Document $documen $document = $this->silent(fn () => $this->updateDocumentRelationships($collection, $old, $document)); } - $this->adapter->updateDocument($collection->getId(), $id, $document); + $this->adapter->updateDocument($collection->getId(), $id, $document, $skipPermissionsUpdate); $this->purgeCachedDocument($collection->getId(), $id); return $document; @@ -4913,12 +4929,21 @@ public function createOrUpdateDocumentsWithIncrease( ))); } - $updatesPermissions = \in_array('$permissions', \array_keys($document->getArrayCopy())) - && $document->getPermissions() != $old->getPermissions(); + $skipPermissionsUpdate = false; + + if ($document->offsetExists('$permissions')){ + $originalPermissions = $old->getPermissions(); + $currentPermissions = $document->getPermissions(); + + sort($originalPermissions); + sort($currentPermissions); + + $skipPermissionsUpdate = ($originalPermissions === $currentPermissions); + } if ( empty($attribute) - && !$updatesPermissions + && $skipPermissionsUpdate && $old->getAttributes() == $document->getAttributes() ) { // If not updating a single attribute and the @@ -4974,7 +4999,7 @@ public function createOrUpdateDocumentsWithIncrease( } } - if (!$updatesPermissions) { + if ($skipPermissionsUpdate) { $document->setAttribute('$permissions', $old->getPermissions()); } From 515f6c15f826912490ca0cffb5e66e1180234660 Mon Sep 17 00:00:00 2001 From: fogelito Date: Thu, 24 Jul 2025 17:06:43 +0300 Subject: [PATCH 2/8] formatting --- src/Database/Adapter/Postgres.php | 174 +++++++++++++++--------------- src/Database/Adapter/SQLite.php | 172 ++++++++++++++--------------- src/Database/Database.php | 6 +- 3 files changed, 177 insertions(+), 175 deletions(-) diff --git a/src/Database/Adapter/Postgres.php b/src/Database/Adapter/Postgres.php index 190d32987..92e44f249 100644 --- a/src/Database/Adapter/Postgres.php +++ b/src/Database/Adapter/Postgres.php @@ -1064,138 +1064,138 @@ public function updateDocument(string $collection, string $id, Document $documen $columns = ''; if (!$skipPermissions) { - $sql = " + $sql = " SELECT _type, _permission FROM {$this->getSQLTable($name . '_perms')} WHERE _document = :_uid {$this->getTenantQuery($collection)} "; - $sql = $this->trigger(Database::EVENT_PERMISSIONS_READ, $sql); + $sql = $this->trigger(Database::EVENT_PERMISSIONS_READ, $sql); - /** - * Get current permissions from the database - */ - $permissionsStmt = $this->getPDO()->prepare($sql); - $permissionsStmt->bindValue(':_uid', $document->getId()); + /** + * Get current permissions from the database + */ + $permissionsStmt = $this->getPDO()->prepare($sql); + $permissionsStmt->bindValue(':_uid', $document->getId()); - if ($this->sharedTables) { - $permissionsStmt->bindValue(':_tenant', $this->tenant); - } + if ($this->sharedTables) { + $permissionsStmt->bindValue(':_tenant', $this->tenant); + } - $this->execute($permissionsStmt); - $permissions = $permissionsStmt->fetchAll(); - $permissionsStmt->closeCursor(); + $this->execute($permissionsStmt); + $permissions = $permissionsStmt->fetchAll(); + $permissionsStmt->closeCursor(); - $initial = []; - foreach (Database::PERMISSIONS as $type) { - $initial[$type] = []; - } + $initial = []; + foreach (Database::PERMISSIONS as $type) { + $initial[$type] = []; + } - $permissions = array_reduce($permissions, function (array $carry, array $item) { - $carry[$item['_type']][] = $item['_permission']; + $permissions = array_reduce($permissions, function (array $carry, array $item) { + $carry[$item['_type']][] = $item['_permission']; - return $carry; - }, $initial); + return $carry; + }, $initial); - /** - * Get removed Permissions - */ - $removals = []; - foreach (Database::PERMISSIONS as $type) { - $diff = \array_diff($permissions[$type], $document->getPermissionsByType($type)); - if (!empty($diff)) { - $removals[$type] = $diff; + /** + * Get removed Permissions + */ + $removals = []; + foreach (Database::PERMISSIONS as $type) { + $diff = \array_diff($permissions[$type], $document->getPermissionsByType($type)); + if (!empty($diff)) { + $removals[$type] = $diff; + } } - } - /** - * Get added Permissions - */ - $additions = []; - foreach (Database::PERMISSIONS as $type) { - $diff = \array_diff($document->getPermissionsByType($type), $permissions[$type]); - if (!empty($diff)) { - $additions[$type] = $diff; + /** + * Get added Permissions + */ + $additions = []; + foreach (Database::PERMISSIONS as $type) { + $diff = \array_diff($document->getPermissionsByType($type), $permissions[$type]); + if (!empty($diff)) { + $additions[$type] = $diff; + } } - } - /** - * Query to remove permissions - */ - $removeQuery = ''; - if (!empty($removals)) { - $removeQuery = ' AND ('; - foreach ($removals as $type => $permissions) { - $removeQuery .= "( + /** + * Query to remove permissions + */ + $removeQuery = ''; + if (!empty($removals)) { + $removeQuery = ' AND ('; + foreach ($removals as $type => $permissions) { + $removeQuery .= "( _type = '{$type}' - AND _permission IN (" . implode(', ', \array_map(fn(string $i) => ":_remove_{$type}_{$i}", \array_keys($permissions))) . ") + AND _permission IN (" . implode(', ', \array_map(fn (string $i) => ":_remove_{$type}_{$i}", \array_keys($permissions))) . ") )"; - if ($type !== \array_key_last($removals)) { - $removeQuery .= ' OR '; + if ($type !== \array_key_last($removals)) { + $removeQuery .= ' OR '; + } } } - } - if (!empty($removeQuery)) { - $removeQuery .= ')'; + if (!empty($removeQuery)) { + $removeQuery .= ')'; - $sql = " + $sql = " DELETE FROM {$this->getSQLTable($name . '_perms')} WHERE _document = :_uid {$this->getTenantQuery($collection)} "; - $removeQuery = $sql . $removeQuery; + $removeQuery = $sql . $removeQuery; - $removeQuery = $this->trigger(Database::EVENT_PERMISSIONS_DELETE, $removeQuery); - $stmtRemovePermissions = $this->getPDO()->prepare($removeQuery); - $stmtRemovePermissions->bindValue(':_uid', $document->getId()); + $removeQuery = $this->trigger(Database::EVENT_PERMISSIONS_DELETE, $removeQuery); + $stmtRemovePermissions = $this->getPDO()->prepare($removeQuery); + $stmtRemovePermissions->bindValue(':_uid', $document->getId()); - if ($this->sharedTables) { - $stmtRemovePermissions->bindValue(':_tenant', $this->tenant); - } + if ($this->sharedTables) { + $stmtRemovePermissions->bindValue(':_tenant', $this->tenant); + } - foreach ($removals as $type => $permissions) { - foreach ($permissions as $i => $permission) { - $stmtRemovePermissions->bindValue(":_remove_{$type}_{$i}", $permission); + foreach ($removals as $type => $permissions) { + foreach ($permissions as $i => $permission) { + $stmtRemovePermissions->bindValue(":_remove_{$type}_{$i}", $permission); + } } } - } - /** - * Query to add permissions - */ - if (!empty($additions)) { - $values = []; - foreach ($additions as $type => $permissions) { - foreach ($permissions as $i => $_) { - $sqlTenant = $this->sharedTables ? ', :_tenant' : ''; - $values[] = "( :_uid, '{$type}', :_add_{$type}_{$i} {$sqlTenant})"; + /** + * Query to add permissions + */ + if (!empty($additions)) { + $values = []; + foreach ($additions as $type => $permissions) { + foreach ($permissions as $i => $_) { + $sqlTenant = $this->sharedTables ? ', :_tenant' : ''; + $values[] = "( :_uid, '{$type}', :_add_{$type}_{$i} {$sqlTenant})"; + } } - } - $sqlTenant = $this->sharedTables ? ', _tenant' : ''; + $sqlTenant = $this->sharedTables ? ', _tenant' : ''; - $sql = " + $sql = " INSERT INTO {$this->getSQLTable($name . '_perms')} (_document, _type, _permission {$sqlTenant}) VALUES" . \implode(', ', $values); - $sql = $this->trigger(Database::EVENT_PERMISSIONS_CREATE, $sql); + $sql = $this->trigger(Database::EVENT_PERMISSIONS_CREATE, $sql); - $stmtAddPermissions = $this->getPDO()->prepare($sql); - $stmtAddPermissions->bindValue(":_uid", $document->getId()); - if ($this->sharedTables) { - $stmtAddPermissions->bindValue(':_tenant', $this->tenant); - } + $stmtAddPermissions = $this->getPDO()->prepare($sql); + $stmtAddPermissions->bindValue(":_uid", $document->getId()); + if ($this->sharedTables) { + $stmtAddPermissions->bindValue(':_tenant', $this->tenant); + } - foreach ($additions as $type => $permissions) { - foreach ($permissions as $i => $permission) { - $stmtAddPermissions->bindValue(":_add_{$type}_{$i}", $permission); + foreach ($additions as $type => $permissions) { + foreach ($permissions as $i => $permission) { + $stmtAddPermissions->bindValue(":_add_{$type}_{$i}", $permission); + } } } } - } /** * Update Attributes diff --git a/src/Database/Adapter/SQLite.php b/src/Database/Adapter/SQLite.php index 6b41634fc..0c8d502c6 100644 --- a/src/Database/Adapter/SQLite.php +++ b/src/Database/Adapter/SQLite.php @@ -651,134 +651,136 @@ public function updateDocument(string $collection, string $id, Document $documen $name = $this->filter($collection); $columns = ''; - $sql = " + if (!$skipPermissions) { + $sql = " SELECT _type, _permission FROM `{$this->getNamespace()}_{$name}_perms` WHERE _document = :_uid {$this->getTenantQuery($collection)} "; - $sql = $this->trigger(Database::EVENT_PERMISSIONS_READ, $sql); + $sql = $this->trigger(Database::EVENT_PERMISSIONS_READ, $sql); - /** - * Get current permissions from the database - */ - $permissionsStmt = $this->getPDO()->prepare($sql); - $permissionsStmt->bindValue(':_uid', $document->getId()); + /** + * Get current permissions from the database + */ + $permissionsStmt = $this->getPDO()->prepare($sql); + $permissionsStmt->bindValue(':_uid', $document->getId()); - if ($this->sharedTables) { - $permissionsStmt->bindValue(':_tenant', $this->tenant); - } + if ($this->sharedTables) { + $permissionsStmt->bindValue(':_tenant', $this->tenant); + } - $permissionsStmt->execute(); - $permissions = $permissionsStmt->fetchAll(); - $permissionsStmt->closeCursor(); + $permissionsStmt->execute(); + $permissions = $permissionsStmt->fetchAll(); + $permissionsStmt->closeCursor(); - $initial = []; - foreach (Database::PERMISSIONS as $type) { - $initial[$type] = []; - } + $initial = []; + foreach (Database::PERMISSIONS as $type) { + $initial[$type] = []; + } - $permissions = array_reduce($permissions, function (array $carry, array $item) { - $carry[$item['_type']][] = $item['_permission']; + $permissions = array_reduce($permissions, function (array $carry, array $item) { + $carry[$item['_type']][] = $item['_permission']; - return $carry; - }, $initial); + return $carry; + }, $initial); - /** - * Get removed Permissions - */ - $removals = []; - foreach (Database::PERMISSIONS as $type) { - $diff = \array_diff($permissions[$type], $document->getPermissionsByType($type)); - if (!empty($diff)) { - $removals[$type] = $diff; + /** + * Get removed Permissions + */ + $removals = []; + foreach (Database::PERMISSIONS as $type) { + $diff = \array_diff($permissions[$type], $document->getPermissionsByType($type)); + if (!empty($diff)) { + $removals[$type] = $diff; + } } - } - /** - * Get added Permissions - */ - $additions = []; - foreach (Database::PERMISSIONS as $type) { - $diff = \array_diff($document->getPermissionsByType($type), $permissions[$type]); - if (!empty($diff)) { - $additions[$type] = $diff; + /** + * Get added Permissions + */ + $additions = []; + foreach (Database::PERMISSIONS as $type) { + $diff = \array_diff($document->getPermissionsByType($type), $permissions[$type]); + if (!empty($diff)) { + $additions[$type] = $diff; + } } - } - /** - * Query to remove permissions - */ - $removeQuery = ''; - if (!empty($removals)) { - $removeQuery = ' AND ('; - foreach ($removals as $type => $permissions) { - $removeQuery .= "( + /** + * Query to remove permissions + */ + $removeQuery = ''; + if (!empty($removals)) { + $removeQuery = ' AND ('; + foreach ($removals as $type => $permissions) { + $removeQuery .= "( _type = '{$type}' AND _permission IN (" . implode(', ', \array_map(fn (string $i) => ":_remove_{$type}_{$i}", \array_keys($permissions))) . ") )"; - if ($type !== \array_key_last($removals)) { - $removeQuery .= ' OR '; + if ($type !== \array_key_last($removals)) { + $removeQuery .= ' OR '; + } } } - } - if (!empty($removeQuery)) { - $removeQuery .= ')'; - $sql = " + if (!empty($removeQuery)) { + $removeQuery .= ')'; + $sql = " DELETE FROM `{$this->getNamespace()}_{$name}_perms` WHERE _document = :_uid {$this->getTenantQuery($collection)} "; - $removeQuery = $sql . $removeQuery; - $removeQuery = $this->trigger(Database::EVENT_PERMISSIONS_DELETE, $removeQuery); + $removeQuery = $sql . $removeQuery; + $removeQuery = $this->trigger(Database::EVENT_PERMISSIONS_DELETE, $removeQuery); - $stmtRemovePermissions = $this->getPDO()->prepare($removeQuery); - $stmtRemovePermissions->bindValue(':_uid', $document->getId()); + $stmtRemovePermissions = $this->getPDO()->prepare($removeQuery); + $stmtRemovePermissions->bindValue(':_uid', $document->getId()); - if ($this->sharedTables) { - $stmtRemovePermissions->bindValue(':_tenant', $this->tenant); - } + if ($this->sharedTables) { + $stmtRemovePermissions->bindValue(':_tenant', $this->tenant); + } - foreach ($removals as $type => $permissions) { - foreach ($permissions as $i => $permission) { - $stmtRemovePermissions->bindValue(":_remove_{$type}_{$i}", $permission); + foreach ($removals as $type => $permissions) { + foreach ($permissions as $i => $permission) { + $stmtRemovePermissions->bindValue(":_remove_{$type}_{$i}", $permission); + } } } - } - /** - * Query to add permissions - */ - if (!empty($additions)) { - $values = []; - foreach ($additions as $type => $permissions) { - foreach ($permissions as $i => $_) { - $tenantQuery = $this->sharedTables ? ', :_tenant' : ''; - $values[] = "(:_uid, '{$type}', :_add_{$type}_{$i} {$tenantQuery})"; + /** + * Query to add permissions + */ + if (!empty($additions)) { + $values = []; + foreach ($additions as $type => $permissions) { + foreach ($permissions as $i => $_) { + $tenantQuery = $this->sharedTables ? ', :_tenant' : ''; + $values[] = "(:_uid, '{$type}', :_add_{$type}_{$i} {$tenantQuery})"; + } } - } - $tenantQuery = $this->sharedTables ? ', _tenant' : ''; + $tenantQuery = $this->sharedTables ? ', _tenant' : ''; - $sql = " + $sql = " INSERT INTO `{$this->getNamespace()}_{$name}_perms` (_document, _type, _permission {$tenantQuery}) VALUES " . \implode(', ', $values); - $sql = $this->trigger(Database::EVENT_PERMISSIONS_CREATE, $sql); + $sql = $this->trigger(Database::EVENT_PERMISSIONS_CREATE, $sql); - $stmtAddPermissions = $this->getPDO()->prepare($sql); + $stmtAddPermissions = $this->getPDO()->prepare($sql); - $stmtAddPermissions->bindValue(":_uid", $document->getId()); - if ($this->sharedTables) { - $stmtAddPermissions->bindValue(":_tenant", $this->tenant); - } + $stmtAddPermissions->bindValue(":_uid", $document->getId()); + if ($this->sharedTables) { + $stmtAddPermissions->bindValue(":_tenant", $this->tenant); + } - foreach ($additions as $type => $permissions) { - foreach ($permissions as $i => $permission) { - $stmtAddPermissions->bindValue(":_add_{$type}_{$i}", $permission); + foreach ($additions as $type => $permissions) { + foreach ($permissions as $i => $permission) { + $stmtAddPermissions->bindValue(":_add_{$type}_{$i}", $permission); + } } } } diff --git a/src/Database/Database.php b/src/Database/Database.php index c909d0818..5ee9c4541 100644 --- a/src/Database/Database.php +++ b/src/Database/Database.php @@ -3590,7 +3590,7 @@ public function createDocument(string $collection, Document $document): Document ->setAttribute('$createdAt', empty($createdAt) || !$this->preserveDates ? $time : $createdAt) ->setAttribute('$updatedAt', empty($updatedAt) || !$this->preserveDates ? $time : $updatedAt); - if (!$document->offsetExists('$permissions')){ + if (!$document->offsetExists('$permissions')) { $document->setAttribute('$permissions', []); } @@ -4097,7 +4097,7 @@ public function updateDocument(string $collection, string $id, Document $documen $skipPermissionsUpdate = false; - if ($document->offsetExists('$permissions')){ + if ($document->offsetExists('$permissions')) { $originalPermissions = $old->getPermissions(); $currentPermissions = $document->getPermissions(); @@ -4931,7 +4931,7 @@ public function createOrUpdateDocumentsWithIncrease( $skipPermissionsUpdate = false; - if ($document->offsetExists('$permissions')){ + if ($document->offsetExists('$permissions')) { $originalPermissions = $old->getPermissions(); $currentPermissions = $document->getPermissions(); From 2e70c4b008582d7c28debaf21c19f2bf480415db Mon Sep 17 00:00:00 2001 From: fogelito Date: Thu, 24 Jul 2025 18:26:04 +0300 Subject: [PATCH 3/8] Return [] --- src/Database/Database.php | 3 +++ tests/e2e/Adapter/Scopes/PermissionTests.php | 14 ++++++++++++++ 2 files changed, 17 insertions(+) diff --git a/src/Database/Database.php b/src/Database/Database.php index 5ee9c4541..c6234f3b0 100644 --- a/src/Database/Database.php +++ b/src/Database/Database.php @@ -6385,6 +6385,9 @@ public function decode(Document $collection, Document $document, array $selectio $attributes = \array_merge($attributes, $this->getInternalAttributes()); foreach ($attributes as $attribute) { + if ($attribute['$id'] === '$permissions'){ + continue; + } $key = $attribute['$id'] ?? ''; $array = $attribute['array'] ?? false; $filters = $attribute['filters'] ?? []; diff --git a/tests/e2e/Adapter/Scopes/PermissionTests.php b/tests/e2e/Adapter/Scopes/PermissionTests.php index e2c82600f..cb6dc7728 100644 --- a/tests/e2e/Adapter/Scopes/PermissionTests.php +++ b/tests/e2e/Adapter/Scopes/PermissionTests.php @@ -14,6 +14,20 @@ trait PermissionTests { + public function testCreateDocumentsEmptyPermission(): void + { + /** @var Database $database */ + $database = static::getDatabase(); + + $database->createCollection(__FUNCTION__); + + $document = $database->createDocument(__FUNCTION__, new Document()); + + $this->assertArrayHasKey('$permissions', $document); + $this->assertEquals([], $document->getPermissions()); + $this->assertEquals([], $document->getAttribute('$permissions')); + } + public function testReadPermissionsFailure(): Document { Authorization::cleanRoles(); From 41b242043dbfb0b04ec2042e32a929455987b43d Mon Sep 17 00:00:00 2001 From: fogelito Date: Thu, 24 Jul 2025 18:26:54 +0300 Subject: [PATCH 4/8] Remove continue --- src/Database/Database.php | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/Database/Database.php b/src/Database/Database.php index c6234f3b0..5ee9c4541 100644 --- a/src/Database/Database.php +++ b/src/Database/Database.php @@ -6385,9 +6385,6 @@ public function decode(Document $collection, Document $document, array $selectio $attributes = \array_merge($attributes, $this->getInternalAttributes()); foreach ($attributes as $attribute) { - if ($attribute['$id'] === '$permissions'){ - continue; - } $key = $attribute['$id'] ?? ''; $array = $attribute['array'] ?? false; $filters = $attribute['filters'] ?? []; From 46bff95c818f04327ec7ada680baf2b89cc34963 Mon Sep 17 00:00:00 2001 From: fogelito Date: Thu, 24 Jul 2025 18:39:40 +0300 Subject: [PATCH 5/8] Revert to a second PR --- src/Database/Database.php | 4 ---- tests/e2e/Adapter/Scopes/PermissionTests.php | 14 -------------- 2 files changed, 18 deletions(-) diff --git a/src/Database/Database.php b/src/Database/Database.php index 5ee9c4541..52a57884b 100644 --- a/src/Database/Database.php +++ b/src/Database/Database.php @@ -3590,10 +3590,6 @@ public function createDocument(string $collection, Document $document): Document ->setAttribute('$createdAt', empty($createdAt) || !$this->preserveDates ? $time : $createdAt) ->setAttribute('$updatedAt', empty($updatedAt) || !$this->preserveDates ? $time : $updatedAt); - if (!$document->offsetExists('$permissions')) { - $document->setAttribute('$permissions', []); - } - if ($this->adapter->getSharedTables()) { if ($this->adapter->getTenantPerDocument()) { if ( diff --git a/tests/e2e/Adapter/Scopes/PermissionTests.php b/tests/e2e/Adapter/Scopes/PermissionTests.php index cb6dc7728..e2c82600f 100644 --- a/tests/e2e/Adapter/Scopes/PermissionTests.php +++ b/tests/e2e/Adapter/Scopes/PermissionTests.php @@ -14,20 +14,6 @@ trait PermissionTests { - public function testCreateDocumentsEmptyPermission(): void - { - /** @var Database $database */ - $database = static::getDatabase(); - - $database->createCollection(__FUNCTION__); - - $document = $database->createDocument(__FUNCTION__, new Document()); - - $this->assertArrayHasKey('$permissions', $document); - $this->assertEquals([], $document->getPermissions()); - $this->assertEquals([], $document->getAttribute('$permissions')); - } - public function testReadPermissionsFailure(): Document { Authorization::cleanRoles(); From 15d95595928ac0cd1297d833e3e0a1f9e2c7bccd Mon Sep 17 00:00:00 2001 From: fogelito Date: Sun, 27 Jul 2025 08:15:58 +0300 Subject: [PATCH 6/8] Remove Reference --- src/Database/Document.php | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/Database/Document.php b/src/Database/Document.php index 43947096e..e30be6d36 100644 --- a/src/Database/Document.php +++ b/src/Database/Document.php @@ -35,22 +35,24 @@ public function __construct(array $input = []) throw new StructureException('$permissions must be of type array'); } - foreach ($input as $key => &$value) { + foreach ($input as $key => $value) { if (!\is_array($value)) { continue; } - if ((isset($value['$id']) || isset($value['$collection']))) { + + if (isset($value['$id']) || isset($value['$collection'])) { $input[$key] = new self($value); continue; } + foreach ($value as $childKey => $child) { if ((isset($child['$id']) || isset($child['$collection'])) && (!$child instanceof self)) { $value[$childKey] = new self($child); } } - } - unset($value); // Unset Reference + $input[$key] = $value; + } parent::__construct($input); } From b9ee1a7981c84c2988c61cfc6cf14064e712d570 Mon Sep 17 00:00:00 2001 From: fogelito Date: Sun, 27 Jul 2025 08:30:38 +0300 Subject: [PATCH 7/8] Lines --- src/Database/Document.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Database/Document.php b/src/Database/Document.php index e30be6d36..a226db0a7 100644 --- a/src/Database/Document.php +++ b/src/Database/Document.php @@ -39,12 +39,10 @@ public function __construct(array $input = []) if (!\is_array($value)) { continue; } - if (isset($value['$id']) || isset($value['$collection'])) { $input[$key] = new self($value); continue; } - foreach ($value as $childKey => $child) { if ((isset($child['$id']) || isset($child['$collection'])) && (!$child instanceof self)) { $value[$childKey] = new self($child); From 5b8a57d69c4b03ee7e176bab6b5f73b56c2b3f53 Mon Sep 17 00:00:00 2001 From: fogelito Date: Sun, 27 Jul 2025 08:42:06 +0300 Subject: [PATCH 8/8] Revert for a second PR --- src/Database/Document.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Database/Document.php b/src/Database/Document.php index a226db0a7..43947096e 100644 --- a/src/Database/Document.php +++ b/src/Database/Document.php @@ -35,11 +35,11 @@ public function __construct(array $input = []) throw new StructureException('$permissions must be of type array'); } - foreach ($input as $key => $value) { + foreach ($input as $key => &$value) { if (!\is_array($value)) { continue; } - if (isset($value['$id']) || isset($value['$collection'])) { + if ((isset($value['$id']) || isset($value['$collection']))) { $input[$key] = new self($value); continue; } @@ -48,10 +48,10 @@ public function __construct(array $input = []) $value[$childKey] = new self($child); } } - - $input[$key] = $value; } + unset($value); // Unset Reference + parent::__construct($input); }