Passed
Push — development ( c0e368...7fdcd4 )
by Spuds
01:05 queued 23s
created

AbstractNotificationMessage::getMembersData()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 4
dl 0
loc 9
rs 10
c 0
b 0
f 0
cc 2
nc 2
nop 1
1
<?php
2
3
/**
4
 * Common methods shared by any type of mention so far.
5
 *
6
 * @package   ElkArte Forum
7
 * @copyright ElkArte Forum contributors
8
 * @license   BSD http://opensource.org/licenses/BSD-3-Clause (see accompanying LICENSE.txt file)
9
 *
10
 * @version 2.0 dev
11
 *
12
 */
13
14
namespace ElkArte\Mentions\MentionType;
15
16
use ElkArte\Database\QueryInterface;
17
use ElkArte\Helper\ValuesContainer;
18
use ElkArte\Languages\Txt;
19
use ElkArte\MembersList;
20
use ElkArte\Notifications\NotificationsTask;
21
use ElkArte\UserInfo;
22
23
/**
24
 * Class AbstractNotificationMessage
25
 */
26
abstract class AbstractNotificationMessage implements NotificationInterface
27
{
28
	/** @var string The identifier of the mention (the name that is stored in the db) */
29
	protected static $_type = '';
30
31
	/** @var QueryInterface The database object */
32
	protected $_db;
33
34
	/** @var ValuesContainer The current user object */
35
	protected $user;
36
37
	/** @var NotificationsTask The \ElkArte\NotificationsTask in use */
38
	protected $_task;
39
40
	protected $_to_members_data;
41
42
	/**
43
	 * Constructs a new instance of the class.
44
	 *
45
	 * @param QueryInterface $db The database query interface to use.
46
	 * @param UserInfo $user The user info object to use.
47
	 * @return void
48
	 */
49
	public function __construct(QueryInterface $db, UserInfo $user)
50
	{
51
		$this->_db = $db;
52
		$this->user = $user;
53
	}
54
55
	/**
56
	 * {@inheritDoc}
57
	 */
58
	public static function getType()
59
	{
60
		return static::$_type;
61
	}
62
63
	/**
64
	 * {@inheritDoc}
65
	 */
66
	public function setUsersToNotify()
67
	{
68
		if ($this->_task !== null)
69
		{
70
			$this->_task->setMembers((array) $this->_task['source_data']['id_members']);
71
		}
72
	}
73
74
	/**
75
	 * {@inheritDoc}
76
	 */
77
	abstract public function getNotificationBody($lang_data, $members);
78
79
	/**
80
	 * {@inheritDoc}
81
	 */
82
	public function setTask(NotificationsTask $task)
83
	{
84
		$this->_task = $task;
85
	}
86
87
	/**
88
	 * {@inheritDoc}
89
	 * By default returns null.
90
	 */
91
	public function getLastId()
92
	{
93
		return null;
94
	}
95
96
	/**
97
	 * {@inheritDoc}
98
	 */
99
	public static function isNotAllowed($method)
100
	{
101
		return false;
102
	}
103
104
	/**
105
	 * {@inheritDoc}
106
	 */
107
	public static function canUse()
108
	{
109
		return true;
110
	}
111
112
	/**
113
	 * {@inheritDoc}
114
	 */
115
	public static function hasHiddenInterface()
116
	{
117
		return false;
118
	}
119
120
	/**
121
	 * {@inheritDoc}
122
	 */
123
	public function insert($member_from, $members_to, $target, $time = null, $status = null, $is_accessible = null)
124
	{
125
		$inserts = [];
126
127
		// $time is not checked because it's useless
128
		$existing = [];
129
		$this->_db->fetchQuery('
130
			SELECT 
131
				id_member
132
			FROM {db_prefix}log_mentions
133
			WHERE id_member IN ({array_int:members_to})
134
				AND mention_type = {string:type}
135
				AND id_member_from = {int:member_from}
136
				AND id_target = {int:target}',
137
			[
138
				'members_to' => $members_to,
139
				'type' => static::$_type,
140
				'member_from' => $member_from,
141
				'target' => $target,
142
			]
143
		)->fetch_callback(
144
			static function ($row) use (&$existing) {
145
				$existing[] = (int) $row['id_member'];
146
			}
147
		);
148
149
		// If the member has already been mentioned, it's not necessary to do it again
150
		$actually_mentioned = [];
151
152
		// If they are using a buddy/ignore list, we need to take that into account
153
		$this->getMembersData($members_to);
154
155
		foreach ($members_to as $id_member)
156
		{
157
			// If the notification can not be sent, mark it as not accessible and read
158
			if (!$this->_validateMemberRelationship($member_from, $id_member))
159
			{
160
				$is_accessible = 0;
161
				$status = 1;
162
			}
163
164
			if (!in_array($id_member, $existing, true))
165
			{
166
				$inserts[] = [
167
					$id_member,
168
					$target,
169
					$status ?? 0,
170
					$is_accessible ?? 1,
171
					$member_from,
172
					$time ?? time(),
173
					static::$_type
174
				];
175
176
				// Don't update the mention counter if they can't really see this
177
				if ($is_accessible !== 0)
178
				{
179
					$actually_mentioned[] = $id_member;
180
				}
181
			}
182
		}
183
184
		if (!empty($inserts))
185
		{
186
			// Insert the new mentions
187
			$this->_db->insert('',
188
				'{db_prefix}log_mentions',
189
				[
190
					'id_member' => 'int',
191
					'id_target' => 'int',
192
					'status' => 'int',
193
					'is_accessible' => 'int',
194
					'id_member_from' => 'int',
195
					'log_time' => 'int',
196
					'mention_type' => 'string-12',
197
				],
198
				$inserts,
199
				['id_mention']
200
			);
201
		}
202
203
		return $actually_mentioned;
204
	}
205
206
	/**
207
	 * Returns basic data about the members to be notified.
208
	 *
209
	 * @return array
210
	 */
211
	protected function getMembersData($members_to)
212
	{
213
		if ($this->_to_members_data === null)
214
		{
215
			require_once(SUBSDIR . '/Members.subs.php');
216
			$this->_to_members_data = getBasicMemberData($members_to, ['preferences' => true, 'lists' => 'true']);
217
		}
218
219
		return $this->_to_members_data;
220
	}
221
222
	/**
223
	 * Validates the relationship between two members.
224
	 *
225
	 * @param int $fromMember The ID of the member sending the notification.
226
	 * @param int $toMember The ID of the member receiving the notification.
227
	 * @return bool Returns true if the notification can be sent, false otherwise.
228
	 */
229
	protected function _validateMemberRelationship($fromMember, $toMember)
230
	{
231
		// Everyone
232
		if (empty($this->_to_members_data[$toMember]['notify_from']))
233
		{
234
			return true;
235
		}
236
237
		// Not those on my ignore list
238
		if ($this->_to_members_data[$toMember]['notify_from'] === 1)
239
		{
240
			return $this->notOnIgnoreList($toMember, $fromMember);
241
		}
242
243
		// Only friends and pesky admins
244
		if ($this->_to_members_data[$toMember]['notify_from'] === 2)
245
		{
246
			return $this->isBuddyNotificationAllowed($fromMember, $toMember);
247
		}
248
249
		return false;
250
	}
251
252
	/**
253
	 * Check if a member is not on the ignore list of another member
254
	 *
255
	 * @param int $toMember The ID of the member to check if they are on the ignore list
256
	 * @param int $fromMember The ID of the member who is being checked against the ignore list
257
	 * @return bool Returns true if the fromMember is not on the ignore list of the toMember, false otherwise
258
	 */
259
	public function notOnIgnoreList(int $toMember, int $fromMember): bool
260
	{
261
		if (empty($this->_to_members_data[$toMember]['pm_ignore_list']))
262
		{
263
			return true;
264
		}
265
266
		$ignoreUsers = array_map('intval', explode(',', $this->_to_members_data[$toMember]['pm_ignore_list']));
267
		if (in_array($fromMember, $ignoreUsers, true))
268
		{
269
			return false;
270
		}
271
272
		return true;
273
	}
274
275
	/**
276
	 * Determines if buddy notification is allowed between two members.
277
	 *
278
	 * @param int $fromMember The member ID of the sender.
279
	 * @param int $toMember The member ID of the recipient.
280
	 *
281
	 * @return bool True if buddy notification is allowed, false otherwise.
282
	 */
283
	public function isBuddyNotificationAllowed(int $fromMember, int $toMember): bool
284
	{
285
		// Admins/Global mods get a pass
286
		MembersList::load($fromMember, false, 'profile');
287
		$fromProfile = MembersList::get($fromMember);
288
		$groups = array_map('intval', array_merge([$fromProfile['id_group'], $fromProfile['id_post_group']], (empty($fromProfile['additional_groups']) ? [] : explode(',', $fromProfile['additional_groups']))));
289
		if (in_array(1, $groups, true) || in_array(2, $groups, true))
290
		{
291
			return true;
292
		}
293
294
		// No buddies, no notification either
295
		if (empty($this->_to_members_data[$toMember]['buddy_list']))
296
		{
297
			return false;
298
		}
299
300
		$buddyList = array_map('intval', explode(',', $this->_to_members_data[$toMember]['buddy_list']));
301
		if (in_array($fromMember, $buddyList, true))
302
		{
303
			return true;
304
		}
305
306
		return false;
307
	}
308
309
	/**
310
	 * Returns an array of notification strings based on template and replacements.
311
	 *
312
	 * @param string $template The email notification template to use.
313
	 * @param array $keys Pair values to match the $txt indexes to subject and body
314
	 * @param array $members The array of member IDs to generate notification strings for.
315
	 * @param NotificationsTask $task The NotificationsTask object to retrieve member data from.
316
	 * @param array $lang_files An optional array of language files to load strings from.
317
	 * @param array $replacements Additional replacements for the loadEmailTemplate function (optional)
318
	 * @return array The array of generated notification strings.
319
	 */
320
	protected function _getNotificationStrings($template, $keys, $members, NotificationsTask $task, $lang_files = [], $replacements = [])
321
	{
322
		$recipientData = $task->getMembersData();
323
324
		$return = [];
325
326
		// Templates are for outbound emails
327
		if (!empty($template))
328
		{
329
			require_once(SUBSDIR . '/Notification.subs.php');
330
331
			foreach ($members as $member)
332
			{
333
				$replacements['REALNAME'] = $recipientData[$member]['real_name'];
334
				$replacements['UNSUBSCRIBELINK'] = replaceBasicActionUrl('{script_url}?action=notify;sa=unsubscribe;token=' .
335
					getNotifierToken($member, $recipientData[$member]['email_address'], $recipientData[$member]['password_salt'], $task->notification_type, $task->id_target));
0 ignored issues
show
Bug Best Practice introduced by
The property id_target does not exist on ElkArte\Notifications\NotificationsTask. Since you implemented __get, consider adding a @property annotation.
Loading history...
Bug Best Practice introduced by
The property notification_type does not exist on ElkArte\Notifications\NotificationsTask. Since you implemented __get, consider adding a @property annotation.
Loading history...
336
				$langStrings = $this->_loadStringsByTemplate($template, $members, $recipientData, $lang_files, $replacements);
337
338
				$return[] = [
339
					'id_member_to' => $member,
340
					'email_address' => $recipientData[$member]['email_address'],
341
					'subject' => $langStrings[$recipientData[$member]['lngfile']]['subject'],
342
					'body' => $langStrings[$recipientData[$member]['lngfile']]['body'],
343
					'last_id' => 0
344
				];
345
			}
346
		}
347
		// No template, must be an on-site notification
348
		else
349
		{
350
			foreach ($members as $member)
351
			{
352
				$return[] = [
353
					'id_member_to' => $member,
354
					'email_address' => $recipientData[$member]['email_address'],
355
					'subject' => $keys['subject'],
356
					'body' => $keys['body'] ?? '',
357
					'last_id' => 0
358
				];
359
			}
360
		}
361
362
		return $return;
363
	}
364
365
	/**
366
	 * Loads template strings for multiple languages based on a template, using $txt values
367
	 *
368
	 * @param string $template The email template name to load strings for.
369
	 * @param array $users An array of user IDs.
370
	 * @param array $users_data An array containing user data, must contain lngfile index
371
	 * @param array $lang_files Optional. An array of language files to load.
372
	 * @param array $replacements Optional. An array of replacements for the template.
373
	 * @return array An associative array where the keys are language codes and the values are the loaded template strings.
374
	 */
375
	protected function _loadStringsByTemplate($template, $users, $users_data, $lang_files = array(), $replacements = array())
376
	{
377
		require_once(SUBSDIR . '/Mail.subs.php');
378
379
		$lang = $this->user->language;
0 ignored issues
show
Bug Best Practice introduced by
The property language does not exist on ElkArte\Helper\ValuesContainer. Since you implemented __get, consider adding a @property annotation.
Loading history...
380
		$langs = [];
381
		foreach ($users as $user)
382
		{
383
			$langs[$users_data[$user]['lngfile']] = $users_data[$user]['lngfile'];
384
		}
385
386
		// Let's load all the languages into a cache thingy.
387
		$langtxt = [];
388
		foreach ($langs as $lang)
389
		{
390
			$langtxt[$lang] = loadEmailTemplate($template, $replacements, $lang, false, true, array('digest', 'snippet'), $lang_files);
391
		}
392
393
		// Better be sure we have the correct language loaded (though it may be useless)
394
		if (!empty($lang_files) && $lang !== $this->user->language)
395
		{
396
			foreach ($lang_files as $file)
397
			{
398
				Txt::load($file);
399
			}
400
		}
401
402
		return $langtxt;
403
	}
404
}
405