ModerationCenter   F
last analyzed

Complexity

Total Complexity 208

Size/Duplication

Total Lines 1960
Duplicated Lines 0 %

Test Coverage

Coverage 45.74%

Importance

Changes 0
Metric Value
eloc 988
dl 0
loc 1960
ccs 354
cts 774
cp 0.4574
rs 1.612
c 0
b 0
f 0
wmc 208

27 Methods

Rating   Name   Duplication   Size   Complexity  
A action_showNotice() 0 24 3
F prepareModcenter() 0 255 29
B action_moderationHome() 0 50 9
A action_modEndSession() 0 9 1
A action_index() 0 9 1
F action_reportedPosts() 0 184 31
F action_viewWarningLog() 0 178 18
A list_getWarningCount() 0 3 1
F action_moderationSettings() 0 108 18
A block_watchedUsers() 0 22 5
A list_getWatchedUserCount() 0 5 1
A list_getWarnings() 0 3 1
A action_viewWarnings() 0 25 1
A list_getWatchedUserPosts() 0 4 1
A block_latestNews() 0 8 1
A list_getWatchedUsers() 0 4 1
C action_modifyWarningTemplate() 0 108 16
F action_modReport() 0 249 23
A list_getWarningTemplates() 0 3 1
A list_getWarningTemplateCount() 0 3 1
B action_viewWarningTemplates() 0 114 4
A block_groupRequests() 0 13 2
A list_getWatchedUserPostsCount() 0 5 1
A block_actionRequired() 0 25 1
F action_viewWatchedUsers() 0 214 22
B block_notes() 0 79 11
A block_reportedPosts() 0 31 4

How to fix   Complexity   

Complex Class

Complex classes like ModerationCenter often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use ModerationCenter, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/**
4
 * Moderation Center, provides at a glance view of moderation items to the team
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
 * This file contains code covered by:
11
 * copyright: 2011 Simple Machines (http://www.simplemachines.org)
12
 *
13
 * @version 2.0 Beta 1
14
 *
15
 */
16
17
namespace ElkArte\Controller;
18
19
use BBC\ParserWrapper;
20
use ElkArte\AbstractController;
21
use ElkArte\Action;
22
use ElkArte\AdminController\ManageMembers;
23
use ElkArte\AdminController\Modlog;
24
use ElkArte\Cache\Cache;
25
use ElkArte\Exceptions\Exception;
26
use ElkArte\Helper\Util;
27
use ElkArte\Languages\Txt;
28
use ElkArte\Menu\Menu;
29
use ElkArte\MessagesDelete;
30
use ElkArte\User;
31
32
/**
33
 * Provides overview of moderation items to the team
34
 */
