Profile::_save_updates()   F
last analyzed

Complexity

Conditions 30
Paths 2746

Size

Total Lines 152
Code Lines 73

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 30
eloc 73
nc 2746
nop 0
dl 0
loc 152
rs 0
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/**
4
 * This file has the primary job of showing and editing people's profiles.
5
 * It also allows the user to change some of their or another's preferences,
6
 * and such things.
7
 *
8
 * @package   ElkArte Forum
9
 * @copyright ElkArte Forum contributors
10
 * @license   BSD http://opensource.org/licenses/BSD-3-Clause (see accompanying LICENSE.txt file)
11
 *
12
 * This file contains code covered by:
13
 * copyright: 2011 Simple Machines (http://www.simplemachines.org)
14
 *
15
 * @version 2.0 Beta 1
16
 *
17
 */
18
19
namespace ElkArte\Profile;
20
21
use ElkArte\AbstractController;
22
use ElkArte\Action;
23
use ElkArte\Cache\Cache;
24
use ElkArte\Controller\Likes;
25
use ElkArte\EventManager;
26
use ElkArte\Exceptions\Exception;
27
use ElkArte\Helper\Util;
28
use ElkArte\Languages\Txt;
29
use ElkArte\Member;
30
use ElkArte\MembersList;
31
use ElkArte\Menu\Menu;
32
use ElkArte\User;
33
34
/**
35
 * Has the job of showing and editing people's profiles.
36
 */
