SubscriptionsService   F
last analyzed

Complexity

Total Complexity 88

Size/Duplication

Total Lines 697
Duplicated Lines 0 %

Test Coverage

Coverage 95.15%

Importance

Changes 0
Metric Value
eloc 235
dl 0
loc 697
ccs 255
cts 268
cp 0.9515
rs 2
c 0
b 0
f 0
wmc 88

20 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 1
A unmuteNotifications() 0 2 1
A removeSubscriptions() 0 18 3
A getSubscriptionsForContainer() 0 28 6
A hasSubscriptions() 0 27 4
A muteNotifications() 0 5 1
A hasMutedNotifications() 0 2 1
A filterMutedNotifications() 0 39 5
A filterSubscriptions() 0 15 2
A getMethodRelationships() 0 13 5
A getSubscriptionRecords() 0 24 4
B assertValidTypeSubtypeActionForSubscription() 0 12 7
A addSubscription() 0 23 5
A hasSubscription() 0 16 4
A removeSubscription() 0 21 5
C getNotificationEventSubscriptions() 0 50 12
C getEntitySubscriptions() 0 51 15
A getSubscribers() 0 23 3
A filterTimedMutedSubscribers() 0 23 1
A filterDelayedEmailSubscribers() 0 11 3

How to fix   Complexity   

Complex Class

Complex classes like SubscriptionsService often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use SubscriptionsService, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace Elgg\Notifications;
4
5
use Elgg\Database;
6
use Elgg\Database\RelationshipsTable;
7
use Elgg\Database\Select;
8
use Elgg\Database\Delete;
9
use Elgg\Database\QueryBuilder;
10
use Elgg\Database\Entities;
11
use Elgg\EventsService;
12
use Elgg\Exceptions\InvalidArgumentException;
13
14
/**
15
 * Subscription service
16
 *
17
 * @internal
18
 * @since 1.9.0
19
 */