35
class ModerationCenter extends AbstractController
36
{
37
	/** @var array Holds function array to pass to callMenu to call the right moderation area */
38
	private $_mod_include_data;
39
40
	/**
41
	 * Entry point for the moderation center.
42
	 *
43
	 * @see AbstractController::action_index
44
	 */
45 6
	public function action_index()
46
	{
47
		// Set up moderation menu.
48 6
		$this->prepareModcenter();
49
50 6
		// And off we go
51 6
		$action = new Action();
52
		$action->initialize(['action' => $this->_mod_include_data]);
53
		$action->dispatch('action');
54
	}
55
56
	/**
57
	 * Prepare menu, make checks, load files, and create moderation menu.
58
	 *
59 8
	 * This can be called from the class, or from outside, to set up moderation menu.
60
	 */
61 8
	public function prepareModcenter(): void
62
	{
63
		global $txt, $context, $modSettings, $options;
64 8
65
		// Don't run this twice... and don't conflict with the admin bar.
66 2
		if (isset($context['admin_area']))
67
		{
68
			return;
69 6
		}
70 6
71 6
		$context['can_moderate_boards'] = $this->user->mod_cache['bq'] !== '0=1';
0 ignored issues
show
Bug Best Practice introduced by
The property mod_cache does not exist on ElkArte\Helper\ValuesContainer. Since you implemented __get, consider adding a @property annotation.
Loading history...
72
		$context['can_moderate_groups'] = $this->user->mod_cache['gq'] !== '0=1';
73
		$context['can_moderate_approvals'] = $modSettings['postmod_active'] && !empty($this->user->mod_cache['ap']);
74 6
75
		// Everyone using this area must be allowed here!
76
		if (!$context['can_moderate_boards'] && !$context['can_moderate_groups'] && !$context['can_moderate_approvals'])
77
		{
78
			isAllowedTo('access_mod_center');
79
		}
80 6
81
		// We're gonna want a menu of some kind.
82
		require_once(SUBSDIR . '/Menu.subs.php');
83 6
84 6
		// Load the language, and the template.
85
		Txt::load('ModerationCenter');
86 6
		loadCSSFile('admin.css');
87
88
		if (!empty($options['admin_preferences']))
89
		{
90
			$context['admin_preferences'] = serializeToJson($options['admin_preferences'], static function ($array_form) {
91
				global $context;
92
93
				$context['admin_preferences'] = $array_form;
94
				require_once(SUBSDIR . '/Admin.subs.php');
95
				updateAdminPreferences();
96
			});
97
		}
98 6
		else
99
		{
100
			$context['admin_preferences'] = [];
101 6
		}
102
103
		$context['robot_no_index'] = true;
104 6
105 6
		// Moderation counts for things that this moderator can take care of
106
		require_once(SUBSDIR . '/Moderation.subs.php');
107
		$mod_counts = loadModeratorMenuCounts();
108
109 3
		// This is the menu structure - refer to subs/Menu.subs.php for the details.
110 6
		$moderation_areas = [
111
			'main' => [
112
				'title' => $txt['mc_main'],
113 6
				'areas' => [
114 6
					'index' => [
115 6
						'label' => $txt['moderation_center'],
116 6
						'controller' => ModerationCenter::class,
117 6
						'function' => 'action_moderationHome',
118
						'class' => 'i-home i-admin',
119
					],
120 6
					'settings' => [
121 6
						'label' => $txt['mc_settings'],
122 6
						'controller' => ModerationCenter::class,
123 6
						'function' => 'action_moderationSettings',
124 6
						'class' => 'i-switch-on i-admin',
125
					],
126
					'modlogoff' => [
127 6
						'label' => $txt['mc_logoff'],
128 6
						'controller' => ModerationCenter::class,
129 6
						'function' => 'action_modEndSession',
130 6
						'enabled' => empty($modSettings['securityDisable_moderate']),
131 6
						'class' => 'i-sign-out i-admin',
132 6
					],
133
					'notice' => [
134
						'controller' => ModerationCenter::class,
135
						'function' => 'action_showNotice',
136
						'select' => 'index',
137
						'class' => 'i-post-text i-admin',
138
					],
139
				],
140
			],
141
			'logs' => [
142
				'title' => $txt['mc_logs'],
143
				'areas' => [
144 6
					'modlog' => [
145
						'label' => $txt['modlog_view'],
146
						'enabled' => featureEnabled('ml') && $context['can_moderate_boards'],
147 6
						'controller' => Modlog::class,
148 6
						'function' => 'action_log',
149 6
						'class' => 'i-comments i-admin',
150 6
					],
151 6
					'warnings' => [
152 6
						'label' => $txt['mc_warnings'],
153
						'enabled' => featureEnabled('w') && !empty($modSettings['warning_enable']) && $context['can_moderate_boards'],
154
						'controller' => ModerationCenter::class,
155 6
						'function' => 'action_viewWarnings',
156 6
						'class' => 'i-warn i-admin',
157 6
						'subsections' => [
158 6
							'log' => [$txt['mc_warning_log']],
159 6
							'templates' => [$txt['mc_warning_templates'], 'issue_warning'],
160 6
						],
161
					],
162 6
				],
163 6
			],
164
			'posts' => [
165
				'title' => $txt['mc_posts'] . (empty($mod_counts['pt_total']) ? '' : ' [' . $mod_counts['pt_total'] . ']'),
166
				'enabled' => $context['can_moderate_boards'] || $context['can_moderate_approvals'],
167
				'areas' => [
168
					'postmod' => [
169 6
						'label' => $txt['mc_unapproved_posts'] . (empty($mod_counts['postmod']) ? '' : ' [' . $mod_counts['postmod'] . ']'),
170 6
						'enabled' => $context['can_moderate_approvals'],
171
						'controller' => PostModeration::class,
172
						'function' => 'action_index',
173 6
						'class' => 'i-post-text i-admin',
174 6
						'custom_url' => getUrl('action', ['action' => 'moderate', 'area' => 'postmod']),
175 6
						'subsections' => [
176 6
							'posts' => [$txt['mc_unapproved_replies']],
177 6
							'topics' => [$txt['mc_unapproved_topics']],
178 6
						],
179 6
					],
180
					'emailmod' => [
181 6
						'label' => $txt['mc_emailerror'] . (empty($mod_counts['emailmod']) ? '' : ' [' . $mod_counts['emailmod'] . ']'),
182 6
						'enabled' => !empty($modSettings['maillist_enabled']) && allowedTo('approve_emails'),
183
						'function' => 'UnapprovedEmails',
184
						'class' => 'i-envelope-blank i-admin',
185
						'custom_url' => getUrl('action', ['action' => 'moderate', 'area' => 'maillist', 'sa' => 'emaillist']),
186 6
					],
187 6
					'attachmod' => [
188 6
						'label' => $txt['mc_unapproved_attachments'] . (empty($mod_counts['attachments']) ? '' : ' [' . $mod_counts['attachments'] . ']'),
189 6
						'enabled' => $context['can_moderate_approvals'],
190 6
						'controller' => PostModeration::class,
191 6
						'function' => 'action_index',
192
						'class' => 'i-paperclip i-admin',
193
						'custom_url' => getUrl('action', ['action' => 'moderate', 'area' => 'attachmod', 'sa' => 'attachments']),
194 6
					],
195 6
					'reports' => [
196 6
						'label' => $txt['mc_reported_posts'] . (empty($mod_counts['reports']) ? '' : ' [' . $mod_counts['reports'] . ']'),
197 6
						'enabled' => $context['can_moderate_boards'],
198 6
						'controller' => ModerationCenter::class,
199 6
						'function' => 'action_reportedPosts',
200 6
						'class' => 'i-modify i-admin',
201
						'subsections' => [
202
							'open' => [$txt['mc_reportedp_active'] . (empty($mod_counts['reports']) ? '' : ' [' . $mod_counts['reports'] . ']')],
203 6
							'closed' => [$txt['mc_reportedp_closed']],
204 6
						],
205 6
					],
206 6
					'pm_reports' => [
207 6
						'label' => $txt['mc_reported_pms'] . (empty($mod_counts['pm_reports']) ? '' : ' [' . $mod_counts['pm_reports'] . ']'),
208 6
						'enabled' => $this->user->is_admin,
0 ignored issues
show
Bug Best Practice introduced by
The property is_admin does not exist on ElkArte\Helper\ValuesContainer. Since you implemented __get, consider adding a @property annotation.
Loading history...
209
						'controller' => ModerationCenter::class,
210 6
						'function' => 'action_reportedPosts',
211 6
						'class' => 'i-alert i-admin',
212
						'subsections' => [
213
							'open' => [$txt['mc_reportedp_active']],
214
							'closed' => [$txt['mc_reportedp_closed']],
215 6
						],
216 6
					],
217 6
				],
218 6
			],
219
			'groups' => [
220 6
				'title' => $txt['mc_groups'] . (empty($mod_counts['mg_total']) ? '' : ' [' . $mod_counts['mg_total'] . ']'),
221 6
				'enabled' => $context['can_moderate_groups'],
222
				'areas' => [
223
					'userwatch' => [
224
						'label' => $txt['mc_watched_users_title'],
225
						'enabled' => featureEnabled('w') && !empty($modSettings['warning_enable']) && $context['can_moderate_boards'],
226
						'controller' => ModerationCenter::class,
227 6
						'function' => 'action_viewWatchedUsers',
228 6
						'class' => 'i-user i-admin',
229
						'subsections' => [
230
							'member' => [$txt['mc_watched_users_member']],
231 6
							'post' => [$txt['mc_watched_users_post']],
232 6
						],
233 6
					],
234 6
					'groups' => [
235 6
						'label' => $txt['mc_group_requests'] . (empty($mod_counts['groupreq']) ? '' : ' [' . $mod_counts['groupreq'] . ']'),
236 6
						'controller' => Groups::class,
237
						'function' => 'action_index',
238 6
						'class' => 'i-users i-admin',
239 6
						'custom_url' => getUrl('action', ['action' => 'moderate', 'area' => 'groups', 'sa' => 'requests']),
240
					],
241
					'members' => [
242
						'enabled' => allowedTo('moderate_forum'),
243 6
						'label' => $txt['mc_member_requests'] . (empty($mod_counts['memberreq']) ? '' : ' [' . $mod_counts['memberreq'] . ']'),
244 6
						'controller' => ManageMembers::class,
245 6
						'function' => 'action_approve',
246 6
						'class' => 'i-user-plus i-admin',
247 6
						'custom_url' => getUrl('admin', ['action' => 'admin', 'area' => 'viewmembers', 'sa' => 'browse', 'type' => 'approve']),
248 6
					],
249
					'viewgroups' => [
250
						'label' => $txt['mc_view_groups'],
251 6
						'controller' => Groups::class,
252 6
						'function' => 'action_index',
253 6
						'class' => 'i-view i-admin',
254 6
					],
255 6
				],
256 6
			],
257 6
		];
258
259
		// Make sure the administrator has a valid session...
260 6
		validateSession('moderate');
261 6
262 6
		// I don't know where we're going - I don't know where we've been...
263 6
		$menuOptions = [
264 6
			'action' => 'moderate',
265
			'hook' => 'moderation',
266
			'disable_url_session_check' => true,
267
		];
268
269
		// Setup the menu
270
		$mod_include_data = (new Menu())
271 6
			->addMenuData($moderation_areas)
272
			->addOptions($menuOptions)
273
			->prepareMenu()
274
			->setContext()
275 6
			->getIncludeData();
276
277
		unset($moderation_areas);
278
279
		// Retain the ID information in case required by a subaction.
280
		$context['moderation_menu_id'] = $context['max_menu_id'];
281 6
		$context['moderation_menu_name'] = 'menu_data_' . $context['moderation_menu_id'];
282 6
283
		$context[$context['moderation_menu_name']]['object']->prepareTabData([
284
			'title' => $txt['moderation_center'],
285 6
			'description' => sprintf($txt['mc_description'], $context['user']['name'], getUrl('action', ['action' => 'moderate', 'area' => 'settings']))
286 6
		]);
287 6
288 6
		// What a pleasant shortcut - even tho we're not *really* on the admin screen who cares...
289 6
		$context['admin_area'] = $mod_include_data['current_area'];
290 6
291
		// Build the link tree.
292
		$context['breadcrumbs'][] = [
293 6
			'url' => getUrl('action', ['action' => 'moderate']),
294
			'name' => $txt['moderation_center'],
295
		];
296 6
297 6
		if (isset($mod_include_data['current_area']) && $mod_include_data['current_area'] !== 'index')
298 6
		{
299
			$context['breadcrumbs'][] = [
300
				'url' => getUrl('action', ['action' => 'moderate', 'area' => $mod_include_data['current_area']]),
301 6
				'name' => $mod_include_data['label'],
302
			];
303 4
		}
304 4
305 4
		if (!empty($mod_include_data['current_subsection']) && isset($mod_include_data['subsections'][$mod_include_data['current_subsection']]['label'])
306
			&& $mod_include_data['subsections'][$mod_include_data['current_subsection']]['label'] !== $mod_include_data['label'])
307
		{
308
			$context['breadcrumbs'][] = [
309 6
				'url' => getUrl('action', ['action' => 'moderate', 'area' => $mod_include_data['current_area'], 'sa' => $mod_include_data['current_subsection']]),
310 6
				'name' => $mod_include_data['subsections'][$mod_include_data['current_subsection']]['label'],
311
			];
312 4
		}
313 4
314 4
		// Finally, store this, so that if we're called from the class, it can use it.
315
		$this->_mod_include_data = $mod_include_data;
316
	}
317
318
	/**
319 6
	 * This handler presents the home page of the moderation center.
320 6
	 */
321
	public function action_moderationHome(): void
322
	{
323
		global $txt, $context;
324
325 2
		theme()->getTemplates()->load('ModerationCenter');
326
		loadJavascriptFile('admin.js', [], 'admin_scripts');
327 2
328
		$context['page_title'] = $txt['moderation_center'];
329 2
		$context['sub_template'] = 'moderation_center';
330 2
331
		// Start off with no blocks
332 2
		$valid_blocks = [];
333 2
334
		// Load what blocks the user actually can see...
335
		$valid_blocks['p'] = 'notes';
336 2
		$valid_blocks['n'] = 'latestNews';
337
338
		if ($context['can_moderate_boards'])
339 2
		{
340 2
			$valid_blocks['a'] = 'actionRequired';
341
			$valid_blocks['r'] = 'reportedPosts';
342 2
			$valid_blocks['w'] = 'watchedUsers';
343
		}
344 2
345 2
		if ($context['can_moderate_groups'])
346 2
		{
347
			$valid_blocks['g'] = 'groupRequests';
348
		}
349 2
350
		if (empty(User::$settings['mod_prefs']))
351 2
		{
352
			$user_blocks = 'n' . ($context['can_moderate_boards'] ? 'wra' : '') . ($context['can_moderate_groups'] ? 'g' : '');
353
		}
354 2
		else
355
		{
356 2
			[, $user_blocks] = explode('|', User::$settings['mod_prefs']);
0 ignored issues
show
Bug introduced by
It seems like ElkArte\User::settings['mod_prefs'] can also be of type null; however, parameter $string of explode() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

356
			[, $user_blocks] = explode('|', /** @scrutinizer ignore-type */ User::$settings['mod_prefs']);
Loading history...
357
		}
358
359
		$user_blocks = str_split($user_blocks);
360
361
		$context['mod_blocks'] = [];
362
		foreach ($valid_blocks as $k => $block)
363 2
		{
364
			if (in_array($k, $user_blocks))
0 ignored issues
show
Bug introduced by
It seems like $user_blocks can also be of type true; however, parameter $haystack of in_array() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

364
			if (in_array($k, /** @scrutinizer ignore-type */ $user_blocks))
Loading history...
365 2
			{
366 2
				$block = 'block_' . $block;
367
368 2
				if (method_exists($this, $block))
369
				{
370 2
					$context['mod_blocks'][] = $this->{$block}();
371
				}
372 2
			}
373
		}
374 2
	}
375
376
	/**
377
	 * This ends a moderator session, requiring authentication to access the MCP again.
378 2
	 */
379
	public function action_modEndSession(): void
380
	{
381
		// This is so easy!
382
		unset($_SESSION['moderate_time']);
383
384
		// Clean any moderator tokens as well.
385
		cleanTokens(false, '-mod');
386
387
		redirectexit('action=moderate');
388
	}
389
390
	/**
391
	 * Show a warning notice sent to a user.
392
	 */
393
	public function action_showNotice(): void
394
	{
395
		global $txt, $context;
396
397
		// What notice have they asked to view
398
		$id_notice = $this->_req->getQuery('nid', 'intval', 0);
399
		$notice = moderatorNotice($id_notice);
400
401
		// legit?
402
		if (empty($notice) || !$context['can_moderate_boards'])
403
		{
404
			throw new Exception('no_access', false);
405
		}
406
407
		[$context['notice_body'], $context['notice_subject']] = $notice;
408
409
		$parser = ParserWrapper::instance();
410
411
		$context['notice_body'] = $parser->parseNotice($context['notice_body']);
412
		$context['page_title'] = $txt['show_notice'];
413
		$context['sub_template'] = 'show_notice';
414
415
		theme()->getLayers()->removeAll();
416
		theme()->getTemplates()->load('ModerationCenter');
417
	}
418
419
	/**
420
	 * Browse all the reported posts.
421
	 *
422
	 * @todo this needs to be given its own file?
423
	 */
424
	public function action_reportedPosts(): void
425
	{
426
		global $txt, $context;
427
428 4
		theme()->getTemplates()->load('ModerationCenter');
429
		require_once(SUBSDIR . '/Moderation.subs.php');
430 4
431
		// Put the open and closed options into tabs, because we can...
432 4
		$context[$context['moderation_menu_name']]['object']->prepareTabData([
433 4
			'title' => $txt['mc_reported_posts'],
434
			'description' => $txt['mc_reported_posts_desc'],
435
		]);
436 4
437 4
		// Set up the comforting bits...
438 4
		$context['page_title'] = $txt['mc_reported_posts'];
439 4
		$context['sub_template'] = 'reported_posts';
440
441
		// This comes under the umbrella of moderating posts.
442
		if ($this->user->mod_cache['bq'] === '0=1')
0 ignored issues
show
Bug Best Practice introduced by
The property mod_cache does not exist on ElkArte\Helper\ValuesContainer. Since you implemented __get, consider adding a @property annotation.
Loading history...
443 4
		{
444 4
			isAllowedTo('moderate_forum');
445
		}
446
447 4
		// Are they wanting to view a particular report?
448
		if ($this->_req->hasQuery('report'))
449
		{
450
			$this->action_modReport();
451
			return;
452
		}
453 4
454
		// This should not be needed...
455 2
		$show_pms = false;
456
		if ($context['admin_area'] === 'pm_reports')
457
		{
458
			$show_pms = true;
459 2
			isAllowedTo('admin_forum');
460 2
461
			// Put the open and closed options into tabs, because we can...
462
			$context[$context['moderation_menu_name']]['object']->prepareTabData([
463
				'title' => $txt['mc_reported_pms'],
464
				'description' => $txt['mc_reported_pms_desc'],
465
			]);
466
			$context['page_title'] = $txt['mc_reported_pms'];
467
		}
468
469
		// Are we viewing open or closed reports?
470
		$context['view_closed'] = $this->_req->getQuery('sa', 'trim|strval', '') === 'closed' ? 1 : 0;
471
472
		// Are we doing any work?
473
		if (($this->_req->hasQuery('ignore') || $this->_req->hasQuery('close')) && $this->_req->hasQuery('rid'))
474
		{
475 2
			checkSession('get');
476
			$rid = $this->_req->getQuery('rid', 'intval', 0);
477
478 2
			// Update the report...
479
			if ($this->_req->hasQuery('ignore'))
480
			{
481
				$ignoreVal = $this->_req->getQuery('ignore', 'intval', 0);
482
				updateReportsStatus($rid, 'ignore', $ignoreVal);
483
			}
484
			elseif ($this->_req->hasQuery('close'))
485
			{
486
				$closeVal = $this->_req->getQuery('close', 'intval', 0);
487
				updateReportsStatus($rid, 'close', $closeVal);
488
			}
489
490
			// Time to update.
491
			updateSettings(['last_mod_report_action' => time()]);
492
			recountOpenReports(true, $show_pms);
493
		}
494
		elseif (isset($this->_req->post->close, $this->_req->post->close_selected))
495
		{
496
			checkSession();
497 2
498
			// All the ones to update...
499
			$toClose = array_map('intval', $this->_req->post->close);
500
			if (!empty($toClose))
501
			{
502
				updateReportsStatus($toClose, 'close', 1);
503
504
				// Time to update.
505
				updateSettings(['last_mod_report_action' => time()]);
506
				recountOpenReports(true, $show_pms);
507
			}
508
		}
509
510
		// How many entries are we viewing?
511
		$context['total_reports'] = totalReports($context['view_closed'], $show_pms);
512
513
		// So, that means we can page index, yes?
514 2
		$start = $this->_req->getQuery('start', 'intval', 0);
515
		$context['page_index'] = constructPageIndex('{scripturl}?action=moderate;area=' . $context['admin_area'] . ($context['view_closed'] ? ';sa=closed' : ''), $start, $context['total_reports'], 10);
516
		$context['start'] = $start;
517 2
518 2
		// By George, that means we in a position to get the reports, golly good.
519
		$context['reports'] = getModReports($context['view_closed'], $context['start'], 10, $show_pms);
520
		$report_ids = array_keys($context['reports']);
521 2
		$report_boards_ids = [];
522 2
		$bbc_parser = ParserWrapper::instance();
523 2
		foreach ($context['reports'] as $row)
524 2
		{
525 2
			$context['reports'][$row['id_report']] = [
526
				'board' => $row['id_board'],
527 2
				'id' => $row['id_report'],
528 2
				'topic_href' => getUrl('topic', ['topic' => $row['id_topic'], 'msg' => $row['id_msg'], 'start' => $row['id_msg'], 'hash' => '#msg' . $row['id_msg']]),
529 2
				'report_href' => getUrl('action', ['action' => 'moderate', 'area' => $context['admin_area'], 'report' => $row['id_report']]),
530 2
				'author' => [
531 2
					'id' => $row['id_author'],
532
					'name' => $row['author_name'],
533 2
					'link' => $row['id_author'] ? '<a href="' . getUrl('profile', ['action' => 'profile', 'u' => $row['id_author']]) . '">' . $row['author_name'] . '</a>' : $row['author_name'],
534 2
					'href' => getUrl('profile', ['action' => 'profile', 'u' => $row['id_author']]),
535 2
				],
536 2
				'comments' => [],
537
				'time_started' => standardTime($row['time_started']),
538
				'last_updated' => standardTime($row['time_updated']),
539 2
				'subject' => $row['subject'],
540 2
				'body' => $bbc_parser->parseReport($row['body']),
541 2
				'num_reports' => $row['num_reports'],
542 2
				'closed' => $row['closed'],
543 2
				'ignore' => $row['ignore_all'],
544 2
				'buttons' => [
545 2
					'inline_mod_check' => [
546
						'checkbox' => 'always',
547
						'enabled' => !$context['view_closed'],
548 2
						'name' => 'close',
549 2
						'value' => $row['id_report'],
550 2
					],
551
					'details' => [
552
						'url' => getUrl('action', ['action' => 'moderate', 'area' => $context['admin_area'], 'report' => $row['id_report']]),
553 2
						'text' => 'mc_reportedp_details',
554 2
						'icon' => 'post-text',
555
					],
556
					'ignore' => [
557 2
						'url' => getUrl('action', ['action' => 'moderate', 'area' => $context['admin_area'], 'sa' => ($context['view_closed'] ? 'closed' : ''), 'ignore' => (int) !$row['ignore_all'], 'rid' => $row['id_report'], 'start' => $context['start'], '{session_data}']),
558 2
						'text' => $row['ignore_all'] ? 'mc_reportedp_unignore' : 'mc_reportedp_ignore',
559 2
						'custom' => $row['ignore_all'] ? '' : 'onclick="return confirm(' . JavaScriptEscape($txt['mc_reportedp_ignore_confirm']) . ');"',
560
						'icon' => 'delete'
561
					],
562 2
					'close' => [
563 2
						'url' => getUrl('action', ['action' => 'moderate', 'area' => $context['admin_area'], 'sa' => ($context['view_closed'] ? 'closed' : ''), 'close' => (int) !$row['closed'], 'rid' => $row['id_report'], 'start' => $context['start'], '{session_data}']),
564
						'text' => $context['view_closed'] ? 'mc_reportedp_open' : 'mc_reportedp_close',
565
						'icon' => $context['view_closed'] ? 'sign-in' : 'close',
566
					],
567 2
				],
568
			];
569
			$report_boards_ids[] = $row['id_board'];
570
		}
571 2
572
		// Get the names of boards these topics are in.
573 2
		if (!empty($report_ids))
574 2
		{
575
			require_once(SUBSDIR . '/Boards.subs.php');
576
			$board_names = getBoardList(['included_boards' => $report_boards_ids], true);
577 2
578
			// Add the board name to the report array
579 2
			foreach ($context['reports'] as $id_report => $report)
580
			{
581 2
				if (!empty($board_names[$report['board']]))
582
				{
583
					$context['reports'][$id_report]['board_name'] = $board_names[$report['board']]['board_name'];
584
				}
585
			}
586
		}
587 2
588
		// Now get all the people who reported it.
589 2
		if (!empty($report_ids))
590 2
		{
591
			$comments = getReportsUserComments($report_ids);
592 2
			foreach ($comments as $id_rep => $rows)
593
			{
594 2
				foreach ($rows as $row)
595 2
				{
596 2
					$context['reports'][$id_rep]['comments'][] = [
597 2
						'id' => $row['id_comment'],
598 2
						'message' => $row['comment'],
599 2
						'raw_time' => $row['time_sent'],
600 2
						'time' => standardTime($row['time_sent']),
601
						'html_time' => htmlTime($row['time_sent']),
602 2
						'timestamp' => forum_time(true, $row['time_sent']),
603 2
						'member' => [
604 2
							'id' => $row['id_member'],
605 2
							'name' => empty($row['reporter']) ? $txt['guest'] : $row['reporter'],
606
							'link' => $row['id_member'] ? '<a href="' . getUrl('profile', ['action' => 'profile', 'u' => $row['id_member']]) . '">' . $row['reporter'] . '</a>' : (empty($row['reporter']) ? $txt['guest'] : $row['reporter']),
607
							'href' => $row['id_member'] ? getUrl('profile', ['action' => 'profile', 'u' => $row['id_member']]) : '',
608
						],
609
					];
610
				}
611 2
			}
612
		}
613
	}
614
615
	/**
616
	 * Get details about the moderation report
617
	 *
618 2
	 * - report is specified in the url param report.
619
	 */
620 2
	public function action_modReport(): void
621
	{
622
		global $context, $txt;
623 2
624 2
		// Have to at least give us something
625
		$report = $this->_req->getQuery('report', 'intval', 0);
626
		if (empty($report))
627
		{
628
			throw new Exception('mc_no_modreport_specified');
629
		}
630 2
631 2
		// This should not be needed...
632
		$show_pms = false;
633
		if ($context['admin_area'] === 'pm_reports')
634
		{
635
			$show_pms = true;
636
			isAllowedTo('admin_forum');
637
		}
638 2
639
		// Get the report details, need this so we can limit access to a particular board
640
		$row = modReportDetails($report, $show_pms);
641 2
642
		// So did we find anything?
643
		if ($row === false)
644
		{
645
			throw new Exception('mc_no_modreport_found');
646
		}
647
648 2
		// Woohoo we found a report and they can see it!  Bad news is we have more work to do
649
		// If they are adding a comment then... add a comment.
650
		if (isset($this->_req->post->add_comment) && !empty($this->_req->post->mod_comment))
651
		{
652
			checkSession();
653
654
			$newComment = trim(Util::htmlspecialchars($this->_req->post->mod_comment));
655
656
			// In it goes.
657
			if (!empty($newComment))
658
			{
659
				addReportComment($report, $newComment);
660
661
				// Redirect to prevent double submission.
662
				redirectexit('action=moderate;area=' . $context['admin_area'] . ';report=' . $report);
663
			}
664 2
		}
665
666 2
		$bbc_parser = ParserWrapper::instance();
667 2
668 2
		$context['report'] = [
669 2
			'id' => $row['id_report'],
670 2
			'topic_id' => $row['id_topic'],
671 2
			'board_id' => $row['id_board'],
672 2
			'message_id' => $row['id_msg'],
673 2
			'message_href' => getUrl('action', ['msg' => $row['id_msg']]),
674
			'message_link' => '<a href="' . getUrl('action', ['msg' => $row['id_msg']]) . '">' . $row['subject'] . '</a>',
675 2
			'report_href' => getUrl('action', ['action' => 'moderate', 'area' => $context['admin_area'], $context['admin_area'] => $row['id_report']]),
676 2
			'author' => [
677 2
				'id' => $row['id_author'],
678 2
				'name' => $row['author_name'],
679
				'link' => $row['id_author'] ? '<a href="' . getUrl('profile', ['action' => 'profile', 'u' => $row['id_author']]) . '">' . $row['author_name'] . '</a>' : $row['author_name'],
680
				'href' => getUrl('profile', ['action' => 'profile', 'u' => $row['id_author']]),
681
			],
682 2
			'comments' => [],
683 2
			'mod_comments' => [],
684 2
			'time_started' => standardTime($row['time_started']),
685 2
			'last_updated' => standardTime($row['time_updated']),
686 2
			'subject' => $row['subject'],
687 2
			'body' => $bbc_parser->parseReport($row['body']),
688 2
			'num_reports' => $row['num_reports'],
689
			'closed' => $row['closed'],
690
			'ignore' => $row['ignore_all']
691
		];
692 2
693 2
		// So what bad things do the reporters have to say about it?
694
		$comments = getReportsUserComments($context['report']['id']);
695 2
		foreach ($comments[$context['report']['id']] as $row)
696 2
		{
697 2
			$context['report']['comments'][] = [
698 2
				'id' => $row['id_comment'],
699 2
				'message' => strtr($row['comment'], ["\n" => '<br />']),
700 2
				'time' => standardTime($row['time_sent']),
701
				'html_time' => htmlTime($row['time_sent']),
702 2
				'timestamp' => forum_time(true, $row['time_sent']),
703 2
				'member' => [
704 2
					'id' => $row['id_member'],
705 2
					'name' => empty($row['reporter']) ? $txt['guest'] : $row['reporter'],
706 2
					'link' => $row['id_member'] ? '<a href="' . getUrl('profile', ['action' => 'profile', 'u' => $row['id_member']]) . '">' . $row['reporter'] . '</a>' : (empty($row['reporter']) ? $txt['guest'] : $row['reporter']),
707
					'href' => $row['id_member'] ? getUrl('profile', ['action' => 'profile', 'u' => $row['id_member']]) : '',
708
					'ip' => !empty($row['member_ip']) && allowedTo('moderate_forum') ? '<a href="' . getUrl('action', ['action' => 'trackip', 'searchip' => $row['member_ip']]) . '">' . $row['member_ip'] . '</a>' : '',
709
				],
710
			];
711
		}
712 2
713 2
		// Hang about old chap, any comments from moderators on this one?
714
		$mod_comments = getReportModeratorsComments($context['report']['id']);
715
		foreach ($mod_comments as $row)
716
		{
717
			$context['report']['mod_comments'][] = [
718
				'id' => $row['id_comment'],
719
				'message' => $bbc_parser->parseReport($row['body']),
720
				'time' => standardTime($row['log_time']),
721
				'html_time' => htmlTime($row['log_time']),
722
				'timestamp' => forum_time(true, $row['log_time']),
723
				'member' => [
724
					'id' => $row['id_member'],
725
					'name' => $row['moderator'],
726
					'link' => $row['id_member'] ? '<a href="' . getUrl('profile', ['action' => 'profile', 'u' => $row['id_member']]) . '">' . $row['moderator'] . '</a>' : $row['moderator'],
727
					'href' => getUrl('profile', ['action' => 'profile', 'u' => $row['id_member']]),
728
				],
729
			];
730
		}
731 2
732 2
		// What have the other moderators done to this message?
733
		require_once(SUBSDIR . '/Modlog.subs.php');
734
		Txt::load('Modlog');
735
736 2
		// This is all the information from the moderation log.
737 2
		$listOptions = [
738 2
			'id' => 'moderation_actions_list',
739 2
			'title' => $txt['mc_modreport_modactions'],
740 2
			'items_per_page' => 15,
741 2
			'no_items_label' => $txt['modlog_no_entries_found'],
742
			'base_href' => getUrl('action', ['action' => 'moderate', 'area' => $context['admin_area'], 'report' => $context['report']['id']]),
743 2
			'default_sort_col' => 'time',
744
			'get_items' => [
745 2
				'function' => 'list_getModLogEntries',
746 2
				'params' => [
747 2
					'lm.id_topic = {int:id_topic}',
748
					['id_topic' => $context['report']['topic_id']],
749
					1,
750
				],
751 2
			],
752
			'get_count' => [
753 2
				'function' => 'list_getModLogEntryCount',
754 2
				'params' => [
755 2
					'lm.id_topic = {int:id_topic}',
756
					['id_topic' => $context['report']['topic_id']],
757
					1,
758
				],
759
			],
760
			// This assumes we are viewing by user.
761
			'columns' => [
762 2
				'action' => [
763
					'header' => [
764
						'value' => $txt['modlog_action'],
765
					],
766
					'data' => [
767
						'db' => 'action_text',
768
						'class' => 'smalltext',
769
					],
770
					'sort' => [
771
						'default' => 'lm.action',
772
						'reverse' => 'lm.action DESC',
773
					],
774
				],
775 2
				'time' => [
776
					'header' => [
777
						'value' => $txt['modlog_date'],
778
					],
779
					'data' => [
780
						'db' => 'time',
781
						'class' => 'smalltext',
782
					],
783
					'sort' => [
784
						'default' => 'lm.log_time',
785
						'reverse' => 'lm.log_time DESC',
786
					],
787
				],
788 2
				'moderator' => [
789
					'header' => [
790
						'value' => $txt['modlog_member'],
791
					],
792
					'data' => [
793
						'db' => 'moderator_link',
794
						'class' => 'smalltext',
795
					],
796
					'sort' => [
797
						'default' => 'mem.real_name',
798
						'reverse' => 'mem.real_name DESC',
799
					],
800
				],
801 2
				'position' => [
802
					'header' => [
803
						'value' => $txt['modlog_position'],
804
					],
805
					'data' => [
806
						'db' => 'position',
807
						'class' => 'smalltext',
808
					],
809
					'sort' => [
810
						'default' => 'mg.group_name',
811
						'reverse' => 'mg.group_name DESC',
812
					],
813
				],
814 2
				'ip' => [
815
					'header' => [
816
						'value' => $txt['modlog_ip'],
817
					],
818
					'data' => [
819
						'db' => 'ip',
820
						'class' => 'smalltext',
821
					],
822
					'sort' => [
823
						'default' => 'lm.ip',
824
						'reverse' => 'lm.ip DESC',
825
					],
826
				],
827
			],
828
		];
829 2
830
		// Create the watched user list.
831
		createList($listOptions);
832 2
833
		// Make sure to get the correct tab selected.
834
		if ($context['report']['closed'])
835
		{
836
			$context[$context['moderation_menu_name']]['current_subsection'] = 'closed';
837
		}
838 2
839 2
		// Finally we are done :P
840
		theme()->getTemplates()->load('ModerationCenter');
841
		if ($context['admin_area'] === 'pm_reports')
842
		{
843
			$context['page_title'] = sprintf($txt['mc_view_pmreport'], $context['report']['author']['name']);
844
			$context['section_title'] = sprintf($txt['mc_view_pmreport'], $context['report']['author']['link']);
845
			$context['section_descripion'] = sprintf($txt['mc_pmreport_summary'], $context['report']['num_reports'], $context['report']['last_updated']);
846
		}
847 2
		else
848 2
		{
849 2
			$context['page_title'] = sprintf($txt['mc_viewmodreport'], $context['report']['subject'], $context['report']['author']['name']);
850
			$context['section_title'] = sprintf($txt['mc_viewmodreport'], $context['report']['message_link'], $context['report']['author']['link']);
851
			$context['section_descripion'] = sprintf($txt['mc_modreport_summary'], $context['report']['num_reports'], $context['report']['last_updated']);
852 2
		}
853 2
854
		$context['mod_buttons'] = [
855
			'ignore' => [
856
				'url' => getUrl('action', ['action' => 'moderate', 'area' => $context['admin_area'], 'ignore' => (int) !$context['report'] ['ignore'], 'rid' => $context['report'] ['id'], '{session_data}']),
857
				'text' => $context['report'] ['ignore'] ? 'mc_reportedp_unignore' : 'mc_reportedp_ignore',
858
				'custom' => $context['report'] ['ignore'] ? '' : 'onclick="return confirm(' . JavaScriptEscape($txt['mc_reportedp_ignore_confirm']) . ');"',
859
				'icon' => 'delete'
860
			],
861
			'close' => [
862
				'url' => getUrl('action', ['action' => 'moderate', 'area' => $context['admin_area'], 'close' => (int) !$context['report'] ['closed'], 'rid' => $context['report'] ['id'], '{session_data}']),
863
				'text' => $context['report'] ['closed'] ? 'mc_reportedp_open' : 'mc_reportedp_close',
864
				'icon' => $context['report'] ['closed'] ? 'sign-in' : 'close',
865
			],
866
		];
867
868
		$context['sub_template'] = 'viewmodreport';
869
	}
870
871
	/**
872
	 * Change moderation preferences.
873
	 */
874
	public function action_moderationSettings(): void
875
	{
876
		global $context, $txt;
877
878
		// Some useful context stuff.
879
		theme()->getTemplates()->load('ModerationCenter');
880
		$context['page_title'] = $txt['mc_settings'];
881
		$context['sub_template'] = 'moderation_settings';
882
		$context[$context['moderation_menu_name']]['object']->prepareTabData([
883
			'title' => $txt['mc_prefs_title'],
884
			'description' => $txt['mc_prefs_desc']
885
		]);
886
887
		// What blocks can this user see?
888
		$context['homepage_blocks'] = [
889
			'n' => $txt['mc_prefs_latest_news'],
890
			'p' => $txt['mc_notes'],
891
		];
892
893
		if ($context['can_moderate_groups'])
894
		{
895
			$context['homepage_blocks']['g'] = $txt['mc_group_requests'];
896
		}
897
898
		if ($context['can_moderate_boards'])
899
		{
900
			$context['homepage_blocks']['r'] = $txt['mc_reported_posts'];
901
			$context['homepage_blocks']['w'] = $txt['mc_watched_users'];
902
			$context['homepage_blocks']['a'] = $txt['mc_required'];
903
		}
904
905
		// Does the user have any settings yet?
906
		if (empty(User::$settings['mod_prefs']))
907
		{
908
			$mod_blocks = 'np' . ($context['can_moderate_boards'] ? 'wra' : '') . ($context['can_moderate_groups'] ? 'g' : '');
909
			$pref_binary = 5;
910
			$show_reports = 1;
911
		}
912
		else
913
		{
914
			[$show_reports, $mod_blocks, $pref_binary] = explode('|', User::$settings['mod_prefs']);
0 ignored issues
show
Bug introduced by
It seems like ElkArte\User::settings['mod_prefs'] can also be of type null; however, parameter $string of explode() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

914
			[$show_reports, $mod_blocks, $pref_binary] = explode('|', /** @scrutinizer ignore-type */ User::$settings['mod_prefs']);
Loading history...
915
		}
916
917
		// Are we saving?
918
		if ($this->_req->hasPost('save'))
919
		{
920
			checkSession();
921
			validateToken('mod-set');
922
923
			/* Current format of mod_prefs is:
924
				x|ABCD|yyy
925
926
				WHERE:
927
					x = Show report count on forum header.
928
					ABCD = Block indexes to show on moderation main page.
929
					yyy = Integer with the following bit status:
930
						- yyy & 1 = Always notify on reports.
931
						- yyy & 2 = Notify on reports for moderators only.
932
						- yyy & 4 = Notify about posts awaiting approval.
933
			*/
934
935
			// Do blocks first!
936
			$mod_blocks = '';
937
			if (!empty($this->_req->post->mod_homepage))
938
			{
939
				foreach ($this->_req->post->mod_homepage as $k => $v)
940
				{
941
					// Make sure they can add this...
942
					if (isset($context['homepage_blocks'][$k]))
943
					{
944
						$mod_blocks .= $k;
945
					}
946
				}
947
			}
948
949
			// Now check other options!
950
			$pref_binary = 0;
951
952
			if ($context['can_moderate_approvals'] && !empty($this->_req->post->mod_notify_approval))
953
			{
954
				$pref_binary |= 4;
955
			}
956
957
			if ($context['can_moderate_boards'])
958
			{
959
				if (!empty($this->_req->post->mod_notify_report))
960
				{
961
					$pref_binary |= ($this->_req->post->mod_notify_report == 2 ? 1 : 2);
962
				}
963
964
				$show_reports = empty($this->_req->post->mod_show_reports) ? 0 : 1;
965
			}
966
967
			// Put it all together.
968
			$mod_prefs = $show_reports . '|' . $mod_blocks . '|' . $pref_binary;
969
			require_once(SUBSDIR . '/Members.subs.php');
970
			updateMemberData($this->user->id, ['mod_prefs' => $mod_prefs]);
971
		}
972
973
		// What blocks does the user currently have selected?
974
		$context['mod_settings'] = [
975
			'show_reports' => $show_reports,
976
			'notify_report' => $pref_binary & 2 ? 1 : ($pref_binary & 1 ? 2 : 0),
977
			'notify_approval' => $pref_binary & 4,
978
			'user_blocks' => str_split($mod_blocks),
979
		];
980
981
		createToken('mod-set');
982
	}
983
984
	/**
985
	 * View watched users and their posts
986
	 */
987
	public function action_viewWatchedUsers(): void
988
	{
989
		global $modSettings, $context, $txt;
990
991
		// Some important context!
992
		$context['page_title'] = $txt['mc_watched_users_title'];
993
		$context['view_posts'] = $this->_req->getQuery('sa', 'trim|strval', '') === 'post';
994
		$context['start'] = $this->_req->getQuery('start', 'intval', 0);
995
996
		theme()->getTemplates()->load('ModerationCenter');
997
998
		// Get some key settings!
999
		$modSettings['warning_watch'] = empty($modSettings['warning_watch']) ? 1 : $modSettings['warning_watch'];
1000
1001
		// Put some pretty tabs on cause we're gonna be doing hot stuff here...
1002
		$context[$context['moderation_menu_name']]['object']->prepareTabData([
1003
			'title' => $txt['mc_watched_users_title'],
1004
			'description' => $txt['mc_watched_users_desc'],
1005
		]);
1006
1007
		// First off - are we deleting?
1008
		if ($this->_req->hasQuery('delete') || !empty($this->_req->post->delete))
1009
		{
1010
			checkSession($this->_req->hasQuery('delete') ? 'get' : 'post');
1011
1012
			// Clicked on remove or using checkboxes to multi delete
1013
			$toDelete = [];
1014
			if ($this->_req->hasQuery('delete'))
1015
			{
1016
				$toDelete[] = $this->_req->getQuery('delete', 'intval', 0);
1017
			}
1018
			else
1019
			{
1020
				$toDelete = array_map('intval', $this->_req->post->delete);
1021
			}
1022
1023
			if (!empty($toDelete))
1024
			{
1025
				$remover = new MessagesDelete($modSettings['recycle_enable'], $modSettings['recycle_board']);
1026
1027
				// If they don't have permission we'll let it error - either way no chance of a security slip here!
1028
				foreach ($toDelete as $did)
1029
				{
1030
					$remover->removeMessage($did);
1031
				}
1032
			}
1033
		}
1034
1035
		// Start preparing the list by grabbing relevant permissions.
1036
		if (!$context['view_posts'])
1037
		{
1038
			$approve_query = '';
1039
			$delete_boards = [];
1040
		}
1041
		else
1042
		{
1043
			// Still obey permissions!
1044
			$approve_boards = empty($this->user->mod_cache['ap']) ? boardsAllowedTo('approve_posts') : $this->user->mod_cache['ap'];
0 ignored issues
show
Bug Best Practice introduced by
The property mod_cache does not exist on ElkArte\Helper\ValuesContainer. Since you implemented __get, consider adding a @property annotation.
Loading history...
1045
			$delete_boards = boardsAllowedTo('delete_any');
1046
1047
			if ($approve_boards == [0])
1048
			{
1049
				$approve_query = '';
1050
			}
1051
			elseif (!empty($approve_boards))
1052
			{
1053
				$approve_query = ' AND m.id_board IN (' . implode(',', $approve_boards) . ')';
1054
			}
1055
			// Nada, zip, etc...
1056
			else
1057
			{
1058
				$approve_query = ' AND 1=0';
1059
			}
1060
		}
1061
1062
		// This is all the information required for a watched user listing.
1063
		$listOptions = [
1064
			'id' => 'watch_user_list',
1065
			'title' => $txt['mc_watched_users_title'] . ' - ' . ($context['view_posts'] ? $txt['mc_watched_users_post'] : $txt['mc_watched_users_member']),
1066
			'width' => '100%',
1067
			'items_per_page' => $modSettings['defaultMaxMessages'],
1068
			'no_items_label' => $context['view_posts'] ? $txt['mc_watched_users_no_posts'] : $txt['mc_watched_users_none'],
1069
			'base_href' => getUrl('action', ['action' => 'moderate', 'area' => 'userwatch', 'sa' => ($context['view_posts'] ? 'post' : 'member')]),
1070
			'default_sort_col' => $context['view_posts'] ? '' : 'member',
1071
			'get_items' => [
1072
				'function' => $context['view_posts']
1073
					? fn($start, $items_per_page, $sort, $approve_query, $delete_boards) => $this->list_getWatchedUserPosts($start, $items_per_page, $sort, $approve_query, $delete_boards)
1074
					: fn($start, $items_per_page, $sort) => $this->list_getWatchedUsers($start, $items_per_page, $sort),
1075
				'params' => [
1076
					$approve_query,
1077
					$delete_boards,
1078
				],
1079
			],
1080
			'get_count' => [
1081
				'function' => $context['view_posts']
1082
					? fn($approve_query) => $this->list_getWatchedUserPostsCount($approve_query)
1083
					: fn() => $this->list_getWatchedUserCount(),
1084
				'params' => [
1085
					$approve_query,
1086
				],
1087
			],
1088
			// This assumes we are viewing by user.
1089
			'columns' => [
1090
				'member' => [
1091
					'header' => [
1092
						'value' => $txt['mc_watched_users_member'],
1093
					],
1094
					'data' => [
1095
						'sprintf' => [
1096
							'format' => '<a href="' . getUrl('profile', ['action' => 'profile', 'u' => '%1$d']) . '">%2$s</a>',
1097
							'params' => [
1098
								'id' => false,
1099
								'name' => false,
1100
							],
1101
						],
1102
					],
1103
					'sort' => [
1104
						'default' => 'real_name',
1105
						'reverse' => 'real_name DESC',
1106
					],
1107
				],
1108
				'warning' => [
1109
					'header' => [
1110
						'value' => $txt['mc_watched_users_warning'],
1111
					],
1112
					'data' => [
1113
						'function' => static fn($member) => allowedTo('issue_warning') ? '<a href="' . getUrl('action', ['action' => 'profile', 'area' => 'issuewarning', 'u' => $member['id']]) . '">' . $member['warning'] . '%</a>' : $member['warning'] . '%',
1114
					],
1115
					'sort' => [
1116
						'default' => 'warning',
1117
						'reverse' => 'warning DESC',
1118
					],
1119
				],
1120
				'posts' => [
1121
					'header' => [
1122
						'value' => $txt['posts'],
1123
					],
1124
					'data' => [
1125
						'sprintf' => [
1126
							'format' => '<a href="' . getUrl('action', ['action' => 'profile', 'u' => '%1$d', 'area' => 'showposts', 'sa' => 'messages']) . '">%2$s</a>',
1127
							'params' => [
1128
								'id' => false,
1129
								'posts' => false,
1130
							],
1131
						],
1132
					],
1133
					'sort' => [
1134
						'default' => 'posts',
1135
						'reverse' => 'posts DESC',
1136
					],
1137
				],
1138
				'last_login' => [
1139
					'header' => [
1140
						'value' => $txt['mc_watched_users_last_login'],
1141
					],
1142
					'data' => [
1143
						'db' => 'last_login',
1144
					],
1145
					'sort' => [
1146
						'default' => 'last_login',
1147
						'reverse' => 'last_login DESC',
1148
					],
1149
				],
1150
				'last_post' => [
1151
					'header' => [
1152
						'value' => $txt['mc_watched_users_last_post'],
1153
					],
1154
					'data' => [
1155
						'function' => static function ($member) {
1156
							if ($member['last_post_id'])
1157
							{
1158
								return '<a href="' . getUrl('action', ['msg' => $member['last_post_id']]) . '">' . $member['last_post'] . '</a>';
1159
							}
1160
1161
							return $member['last_post'];
1162
						},
1163
					],
1164
				],
1165
			],
1166
			'form' => [
1167
				'href' => getUrl('action', ['action' => 'moderate', 'area' => 'userwatch', 'sa' => 'post']),
1168
				'include_sort' => true,
1169
				'include_start' => true,
1170
				'hidden_fields' => [
1171
					$context['session_var'] => $context['session_id'],
1172
				],
1173
			],
1174
			'additional_rows' => [
1175
				$context['view_posts'] ?
1176
					[
1177
						'position' => 'below_table_data',
1178
						'value' => '
1179
						<input type="submit" name="delete_selected" value="' . $txt['quickmod_delete_selected'] . '" class="right_submit" />',
1180
					] : [],
1181
			],
1182
		];
1183
1184
		// If this is being viewed by posts we actually change the columns to call a template each time.
1185
		if ($context['view_posts'])
1186
		{
1187
			$listOptions['columns'] = [
1188
				'posts' => [
1189
					'data' => [
1190
						'function' => static fn($post) => template_user_watch_post_callback($post),
1191
					],
1192
				],
1193
			];
1194
		}
1195
1196
		// Create the watched user list.
1197
		createList($listOptions);
1198
1199
		$context['sub_template'] = 'show_list';
1200
		$context['default_list'] = 'watch_user_list';
1201
	}
1202
1203
	/**
1204
	 * Callback for createList().
1205
	 *
1206
	 * @param int $start The item to start with (for pagination purposes)
1207
	 * @param int $items_per_page The number of items to show per page
1208
	 * @param string $sort A string indicating how to sort the results
1209
	 * @param string $approve_query
1210
	 * @param int[] $delete_boards
1211
	 *
1212
	 * @return array
1213
	 * @uses watchedUserPosts()
1214
	 *
1215
	 */
1216
	public function list_getWatchedUserPosts($start, $items_per_page, $sort, $approve_query, $delete_boards): array
1217
	{
1218
		// Watched users posts
1219
		return watchedUserPosts($start, $items_per_page, $approve_query, $delete_boards);
1220
	}
1221
1222
	/**
1223
	 * Callback for createList() used in watched users
1224
	 *
1225
	 * @param int $start The item to start with (for pagination purposes)
1226
	 * @param int $items_per_page The number of items to show per page
1227
	 * @param string $sort A string indicating how to sort the results
1228
	 *
1229
	 * @return array
1230
	 * @uses watchedUsers()
1231
	 *
1232
	 */
1233
	public function list_getWatchedUsers($start, $items_per_page, $sort): array
1234
	{
1235
		// Find all our watched users
1236
		return watchedUsers($start, $items_per_page, $sort);
1237
	}
1238
1239
	/**
1240
	 * Callback for createList().
1241
	 *
1242
	 * @param string $approve_query
1243
	 *
1244
	 * @return int
1245
	 * @uses watchedUserPostsCount()
1246
	 *
1247
	 */
1248
	public function list_getWatchedUserPostsCount($approve_query): int
1249
	{
1250
		global $modSettings;
1251
1252
		return watchedUserPostsCount($approve_query, $modSettings['warning_watch']);
1253
	}
1254
1255
	/**
1256
	 * Callback for createList() for watched users
1257
	 *
1258
	 * - returns count
1259
	 *
1260
	 * @uses watchedUserCount()
1261
	 */
1262
	public function list_getWatchedUserCount(): int
1263
	{
1264
		global $modSettings;
1265
1266
		return watchedUserCount($modSettings['warning_watch']);
1267
	}
1268
1269
	/**
1270
	 * Simply put, look at the warning log!
1271
	 */
1272
	public function action_viewWarningLog(): void
1273
	{
1274
		global $modSettings, $context, $txt;
1275
1276
		// Setup context as always.
1277
		$context['page_title'] = $txt['mc_warning_log_title'];
1278
1279
		require_once(SUBSDIR . '/Moderation.subs.php');
1280
		Txt::load('Modlog');
1281
1282
		// If we're coming in from a search, get the variables.
1283
		if (!empty($this->_req->post->params) && empty($this->_req->post->is_search))
1284
		{
1285
			$search_params = base64_decode(strtr($this->_req->post->params, [' ' => '+']));
1286
			$search_params = @json_decode($search_params);
1287
		}
1288
1289
		// This array houses all the valid search types.
1290
		$searchTypes = [
1291
			'member' => ['sql' => 'mem.real_name', 'label' => $txt['profile_warning_previous_issued']],
1292
			'recipient' => ['sql' => 'recipient_name', 'label' => $txt['mc_warnings_recipient']],
1293
		];
1294
1295
		// Setup the allowed quick search type
1296
		$reqSort = $this->_req->getQuery('sort', 'trim|strval');
1297
		$context['order'] = ($reqSort !== null && isset($searchTypes[$reqSort])) ? $reqSort : 'member';
1298
1299
		if (!isset($search_params['string']) || (!empty($this->_req->post->search) && $search_params['string'] !== $this->_req->post->search))
1300
		{
1301
			$search_params_string = empty($this->_req->post->search) ? '' : $this->_req->post->search;
1302
		}
1303
		else
1304
		{
1305
			$search_params_string = $search_params['string'];
1306
		}
1307
1308
		if (isset($this->_req->post->search_type) || empty($search_params['type']) || !isset($searchTypes[$search_params['type']]))
1309
		{
1310
			$search_params_type = isset($this->_req->post->search_type, $searchTypes[$this->_req->post->search_type]) ? $this->_req->post->search_type : (isset($searchTypes[$context['order']]) ? $context['order'] : 'member');
1311
		}
1312
		else
1313
		{
1314
			$search_params_type = $search_params['type'];
1315
		}
1316
1317
		$search_params_column = $searchTypes[$search_params_type]['sql'];
1318
		$search_params = [
1319
			'string' => $search_params_string,
1320
			'type' => $search_params_type,
1321
		];
1322
1323
		// Setup the search context.
1324
		$context['search_params'] = empty($search_params['string']) ? '' : base64_encode(json_encode($search_params));
1325
		$context['search'] = [
1326
			'string' => $search_params['string'],
1327
			'type' => $search_params['type'],
1328
			'label' => $searchTypes[$search_params_type]['label'],
1329
		];
1330
1331
		// This is all the information required for a watched user listing.
1332
		$listOptions = [
1333
			'id' => 'warning_list',
1334
			'title' => $txt['mc_warning_log_title'],
1335
			'items_per_page' => $modSettings['defaultMaxMessages'],
1336
			'no_items_label' => $txt['mc_warnings_none'],
1337
			'base_href' => getUrl('action', ['action' => 'moderate', 'area' => 'warnings', 'sa' => 'log', '{session_data}']),
1338
			'default_sort_col' => 'time',
1339
			'get_items' => [
1340
				'function' => fn($start, $items_per_page, $sort, $query_string, $query_params) => $this->list_getWarnings($start, $items_per_page, $sort, $query_string, $query_params),
1341
				'params' => [
1342
					(empty($search_params['string']) ? '' : ' INSTR({raw:sql_type}, {string:search_string})'),
1343
					['sql_type' => $search_params_column, 'search_string' => $search_params['string']],
1344
				],
1345
			],
1346
			'get_count' => [
1347
				'function' => fn($query_string, $query_params) => $this->list_getWarningCount($query_string, $query_params),
1348
				'params' => [
1349
					(empty($search_params['string']) ? '' : ' INSTR({raw:sql_type}, {string:search_string})'),
1350
					['sql_type' => $search_params_column, 'search_string' => $search_params['string']],
1351
				],
1352
			],
1353
			// This assumes we are viewing by user.
1354
			'columns' => [
1355
				'issuer' => [
1356
					'header' => [
1357
						'value' => $txt['profile_warning_previous_issued'],
1358
					],
1359
					'data' => [
1360
						'db' => 'issuer_link',
1361
					],
1362
					'sort' => [
1363
						'default' => 'member_name_col',
1364
						'reverse' => 'member_name_col DESC',
1365
					],
1366
				],
1367
				'recipient' => [
1368
					'header' => [
1369
						'value' => $txt['mc_warnings_recipient'],
1370
					],
1371
					'data' => [
1372
						'db' => 'recipient_link',
1373
					],
1374
					'sort' => [
1375
						'default' => 'recipient_name',
1376
						'reverse' => 'recipient_name DESC',
1377
					],
1378
				],
1379
				'time' => [
1380
					'header' => [
1381
						'value' => $txt['profile_warning_previous_time'],
1382
					],
1383
					'data' => [
1384
						'db' => 'time',
1385
					],
1386
					'sort' => [
1387
						'default' => 'lc.log_time DESC',
1388
						'reverse' => 'lc.log_time',
1389
					],
1390
				],
1391
				'reason' => [
1392
					'header' => [
1393
						'value' => $txt['profile_warning_previous_reason'],
1394
					],
1395
					'data' => [
1396
						'function' => static function ($warning) {
1397
							global $txt;
1398
1399
							$output = '
1400
								<div class="floatleft">
1401
									' . $warning['reason'] . '
1402
								</div>';
1403
1404
							// If a notice was sent, provide a link to it
1405
							if (!empty($warning['id_notice']))
1406
							{
1407
								$output .= '
1408
									<a href="' . getUrl('action', ['action' => 'moderate', 'area' => 'notice', 'nid' => $warning['id_notice']]) . '" onclick="window.open(this.href, \'\', \'scrollbars=yes,resizable=yes,width=480,height=320\');return false;" target="_blank" class="new_win" title="' . $txt['profile_warning_previous_notice'] . '"><i class="icon icon-small i-search" title"' . $txt['profile_warning_previous_notice'] . '"></i></a>';
1409
							}
1410
1411
							return $output;
1412
						},
1413
					],
1414
				],
1415
				'points' => [
1416
					'header' => [
1417
						'value' => $txt['profile_warning_previous_level'],
1418
					],
1419
					'data' => [
1420
						'db' => 'counter',
1421
					],
1422
				],
1423
			],
1424
			'form' => [
1425
				'href' => getUrl('action', ['action' => 'moderate', 'area' => 'warnings', 'sa' => 'log', 'sort' => $context['order']]),
1426
				'include_sort' => true,
1427
				'include_start' => true,
1428
				'hidden_fields' => [
1429
					$context['session_var'] => $context['session_id'],
1430
					'params' => $context['search_params']
1431
				],
1432
			],
1433
			'additional_rows' => [
1434
				[
1435
					'class' => 'submitbutton',
1436
					'position' => 'below_table_data',
1437
					'value' => '
1438
						' . $txt['modlog_search'] . ' (' . $txt['modlog_by'] . ': ' . $context['search']['label'] . ')
1439
						<input type="text" name="search" size="18" value="' . Util::htmlspecialchars($context['search']['string']) . '" class="input_text" />
1440
						<input type="submit" name="is_search" value="' . $txt['modlog_go'] . '" />',
1441
				],
1442
			],
1443
		];
1444
1445
		// Create the watched user list.
1446
		createList($listOptions);
1447
1448
		$context['sub_template'] = 'show_list';
1449
		$context['default_list'] = 'warning_list';
1450
	}
1451
1452
	/**
1453
	 * Callback for createList()
1454
	 *
1455
	 * - Used to get all issued warnings in the system
1456
	 *
1457
	 * @param int $start The item to start with (for pagination purposes)
1458
	 * @param int $items_per_page The number of items to show per page
1459
	 * @param string $sort A string indicating how to sort the results
1460
	 * @param string $query_string
1461
	 * @param array $query_params
1462
	 *
1463
	 * @return array
1464
	 * @uses warnings() function in moderation.subs
1465
	 *
1466
	 */
1467
	public function list_getWarnings($start, $items_per_page, $sort, $query_string, $query_params): array
1468
	{
1469
		return warnings($start, $items_per_page, $sort, $query_string, $query_params);
1470
	}
1471
1472
	/**
1473
	 * Callback for createList()
1474
	 *
1475
	 * - Get the total count of all current warnings
1476
	 *
1477
	 * @param string $query_string
1478
	 * @param array $query_params
1479
	 *
1480
	 * @return int
1481
	 * @uses warningCount() function in moderation.subs
1482
	 *
1483
	 */
1484
	public function list_getWarningCount($query_string, $query_params): int
1485
	{
1486
		return warningCount($query_string, $query_params);
1487
	}
1488
1489
	/**
1490
	 * View all the custom warning templates.
1491
	 *
1492
	 *  - Shows all the templates in the system
1493
	 *  - Provides for actions to add or delete them
1494
	 */
1495
	public function action_viewWarningTemplates(): ?bool
1496
	{
1497
		global $modSettings, $context, $txt;
1498
1499
		require_once(SUBSDIR . '/Moderation.subs.php');
1500
		// Submitting a new one?
1501
		if (isset($this->_req->post->add))
1502
		{
1503
			$this->action_modifyWarningTemplate();
1504
			return true;
1505
		}
1506
1507
		// Deleting and existing one
1508
		if (isset($this->_req->post->delete) && !empty($this->_req->post->deltpl))
1509
		{
1510
			checkSession();
1511
			validateToken('mod-wt');
1512
			removeWarningTemplate($this->_req->post->deltpl);
1513
		}
1514
1515
		// Setup context as always.
1516
		$context['page_title'] = $txt['mc_warning_templates_title'];
1517
1518
		// This is all the information required for a watched user listing.
1519
		$listOptions = [
1520
			'id' => 'warning_template_list',
1521
			'title' => $txt['mc_warning_templates_title'],
1522
			'items_per_page' => $modSettings['defaultMaxMessages'],
1523
			'no_items_label' => $txt['mc_warning_templates_none'],
1524
			'base_href' => getUrl('action', ['action' => 'moderate', 'area' => 'warnings', 'sa' => 'templates', '{session_data}']),
1525
			'default_sort_col' => 'title',
1526
			'get_items' => [
1527
				'function' => fn($start, $items_per_page, $sort, $template_type = 'warntpl') => $this->list_getWarningTemplates($start, $items_per_page, $sort, $template_type),
1528
			],
1529
			'get_count' => [
1530
				'function' => fn($template_type = 'warntpl') => $this->list_getWarningTemplateCount($template_type),
1531
			],
1532
			'columns' => [
1533
				'title' => [
1534
					'header' => [
1535
						'value' => $txt['mc_warning_templates_name'],
1536
					],
1537
					'data' => [
1538
						'sprintf' => [
1539
							'format' => '<a href="' . getUrl('action', ['action' => 'moderate', 'area' => 'warnings', 'sa' => 'templateedit', 'tid' => '%1$d']) . '">%2$s</a>',
1540
							'params' => [
1541
								'id_comment' => false,
1542
								'title' => false,
1543
								'body' => false,
1544
							],
1545
						],
1546
					],
1547
					'sort' => [
1548
						'default' => 'template_title',
1549
						'reverse' => 'template_title DESC',
1550
					],
1551
				],
1552
				'creator' => [
1553
					'header' => [
1554
						'value' => $txt['mc_warning_templates_creator'],
1555
					],
1556
					'data' => [
1557
						'db' => 'creator',
1558
					],
1559
					'sort' => [
1560
						'default' => 'creator_name',
1561
						'reverse' => 'creator_name DESC',
1562
					],
1563
				],
1564
				'time' => [
1565
					'header' => [
1566
						'value' => $txt['mc_warning_templates_time'],
1567
					],
1568
					'data' => [
1569
						'db' => 'time',
1570
					],
1571
					'sort' => [
1572
						'default' => 'lc.log_time DESC',
1573
						'reverse' => 'lc.log_time',
1574
					],
1575
				],
1576
				'delete' => [
1577
					'header' => [
1578
						'value' => '<input type="checkbox" class="input_check" onclick="invertAll(this, this.form);" />',
1579
						'style' => 'width: 4%;text-align: center;',
1580
					],
1581
					'data' => [
1582
						'function' => static fn($rowData) => '<input type="checkbox" name="deltpl[]" value="' . $rowData['id_comment'] . '" class="input_check" />',
1583
						'class' => 'centertext',
1584
					],
1585
				],
1586
			],
1587
			'form' => [
1588
				'href' => getUrl('action', ['action' => 'moderate', 'area' => 'warnings', 'sa' => 'templates']),
1589
				'token' => 'mod-wt',
1590
			],
1591
			'additional_rows' => [
1592
				[
1593
					'position' => 'below_table_data',
1594
					'value' => '
1595
						<input type="submit" name="delete" value="' . $txt['mc_warning_template_delete'] . '" onclick="return confirm(\'' . $txt['mc_warning_template_delete_confirm'] . '\');" class="right_submit" />
1596
						<input type="submit" name="add" value="' . $txt['mc_warning_template_add'] . '" class="right_submit" />',
1597
				],
1598
			],
1599
		];
1600
1601
		// Create the watched user list.
1602
		createToken('mod-wt');
1603
		createList($listOptions);
1604
1605
		$context['sub_template'] = 'show_list';
1606
		$context['default_list'] = 'warning_template_list';
1607
1608
		return null;
1609
	}
1610
1611
	/**
1612
	 * Edit a warning template.
1613
	 *
1614
	 * @uses template_warn_template()
1615
	 */
1616
	public function action_modifyWarningTemplate(): void
1617
	{
1618
		global $context, $txt;
1619
1620
		require_once(SUBSDIR . '/Moderation.subs.php');
1621
		loadJavascriptFile('admin.js', [], 'admin_scripts');
1622
1623
		$context['id_template'] = $this->_req->getQuery('tid', 'intval', 0);
1624
		$context['is_edit'] = $context['id_template'];
1625
1626
		// Standard template things.
1627
		$context['page_title'] = $context['is_edit'] ? $txt['mc_warning_template_modify'] : $txt['mc_warning_template_add'];
1628
		$context['sub_template'] = 'warn_template';
1629
		$context[$context['moderation_menu_name']]['current_subsection'] = 'templates';
1630
1631
		// Defaults.
1632
		$context['template_data'] = [
1633
			'title' => '',
1634
			'body' => $txt['mc_warning_template_body_default'],
1635
			'personal' => false,
1636
			'can_edit_personal' => true,
1637
		];
1638
1639
		// If it's an edit load it.
1640
		if ($context['is_edit'])
1641
		{
1642
			modLoadTemplate($context['id_template']);
1643
		}
1644
1645
		// Wait, we are saving?
1646
		if ($this->_req->hasPost('save'))
1647
		{
1648
			checkSession();
1649
			validateToken('mod-wt');
1650
1651
			// To check the BBC is pretty good...
1652
			require_once(SUBSDIR . '/Post.subs.php');
1653
1654
			// Bit of cleaning!
1655
			$template_body = trim($this->_req->post->template_body);
1656
			$template_title = trim($this->_req->post->template_title);
1657
1658
			// Need something in both boxes.
1659
			if (!empty($template_body) && !empty($template_title))
1660
			{
1661
				// Safety first.
1662
				$template_title = Util::htmlspecialchars($template_title);
1663
1664
				// Clean up BBC.
1665
				preparsecode($template_body);
1666
1667
				// But put line breaks back!
1668
				$template_body = strtr($template_body, ['<br />' => "\n"]);
1669
1670
				// Is this personal?
1671
				$recipient_id = empty($this->_req->post->make_personal) ? 0 : $this->user->id;
1672
1673
				// If we are this far it's save time.
1674
				if ($context['is_edit'])
1675
				{
1676
					// Simple update...
1677
					modAddUpdateTemplate($recipient_id, $template_title, $template_body, $context['id_template']);
1678
1679
					// If it wasn't visible and now is they've effectively added it.
1680
					if ($context['template_data']['personal'] && !$recipient_id)
1681
					{
1682
						logAction('add_warn_template', ['template' => $template_title]);
1683
					}
1684
					// Conversely if they made it personal it's a delete.
1685
					elseif (!$context['template_data']['personal'] && $recipient_id)
1686
					{
1687
						logAction('delete_warn_template', ['template' => $template_title]);
1688
					}
1689
					// Otherwise just an edit.
1690
					else
1691
					{
1692
						logAction('modify_warn_template', ['template' => $template_title]);
1693
					}
1694
				}
1695
				else
1696
				{
1697
					modAddUpdateTemplate($recipient_id, $template_title, $template_body, $context['id_template'], false);
1698
					logAction('add_warn_template', ['template' => $template_title]);
1699
				}
1700
1701
				// Get out of town...
1702
				redirectexit('action=moderate;area=warnings;sa=templates');
1703
			}
1704
			else
1705
			{
1706
				$context['warning_errors'] = [];
1707
				$context['template_data']['title'] = empty($template_title) ? '' : $template_title;
1708
				$context['template_data']['body'] = empty($template_body) ? $txt['mc_warning_template_body_default'] : $template_body;
1709
				$context['template_data']['personal'] = !empty($this->_req->post->make_personal);
1710
1711
				if (empty($template_title))
1712
				{
1713
					$context['warning_errors'][] = $txt['mc_warning_template_error_no_title'];
1714
				}
1715
1716
				if (empty($template_body))
1717
				{
1718
					$context['warning_errors'][] = $txt['mc_warning_template_error_no_body'];
1719
				}
1720
			}
1721
		}
1722
1723
		createToken('mod-wt');
1724
	}
1725
1726
	/**
1727
	 * Callback for createList() to get all the templates of a type from the system
1728
	 *
1729
	 * @param int $start The item to start with (for pagination purposes)
1730
	 * @param int $items_per_page The number of items to show per page
1731
	 * @param string $sort A string indicating how to sort the results
1732
	 * @param string $template_type type of template to load
1733
	 *
1734
	 * @return array
1735
	 * @uses warningTemplates()
1736
	 *
1737
	 */
1738
	public function list_getWarningTemplates($start, $items_per_page, $sort, $template_type = 'warntpl'): array
1739
	{
1740
		return warningTemplates($start, $items_per_page, $sort, $template_type);
1741
	}
1742
1743
	/**
1744
	 * Callback for createList() to get the number of templates of a type in the system
1745
	 *
1746
	 * @param string $template_type
1747
	 *
1748
	 * @return int
1749
	 * @uses warningTemplateCount()
1750
	 *
1751
	 */
1752
	public function list_getWarningTemplateCount($template_type = 'warntpl'): int
1753
	{
1754
		return warningTemplateCount($template_type);
1755
	}
1756
1757
	/**
1758
	 * Entry point for viewing warning related stuff.
1759
	 */
1760
	public function action_viewWarnings(): void
1761
	{
1762
		global $context, $txt;
1763
1764
		// Some of this stuff is overseas, so to speak.
1765
		theme()->getTemplates()->load('ModerationCenter');
1766
		Txt::load('Profile');
1767
1768
		$subActions = [
1769
			'log' => [$this, 'action_viewWarningLog'],
1770
			'templateedit' => [$this, 'action_modifyWarningTemplate', 'permission' => 'issue_warning'],
1771
			'templates' => [$this, 'action_viewWarningTemplates', 'permission' => 'issue_warning'],
1772
		];
1773
1774
		// Setup the admin tabs.
1775
		$context[$context['moderation_menu_name']]['object']->prepareTabData([
1776
			'title' => $txt['mc_warnings'],
1777
			'description' => $txt['mc_warnings_description'],
1778
		]);
1779
1780
		// Call the right function.
1781
		$action = new Action('moderation_center');
1782
		$subAction = $action->initialize($subActions, 'log');
1783
		$context['sub_action'] = $subAction;
1784
		$action->dispatch($subAction);
1785
	}
1786
1787
	/**
1788
	 * Show a list of all the group requests they can see.
1789
	 * Checks permissions for group moderation.
1790
	 */
1791
	public function block_groupRequests(): string
1792
	{
1793
		global $context;
1794
1795
		// Make sure they can even moderate someone!
1796
		if ($this->user->mod_cache['gq'] === '0=1')
0 ignored issues
show
Bug Best Practice introduced by
The property mod_cache does not exist on ElkArte\Helper\ValuesContainer. Since you implemented __get, consider adding a @property annotation.
Loading history...
1797
		{
1798
			return 'group_requests_block';
1799
		}
1800
1801
		$context['group_requests'] = groupRequests();
1802
1803
		return 'group_requests_block';
1804
	}
1805 2
1806
	/**
1807 2
	 * Just prepares the time stuff for the latest news.
1808
	 */
1809
	public function block_latestNews(): string
1810 2
	{
1811
		global $context;
1812
1813
		$context['time_format'] = urlencode($this->user->time_format);
0 ignored issues
show
Bug introduced by
It seems like $this->user->time_format can also be of type null; however, parameter $string of urlencode() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1813
		$context['time_format'] = urlencode(/** @scrutinizer ignore-type */ $this->user->time_format);
Loading history...
Bug Best Practice introduced by
The property time_format does not exist on ElkArte\Helper\ValuesContainer. Since you implemented __get, consider adding a @property annotation.
Loading history...
1814
1815 2
		// Return the template to use.
1816
		return 'latest_news';
1817 2
	}
1818
1819
	/**
1820
	 * Show a list of the most active watched users.
1821
	 */
1822
	public function block_watchedUsers(): string
1823 2
	{
1824
		global $context;
1825 2
1826
		$watched_users = basicWatchedUsers();
1827 2
1828
		$context['watched_users'] = [];
1829
		if (is_array($watched_users) || is_object($watched_users))
1830 2
		{
1831
			foreach ($watched_users as $user)
1832
			{
1833
				$context['watched_users'][] = [
1834
					'id' => $user['id_member'],
1835
					'name' => $user['real_name'],
1836 2
					'link' => '<a href="' . getUrl('profile', ['action' => 'profile', 'u' => $user['id_member']]) . '">' . $user['real_name'] . '</a>',
1837
					'href' => getUrl('profile', ['action' => 'profile', 'u' => $user['id_member']]),
1838 2
					'last_login' => empty($user['last_login']) ? '' : standardTime($user['last_login']),
1839
				];
1840 2
			}
1841
		}
1842 2
1843 2
		return 'watched_users';
1844
	}
1845
1846
	/**
1847
	 * Shows a list of items requiring moderation action
1848
	 * Includes post, topic, attachment, group, member and PBE values with links to each
1849
	 */
1850
	public function block_actionRequired(): string
1851
	{
1852
		global $context;
1853
1854
		// Get the action totals
1855
		$mod_totals = loadModeratorMenuCounts();
1856
1857 2
		// This blocks total is only these fields
1858
		$context['mc_required'] = $mod_totals['attachments'] + $mod_totals['emailmod'] + $mod_totals['topics'] + $mod_totals['posts'] + $mod_totals['memberreq'] + $mod_totals['groupreq'] + +$mod_totals['reports'];
1859
		unset($mod_totals['postmod'], $mod_totals['pt_total'], $mod_totals['mg_total'], $mod_totals['grand_total']);
1860
		$context['required'] = $mod_totals;
1861
1862
		// Links to the areas
1863
		$context['links'] = [
1864 2
			'attachments' => '?action=moderate;area=attachmod;sa=attachments',
1865
			'emailmod' => '?action=admin;area=maillist;sa=emaillist',
1866 2
			'topics' => '?action=moderate;area=postmod;sa=topics',
1867
			'posts' => '?action=moderate;area=postmod;sa=posts',
1868
			'memberreq' => '?action=admin;area=viewmembers;sa=browse;type=approve',
1869 2
			'groupreq' => '?action=moderate;area=groups;sa=requests',
1870
			'reports' => '?action=moderate;area=reports;sa=open',
1871
			'pm_reports' => '?action=moderate;area=pm_reports;sa=open',
1872 2
		];
1873 2
1874 2
		return 'action_required';
1875
	}
1876
1877 2
	/**
1878
	 * Show an area for the moderator to type into.
1879
	 */
1880
	public function block_notes(): string
1881
	{
1882
		global $context, $txt;
1883
1884
		// Are we saving a note?
1885
		if (isset($this->_req->post->makenote, $this->_req->post->new_note))
1886
		{
1887
			checkSession();
1888 2
1889
			$new_note = Util::htmlspecialchars(trim($this->_req->post->new_note));
1890
1891
			// Make sure they actually entered something.
1892
			if (!empty($new_note) && $new_note !== $txt['mc_click_add_note'])
1893
			{
1894
				// Insert it into the database then!
1895
				addModeratorNote($this->user->id, $this->user->name, $new_note);
1896
1897
				// Clear the cache.
1898
				Cache::instance()->remove('moderator_notes');
1899
				Cache::instance()->remove('moderator_notes_total');
1900
			}
1901
1902
			// Redirect otherwise people can resubmit.
1903
			redirectexit('action=moderate');
1904
		}
1905
1906
		// Bye... bye...
1907
		if ($this->_req->hasQuery('notes') && $this->_req->hasQuery('delete'))
1908
		{
1909
			checkSession('get');
1910
1911
			// Just checkin'!
1912
			$id_delete = $this->_req->getQuery('delete', 'intval', 0);
1913
			if ($id_delete <= 0)
1914
			{
1915
				redirectexit('action=moderate');
1916
			}
1917
1918
			// Lets delete it.
1919
			removeModeratorNote($id_delete);
1920
1921
			// Clear the cache.
1922
			Cache::instance()->remove('moderator_notes');
1923
			Cache::instance()->remove('moderator_notes_total');
1924
1925
			redirectexit('action=moderate');
1926
		}
1927
1928
		// How many notes in total?
1929
		$moderator_notes_total = countModeratorNotes();
1930
1931
		// Grab the current notes. We can only use the cache for the first page of notes.
1932
		$offset = ($this->_req->hasQuery('notes') && $this->_req->hasQuery('start')) ? $this->_req->getQuery('start', 'intval', 0) : 0;
1933
		$moderator_notes = moderatorNotes($offset);
1934
1935
		// Lets construct a page index.
1936
		$start = $this->_req->getQuery('start', 'intval', 0);
1937
		$context['page_index'] = constructPageIndex('{scripturl}?action=moderate;area=index;notes', $start, $moderator_notes_total, 10);
1938
		$context['start'] = $start;
1939
1940
		$bbc_parser = ParserWrapper::instance();
1941
1942
		$context['notes'] = [];
1943
		foreach ($moderator_notes as $note)
1944
		{
1945
			$context['notes'][] = [
1946
				'author' => [
1947
					'id' => $note['id_member'],
1948
					'link' => $note['id_member'] ? ('<a href="' . getUrl('profile', ['action' => 'profile', 'u' => $note['id_member']]) . '" title="' . $txt['on'] . ' ' . strip_tags(standardTime($note['log_time'])) . '">' . $note['member_name'] . '</a>') : $note['member_name'],
1949
				],
1950
				'time' => standardTime($note['log_time']),
1951
				'html_time' => htmlTime($note['log_time']),
1952
				'timestamp' => forum_time(true, $note['log_time']),
1953
				'text' => $bbc_parser->parseReport($note['body']),
1954
				'delete_href' => getUrl('action', ['action' => 'moderate', 'area' => 'index', 'notes' => '', 'delete=' . $note['id_note'], '{session_data}']),
1955
			];
1956
		}
1957
1958
		return 'notes';
1959
	}
1960
1961
	/**
1962
	 * Show a list of the most recent reported posts.
1963
	 */
1964
	public function block_reportedPosts(): string
1965
	{
1966
		global $context;
1967
1968
		if ($this->user->mod_cache['bq'] === '0=1')
0 ignored issues
show
Bug Best Practice introduced by
The property mod_cache does not exist on ElkArte\Helper\ValuesContainer. Since you implemented __get, consider adding a @property annotation.
Loading history...
1969
		{
1970
			return 'reported_posts_block';
1971
		}
1972
1973 2
		$context['reported_posts'] = [];
1974
1975 2
		$reported_posts = reportedPosts();
1976
		foreach ($reported_posts as $row)
1977 2
		{
1978
			$context['reported_posts'][] = [
1979
				'id' => $row['id_report'],
1980
				'topic_href' => getUrl('topic', ['topic' => $row['id_topic'], 'msg' => $row['id_msg'], 'start' => $row['id_msg'], 'hash' => '#msg' . $row['id_msg']]),
1981
				'report_href' => getUrl('action', ['action' => 'moderate', 'area' => $context['admin_area'], 'report' => $row['id_report']]),
1982 2
				'author' => [
1983
					'id' => $row['id_author'],
1984 2
					'name' => $row['author_name'],
1985 2
					'link' => $row['id_author'] ? '<a href="' . getUrl('profile', ['action' => 'profile', 'u' => $row['id_author']]) . '">' . $row['author_name'] . '</a>' : $row['author_name'],
1986
					'href' => getUrl('profile', ['action' => 'profile', 'u' => $row['id_author']]),
1987 2
				],
1988 2
				'comments' => [],
1989 2
				'subject' => $row['subject'],
1990 2
				'num_reports' => $row['num_reports'],
1991 2
			];
1992
		}
1993 2
1994 2
		return 'reported_posts_block';
1995 2
	}
1996
}
1997