37
class Profile extends AbstractController
38
{
39
	/** @var bool If the save was successful or not */
40
	private $completedSave = false;
41
42
	/** @var null If this was a request to save an update */
43
	private $isSaving;
44
45
	/** @var null What it says, on completion */
46
	private $_force_redirect;
47
48
	/** @var array|bool Holds the output of createMenu for the profile areas */
49
	private $_profile_include_data;
50
51
	/** @var string The current area chosen from the menu */
52
	private $_current_area;
53
54
	/** @var string The current subsection, if any, of the area chosen */
55
	private $_current_subsection;
56
57
	/** @var int Member id for the history being viewed */
58
	private $_memID = 0;
59
60
	/** @var Member The \ElkArte\Member object is stored here to avoid some global */
61
	private $_profile;
62
63
	/**
64
	 * Called before all other methods when coming from the dispatcher or
65
	 * action class.
66
	 */
67
	public function pre_dispatch()
68
	{
69
		require_once(SUBSDIR . '/Menu.subs.php');
70
		require_once(SUBSDIR . '/Profile.subs.php');
71
72
		$this->_memID = currentMemberID();
73
		MembersList::load($this->_memID, false, 'profile');
74
		$this->_profile = MembersList::get($this->_memID);
0 ignored issues
show
Documentation Bug introduced by
It seems like ElkArte\MembersList::get($this->_memID) can also be of type anonymous//sources/ElkArte/MembersList.php$0. However, the property $_profile is declared as type ElkArte\Member. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
75
	}
76
77
	/**
78
	 * Allow the change or view of profiles.
79
	 *
80
	 * - Fires the pre_load event
81
	 *
82
	 * @see AbstractController::action_index
83
	 */
84
	public function action_index()
85
	{
86
		global $txt, $context, $cur_profile, $profile_vars, $post_errors;
87
88
		// Don't reload this as we may have processed error strings.
89
		if (empty($post_errors))
90
		{
91
			Txt::load('Profile');
92
		}
93
94
		theme()->getTemplates()->load('Profile');
95
96
		// Trigger profile pre-load event
97
		$this->_events->trigger('pre_load', ['post_errors' => $post_errors]);
98
99
		// A little bit about this member
100
		$context['id_member'] = $this->_memID;
101
		$cur_profile = $this->_profile;
102
103
		// Let's have some information about this member ready, too.
104
		$context['member'] = $this->_profile;
105
		$context['member']->loadContext();
106
107
		// Is this their own profile or are they looking at someone else?
108
		$context['user']['is_owner'] = $this->_memID === (int) $this->user->id;
0 ignored issues
show
Bug Best Practice introduced by
The property id does not exist on ElkArte\Helper\ValuesContainer. Since you implemented __get, consider adding a @property annotation.
Loading history...
109
110
		// Create the menu of profile options
111
		$this->_define_profile_menu();
112
113
		// Is there an updated message to show?
114
		if (isset($this->_req->query->updated))
115
		{
116
			$context['push_alert'] = $_SESSION['push_enabled'] ?? null;
117
			unset($_SESSION['push_enabled']);
118
			$context['profile_updated'] = $txt['profile_updated_own'];
119
		}
120
121
		// If it said no permissions, that meant it wasn't valid!
122
		if (empty($this->_profile_include_data['permission']))
123
		{
124
			throw new Exception('no_access', false);
125
		}
126
127
		// Choose the right permission set and do a pat check for good measure.
128
		$this->_profile_include_data['permission'] = $this->_profile_include_data['permission'][$context['user']['is_owner'] ? 'own' : 'any'];
129
		isAllowedTo($this->_profile_include_data['permission']);
130
131
		// Set the selected item - now it's been validated.
132
		$this->_current_area = $this->_profile_include_data['current_area'];
133
		$this->_current_subsection = $this->_profile_include_data['current_subsection'] ?? '';
134
		$context['menu_item_selected'] = $this->_current_area;
135
136
		// Before we go any further, let's work on the area we've said is valid.
137
		// Note this is done here just in case we ever compromise the menu function in error!
138
		$context['do_preview'] = isset($this->_req->post->preview_signature);
139
		$this->isSaving = $this->_req->getRequest('save', 'trim');
140
141
		// Session validation and/or Token Checks
142
		$this->_check_access();
143
144
		// Build the link tree.
145
		$this->_build_profile_breadcrumbs();
146
147
		// Set the template for this area... if you still can :P
148
		// and add the profile layer.
149
		$context['sub_template'] = $this->_profile_include_data['function'];
150
		theme()->getLayers()->add('profile');
151
152
		// Need JS if we made it this far
153
		loadJavascriptFile('profile.js');
154
155
		// Right - are we saving - if so, let's save the old data first.
156
		$this->_save_updates();
157
158
		// Have some errors for some reason?
159
		// @todo check that this can be safely removed.
160
		if (!empty($post_errors))
161
		{
162
			// Set all the errors so the template knows what went wrong.
163
			foreach ($post_errors as $error_type)
164
			{
165
				$context['modify_error'][$error_type] = true;
166
			}
167
		}
168
		// If it's you, then we should redirect upon save.
169
		elseif (!empty($profile_vars) && $context['user']['is_owner'] && !$context['do_preview'])
170
		{
171
			redirectexit('action=profile;area=' . $this->_current_area . (empty($this->_current_subsection) ? '' : ';sa=' . $this->_current_subsection) . ';updated');
172
		}
173
		elseif (!empty($this->_force_redirect))
174
		{
175
			redirectexit('action=profile' . ($context['user']['is_owner'] ? '' : ';u=' . $this->_memID) . ';area=' . $this->_current_area);
176
		}
177
178
		// Set the page title if it's not already set...
179
		if (!isset($context['page_title']))
180
		{
181
			$context['page_title'] = $txt['profile'] . (isset($txt[$this->_current_area]) ? ' - ' . $txt[$this->_current_area] : '');
182
		}
183
184
		// And off we go,
185
		$action = new Action();
186
		$action->initialize(['action' => $this->_profile_include_data]);
187
		$action->dispatch('action');
188
	}
189
190
	/**
191
	 * Define all the sections within the profile area!
192
	 *
193
	 * We start by defining the permission required - then we take this and turn
194
	 * it into the relevant context ;)
195
	 *
196
	 * Possible fields:
197
	 *   For Section:
198
	 *    - string $title: Section title.
199
	 *    - array $areas: Array of areas within this section.
200
	 *
201
	 *   For Areas:
202
	 *    - string $label:      Text string that will be used to show the area in the menu.
203
	 *    - string $file:       Optional text string that may contain a file name that's needed for inclusion in order to display the area properly.
204
	 *    - string $custom_url: Optional href for area.
205
	 *    - string $function:   Function to execute for this section.
206
	 *    - bool $enabled:      Should area be shown?
207
	 *    - string $sc:         Session check validation to do on save - note without this save will get unset - if set.
208
	 *    - bool $hidden:       Does this not actually appear on the menu?
209
	 *    - bool $password:     Whether to require the user's password in order to save the data in the area.
210
	 *    - array $subsections: Array of subsections, in order of appearance.
211
	 *    - array $permission:  Array of permissions to determine who can access this area. Should contain arrays $own and $any.
212
	 */
213
	private function _define_profile_menu(): void
214
	{
215
		global $txt, $context, $modSettings;
216
217
		$profile_areas = [
218
			'info' => [
219
				'title' => $txt['profileInfo'],
220
				'areas' => [
221
					'summary' => [
222
						'label' => $txt['summary'],
223
						'controller' => ProfileInfo::class,
224
						'function' => 'action_summary',
225
						// From the summary it's possible to activate an account, so we need the token
226
						'token' => 'profile-aa%u',
227
						'token_type' => 'get',
228
						'permission' => [
229
							'own' => 'profile_view_own',
230
							'any' => 'profile_view_any',
231
						],
232
					],
233
					'statistics' => [
234
						'label' => $txt['statPanel'],
235
						'controller' => ProfileInfo::class,
236
						'function' => 'action_statPanel',
237
						'permission' => [
238
							'own' => 'profile_view_own',
239
							'any' => 'profile_view_any',
240
						],
241
					],
242
					'showposts' => [
243
						'label' => $txt['showPosts'],
244
						'controller' => ProfileInfo::class,
245
						'function' => 'action_showPosts',
246
						'subsections' => [
247
							'messages' => [$txt['showMessages'], ['profile_view_own', 'profile_view_any']],
248
							'topics' => [$txt['showTopics'], ['profile_view_own', 'profile_view_any']],
249
							'unwatchedtopics' => [$txt['showUnwatched'], ['profile_view_own', 'profile_view_any'], 'enabled' => $modSettings['enable_unwatch'] && $context['user']['is_owner']],
250
							'attach' => [$txt['showAttachments'], ['profile_view_own', 'profile_view_any']],
251
						],
252
						'permission' => [
253
							'own' => 'profile_view_own',
254
							'any' => 'profile_view_any',
255
						],
256
					],
257
					'showlikes' => [
258
						'label' => $txt['likes_show'],
259
						'controller' => Likes::class,
260
						'function' => 'action_showProfileLikes',
261
						'enabled' => !empty($modSettings['likes_enabled']) && $context['user']['is_owner'],
262
						'subsections' => [
263
							'given' => [$txt['likes_given'], ['profile_view_own']],
264
							'received' => [$txt['likes_received'], ['profile_view_own']],
265
						],
266
						'permission' => [
267
							'own' => 'profile_view_own',
268
							'any' => [],
269
						],
270
					],
271
					'permissions' => [
272
						'label' => $txt['showPermissions'],
273
						'controller' => ProfileInfo::class,
274
						'function' => 'action_showPermissions',
275
						'permission' => [
276
							'own' => 'manage_permissions',
277
							'any' => 'manage_permissions',
278
						],
279
					],
280
					'history' => [
281
						'label' => $txt['history'],
282
						'controller' => ProfileHistory::class,
283
						'function' => 'action_index',
284
						'subsections' => [
285
							'activity' => [$txt['trackActivity'], 'moderate_forum'],
286
							'ip' => [$txt['trackIP'], 'moderate_forum'],
287
							'edits' => [$txt['trackEdits'], 'moderate_forum', 'enabled' => featureEnabled('ml') && !empty($modSettings['userlog_enabled'])],
288
							'logins' => [$txt['trackLogins'], ['profile_view_own', 'moderate_forum']],
289
						],
290
						'permission' => [
291
							'own' => 'moderate_forum',
292
							'any' => 'moderate_forum',
293
						],
294
					],
295
					'viewwarning' => [
296
						'label' => $txt['profile_view_warnings'],
297
						'enabled' => featureEnabled('w') && !empty($modSettings['warning_enable']) && $this->_profile['warning'] && (!empty($modSettings['warning_show']) && ($context['user']['is_owner'] || $modSettings['warning_show'] == 2)),
298
						'controller' => ProfileInfo::class,
299
						'function' => 'action_viewWarning',
300
						'permission' => [
301
							'own' => 'profile_view_own',
302
							'any' => 'issue_warning',
303
						],
304
					],
305
				],
306
			],
307
			'edit_profile' => [
308
				'title' => $txt['profileEdit'],
309
				'areas' => [
310
					'account' => [
311
						'label' => $txt['account'],
312
						'controller' => ProfileOptions::class,
313
						'function' => 'action_account',
314
						'enabled' => $context['user']['is_admin'] || ((int) $this->_profile['id_group'] !== 1 && !in_array(1, array_map('intval', explode(',', $this->_profile['additional_groups'])), true)), 'sc' => 'post',
0 ignored issues
show
Bug introduced by
It seems like $this->_profile['additional_groups'] 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

314
						'enabled' => $context['user']['is_admin'] || ((int) $this->_profile['id_group'] !== 1 && !in_array(1, array_map('intval', explode(',', /** @scrutinizer ignore-type */ $this->_profile['additional_groups'])), true)), 'sc' => 'post',
Loading history...
315
						'token' => 'profile-ac%u',
316
						'password' => true,
317
						'permission' => [
318
							'own' => ['profile_identity_any', 'profile_identity_own', 'manage_membergroups'],
319
							'any' => ['profile_identity_any', 'manage_membergroups'],
320
						],
321
					],
322
					'forumprofile' => [
323
						'label' => $txt['forumprofile'],
324
						'controller' => ProfileOptions::class,
325
						'function' => 'action_forumProfile',
326
						'sc' => 'post',
327
						'token' => 'profile-fp%u',
328
						'permission' => [
329
							'own' => ['profile_extra_any', 'profile_extra_own', 'profile_title_own', 'profile_title_any'],
330
							'any' => ['profile_extra_any', 'profile_title_any'],
331
						],
332
					],
333
					'theme' => [
334
						'label' => $txt['theme'],
335
						'controller' => ProfileOptions::class,
336
						'function' => 'action_themepick',
337
						'sc' => 'post',
338
						'token' => 'profile-th%u',
339
						'permission' => [
340
							'own' => ['profile_extra_any', 'profile_extra_own'],
341
							'any' => ['profile_extra_any'],
342
						],
343
					],
344
					'pick' => [
345
						'label' => $txt['theme'],
346
						'controller' => ProfileOptions::class,
347
						'function' => 'action_pick',
348
						'hidden' => true,
349
						'sc' => 'post',
350
						'token' => 'profile-th%u',
351
						'permission' => [
352
							'own' => ['profile_extra_any', 'profile_extra_own'],
353
							'any' => ['profile_extra_any'],
354
						],
355
					],
356
					'notification' => [
357
						'label' => $txt['notifications'],
358
						'controller' => ProfileOptions::class,
359
						'function' => 'action_notification',
360
						'sc' => 'post',
361
						'token' => 'profile-nt%u',
362
						'subsections' => [
363
							'settings' => [$txt['notify_settings']],
364
							'boards' => [$txt['notify_boards']],
365
							'topics' => [$txt['notify_topics']],
366
						],
367
						'permission' => [
368
							'own' => ['profile_extra_any', 'profile_extra_own'],
369
							'any' => ['profile_extra_any'],
370
						],
371
					],
372
					// Without profile_extra_own, settings are accessible from the PM section.
373
					// @todo at some point decouple it from PMs
374
					'contactprefs' => [
375
						'label' => $txt['contactprefs'],
376
						'controller' => ProfileOptions::class,
377
						'function' => 'action_pmprefs',
378
						'enabled' => allowedTo(['profile_extra_own', 'profile_extra_any']),
379
						'sc' => 'post',
380
						'token' => 'profile-pm%u',
381
						'permission' => [
382
							'own' => ['pm_read'],
383
							'any' => ['profile_extra_any'],
384
						],
385
					],
386
					'ignoreboards' => [
387
						'label' => $txt['ignoreboards'],
388
						'controller' => ProfileOptions::class,
389
						'function' => 'action_ignoreboards',
390
						'enabled' => !empty($modSettings['allow_ignore_boards']),
391
						'sc' => 'post',
392
						'token' => 'profile-ib%u',
393
						'permission' => [
394
							'own' => ['profile_extra_any', 'profile_extra_own'],
395
							'any' => ['profile_extra_any'],
396
						],
397
					],
398
					'lists' => [
399
						'label' => $txt['editBuddyIgnoreLists'],
400
						'controller' => ProfileOptions::class,
401
						'function' => 'action_editBuddyIgnoreLists',
402
						'enabled' => !empty($modSettings['enable_buddylist']) && $context['user']['is_owner'],
403
						'sc' => 'post',
404
						'token' => 'profile-bl%u',
405
						'subsections' => [
406
							'buddies' => [$txt['editBuddies']],
407
							'ignore' => [$txt['editIgnoreList']],
408
						],
409
						'permission' => [
410
							'own' => ['profile_extra_any', 'profile_extra_own'],
411
							'any' => [],
412
						],
413
					],
414
					'groupmembership' => [
415
						'label' => $txt['groupmembership'],
416
						'controller' => ProfileOptions::class,
417
						'function' => 'action_groupMembership',
418
						'enabled' => !empty($modSettings['show_group_membership']) && $context['user']['is_owner'],
419
						'sc' => 'request',
420
						'token' => 'profile-gm%u',
421
						'token_type' => 'request',
422
						'permission' => [
423
							'own' => ['profile_view_own'],
424
							'any' => ['manage_membergroups'],
425
						],
426
					],
427
				],
428
			],
429
			'profile_action' => [
430
				'title' => $txt['profileAction'],
431
				'areas' => [
432
					'sendpm' => [
433
						'label' => $txt['profileSendIm'],
434
						'custom_url' => getUrl('action', ['action' => 'pm', 'sa' => 'send']),
435
						'permission' => [
436
							'own' => [],
437
							'any' => ['pm_send'],
438
						],
439
					],
440
					'issuewarning' => [
441
						'label' => $txt['profile_issue_warning'],
442
						'enabled' => featureEnabled('w') && !empty($modSettings['warning_enable']) && (!$context['user']['is_owner'] || $context['user']['is_admin']),
443
						'controller' => ProfileAccount::class,
444
						'function' => 'action_issuewarning',
445
						'token' => 'profile-iw%u',
446
						'permission' => [
447
							'own' => [],
448
							'any' => ['issue_warning'],
449
						],
450
					],
451
					'banuser' => [
452
						'label' => $txt['profileBanUser'],
453
						'custom_url' => getUrl('admin', ['action' => 'admin', 'area' => 'ban', 'sa' => 'add']),
454
						'enabled' => (int) $this->_profile['id_group'] !== 1 && !in_array(1, array_map('intval', explode(',', $this->_profile['additional_groups'])), true),
455
						'permission' => [
456
							'own' => [],
457
							'any' => ['manage_bans'],
458
						],
459
					],
460
					'subscriptions' => [
461
						'label' => $txt['subscriptions'],
462
						'controller' => ProfileSubscriptions::class,
463
						'function' => 'action_subscriptions',
464
						'enabled' => !empty($modSettings['paid_enabled']) && ((empty($modSettings['paidsubs_test']) || $context['user']['is_admin'])),
465
						'permission' => [
466
							'own' => ['profile_view_own'],
467
							'any' => ['moderate_forum'],
468
						],
469
					],
470
					'deleteaccount' => [
471
						'label' => $txt['deleteAccount'],
472
						'controller' => ProfileAccount::class,
473
						'function' => 'action_deleteaccount',
474
						'sc' => 'post',
475
						'token' => 'profile-da%u',
476
						'password' => true,
477
						'permission' => [
478
							'own' => ['profile_remove_any', 'profile_remove_own'],
479
							'any' => ['profile_remove_any'],
480
						],
481
					],
482
					'activateaccount' => [
483
						'label' => $txt['account_activate'],
484
						'hidden' => true,
485
						'controller' => ProfileAccount::class,
486
						'function' => 'action_activateaccount',
487
						'sc' => 'get',
488
						'token' => 'profile-aa%u',
489
						'token_type' => 'get',
490
						'permission' => [
491
							'own' => [],
492
							'any' => ['moderate_forum'],
493
						],
494
					],
495
				],
496
			],
497
		];
498
499
		// Set a few options for the menu.
500
		$menuOptions = [
501
			'disable_url_session_check' => true,
502
			'hook' => 'profile',
503
			'extra_url_parameters' => [
504
				'u' => $context['id_member'],
505
			],
506
		];
507
508
		// Actually create the menu!
509
		$this->_profile_include_data = (new Menu())
510
			->addMenuData($profile_areas)
511
			->addOptions($menuOptions)
512
			->prepareMenu()
513
			->setContext()
514
			->getIncludeData();
515
516
		unset($profile_areas);
517
518
		// Make a note of the Unique ID for this menu.
519
		$context['profile_menu_id'] = $context['max_menu_id'];
520
		$context['profile_menu_name'] = 'menu_data_' . $context['profile_menu_id'];
521
	}
522
523
	/**
524
	 * Does session and token checks for the areas that require those
525
	 */
526
	private function _check_access(): void
527
	{
528
		global $context;
529
530
		// Check the session, if required, and they are trying to save
531
		$this->completedSave = false;
532
		if (isset($this->_profile_include_data['sc']) && ($this->isSaving !== null || $context['do_preview']))
533
		{
534
			checkSession($this->_profile_include_data['sc']);
535
			$this->completedSave = true;
536
		}
537
538
		// Does this require admin/moderator session validating?
539
		if ($this->isSaving !== null && !$context['user']['is_owner'])
540
		{
541
			validateSession();
542
		}
543
544
		// Do we need to perform a token check?
545
		if (!empty($this->_profile_include_data['token']))
546
		{
547
			$token_name = str_replace('%u', $context['id_member'], $this->_profile_include_data['token']);
548
			$token_type = $this->_profile_include_data['tokenType'] ?? 'post';
549
550
			if (!in_array($token_type, ['request', 'post', 'get']))
551
			{
552
				$token_type = 'post';
553
			}
554
555
			if ($this->isSaving !== null)
556
			{
557
				validateToken($token_name, $token_type);
558
			}
559
560
			createToken($token_name, $token_type);
561
			$context['token_check'] = $token_name;
562
		}
563
	}
564
565
	/**
566
	 * Just builds the link tree based on where we are in the profile section
567
	 * and whose profile is being viewed, etc.
568
	 */
569
	private function _build_profile_breadcrumbs(): void
570
	{
571
		global $context, $txt;
572
573
		$context['breadcrumbs'][] = [
574
			'url' => getUrl('profile', ['action' => 'profile', 'u' => $this->_memID, 'name' => $this->_profile['real_name']]),
575
			'name' => sprintf($txt['profile_of_username'], $context['member']['name']),
576
		];
577
578
		if (!empty($this->_profile_include_data['label']))
579
		{
580
			$context['breadcrumbs'][] = [
581
				'url' => getUrl('profile', ['action' => 'profile', 'area' => $this->_profile_include_data['current_area'], 'u' => $this->_memID, 'name' => $this->_profile['real_name']]),
582
				'name' => $this->_profile_include_data['label'],
583
			];
584
		}
585
586
		if (empty($this->_current_subsection))
587
		{
588
			return;
589
		}
590
591
		if (!isset($this->_profile_include_data['subsections'][$this->_current_subsection]))
592
		{
593
			return;
594
		}
595
596
		if ($this->_profile_include_data['subsections'][$this->_current_subsection]['label'] === $this->_profile_include_data['label'])
597
		{
598
			return;
599
		}
600
601
		$context['breadcrumbs'][] = [
602
			'url' => getUrl('profile', ['action' => 'profile', 'area' => $this->_profile_include_data['current_area'], 'sa' => $this->_current_subsection, 'u' => $this->_memID, 'name' => $this->_profile['real_name']]),
603
			'name' => $this->_profile_include_data['subsections'][$this->_current_subsection]['label'],
604
		];
605
	}
606
607
	/**
608
	 * Save profile updates
609
	 */
610
	private function _save_updates(): void
611
	{
612
		global $txt, $context, $modSettings, $post_errors, $profile_vars;
613
614
		// All the subActions that require a user password to validate.
615
		$check_password = $context['user']['is_owner'] && !empty($this->_profile_include_data['password']);
616
		$context['require_password'] = $check_password;
617
618
		// These will get populated soon!
619
		$post_errors = [];
620
		$profile_vars = [];
621
622
		if ($this->completedSave)
623
		{
624
			// Clean up the POST variables.
625
			$post = Util::htmltrim__recursive((array) $this->_req->post);
626
			$post = Util::htmlspecialchars__recursive($post);
627
			$this->_req->post = new \ArrayObject($post, \ArrayObject::ARRAY_AS_PROPS);
628
629
			// Does the change require the current password as well?
630
			$this->_check_password($check_password);
631
632
			// Change the IP address in the database.
633
			if ($context['user']['is_owner'])
634
			{
635
				$profile_vars['member_ip'] = $this->user->ip;
0 ignored issues
show
Bug Best Practice introduced by
The property ip does not exist on ElkArte\Helper\ValuesContainer. Since you implemented __get, consider adding a @property annotation.
Loading history...
636
			}
637
638
			// Now call the sub-action function...
639
			if ($this->_current_area === 'activateaccount' && empty($post_errors))
640
			{
641
				$controller = new ProfileAccount(new EventManager());
642
				$controller->setUser(User::$info);
643
				$controller->pre_dispatch();
644
				$controller->action_activateaccount();
645
			}
646
			elseif ($this->_current_area === 'deleteaccount' && empty($post_errors))
647
			{
648
				$controller = new ProfileAccount(new EventManager());
649
				$controller->setUser(User::$info);
650
				$controller->pre_dispatch();
651
				$controller->action_deleteaccount2();
652
653
				// Done
654
				redirectexit();
655
			}
656
			elseif ($this->_current_area === 'groupmembership' && empty($post_errors))
657
			{
658
				$controller = new ProfileOptions(new EventManager());
659
				$controller->setUser(User::$info);
660
				$controller->pre_dispatch();
661
				$msg = $controller->action_groupMembership2();
662
663
				// Whatever we've done, we have nothing else to do here...
664
				redirectexit('action=profile' . ($context['user']['is_owner'] ? '' : ';u=' . $this->_memID) . ';area=groupmembership' . (empty($msg) ? '' : ';msg=' . $msg));
665
			}
666
			elseif (in_array($this->_current_area, ['account', 'forumprofile', 'theme', 'contactprefs']))
667
			{
668
				if ($this->_current_area === 'account' && !empty($modSettings['enableOTP']))
669
				{
670
					$fields = ProfileOptions::getFields('account_otp');
671
				}
672
				else
673
				{
674
					$fields = ProfileOptions::getFields($this->_current_area);
675
				}
676
677
				$profileFields = new ProfileFields();
678
				$profileFields->saveProfileFields($fields['fields'], $fields['hook']);
679
			}
680
			elseif (empty($post_errors))
681
			{
682
				// @todo yes this is also ugly, but saveProfileChanges needs to be updated first
683
				$_POST = (array) $this->_req->post;
684
685
				$this->_force_redirect = true;
0 ignored issues
show
Documentation Bug introduced by
It seems like true of type true is incompatible with the declared type null of property $_force_redirect.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
686
				saveProfileChanges($profile_vars, $this->_memID);
687
			}
688
689
			call_integration_hook('integrate_profile_save', [&$profile_vars, &$post_errors, $this->_memID]);
690
691
			// There was a problem, let them try to re-enter.
692
			if (!empty($post_errors))
693
			{
694
				// Load the language file so we can give a nice explanation of the errors.
695
				Txt::load('Errors');
696
				$context['post_errors'] = $post_errors;
697
			}
698
			elseif (!empty($profile_vars))
699
			{
700
				// If we've changed the password, notify any integration that may be listening in.
701
				if (isset($profile_vars['passwd']))
702
				{
703
					call_integration_hook('integrate_reset_pass', [$this->_profile['member_name'], $this->_profile['member_name'], $this->_req->post->passwrd2]);
0 ignored issues
show
Bug introduced by
The property passwrd2 does not seem to exist on ArrayObject.
Loading history...
704
				}
705
706
				require_once(SUBSDIR . '/Members.subs.php');
707
				updateMemberData($this->_memID, $profile_vars);
708
709
				// What if this is the newest member?
710
				if ((int) $modSettings['latestMember'] === $this->_memID)
711
				{
712
					require_once(SUBSDIR . '/Members.subs.php');
713
					updateMemberStats();
714
				}
715
				elseif (isset($profile_vars['real_name']))
716
				{
717
					updateSettings(['memberlist_updated' => time()]);
718
				}
719
720
				// If the member changed his/her birthdate, update calendar statistics.
721
				if (isset($profile_vars['birthdate']) || isset($profile_vars['real_name']))
722
				{
723
					updateSettings([
724
						'calendar_updated' => time(),
725
					]);
726
				}
727
728
				// Anything worth logging?
729
				if (!empty($context['log_changes']) && !empty($modSettings['userlog_enabled']) && featureEnabled('ml'))
730
				{
731
					$log_changes = [];
732
					foreach ($context['log_changes'] as $k => $v)
733
					{
734
						$log_changes[] = [
735
							'action' => $k,
736
							'log_type' => 'user',
737
							'extra' => array_merge($v, [
738
								'applicator' => $this->user->id,
0 ignored issues
show
Bug Best Practice introduced by
The property id does not exist on ElkArte\Helper\ValuesContainer. Since you implemented __get, consider adding a @property annotation.
Loading history...
739
								'member_affected' => $this->_memID,
740
							]),
741
						];
742
					}
743
744
					logActions($log_changes);
745
				}
746
747
				// Have we got any post-save functions to execute?
748
				if (!empty($context['profile_execute_on_save']))
749
				{
750
					foreach ($context['profile_execute_on_save'] as $saveFunc)
751
					{
752
						$saveFunc();
753
					}
754
				}
755
756
				// Let them know it worked!
757
				$context['profile_updated'] = $context['user']['is_owner'] ? $txt['profile_updated_own'] : sprintf($txt['profile_updated_else'], $this->_profile['member_name']);
758
759
				// Invalidate any cached data.
760
				Cache::instance()->remove('member_data-profile-' . $this->_memID);
761
				MembersList::load($this->_memID, false, 'profile');
762
			}
763
		}
764
	}
765
766
	/**
767
	 * If a password validation before a change is needed, this is the function to do it
768
	 *
769
	 * @param bool $check_password if this profile update requires password verification
770
	 * @throws Exception
771
	 */
772
	private function _check_password($check_password): void
773
	{
774
		global $post_errors, $context;
775
776
		if ($check_password)
777
		{
778
			// You didn't even enter a password!
779
			if (trim($this->_req->post->oldpasswrd) === '')
780
			{
781
				$post_errors[] = 'no_password';
782
			}
783
784
			// Since the password got modified due to all the $_POST cleaning, let's undo it so we can get the correct password
785
			$this->_req->post->oldpasswrd = un_htmlspecialchars($this->_req->post->oldpasswrd);
786
787
			// Does the integration want to check passwords?
788
			$good_password = in_array(true, call_integration_hook('integrate_verify_password', [$this->_profile['member_name'], $this->_req->post->oldpasswrd, false]), true);
789
790
			// Start up the password checker; we have work to do
791
			require_once(SUBSDIR . '/Auth.subs.php');
792
793
			// Bad password!!!
794
			if (!$good_password && !validateLoginPassword($this->_req->post->oldpasswrd, $this->user->passwd, $this->_profile['member_name']))
0 ignored issues
show
Bug Best Practice introduced by
The property passwd does not exist on ElkArte\Helper\ValuesContainer. Since you implemented __get, consider adding a @property annotation.
Loading history...
795
			{
796
				$post_errors[] = 'bad_password';
797
			}
798
799
			// Warn other elements not to jump the gun and do custom changes!
800
			if (in_array('bad_password', $post_errors, true))
801
			{
802
				$context['password_auth_failed'] = true;
803
			}
804
		}
805
	}
806
}
807