Elgg  Version 4.3
SubscriptionsService.php
Go to the documentation of this file.
1 <?php
2 
3 namespace Elgg\Notifications;
4 
5 use Elgg\Database;
13 
21 
25  const RELATIONSHIP_PREFIX = 'notify';
26 
30  const MUTE_NOTIFICATIONS_RELATIONSHIP = 'mute_notifications';
31 
35  protected $db;
36 
41 
45  protected $hooks;
46 
55  $this->db = $db;
56  $this->relationshipsTable = $relationshipsTable;
57  $this->hooks = $hooks;
58  }
59 
75  public function getNotificationEventSubscriptions(NotificationEvent $event, array $methods, array $exclude_guids_for_records = []) {
76 
77  if (empty($methods)) {
78  return [];
79  }
80 
81  $object = $event->getObject();
82  if (!$object instanceof \ElggData) {
83  return [];
84  }
85 
86  // get subscribers only for \ElggEntity if it isn't private
87  if (!$object instanceof \ElggEntity || $object->access_id === ACCESS_PRIVATE) {
88  return [];
89  }
90 
91  $guids = [
92  $object->owner_guid,
93  $object->container_guid,
94  ];
95  if ($object instanceof \ElggObject || $object instanceof \ElggGroup) {
96  $guids[] = $object->guid;
97  }
98 
99  $guids = array_diff($guids, $exclude_guids_for_records);
100  if (empty($guids)) {
101  return [];
102  }
103 
104  $subscriptions = [];
105  $records = $this->getSubscriptionRecords($guids, $methods, $object->type, $object->subtype, $event->getAction(), $event->getActorGUID());
106  foreach ($records as $record) {
107  if (empty($record->guid)) {
108  // happens when no records are found
109  continue;
110  }
111 
112  if (!isset($subscriptions[$record->guid])) {
113  $subscriptions[$record->guid] = [];
114  }
115 
116  $deliveryMethods = explode(',', $record->methods);
117  foreach ($deliveryMethods as $relationship) {
118  $relationship_array = explode(':', $relationship);
119 
120  $subscriptions[$record->guid][] = end($relationship_array);
121  }
122  }
123 
124  return $subscriptions;
125  }
126 
145  public function getSubscriptionsForContainer(int $container_guid, array $methods, string $type = null, string $subtype = null, string $action = null, int $actor_guid = 0) {
146 
147  if (empty($methods)) {
148  return [];
149  }
150 
151  $subscriptions = [];
152 
153  $records = $this->getSubscriptionRecords([$container_guid], $methods, $type, $subtype, $action, $actor_guid);
154  foreach ($records as $record) {
155  if (empty($record->guid)) {
156  // happens when no records are found
157  continue;
158  }
159 
160  if (!isset($subscriptions[$record->guid])) {
161  $subscriptions[$record->guid] = [];
162  }
163 
164  $deliveryMethods = explode(',', $record->methods);
165  foreach ($deliveryMethods as $relationship) {
166  $relationship_array = explode(':', $relationship);
167 
168  $subscriptions[$record->guid][] = end($relationship_array);
169  }
170  }
171 
172  return $subscriptions;
173  }
174 
190  public function addSubscription(int $user_guid, string $method, int $target_guid, string $type = null, string $subtype = null, string $action = null) {
192 
193  $rel = [
194  self::RELATIONSHIP_PREFIX,
195  ];
196 
197  if (!_elgg_services()->notifications->isRegisteredMethod($method)) {
198  return false;
199  }
200 
201  // remove the muted notification relationship
202  $this->unmuteNotifications($user_guid, $target_guid);
203 
204  if (!empty($type) && !empty($subtype) && !empty($action)) {
205  $rel[] = $type;
206  $rel[] = $subtype;
207  $rel[] = $action;
208  }
209 
210  $rel[] = $method;
211 
212  return $this->relationshipsTable->add($user_guid, implode(':', $rel), $target_guid);
213  }
214 
229  public function hasSubscription(int $user_guid, string $method, int $target_guid, string $type = null, string $subtype = null, string $action = null): bool {
231 
232  $rel = [
233  self::RELATIONSHIP_PREFIX,
234  ];
235 
236  if (!empty($type) && !empty($subtype) && !empty($action)) {
237  $rel[] = $type;
238  $rel[] = $subtype;
239  $rel[] = $action;
240  }
241 
242  $rel[] = $method;
243 
244  return $this->relationshipsTable->check($user_guid, implode(':', $rel), $target_guid) instanceof \ElggRelationship;
245  }
246 
257  public function hasSubscriptions(int $user_guid, int $target_guid, array $methods = []): bool {
258  if (empty($methods)) {
259  // all currently registered methods
260  $methods = _elgg_services()->notifications->getMethods();
261  }
262 
263  if (empty($methods)) {
264  // no methods available
265  return false;
266  }
267 
268  $select = Select::fromTable('entity_relationships');
269  $select->select('count(*) as total')
270  ->where($select->compare('guid_one', '=', $user_guid, ELGG_VALUE_GUID))
271  ->andWhere($select->compare('guid_two', '=', $target_guid, ELGG_VALUE_GUID));
272 
273  $ors = [];
274  foreach ($methods as $method) {
275  $ors[] = $select->compare('relationship', '=', self::RELATIONSHIP_PREFIX . ':' . $method, ELGG_VALUE_STRING);
276  $ors[] = $select->compare('relationship', 'like', self::RELATIONSHIP_PREFIX . ':%:' . $method, ELGG_VALUE_STRING);
277  }
278 
279  $select->andWhere($select->merge($ors, 'OR'));
280 
281  $result = $this->db->getDataRow($select);
282 
283  return (bool) $result->total;
284  }
285 
299  public function removeSubscription(int $user_guid, string $method, int $target_guid, string $type = null, string $subtype = null, string $action = null) {
301 
302  $rel = [
303  self::RELATIONSHIP_PREFIX,
304  ];
305 
306  if (!empty($type) && !empty($subtype) && !empty($action)) {
307  $rel[] = $type;
308  $rel[] = $subtype;
309  $rel[] = $action;
310  }
311 
312  $rel[] = $method;
313 
314  if (!$this->relationshipsTable->check($user_guid, implode(':', $rel), $target_guid)) {
315  // subscription doesn't exist
316  return true;
317  }
318 
319  return $this->relationshipsTable->remove($user_guid, implode(':', $rel), $target_guid);
320  }
321 
332  public function removeSubscriptions(int $user_guid, int $target_guid, array $methods = []): bool {
333  $delete = Delete::fromTable('entity_relationships');
334  $delete->where($delete->compare('guid_one', '=', $user_guid, ELGG_VALUE_GUID))
335  ->andWhere($delete->compare('guid_two', '=', $target_guid, ELGG_VALUE_GUID));
336 
337  if (empty($methods)) {
338  $delete->andWhere($delete->compare('relationship', 'like', self::RELATIONSHIP_PREFIX . ':%', ELGG_VALUE_STRING));
339  } else {
340  $ors = [];
341  foreach ($methods as $method) {
342  $ors[] = $delete->compare('relationship', '=', self::RELATIONSHIP_PREFIX . ':' . $method, ELGG_VALUE_STRING);
343  $ors[] = $delete->compare('relationship', 'like', self::RELATIONSHIP_PREFIX . ':%:' . $method, ELGG_VALUE_STRING);
344  }
345 
346  $delete->andWhere($delete->merge($ors, 'OR'));
347  }
348 
349  return (bool) $this->db->deleteData($delete);
350  }
351 
360  public function getSubscribers(int $target_guid, array $methods = []): array {
361  return elgg_get_entities([
362  'limit' => false,
363  'wheres' => [
364  function(QueryBuilder $qb, $main_alias) use ($target_guid) {
365  $rel = $qb->joinRelationshipTable($main_alias, 'guid', null, true);
366 
367  return $qb->compare("{$rel}.guid_two", '=', $target_guid, ELGG_VALUE_GUID);
368  },
369  function(QueryBuilder $qb, $main_alias) use ($methods) {
370  $rel = $qb->joinRelationshipTable($main_alias, 'guid', null, true);
371 
372  if (empty($methods)) {
373  return $qb->compare("{$rel}.relationship", 'like', self::RELATIONSHIP_PREFIX . ':%', ELGG_VALUE_STRING);
374  }
375 
376  $ors = [];
377  foreach ($methods as $method) {
378  $ors[] = $qb->compare("{$rel}.relationship", '=', self::RELATIONSHIP_PREFIX . ':' . $method, ELGG_VALUE_STRING);
379  $ors[] = $qb->compare("{$rel}.relationship", 'like', self::RELATIONSHIP_PREFIX . ':%:' . $method, ELGG_VALUE_STRING);
380  }
381 
382  return $qb->merge($ors, 'OR');
383  },
384  ],
385  ]);
386  }
387 
401  public function getEntitySubscriptions(int $target_guid = 0, int $user_guid = 0, array $methods = [], string $type = null, string $subtype = null, string $action = null): array {
403 
404  if (empty($target_guid) && empty($user_guid)) {
405  return [];
406  }
407 
408  if (empty($target_guid)) {
410  }
411 
412  return elgg_get_relationships([
413  'limit' => false,
414  'wheres' => [
415  function(QueryBuilder $qb, $main_alias) use ($target_guid) {
416  if (empty($target_guid)) {
417  return;
418  }
419 
420  return $qb->compare("{$main_alias}.guid_two", '=', $target_guid, ELGG_VALUE_GUID);
421  },
422  function(QueryBuilder $qb, $main_alias) use ($user_guid) {
423  if (empty($user_guid)) {
424  return;
425  }
426 
427  return $qb->compare("{$main_alias}.guid_one", '=', $user_guid, ELGG_VALUE_GUID);
428  },
429  function(QueryBuilder $qb, $main_alias) use ($methods, $type, $subtype, $action) {
430  if (empty($methods) && (empty($type) || empty($subtype) || empty($action))) {
431  return $qb->compare("{$main_alias}.relationship", 'like', self::RELATIONSHIP_PREFIX . ':%', ELGG_VALUE_STRING);
432  }
433 
434  if (!empty($methods)) {
435  if (empty($type) || empty($subtype) || empty($action)) {
436  // only methods
437  $ors = [];
438  foreach ($methods as $method) {
439  $ors[] = $qb->compare("{$main_alias}.relationship", '=', self::RELATIONSHIP_PREFIX . ':' . $method, ELGG_VALUE_STRING);
440  $ors[] = $qb->compare("{$main_alias}.relationship", 'like', self::RELATIONSHIP_PREFIX . ':%:' . $method, ELGG_VALUE_STRING);
441  }
442 
443  return $qb->merge($ors, 'OR');
444  } else {
445  // with type limitation
446  return $qb->compare("{$main_alias}.relationship", 'in', $this->getMethodRelationships($methods, $type, $subtype, $action), ELGG_VALUE_STRING);
447  }
448  }
449 
450  // only type limitation
451  return $qb->compare("{$main_alias}.relationship", 'like', self::RELATIONSHIP_PREFIX . ":{$type}:{$subtype}:{$action}:%", ELGG_VALUE_STRING);
452  },
453  ],
454  ]);
455  }
456 
465  public function muteNotifications(int $user_guid, int $target_guid): bool {
466  // remove all current subscriptions
467  $this->removeSubscriptions($user_guid, $target_guid);
468 
469  return $this->relationshipsTable->add($user_guid, self::MUTE_NOTIFICATIONS_RELATIONSHIP, $target_guid);
470  }
471 
480  public function unmuteNotifications(int $user_guid, int $target_guid): bool {
481  return $this->relationshipsTable->remove($user_guid, self::MUTE_NOTIFICATIONS_RELATIONSHIP, $target_guid);
482  }
483 
492  public function hasMutedNotifications(int $user_guid, int $target_guid): bool {
493  return $this->relationshipsTable->check($user_guid, self::MUTE_NOTIFICATIONS_RELATIONSHIP, $target_guid) instanceof \ElggRelationship;
494  }
495 
505  public function filterSubscriptions(array $subscriptions, NotificationEvent $event, bool $filter_muted = true): array {
506  // sanitize
507  // make methods unique and remove empties
508  $subscriptions = array_map(function($user_methods) {
509  return array_values(array_filter(array_unique($user_methods)));
510  }, $subscriptions);
511  $subscriptions = $this->filterDelayedEmailSubscribers($subscriptions);
512 
513  // apply filters
514  if ($filter_muted) {
515  $subscriptions = $this->filterMutedNotifications($subscriptions, $event);
516  $subscriptions = $this->filterTimedMutedSubscribers($subscriptions);
517  }
518 
519  return $subscriptions;
520  }
521 
536  protected function filterMutedNotifications(array $subscriptions, NotificationEvent $event): array {
537  $guids_to_check = [];
538 
539  // Event actor
540  $guids_to_check[] = $event->getActorGUID();
541 
542  // Event object
543  $entity = false;
544  $object = $event->getObject();
545  if ($object instanceof \ElggEntity) {
546  $entity = $object;
547  } elseif ($object instanceof \ElggAnnotation) {
548  $entity = $object->getEntity();
549  }
550 
551  if ($entity instanceof \ElggEntity) {
552  $guids_to_check[] = $entity->guid;
553  $guids_to_check[] = $entity->owner_guid;
554  $guids_to_check[] = $entity->container_guid;
555  }
556 
557  // are there GUIDs to check
558  $guids_to_check = array_filter($guids_to_check);
559  if (empty($guids_to_check)) {
560  return $subscriptions;
561  }
562 
563  // get muted relations
564  $select = Select::fromTable('entity_relationships');
565  $select->select('guid_one')
566  ->where($select->compare('relationship', '=', self::MUTE_NOTIFICATIONS_RELATIONSHIP, ELGG_VALUE_STRING))
567  ->andWhere($select->compare('guid_two', 'in', $guids_to_check, ELGG_VALUE_GUID));
568 
569  $muted = $this->db->getData($select, function($row) {
570  return (int) $row->guid_one;
571  });
572 
573  // filter subscriptions
574  return array_diff_key($subscriptions, array_flip($muted));
575  }
576 
584  protected function filterDelayedEmailSubscribers(array $subscriptions): array {
585  return array_map(function ($user_methods) {
586  if (!in_array('delayed_email', $user_methods) || !in_array('email', $user_methods)) {
587  return $user_methods;
588  }
589  $pos = array_search('delayed_email', $user_methods);
590  unset($user_methods[$pos]);
591 
592  return array_values($user_methods);
593  }, $subscriptions);
594  }
595 
603  protected function filterTimedMutedSubscribers(array $subscriptions): array {
604  $muted = Entities::find([
605  'type' => 'user',
606  'guids' => array_keys($subscriptions),
607  'limit' => false,
608  'callback' => function ($row) {
609  return (int) $row->guid;
610  },
611  'private_setting_name_value_pairs' => [
612  [
613  'name' => 'timed_muting_start',
614  'value' => time(),
615  'operand' => '<=',
616  ],
617  [
618  'name' => 'timed_muting_end',
619  'value' => time(),
620  'operand' => '>=',
621  ],
622  ],
623  ]);
624 
625  return array_diff_key($subscriptions, array_flip($muted));
626  }
627 
643  protected function getSubscriptionRecords(array $container_guid, array $methods, string $type = null, string $subtype = null, string $action = null, int $actor_guid = 0): array {
644  // create IN clause
645  $rels = $this->getMethodRelationships($methods, $type, $subtype, $action);
646  if (!$rels) {
647  return [];
648  }
649 
650  $container_guid = array_unique(array_filter($container_guid));
651  if (empty($container_guid)) {
652  return [];
653  }
654 
655  $select = Select::fromTable('entity_relationships');
656  $select->select('guid_one AS guid')
657  ->addSelect("GROUP_CONCAT(relationship SEPARATOR ',') AS methods")
658  ->where($select->compare('guid_two', 'in', $container_guid, ELGG_VALUE_GUID))
659  ->andWhere($select->compare('relationship', 'in', $rels, ELGG_VALUE_STRING))
660  ->groupBy('guid_one');
661 
662  if (!empty($actor_guid)) {
663  $select->andWhere($select->compare('guid_one', '!=', $actor_guid, ELGG_VALUE_GUID));
664  }
665 
666  return $this->db->getData($select);
667  }
668 
679  protected function getMethodRelationships(array $methods, string $type = null, string $subtype = null, string $action = null): array {
680  $prefix = self::RELATIONSHIP_PREFIX;
681 
682  $names = [];
683  foreach ($methods as $method) {
684  $names[] = "{$prefix}:{$method}";
685 
686  if (!empty($type) && !empty($subtype) && !empty($action)) {
687  $names[] = "{$prefix}:{$type}:{$subtype}:{$action}:{$method}";
688  }
689  }
690 
691  return $names;
692  }
693 
705  if (empty($type) && empty($subtype) && empty($action)) {
706  // all empty, this is valid
707  return;
708  }
709 
710  if (!empty($type) && !empty($subtype) && !empty($action)) {
711  // all set, also valid
712  return;
713  }
714 
715  throw new InvalidArgumentException('$type, $subtype and $action need to all be empty or all have a value');
716  }
717 }
filterTimedMutedSubscribers(array $subscriptions)
Filter users who have set a period in which not to receive notifications.
getObject()
Get the object of the event.
$user_guid
Definition: login_as.php:10
Exception thrown if an argument is not of the expected type.
getSubscribers(int $target_guid, array $methods=[])
Get all subscribers of the target guid.
static find(array $options=[])
Build and execute a new query from an array of legacy options.
Definition: Repository.php:86
$actor_guid
Definition: mute.php:22
getEntitySubscriptions(int $target_guid=0, int $user_guid=0, array $methods=[], string $type=null, string $subtype=null, string $action=null)
Get the current subscriptions for the given entity.
The Elgg database.
Definition: Database.php:25
getActorGUID()
Get the GUID of the actor.
c Accompany it with the information you received as to the offer to distribute corresponding source complete source code means all the source code for all modules it plus any associated interface definition plus the scripts used to control compilation and installation of the executable as a special the source code distributed need not include anything that is normally and so on of the operating system on which the executable unless that component itself accompanies the executable If distribution of executable or object code is made by offering access to copy from a designated then offering equivalent access to copy the source code from the same place counts as distribution of the source even though third parties are not compelled to copy the source along with the object code You may not or distribute the Program except as expressly provided under this License Any attempt otherwise to sublicense or distribute the Program is void
Definition: LICENSE.txt:215
getMethodRelationships(array $methods, string $type=null, string $subtype=null, string $action=null)
Get the relationship names for notifications.
$relationship
Elgg default relationship view.
Definition: default.php:10
const ELGG_VALUE_GUID
Definition: constants.php:128
hasSubscriptions(int $user_guid, int $target_guid, array $methods=[])
Check if any subscription exists.
Database abstraction query builder.
$delete
getAction()
Get the name of the action.
$type
Definition: delete.php:21
$target_guid
Definition: groups.php:25
__construct(Database $db, RelationshipsTable $relationshipsTable, PluginHooksService $hooks)
Constructor.
filterMutedNotifications(array $subscriptions, NotificationEvent $event)
Filter subscriptions based on muted notification settings related to the notification event...
const ACCESS_PRIVATE
Definition: constants.php:12
$entity
Definition: reset.php:8
elgg_get_entities(array $options=[])
Fetches/counts entities or performs a calculation on their properties.
Definition: entities.php:545
Relationships table database service.
removeSubscriptions(int $user_guid, int $target_guid, array $methods=[])
Unsubscribe a user from all notifications about the target entity.
compare($x, $comparison, $y=null, $type=null, $case_sensitive=null)
Build value comparison clause.
if(in_array('elgg-popup', $classes)) if(in_array('elgg-toggle', $classes)) $rel
Definition: url.php:155
const ELGG_ENTITIES_ANY_VALUE
Definition: constants.php:24
A generic class that contains shared code among , , and .
Definition: ElggData.php:10
$action
Definition: subscribe.php:11
static fromTable($table, $alias=null)
{}
Definition: Select.php:13
getNotificationEventSubscriptions(NotificationEvent $event, array $methods, array $exclude_guids_for_records=[])
Get the subscriptions for this notification event.
$guids
Activates all specified installed and inactive plugins.
Definition: activate_all.php:9
if($email instanceof\Elgg\Email) $object
Definition: body.php:24
removeSubscription(int $user_guid, string $method, int $target_guid, string $type=null, string $subtype=null, string $action=null)
Unsubscribe a user to notifications about a target entity.
merge($parts=null, $boolean= 'AND')
Merges multiple composite expressions with a boolean.
filterSubscriptions(array $subscriptions, NotificationEvent $event, bool $filter_muted=true)
Apply filtering to subscriptions, like muted notifications etc.
Notification event interface.
unmuteNotifications(int $user_guid, int $target_guid)
No longer nute notifications about events affecting the target.
if($item instanceof\ElggEntity) elseif($item instanceof\ElggRiverItem) elseif($item instanceof ElggRelationship) elseif(is_callable([$item, 'getType']))
Definition: item.php:48
const ELGG_VALUE_STRING
Definition: constants.php:127
addSubscription(int $user_guid, string $method, int $target_guid, string $type=null, string $subtype=null, string $action=null)
Subscribe a user to notifications about a target entity.
$subtype
Definition: delete.php:22
elgg_get_relationships(array $options=[])
Fetch relationships or perform a calculation on them.
hasSubscription(int $user_guid, string $method, int $target_guid, string $type=null, string $subtype=null, string $action=null)
Check if a subscription exists.
_elgg_services()
Get the global service provider.
Definition: elgglib.php:638
joinRelationshipTable($from_alias= '', $from_column= 'guid', $name=null, $inverse=false, $join_type= 'inner', $joined_alias=null)
Join relationship table from alias and return joined table alias.
getSubscriptionsForContainer(int $container_guid, array $methods, string $type=null, string $subtype=null, string $action=null, int $actor_guid=0)
Get the subscriptions for the content created inside this container.
if(empty($methods)) $subscriptions
muteNotifications(int $user_guid, int $target_guid)
Mute notifications about events affecting the target.
$container_guid
assertValidTypeSubtypeActionForSubscription($type, $subtype, $action)
Validate subscription input for type, subtype and action.
getSubscriptionRecords(array $container_guid, array $methods, string $type=null, string $subtype=null, string $action=null, int $actor_guid=0)
Get subscription records from the database.
filterDelayedEmailSubscribers(array $subscriptions)
When a user has both &#39;email&#39; and &#39;delayed_email&#39; subscription remove the delayed email as it would be...
$qb
Definition: queue.php:11
$methods
Definition: subscribe.php:8
static fromTable($table, $alias=null)
{}
Definition: Delete.php:13
hasMutedNotifications(int $user_guid, int $target_guid)
Check if the user has notifications muted about events affecting the target.