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  return $this->relationshipsTable->add($user_guid, implode(':', $rel), $target_guid);
199  }
200 
215  public function hasSubscription(int $user_guid, string $method, int $target_guid, string $type = null, string $subtype = null, string $action = null): bool {
217 
218  $rel = [
219  self::RELATIONSHIP_PREFIX,
220  ];
221 
222  if (!empty($type) && !empty($subtype) && !empty($action)) {
223  $rel[] = $type;
224  $rel[] = $subtype;
225  $rel[] = $action;
226  }
227 
228  $rel[] = $method;
229 
230  return $this->relationshipsTable->check($user_guid, implode(':', $rel), $target_guid) instanceof \ElggRelationship;
231  }
232 
243  public function hasSubscriptions(int $user_guid, int $target_guid, array $methods = []): bool {
244  if (empty($methods)) {
245  // all currently registered methods
246  $methods = _elgg_services()->notifications->getMethods();
247  }
248 
249  if (empty($methods)) {
250  // no methods available
251  return false;
252  }
253 
255  $select->select('count(*) as total')
256  ->where($select->compare('guid_one', '=', $user_guid, ELGG_VALUE_GUID))
257  ->andWhere($select->compare('guid_two', '=', $target_guid, ELGG_VALUE_GUID));
258 
259  $ors = [];
260  foreach ($methods as $method) {
261  $ors[] = $select->compare('relationship', '=', self::RELATIONSHIP_PREFIX . ':' . $method, ELGG_VALUE_STRING);
262  $ors[] = $select->compare('relationship', 'like', self::RELATIONSHIP_PREFIX . ':%:' . $method, ELGG_VALUE_STRING);
263  }
264 
265  $select->andWhere($select->merge($ors, 'OR'));
266 
267  $result = $this->db->getDataRow($select);
268 
269  return (bool) $result->total;
270  }
271 
285  public function removeSubscription(int $user_guid, string $method, int $target_guid, string $type = null, string $subtype = null, string $action = null) {
287 
288  $rel = [
289  self::RELATIONSHIP_PREFIX,
290  ];
291 
292  if (!empty($type) && !empty($subtype) && !empty($action)) {
293  $rel[] = $type;
294  $rel[] = $subtype;
295  $rel[] = $action;
296  }
297 
298  $rel[] = $method;
299 
300  if (!$this->relationshipsTable->check($user_guid, implode(':', $rel), $target_guid)) {
301  // subscription doesn't exist
302  return true;
303  }
304 
305  return $this->relationshipsTable->remove($user_guid, implode(':', $rel), $target_guid);
306  }
307 
318  public function removeSubscriptions(int $user_guid, int $target_guid, array $methods = []): bool {
320  $delete->where($delete->compare('guid_one', '=', $user_guid, ELGG_VALUE_GUID))
321  ->andWhere($delete->compare('guid_two', '=', $target_guid, ELGG_VALUE_GUID));
322 
323  if (empty($methods)) {
324  $delete->andWhere($delete->compare('relationship', 'like', self::RELATIONSHIP_PREFIX . ':%', ELGG_VALUE_STRING));
325  } else {
326  $ors = [];
327  foreach ($methods as $method) {
328  $ors[] = $delete->compare('relationship', '=', self::RELATIONSHIP_PREFIX . ':' . $method, ELGG_VALUE_STRING);
329  $ors[] = $delete->compare('relationship', 'like', self::RELATIONSHIP_PREFIX . ':%:' . $method, ELGG_VALUE_STRING);
330  }
331 
332  $delete->andWhere($delete->merge($ors, 'OR'));
333  }
334 
335  return (bool) $this->db->deleteData($delete);
336  }
337 
346  public function getSubscribers(int $target_guid, array $methods = []): array {
347  return elgg_get_entities([
348  'limit' => false,
349  'wheres' => [
350  function(QueryBuilder $qb, $main_alias) use ($target_guid) {
351  $rel = $qb->joinRelationshipTable($main_alias, 'guid', null, true);
352 
353  return $qb->compare("{$rel}.guid_two", '=', $target_guid, ELGG_VALUE_GUID);
354  },
355  function(QueryBuilder $qb, $main_alias) use ($methods) {
356  $rel = $qb->joinRelationshipTable($main_alias, 'guid', null, true);
357 
358  if (empty($methods)) {
359  return $qb->compare("{$rel}.relationship", 'like', self::RELATIONSHIP_PREFIX . ':%', ELGG_VALUE_STRING);
360  }
361 
362  $ors = [];
363  foreach ($methods as $method) {
364  $ors[] = $qb->compare("{$rel}.relationship", '=', self::RELATIONSHIP_PREFIX . ':' . $method, ELGG_VALUE_STRING);
365  $ors[] = $qb->compare("{$rel}.relationship", 'like', self::RELATIONSHIP_PREFIX . ':%:' . $method, ELGG_VALUE_STRING);
366  }
367 
368  return $qb->merge($ors, 'OR');
369  },
370  ],
371  ]);
372  }
373 
387  public function getEntitySubscriptions(int $target_guid = 0, int $user_guid = 0, array $methods = [], string $type = null, string $subtype = null, string $action = null): array {
389 
390  if (empty($target_guid) && empty($user_guid)) {
391  return [];
392  }
393 
394  if (empty($target_guid)) {
396  }
397 
398  return elgg_get_relationships([
399  'limit' => false,
400  'wheres' => [
401  function(QueryBuilder $qb, $main_alias) use ($target_guid) {
402  if (empty($target_guid)) {
403  return;
404  }
405 
406  return $qb->compare("{$main_alias}.guid_two", '=', $target_guid, ELGG_VALUE_GUID);
407  },
408  function(QueryBuilder $qb, $main_alias) use ($user_guid) {
409  if (empty($user_guid)) {
410  return;
411  }
412 
413  return $qb->compare("{$main_alias}.guid_one", '=', $user_guid, ELGG_VALUE_GUID);
414  },
415  function(QueryBuilder $qb, $main_alias) use ($methods, $type, $subtype, $action) {
416  if (empty($methods) && (empty($type) || empty($subtype) || empty($action))) {
417  return $qb->compare("{$main_alias}.relationship", 'like', self::RELATIONSHIP_PREFIX . ':%', ELGG_VALUE_STRING);
418  }
419 
420  if (!empty($methods)) {
421  if (empty($type) || empty($subtype) || empty($action)) {
422  // only methods
423  $ors = [];
424  foreach ($methods as $method) {
425  $ors[] = $qb->compare("{$main_alias}.relationship", '=', self::RELATIONSHIP_PREFIX . ':' . $method, ELGG_VALUE_STRING);
426  $ors[] = $qb->compare("{$main_alias}.relationship", 'like', self::RELATIONSHIP_PREFIX . ':%:' . $method, ELGG_VALUE_STRING);
427  }
428 
429  return $qb->merge($ors, 'OR');
430  } else {
431  // with type limitation
432  return $qb->compare("{$main_alias}.relationship", 'in', $this->getMethodRelationships($methods, $type, $subtype, $action), ELGG_VALUE_STRING);
433  }
434  }
435 
436  // only type limitation
437  return $qb->compare("{$main_alias}.relationship", 'like', self::RELATIONSHIP_PREFIX . ":{$type}:{$subtype}:{$action}:%", ELGG_VALUE_STRING);
438  },
439  ],
440  ]);
441  }
442 
451  public function muteNotifications(int $user_guid, int $target_guid): bool {
452  // remove all current subscriptions
453  $this->removeSubscriptions($user_guid, $target_guid);
454 
455  return $this->relationshipsTable->add($user_guid, self::MUTE_NOTIFICATIONS_RELATIONSHIP, $target_guid);
456  }
457 
466  public function unmuteNotifications(int $user_guid, int $target_guid): bool {
467  return $this->relationshipsTable->remove($user_guid, self::MUTE_NOTIFICATIONS_RELATIONSHIP, $target_guid);
468  }
469 
478  public function hasMutedNotifications(int $user_guid, int $target_guid): bool {
479  return $this->relationshipsTable->check($user_guid, self::MUTE_NOTIFICATIONS_RELATIONSHIP, $target_guid) instanceof \ElggRelationship;
480  }
481 
491  public function filterSubscriptions(array $subscriptions, NotificationEvent $event, bool $filter_muted = true): array {
492  // sanitize
493  // make methods unique and remove empties
494  $subscriptions = array_map(function($user_methods) {
495  return array_values(array_filter(array_unique($user_methods)));
496  }, $subscriptions);
497  $subscriptions = $this->filterDelayedEmailSubscribers($subscriptions);
498 
499  // apply filters
500  if ($filter_muted) {
501  $subscriptions = $this->filterMutedNotifications($subscriptions, $event);
502  $subscriptions = $this->filterTimedMutedSubscribers($subscriptions);
503  }
504 
505  return $subscriptions;
506  }
507 
522  protected function filterMutedNotifications(array $subscriptions, NotificationEvent $event): array {
523  $guids_to_check = [];
524 
525  // Event actor
526  $guids_to_check[] = $event->getActorGUID();
527 
528  // Event object
529  $entity = false;
530  $object = $event->getObject();
531  if ($object instanceof \ElggEntity) {
532  $entity = $object;
533  } elseif ($object instanceof \ElggAnnotation) {
534  $entity = $object->getEntity();
535  }
536 
537  if ($entity instanceof \ElggEntity) {
538  $guids_to_check[] = $entity->guid;
539  $guids_to_check[] = $entity->owner_guid;
540  $guids_to_check[] = $entity->container_guid;
541  }
542 
543  // are there GUIDs to check
544  $guids_to_check = array_filter($guids_to_check);
545  if (empty($guids_to_check)) {
546  return $subscriptions;
547  }
548 
549  // get muted relations
551  $select->select('guid_one')
552  ->where($select->compare('relationship', '=', self::MUTE_NOTIFICATIONS_RELATIONSHIP, ELGG_VALUE_STRING))
553  ->andWhere($select->compare('guid_two', 'in', $guids_to_check, ELGG_VALUE_GUID));
554 
555  $muted = $this->db->getData($select, function($row) {
556  return (int) $row->guid_one;
557  });
558 
559  // filter subscriptions
560  return array_diff_key($subscriptions, array_flip($muted));
561  }
562 
570  protected function filterDelayedEmailSubscribers(array $subscriptions): array {
571  return array_map(function ($user_methods) {
572  if (!in_array('delayed_email', $user_methods) || !in_array('email', $user_methods)) {
573  return $user_methods;
574  }
575 
576  $pos = array_search('delayed_email', $user_methods);
577  unset($user_methods[$pos]);
578 
579  return array_values($user_methods);
580  }, $subscriptions);
581  }
582 
590  protected function filterTimedMutedSubscribers(array $subscriptions): array {
591  $muted = Entities::find([
592  'type' => 'user',
593  'guids' => array_keys($subscriptions),
594  'limit' => false,
595  'callback' => function ($row) {
596  return (int) $row->guid;
597  },
598  'metadata_name_value_pairs' => [
599  [
600  'name' => 'timed_muting_start',
601  'value' => time(),
602  'operand' => '<=',
603  ],
604  [
605  'name' => 'timed_muting_end',
606  'value' => time(),
607  'operand' => '>=',
608  ],
609  ],
610  ]);
611 
612  return array_diff_key($subscriptions, array_flip($muted));
613  }
614 
630  protected function getSubscriptionRecords(array $container_guid, array $methods, string $type = null, string $subtype = null, string $action = null, int $actor_guid = 0): array {
631  // create IN clause
632  $rels = $this->getMethodRelationships($methods, $type, $subtype, $action);
633  if (!$rels) {
634  return [];
635  }
636 
637  $container_guid = array_unique(array_filter($container_guid));
638  if (empty($container_guid)) {
639  return [];
640  }
641 
643  $select->select('guid_one AS guid')
644  ->addSelect("GROUP_CONCAT(relationship SEPARATOR ',') AS methods")
645  ->where($select->compare('guid_two', 'in', $container_guid, ELGG_VALUE_GUID))
646  ->andWhere($select->compare('relationship', 'in', $rels, ELGG_VALUE_STRING))
647  ->groupBy('guid_one');
648 
649  if (!empty($actor_guid)) {
650  $select->andWhere($select->compare('guid_one', '!=', $actor_guid, ELGG_VALUE_GUID));
651  }
652 
653  return $this->db->getData($select);
654  }
655 
666  protected function getMethodRelationships(array $methods, string $type = null, string $subtype = null, string $action = null): array {
667  $prefix = self::RELATIONSHIP_PREFIX;
668 
669  $names = [];
670  foreach ($methods as $method) {
671  $names[] = "{$prefix}:{$method}";
672 
673  if (!empty($type) && !empty($subtype) && !empty($action)) {
674  $names[] = "{$prefix}:{$type}:{$subtype}:{$action}:{$method}";
675  }
676  }
677 
678  return $names;
679  }
680 
692  if (empty($type) && empty($subtype) && empty($action)) {
693  // all empty, this is valid
694  return;
695  }
696 
697  if (!empty($type) && !empty($subtype) && !empty($action)) {
698  // all set, also valid
699  return;
700  }
701 
702  throw new InvalidArgumentException('$type, $subtype and $action need to all be empty or all have a value');
703  }
704 }
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.