Completed
Push — development ( 7ace62...380f58 )
by Stephen
20s
created

Notifications::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 15
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
eloc 9
nc 1
nop 1
dl 0
loc 15
ccs 0
cts 10
cp 0
crap 2
rs 9.4285
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::getInstance()->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
	 * @global $modSettings - Not sure if actually necessary
213
	 */
214 2
	protected function _send_notification(\ElkArte\sources\subs\MentionType\Mention_Type_Interface $obj, \Notifications_Task $task, $bodies)
215
	{
216 2
		global $modSettings;
217
218 2
		$mentioning = new Mentioning($this->_db, new Data_Validator(), $modSettings['enabled_mentions']);
219 2
		foreach ($bodies as $body)
220
		{
221 2
			$mentioning->create($obj, array(
222 2
				'id_member_from' => $task['id_member_from'],
223 2
				'id_member' => $body['id_member_to'],
224 2
				'id_msg' => $task['id_target'],
225 2
				'type' => $task['notification_type'],
226 2
				'log_time' => $task['log_time'],
227 2
				'status' => $task['source_data']['status'],
228 2
			));
229 2
		}
230 2
	}
231
232
	/**
233
	 * Sends an immediate email notification.
234
	 *
235
	 * @param \ElkArte\sources\subs\MentionType\Mention_Type_Interface $obj
236
	 * @param \Notifications_Task $task
237
	 * @param mixed[] $bodies
238
	 */
239
	protected function _send_email(\ElkArte\sources\subs\MentionType\Mention_Type_Interface $obj, \Notifications_Task $task, $bodies)
240
	{
241
		$last_id = $obj->getLastId();
242
		foreach ($bodies as $body)
243
		{
244
			sendmail($body['email_address'], $body['subject'], $body['body'], null, $last_id);
245
		}
246
	}
247
248
	/**
249
	 * Stores data in the database to send a daily digest.
250
	 *
251
	 * @param \ElkArte\sources\subs\MentionType\Mention_Type_Interface $obj
252
	 * @param \Notifications_Task $task
253
	 * @param mixed[] $bodies
254
	 */
255 View Code Duplication
	protected function _send_daily_email(\ElkArte\sources\subs\MentionType\Mention_Type_Interface $obj, \Notifications_Task $task, $bodies)
256
	{
257
		foreach ($bodies as $body)
258
		{
259
			$this->_insert_delayed(array(
260
				$task['notification_type'],
261
				$body['id_member_to'],
262
				$task['log_time'],
263
				'd',
264
				$body['body']
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 View Code Duplication
	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
			$this->_insert_delayed(array(
281
				$task['notification_type'],
282
				$body['id_member_to'],
283
				$task['log_time'],
284
				'w',
285
				$body['body']
286
			));
287
		}
288
	}
289
290
	/**
291
	 * Do the insert into the database for daily and weekly digests.
292
	 *
293
	 * @param mixed[] $insert_array
294
	 */
295
	protected function _insert_delayed($insert_array)
296
	{
297
		$this->_db->insert('ignore',
298
			'{db_prefix}pending_notifications',
299
			array(
300
				'notification_type' => 'string-10',
301
				'id_member' => 'int',
302
				'log_time' => 'int',
303
				'frequency' => 'string-1',
304
				'snippet' => 'string',
305
			),
306
			$insert_array,
307
			array()
308
		);
309
	}
310
311
	/**
312
	 * Loads from the database the notification preferences for a certain type
313
	 * of mention for a bunch of members.
314
	 *
315
	 * @param string[] $notification_frequencies
316
	 * @param string $notification_type
317
	 * @param int[] $members
318
	 */
319 2
	protected function _getNotificationPreferences($notification_frequencies, $notification_type, $members)
320
	{
321 2
		$query_members = $members;
322
		// The member 0 is the "default" setting
323 2
		$query_members[] = 0;
324
325 2
		require_once(SUBSDIR . '/Notification.subs.php');
326 2
		$preferences = getUsersNotificationsPreferences($notification_type, $query_members);
327
328 2
		$notification_types = array();
329 2
		foreach ($notification_frequencies as $freq)
330
		{
331 2
			$notification_types[$freq] = array();
332 2
		}
333
334
		// notification_level can be:
335
		//    - 0 => no notification
336
		//    - 1 => only mention
337
		//    - 2 => mention + immediate email
338
		//    - 3 => mention + daily email
339
		//    - 4 => mention + weekly email
340
		//    - 5+ => usable by addons
341 2
		foreach ($members as $member)
342
		{
343 2
			$this_pref = $preferences[$member][$notification_type];
344 2
			if ($this_pref === 0)
345 2
			{
346
				continue;
347
			}
348
349
			// In the following two checks the use of the $this->_notification_frequencies
350
			// is intended, because the numeric id is important and is not preserved
351
			// in the local $notification_frequencies
352 2
			if (isset($this->_notification_frequencies[1]))
353 2
			{
354 2
				$notification_types[$this->_notification_frequencies[1]][] = $member;
355 2
			}
356
357 2
			if ($this_pref > 1)
358 2
			{
359
				if (isset($this->_notification_frequencies[$this_pref]) && isset($notification_types[$this->_notification_frequencies[$this_pref]]))
360
				{
361
					$notification_types[$this->_notification_frequencies[$this_pref]][] = $member;
362
				}
363
			}
364 2
		}
365
366 2
		return $notification_types;
367
	}
368
369
	/**
370
	 * Singleton... until we have something better.
371
	 *
372
	 * @return Notifications
373
	 */
374 3
	public static function getInstance()
375
	{
376 3
		if (self::$_instance === null)
377 3
		{
378
			self::$_instance = new Notifications(database());
379
		}
380
381 3
		return self::$_instance;
382
	}
383
}
384