20
class SubscriptionsService {
21
22
	/**
23
	 * @var string Elgg has historically stored subscriptions as relationships with the prefix 'notify'
24
	 */
25
	const RELATIONSHIP_PREFIX = 'notify';
26
27
	/**
28
	 * @var string Used when an entity no longer wishes to recieve notifications
29
	 */
30
	const MUTE_NOTIFICATIONS_RELATIONSHIP = 'mute_notifications';
31
	
32
	/**
33
	 * @var Database
34
	 */
35
	protected $db;
36
	
37
	/**
38
	 * @var RelationshipsTable
39
	 */
40
	protected $relationshipsTable;
41
	
42
	/**
43
	 * @var EventsService
44
	 */
45
	protected $events;
46
47
	/**
48
	 * Constructor
49
	 *
50
	 * @param Database           $db                 Database service
51
	 * @param RelationshipsTable $relationshipsTable Relationship database table
52
	 * @param EventsService      $events             Events service
53
	 */
54 247
	public function __construct(Database $db, RelationshipsTable $relationshipsTable, EventsService $events) {
55 247
		$this->db = $db;
56 247
		$this->relationshipsTable = $relationshipsTable;
57 247
		$this->events = $events;
58
	}
59
60
	/**
61
	 * Get the subscriptions for this notification event
62
	 *
63
	 * The return array is of the form:
64
	 *
65
	 * array(
66
	 *     <user guid> => array('email', 'sms', 'ajax'),
67
	 * );
68
	 *
69
	 * @param NotificationEvent $event                     Notification event
70
	 * @param array             $methods                   Notification methods
71
	 * @param array             $exclude_guids_for_records GUIDs to exclude from fetching subscription records
72
	 *
73
	 * @return array
74
	 */
75 16
	public function getNotificationEventSubscriptions(NotificationEvent $event, array $methods, array $exclude_guids_for_records = []) {
76
77 16
		if (empty($methods)) {
78 1
			return [];
79
		}
80
81 15
		$object = $event->getObject();
82 15
		if (!$object instanceof \ElggData) {
83 1
			return [];
84
		}
85
86
		// get subscribers only for \ElggEntity if it isn't private
87 14
		if (!$object instanceof \ElggEntity || $object->access_id === ACCESS_PRIVATE) {
88
			return [];
89
		}
90
91 14
		$guids = [
92 14
			$object->owner_guid,
93 14
			$object->container_guid,
94 14
		];
95 14
		if ($object instanceof \ElggObject || $object instanceof \ElggGroup) {
96 13
			$guids[] = $object->guid;
97
		}
98
		
99 14
		$guids = array_diff($guids, $exclude_guids_for_records);
100 14
		if (empty($guids)) {
101
			return [];
102
		}
103
		
104 14
		$subscriptions = [];
105 14
		$records = $this->getSubscriptionRecords($guids, $methods, $object->type, $object->subtype, $event->getAction(), $event->getActorGUID());
106 14
		foreach ($records as $record) {
107 12
			if (empty($record->guid)) {
108
				// happens when no records are found
109
				continue;
110
			}
111
			
112 12
			if (!isset($subscriptions[$record->guid])) {
113 12
				$subscriptions[$record->guid] = [];
114
			}
115
			
116 12
			$deliveryMethods = explode(',', $record->methods);
117 12
			foreach ($deliveryMethods as $relationship) {
118 12
				$relationship_array = explode(':', $relationship);
119
				
120 12
				$subscriptions[$record->guid][] = end($relationship_array);
121
			}
122
		}
123
124 14
		return $subscriptions;
125
	}
126
127
	/**
128
	 * Get the subscriptions for the content created inside this container.
129
	 *
130
	 * The return array is of the form:
131
	 *
132
	 * array(
133
	 *     <user guid> => array('email', 'sms', 'ajax'),
134
	 * );
135
	 *
136
	 * @param int    $container_guid GUID of the entity acting as a container
137
	 * @param array  $methods        Notification methods
138
	 * @param string $type           (optional) entity type
139
	 * @param string $subtype        (optional) entity subtype
140
	 * @param string $action         (optional) notification action (eg. 'create')
141
	 * @param int    $actor_guid     (optional) Notification event actor to exclude from the database subscriptions
142
	 *
143
	 * @return array User GUIDs (keys) and their subscription types (values).
144
	 */
145 3
	public function getSubscriptionsForContainer(int $container_guid, array $methods, string $type = null, string $subtype = null, string $action = null, int $actor_guid = 0) {
146
147 3
		if (empty($methods)) {
148 1
			return [];
149
		}
150
151 2
		$subscriptions = [];
152
153 2
		$records = $this->getSubscriptionRecords([$container_guid], $methods, $type, $subtype, $action, $actor_guid);
154 2
		foreach ($records as $record) {
155 2
			if (empty($record->guid)) {
156
				// happens when no records are found
157
				continue;
158
			}
159
			
160 2
			if (!isset($subscriptions[$record->guid])) {
161 2
				$subscriptions[$record->guid] = [];
162
			}
163
			
164 2
			$deliveryMethods = explode(',', $record->methods);
165 2
			foreach ($deliveryMethods as $relationship) {
166 2
				$relationship_array = explode(':', $relationship);
167
				
168 2
				$subscriptions[$record->guid][] = end($relationship_array);
169
			}
170
		}
171
172 2
		return $subscriptions;
173
	}
174
175
	/**
176
	 * Subscribe a user to notifications about a target entity
177
	 *
178
	 * This method will return false if the subscription already exists.
179
	 *
180
	 * @param int    $user_guid   The GUID of the user to subscribe to notifications
181
	 * @param string $method      The delivery method of the notifications
182
	 * @param int    $target_guid The entity to receive notifications about
183
	 * @param string $type        (optional) entity type
184
	 * @param string $subtype     (optional) entity subtype
185
	 * @param string $action      (optional) notification action (eg. 'create')
186
	 *
187
	 * @return bool
188
	 * @throws InvalidArgumentException
189
	 */
190 332
	public function addSubscription(int $user_guid, string $method, int $target_guid, string $type = null, string $subtype = null, string $action = null) {
191 332
		$this->assertValidTypeSubtypeActionForSubscription($type, $subtype, $action);
192
		
193 326
		$rel = [
194 326
			self::RELATIONSHIP_PREFIX,
195 326
		];
196
		
197 326
		if (!_elgg_services()->notifications->isRegisteredMethod($method)) {
198 4
			return false;
199
		}
200
		
201
		// remove the muted notification relationship
202 325
		$this->unmuteNotifications($user_guid, $target_guid);
203
		
204 325
		if (!empty($type) && !empty($subtype) && !empty($action)) {
205 20
			$rel[] = $type;
206 20
			$rel[] = $subtype;
207 20
			$rel[] = $action;
208
		}
209
		
210 325
		$rel[] = $method;
211
		
212 325
		return $this->relationshipsTable->add($user_guid, implode(':', $rel), $target_guid);
213
	}
214
	
215
	/**
216
	 * Check if a subscription exists
217
	 *
218
	 * @param int    $user_guid   The GUID of the user to check subscriptions for
219
	 * @param string $method      The delivery method of the notifications
220
	 * @param int    $target_guid The entity to receive notifications about
221
	 * @param string $type        (optional) entity type
222
	 * @param string $subtype     (optional) entity subtype
223
	 * @param string $action      (optional) notification action (eg. 'create')
224
	 *
225
	 * @return bool
226
	 * @throws InvalidArgumentException
227
	 * @since 4.0
228
	 */
229 38
	public function hasSubscription(int $user_guid, string $method, int $target_guid, string $type = null, string $subtype = null, string $action = null): bool {
230 38
		$this->assertValidTypeSubtypeActionForSubscription($type, $subtype, $action);
231
		
232 32
		$rel = [
233 32
			self::RELATIONSHIP_PREFIX,
234 32
		];
235
		
236 32
		if (!empty($type) && !empty($subtype) && !empty($action)) {
237 13
			$rel[] = $type;
238 13
			$rel[] = $subtype;
239 13
			$rel[] = $action;
240
		}
241
		
242 32
		$rel[] = $method;
243
		
244 32
		return $this->relationshipsTable->check($user_guid, implode(':', $rel), $target_guid) instanceof \ElggRelationship;
245
	}
246
	
247
	/**
248
	 * Check if any subscription exists
249
	 *
250
	 * @param int   $user_guid   The GUID of the user to check subscriptions for
251
	 * @param int   $target_guid The entity to receive notifications about
252
	 * @param array $methods     The delivery method of the notifications
253
	 *
254
	 * @return bool
255
	 * @since 4.0
256
	 */
257 9
	public function hasSubscriptions(int $user_guid, int $target_guid, array $methods = []): bool {
258 9
		if (empty($methods)) {
259
			// all currently registered methods
260
			$methods = _elgg_services()->notifications->getMethods();
261
		}
262
		
263 9
		if (empty($methods)) {
264
			// no methods available
265
			return false;
266
		}
267
		
268 9
		$select = Select::fromTable('entity_relationships');
269 9
		$select->select('count(*) as total')
270 9
			->where($select->compare('guid_one', '=', $user_guid, ELGG_VALUE_GUID))
271 9
			->andWhere($select->compare('guid_two', '=', $target_guid, ELGG_VALUE_GUID));
272
		
273 9
		$ors = [];
274 9
		foreach ($methods as $method) {
275 9
			$ors[] = $select->compare('relationship', '=', self::RELATIONSHIP_PREFIX . ':' . $method, ELGG_VALUE_STRING);
276 9
			$ors[] = $select->compare('relationship', 'like', self::RELATIONSHIP_PREFIX . ':%:' . $method, ELGG_VALUE_STRING);
277
		}
278
		
279 9
		$select->andWhere($select->merge($ors, 'OR'));
280
		
281 9
		$result = $this->db->getDataRow($select);
282
		
283 9
		return (bool) $result->total;
284
	}
285
286
	/**
287
	 * Unsubscribe a user to notifications about a target entity
288
	 *
289
	 * @param int    $user_guid   The GUID of the user to unsubscribe to notifications
290
	 * @param string $method      The delivery method of the notifications to stop
291
	 * @param int    $target_guid The entity to stop receiving notifications about
292
	 * @param string $type        (optional) entity type
293
	 * @param string $subtype     (optional) entity subtype
294
	 * @param string $action      (optional) notification action (eg. 'create')
295
	 *
296
	 * @return bool
297
	 * @throws InvalidArgumentException
298
	 */
299 21
	public function removeSubscription(int $user_guid, string $method, int $target_guid, string $type = null, string $subtype = null, string $action = null) {
300 21
		$this->assertValidTypeSubtypeActionForSubscription($type, $subtype, $action);
301
		
302 15
		$rel = [
303 15
			self::RELATIONSHIP_PREFIX,
304 15
		];
305
		
306 15
		if (!empty($type) && !empty($subtype) && !empty($action)) {
307 5
			$rel[] = $type;
308 5
			$rel[] = $subtype;
309 5
			$rel[] = $action;
310
		}
311
		
312 15
		$rel[] = $method;
313
		
314 15
		if (!$this->relationshipsTable->check($user_guid, implode(':', $rel), $target_guid)) {
315
			// subscription doesn't exist
316 14
			return true;
317
		}
318
		
319 14
		return $this->relationshipsTable->remove($user_guid, implode(':', $rel), $target_guid);
320
	}
321
	
322
	/**
323
	 * Unsubscribe a user from all notifications about the target entity
324
	 *
325
	 * @param int   $user_guid   The GUID of the user to unsubscribe to notifications
326
	 * @param int   $target_guid The entity to stop receiving notifications about
327
	 * @param array $methods     (optional) The delivery method of the notifications to stop
328
	 *
329
	 * @return bool
330
	 * @since 4.0
331
	 */
332 78
	public function removeSubscriptions(int $user_guid, int $target_guid, array $methods = []): bool {
333 78
		$delete = Delete::fromTable('entity_relationships');
334 78
		$delete->where($delete->compare('guid_one', '=', $user_guid, ELGG_VALUE_GUID))
335 78
			->andWhere($delete->compare('guid_two', '=', $target_guid, ELGG_VALUE_GUID));
336
		
337 78
		if (empty($methods)) {
338 77
			$delete->andWhere($delete->compare('relationship', 'like', self::RELATIONSHIP_PREFIX . ':%', ELGG_VALUE_STRING));
339
		} else {
340 3
			$ors = [];
341 3
			foreach ($methods as $method) {
342 3
				$ors[] = $delete->compare('relationship', '=', self::RELATIONSHIP_PREFIX . ':' . $method, ELGG_VALUE_STRING);
343 3
				$ors[] = $delete->compare('relationship', 'like', self::RELATIONSHIP_PREFIX . ':%:' . $method, ELGG_VALUE_STRING);
344
			}
345
			
346 3
			$delete->andWhere($delete->merge($ors, 'OR'));
347
		}
348
		
349 78
		return (bool) $this->db->deleteData($delete);
350
	}
351
	
352
	/**
353
	 * Get all subscribers of the target guid
354
	 *
355
	 * @param int   $target_guid the entity of the subscriptions
356
	 * @param array $methods     (optional) The delivery method of the notifications
357
	 *
358
	 * @return \ElggEntity[]
359
	 */
360 12
	public function getSubscribers(int $target_guid, array $methods = []): array {
361 12
		return elgg_get_entities([
362 12
			'limit' => false,
363 12
			'wheres' => [
364 12
				function(QueryBuilder $qb, $main_alias) use ($target_guid) {
365 12
					$rel = $qb->joinRelationshipTable($main_alias, 'guid', null, true);
366
					
367 12
					return $qb->compare("{$rel}.guid_two", '=', $target_guid, ELGG_VALUE_GUID);
368 12
				},
369 12
				function(QueryBuilder $qb, $main_alias) use ($methods) {
370 12
					$rel = $qb->joinRelationshipTable($main_alias, 'guid', null, true);
371
					
372 12
					if (empty($methods)) {
373 9
						return $qb->compare("{$rel}.relationship", 'like', self::RELATIONSHIP_PREFIX . ':%', ELGG_VALUE_STRING);
374
					}
375
					
376 9
					$ors = [];
377 9
					foreach ($methods as $method) {
378 9
						$ors[] = $qb->compare("{$rel}.relationship", '=', self::RELATIONSHIP_PREFIX . ':' . $method, ELGG_VALUE_STRING);
379 9
						$ors[] = $qb->compare("{$rel}.relationship", 'like', self::RELATIONSHIP_PREFIX . ':%:' . $method, ELGG_VALUE_STRING);
380
					}
381
					
382 9
					return $qb->merge($ors, 'OR');
383 12
				},
384 12
			],
385 12
		]);
386
	}
387
	
388
	/**
389
	 * Get the current subscriptions for the given entity
390
	 *
391
	 * @param int    $target_guid The GUID of the entity to get subscriptions for
392
	 * @param int    $user_guid   The GUID of the user to check subscriptions for
393
	 * @param string $methods     The delivery method of the notifications
394
	 * @param string $type        (optional) entity type
395
	 * @param string $subtype     (optional) entity subtype
396
	 * @param string $action      (optional) notification action (eg. 'create')
397
	 *
398
	 * @return \ElggRelationship[]
399
	 * @throws InvalidArgumentException
400
	 */
401 15
	public function getEntitySubscriptions(int $target_guid = 0, int $user_guid = 0, array $methods = [], string $type = null, string $subtype = null, string $action = null): array {
402 15
		$this->assertValidTypeSubtypeActionForSubscription($type, $subtype, $action);
403
		
404 9
		if (empty($target_guid) && empty($user_guid)) {
405
			return [];
406
		}
407
		
408 9
		if (empty($target_guid)) {
409
			$target_guid = ELGG_ENTITIES_ANY_VALUE;
410
		}
411
		
412 9
		return elgg_get_relationships([
413 9
			'limit' => false,
414 9
			'wheres' => [
415 9
				function(QueryBuilder $qb, $main_alias) use ($target_guid) {
416 9
					if (empty($target_guid)) {
417
						return;
418
					}
419
					
420 9
					return $qb->compare("{$main_alias}.guid_two", '=', $target_guid, ELGG_VALUE_GUID);
421 9
				},
422 9
				function(QueryBuilder $qb, $main_alias) use ($user_guid) {
423 9
					if (empty($user_guid)) {
424 9
						return;
425
					}
426
					
427
					return $qb->compare("{$main_alias}.guid_one", '=', $user_guid, ELGG_VALUE_GUID);
428 9
				},
429 9
				function(QueryBuilder $qb, $main_alias) use ($methods, $type, $subtype, $action) {
430 9
					if (empty($methods) && (empty($type) || empty($subtype) || empty($action))) {
431 9
						return $qb->compare("{$main_alias}.relationship", 'like', self::RELATIONSHIP_PREFIX . ':%', ELGG_VALUE_STRING);
432
					}
433
					
434 6
					if (!empty($methods)) {
435 3
						if (empty($type) || empty($subtype) || empty($action)) {
436
							// only methods
437 3
							$ors = [];
438 3
							foreach ($methods as $method) {
439 3
								$ors[] = $qb->compare("{$main_alias}.relationship", '=', self::RELATIONSHIP_PREFIX . ':' . $method, ELGG_VALUE_STRING);
440 3
								$ors[] = $qb->compare("{$main_alias}.relationship", 'like', self::RELATIONSHIP_PREFIX . ':%:' . $method, ELGG_VALUE_STRING);
441
							}
442
							
443 3
							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 3
					return $qb->compare("{$main_alias}.relationship", 'like', self::RELATIONSHIP_PREFIX . ":{$type}:{$subtype}:{$action}:%", ELGG_VALUE_STRING);
452 9
				},
453 9
			],
454 9
		]);
455
	}
456
	
457
	/**
458
	 * Mute notifications about events affecting the target
459
	 *
460
	 * @param int $user_guid   The GUID of the user to mute notifcations for
461
	 * @param int $target_guid The GUID of the entity to for which to mute notifications
462
	 *
463
	 * @return bool
464
	 */
465 15
	public function muteNotifications(int $user_guid, int $target_guid): bool {
466
		// remove all current subscriptions
467 15
		$this->removeSubscriptions($user_guid, $target_guid);
468
		
469 15
		return $this->relationshipsTable->add($user_guid, self::MUTE_NOTIFICATIONS_RELATIONSHIP, $target_guid);
470
	}
471
	
472
	/**
473
	 * No longer nute notifications about events affecting the target
474
	 *
475
	 * @param int $user_guid   The GUID of the user to unmute notifcations for
476
	 * @param int $target_guid The GUID of the entity to for which to unmute notifications
477
	 *
478
	 * @return bool
479
	 */
480 326
	public function unmuteNotifications(int $user_guid, int $target_guid): bool {
481 326
		return $this->relationshipsTable->remove($user_guid, self::MUTE_NOTIFICATIONS_RELATIONSHIP, $target_guid);
482
	}
483
	
484
	/**
485
	 * Check if the user has notifications muted about events affecting the target
486
	 *
487
	 * @param int $user_guid   The GUID of the user to check muted notifcations for
488
	 * @param int $target_guid The GUID of the entity to for which to check muted notifications
489
	 *
490
	 * @return bool
491
	 */
492 28
	public function hasMutedNotifications(int $user_guid, int $target_guid): bool {
493 28
		return $this->relationshipsTable->check($user_guid, self::MUTE_NOTIFICATIONS_RELATIONSHIP, $target_guid) instanceof \ElggRelationship;
494
	}
495
	
496
	/**
497
	 * Apply filtering to subscriptions, like muted notifications etc
498
	 *
499
	 * @param array             $subscriptions List of subscribers to filter
500
	 * @param NotificationEvent $event         Notification event from which to get information
501
	 * @param bool              $filter_muted  Should the muting rules be applied to the subscriptions (default: true)
502
	 *
503
	 * @return array
504
	 */
505 46
	public function filterSubscriptions(array $subscriptions, NotificationEvent $event, bool $filter_muted = true): array {
506
		// sanitize
507
		// make methods unique and remove empties
508 46
		$subscriptions = array_map(function($user_methods) {
509 46
			return array_values(array_filter(array_unique($user_methods)));
510 46
		}, $subscriptions);
511 46
		$subscriptions = $this->filterDelayedEmailSubscribers($subscriptions);
512
		
513
		// apply filters
514 46
		if ($filter_muted) {
515 41
			$subscriptions = $this->filterMutedNotifications($subscriptions, $event);
516 41
			$subscriptions = $this->filterTimedMutedSubscribers($subscriptions);
517
		}
518
		
519 46
		return $subscriptions;
520
	}
521
	
522
	/**
523
	 * Filter subscriptions based on muted notification settings related to the notification event
524
	 *
525
	 * This filters out muted notifications based on:
526
	 * - Event actor
527
	 * - Event entity
528
	 * - Event entity owner
529
	 * - Event entity container
530
	 *
531
	 * @param array             $subscriptions List of subscribers to filter
532
	 * @param NotificationEvent $event         Notification event from which to get information
533
	 *
534
	 * @return array
535
	 */
536 45
	protected function filterMutedNotifications(array $subscriptions, NotificationEvent $event): array {
537 45
		$guids_to_check = [];
538
		
539
		// Event actor
540 45
		$guids_to_check[] = $event->getActorGUID();
541
		
542
		// Event object
543 45
		$entity = false;
544 45
		$object = $event->getObject();
545 45
		if ($object instanceof \ElggEntity) {
546 27
			$entity = $object;
547 18
		} elseif ($object instanceof \ElggAnnotation) {
548 2
			$entity = $object->getEntity();
549
		}
550
		
551 45
		if ($entity instanceof \ElggEntity) {
552 29
			$guids_to_check[] = $entity->guid;
553 29
			$guids_to_check[] = $entity->owner_guid;
554 29
			$guids_to_check[] = $entity->container_guid;
555
		}
556
		
557
		// are there GUIDs to check
558 45
		$guids_to_check = array_filter($guids_to_check);
559 45
		if (empty($guids_to_check)) {
560
			return $subscriptions;
561
		}
562
		
563
		// get muted relations
564 45
		$select = Select::fromTable('entity_relationships');
565 45
		$select->select('guid_one')
566 45
			->where($select->compare('relationship', '=', self::MUTE_NOTIFICATIONS_RELATIONSHIP, ELGG_VALUE_STRING))
567 45
			->andWhere($select->compare('guid_two', 'in', $guids_to_check, ELGG_VALUE_GUID));
568
		
569 45
		$muted = $this->db->getData($select, function($row) {
570 9
			return (int) $row->guid_one;
571 45
		});
572
		
573
		// filter subscriptions
574 45
		return array_diff_key($subscriptions, array_flip($muted));
575
	}
576
	
577
	/**
578
	 * When a user has both 'email' and 'delayed_email' subscription remove the delayed email as it would be a duplicate
579
	 *
580
	 * @param array $subscriptions List of subscribers to filter
581
	 *
582
	 * @return array
583
	 */
584 47
	protected function filterDelayedEmailSubscribers(array $subscriptions): array {
585 47
		return array_map(function ($user_methods) {
586 47
			if (!in_array('delayed_email', $user_methods) || !in_array('email', $user_methods)) {
587 47
				return $user_methods;
588
			}
589
			
590 1
			$pos = array_search('delayed_email', $user_methods);
591 1
			unset($user_methods[$pos]);
592
			
593 1
			return array_values($user_methods);
594 47
		}, $subscriptions);
595
	}
596
	
597
	/**
598
	 * Filter users who have set a period in which not to receive notifications
599
	 *
600
	 * @param array $subscriptions List of subscribers to filter
601
	 *
602
	 * @return array
603
	 */
604 41
	protected function filterTimedMutedSubscribers(array $subscriptions): array {
605 41
		$muted = Entities::find([
606 41
			'type' => 'user',
607 41
			'guids' => array_keys($subscriptions),
608 41
			'limit' => false,
609 41
			'callback' => function ($row) {
610 1
				return (int) $row->guid;
611 41
			},
612 41
			'metadata_name_value_pairs' => [
613 41
				[
614 41
					'name' => 'timed_muting_start',
615 41
					'value' => time(),
616 41
					'operand' => '<=',
617 41
				],
618 41
				[
619 41
					'name' => 'timed_muting_end',
620 41
					'value' => time(),
621 41
					'operand' => '>=',
622 41
				],
623 41
			],
624 41
		]);
625
		
626 41
		return array_diff_key($subscriptions, array_flip($muted));
627
	}
628
629
	/**
630
	 * Get subscription records from the database
631
	 *
632
	 * Records are an object with two vars: guid and methods with the latter
633
	 * being a comma-separated list of subscription relationship names.
634
	 *
635
	 * @param int[]  $container_guid The GUID of the subscription target
636
	 * @param array  $methods        Notification methods
637
	 * @param string $type           (optional) entity type
638
	 * @param string $subtype        (optional) entity subtype
639
	 * @param string $action         (optional) notification action (eg. 'create')
640
	 * @param int    $actor_guid     (optional) Notification event actor to exclude from the database subscriptions
641
	 *
642
	 * @return array
643
	 */
644 16
	protected function getSubscriptionRecords(array $container_guid, array $methods, string $type = null, string $subtype = null, string $action = null, int $actor_guid = 0): array {
645
		// create IN clause
646 16
		$rels = $this->getMethodRelationships($methods, $type, $subtype, $action);
647 16
		if (!$rels) {
648
			return [];
649
		}
650
		
651 16
		$container_guid = array_unique(array_filter($container_guid));
652 16
		if (empty($container_guid)) {
653 1
			return [];
654
		}
655
656 15
		$select = Select::fromTable('entity_relationships');
657 15
		$select->select('guid_one AS guid')
658 15
			->addSelect("GROUP_CONCAT(relationship SEPARATOR ',') AS methods")
659 15
			->where($select->compare('guid_two', 'in', $container_guid, ELGG_VALUE_GUID))
660 15
			->andWhere($select->compare('relationship', 'in', $rels, ELGG_VALUE_STRING))
661 15
			->groupBy('guid_one');
662
		
663 15
		if (!empty($actor_guid)) {
664 11
			$select->andWhere($select->compare('guid_one', '!=', $actor_guid, ELGG_VALUE_GUID));
665
		}
666
		
667 15
		return $this->db->getData($select);
668
	}
669
670
	/**
671
	 * Get the relationship names for notifications
672
	 *
673
	 * @param array  $methods Notification methods
674
	 * @param string $type    (optional) entity type
675
	 * @param string $subtype (optional) entity subtype
676
	 * @param string $action  (optional) notification action (eg. 'create')
677
	 *
678
	 * @return array
679
	 */
680 16
	protected function getMethodRelationships(array $methods, string $type = null, string $subtype = null, string $action = null): array {
681 16
		$prefix = self::RELATIONSHIP_PREFIX;
682
		
683 16
		$names = [];
684 16
		foreach ($methods as $method) {
685 16
			$names[] = "{$prefix}:{$method}";
686
			
687 16
			if (!empty($type) && !empty($subtype) && !empty($action)) {
688 12
				$names[] = "{$prefix}:{$type}:{$subtype}:{$action}:{$method}";
689
			}
690
		}
691
		
692 16
		return $names;
693
	}
694
	
695
	/**
696
	 * Validate subscription input for type, subtype and action
697
	 *
698
	 * @param string $type    entity type
699
	 * @param string $subtype entity subtype
700
	 * @param string $action  notification action (eg. 'create')
701
	 *
702
	 * @return void
703
	 * @throws InvalidArgumentException
704
	 */
705 350
	protected function assertValidTypeSubtypeActionForSubscription($type, $subtype, $action): void {
706 350
		if (empty($type) && empty($subtype) && empty($action)) {
707
			// all empty, this is valid
708 324
			return;
709
		}
710
		
711 44
		if (!empty($type) && !empty($subtype) && !empty($action)) {
712
			// all set, also valid
713 20
			return;
714
		}
715
		
716 24
		throw new InvalidArgumentException('$type, $subtype and $action need to all be empty or all have a value');
717
	}
718
}
719