Passed
Push — development ( e156b5...b889cb )
by Spuds
01:09 queued 28s
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
		$ignoreUsers = array_map('intval', explode(',', $this->_to_members_data[$toMember]['pm_ignore_list']));
238
		$buddyList = array_map('intval', explode(',', $this->_to_members_data[$toMember]['buddy_list']));
239
240
		// Not those on my ignore list
241
		if ($this->_to_members_data[$toMember]['notify_from'] === 1 && in_array($fromMember, $ignoreUsers, true))
242
		{
243
			return false;
244
		}
245
246
		// Only friends and pesky admins/global mods
247
		if ($this->_to_members_data[$toMember]['notify_from'] === 2)
248
		{
249
			if (in_array($fromMember, $buddyList, true))
250
			{
251
				return true;
252
			}
253
254
			MembersList::load($fromMember, false, 'profile');
255
			$fromProfile = MembersList::get($fromMember);
256
			$groups = array_map('intval', array_merge([$fromProfile['id_group'], $fromProfile['id_post_group']], (empty($fromProfile['additional_groups']) ? [] : explode(',', $fromProfile['additional_groups']))));
257
			if (in_array(1, $groups, true) || in_array(2, $groups, true))
258
			{
259
				return true;
260
			}
261
		}
262
263
		return false;
264
	}
265
266
	/**
267
	 * Returns an array of notification strings based on template and replacements.
268
	 *
269
	 * @param string $template The email notification template to use.
270
	 * @param array $keys Pair values to match the $txt indexes to subject and body
271
	 * @param array $members The array of member IDs to generate notification strings for.
272
	 * @param NotificationsTask $task The NotificationsTask object to retrieve member data from.
273
	 * @param array $lang_files An optional array of language files to load strings from.
274
	 * @param array $replacements Additional replacements for the loadEmailTemplate function (optional)
275
	 * @return array The array of generated notification strings.
276
	 */
277
	protected function _getNotificationStrings($template, $keys, $members, NotificationsTask $task, $lang_files = [], $replacements = [])
278
	{
279
		$recipientData = $task->getMembersData();
280
281
		$return = [];
282
283
		// Templates are for outbound emails
284
		if (!empty($template))
285
		{
286
			require_once(SUBSDIR . '/Notification.subs.php');
287
288
			foreach ($members as $member)
289
			{
290
				$replacements['REALNAME'] = $recipientData[$member]['real_name'];
291
				$replacements['UNSUBSCRIBELINK'] = replaceBasicActionUrl('{script_url}?action=notify;sa=unsubscribe;token=' .
292
					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...
293
				$langStrings = $this->_loadStringsByTemplate($template, $members, $recipientData, $lang_files, $replacements);
294
295
				$return[] = [
296
					'id_member_to' => $member,
297
					'email_address' => $recipientData[$member]['email_address'],
298
					'subject' => $langStrings[$recipientData[$member]['lngfile']]['subject'],
299
					'body' => $langStrings[$recipientData[$member]['lngfile']]['body'],
300
					'last_id' => 0
301
				];
302
			}
303
		}
304
		// No template, must be an on-site notification
305
		else
306
		{
307
			foreach ($members as $member)
308
			{
309
				$return[] = [
310
					'id_member_to' => $member,
311
					'email_address' => $recipientData[$member]['email_address'],
312
					'subject' => $keys['subject'],
313
					'body' => $keys['body'] ?? '',
314
					'last_id' => 0
315
				];
316
			}
317
		}
318
319
		return $return;
320
	}
321
322
	/**
323
	 * Loads template strings for multiple languages based on a template, using $txt values
324
	 *
325
	 * @param string $template The email template name to load strings for.
326
	 * @param array $users An array of user IDs.
327
	 * @param array $users_data An array containing user data, must contain lngfile index
328
	 * @param array $lang_files Optional. An array of language files to load.
329
	 * @param array $replacements Optional. An array of replacements for the template.
330
	 * @return array An associative array where the keys are language codes and the values are the loaded template strings.
331
	 */
332
	protected function _loadStringsByTemplate($template, $users, $users_data, $lang_files = array(), $replacements = array())
333
	{
334
		require_once(SUBSDIR . '/Mail.subs.php');
335
336
		$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...
337
		$langs = [];
338
		foreach ($users as $user)
339
		{
340
			$langs[$users_data[$user]['lngfile']] = $users_data[$user]['lngfile'];
341
		}
342
343
		// Let's load all the languages into a cache thingy.
344
		$langtxt = [];
345
		foreach ($langs as $lang)
346
		{
347
			$langtxt[$lang] = loadEmailTemplate($template, $replacements, $lang, false, true, array('digest', 'snippet'), $lang_files);
348
		}
349
350
		// Better be sure we have the correct language loaded (though it may be useless)
351
		if (!empty($lang_files) && $lang !== $this->user->language)
352
		{
353
			foreach ($lang_files as $file)
354
			{
355
				Txt::load($file);
356
			}
357
		}
358
359
		return $langtxt;
360
	}
361
}
362