Passed
Pull Request — patch_1-1-7 (#3404)
by
unknown
15:47
created

Notifications::_send_notification()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 12
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 1 Features 0
Metric Value
cc 2
eloc 9
nc 2
nop 3
dl 0
loc 12
rs 9.9666
c 1
b 1
f 0
1
<?php
2
3
/**
4
 * Class that centralize the "notification" process.
5
 * ... or at least tries to.
6
 *
7
 * @name      ElkArte Forum
8
 * @copyright ElkArte Forum contributors
9
 * @license   BSD http://opensource.org/licenses/BSD-3-Clause
10
 *
11
 * @version 1.1.4
12
 *
13
 */
14
15
/**
16
 * Class Notifications
17
 *
18
 * Core area for notifications, defines the abstract model
19
 */
20
class Notifications extends AbstractModel
21
{
22
	/**
23
	 * Instance manager
24
	 *
25
	 * @var Notifications
26
	 */
27
	protected static $_instance;
28
29
	/**
30
	 * List of notifications to send
31
	 *
32
	 * @var \Notifications_Task[]
33
	 */
34
	protected $_to_send;
35
36
	/**
37
	 * Available notification frequencies
38
	 *
39
	 * @var string[]
40
	 */
41
	protected $_notification_frequencies;
42
43
	/**
44
	 * Available notification frequencies
45
	 *
46
	 * @var array
47
	 */
48
	protected $_notifiers;
49
50
	/**
51
	 * Only the members that should be notified.
52
	 * For example, in case of editing a message, quoted members
53
	 * should not be mentioned twice.
54
	 *
55
	 * @var array
56
	 */
57
	protected $_to_actually_mention = array();
58
	/**
59
	 * Notifications constructor.
60
	 *
61
	 * Registers the known notifications to the system, allows for integration to add more
62
	 *
63
	 * @param \Database $db
64
	 * @throws Elk_Exception
65
	 */
66
	public function __construct($db)
67
	{
68
		parent::__construct($db);
69
70
		// Let's register all the notifications we know by default
71
		$this->register(1, 'notification', array($this, '_send_notification'));
0 ignored issues
show
Bug introduced by
'notification' of type string is incompatible with the type integer expected by parameter $key of Notifications::register(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

71
		$this->register(1, /** @scrutinizer ignore-type */ 'notification', array($this, '_send_notification'));
Loading history...
72
		$this->register(2, 'email', array($this, '_send_email'), array('subject' => 'subject', 'body' => 'body', 'suffix' => true));
0 ignored issues
show
Bug introduced by
array('subject' => 'subj...ody', 'suffix' => true) of type array<string,string|true> is incompatible with the type null|string[] expected by parameter $lang_data of Notifications::register(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

72
		$this->register(2, 'email', array($this, '_send_email'), /** @scrutinizer ignore-type */ array('subject' => 'subject', 'body' => 'body', 'suffix' => true));
Loading history...
73
		$this->register(3, 'email_daily', array($this, '_send_daily_email'), array('subject' => 'subject', 'body' => 'snippet', 'suffix' => true));
74
		$this->register(4, 'email_weekly', array($this, '_send_weekly_email'), array('subject' => 'subject', 'body' => 'snippet', 'suffix' => true));
75
76
		call_integration_hook('integrate_notifications_methods', array($this));
77
	}
78
79
	/**
80
	 * We hax a new notification to send out!
81
	 *
82
	 * @param \Notifications_Task $task
83
	 */
84
	public function add(\Notifications_Task $task)
85
	{
86
		$this->_to_send[] = $task;
87
	}
88
89
	/**
90
	 * Time to notify our beloved members! YAY!
91
	 */
92
	public function send()
93
	{
94
		Elk_Autoloader::instance()->register(SUBSDIR . '/MentionType', '\\ElkArte\\sources\\subs\\MentionType');
95
96
		$this->_notification_frequencies = array(
97
			// 0 is for no notifications, so we start from 1 the counting, that saves a +1 later
98
			1 => 'notification',
99
			'email',
100
			'email_daily',
101
			'email_weekly',
102
		);
103
104
		if (!empty($this->_to_send))
105
		{
106
			foreach ($this->_to_send as $task)
107
			{
108
				$this->_send_task($task);
109
			}
110
		}
111
112
		$this->_to_send = array();
113
	}
114
115
	/**
116
	 * Function to register any new notification method.
117
	 *
118
	 * @param int $id This shall be a unique integer representing the notification method.
119
	 *
120
	 * <b>WARNING for addons developers</b>: note that this has to be **unique** across
121
	 * addons, so if you develop an addon that extends notifications, please verify this id
122
	 * has not been "taken" by someone else! 1-4 are reserved system notifications.
123
	 * @param int $key The string name identifying the notification method
124
	 *
125
	 *  - _send_email
126
	 *  - _send_daily_email
127
	 *  - _send_weekly_email
128
	 * @param mixed|mixed[] $callback A callable function/array/whatever that will take care
129
	 * of sending the notification
130
	 * @param null|string[] $lang_data For the moment an array containing at least:
131
	 *
132
	 *  - 'subject' => 'something'
133
	 *  - 'body' => 'something_else'
134
	 *  - 'suffix' => true/false
135
	 *
136
	 * Used to identify the strings for the subject and body respectively of the notification.
137
	 * @throws Elk_Exception
138
	 */
139
	public function register($id, $key, $callback, $lang_data = null)
140
	{
141
		if (isset($this->_notifiers[$key]))
142
		{
143
			throw new Elk_Exception('error_invalid_notification_id');
144
		}
145
146
		$this->_notifiers[$key] = array(
147
			'id' => $id,
148
			'key' => $key,
149
			'callback' => $callback,
150
			'lang_data' => $lang_data,
151
		);
152
153
		$this->_notification_frequencies[$id] = $key;
154
	}
155
156
	/**
157
	 * Returns the notifications in the system, daily, weekly, etc
158
	 *
159
	 * @return string[]
160
	 */
161
	public function getNotifiers()
162
	{
163
		return $this->_notification_frequencies;
164
	}
165
166
	/**
167
	 * Process a certain task in order to send out the notifications.
168
	 *
169
	 * @param \Notifications_Task $task
170
	 */
171
	protected function _send_task(\Notifications_Task $task)
172
	{
173
		$class = $task->getClass();
174
		$obj = new $class($this->_db);
175
		$obj->setTask($task);
176
177
		require_once(SUBSDIR . '/Notification.subs.php');
178
		require_once(SUBSDIR . '/Mail.subs.php');
179
		$notification_frequencies = filterNotificationMethods($this->_notification_frequencies, $class::getType());
180
181
		// Cleanup the list of members to notify,
182
		// in certain cases it may differ from the list passed (if any)
183
		$task->setMembers($obj->getUsersToNotify());
184
		$notif_prefs = $this->_getNotificationPreferences($notification_frequencies, $task->notification_type, $task->getMembers());
0 ignored issues
show
Bug Best Practice introduced by
The property notification_type does not exist on Notifications_Task. Since you implemented __get, consider adding a @property annotation.
Loading history...
185
186
		foreach ($notification_frequencies as $key)
187
		{
188
			if (!empty($notif_prefs[$key]))
189
			{
190
				$bodies = $obj->getNotificationBody($this->_notifiers[$key]['lang_data'], $notif_prefs[$key]);
191
192
				// Just in case...
193
				if (empty($bodies))
194
				{
195
					continue;
196
				}
197
198
				call_user_func_array($this->_notifiers[$key]['callback'], array($obj, $task, $bodies, $this->_db));
199
			}
200
		}
201
	}
202
203
	/**
204
	 * Inserts a new mention in the database (those that appear in the mentions area).
205
	 *
206
	 * @param \ElkArte\sources\subs\MentionType\Mention_Type_Interface $obj
207
	 * @param \Notifications_Task $task
208
	 * @param mixed[] $bodies
209
	 */
210
	protected function _send_notification(\ElkArte\sources\subs\MentionType\Mention_Type_Interface $obj, \Notifications_Task $task, $bodies)
211
	{
212
		$mentioning = new Mentioning($this->_db, new Data_Validator(), $this->_modSettings->enabled_mentions);
213
		foreach ($bodies as $key => $body)
214
		{
215
			$this->_to_actually_mention[$task['notification_type']] = $mentioning->create($obj, array(
216
				'id_member_from' => $task['id_member_from'],
217
				'id_member' => $body['id_member_to'],
218
				'id_msg' => $task['id_target'],
219
				'type' => $task['notification_type'],
220
				'log_time' => $task['log_time'],
221
				'status' => $task['source_data']['status'],
222
			));
223
		}
224
	}
225
226
	/**
227
	 * Sends an immediate email notification.
228
	 *
229
	 * @param \ElkArte\sources\subs\MentionType\Mention_Type_Interface $obj
230
	 * @param \Notifications_Task $task
231
	 * @param mixed[] $bodies
232
	 */
233
	protected function _send_email(\ElkArte\sources\subs\MentionType\Mention_Type_Interface $obj, \Notifications_Task $task, $bodies)
234
	{
235
		$last_id = $obj->getLastId();
236
		foreach ($bodies as $body)
237
		{
238
			if (in_array($body['id_member_to'], $this->_to_actually_mention[$task['notification_type']]))
239
			{
240
				sendmail($body['email_address'], $body['subject'], $body['body'], null, $last_id);
241
			}
242
		}
243
	}
244
245
	/**
246
	 * Stores data in the database to send a daily digest.
247
	 *
248
	 * @param \ElkArte\sources\subs\MentionType\Mention_Type_Interface $obj
249
	 * @param \Notifications_Task $task
250
	 * @param mixed[] $bodies
251
	 */
252
	protected function _send_daily_email(\ElkArte\sources\subs\MentionType\Mention_Type_Interface $obj, \Notifications_Task $task, $bodies)
253
	{
254
		foreach ($bodies as $body)
255
		{
256
			if (in_array($body['id_member_to'], $this->_to_actually_mention[$task['notification_type']]))
257
			{
258
				$this->_insert_delayed(array(
259
					$task['notification_type'],
260
					$body['id_member_to'],
261
					$task['log_time'],
262
					'd',
263
					$body['body']
264
				));
265
			}
266
		}
267
	}
268
269
	/**
270
	 * Stores data in the database to send a weekly digest.
271
	 *
272
	 * @param \ElkArte\sources\subs\MentionType\Mention_Type_Interface $obj
273
	 * @param \Notifications_Task $task
274
	 * @param mixed[] $bodies
275
	 */
276
	protected function _send_weekly_email(\ElkArte\sources\subs\MentionType\Mention_Type_Interface $obj, \Notifications_Task $task, $bodies)
277
	{
278
		foreach ($bodies as $body)
279
		{
280
			if (in_array($body['id_member_to'], $this->_to_actually_mention[$task['notification_type']]))
281
			{
282
				$this->_insert_delayed(array(
283
					$task['notification_type'],
284
					$body['id_member_to'],
285
					$task['log_time'],
286
					'w',
287
					$body['body']
288
				));
289
			}
290
		}
291
	}
292
293
	/**
294
	 * Do the insert into the database for daily and weekly digests.
295
	 *
296
	 * @param mixed[] $insert_array
297
	 */
298
	protected function _insert_delayed($insert_array)
299
	{
300
		$this->_db->insert('ignore',
301
			'{db_prefix}pending_notifications',
302
			array(
303
				'notification_type' => 'string-10',
304
				'id_member' => 'int',
305
				'log_time' => 'int',
306
				'frequency' => 'string-1',
307
				'snippet' => 'string',
308
			),
309
			$insert_array,
310
			array()
311
		);
312
	}
313
314
	/**
315
	 * Loads from the database the notification preferences for a certain type
316
	 * of mention for a bunch of members.
317
	 *
318
	 * @param string[] $notification_frequencies
319
	 * @param string $notification_type
320
	 * @param int[] $members
321
	 */
322
	protected function _getNotificationPreferences($notification_frequencies, $notification_type, $members)
323
	{
324
		$query_members = $members;
325
		// The member 0 is the "default" setting
326
		$query_members[] = 0;
327
328
		require_once(SUBSDIR . '/Notification.subs.php');
329
		$preferences = getUsersNotificationsPreferences($notification_type, $query_members);
330
331
		$notification_types = array();
332
		foreach ($notification_frequencies as $freq)
333
		{
334
			$notification_types[$freq] = array();
335
		}
336
337
		// notification_level can be:
338
		//    - 0 => no notification
339
		//    - 1 => only mention
340
		//    - 2 => mention + immediate email
341
		//    - 3 => mention + daily email
342
		//    - 4 => mention + weekly email
343
		//    - 5+ => usable by addons
344
		foreach ($members as $member)
345
		{
346
			$this_pref = $preferences[$member][$notification_type];
347
			if ($this_pref === 0)
348
			{
349
				continue;
350
			}
351
352
			// In the following two checks the use of the $this->_notification_frequencies
353
			// is intended, because the numeric id is important and is not preserved
354
			// in the local $notification_frequencies
355
			if (isset($this->_notification_frequencies[1]))
356
			{
357
				$notification_types[$this->_notification_frequencies[1]][] = $member;
358
			}
359
360
			if ($this_pref > 1)
361
			{
362
				if (isset($this->_notification_frequencies[$this_pref]) && isset($notification_types[$this->_notification_frequencies[$this_pref]]))
363
				{
364
					$notification_types[$this->_notification_frequencies[$this_pref]][] = $member;
365
				}
366
			}
367
		}
368
369
		return $notification_types;
370
	}
371
372
	/**
373
	 * Singleton... until we have something better.
374
	 *
375
	 * @return Notifications
376
	 */
377
	public static function instance()
378
	{
379
		if (self::$_instance === null)
380
		{
381
			self::$_instance = new Notifications(database());
382
		}
383
384
		return self::$_instance;
385
	}
386
}
387