Skip to content

Commit

Permalink
feat(access): the friends access is now an access collection
Browse files Browse the repository at this point in the history
Fixes #3391, #5038
Refs #8073, #3872
  • Loading branch information
jdalsem committed Dec 20, 2017
1 parent 4e666ab commit eccc971
Show file tree
Hide file tree
Showing 26 changed files with 418 additions and 130 deletions.
4 changes: 4 additions & 0 deletions docs/appendix/upgrade-notes/2.x-to-3.0.rst
Expand Up @@ -428,6 +428,10 @@ The user owned access collections are assumed to be used as Friends Collections
The groups access collection information was previously stored in the group_acl metadata. With the introduction of the ACL subtype
this information has been moved to the ACL subtype attribute.

The ``ACCESS_FRIENDS`` access_id has been migrated to an actual access collection (with the subtype ``friends``). All entities and annotations have been updated to use the new
access collection id. The access collection is created when a user is created. When a relationship of the type ``friends`` is created, the related guid will
also be added to the access collection. You can no longer save or update entities with the access id ``ACCESS_FRIENDS``.

Subtypes no longer have an ID
-----------------------------

Expand Down
2 changes: 1 addition & 1 deletion docs/contribute/code.rst
Expand Up @@ -517,7 +517,7 @@ Naming

* All other function names must begin with ``_elgg_``.

* Name globals and constants in ``ALL_CAPS`` (``ACCESS_FRIENDS``, ``$CONFIG``).
* Name globals and constants in ``ALL_CAPS`` (``ACCESS_PUBLIC``, ``$CONFIG``).

Miscellaneous
^^^^^^^^^^^^^
Expand Down
1 change: 0 additions & 1 deletion docs/design/database.rst
Expand Up @@ -653,7 +653,6 @@ Pre-defined access controls
- ``ACCESS_PRIVATE`` (value: 0) Private.
- ``ACCESS_LOGGED_IN`` (value: 1) Logged in users.
- ``ACCESS_PUBLIC`` (value: 2) Public data.
- ``ACCESS_FRIENDS`` (value: -2) Owner and his/her friends.

User defined access controls
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Expand Down
1 change: 0 additions & 1 deletion docs/guides/database.rst
Expand Up @@ -45,7 +45,6 @@ object will be private, and only the creator user will be able to see
it. Elgg defines constants for the special values of ``access_id``:

- **ACCESS_PRIVATE** Only the owner can see it
- **ACCESS_FRIENDS** Only the owner and his/her friends can see it
- **ACCESS_LOGGED_IN** Any logged in user can see it
- **ACCESS_PUBLIC** Even visitors not logged in can see it

Expand Down
23 changes: 19 additions & 4 deletions engine/classes/Elgg/Database/AccessCollections.php
Expand Up @@ -352,7 +352,22 @@ public function getWriteAccessArray($user_guid = 0, $flush = false, array $input
'user_id' => $user_guid,
'input_params' => $input_params,
];
return $this->hooks->trigger('access:collections:write', 'user', $options, $access_array);

$access_array = $this->hooks->trigger('access:collections:write', 'user', $options, $access_array);

// move logged in and public to the end of the array
foreach ([ACCESS_LOGGED_IN, ACCESS_PUBLIC] as $access) {
if (!isset($access_array[$access])) {
continue;
}

$temp = $access_array[$access];
unset($access_array[$access]);
$access_array[$access] = $temp;
}


return $access_array;
}

/**
Expand Down Expand Up @@ -841,9 +856,9 @@ public function getReadableAccessLevel($entity_access_id) {
$collection = $this->get($access);

$user_guid = $this->session->getLoggedInUserGuid();

if (!$collection || !$user_guid) {
// return 'Limited' if there is no logged in user or collection can not be loaded
if (!$collection || !$collection->canEdit()) {
// return 'Limited' if the collection can not be loaded or it can not be edited
return $translator->translate('access:limited:label');
}

Expand Down
14 changes: 0 additions & 14 deletions engine/classes/Elgg/Database/Clauses/AccessWhereClause.php
Expand Up @@ -71,20 +71,6 @@ public function prepare(QueryBuilder $qb, $table_alias = null) {

if (!$this->ignore_access) {
if ($this->viewer_guid) {
// include content of user's friends
$ors['friends_access'] = $qb->merge([
$qb->compare($alias($this->access_column), '=', ACCESS_FRIENDS, ELGG_VALUE_INTEGER),
$qb->compare(
$alias($this->owner_guid_column),
'in',
$qb->subquery('entity_relationships')
->select('guid_one')
->where($qb->compare('relationship', '=', 'friend', ELGG_VALUE_STRING))
->andWhere($qb->compare('guid_two', '=', $this->viewer_guid, ELGG_VALUE_INTEGER))
->getSQL()
)
]);

// include user's content
$ors['owner_access'] = $qb->compare($alias($this->owner_guid_column), '=', $this->viewer_guid, ELGG_VALUE_INTEGER);
}
Expand Down
133 changes: 133 additions & 0 deletions engine/classes/Elgg/Upgrades/MigrateFriendsACL.php
@@ -0,0 +1,133 @@
<?php

namespace Elgg\Upgrades;

use Elgg\Upgrade\Batch;
use Elgg\Upgrade\Result;

/**
* Creates user friends access collection and migrates entity access_id
*/
class MigrateFriendsACL implements Batch {

/**
* {@inheritdoc}
*/
public function getVersion() {
return 2017121200;
}

/**
* {@inheritdoc}
*/
public function needsIncrementOffset() {
return false;
}

/**
* {@inheritdoc}
*/
public function shouldBeSkipped() {
return empty($this->countItems());
}

/**
* {@inheritdoc}
*/
public function countItems() {
// users without a friends acl
$db = elgg()->getDb();

return elgg_get_entities([
'type' => 'user',
'count' => true,
'wheres' => [
"e.guid NOT IN (
SELECT acl.owner_guid FROM {$db->prefix}access_collections acl WHERE acl.subtype = 'friends'
)",
],
]);
}

