Passed
Push — development ( b93807...dda237 )
by Emanuele
01:10 queued 23s
created

ProfileInfo::_load_buddies()   B

Complexity

Conditions 7
Paths 4

Size

Total Lines 20
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 56

Importance

Changes 0
Metric Value
cc 7
eloc 11
nc 4
nop 0
dl 0
loc 20
ccs 0
cts 9
cp 0
crap 56
rs 8.8333
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * Handles the retrieving and display of a users posts, attachments, stats, permissions
5
 * warnings and the like
6
 *
7
 * @package   ElkArte Forum
8
 * @copyright ElkArte Forum contributors
9
 * @license   BSD http://opensource.org/licenses/BSD-3-Clause (see accompanying LICENSE.txt file)
10
 *
11
 * This file contains code covered by:
12
 * copyright: 2011 Simple Machines (http://www.simplemachines.org)
13
 *
14
 * @version 2.0 dev
15
 *
16
 */
17
18
namespace ElkArte\Controller;
19
20
use BBC\ParserWrapper;
21
use ElkArte\AbstractController;
22
use ElkArte\Action;
23
use ElkArte\Exceptions\Exception;
24
use ElkArte\MembersList;
25
use ElkArte\MessagesDelete;
26
use ElkArte\Themes\ThemeLoader;
27
use ElkArte\Util;
28
29
/**
30
 * Access all profile summary areas for a user including overall summary,
31
 * post listing, attachment listing, user statistics user permissions, user warnings
32
 */
33
class ProfileInfo extends AbstractController
34
{
35
	/**
36
	 * Member id for the profile being worked with
37
	 *
38
	 * @var int
39
	 */
40
	private $_memID = 0;
41
42
	/**
43
	 * The \ElkArte\Member object is stored here to avoid some global
44
	 *
45
	 * @var \ElkArte\Member
46
	 */
47
	private $_profile = null;
48
49
	/**
50
	 * Holds the current summary tabs to load
51
	 *
52
	 * @var array
53
	 */
54
	private $_summary_areas;
55
56
	/**
57
	 * Called before all other methods when coming from the dispatcher or
58
	 * action class.
59
	 *
60
	 * - If you initiate the class outside of those methods, call this method.
61
	 * or setup the class yourself or fall awaits.
62 2
	 */
63
	public function pre_dispatch()
64 2
	{
65
		global $context;
66 2
67
		require_once(SUBSDIR . '/Profile.subs.php');
68 2
69 2
		$this->_memID = currentMemberID();
70
		$this->_profile = MembersList::get($this->_memID);
71 2
72
		if (!isset($context['user']['is_owner']))
73
		{
74
			$context['user']['is_owner'] = (int) $this->_memID === (int) $this->user->id;
75
		}
76 2
77 2
		// Attempt to load the member's profile data.
78
		if ($this->_profile->isEmpty())
79
		{
80
			throw new Exception('not_a_user', false);
81
		}
82
		$this->_profile->loadContext();
83
84
		ThemeLoader::loadLanguageFile('Profile');
85
	}
86
87
	/**
88
	 * Intended as entry point which delegates to methods in this class...
89
	 *
90
	 * - But here, today, for now, the methods are mainly called from other places
91
	 * like menu picks and the like.
92
	 */
93
	public function action_index()
94
	{
95
		global $context;
96
97
		// What do we do, do you even know what you do?
98
		$subActions = array(
99
			'buddies' => array($this, 'action_profile_buddies'),
100
			'recent' => array($this, 'action_profile_recent'),
101
			'summary' => array('controller' => '\\ElkArte\\Controller\\Profile', 'function' => 'action_index'),
102
		);
103
104
		// Action control
105
		$action = new Action('profile_info');
106
107
		// By default we want the summary
108
		$subAction = $action->initialize($subActions, 'summary');
109
110
		// Final bits
111
		$context['sub_action'] = $subAction;
112
113
		// Call the right function for this sub-action.
114 2
		$action->dispatch($subAction);
115
	}
116 2
117
	/**
118
	 * View the user profile summary.
119 2
	 *
120
	 * @uses ProfileInfo template
121
	 */
122
	public function action_summary()
123 2
	{
124
		global $context, $modSettings;
125 2
126 2
		theme()->getTemplates()->load('ProfileInfo');
127
		ThemeLoader::loadLanguageFile('Profile');
128
129 2
		// Set a canonical URL for this page.
130
		$context['canonical_url'] = getUrl('action', ['action' => 'profile', 'u' => $this->_memID]);
131
132 2
		// Are there things we don't show?
133
		$context['disabled_fields'] = isset($modSettings['disabled_profile_fields']) ? array_flip(explode(',', $modSettings['disabled_profile_fields'])) : array();
134
135 2
		// Menu tab
136
		$context[$context['profile_menu_name']]['tab_data'] = array();
137
138 2
		// Profile summary tabs, like Summary, Recent, Buddies
139
		$this->_register_summarytabs();
140
141 2
		// Load in everything we know about the user to preload the summary tab
142 2
		$this->_define_user_values();
143
		$this->_load_summary();
144
145 2
		// To finish this off, custom profile fields.
146
		loadCustomFields($this->_memID);
147
148 2
		// To make tabs work, we need jQueryUI
149 2
		$modSettings['jquery_include_ui'] = true;
150 2
		theme()->addInlineJavascript('start_tabs();', true);
151 2
		loadCSSFile('jquery.ui.tabs.css');
152
	}
153
154
	/**
155
	 * Prepares the tabs for the profile summary page
156
	 *
157
	 * What it does:
158
	 *
159
	 * - Tab information for use in the summary page
160
	 * - Each tab template defines a div, the value of which are the template(s) to load in that div
161
	 * - array(array(1, 2), array(3, 4)) <div>template 1, template 2</div><div>template 3 template 4</div>
162
	 * - Templates are named template_profile_block_YOURNAME
163
	 * - Tabs with href defined will not preload/create any page divs but instead be loaded via ajax
164 2
	 */
165
	private function _register_summarytabs()
166 2
	{
167
		global $txt, $context, $modSettings;
168 2
169 1
		$context['summarytabs'] = array(
170 2
			'summary' => array(
171
				'name' => $txt['summary'],
172
				'templates' => array(
173
					array('summary', 'user_info'),
174
					array('contact', 'other_info'),
175
					array('user_customprofileinfo', 'moderation'),
176
				),
177
				'active' => true,
178
			),
179 2
			'recent' => array(
180
				'name' => $txt['profile_recent_activity'],
181
				'templates' => array('posts', 'topics', 'attachments'),
182 2
				'active' => true,
183
				'href' => getUrl('action', ['action' => 'profileInfo', 'sa' => 'recent;xml', 'u' => $this->_memID, '{session_data}']),
184
			),
185 2
			'buddies' => array(
186
				'name' => $txt['buddies'],
187 2
				'templates' => array('buddies'),
188 2
				'active' => !empty($modSettings['enable_buddylist']) && $context['user']['is_owner'],
189
				'href' => getUrl('action', ['action' => 'profileInfo', 'sa' => 'buddies;xml', 'u' => $this->_memID, '{session_data}']),
190
			)
191
		);
192
193 2
		// Let addons add or remove to the tabs array
194
		call_integration_hook('integrate_profile_summary', array($this->_memID));
195
196 2
		// Go forward with whats left after integration adds or removes
197 2
		$summary_areas = '';
198
		foreach ($context['summarytabs'] as $id => $tab)
199
		{
200 2
			// If the tab is active we add it
201
			if ($tab['active'] !== true)
202
			{
203
				unset($context['summarytabs'][$id]);
204
			}
205
			else
206
			{
207 2
				// All the active templates, used to prevent processing data we don't need
208
				foreach ($tab['templates'] as $template)
209 2
				{
210
					$summary_areas .= is_array($template) ? implode(',', $template) : ',' . $template;
211
				}
212
			}
213
		}
214 2
215 2
		$this->_summary_areas = explode(',', $summary_areas);
216
	}
217
218
	/**
219
	 * Sets in to context what we know about a given user
220
	 *
221
	 * - Defines various user permissions for profile views
222 2
	 */
223
	private function _define_user_values()
224 2
	{
225
		global $context, $modSettings, $txt;
226
227
		// Set up the context stuff and load the user.
228 2
		$context += array(
229 2
			'page_title' => sprintf($txt['profile_of_username'], $this->_profile['name']),
0 ignored issues
show
Bug introduced by
It seems like $this->_profile['name'] can also be of type array<mixed,mixed>; however, parameter $values of sprintf() does only seem to accept double|integer|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

229
			'page_title' => sprintf($txt['profile_of_username'], /** @scrutinizer ignore-type */ $this->_profile['name']),
Loading history...
230 2
			'can_send_pm' => allowedTo('pm_send'),
231 2
			'can_send_email' => allowedTo('send_email_to_members'),
232 2
			'can_have_buddy' => allowedTo('profile_identity_own') && !empty($modSettings['enable_buddylist']),
233 2
			'can_issue_warning' => featureEnabled('w') && allowedTo('issue_warning') && !empty($modSettings['warning_enable']),
234
			'can_view_warning' => featureEnabled('w') && (allowedTo('issue_warning') && !$context['user']['is_owner']) || (!empty($modSettings['warning_show']) && ($modSettings['warning_show'] > 1 || $context['user']['is_owner']))
235
		);
236
237 2
		// @critical: potential problem here
238 2
		$context['member'] = $this->_profile;
239 2
		$context['member']->loadContext();
240
		$context['member']['id'] = $this->_memID;
241
242 2
		// Is the signature even enabled on this forum?
243 2
		$context['signature_enabled'] = substr($modSettings['signature_settings'], 0, 1) == 1;
244
	}
245
246
	/**
247
	 * Loads the information needed to create the profile summary view
248 2
	 */
249
	private function _load_summary()
250
	{
251 2
		// Load all areas of interest in to context for template use
252 2
		$this->_determine_warning_level();
253 2
		$this->_determine_posts_per_day();
254 2
		$this->_determine_age_birth();
255 2
		$this->_determine_member_ip();
256 2
		$this->_determine_member_action();
257 2
		$this->_determine_member_activation();
258 2
		$this->_determine_member_bans();
259
	}
260
261
	/**
262
	 * If they have been disciplined, show the warning level for those that can see it.
263 2
	 */
264
	private function _determine_warning_level()
265 2
	{
266
		global $modSettings, $context, $txt;
267
268 2
		// See if they have broken any warning levels...
269
		if (!empty($modSettings['warning_mute']) && $modSettings['warning_mute'] <= $context['member']['warning'])
270
		{
271
			$context['warning_status'] = $txt['profile_warning_is_muted'];
272 2
		}
273
		elseif (!empty($modSettings['warning_moderate']) && $modSettings['warning_moderate'] <= $context['member']['warning'])
274
		{
275
			$context['warning_status'] = $txt['profile_warning_is_moderation'];
276 2
		}
277
		elseif (!empty($modSettings['warning_watch']) && $modSettings['warning_watch'] <= $context['member']['warning'])
278
		{
279
			$context['warning_status'] = $txt['profile_warning_is_watch'];
280 2
		}
281
	}
282
283
	/**
284
	 * Gives their spam level as a posts per day kind of statistic
285 2
	 */
286
	private function _determine_posts_per_day()
287 2
	{
288
		global $context, $txt;
289
290 2
		// They haven't even been registered for a full day!?
291 2
		$days_registered = (int) ((time() - $this->_profile['registered_raw']) / (3600 * 24));
292
		if (empty($this->_profile['date_registered']) || $days_registered < 1)
293 2
		{
294
			$context['member']['posts_per_day'] = $txt['not_applicable'];
295
		}
296
		else
297
		{
298
			$context['member']['posts_per_day'] = comma_format($context['member']['real_posts'] / $days_registered, 3);
299 2
		}
300
	}
301
302
	/**
303
	 * Show age and birthday data if applicable.
304 2
	 */
305
	private function _determine_age_birth()
306 2
	{
307
		global $context, $txt;
308
309 2
		// Set the age...
310
		if (empty($context['member']['birth_date']))
311
		{
312
			$context['member']['age'] = $txt['not_applicable'];
313
			$context['member']['today_is_birthday'] = false;
314
		}
315
		else
316 2
		{
317 2
			list ($birth_year, $birth_month, $birth_day) = sscanf($context['member']['birth_date'], '%d-%d-%d');
318 2
			$datearray = getdate(forum_time());
319 2
			$context['member']['age'] = $birth_year <= 4 ? $txt['not_applicable'] : $datearray['year'] - $birth_year - (($datearray['mon'] > $birth_month || ($datearray['mon'] === $birth_month && $datearray['mday'] >= $birth_day)) ? 0 : 1);
320
			$context['member']['today_is_birthday'] = $datearray['mon'] === $birth_month && $datearray['mday'] === $birth_day;
321
322 2
		}
323
	}
324
325
	/**
326
	 * Show IP and hostname information for the users current IP of record.
327 2
	 */
328
	private function _determine_member_ip()
329 2
	{
330
		global $context, $modSettings;
331 2
332
		if (allowedTo('moderate_forum'))
333
		{
334 2
			// Make sure it's a valid ip address; otherwise, don't bother...
335
			if (filter_var($this->_profile['ip'], FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) === false && empty($modSettings['disableHostnameLookup']))
336
			{
337
				$context['member']['hostname'] = host_from_ip($this->_profile['ip']);
0 ignored issues
show
Bug introduced by
It seems like $this->_profile['ip'] can also be of type array<mixed,mixed>; however, parameter $ip of host_from_ip() 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

337
				$context['member']['hostname'] = host_from_ip(/** @scrutinizer ignore-type */ $this->_profile['ip']);
Loading history...
338
			}
339
			else
340 2
			{
341
				$context['member']['hostname'] = '';
342
			}
343 2
344
			$context['can_see_ip'] = true;
345
		}
346
		else
347
		{
348
			$context['can_see_ip'] = false;
349 2
		}
350
	}
351
352
	/**
353
	 * Determines what action user is "doing" at the time of the summary view
354 2
	 */
355
	private function _determine_member_action()
356 2
	{
357
		global $context, $modSettings;
358 2
359
		if (!empty($modSettings['who_enabled']) && $context['member']['online']['is_online'])
360
		{
361
			include_once(SUBSDIR . '/Who.subs.php');
362
			$action = determineActions($this->_profile['url']);
363
			ThemeLoader::loadLanguageFile('index');
364
365
			if ($action !== false)
0 ignored issues
show
introduced by
The condition $action !== false is always true.
Loading history...
366
			{
367
				$context['member']['action'] = $action;
368
			}
369 2
		}
370
	}
371
372
	/**
373
	 * Checks if hte member is activated
374
	 *
375
	 * - Creates a link if the viewing member can activate a user
376 2
	 */
377
	private function _determine_member_activation()
378 2
	{
379
		global $context, $txt;
380
381 2
		// If the user is awaiting activation, and the viewer has permission - setup some activation context messages.
382
		if ($context['member']['is_activated'] % 10 !== 1 && allowedTo('moderate_forum'))
383
		{
384
			$context['activate_type'] = $context['member']['is_activated'];
385
386
			// What should the link text be?
387
			$context['activate_link_text'] = in_array($context['member']['is_activated'], array(3, 4, 5, 13, 14, 15))
388
				? $txt['account_approve']
389
				: $txt['account_activate'];
390
391
			// Should we show a custom message?
392
			$context['activate_message'] = isset($txt['account_activate_method_' . $context['member']['is_activated'] % 10])
393
				? $txt['account_activate_method_' . $context['member']['is_activated'] % 10]
394
				: $txt['account_not_activated'];
395
396
			$context['activate_url'] = getUrl('action', ['action' => 'profile;save', 'area' => 'activateaccount', 'u' => $this->_memID, '{session_data}', $context['profile-aa' . $this->_memID . '_token_var'] => $context['profile-aa' . $this->_memID . '_token']]);
397 2
		}
398
	}
399
400
	/**
401
	 * Checks if a member has been banned
402 2
	 */
403
	private function _determine_member_bans()
404 2
	{
405
		global $context;
406
407 2
		// How about, are they banned?
408
		if (allowedTo('moderate_forum'))
409 2
		{
410
			require_once(SUBSDIR . '/Bans.subs.php');
411 2
412 2
			$hostname = !empty($context['member']['hostname']) ? $context['member']['hostname'] : '';
413 2
			$email = !empty($context['member']['email']) ? $context['member']['email'] : '';
414
			$context['member']['bans'] = BanCheckUser($this->_memID, $hostname, $email);
415
416 2
			// Can they edit the ban?
417
			$context['can_edit_ban'] = allowedTo('manage_bans');
418 2
		}
419
	}
420
421
	/**
422
	 * Show all posts by the current user.
423
	 *
424
	 * @todo This function needs to be split up properly.
425
	 */
426
	public function action_showPosts()
427
	{
428
		global $txt, $modSettings, $context, $board;
429
430
		// Some initial context.
431
		$context['start'] = $this->_req->getQuery('start', 'intval', 0);
432
		$context['current_member'] = $this->_memID;
433
434
		// What are we viewing
435
		$action = $this->_req->getQuery('sa', 'trim', '');
436
		$action_title = array('messages' => 'Messages', 'attach' => 'Attachments', 'topics' => 'Topics', 'unwatchedtopics' => 'Unwatched');
437
		$action_title = isset($action_title[$action]) ? $action_title[$action] : 'Posts';
438
439
		theme()->getTemplates()->load('ProfileInfo');
440
441
		// Create the tabs for the template.
442
		$context[$context['profile_menu_name']]['tab_data'] = array(
443
			'title' => $txt['show' . $action_title],
444
			'description' => sprintf($txt['showGeneric_help'], $txt['show' . $action_title]),
445
			'class' => 'profile',
446
			'tabs' => array(
447
				'messages' => array(),
448
				'topics' => array(),
449
				'unwatchedtopics' => array(),
450
				'attach' => array(),
451
			),
452
		);
453
454
		// Set the page title
455
		$context['page_title'] = $txt['showPosts'] . ' - ' . $this->_profile['real_name'];
0 ignored issues
show
Bug introduced by
Are you sure $this->_profile['real_name'] of type array<mixed,mixed>|mixed can be used in concatenation? ( Ignorable by Annotation )

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

455
		$context['page_title'] = $txt['showPosts'] . ' - ' . /** @scrutinizer ignore-type */ $this->_profile['real_name'];
Loading history...
456
457
		// Is the load average too high to allow searching just now?
458
		if (!empty($modSettings['loadavg_show_posts']) && $modSettings['current_load'] >= $modSettings['loadavg_show_posts'])
459
		{
460
			throw new Exception('loadavg_show_posts_disabled', false);
461
		}
462
463
		// If we're specifically dealing with attachments use that function!
464
		if ($action === 'attach')
465
		{
466
			return $this->action_showAttachments();
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->action_showAttachments() targeting ElkArte\Controller\Profi...ction_showAttachments() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
467
		}
468
469
		// Instead, if we're dealing with unwatched topics (and the feature is enabled) use that other function.
470
		if ($action === 'unwatchedtopics' && $modSettings['enable_unwatch'])
471
		{
472
			return $this->action_showUnwatched();
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->action_showUnwatched() targeting ElkArte\Controller\Profi...:action_showUnwatched() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
473
		}
474
475
		// Are we just viewing topics?
476
		$context['is_topics'] = $action === 'topics';
477
478
		// If just deleting a message, do it and then redirect back.
479
		if (isset($this->_req->query->delete) && !$context['is_topics'])
480
		{
481
			checkSession('get');
482
483
			// We can be lazy, since removeMessage() will check the permissions for us.
484
			$remover = new MessagesDelete($modSettings['recycle_enable'], $modSettings['recycle_board']);
485
			$remover->removeMessage((int) $this->_req->query->delete);
486
487
			// Back to... where we are now ;).
488
			redirectexit('action=profile;u=' . $this->_memID . ';area=showposts;start=' . $context['start']);
489
		}
490
491
		$msgCount = $context['is_topics'] ? count_user_topics($this->_memID, $board) : count_user_posts($this->_memID, $board);
492
493
		list ($min_msg_member, $max_msg_member) = findMinMaxUserMessage($this->_memID, $board);
494
		$range_limit = '';
495
		$maxIndex = (int) $modSettings['defaultMaxMessages'];
496
497
		// Make sure the starting place makes sense and construct our friend the page index.
498
		$context['page_index'] = constructPageIndex('{scripturl}?action=profile;u=' . $this->_memID . ';area=showposts' . ($context['is_topics'] ? ';sa=topics' : ';sa=messages') . (!empty($board) ? ';board=' . $board : ''), $context['start'], $msgCount, $maxIndex);
499
		$context['current_page'] = $context['start'] / $maxIndex;
500
501
		// Reverse the query if we're past 50% of the pages for better performance.
502
		$start = $context['start'];
503
		$reverse = $this->_req->getQuery('start', 'intval', 0) > $msgCount / 2;
504
		if ($reverse)
505
		{
506
			$maxIndex = $msgCount < $context['start'] + $modSettings['defaultMaxMessages'] + 1 && $msgCount > $context['start'] ? $msgCount - $context['start'] : (int) $modSettings['defaultMaxMessages'];
507
			$start = $msgCount < $context['start'] + $modSettings['defaultMaxMessages'] + 1 || $msgCount < $context['start'] + $modSettings['defaultMaxMessages'] ? 0 : $msgCount - $context['start'] - $modSettings['defaultMaxMessages'];
508
		}
509
510
		// Guess the range of messages to be shown to help minimize what the query needs to do
511
		if ($msgCount > 1000)
512
		{
513
			$margin = floor(($max_msg_member - $min_msg_member) * (($start + $modSettings['defaultMaxMessages']) / $msgCount) + .1 * ($max_msg_member - $min_msg_member));
514
515
			// Make a bigger margin for topics only.
516
			if ($context['is_topics'])
517
			{
518
				$margin *= 5;
519
				$range_limit = $reverse ? 't.id_first_msg < ' . ($min_msg_member + $margin) : 't.id_first_msg > ' . ($max_msg_member - $margin);
520
			}
521
			else
522
			{
523
				$range_limit = $reverse ? 'm.id_msg < ' . ($min_msg_member + $margin) : 'm.id_msg > ' . ($max_msg_member - $margin);
524
			}
525
		}
526
527
		// Find this user's posts or topics started
528
		if ($context['is_topics'])
529
		{
530
			$rows = load_user_topics($this->_memID, $start, $maxIndex, $range_limit, $reverse, $board);
531
		}
532
		else
533
		{
534
			$rows = load_user_posts($this->_memID, $start, $maxIndex, $range_limit, $reverse, $board);
535
		}
536
537
		// Start counting at the number of the first message displayed.
538
		$counter = $reverse ? $context['start'] + $maxIndex + 1 : $context['start'];
539
		$context['posts'] = array();
540
		$board_ids = array('own' => array(), 'any' => array());
541
		$bbc_parser = ParserWrapper::instance();
542
		foreach ($rows as $row)
543
		{
544
			// Censor....
545
			$row['body'] = censor($row['body']);
546
			$row['subject'] = censor($row['subject']);
547
548
			// Do the code.
549
			$row['body'] = $bbc_parser->parseMessage($row['body'], $row['smileys_enabled']);
550
551
			// And the array...
552
			$context['posts'][$counter += $reverse ? -1 : 1] = array(
553
				'body' => $row['body'],
554
				'counter' => $counter,
555
				'alternate' => $counter % 2,
556
				'category' => array(
557
					'name' => $row['cname'],
558
					'id' => $row['id_cat']
559
				),
560
				'board' => array(
561
					'name' => $row['bname'],
562
					'id' => $row['id_board'],
563
					'link' => '<a href="' . getUrl('board', ['board' => $row['id_board'], 'start' => 0, 'name' => $row['bname']]) .'">' . $row['bname'] . '</a>',
564
				),
565
				'topic' => array(
566
					'id' => $row['id_topic'],
567
					'link' => '<a href="' . getUrl('topic', ['topic' => $row['id_topic'], 'msg' => $row['id_msg'], 'subject' => $row['subject'], 'start' => '0', 'hash' => '#msg' . $row['id_msg']]) . '">' . $row['subject'] . '</a>',
568
				),
569
				'subject' => $row['subject'],
570
				'start' => 'msg' . $row['id_msg'],
571
				'time' => standardTime($row['poster_time']),
572
				'html_time' => htmlTime($row['poster_time']),
573
				'timestamp' => forum_time(true, $row['poster_time']),
574
				'id' => $row['id_msg'],
575
				'tests' => array(
576
					'can_reply' => false,
577
					'can_mark_notify' => false,
578
					'can_delete' => false,
579
				),
580
				'delete_possible' => ($row['id_first_msg'] != $row['id_msg'] || $row['id_last_msg'] == $row['id_msg']) && (empty($modSettings['edit_disable_time']) || $row['poster_time'] + $modSettings['edit_disable_time'] * 60 >= time()),
581
				'approved' => $row['approved'],
582
583
				'buttons' => array(
584
					// How about... even... remove it entirely?!
585
					'remove' => array(
586
						'href' => getUrl('action', ['action' => 'deletemsg', 'msg' => $row['id_msg'], 'topic' => $row['id_topic'] . ';profile', 'u' => $context['member']['id'], 'start' => $context['start'], '{session_data}']),
587
						'text' => $txt['remove'],
588
						'test' => 'can_delete',
589
						'custom' => 'onclick="return confirm(' . JavaScriptEscape($txt['remove_message'] . '?') . ');"',
590
					),
591
					// Can we request notification of topics?
592
					'notify' => array(
593
						'href' => getUrl('action', ['action' => 'notify', 'topic' => $row['id_topic'], 'msg' => $row['id_msg']]),
594
						'text' => $txt['notify'],
595
						'test' => 'can_mark_notify',
596
					),
597
					// If they *can* reply?
598
					'reply' => array(
599
						'href' => getUrl('action', ['action' => 'post', 'topic' => $row['id_topic'], 'msg' => $row['id_msg']]),
600
						'text' => $txt['reply'],
601
						'test' => 'can_reply',
602
					),
603
					// If they *can* quote?
604
					'quote' => array(
605
						'href' => getUrl('action', ['action' => 'post', 'topic' => $row['id_topic'], 'msg' => $row['id_msg'], 'quote' => $row['id_msg']]),
606
						'text' => $txt['quote'],
607
						'test' => 'can_quote',
608
					),
609
				)
610
			);
611
612
			if ($this->user->id == $row['id_member_started'])
613
			{
614
				$board_ids['own'][$row['id_board']][] = $counter;
615
			}
616
			$board_ids['any'][$row['id_board']][] = $counter;
617
		}
618
619
		// All posts were retrieved in reverse order, get them right again.
620
		if ($reverse)
621
		{
622
			$context['posts'] = array_reverse($context['posts'], true);
623
		}
624
625
		// These are all the permissions that are different from board to board..
626
		if ($context['is_topics'])
627
		{
628
			$permissions = array(
629
				'own' => array(
630
					'post_reply_own' => 'can_reply',
631
				),
632
				'any' => array(
633
					'post_reply_any' => 'can_reply',
634
					'mark_any_notify' => 'can_mark_notify',
635
				)
636
			);
637
		}
638
		else
639
		{
640
			$permissions = array(
641
				'own' => array(
642
					'post_reply_own' => 'can_reply',
643
					'delete_own' => 'can_delete',
644
				),
645
				'any' => array(
646
					'post_reply_any' => 'can_reply',
647
					'mark_any_notify' => 'can_mark_notify',
648
					'delete_any' => 'can_delete',
649
				)
650
			);
651
		}
652
653
		// For every permission in the own/any lists...
654
		foreach ($permissions as $type => $list)
655
		{
656
			foreach ($list as $permission => $allowed)
657
			{
658
				// Get the boards they can do this on...
659
				$boards = boardsAllowedTo($permission);
660
661
				// Hmm, they can do it on all boards, can they?
662
				if (!empty($boards) && $boards[0] == 0)
663
				{
664
					$boards = array_keys($board_ids[$type]);
665
				}
666
667
				// Now go through each board they can do the permission on.
668
				foreach ($boards as $board_id)
669
				{
670
					// There aren't any posts displayed from this board.
671
					if (!isset($board_ids[$type][$board_id]))
672
					{
673
						continue;
674
					}
675
676
					// Set the permission to true ;).
677
					foreach ($board_ids[$type][$board_id] as $counter)
678
					{
679
						$context['posts'][$counter]['tests'][$allowed] = true;
680
					}
681
				}
682
			}
683
		}
684
685
		// Clean up after posts that cannot be deleted and quoted.
686
		$quote_enabled = empty($modSettings['disabledBBC']) || !in_array('quote', explode(',', $modSettings['disabledBBC']));
687
		foreach ($context['posts'] as $counter => $dummy)
688
		{
689
			$context['posts'][$counter]['tests']['can_delete'] &= $context['posts'][$counter]['delete_possible'];
690
			$context['posts'][$counter]['tests']['can_quote'] = $context['posts'][$counter]['tests']['can_reply'] && $quote_enabled;
691
		}
692
	}
693
694
	/**
695
	 * Show all the attachments of a user.
696
	 */
697
	public function action_showAttachments()
698
	{
699
		global $txt, $modSettings, $context;
700
701
		// OBEY permissions!
702
		$boardsAllowed = boardsAllowedTo('view_attachments');
703
704
		// Make sure we can't actually see anything...
705
		if (empty($boardsAllowed))
706
		{
707
			$boardsAllowed = array(-1);
708
		}
709
710
		// This is all the information required to list attachments.
711
		$listOptions = array(
712
			'id' => 'profile_attachments',
713
			'title' => $txt['showAttachments'] . ($context['user']['is_owner'] ? '' : ' - ' . $context['member']['name']),
714
			'items_per_page' => $modSettings['defaultMaxMessages'],
715
			'no_items_label' => $txt['show_attachments_none'],
716
			'base_href' => getUrl('action', ['action' => 'profile', 'area' => 'showposts', 'sa' => 'attach', 'u' => $this->_memID]),
717
			'default_sort_col' => 'filename',
718
			'get_items' => array(
719
				'function' => function ($start, $items_per_page, $sort, $boardsAllowed) {
720
					return $this->list_getAttachments($start, $items_per_page, $sort, $boardsAllowed);
721
				},
722
				'params' => array(
723
					$boardsAllowed,
724
				),
725
			),
726
			'get_count' => array(
727
				'function' => function ($boardsAllowed) {
728
					return $this->list_getNumAttachments($boardsAllowed);
729
				},
730
				'params' => array(
731
					$boardsAllowed,
732
				),
733
			),
734
			'data_check' => array(
735
				'class' => function ($data) {
736
					return $data['approved'] ? '' : 'approvebg';
737
				},
738
			),
739
			'columns' => array(
740
				'filename' => array(
741
					'header' => array(
742
						'value' => $txt['show_attach_filename'],
743
						'class' => 'lefttext grid25',
744
					),
745
					'data' => array(
746
						'db' => 'filename',
747
					),
748
					'sort' => array(
749
						'default' => 'a.filename',
750
						'reverse' => 'a.filename DESC',
751
					),
752
				),
753
				'thumb' => array(
754
					'header' => array(
755
						'value' => '',
756
					),
757
					'data' => array(
758
						'function' => function ($rowData) {
759
							if ($rowData['is_image'] && !empty($rowData['id_thumb']))
760
							{
761
								return '<img src="' . getUrl('action', ['action' => 'dlattach', 'attach' => $rowData['id_thumb'] . ';image']) .'" />';
762
							}
763
764
							return '<img src="' . getUrl('action', ['action' => 'dlattach', 'attach' => $rowData['id'] . ';thumb']) . '" />';
765
						},
766
						'class' => 'centertext recent_attachments',
767
					),
768
					'sort' => array(
769
						'default' => 'a.filename',
770
						'reverse' => 'a.filename DESC',
771
					),
772
				),
773
				'downloads' => array(
774
					'header' => array(
775
						'value' => $txt['show_attach_downloads'],
776
					),
777
					'data' => array(
778
						'db' => 'downloads',
779
						'comma_format' => true,
780
					),
781
					'sort' => array(
782
						'default' => 'a.downloads',
783
						'reverse' => 'a.downloads DESC',
784
					),
785
				),
786
				'subject' => array(
787
					'header' => array(
788
						'value' => $txt['message'],
789
						'class' => 'lefttext grid30',
790
					),
791
					'data' => array(
792
						'db' => 'subject',
793
					),
794
					'sort' => array(
795
						'default' => 'm.subject',
796
						'reverse' => 'm.subject DESC',
797
					),
798
				),
799
				'posted' => array(
800
					'header' => array(
801
						'value' => $txt['show_attach_posted'],
802
						'class' => 'lefttext',
803
					),
804
					'data' => array(
805
						'db' => 'posted',
806
						'timeformat' => true,
807
					),
808
					'sort' => array(
809
						'default' => 'm.poster_time',
810
						'reverse' => 'm.poster_time DESC',
811
					),
812
				),
813
			),
814
		);
815
816
		// Create the request list.
817
		createList($listOptions);
818
819
		$context['sub_template'] = 'show_list';
820
		$context['default_list'] = 'profile_attachments';
821
	}
822
823
	/**
824
	 * Get a list of attachments for this user
825
	 * Callback for createList()
826
	 *
827
	 * @param int $start The item to start with (for pagination purposes)
828
	 * @param int $items_per_page The number of items to show per page
829
	 * @param string $sort A string indicating how to sort the results
830
	 * @param int[] $boardsAllowed
831
	 *
832
	 * @return array
833
	 */
834
	public function list_getAttachments($start, $items_per_page, $sort, $boardsAllowed)
835
	{
836
		// @todo tweak this method to use $context, etc,
837
		// then call subs function with params set.
838
		return profileLoadAttachments($start, $items_per_page, $sort, $boardsAllowed, $this->_memID);
839
	}
840
841
	/**
842
	 * Callback for createList()
843
	 *
844
	 * @param int[] $boardsAllowed
845
	 *
846
	 * @return int
847
	 */
848
	public function list_getNumAttachments($boardsAllowed)
849
	{
850
		// @todo tweak this method to use $context, etc,
851
		// then call subs function with params set.
852
		return getNumAttachments($boardsAllowed, $this->_memID);
853
	}
854
855
	/**
856
	 * Show all the unwatched topics.
857
	 */
858
	public function action_showUnwatched()
859
	{
860
		global $txt, $modSettings, $context;
861
862
		// Only the owner can see the list (if the function is enabled of course)
863
		if ($this->user->id != $this->_memID || !$modSettings['enable_unwatch'])
864
		{
865
			return;
866
		}
867
868
		// And here they are: the topics you don't like
869
		$listOptions = array(
870
			'id' => 'unwatched_topics',
871
			'title' => $txt['showUnwatched'],
872
			'items_per_page' => $modSettings['defaultMaxMessages'],
873
			'no_items_label' => $txt['unwatched_topics_none'],
874
			'base_href' => getUrl('action', ['action' => 'profile', 'area' => 'showposts', 'sa' => 'unwatchedtopics', 'u' => $this->_memID]),
875
			'default_sort_col' => 'started_on',
876
			'get_items' => array(
877
				'function' => function ($start, $items_per_page, $sort) {
878
					return $this->list_getUnwatched($start, $items_per_page, $sort);
879
				},
880
			),
881
			'get_count' => array(
882
				'function' => function () {
883
					return $this->list_getNumUnwatched();
884
				},
885
			),
886
			'columns' => array(
887
				'subject' => array(
888
					'header' => array(
889
						'value' => $txt['subject'],
890
						'class' => 'lefttext',
891
						'style' => 'width: 30%;',
892
					),
893
					'data' => array(
894
						'sprintf' => array(
895
							'format' => '<a href="' . getUrl('action', ['topic' => '%1$d.0']) . '">%2$s</a>',
896
							'params' => array(
897
								'id_topic' => false,
898
								'subject' => false,
899
							),
900
						),
901
					),
902
					'sort' => array(
903
						'default' => 'm.subject',
904
						'reverse' => 'm.subject DESC',
905
					),
906
				),
907
				'started_by' => array(
908
					'header' => array(
909
						'value' => $txt['started_by'],
910
						'style' => 'width: 15%;',
911
					),
912
					'data' => array(
913
						'db' => 'started_by',
914
					),
915
					'sort' => array(
916
						'default' => 'mem.real_name',
917
						'reverse' => 'mem.real_name DESC',
918
					),
919
				),
920
				'started_on' => array(
921
					'header' => array(
922
						'value' => $txt['on'],
923
						'class' => 'lefttext',
924
						'style' => 'width: 20%;',
925
					),
926
					'data' => array(
927
						'db' => 'started_on',
928
						'timeformat' => true,
929
					),
930
					'sort' => array(
931
						'default' => 'm.poster_time',
932
						'reverse' => 'm.poster_time DESC',
933
					),
934
				),
935
				'last_post_by' => array(
936
					'header' => array(
937
						'value' => $txt['last_post'],
938
						'style' => 'width: 15%;',
939
					),
940
					'data' => array(
941
						'db' => 'last_post_by',
942
					),
943
					'sort' => array(
944
						'default' => 'mem.real_name',
945
						'reverse' => 'mem.real_name DESC',
946
					),
947
				),
948
				'last_post_on' => array(
949
					'header' => array(
950
						'value' => $txt['on'],
951
						'class' => 'lefttext',
952
						'style' => 'width: 20%;',
953
					),
954
					'data' => array(
955
						'db' => 'last_post_on',
956
						'timeformat' => true,
957
					),
958
					'sort' => array(
959
						'default' => 'm.poster_time',
960
						'reverse' => 'm.poster_time DESC',
961
					),
962
				),
963
			),
964
		);
965
966
		// Create the request list.
967
		createList($listOptions);
968
969
		$context['sub_template'] = 'show_list';
970
		$context['default_list'] = 'unwatched_topics';
971
	}
972
973
	/**
974
	 * Get the relevant topics in the unwatched list
975
	 * Callback for createList()
976
	 *
977
	 * @param int $start The item to start with (for pagination purposes)
978
	 * @param int $items_per_page The number of items to show per page
979
	 * @param string $sort A string indicating how to sort the results
980
	 *
981
	 * @return array
982
	 */
983
	public function list_getUnwatched($start, $items_per_page, $sort)
984
	{
985
		return getUnwatchedBy($start, $items_per_page, $sort, $this->_memID);
986
	}
987
988
	/**
989
	 * Count the number of topics in the unwatched list
990
	 * Callback for createList()
991
	 */
992
	public function list_getNumUnwatched()
993
	{
994
		return getNumUnwatchedBy($this->_memID);
995
	}
996
997
	/**
998
	 * Gets the user stats for display.
999
	 */
1000
	public function action_statPanel()
1001
	{
1002
		global $txt, $context, $modSettings;
1003
1004
		require_once(SUBSDIR . '/Stats.subs.php');
1005
1006
		$context['page_title'] = $txt['statPanel_showStats'] . ' ' . $this->_profile['real_name'];
0 ignored issues
show
Bug introduced by
Are you sure $this->_profile['real_name'] of type array<mixed,mixed>|mixed can be used in concatenation? ( Ignorable by Annotation )

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

1006
		$context['page_title'] = $txt['statPanel_showStats'] . ' ' . /** @scrutinizer ignore-type */ $this->_profile['real_name'];
Loading history...
1007
1008
		// Is the load average too high to allow searching just now?
1009
		if (!empty($modSettings['loadavg_userstats']) && $modSettings['current_load'] >= $modSettings['loadavg_userstats'])
1010
		{
1011
			throw new Exception('loadavg_userstats_disabled', false);
1012
		}
1013
1014
		theme()->getTemplates()->load('ProfileInfo');
1015
1016
		// General user statistics.
1017
		$timeDays = floor($this->_profile['total_time_logged_in'] / 86400);
1018
		$timeHours = floor(($this->_profile['total_time_logged_in'] % 86400) / 3600);
1019
		$context['time_logged_in'] = ($timeDays > 0 ? $timeDays . $txt['totalTimeLogged2'] : '') . ($timeHours > 0 ? $timeHours . $txt['totalTimeLogged3'] : '') . floor(($this->_profile['total_time_logged_in'] % 3600) / 60) . $txt['totalTimeLogged4'];
1020
		$context['num_posts'] = comma_format($this->_profile['posts']);
0 ignored issues
show
Bug introduced by
It seems like $this->_profile['posts'] can also be of type array<mixed,mixed>; however, parameter $number of comma_format() does only seem to accept double, 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

1020
		$context['num_posts'] = comma_format(/** @scrutinizer ignore-type */ $this->_profile['posts']);
Loading history...
1021
1022
		// Menu tab
1023
		$context[$context['profile_menu_name']]['tab_data'] = array(
1024
			'title' => $txt['statPanel_generalStats'] . ' - ' . $context['member']['name'],
1025
			'class' => 'stats_info'
1026
		);
1027
1028
		// Number of topics started.
1029
		$context['num_topics'] = UserStatsTopicsStarted($this->_memID);
1030
1031
		// Number of polls started.
1032
		$context['num_polls'] = UserStatsPollsStarted($this->_memID);
1033
1034
		// Number of polls voted in.
1035
		$context['num_votes'] = UserStatsPollsVoted($this->_memID);
1036
1037
		// Format the numbers...
1038
		$context['num_topics'] = comma_format($context['num_topics']);
1039
		$context['num_polls'] = comma_format($context['num_polls']);
1040
		$context['num_votes'] = comma_format($context['num_votes']);
1041
1042
		// Grab the boards this member posted in most often.
1043
		$context['popular_boards'] = UserStatsMostPostedBoard($this->_memID);
1044
1045
		// Now get the 10 boards this user has most often participated in.
1046
		$context['board_activity'] = UserStatsMostActiveBoard($this->_memID);
1047
1048
		// Posting activity by time.
1049
		$context['posts_by_time'] = UserStatsPostingTime($this->_memID);
1050
1051
		// Custom stats (just add a template_layer to add it to the template!)
1052
		call_integration_hook('integrate_profile_stats', array($this->_memID));
1053
	}
1054
1055
	/**
1056
	 * Show permissions for a user.
1057
	 */
1058
	public function action_showPermissions()
1059
	{
1060
		global $txt, $board, $context;
1061
1062
		// Verify if the user has sufficient permissions.
1063
		isAllowedTo('manage_permissions');
1064
1065
		ThemeLoader::loadLanguageFile('ManagePermissions');
1066
		ThemeLoader::loadLanguageFile('Admin');
1067
		theme()->getTemplates()->load('ManageMembers');
1068
		theme()->getTemplates()->load('ProfileInfo');
1069
1070
		// Load all the permission profiles.
1071
		require_once(SUBSDIR . '/ManagePermissions.subs.php');
1072
		loadPermissionProfiles();
1073
1074
		$context['member']['id'] = $this->_memID;
1075
		$context['member']['name'] = $this->_profile['real_name'];
1076
1077
		$context['page_title'] = $txt['showPermissions'];
1078
		$board = empty($board) ? 0 : (int) $board;
1079
		$context['board'] = $board;
1080
1081
		$curGroups = empty($this->_profile['additional_groups']) ? array() : explode(',', $this->_profile['additional_groups']);
0 ignored issues
show
Bug introduced by
It seems like $this->_profile['additional_groups'] can also be of type array<mixed,mixed>; 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

1081
		$curGroups = empty($this->_profile['additional_groups']) ? array() : explode(',', /** @scrutinizer ignore-type */ $this->_profile['additional_groups']);
Loading history...
1082
1083
		$curGroups[] = $this->_profile['id_group'];
1084
		$curGroups[] = $this->_profile['id_post_group'];
1085
1086
		// Load a list of boards for the jump box - except the defaults.
1087
		require_once(SUBSDIR . '/Boards.subs.php');
1088
		$board_list = getBoardList(array('moderator' => $this->_memID), true);
1089
1090
		$context['boards'] = array();
1091
		$context['no_access_boards'] = array();
1092
		foreach ($board_list as $row)
1093
		{
1094
			if (count(array_intersect($curGroups, explode(',', $row['member_groups']))) === 0 && !$row['is_mod'])
1095
			{
1096
				$context['no_access_boards'][] = array(
1097
					'id' => $row['id_board'],
1098
					'name' => $row['board_name'],
1099
					'is_last' => false,
1100
				);
1101
			}
1102
			elseif ($row['id_profile'] != 1 || $row['is_mod'])
1103
			{
1104
				$context['boards'][$row['id_board']] = array(
1105
					'id' => $row['id_board'],
1106
					'name' => $row['board_name'],
1107
					'url' => getUrl('board', ['board' => $row['id_board'], 'start' => 0, 'name' => $row['board_name']]),
1108
					'selected' => $board == $row['id_board'],
1109
					'profile' => $row['id_profile'],
1110
					'profile_name' => $context['profiles'][$row['id_profile']]['name'],
1111
				);
1112
			}
1113
		}
1114
1115
		if (!empty($context['no_access_boards']))
1116
		{
1117
			$context['no_access_boards'][count($context['no_access_boards']) - 1]['is_last'] = true;
1118
		}
1119
1120
		$context['member']['permissions'] = array(
1121
			'general' => array(),
1122
			'board' => array()
1123
		);
1124
1125
		// If you're an admin we know you can do everything, we might as well leave.
1126
		$context['member']['has_all_permissions'] = in_array(1, $curGroups);
1127
		if ($context['member']['has_all_permissions'])
1128
		{
1129
			return;
1130
		}
1131
1132
		// Get all general permissions for the groups this member is in
1133
		$context['member']['permissions']['general'] = getMemberGeneralPermissions($curGroups);
0 ignored issues
show
Bug introduced by
It seems like $curGroups can also be of type string[]; however, parameter $curGroups of getMemberGeneralPermissions() does only seem to accept integer[], 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

1133
		$context['member']['permissions']['general'] = getMemberGeneralPermissions(/** @scrutinizer ignore-type */ $curGroups);
Loading history...
1134
1135
		// Get all board permissions for this member
1136
		$context['member']['permissions']['board'] = getMemberBoardPermissions($this->_memID, $curGroups, $board);
0 ignored issues
show
Bug introduced by
It seems like $curGroups can also be of type string[]; however, parameter $curGroups of getMemberBoardPermissions() does only seem to accept integer[], 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

1136
		$context['member']['permissions']['board'] = getMemberBoardPermissions($this->_memID, /** @scrutinizer ignore-type */ $curGroups, $board);
Loading history...
1137
	}
1138
1139
	/**
1140
	 * View a members warnings.
1141
	 */
1142
	public function action_viewWarning()
1143
	{
1144
		global $modSettings, $context, $txt;
1145
1146
		// Firstly, can we actually even be here?
1147
		if (!allowedTo('issue_warning') && (empty($modSettings['warning_show']) || ($modSettings['warning_show'] == 1 && !$context['user']['is_owner'])))
1148
		{
1149
			throw new Exception('no_access', false);
1150
		}
1151
1152
		theme()->getTemplates()->load('ProfileInfo');
1153
1154
		// We need this because of template_load_warning_variables
1155
		theme()->getTemplates()->load('Profile');
1156
1157
		// Make sure things which are disabled stay disabled.
1158
		$modSettings['warning_watch'] = !empty($modSettings['warning_watch']) ? $modSettings['warning_watch'] : 110;
1159
		$modSettings['warning_moderate'] = !empty($modSettings['warning_moderate']) && !empty($modSettings['postmod_active']) ? $modSettings['warning_moderate'] : 110;
1160
		$modSettings['warning_mute'] = !empty($modSettings['warning_mute']) ? $modSettings['warning_mute'] : 110;
1161
1162
		// Let's use a generic list to get all the current warnings
1163
		// and use the issue warnings grab-a-granny thing.
1164
		$listOptions = array(
1165
			'id' => 'view_warnings',
1166
			'title' => $txt['profile_viewwarning_previous_warnings'],
1167
			'items_per_page' => $modSettings['defaultMaxMessages'],
1168
			'no_items_label' => $txt['profile_viewwarning_no_warnings'],
1169
			'base_href' => getUrl('action', ['action' => 'profile', 'area' => 'viewwarning', 'sa' => 'user', 'u' => $this->_memID]),
1170
			'default_sort_col' => 'log_time',
1171
			'get_items' => array(
1172
				'function' => 'list_getUserWarnings',
1173
				'params' => array(
1174
					$this->_memID,
1175
				),
1176
			),
1177
			'get_count' => array(
1178
				'function' => 'list_getUserWarningCount',
1179
				'params' => array(
1180
					$this->_memID,
1181
				),
1182
			),
1183
			'columns' => array(
1184
				'log_time' => array(
1185
					'header' => array(
1186
						'value' => $txt['profile_warning_previous_time'],
1187
					),
1188
					'data' => array(
1189
						'db' => 'time',
1190
					),
1191
					'sort' => array(
1192
						'default' => 'lc.log_time DESC',
1193
						'reverse' => 'lc.log_time',
1194
					),
1195
				),
1196
				'reason' => array(
1197
					'header' => array(
1198
						'value' => $txt['profile_warning_previous_reason'],
1199
						'style' => 'width: 50%;',
1200
					),
1201
					'data' => array(
1202
						'db' => 'reason',
1203
					),
1204
				),
1205
				'level' => array(
1206
					'header' => array(
1207
						'value' => $txt['profile_warning_previous_level'],
1208
					),
1209
					'data' => array(
1210
						'db' => 'counter',
1211
					),
1212
					'sort' => array(
1213
						'default' => 'lc.counter DESC',
1214
						'reverse' => 'lc.counter',
1215
					),
1216
				),
1217
			),
1218
			'additional_rows' => array(
1219
				array(
1220
					'position' => 'after_title',
1221
					'value' => $txt['profile_viewwarning_desc'],
1222
					'class' => 'smalltext',
1223
					'style' => 'padding: 2ex;',
1224
				),
1225
			),
1226
		);
1227
1228
		// Create the list for viewing.
1229
		createList($listOptions);
1230
1231
		// Create some common text bits for the template.
1232
		$context['level_effects'] = array(
1233
			0 => '',
1234
			$modSettings['warning_watch'] => $txt['profile_warning_effect_own_watched'],
1235
			$modSettings['warning_moderate'] => $txt['profile_warning_effect_own_moderated'],
1236
			$modSettings['warning_mute'] => $txt['profile_warning_effect_own_muted'],
1237
		);
1238
		$context['current_level'] = 0;
1239
		$context['sub_template'] = 'viewWarning';
1240
1241
		foreach ($context['level_effects'] as $limit => $dummy)
1242
		{
1243
			if ($context['member']['warning'] >= $limit)
1244
			{
1245
				$context['current_level'] = $limit;
1246
			}
1247
		}
1248
	}
1249
1250
	/**
1251
	 * Collect and output data related to the profile buddy tab
1252
	 *
1253
	 * - Ajax call from profile info buddy tab
1254
	 */
1255
	public function action_profile_buddies()
1256
	{
1257
		global $context;
1258
1259
		checkSession('get');
1260
1261
		// Need the ProfileInfo and Index (for helper functions) templates
1262
		theme()->getTemplates()->load('ProfileInfo');
1263
1264
		// Prep for a buddy check
1265
		$this->_register_summarytabs();
1266
		$this->_define_user_values();
1267
1268
		// This is returned only for ajax request to a jqueryUI tab
1269
		theme()->getLayers()->removeAll();
1270
1271
		// Some buddies for you
1272
		if (in_array('buddies', $this->_summary_areas))
1273
		{
1274
			$this->_load_buddies();
1275
			$context['sub_template'] = 'profile_block_buddies';
1276
		}
1277
	}
1278
1279
	/**
1280
	 * Load the buddies tab with their buddies, real or imaginary
1281
	 */
1282
	private function _load_buddies()
1283
	{
1284
		global $context, $modSettings;
1285
1286
		// Would you be mine? Could you be mine? Be my buddy :D
1287
		$context['buddies'] = [];
1288
		if (!empty($modSettings['enable_buddylist'])
1289
			&& $context['user']['is_owner']
1290
			&& !empty($this->user->buddies)
1291
			&& in_array('buddies', $this->_summary_areas))
1292
		{
1293
			if (MembersList::load($this->user->buddies, false, 'profile'))
1294
			{
1295
				// Get the info for this buddy
1296
				foreach ($this->user->buddies as $buddy)
1297
				{
1298
					$member = MembersList::get($buddy);
1299
					$member->loadContext(true);
1300
1301
					$context['buddies'][$buddy] = $member;
1302
				}
1303
			}
1304
		}
1305
	}
1306
1307
	/**
1308
	 * Collect and output data related to the profile recent tab
1309
	 *
1310
	 * - Ajax call from profile info recent tab
1311
	 */
1312
	public function action_profile_recent()
1313
	{
1314
		global $context;
1315
1316
		checkSession('get');
1317
1318
		// Prep for recent activity
1319
		$this->_register_summarytabs();
1320
		$this->_define_user_values();
1321
1322
		// The block templates are here
1323
		theme()->getTemplates()->load('ProfileInfo');
1324
		$context['sub_template'] = 'profile_blocks';
1325
		$context['profile_blocks'] = array();
1326
1327
		// Flush everything since we intend to return the information to an ajax handler
1328
		theme()->getLayers()->removeAll();
1329
1330
		// So, just what have you been up to?
1331
		if (in_array('posts', $this->_summary_areas))
1332
		{
1333
			$this->_load_recent_posts();
1334
			$context['profile_blocks'][] = 'template_profile_block_posts';
1335
		}
1336
1337
		if (in_array('topics', $this->_summary_areas))
1338
		{
1339
			$this->_load_recent_topics();
1340
			$context['profile_blocks'][] = 'template_profile_block_topics';
1341
		}
1342
1343
		if (in_array('attachments', $this->_summary_areas))
1344
		{
1345
			$this->_load_recent_attachments();
1346
			$context['profile_blocks'][] = 'template_profile_block_attachments';
1347
		}
1348
	}
1349
1350
	/**
1351
	 * Load a members most recent posts
1352
	 */
1353
	private function _load_recent_posts()
1354
	{
1355
		global $context, $modSettings;
1356
1357
		// How about their most recent posts?
1358
		if (in_array('posts', $this->_summary_areas))
1359
		{
1360
			// Is the load average too high just now, then let them know
1361
			if (!empty($modSettings['loadavg_show_posts']) && $modSettings['current_load'] >= $modSettings['loadavg_show_posts'])
1362
			{
1363
				$context['loadaverage'] = true;
1364
			}
1365
			else
1366
			{
1367
				// Set up to get the last 10 posts of this member
1368
				$msgCount = count_user_posts($this->_memID);
1369
				$range_limit = '';
1370
				$maxIndex = 10;
1371
				$start = $this->_req->getQuery('start', 'intval', 0);
1372
1373
				// If they are a frequent poster, we guess the range to help minimize what the query work
1374
				if ($msgCount > 1000)
1375
				{
1376
					list ($min_msg_member, $max_msg_member) = findMinMaxUserMessage($this->_memID);
1377
					$margin = floor(($max_msg_member - $min_msg_member) * (($start + $modSettings['defaultMaxMessages']) / $msgCount) + .1 * ($max_msg_member - $min_msg_member));
1378
					$range_limit = 'm.id_msg > ' . ($max_msg_member - $margin);
1379
				}
1380
1381
				// Find this user's most recent posts
1382
				$rows = load_user_posts($this->_memID, 0, $maxIndex, $range_limit);
1383
				$bbc_parser = ParserWrapper::instance();
1384
				$context['posts'] = array();
1385
				foreach ($rows as $row)
1386
				{
1387
					// Censor....
1388
					$row['body'] = censor($row['body']);
1389
					$row['subject'] = censor($row['subject']);
1390
1391
					// Do the code.
1392
					$row['body'] = $bbc_parser->parseMessage($row['body'], $row['smileys_enabled']);
1393
					$preview = strip_tags(strtr($row['body'], array('<br />' => '&#10;')));
1394
					$preview = Util::shorten_text($preview, !empty($modSettings['ssi_preview_length']) ? $modSettings['ssi_preview_length'] : 128);
1395
					$short_subject = Util::shorten_text($row['subject'], !empty($modSettings['ssi_subject_length']) ? $modSettings['ssi_subject_length'] : 24);
1396
1397
					// And the array...
1398
					$context['posts'][] = array(
1399
						'body' => $preview,
1400
						'board' => array(
1401
							'name' => $row['bname'],
1402
							'link' => '<a href="' . getUrl('board', ['board' => $row['id_board'], 'start' => 0, 'name' => $row['bname']]). '">' . $row['bname'] . '</a>'
1403
						),
1404
						'subject' => $row['subject'],
1405
						'short_subject' => $short_subject,
1406
						'time' => standardTime($row['poster_time']),
1407
						'html_time' => htmlTime($row['poster_time']),
1408
						'timestamp' => forum_time(true, $row['poster_time']),
1409
						'link' => '<a href="' . getUrl('topic', ['topic' => $row['id_topic'], 'msg' => $row['id_msg'], 'subject' => $row['subject'], 'hash' => '#msg' . $row['id_msg']]) . '" rel="nofollow">' . $short_subject . '</a>',
1410
					);
1411
				}
1412
			}
1413
		}
1414
	}
1415
1416
	/**
1417
	 * Load a users recent topics
1418
	 */
1419
	private function _load_recent_topics()
1420
	{
1421
		global $context, $modSettings;
1422
1423
		// How about the most recent topics that they started?
1424
		if (in_array('topics', $this->_summary_areas))
1425
		{
1426
			// Is the load average still too high?
1427
			if (!empty($modSettings['loadavg_show_posts']) && $modSettings['current_load'] >= $modSettings['loadavg_show_posts'])
1428
			{
1429
				$context['loadaverage'] = true;
1430
			}
1431
			else
1432
			{
1433
				// Set up to get the last 10 topics of this member
1434
				$topicCount = count_user_topics($this->_memID);
1435
				$range_limit = '';
1436
				$maxIndex = 10;
1437
				$start = $this->_req->getQuery('start', 'intval', 0);
1438
1439
				// If they are a frequent topic starter we guess the range to help the query
1440
				if ($topicCount > 1000)
1441
				{
1442
					list ($min_topic_member, $max_topic_member) = findMinMaxUserTopic($this->_memID);
1443
					$margin = floor(($max_topic_member - $min_topic_member) * (($start + $modSettings['defaultMaxMessages']) / $topicCount) + .1 * ($max_topic_member - $min_topic_member));
1444
					$margin *= 5;
1445
					$range_limit = 't.id_first_msg > ' . ($max_topic_member - $margin);
1446
				}
1447
1448
				// Find this user's most recent topics
1449
				$rows = load_user_topics($this->_memID, 0, $maxIndex, $range_limit);
1450
				$context['topics'] = array();
1451
				$bbc_parser = ParserWrapper::instance();
1452
1453
				foreach ($rows as $row)
1454
				{
1455
					// Censor....
1456
					$row['body'] = censor($row['body']);
1457
					$row['subject'] = censor($row['subject']);
1458
1459
					// Do the code.
1460
					$row['body'] = $bbc_parser->parseMessage($row['body'], $row['smileys_enabled']);
1461
					$preview = strip_tags(strtr($row['body'], array('<br />' => '&#10;')));
1462
					$preview = Util::shorten_text($preview, !empty($modSettings['ssi_preview_length']) ? $modSettings['ssi_preview_length'] : 128);
1463
					$short_subject = Util::shorten_text($row['subject'], !empty($modSettings['ssi_subject_length']) ? $modSettings['ssi_subject_length'] : 24);
1464
1465
					// And the array...
1466
					$context['topics'][] = array(
1467
						'board' => array(
1468
							'name' => $row['bname'],
1469
							'link' => '<a href="' . getUrl('board', ['board' => $row['id_board'], 'start' => 0, 'name' => $row['bname']]) . '</a>'
1470
						),
1471
						'subject' => $row['subject'],
1472
						'short_subject' => $short_subject,
1473
						'body' => $preview,
1474
						'time' => standardTime($row['poster_time']),
1475
						'html_time' => htmlTime($row['poster_time']),
1476
						'timestamp' => forum_time(true, $row['poster_time']),
1477
						'link' => '<a href="' . getUrl('topic', ['topic' => $row['id_topic'], 'msg' => $row['id_msg'], 'subject' => $row['subject'], 'hash' => '#msg' . $row['id_msg']]) . '" rel="nofollow">' . $short_subject . '</a>',
1478
					);
1479
				}
1480
			}
1481
		}
1482
	}
1483
1484
	/**
1485
	 * If they have made recent attachments, lets get a list of them to display
1486
	 */
1487
	private function _load_recent_attachments()
1488
	{
1489
		global $context, $modSettings, $settings;
1490
1491
		$context['thumbs'] = array();
1492
1493
		// Load up the most recent attachments for this user for use in profile views etc.
1494
		if (!empty($modSettings['attachmentEnable'])
1495
			&& !empty($settings['attachments_on_summary'])
1496
			&& in_array('attachments', $this->_summary_areas))
1497
		{
1498
			$boardsAllowed = boardsAllowedTo('view_attachments');
1499
1500
			if (empty($boardsAllowed))
1501
			{
1502
				$boardsAllowed = array(-1);
1503
			}
1504
1505
			$attachments = $this->list_getAttachments(0, $settings['attachments_on_summary'], 'm.poster_time DESC', $boardsAllowed);
1506
1507
			// Some generic images for mime types
1508
			$mime_images_url = $settings['default_images_url'] . '/mime_images/';
1509
			$mime_path = $settings['default_theme_dir'] . '/images/mime_images/';
1510
1511
			// Load them in to $context for use in the template
1512
			foreach ($attachments as $i => $attachment)
1513
			{
1514
				$context['thumbs'][$i] = array(
1515
					'url' => getUrl('action', ['action' => 'dlattach', 'topic' => $attachment['topic'] . '.0', 'attach' => $attachment['id']]),
1516
					'img' => '',
1517
					'filename' => $attachment['filename'],
1518
					'downloads' => $attachment['downloads'],
1519
					'subject' => $attachment['subject'],
1520
					'id' => $attachment['id'],
1521
				);
1522
1523
				// Show a thumbnail image as well?
1524
				if ($attachment['is_image'] && !empty($modSettings['attachmentShowImages']) && !empty($modSettings['attachmentThumbnails']))
1525
				{
1526
					if (!empty($attachment['id_thumb']))
1527
					{
1528
						$context['thumbs'][$i]['img'] = '<img id="thumb_' . $attachment['id'] . '" src="' . getUrl('action', ['action' => 'dlattach', 'topic' => $attachment['topic'] . '.0', 'attach' => $attachment['id_thumb'] . ';image']) . '" title="" alt="" />';
1529
					}
1530
					elseif (!empty($modSettings['attachmentThumbWidth']) && !empty($modSettings['attachmentThumbHeight']))
1531
					{
1532
						// No thumbnail available ... use html instead
1533
						if ($attachment['width'] > $modSettings['attachmentThumbWidth'] || $attachment['height'] > $modSettings['attachmentThumbHeight'])
1534
						{
1535
							$context['thumbs'][$i]['img'] = '<img id="thumb_' . $attachment['id'] . '" src="' . getUrl('action', ['action' => 'dlattach', 'topic' => $attachment['topic'] . '.0', 'attach' => $attachment['id']]) . '" title="" alt="" width="' . $modSettings['attachmentThumbWidth'] . '" height="' . $modSettings['attachmentThumbHeight'] . '" />';
1536
						}
1537
						else
1538
						{
1539
							$context['thumbs'][$i]['img'] = '<img id="thumb_' . $attachment['id'] . '" src="' . getUrl('action', ['action' => 'dlattach', 'topic' => $attachment['topic'] . '.0', 'attach' => $attachment['id']]) . '" title="" alt="" width="' . $attachment['width'] . '" height="' . $attachment['height'] . '" />';
1540
						}
1541
					}
1542
				}
1543
				// Not an image so lets set a mime thumbnail based off the filetype
1544
				elseif ((!empty($modSettings['attachmentThumbWidth']) && !empty($modSettings['attachmentThumbHeight'])) && (128 > $modSettings['attachmentThumbWidth'] || 128 > $modSettings['attachmentThumbHeight']))
1545
				{
1546
					$context['thumbs'][$i]['img'] = '<img src="' . $mime_images_url . (!file_exists($mime_path . $attachment['fileext'] . '.png') ? 'default' : $attachment['fileext']) . '.png" title="" alt="" width="' . $modSettings['attachmentThumbWidth'] . '" height="' . $modSettings['attachmentThumbHeight'] . '" />';
1547
				}
1548
				else
1549
				{
1550
					$context['thumbs'][$i]['img'] = '<img src="' . $mime_images_url . (!file_exists($mime_path . $attachment['fileext'] . '.png') ? 'default' : $attachment['fileext']) . '.png" title="" alt="" />';
1551
				}
1552
			}
1553
		}
1554
	}
1555
}
1556