Completed
Pull Request — development (#2330)
by Joshua
41:37 queued 30:33
created

Notifications::add()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2
Metric Value
dl 0
loc 4
ccs 0
cts 4
cp 0
rs 10
cc 1
eloc 2
nc 1
nop 1
crap 2
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.0.3
12
 *
13
 */
14
15
if (!defined('ELK'))
16
	die('No access...');
17
18
class Notifications extends AbstractModel
19
{
20
	/**
21
	 * Instance manager
22
	 *
23
	 * @var Notifications
24
	 */
25
	protected static $_instance;
26
27
	/**
28
	 * List of notifications to send
29
	 *
30
	 * @var \Notifications_Task[]
31
	 */
32
	protected $_to_send;
33
34
	/**
35
	 * Available notification frequencies
36
	 *
37
	 * @var string[]
38
	 */
39
	protected $_notification_frequencies;
40
41
	/**
42
	 * Available notification frequencies
43
	 *
44
	 * @var string[]
45
	 */
46
	protected $_notifiers;
47
48
	/**
49
	 * Disallows to register notification types with id < 5
50
	 *
51
	 * @var bool
52
	 */
53
	protected $_protect_id = true;
54
55
	public function __construct($db)
56
	{
57
		parent::__construct($db);
58
59
		$this->_protect_id = false;
60
61
		// Let's register all the notifications we know by default
62
		$this->register(1, 'notification', array($this, '_send_notification'));
63
		$this->register(2, 'email', array($this, '_send_email'), array('subject' => 'subject', 'body' => 'body'));
64
		$this->register(3, 'email_daily', array($this, '_send_daily_email'), array('subject' => 'subject', 'body' => 'snippet'));
65
		$this->register(4, 'email_weekly', array($this, '_send_weekly_email'), array('subject' => 'subject', 'body' => 'snippet'));
66
67
		$this->_protect_id = true;
68
69
		call_integration_hook('integrate_notifications_methods', array($this));
70
	}
71
72
	/**
73
	 * We hax a new notification to send out!
74
	 *
75
	 * @param \Notifications_Task $task
76
	 */
77
	public function add(\Notifications_Task $task)
78
	{
79
		$this->_to_send[] = $task;
80
	}
81
82
	/**
83
	 * Time to notify our beloved members! YAY!
84
	 */
85
	public function send()
86
	{
87
		Elk_Autoloader::getInstance()->register(SUBSDIR . '/MentionType', '\\ElkArte\\sources\\subs\\MentionType');
88
89
		$this->_notification_frequencies = array(
90
			// 0 is for no notifications, so we start from 1 the counting, that saves a +1 later
91
			1 => 'notification',
92
			'email',
93
			'email_daily',
94
			'email_weekly',
95
		);
96
97
		if (!empty($this->_to_send))
98
		{
99
			foreach ($this->_to_send as $task)
100
				$this->_send_task($task);
101
		}
102
103
		$this->_to_send = array();
104
	}
105
106
	/**
107
	 * Function to register any new notification method.
108
	 *
109
	 * @param int $id This shall be a unique integer representing the
110
	 *            notification method.
111
	 *            <b>WARNING for addons developers</b>: please note that this has
112
	 *            to be unique across addons, so if you develop an addon that
113
	 *            extends notifications, please verify this id has not been
114
	 *            "taken" by someone else!
115
	 * @param int $key The string name identifying the notification method
116
	 * @param mixed|mixed[] $callback A callable function/array/whatever that
117
	 *                      will take care of sending the notification
118
	 * @param null|string[] $lang_data For the moment an array containing at least:
119
	 *                        - 'subject' => 'something'
120
	 *                        - 'body' => 'something_else'
121
	 *                       the two will be used to identify the strings to be
122
	 *                       used for the subject and the body respectively of
123
	 *                       the notification.
124
	 * @throws Elk_Exception
125
	 */
126
	public function register($id, $key, $callback, $lang_data = null)
127
	{
128
		if ($this->_protect_id && $id < 5)
129
			throw new Elk_Exception('error_invalid_notification_id');
130
131
		$this->_notifiers[$key] = array(
132
			'id' => $id,
133
			'key' => $key,
134
			'callback' => $callback,
135
			'lang_data' => $lang_data,
136
		);
137
138
		$this->_notification_frequencies[$id] = $key;
139
	}
140
141
	public function getNotifiers()
142
	{
143
		return $this->_notification_frequencies;
144
	}
145
146
	/**
147
	 * Process a certain task in order to send out the notifications.
148
	 *
149
	 * @param \Notifications_Task $task
150
	 */
151
	protected function _send_task(\Notifications_Task $task)
152
	{
153
		$class = $task->getClass();
154
		$obj = new $class($this->_db);
155
		$obj->setTask($task);
156
157
		require_once(SUBSDIR . '/Notification.subs.php');
158
		$notification_frequencies = filterNotificationMethods($this->_notification_frequencies, $class::getType());
159
160
		// Cleanup the list of members to notify,
161
		// in certain cases it may differ from the list passed (if any)
162
		$task->setMembers($obj->getUsersToNotify());
163
		$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...
164
165
		foreach ($notification_frequencies as $key)
166
		{
167
			if (!empty($notif_prefs[$key]))
168
			{
169
				$bodies = $obj->getNotificationBody($this->_notifiers[$key]['lang_data'], $notif_prefs[$key]);
170
171
				// Just in case...
172
				if (empty($bodies))
173
					continue;
174
175
				call_user_func_array($this->_notifiers[$key]['callback'], array($obj, $task, $bodies, $this->_db));
176
			}
177
		}
178
	}
179
180
	/**
181
	 * Inserts a new mention in the database (those that appear in the mentions area).
182
	 *
183
	 * @param \ElkArte\sources\subs\MentionType\Mention_Type_Interface $obj
184
	 * @param \Notifications_Task $task
185
	 * @param mixed[] $bodies
186
	 * @global $modSettings - Not sure if actually necessary
187
	 */
188
	protected function _send_notification(\ElkArte\sources\subs\MentionType\Mention_Type_Interface $obj, \Notifications_Task $task, $bodies)
189
	{
190
		global $modSettings;
191
192
		$mentioning = new Mentioning($this->_db, new Data_Validator(), $modSettings['enabled_mentions']);
193
		foreach ($bodies as $body)
194
		{
195
			$mentioning->create($obj, array(
0 ignored issues
show
Documentation introduced by
$obj is of type object<ElkArte\sources\s...Mention_Type_Interface>, but the function expects a object<Mention_Type_Interface>.

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...
196
				'id_member_from' => $task['id_member_from'],
197
				'id_member' => $body['id_member_to'],
198
				'id_msg' => $task['id_target'],
199
				'type' => $task['notification_type'],
200
				'log_time' => $task['log_time'],
201
				'status' => $task['source_data']['status'],
202
			));
203
		}
204
	}
205
206
	/**
207
	 * Sends an immediate email notification.
208
	 *
209
	 * @param \ElkArte\sources\subs\MentionType\Mention_Type_Interface $obj
210
	 * @param \Notifications_Task $task
211
	 * @param mixed[] $bodies
212
	 */
213
	protected function _send_email(\ElkArte\sources\subs\MentionType\Mention_Type_Interface $obj, \Notifications_Task $task, $bodies)
214
	{
215
		$last_id = $obj->getLastId();
216
		foreach ($bodies as $body)
217
		{
218
			sendmail($body['email_address'], $body['subject'], $body['body'], null, $last_id);
219
		}
220
	}
221
222
	/**
223
	 * Stores data in the database to send a daily digest.
224
	 *
225
	 * @param \ElkArte\sources\subs\MentionType\Mention_Type_Interface $obj
226
	 * @param \Notifications_Task $task
227
	 * @param mixed[] $bodies
228
	 */
229
	protected function _send_daily_email(\ElkArte\sources\subs\MentionType\Mention_Type_Interface $obj, \Notifications_Task $task, $bodies)
230
	{
231
		foreach ($bodies as $body)
232
		{
233
			$this->_insert_delayed(array(
234
				$task['notification_type'],
235
				$body['id_member_to'],
236
				$task['log_time'],
237
				'd',
238
				$body['body']
239
			));
240
		}
241
	}
242
243
	/**
244
	 * Stores data in the database to send a weekly digest.
245
	 *
246
	 * @param \ElkArte\sources\subs\MentionType\Mention_Type_Interface $obj
247
	 * @param \Notifications_Task $task
248
	 * @param mixed[] $bodies
249
	 */
250
	protected function _send_weekly_email(\ElkArte\sources\subs\MentionType\Mention_Type_Interface $obj, \Notifications_Task $task, $bodies)
251
	{
252
		foreach ($bodies as $body)
253
		{
254
			$this->_insert_delayed(array(
255
				$task['notification_type'],
256
				$body['id_member_to'],
257
				$task['log_time'],
258
				'w',
259
				$body['body']
260
			));
261
		}
262
	}
263
264
	/**
265
	 * Do the insert into the database for daily and weekly digests.
266
	 *
267
	 * @param mixed[] $insert_array
268
	 */
269
	protected function _insert_delayed($insert_array)
270
	{
271
		$this->_db->insert('ignore',
272
			'{db_prefix}pending_notifications',
273
			array(
274
				'notification_type' => 'string-10',
275
				'id_member' => 'int',
276
				'log_time' => 'int',
277
				'frequency' => 'string-1',
278
				'snippet ' => 'string',
279
			),
280
			$insert_array,
281
			array()
282
		);
283
	}
284
285
	/**
286
	 * Loads from the database the notification preferences for a certain type
287
	 * of mention for a bunch of members.
288
	 *
289
	 * @param string[] $notification_frequencies
290
	 * @param string $notification_type
291
	 * @param int[] $members
292
	 */
293
	protected function _getNotificationPreferences($notification_frequencies, $notification_type, $members)
294
	{
295
		$query_members = $members;
296
		// The member 0 is the "default" setting
297
		$query_members[] = 0;
298
299
		require_once(SUBSDIR . '/Notification.subs.php');
300
		$preferences = getUsersNotificationsPreferences($notification_type, $query_members);
301
302
		$notification_types = array();
303
		foreach ($notification_frequencies as $freq)
304
			$notification_types[$freq] = array();
305
306
		// notification_level can be:
307
		//    - 0 => no notification
308
		//    - 1 => only mention
309
		//    - 2 => mention + immediate email
310
		//    - 3 => mention + daily email
311
		//    - 4 => mention + weekly email
312
		//    - 5+ => usable by addons
313
		foreach ($members as $member)
314
		{
315
			$this_pref = $preferences[$member][$notification_type];
316
			if ($this_pref === 0)
317
				continue;
318
319
			// In the following two checks the use of the $this->_notification_frequencies
320
			// is intended, because the numeric id is important and is not preserved
321
			// in the local $notification_frequencies
322
			if (isset($this->_notification_frequencies[1]))
323
				$notification_types[$this->_notification_frequencies[1]][] = $member;
324
325
			if ($this_pref > 1)
326
			{
327
				if (isset($this->_notification_frequencies[$this_pref]) && isset($notification_types[$this->_notification_frequencies[$this_pref]]))
328
					$notification_types[$this->_notification_frequencies[$this_pref]][] = $member;
329
			}
330
		}
331
332
		return $notification_types;
333
	}
334
335
	/**
336
	 * Singleton... until we have something better.
337
	 *
338
	 * @return Notifications
339
	 */
340
	public static function getInstance()
341
	{
342
		if (self::$_instance === null)
343
			self::$_instance = new Notifications(database());
344
345
		return self::$_instance;
346
	}
347
}