Elgg  Version master
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 
39  public function __construct(
40  protected Database $db,
41  protected RelationshipsTable $relationshipsTable,
42  protected EventsService $events
43  ) {
44  }
45 
61  public function getNotificationEventSubscriptions(NotificationEvent $event, array $methods, array $exclude_guids_for_records = []) {
62 
63  if (empty($methods)) {
64  return [];
65  }
66 
67  $object = $event->getObject();
68  if (!$object instanceof \ElggData) {
69  return [];
70  }
71 
72  // get subscribers only for \ElggEntity if it isn't private
73  if (!$object instanceof \ElggEntity || $object->access_id === ACCESS_PRIVATE) {
74  return [];
75  }
76 
77  $guids = [
78  $object->owner_guid,
79  $object->container_guid,
80  ];
81  if ($object instanceof \ElggObject || $object instanceof \ElggGroup) {
82  $guids[] = $object->guid;
83  }
84 
85  $guids = array_diff($guids, $exclude_guids_for_records);
86  if (empty($guids)) {
87  return [];
88  }
89 
90  $subscriptions = [];
91  $records = $this->getSubscriptionRecords($guids, $methods, $object->type, $object->subtype, $event->getAction(), $event->getActorGUID());
92  foreach ($records as $record) {
93  if (empty($record->guid)) {
94  // happens when no records are found
95  continue;
96  }
97 
98  if (!isset($subscriptions[$record->guid])) {
99  $subscriptions[$record->guid] = [];
100  }
101 
102  $deliveryMethods = explode(',', $record->methods);
103  foreach ($deliveryMethods as $relationship) {
104  $relationship_array = explode(':', $relationship);
105 
106  $subscriptions[$record->guid][] = end($relationship_array);
107  }
108  }
109 
110  return $subscriptions;
111  }
112 
131  public function getSubscriptionsForContainer(int $container_guid, array $methods, string $type = null, string $subtype = null, string $action = null, int $actor_guid = 0) {
132 
133  if (empty($methods)) {
134  return [];
135  }
136 
137  $subscriptions = [];
138 
139  $records = $this->getSubscriptionRecords([$container_guid], $methods, $type, $subtype, $action, $actor_guid);
140  foreach ($records as $record) {
141  if (empty($record->guid)) {
142  // happens when no records are found
143  continue;
144  }
145 
146  if (!isset($subscriptions[$record->guid])) {
147  $subscriptions[$record->guid] = [];
148  }
149 
150  $deliveryMethods = explode(',', $record->methods);
151  foreach ($deliveryMethods as $relationship) {
152  $relationship_array = explode(':', $relationship);
153 
154  $subscriptions[$record->guid][] = end($relationship_array);
155  }
156  }
157 
158  return $subscriptions;
159  }
160 
176  public function addSubscription(int $user_guid, string $method, int $target_guid, string $type = null, string $subtype = null, string $action = null) {
178 
179  $rel = [
180  self::RELATIONSHIP_PREFIX,
181  ];
182 
183  if (!_elgg_services()->notifications->isRegisteredMethod($method)) {
184  return false;
185  }
186 
187  // remove the muted notification relationship
188  $this->unmuteNotifications($user_guid, $target_guid);
189 
190  if (!empty($type) && !empty($subtype) && !empty($action)) {
191  $rel[] = $type;
192  $rel[] = $subtype;
193  $rel[] = $action;
194  }
195 
196  $rel[] = $method;
197 
198  $relationship = new \ElggRelationship();
199  $relationship->guid_one = $user_guid;
200  $relationship->relationship = implode(':', $rel);
201  $relationship->guid_two = $target_guid;
202 
203  return $relationship->save();
204  }
205 
220  public function hasSubscription(int $user_guid, string $method, int $target_guid, string $type = null, string $subtype = null, string $action = null): bool {
222 
223  $rel = [
224  self::RELATIONSHIP_PREFIX,
225  ];
226 
227  if (!empty($type) && !empty($subtype) && !empty($action)) {
228  $rel[] = $type;
229  $rel[] = $subtype;
230  $rel[] = $action;
231  }
232 
233  $rel[] = $method;
234 
235  return $this->relationshipsTable->check($user_guid, implode(':', $rel), $target_guid) instanceof \ElggRelationship;
236  }
237 
248  public function hasSubscriptions(int $user_guid, int $target_guid, array $methods = []): bool {
249  if (empty($methods)) {
250  // all currently registered methods
251  $methods = _elgg_services()->notifications->getMethods();
252  }
253 
254  if (empty($methods)) {
255  // no methods available
256  return false;
257  }
258 
260  $select->select('count(*) as total')
261  ->where($select->compare('guid_one', '=', $user_guid, ELGG_VALUE_GUID))
262  ->andWhere($select->compare('guid_two', '=', $target_guid, ELGG_VALUE_GUID));
263 
264  $ors = [];
265  foreach ($methods as $method) {
266  $ors[] = $select->compare('relationship', '=', self::RELATIONSHIP_PREFIX . ':' . $method, ELGG_VALUE_STRING);
267  $ors[] = $select->compare('relationship', 'like', self::RELATIONSHIP_PREFIX . ':%:' . $method, ELGG_VALUE_STRING);
268  }
269 
270  $select->andWhere($select->merge($ors, 'OR'));
271 
272  $result = $this->db->getDataRow($select);
273 
274  return (bool) $result->total;
275  }
276 
290  public function removeSubscription(int $user_guid, string $method, int $target_guid, string $type = null, string $subtype = null, string $action = null) {
292 
293  $rel = [
294  self::RELATIONSHIP_PREFIX,
295  ];
296 
297  if (!empty($type) && !empty($subtype) && !empty($action)) {
298  $rel[] = $type;
299  $rel[] = $subtype;
300  $rel[] = $action;
301  }
302 
303  $rel[] = $method;
304 
305  if (!$this->relationshipsTable->check($user_guid, implode(':', $rel), $target_guid)) {
306  // subscription doesn't exist
307  return true;
308  }
309 
310  return $this->relationshipsTable->remove($user_guid, implode(':', $rel), $target_guid);
311  }
312 
323  public function removeSubscriptions(int $user_guid, int $target_guid, array $methods = []): bool {
325  $delete->where($delete->compare('guid_one', '=', $user_guid, ELGG_VALUE_GUID))
326  ->andWhere($delete->compare('guid_two', '=', $target_guid, ELGG_VALUE_GUID));
327 
328  if (empty($methods)) {
329  $delete->andWhere($delete->compare('relationship', 'like', self::RELATIONSHIP_PREFIX . ':%', ELGG_VALUE_STRING));
330  } else {
331  $ors = [];
332  foreach ($methods as $method) {
333  $ors[] = $delete->compare('relationship', '=', self::RELATIONSHIP_PREFIX . ':' . $method, ELGG_VALUE_STRING);
334  $ors[] = $delete->compare('relationship', 'like', self::RELATIONSHIP_PREFIX . ':%:' . $method, ELGG_VALUE_STRING);
335  }
336 
337  $delete->andWhere($delete->merge($ors, 'OR'));
338  }
339 
340  return (bool) $this->db->deleteData($delete);
341  }
342 
351  public function getSubscribers(int $target_guid, array $methods = []): array {
352  return elgg_get_entities([
353  'limit' => false,
354  'wheres' => [
355  function(QueryBuilder $qb, $main_alias) use ($target_guid) {
356  $rel = $qb->joinRelationshipTable($main_alias, 'guid', null, true);
357 
358  return $qb->compare("{$rel}.guid_two", '=', $target_guid, ELGG_VALUE_GUID);
359  },
360  function(QueryBuilder $qb, $main_alias) use ($methods) {
361  $rel = $qb->joinRelationshipTable($main_alias, 'guid', null, true);
362 
363  if (empty($methods)) {
364  return $qb->compare("{$rel}.relationship", 'like', self::RELATIONSHIP_PREFIX . ':%', ELGG_VALUE_STRING);
365  }
366 
367  $ors = [];
368  foreach ($methods as $method) {
369  $ors[] = $qb->compare("{$rel}.relationship", '=', self::RELATIONSHIP_PREFIX . ':' . $method, ELGG_VALUE_STRING);
370  $ors[] = $qb->compare("{$rel}.relationship", 'like', self::RELATIONSHIP_PREFIX . ':%:' . $method, ELGG_VALUE_STRING);
371  }
372 
373  return $qb->merge($ors, 'OR');
374  },
375  ],
376  ]);
377  }
378 
392  public function getEntitySubscriptions(int $target_guid = 0, int $user_guid = 0, array $methods = [], string $type = null, string $subtype = null, string $action = null): array {
394 
395  if (empty($target_guid) && empty($user_guid)) {
396  return [];
397  }
398 
399  if (empty($target_guid)) {
401  }
402 
403  return elgg_get_relationships([
404  'limit' => false,
405  'wheres' => [
406  function(QueryBuilder $qb, $main_alias) use ($target_guid) {
407  if (empty($target_guid)) {
408  return;
409  }
410 
411  return $qb->compare("{$main_alias}.guid_two", '=', $target_guid, ELGG_VALUE_GUID);
412  },
413  function(QueryBuilder $qb, $main_alias) use ($user_guid) {
414  if (empty($user_guid)) {
415  return;
416  }
417 
418  return $qb->compare("{$main_alias}.guid_one", '=', $user_guid, ELGG_VALUE_GUID);
419  },
420  function(QueryBuilder $qb, $main_alias) use ($methods, $type, $subtype, $action) {
421  if (empty($methods) && (empty($type) || empty($subtype) || empty($action))) {
422  return $qb->compare("{$main_alias}.relationship", 'like', self::RELATIONSHIP_PREFIX . ':%', ELGG_VALUE_STRING);
423  }
424 
425  if (!empty($methods)) {
426  if (empty($type) || empty($subtype) || empty($action)) {
427  // only methods
428  $ors = [];
429  foreach ($methods as $method) {
430  $ors[] = $qb->compare("{$main_alias}.relationship", '=', self::RELATIONSHIP_PREFIX . ':' . $method, ELGG_VALUE_STRING);
431  $ors[] = $qb->compare("{$main_alias}.relationship", 'like', self::RELATIONSHIP_PREFIX . ':%:' . $method, ELGG_VALUE_STRING);
432  }
433 
434  return $qb->merge($ors, 'OR');
435  } else {
436  // with type limitation
437  return $qb->compare("{$main_alias}.relationship", 'in', $this->getMethodRelationships($methods, $type, $subtype, $action), ELGG_VALUE_STRING);
438  }
439  }
440 
441  // only type limitation
442  return $qb->compare("{$main_alias}.relationship", 'like', self::RELATIONSHIP_PREFIX . ":{$type}:{$subtype}:{$action}:%", ELGG_VALUE_STRING);
443  },
444  ],
445  ]);
446  }
447 
456  public function muteNotifications(int $user_guid, int $target_guid): bool {
457  // remove all current subscriptions
458  $this->removeSubscriptions($user_guid, $target_guid);
459 
460  $rel = new \ElggRelationship();
461  $rel->guid_one = $user_guid;
462  $rel->relationship = self::MUTE_NOTIFICATIONS_RELATIONSHIP;
463  $rel->guid_two = $target_guid;
464 
465  return $rel->save();
466  }
467 
476  public function unmuteNotifications(int $user_guid, int $target_guid): bool {
477  return $this->relationshipsTable->remove($user_guid, self::MUTE_NOTIFICATIONS_RELATIONSHIP, $target_guid);
478  }
479 
488  public function hasMutedNotifications(int $user_guid, int $target_guid): bool {
489  return $this->relationshipsTable->check($user_guid, self::MUTE_NOTIFICATIONS_RELATIONSHIP, $target_guid) instanceof \ElggRelationship;
490  }
491 
501  public function filterSubscriptions(array $subscriptions, NotificationEvent $event, bool $filter_muted = true): array {
502  // sanitize
503  // make methods unique and remove empties
504  $subscriptions = array_map(function($user_methods) {
505  return array_values(array_filter(array_unique($user_methods)));
506  }, $subscriptions);
507  $subscriptions = $this->filterDelayedEmailSubscribers($subscriptions);
508 
509  // apply filters
510  if ($filter_muted) {
511  $subscriptions = $this->filterMutedNotifications($subscriptions, $event);
512  $subscriptions = $this->filterTimedMutedSubscribers($subscriptions);
513  }
514 
515  return $subscriptions;
516  }
517 
532  protected function filterMutedNotifications(array $subscriptions, NotificationEvent $event): array {
533  $guids_to_check = [];
534 
535  // Event actor
536  $guids_to_check[] = $event->getActorGUID();
537 
538  // Event object
539  $entity = false;
540  $object = $event->getObject();
541  if ($object instanceof \ElggEntity) {
542  $entity = $object;
543  } elseif ($object instanceof \ElggAnnotation) {
544  $entity = $object->getEntity();
545  }
546 
547  if ($entity instanceof \ElggEntity) {
548  $guids_to_check[] = $entity->guid;
549  $guids_to_check[] = $entity->owner_guid;
550  $guids_to_check[] = $entity->container_guid;
551  }
552 
553  // are there GUIDs to check
554  $guids_to_check = array_filter($guids_to_check);
555  if (empty($guids_to_check)) {
556  return $subscriptions;
557  }
558 
559  // get muted relations
561  $select->select('guid_one')
562  ->where($select->compare('relationship', '=', self::MUTE_NOTIFICATIONS_RELATIONSHIP, ELGG_VALUE_STRING))
563  ->andWhere($select->compare('guid_two', 'in', $guids_to_check, ELGG_VALUE_GUID));
564 
565  $muted = $this->db->getData($select, function($row) {
566  return (int) $row->guid_one;
567  });
568 
569  // filter subscriptions
570  return array_diff_key($subscriptions, array_flip($muted));
571  }
572 
580  protected function filterDelayedEmailSubscribers(array $subscriptions): array {
581  return array_map(function ($user_methods) {
582  if (!in_array('delayed_email', $user_methods) || !in_array('email', $user_methods)) {
583  return $user_methods;
584  }
585 
586  $pos = array_search('delayed_email', $user_methods);
587  unset($user_methods[$pos]);
588 
589  return array_values($user_methods);
590  }, $subscriptions);
591  }
592 
600  protected function filterTimedMutedSubscribers(array $subscriptions): array {
601  $muted = Entities::find([
602  'type' => 'user',
603  'guids' => array_keys($subscriptions),
604  'limit' => false,
605  'callback' => function ($row) {
606  return (int) $row->guid;
607  },
608  'metadata_name_value_pairs' => [
609  [
610  'name' => 'timed_muting_start',
611  'value' => time(),
612  'operand' => '<=',
613  ],
614  [
615  'name' => 'timed_muting_end',
616  'value' => time(),
617  'operand' => '>=',
618  ],
619  ],
620  ]);
621 
622  return array_diff_key($subscriptions, array_flip($muted));
623  }
624 
640  protected function getSubscriptionRecords(array $container_guid, array $methods, string $type = null, string $subtype = null, string $action = null, int $actor_guid = 0): array {
641  // create IN clause
642  $rels = $this->getMethodRelationships($methods, $type, $subtype, $action);
643  if (!$rels) {
644  return [];
645  }
646 
647  $container_guid = array_unique(array_filter($container_guid));
648  if (empty($container_guid)) {
649  return [];
650  }
651 
653  $select->select('guid_one AS guid')
654  ->addSelect("GROUP_CONCAT(relationship SEPARATOR ',') AS methods")
655  ->where($select->compare('guid_two', 'in', $container_guid, ELGG_VALUE_GUID))
656  ->andWhere($select->compare('relationship', 'in', $rels, ELGG_VALUE_STRING))
657  ->groupBy('guid_one');
658 
659  if (!empty($actor_guid)) {
660  $select->andWhere($select->compare('guid_one', '!=', $actor_guid, ELGG_VALUE_GUID));
661  }
662 
663  return $this->db->getData($select);
664  }
665 
676  protected function getMethodRelationships(array $methods, string $type = null, string $subtype = null, string $action = null): array {
677  $prefix = self::RELATIONSHIP_PREFIX;
678 
679  $names = [];
680  foreach ($methods as $method) {
681  $names[] = "{$prefix}:{$method}";
682 
683  if (!empty($type) && !empty($subtype) && !empty($action)) {
684  $names[] = "{$prefix}:{$type}:{$subtype}:{$action}:{$method}";
685  }
686  }
687 
688  return $names;
689  }
690 
702  if (empty($type) && empty($subtype) && empty($action)) {
703  // all empty, this is valid
704  return;
705  }
706 
707  if (!empty($type) && !empty($subtype) && !empty($action)) {
708  // all set, also valid
709  return;
710  }
711 
712  throw new InvalidArgumentException('$type, $subtype and $action need to all be empty or all have a value');
713  }
714 }
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:110
$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:26
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
Entity Annotation.
const ELGG_VALUE_GUID
Definition: constants.php:113
Events service.
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:21
if($item instanceof\ElggEntity) elseif($item instanceof\ElggRiverItem) elseif($item instanceof\ElggRelationship) elseif(is_callable([$item, 'getType']))
Definition: item.php:48
filterMutedNotifications(array $subscriptions, NotificationEvent $event)
Filter subscriptions based on muted notification settings related to the notification event...
const ACCESS_PRIVATE
Definition: constants.php:10
$entity
Definition: reset.php:8
elgg_get_entities(array $options=[])
Fetches/counts entities or performs a calculation on their properties.
Definition: entities.php:507
foreach($recommendedExtensions as $extension) if(empty(ini_get('session.gc_probability'))||empty(ini_get('session.gc_divisor'))) $db
Relationships table database service.
compare(string $x, string $comparison, $y=null, string $type=null, bool $case_sensitive=null)
Build value comparison clause.
removeSubscriptions(int $user_guid, int $target_guid, array $methods=[])
Unsubscribe a user from all notifications about the target entity.
static fromTable(string $table)
Returns a QueryBuilder for deleting data from a given table.
Definition: Delete.php:17
const ELGG_ENTITIES_ANY_VALUE
Constant to request the value of a parameter be ignored in elgg_get_*() functions.
Definition: constants.php:21
A generic class that contains shared code among , , and .
Definition: ElggData.php:10
joinRelationshipTable(string $from_alias= '', string $from_column= 'guid', $name=null, bool $inverse=false,?string $join_type= 'inner', string $joined_alias=null)
Join relationship table from alias and return joined table alias.
$action
Definition: subscribe.php:11
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.
const ELGG_VALUE_STRING
Definition: constants.php:112
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.
static fromTable(string $table, string $alias=null)
Returns a QueryBuilder for selecting data from a given table.
Definition: Select.php:18
_elgg_services()
Get the global service provider.
Definition: elgglib.php:351
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:12
$methods
Definition: subscribe.php:8
hasMutedNotifications(int $user_guid, int $target_guid)
Check if the user has notifications muted about events affecting the target.
__construct(protected Database $db, protected RelationshipsTable $relationshipsTable, protected EventsService $events)
Constructor.