Elgg  Version 3.0
MigrateFriendsACL.php
Go to the documentation of this file.
1 <?php
2 
3 namespace Elgg\Upgrades;
4 
8 use ElggUser;
9 use Exception;
10 
15 
16  protected $prepared;
17 
21  public function getVersion() {
22  return 2017121200;
23  }
24 
28  public function needsIncrementOffset() {
29  return false;
30  }
31 
35  public function shouldBeSkipped() {
36  return empty($this->countItems());
37  }
38 
39  protected function getItems(array $options = []) {
40 
41  $options['types'] = 'user';
42  $options['wheres'][] = function(QueryBuilder $qb, $main_alias) {
43  $subquery = $qb->subquery('entities', 'e2');
44  $subquery->select('distinct(e2.owner_guid)')
45  ->where($qb->compare('e2.access_id', '=', ACCESS_FRIENDS, ELGG_VALUE_INTEGER));
46 
47  return $qb->compare("{$main_alias}.guid", 'IN', $subquery->getSQL());
48  };
49 
51  }
52 
53  protected function prepareMigration() {
54  if (!$this->isPreparationNeeded()) {
55  return;
56  }
57 
58  // create access collections in bulk
59  $this->createFriendsACLs();
60 
61  // add friends to acls in bulk
62  $this->addFriendsToACLs();
63 
64  // update annotations friends acl in bulk
65  $this->updateAnnotations();
66 
67  // save for future checks
68  $this->prepared = true;
69 
70  }
71 
72  protected function isPreparationNeeded() {
73 
74  if ($this->prepared === true) {
75  return false;
76  }
77 
78  // check if users are missing an acl
79  $total = $this->getItems(['count' => true]);
80  if ($total) {
81  return true;
82  }
83 
84  // check if friends are missing in friends acls
85  $check_friends = \Elgg\Database\Select::fromTable('entities', 'e');
86 
87  $check_friends_sub = $check_friends->subquery('access_collection_membership', 'mem');
88  $check_friends_sub->select('mem.user_guid')
89  ->join('mem', 'access_collections', 'acl', 'acl.id = mem.access_collection_id')
90  ->where($check_friends->compare('acl.owner_guid', '=', 'e.guid'))
91  ->andWhere($check_friends->compare('acl.subtype', '=', 'friends', ELGG_VALUE_STRING));
92 
93  $check_friends->select('count(r.guid_two) AS total');
94  $check_friends->joinRelationshipTable('e', 'guid', 'friend', true, 'inner', 'r');
95  $check_friends->where($check_friends->compare('e.type', '=', 'user', ELGG_VALUE_STRING))
96  ->andWhere($check_friends->compare('r.guid_two', 'NOT IN', $check_friends_sub->getSQL()));
97 
98  $total = $check_friends->execute()->fetchColumn();
99  if ($total) {
100  return true;
101  }
102 
103  // check if annotations have friends acl
104  $count_annotation = \Elgg\Database\Select::fromTable('annotations', 'a');
105  $count_annotation->select('count(*) AS total');
106  $count_annotation->joinEntitiesTable('a', 'owner_guid', 'inner', 'e');
107  $count_annotation->where($count_annotation->compare('a.access_id', '=', ACCESS_FRIENDS, ELGG_VALUE_INTEGER))
108  ->andWhere($count_annotation->compare('e.type', '=', 'user', ELGG_VALUE_STRING));
109 
110  $total = $count_annotation->execute()->fetchColumn();
111  if ($total) {
112  return true;
113  }
114 
115  // save for future checks
116  $this->prepared = true;
117 
118  return false;
119  }
120 
124  public function countItems() {
125  $count = (int) $this->isPreparationNeeded();
126 
127  $count += $this->getItems(['count' => true]);
128 
129  return $count;
130  }
131 
135  public function run(Result $result, $offset) {
136 
137  try {
138  $this->prepareMigration();
139  } catch (\Exception $e) {
140  $result->addError($e->getMessage());
141  $result->addFailures($this->countItems());
142  return $result;
143  }
144 
145  $users = $this->getItems([
146  'limit' => 10,
147  'offset' => $offset,
148  'batch' => true,
149  'batch_inc_offset' => false,
150  ]);
151 
152  if (empty($users)) {
153  // mark as complete
154  $result->addSuccesses(1);
155  return $result;
156  }
157 
158  /* @var $user ElggUser */
159  foreach ($users as $user) {
160  $acl = $user->getOwnedAccessCollection('friends');
161 
162  if (!$acl) {
163  $result->addError("Failed to find a friends ACL for [user: $user->guid]");
164  $result->addFailures(1);
165  continue;
166  }
167 
168  try {
169  $this->updateEntities($user, $acl);
170 
171  $result->addSuccesses(1);
172  } catch (Exception $ex) {
173  $result->addFailures(1);
174  $result->addError($ex->getMessage());
175  }
176  }
177 
178  return $result;
179  }
180 
181  protected function updateEntities(ElggUser $user, \ElggAccessCollection $acl) {
182  $entities = elgg_get_entities([
183  'type' => 'object',
184  'owner_guid' => $user->guid,
185  'access_id' => ACCESS_FRIENDS,
186  'limit' => false,
187  'batch' => true,
188  'batch_inc_offset' => false,
189  ]);
190 
191  foreach ($entities as $entity) {
192  $entity->access_id = $acl->id;
193  $entity->save();
194  }
195  }
196 
197  protected function createFriendsACLs() {
198 
199  $dbprefix = elgg_get_config('dbprefix');
200 
201  $query = "
202  INSERT INTO {$dbprefix}access_collections (name, subtype, owner_guid)
203  SELECT 'friends', 'friends', e.guid
204  FROM {$dbprefix}entities e
205  WHERE e.type = 'user'
206  AND e.guid NOT IN (
207  SELECT acl.owner_guid
208  FROM {$dbprefix}access_collections acl
209  WHERE acl.subtype = 'friends'
210  )
211  ";
212 
213  _elgg_services()->db->updateData($query);
214  }
215 
216  protected function addFriendsToACLs() {
217  $dbprefix = elgg_get_config('dbprefix');
218 
219  $query = "
220  INSERT INTO {$dbprefix}access_collection_membership (user_guid, access_collection_id)
221  SELECT r.guid_two, acl.id
222  FROM {$dbprefix}entity_relationships r
223  JOIN {$dbprefix}access_collections acl ON r.guid_one = acl.owner_guid
224  WHERE r.relationship = 'friend'
225  AND r.guid_two NOT IN (
226  SELECT subacl.user_guid
227  FROM {$dbprefix}access_collection_membership subacl
228  WHERE subacl.access_collection_id = acl.id
229  )
230  ";
231 
232  _elgg_services()->db->updateData($query);
233  }
234 
235  protected function updateAnnotations() {
236  $dbprefix = elgg_get_config('dbprefix');
237 
238  $query = "
239  UPDATE {$dbprefix}annotations a
240  JOIN {$dbprefix}access_collections acl ON a.owner_guid = acl.owner_guid AND acl.subtype = 'friends'
241  SET a.access_id = acl.id
242  WHERE a.access_id = " . ACCESS_FRIENDS;
243 
244  _elgg_services()->db->updateData($query);
245  }
246 }
Interface to be implement for asynchronous upgrades, i.e.
needsIncrementOffset()
{Should the run() method receive an offset representing all processed items?If true, run() will receive as $offset the number of items already processed. This is useful if you are only modifying data, and need to use the $offset in a function like elgg_get_entities*() to know how many to skip over.If false, run() will receive as $offset the total number of failures. This should be used if your process deletes or moves data out of the way of the process. E.g. if you delete 50 objects on each run(), you may still use the $offset to skip objects that already failed once.bool}
$query
Definition: groups.php:8
getVersion()
{Version of the upgrade.This tells the date when the upgrade was added. It consists of eight digits a...
const ACCESS_FRIENDS
Definition: constants.php:15
const ELGG_VALUE_INTEGER
Value types.
Definition: constants.php:138
if(!$count) $offset
Definition: pagination.php:26
Database abstraction query builder.
$options
Elgg admin footer.
Definition: footer.php:6
addSuccesses($num=1)
Set an item (or items) as successfully upgraded.
Definition: Result.php:68
$entity
Definition: reset.php:8
elgg_get_entities(array $options=[])
Fetches/counts entities or performs a calculation on their properties.
Definition: entities.php:545
addFailures($num=1)
Increment failure count.
Definition: Result.php:49
countItems()
{The total number of items to process during the upgrade.If unknown, Batch::UNKNOWN_COUNT should be r...
$user
Definition: ban.php:7
compare($x, $comparison, $y=null, $type=null, $case_sensitive=null)
Build value comparison clause.
elgg ElggUser
Definition: ElggUser.js:12
addError($message)
Add new error message to the batch.
Definition: Result.php:24
Result of a single BatchUpgrade run.
Definition: Result.php:8
shouldBeSkipped()
{Should this upgrade be skipped?If true, the upgrade will not be performed and cannot be accessed lat...
subquery($table, $alias=null)
Creates a new SelectQueryBuilder for join/where subqueries using the DB connection of the primary Que...
run(Result $result, $offset)
{Runs upgrade on a single batch of items.If countItems() returns Batch::UNKNOWN_COUNT, this method must call $result->markCompleted() when the upgrade is complete.Result of the batch (this must be returned) Number to skip when processingResult}
updateEntities(ElggUser $user,\ElggAccessCollection $acl)
Creates user friends access collection and migrates entity access_id.
const ELGG_VALUE_STRING
Definition: constants.php:139
if(elgg_in_context('widget')) $count
Definition: pagination.php:21
_elgg_services()
Get the global service provider.
Definition: elgglib.php:1292
elgg_get_config($name, $default=null)
Get an Elgg configuration value.