Completed
Pull Request — development (#2979)
by Stephen
08:55
created

Notifications::_send_task()   B

Complexity

Conditions 4
Paths 4

Size

Total Lines 31
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 19
CRAP Score 4.002

Importance

Changes 0
Metric Value
cc 4
eloc 15
nc 4
nop 1
dl 0
loc 31
ccs 19
cts 20
cp 0.95
crap 4.002
rs 8.5806
c 0
b 0
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 Release Candidate 1
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
	 * Disallows to register notification types with id < 5
52
	 *
53
	 * @var bool
54
	 */
55
	protected $_protect_id = true;
56
57
	/**
58
	 * Notifications constructor.
59
	 *
60
	 * Registers the known notifications to the system, allows for integration to add more
61
	 *
62
	 * @param \Database $db
63
	 * @throws Elk_Exception
64
	 */
65
	public function __construct($db)
66
	{
67
		parent::__construct($db);
68
69
		$this->_protect_id = false;
70
71
		// Let's register all the notifications we know by default
72
		$this->register(1, 'notification', array($this, '_send_notification'));
73
		$this->register(2, 'email', array($this, '_send_email'), array('subject' => 'subject', 'body' => 'body', 'suffix' => true));
0 ignored issues
show
Documentation introduced by
array('subject' => 'subj...ody', 'suffix' => true) is of type array<string,string|bool...g","suffix":"boolean"}>, but the function expects a null|array<integer,string>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
74
		$this->register(3, 'email_daily', array($this, '_send_daily_email'), array('subject' => 'subject', 'body' => 'snippet', 'suffix' => true));
0 ignored issues
show
Documentation introduced by
array('subject' => 'subj...pet', 'suffix' => true) is of type array<string,string|bool...g","suffix":"boolean"}>, but the function expects a null|array<integer,string>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
75
		$this->register(4, 'email_weekly', array($this, '_send_weekly_email'), array('subject' => 'subject', 'body' => 'snippet', 'suffix' => true));
0 ignored issues
show
Documentation introduced by
array('subject' => 'subj...pet', 'suffix' => true) is of type array<string,string|bool...g","suffix":"boolean"}>, but the function expects a null|array<integer,string>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
76
		$this->_protect_id = true;
77
78
		call_integration_hook('integrate_notifications_methods', array($this));
79
	}
80
81
	/**
82
	 * We hax a new notification to send out!
83
	 *
84
	 * @param \Notifications_Task $task
85
	 */
86 2
	public function add(\Notifications_Task $task)
87
	{
88 2
		$this->_to_send[] = $task;
89 2
	}
90
91
	/**
92
	 * Time to notify our beloved members! YAY!
93
	 */
94 2
	public function send()
95
	{
96 2
		Elk_Autoloader::instance()->register(SUBSDIR . '/MentionType', '\\ElkArte\\sources\\subs\\MentionType');
97
98 2
		$this->_notification_frequencies = array(
99
			// 0 is for no notifications, so we start from 1 the counting, that saves a +1 later
100 2
			1 => 'notification',
101 2
			'email',
102 2
			'email_daily',
103 2
			'email_weekly',
104
		);
105
106 2
		if (!empty($this->_to_send))
107 2
		{
108 2
			foreach ($this->_to_send as $task)
109
			{
110 2
				$this->_send_task($task);
111 2
			}
112 2
		}
113
114 2
		$this->_to_send = array();
115 2
	}
116
117
	/**
118
	 * Function to register any new notification method.
119
	 *
120
	 * @param int $id This shall be a unique integer representing the notification method.
121
	 *
122
	 * <b>WARNING for addons developers</b>: note that this has to be **unique** across
123
	 * addons, so if you develop an addon that extends notifications, please verify this id
124
	 * has not been "taken" by someone else! 1-4 are reserved system notifications.
125
	 * @param int $key The string name identifying the notification method
126
	 *
127
	 *  - _send_email
128
	 *  - _send_daily_email
129
	 *  - _send_weekly_email
130
	 * @param mixed|mixed[] $callback A callable function/array/whatever that will take care
131
	 * of sending the notification
132
	 * @param null|string[] $lang_data For the moment an array containing at least:
133
	 *
134
	 *  - 'subject' => 'something'
135
	 *  - 'body' => 'something_else'
136
	 *  - 'suffix' => true/false
137
	 *
138
	 * Used to identify the strings for the subject and body respectively of the notification.
139
	 * @throws Elk_Exception
140
	 */
141
	public function register($id, $key, $callback, $lang_data = null)
142
	{
143
		// 1-4 are system notifications and can not be changed.
144
		if ($this->_protect_id && $id < 5)
145
		{
146
			throw new Elk_Exception('error_invalid_notification_id');
147
		}
148
149
		$this->_notifiers[$key] = array(
150
			'id' => $id,
151
			'key' => $key,
152
			'callback' => $callback,
153
			'lang_data' => $lang_data,
154
		);
155
156
		$this->_notification_frequencies[$id] = $key;
157
	}
158
159
	/**
160
	 * Returns the notifications in the system, daily, weekly, etc
161
	 *
162
	 * @return string[]
163
	 */
164 1
	public function getNotifiers()
165
	{
166 1
		return $this->_notification_frequencies;
167
	}
168
169
	/**
170
	 * Process a certain task in order to send out the notifications.
171
	 *
172
	 * @param \Notifications_Task $task
173
	 */
174 2
	protected function _send_task(\Notifications_Task $task)
175
	{
176 2
		$class = $task->getClass();
177 2
		$obj = new $class($this->_db);
178 2
		$obj->setTask($task);
179
180 2
		require_once(SUBSDIR . '/Notification.subs.php');
181 2
		require_once(SUBSDIR . '/Mail.subs.php');
182 2
		$notification_frequencies = filterNotificationMethods($this->_notification_frequencies, $class::getType());
183
184
		// Cleanup the list of members to notify,
185
		// in certain cases it may differ from the list passed (if any)
186 2
		$task->setMembers($obj->getUsersToNotify());
187 2
		$notif_prefs = $this->_getNotificationPreferences($notification_frequencies, $task->notification_type, $task->getMembers());
0 ignored issues
show
Documentation introduced by
The property notification_type does not exist on object<Notifications_Task>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
188
189 2
		foreach ($notification_frequencies as $key)
190
		{
191 2
			if (!empty($notif_prefs[$key]))
192 2
			{
193 2
				$bodies = $obj->getNotificationBody($this->_notifiers[$key]['lang_data'], $notif_prefs[$key]);
194
195
				// Just in case...
196 2
				if (empty($bodies))
197 2
				{
198
					continue;
199
				}
200
201 2
				call_user_func_array($this->_notifiers[$key]['callback'], array($obj, $task, $bodies, $this->_db));
202 2
			}
203 2
		}
204 2
	}
205
206
	/**
207
	 * Inserts a new mention in the database (those that appear in the mentions area).
208
	 *
209
	 * @param \ElkArte\sources\subs\MentionType\Mention_Type_Interface $obj
210
	 * @param \Notifications_Task $task
211
	 * @param mixed[] $bodies
212
	 */
213 2
	protected function _send_notification(\ElkArte\sources\subs\MentionType\Mention_Type_Interface $obj, \Notifications_Task $task, $bodies)
214
	{
215 2
		$mentioning = new Mentioning($this->_db, new Data_Validator(), $this->_modSettings->enabled_mentions);
216 2
		foreach ($bodies as $body)
217
		{
218 2
			$mentioning->create($obj, array(
219 2
				'id_member_from' => $task['id_member_from'],
220 2
				'id_member' => $body['id_member_to'],
221 2
				'id_msg' => $task['id_target'],
222 2
				'type' => $task['notification_type'],
223 2
				'log_time' => $task['log_time'],
224 2
				'status' => $task['source_data']['status'],
225 2
			));
226 2
		}
227 2
	}
228
229
	/**
230
	 * Sends an immediate email notification.
231
	 *
232
	 * @param \ElkArte\sources\subs\MentionType\Mention_Type_Interface $obj
233
	 * @param \Notifications_Task $task
234
	 * @param mixed[] $bodies
235
	 */
236
	protected function _send_email(\ElkArte\sources\subs\MentionType\Mention_Type_Interface $obj, \Notifications_Task $task, $bodies)
237
	{
238
		$last_id = $obj->getLastId();
239
		foreach ($bodies as $body)
240
		{
241
			sendmail($body['email_address'], $body['subject'], $body['body'], null, $last_id);
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 View Code Duplication
	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
			$this->_insert_delayed(array(
257
				$task['notification_type'],
258
				$body['id_member_to'],
259
				$task['log_time'],
260
				'd',
261
				$body['body']
262
			));
263
		}
264
	}
265
266
	/**
267
	 * Stores data in the database to send a weekly digest.
268
	 *
269
	 * @param \ElkArte\sources\subs\MentionType\Mention_Type_Interface $obj
270
	 * @param \Notifications_Task $task
271
	 * @param mixed[] $bodies
272
	 */
273 View Code Duplication
	protected function _send_weekly_email(\ElkArte\sources\subs\MentionType\Mention_Type_Interface $obj, \Notifications_Task $task, $bodies)
274
	{
275
		foreach ($bodies as $body)
276
		{
277
			$this->_insert_delayed(array(
278
				$task['notification_type'],
279
				$body['id_member_to'],
280
				$task['log_time'],
281
				'w',
282
				$body['body']
283
			));
284
		}
285
	}
286
287
	/**
288
	 * Do the insert into the database for daily and weekly digests.
289
	 *
290
	 * @param mixed[] $insert_array
291
	 */
292
	protected function _insert_delayed($insert_array)
293
	{
294
		$this->_db->insert('ignore',
295
			'{db_prefix}pending_notifications',
296
			array(
297
				'notification_type' => 'string-10',
298
				'id_member' => 'int',
299
				'log_time' => 'int',
300
				'frequency' => 'string-1',
301
				'snippet' => 'string',
302
			),
303
			$insert_array,
304
			array()
305
		);
306
	}
307
308
	/**
309
	 * Loads from the database the notification preferences for a certain type
310
	 * of mention for a bunch of members.
311
	 *
312
	 * @param string[] $notification_frequencies
313
	 * @param string $notification_type
314
	 * @param int[] $members
315
	 */
316 2
	protected function _getNotificationPreferences($notification_frequencies, $notification_type, $members)
317
	{
318 2
		$query_members = $members;
319
		// The member 0 is the "default" setting
320 2
		$query_members[] = 0;
321
322 2
		require_once(SUBSDIR . '/Notification.subs.php');
323 2
		$preferences = getUsersNotificationsPreferences($notification_type, $query_members);
324
325 2
		$notification_types = array();
326 2
		foreach ($notification_frequencies as $freq)
327
		{
328 2
			$notification_types[$freq] = array();
329 2
		}
330
331
		// notification_level can be:
332
		//    - 0 => no notification
333
		//    - 1 => only mention
334
		//    - 2 => mention + immediate email
335
		//    - 3 => mention + daily email
336
		//    - 4 => mention + weekly email
337
		//    - 5+ => usable by addons
338 2
		foreach ($members as $member)
339
		{
340 2
			$this_pref = $preferences[$member][$notification_type];
341 2
			if ($this_pref === 0)
342 2
			{
343
				continue;
344
			}
345
346
			// In the following two checks the use of the $this->_notification_frequencies
347
			// is intended, because the numeric id is important and is not preserved
348
			// in the local $notification_frequencies
349 2
			if (isset($this->_notification_frequencies[1]))
350 2
			{
351 2
				$notification_types[$this->_notification_frequencies[1]][] = $member;
352 2
			}
353
354 2
			if ($this_pref > 1)
355 2
			{
356
				if (isset($this->_notification_frequencies[$this_pref]) && isset($notification_types[$this->_notification_frequencies[$this_pref]]))
357
				{
358
					$notification_types[$this->_notification_frequencies[$this_pref]][] = $member;
359
				}
360
			}
361 2
		}
362
363 2
		return $notification_types;
364
	}
365
366
	/**
367
	 * Singleton... until we have something better.
368
	 *
369
	 * @return Notifications
370
	 */
371 3
	public static function instance()
372
	{
373 3
		if (self::$_instance === null)
374 3
		{
375
			self::$_instance = new Notifications(database());
376
		}
377
378 3
		return self::$_instance;
379
	}
380
}
381