/**
* {@inheritdoc}
*/
public function run(Result $result, $offset) {

$db = elgg()->getDb();

$users = elgg_get_entities([
'type' => 'user',
'limit' => 1,
'offset' => $offset,
'wheres' => [
"e.guid NOT IN (
SELECT acl.owner_guid FROM {$db->prefix}access_collections acl WHERE acl.subtype = 'friends'
)",
],
]);

if (empty($users)) {
// mark as complete
$result->addSuccesses(1);
return $result;
}

$user = $users[0];

// create acl
$acl_id = create_access_collection('friends', $user->guid, 'friends');
if (!$acl_id) {
$result->addError('Failed to create an ACL for user');
return $result;
}

$acl = get_access_collection($acl_id);

$this->addFriendsToACL($user, $acl);
$this->updateEntities($user, $acl);
$this->updateAnnotations($user, $acl);

$result->addSuccesses(1);

return $result;
}

protected function addFriendsToACL(\ElggUser $user, \ElggAccessCollection $acl) {
$friends = $user->getFriends(['batch' => true]);
foreach ($friends as $friend) {
$acl->addMember($friend->guid);
}
}

protected function updateEntities(\ElggUser $user, \ElggAccessCollection $acl) {
$entities = elgg_get_entities([
'type' => 'object',
'owner_guid' => $user->guid,
'access_id' => ACCESS_FRIENDS,
'limit' => false,
'batch' => true,
]);

foreach ($entities as $entity) {
$entity->access_id = $acl->id;
$entity->save();
}
}

protected function updateAnnotations(\ElggUser $user, \ElggAccessCollection $acl) {
$annotations = elgg_get_annotations([
'type' => 'object',
'owner_guid' => $user->guid,
'access_id' => ACCESS_FRIENDS,
'limit' => false,
'batch' => true,
]);

foreach ($annotations as $annotation) {
$annotation->access_id = $acl->id;
$annotation->save();
}
}

}
33 changes: 31 additions & 2 deletions engine/classes/ElggEntity.php
Expand Up @@ -926,6 +926,27 @@ public function getOwnedAccessCollections($options = []) {
$options['owner_guid'] = $this->guid;
return _elgg_services()->accessCollections->getEntityCollections($options);
}

/**
* Returns the first ACL owned by the entity with a given subtype
*
* @param string $subtype subtype of the ACL
*
* @return \ElggAccessCollection|false
*
* @since 3.0
*/
public function getOwnedAccessCollection($subtype) {
if (!is_string($subtype) || $subtype === '') {
return false;
}

$acls = $this->getOwnedAccessCollections([
'subtype' => $subtype,
]);

return elgg_extract(0, $acls, false);
}

/**
* Gets an array of entities with a relationship to this entity.
Expand Down Expand Up @@ -1327,7 +1348,11 @@ protected function create() {
$container_guid = (int) $container_guid;

if ($access_id == ACCESS_DEFAULT) {
throw new \InvalidParameterException('ACCESS_DEFAULT is not a valid access level. See its documentation in elgglib.h');
throw new \InvalidParameterException('ACCESS_DEFAULT is not a valid access level. See its documentation in constants.php');
}

if ($access_id == ACCESS_FRIENDS) {
throw new \InvalidParameterException('ACCESS_FRIENDS is not a valid access level. See its documentation in constants.php');
}

$user_guid = elgg_get_logged_in_user_guid();
Expand Down Expand Up @@ -1458,7 +1483,11 @@ protected function update() {
$time = $this->getCurrentTime()->getTimestamp();

if ($access_id == ACCESS_DEFAULT) {
throw new \InvalidParameterException('ACCESS_DEFAULT is not a valid access level. See its documentation in elgglib.php');
throw new \InvalidParameterException('ACCESS_DEFAULT is not a valid access level. See its documentation in constants.php');
}

if ($access_id == ACCESS_FRIENDS) {
throw new \InvalidParameterException('ACCESS_FRIENDS is not a valid access level. See its documentation in constants.php');
}

// Update primary table
Expand Down

0 comments on commit eccc971

Please sign in to comment.