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 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 dev |
16
|
|
|
* |
17
|
|
|
*/ |
18
|
|
|
|
19
|
|
|
namespace ElkArte\Profile; |
20
|
|
|
|
21
|
|
|
use ElkArte\AbstractController; |
22
|
|
|
use ElkArte\Action; |
23
|
|
|
use ElkArte\Cache\Cache; |
24
|
|
|
use ElkArte\Exceptions\Exception; |
25
|
|
|
use ElkArte\Languages\Txt; |
26
|
|
|
use ElkArte\Member; |
27
|
|
|
use ElkArte\MembersList; |
28
|
|
|
|
29
|
|
|
/** |
30
|
|
|
* Options a user can set to customize their site experience |
31
|
|
|
* |
32
|
|
|
* - Does the job of showing and editing people's profiles. |
33
|
|
|
* - Interface to buddy list, ignore list, notifications, authentication options, forum profile |
34
|
|
|
* account settings, etc |
35
|
|
|
*/ |
36
|
|
|
class ProfileOptions extends AbstractController |
37
|
|
|
{ |
38
|
|
|
/** @var int Member id for the profile being viewed */ |
39
|
|
|
private $_memID = 0; |
40
|
|
|
|
41
|
|
|
/** @var Member The \ElkArte\Member object is stored here to avoid some global */ |
42
|
|
|
private $_profile; |
43
|
|
|
|
44
|
|
|
/** |
45
|
|
|
* Called before all other methods when coming from the dispatcher or |
46
|
|
|
* action class. |
47
|
|
|
* |
48
|
|
|
* - If you initiate the class outside those methods, call this method. |
49
|
|
|
* or setup the class yourself else a horrible fate awaits you |
50
|
|
|
*/ |
51
|
|
|
public function pre_dispatch() |
52
|
|
|
{ |
53
|
|
|
$this->_memID = currentMemberID(); |
54
|
|
|
$this->_profile = MembersList::get($this->_memID); |
|
|
|
|
55
|
|
|
} |
56
|
|
|
|
57
|
|
|
/** |
58
|
|
|
* Default method, if another action is not called by the menu. |
59
|
|
|
* |
60
|
|
|
* @see AbstractController::action_index() |
61
|
|
|
*/ |
62
|
|
|
public function action_index() |
63
|
|
|
{ |
64
|
|
|
// action_account() is the first to do |
65
|
|
|
// these subactions are mostly routed to from the profile |
66
|
|
|
// menu though. |
67
|
|
|
} |
68
|
|
|
|
69
|
|
|
/** |
70
|
|
|
* Show all the users buddies, as well as a add/delete interface. |
71
|
|
|
* |
72
|
|
|
* @throws Exception |
73
|
|
|
*/ |
74
|
|
|
public function action_editBuddyIgnoreLists() |
75
|
|
|
{ |
76
|
|
|
global $context, $txt, $modSettings; |
77
|
|
|
|
78
|
|
|
// Do a quick check to ensure people aren't getting here illegally! |
79
|
|
|
if (!$context['user']['is_owner'] || empty($modSettings['enable_buddylist'])) |
80
|
|
|
{ |
81
|
|
|
throw new Exception('no_access', false); |
82
|
|
|
} |
83
|
|
|
|
84
|
|
|
theme()->getTemplates()->load('ProfileOptions'); |
85
|
|
|
|
86
|
|
|
// Can we email the user direct? |
87
|
|
|
$context['can_moderate_forum'] = allowedTo('moderate_forum'); |
88
|
|
|
$context['can_send_email'] = allowedTo('send_email_to_members'); |
89
|
|
|
|
90
|
|
|
$subActions = [ |
91
|
|
|
'buddies' => [$this, 'action_editBuddies'], |
92
|
|
|
'ignore' => [$this, 'action_editIgnoreList'], |
93
|
|
|
]; |
94
|
|
|
|
95
|
|
|
// Set a subaction |
96
|
|
|
$action = new Action('buddy_actions'); |
97
|
|
|
$subAction = $action->initialize($subActions, 'buddies'); |
98
|
|
|
|
99
|
|
|
// Create the tabs for the template. |
100
|
|
|
$context[$context['profile_menu_name']]['object']->prepareTabData([ |
101
|
|
|
'title' => $txt['editBuddyIgnoreLists'], |
102
|
|
|
'description' => $txt['buddy_ignore_desc'], |
103
|
|
|
'class' => 'i-user', |
104
|
|
|
]); |
105
|
|
|
|
106
|
|
|
// Pass on to the actual function. |
107
|
|
|
$action->dispatch($subAction); |
108
|
|
|
} |
109
|
|
|
|
110
|
|
|
/** |
111
|
|
|
* Show all the users buddies, as well as an add/delete interface. |
112
|
|
|
* |
113
|
|
|
* @uses template_editBuddies() |
114
|
|
|
*/ |
115
|
|
|
public function action_editBuddies() |
116
|
|
|
{ |
117
|
|
|
global $context; |
118
|
|
|
|
119
|
|
|
theme()->getTemplates()->load('ProfileOptions'); |
120
|
|
|
|
121
|
|
|
// We want to view what we're doing :P |
122
|
|
|
$context['sub_template'] = 'editBuddies'; |
123
|
|
|
|
124
|
|
|
// Use suggest finding the right buddies |
125
|
|
|
loadJavascriptFile('suggest.js', array('defer' => true)); |
126
|
|
|
|
127
|
|
|
// For making changes! |
128
|
|
|
$buddiesArray = array_map('intval', explode(',', $this->_profile['buddy_list'])); |
|
|
|
|
129
|
|
|
$buddiesArray = array_filter($buddiesArray, static fn($value) => $value !== ''); |
130
|
|
|
|
131
|
|
|
// Removing a buddy? |
132
|
|
|
$notMyBuddy = $this->_req->getQuery('remove', 'intval'); |
133
|
|
|
if ($notMyBuddy !== null) |
134
|
|
|
{ |
135
|
|
|
checkSession('get'); |
136
|
|
|
|
137
|
|
|
call_integration_hook('integrate_remove_buddy', [$this->_memID]); |
138
|
|
|
|
139
|
|
|
$key = array_search($notMyBuddy, $buddiesArray, true); |
140
|
|
|
if ($key !== false) |
141
|
|
|
{ |
142
|
|
|
unset($buddiesArray[$key]); |
143
|
|
|
} |
144
|
|
|
|
145
|
|
|
// Make the changes. |
146
|
|
|
$this->_profile['buddy_list'] = implode(',', $buddiesArray); |
147
|
|
|
require_once(SUBSDIR . '/Members.subs.php'); |
148
|
|
|
updateMemberData($this->_memID, ['buddy_list' => $this->_profile['buddy_list']]); |
149
|
|
|
|
150
|
|
|
// Redirect off the page because we don't like all this ugly query stuff to stick in the history. |
151
|
|
|
redirectexit('action=profile;area=lists;sa=buddies;u=' . $this->_memID); |
152
|
|
|
} |
153
|
|
|
// Or adding a new one |
154
|
|
|
elseif (isset($this->_req->post->new_buddy)) |
155
|
|
|
{ |
156
|
|
|
checkSession(); |
157
|
|
|
|
158
|
|
|
// Prepare the string for extraction... |
159
|
|
|
$new_buddy = strtr($this->_req->getPost('new_buddy', 'trim|htmlspecialchars[ENT_QUOTES]'), ['"' => '"']); |
160
|
|
|
if ($new_buddy === '' || in_array($new_buddy, [$this->_profile['member_name'], $this->_profile['real_name']], true)) |
161
|
|
|
{ |
162
|
|
|
unset($new_buddy); |
163
|
|
|
} |
164
|
|
|
|
165
|
|
|
call_integration_hook('integrate_add_buddies', [$this->_memID, &$new_buddies]); |
|
|
|
|
166
|
|
|
|
167
|
|
|
if (!empty($new_buddy)) |
168
|
|
|
{ |
169
|
|
|
// Now find out the id_member of the buddy. |
170
|
|
|
require_once(SUBSDIR . '/ProfileOptions.subs.php'); |
171
|
|
|
$new_buddiesArray = getBuddiesID([$new_buddy]); |
172
|
|
|
$old_buddiesArray = explode(',', $this->_profile['buddy_list']); |
173
|
|
|
|
174
|
|
|
// Now update the current users buddy list. |
175
|
|
|
$this->_profile['buddy_list'] = implode(',', array_filter(array_unique(array_merge($new_buddiesArray, $old_buddiesArray)))); |
176
|
|
|
|
177
|
|
|
require_once(SUBSDIR . '/Members.subs.php'); |
178
|
|
|
updateMemberData($this->_memID, ['buddy_list' => $this->_profile['buddy_list']]); |
179
|
|
|
} |
180
|
|
|
|
181
|
|
|
// Back to the buddy list! |
182
|
|
|
redirectexit('action=profile;area=lists;sa=buddies;u=' . $this->_memID); |
183
|
|
|
} |
184
|
|
|
|
185
|
|
|
// Get all the users "buddies"... |
186
|
|
|
$buddies = []; |
187
|
|
|
|
188
|
|
|
if (!empty($buddiesArray)) |
189
|
|
|
{ |
190
|
|
|
require_once(SUBSDIR . '/Members.subs.php'); |
191
|
|
|
$result = getBasicMemberData($buddiesArray, ['sort' => 'real_name', 'limit' => substr_count($this->_profile['buddy_list'], ',') + 1]); |
|
|
|
|
192
|
|
|
foreach ($result as $row) |
193
|
|
|
{ |
194
|
|
|
$buddies[] = (int) $row['id_member']; |
195
|
|
|
} |
196
|
|
|
} |
197
|
|
|
|
198
|
|
|
$context['buddy_count'] = count($buddies); |
199
|
|
|
|
200
|
|
|
// Load all the members up. |
201
|
|
|
MembersList::load($buddies, false, 'profile'); |
202
|
|
|
|
203
|
|
|
// Set the context for each buddy. |
204
|
|
|
$context['buddies'] = []; |
205
|
|
|
foreach ($buddies as $buddy) |
206
|
|
|
{ |
207
|
|
|
$context['buddies'][$buddy] = MembersList::get($buddy); |
208
|
|
|
$context['buddies'][$buddy]->loadContext(); |
209
|
|
|
} |
210
|
|
|
|
211
|
|
|
call_integration_hook('integrate_view_buddies', [$this->_memID]); |
212
|
|
|
} |
213
|
|
|
|
214
|
|
|
/** |
215
|
|
|
* Allows the user to view their ignore list, |
216
|
|
|
* |
217
|
|
|
* - Provides the option to manage members on it. |
218
|
|
|
*/ |
219
|
|
|
public function action_editIgnoreList() |
220
|
|
|
{ |
221
|
|
|
global $context; |
222
|
|
|
|
223
|
|
|
theme()->getTemplates()->load('ProfileOptions'); |
224
|
|
|
|
225
|
|
|
// We want to view what we're doing :P |
226
|
|
|
$context['sub_template'] = 'editIgnoreList'; |
227
|
|
|
loadJavascriptFile('suggest.js', array('defer' => true)); |
228
|
|
|
|
229
|
|
|
// For making changes! |
230
|
|
|
$ignoreArray = array_map('intval', explode(',', $this->_profile['pm_ignore_list'])); |
|
|
|
|
231
|
|
|
$ignoreArray = array_filter($ignoreArray, static fn($value) => $value !== ''); |
232
|
|
|
|
233
|
|
|
// Removing a member from the ignore list? |
234
|
|
|
$id_remove = $this->_req->getQuery('remove', 'intval'); |
235
|
|
|
if (isset($id_remove)) |
236
|
|
|
{ |
237
|
|
|
checkSession('get'); |
238
|
|
|
|
239
|
|
|
// Heh, I'm lazy, do it the easy way... |
240
|
|
|
$key = array_search($id_remove, $ignoreArray, true); |
241
|
|
|
if ($key !== false) |
242
|
|
|
{ |
243
|
|
|
unset($ignoreArray[$key]); |
244
|
|
|
} |
245
|
|
|
|
246
|
|
|
// Make the changes. |
247
|
|
|
$this->_profile['pm_ignore_list'] = implode(',', $ignoreArray); |
248
|
|
|
require_once(SUBSDIR . '/Members.subs.php'); |
249
|
|
|
updateMemberData($this->_memID, ['pm_ignore_list' => $this->_profile['pm_ignore_list']]); |
250
|
|
|
|
251
|
|
|
// Redirect off the page because we don't like all this ugly query stuff |
252
|
|
|
// to stick in the history. |
253
|
|
|
redirectexit('action=profile;area=lists;sa=ignore;u=' . $this->_memID); |
254
|
|
|
} |
255
|
|
|
elseif (isset($this->_req->post->new_ignore)) |
256
|
|
|
{ |
257
|
|
|
checkSession(); |
258
|
|
|
|
259
|
|
|
// Prepare the string for extraction... |
260
|
|
|
$new_ignore = strtr($this->_req->getPost('new_ignore', 'trim|htmlspecialchars[ENT_QUOTES]'), ['"' => '"']); |
261
|
|
|
if ($new_ignore === '' || in_array($new_ignore, [$this->_profile['member_name'], $this->_profile['real_name']], true)) |
262
|
|
|
{ |
263
|
|
|
unset($new_ignore); |
264
|
|
|
} |
265
|
|
|
|
266
|
|
|
if (!empty($new_ignore)) |
267
|
|
|
{ |
268
|
|
|
// Now find out the id_member for the members in question. |
269
|
|
|
require_once(SUBSDIR . '/ProfileOptions.subs.php'); |
270
|
|
|
$ignoreArray = array_merge($ignoreArray, getBuddiesID([$new_ignore], false)); |
271
|
|
|
|
272
|
|
|
// Now update the current users buddy list. |
273
|
|
|
$this->_profile['pm_ignore_list'] = implode(',', $ignoreArray); |
274
|
|
|
require_once(SUBSDIR . '/Members.subs.php'); |
275
|
|
|
updateMemberData($this->_memID, ['pm_ignore_list' => $this->_profile['pm_ignore_list']]); |
276
|
|
|
} |
277
|
|
|
|
278
|
|
|
// Back to the list of pitiful people! |
279
|
|
|
redirectexit('action=profile;area=lists;sa=ignore;u=' . $this->_memID); |
280
|
|
|
} |
281
|
|
|
|
282
|
|
|
// Initialise the list of members we're ignoring. |
283
|
|
|
$ignored = []; |
284
|
|
|
|
285
|
|
|
if (!empty($ignoreArray)) |
286
|
|
|
{ |
287
|
|
|
require_once(SUBSDIR . '/Members.subs.php'); |
288
|
|
|
$result = getBasicMemberData($ignoreArray, ['sort' => 'real_name', 'limit' => substr_count($this->_profile['pm_ignore_list'], ',') + 1]); |
|
|
|
|
289
|
|
|
foreach ($result as $row) |
290
|
|
|
{ |
291
|
|
|
$ignored[] = (int) $row['id_member']; |
292
|
|
|
} |
293
|
|
|
} |
294
|
|
|
|
295
|
|
|
$context['ignore_count'] = count($ignored); |
296
|
|
|
|
297
|
|
|
// Load all the members up. |
298
|
|
|
MembersList::load($ignored, false, 'profile'); |
299
|
|
|
|
300
|
|
|
// Set the context for everyone we ignore. |
301
|
|
|
$context['ignore_list'] = []; |
302
|
|
|
foreach ($ignored as $ignore_member) |
303
|
|
|
{ |
304
|
|
|
$context['ignore_list'][$ignore_member] = MembersList::get($ignore_member); |
305
|
|
|
$context['ignore_list'][$ignore_member]->loadContext(); |
306
|
|
|
} |
307
|
|
|
} |
308
|
|
|
|
309
|
|
|
/** |
310
|
|
|
* Allows the user to see or change their account info. |
311
|
|
|
*/ |
312
|
|
|
public function action_account() |
313
|
|
|
{ |
314
|
|
|
global $modSettings, $context, $txt; |
315
|
|
|
|
316
|
|
|
theme()->getTemplates()->load('ProfileOptions'); |
317
|
|
|
$this->loadThemeOptions(); |
318
|
|
|
|
319
|
|
|
if (allowedTo(['profile_identity_own', 'profile_identity_any'])) |
320
|
|
|
{ |
321
|
|
|
$profileFields = new ProfileFields(); |
322
|
|
|
$profileFields->loadCustomFields($this->_memID, 'account'); |
323
|
|
|
} |
324
|
|
|
|
325
|
|
|
$context['sub_template'] = 'edit_options'; |
326
|
|
|
$context['page_desc'] = $txt['account_info']; |
327
|
|
|
|
328
|
|
|
if (!empty($modSettings['enableOTP'])) |
329
|
|
|
{ |
330
|
|
|
$fields = self::getFields('account_otp'); |
331
|
|
|
setupProfileContext($fields['fields'], $fields['hook']); |
332
|
|
|
|
333
|
|
|
loadJavascriptFile('ext/qrcode.js'); |
334
|
|
|
$context['load_google_authenticator'] = true; |
335
|
|
|
} |
336
|
|
|
else |
337
|
|
|
{ |
338
|
|
|
$fields = self::getFields('account'); |
339
|
|
|
} |
340
|
|
|
|
341
|
|
|
setupProfileContext($fields['fields'], $fields['hook']); |
342
|
|
|
} |
343
|
|
|
|
344
|
|
|
/** |
345
|
|
|
* Load the options for a user. |
346
|
|
|
*/ |
347
|
|
|
public function loadThemeOptions() |
348
|
|
|
{ |
349
|
|
|
global $context, $cur_profile, $options; |
350
|
|
|
|
351
|
|
|
$default_options = $this->_req->getPost('default_options'); |
352
|
|
|
$post_options = $this->_req->getPost('options'); |
353
|
|
|
if (isset($default_options)) |
354
|
|
|
{ |
355
|
|
|
$post_options = isset($post_options) ? $post_options + $default_options : $default_options; |
356
|
|
|
} |
357
|
|
|
|
358
|
|
|
if ($context['user']['is_owner']) |
359
|
|
|
{ |
360
|
|
|
$context['member']['options'] = $options + $this->_profile->options; |
361
|
|
|
|
362
|
|
|
if (isset($post_options) && is_array($post_options)) |
363
|
|
|
{ |
364
|
|
|
foreach ($post_options as $k => $v) |
365
|
|
|
{ |
366
|
|
|
$context['member']['options'][$k] = $v; |
367
|
|
|
} |
368
|
|
|
} |
369
|
|
|
} |
370
|
|
|
else |
371
|
|
|
{ |
372
|
|
|
require_once(SUBSDIR . '/Themes.subs.php'); |
373
|
|
|
$context['member']['options'] = loadThemeOptionsInto( |
374
|
|
|
[1, (int) $cur_profile['id_theme']], |
375
|
|
|
[-1, $this->_memID], $context['member']['options'] |
376
|
|
|
); |
377
|
|
|
|
378
|
|
|
if (isset($post_options)) |
379
|
|
|
{ |
380
|
|
|
foreach ($post_options as $var => $val) |
381
|
|
|
{ |
382
|
|
|
$context['member']['options'][$var] = $val; |
383
|
|
|
} |
384
|
|
|
} |
385
|
|
|
} |
386
|
|
|
} |
387
|
|
|
|
388
|
|
|
/** |
389
|
|
|
* Returns the profile fields for a given area |
390
|
|
|
* |
391
|
|
|
* @param string $area |
392
|
|
|
* @return array |
393
|
|
|
*/ |
394
|
|
|
public static function getFields($area) |
395
|
|
|
{ |
396
|
|
|
global $modSettings; |
397
|
|
|
|
398
|
|
|
$fields = [ |
399
|
|
|
'account' => [ |
400
|
|
|
'fields' => [ |
401
|
|
|
'member_name', 'real_name', 'date_registered', 'posts', 'lngfile', 'hr', |
402
|
|
|
'id_group', 'hr', |
403
|
|
|
'email_address', 'show_online', 'hr', |
404
|
|
|
'passwrd1', 'passwrd2', 'hr', |
405
|
|
|
'secret_question', 'secret_answer', |
406
|
|
|
], |
407
|
|
|
'hook' => 'account' |
408
|
|
|
], |
409
|
|
|
'account_otp' => [ |
410
|
|
|
'fields' => [ |
411
|
|
|
'member_name', 'real_name', 'date_registered', 'posts', 'lngfile', 'hr', |
412
|
|
|
'id_group', 'hr', |
413
|
|
|
'email_address', 'show_online', 'hr', |
414
|
|
|
'passwrd1', 'passwrd2', 'hr', |
415
|
|
|
'secret_question', 'secret_answer', 'hr', |
416
|
|
|
'enable_otp', 'otp_secret', 'hr' |
417
|
|
|
], |
418
|
|
|
'hook' => 'account' |
419
|
|
|
], |
420
|
|
|
'forumprofile' => [ |
421
|
|
|
'fields' => [ |
422
|
|
|
'avatar_choice', 'hr', |
423
|
|
|
'bday1', 'usertitle', 'hr', |
424
|
|
|
'signature', 'hr', |
425
|
|
|
'karma_good', 'hr', |
426
|
|
|
'website_title', 'website_url', |
427
|
|
|
], |
428
|
|
|
'hook' => 'forum' |
429
|
|
|
], |
430
|
|
|
'theme' => [ |
431
|
|
|
'fields' => [ |
432
|
|
|
'id_theme', 'smiley_set', 'hr', |
433
|
|
|
'time_format', 'time_offset', 'hr', |
434
|
|
|
'theme_settings', |
435
|
|
|
], |
436
|
|
|
'hook' => 'themepick' |
437
|
|
|
], |
438
|
|
|
'contactprefs' => [ |
439
|
|
|
'fields' => [ |
440
|
|
|
'receive_from', |
441
|
|
|
'hr', |
442
|
|
|
'pm_settings', |
443
|
|
|
], |
444
|
|
|
'hook' => 'pmprefs' |
445
|
|
|
], |
446
|
|
|
'registration' => [ |
447
|
|
|
'fields' => empty($modSettings['registration_fields']) ? [] : explode(',', $modSettings['registration_fields']), |
448
|
|
|
'hook' => 'registration' |
449
|
|
|
] |
450
|
|
|
]; |
451
|
|
|
|
452
|
|
|
return $fields[$area] ?? []; |
453
|
|
|
} |
454
|
|
|
|
455
|
|
|
/** |
456
|
|
|
* Allow the user to change the forum options in their profile. |
457
|
|
|
*/ |
458
|
|
|
public function action_forumProfile() |
459
|
|
|
{ |
460
|
|
|
global $context, $txt; |
461
|
|
|
|
462
|
|
|
theme()->getTemplates()->load('ProfileOptions'); |
463
|
|
|
$this->loadThemeOptions(); |
464
|
|
|
|
465
|
|
|
if (allowedTo(['profile_extra_own', 'profile_extra_any'])) |
466
|
|
|
{ |
467
|
|
|
$profileFields = new ProfileFields(); |
468
|
|
|
$profileFields->loadCustomFields($this->_memID, 'forumprofile'); |
469
|
|
|
} |
470
|
|
|
|
471
|
|
|
$context['sub_template'] = 'edit_options'; |
472
|
|
|
$context['page_desc'] = replaceBasicActionUrl($txt['forumProfile_info']); |
473
|
|
|
$context['show_preview_button'] = true; |
474
|
|
|
|
475
|
|
|
$fields = self::getFields('forumprofile'); |
476
|
|
|
setupProfileContext($fields['fields'], $fields['hook']); |
477
|
|
|
} |
478
|
|
|
|
479
|
|
|
/** |
480
|
|
|
* Allow the edit of *someone else's* personal message settings. |
481
|
|
|
*/ |
482
|
|
|
public function action_pmprefs() |
483
|
|
|
{ |
484
|
|
|
global $context, $txt; |
485
|
|
|
|
486
|
|
|
$this->loadThemeOptions(); |
487
|
|
|
$profileFields = new ProfileFields(); |
488
|
|
|
$profileFields->loadCustomFields($this->_memID, 'pmprefs'); |
489
|
|
|
theme()->getTemplates()->load('ProfileOptions'); |
490
|
|
|
|
491
|
|
|
$context['sub_template'] = 'edit_options'; |
492
|
|
|
$context['page_desc'] = $txt['pm_settings_desc']; |
493
|
|
|
|
494
|
|
|
// Set up the profile context and call the 'integrate_pmprefs_profile_fields' hook |
495
|
|
|
$fields = self::getFields('contactprefs'); |
496
|
|
|
setupProfileContext($fields['fields'], $fields['hook']); |
497
|
|
|
} |
498
|
|
|
|
499
|
|
|
/** |
500
|
|
|
* Allow the user to pick a theme, Set time formats, and set |
501
|
|
|
* overall look and layout options. |
502
|
|
|
*/ |
503
|
|
|
public function action_themepick() |
504
|
|
|
{ |
505
|
|
|
global $txt, $context; |
506
|
|
|
|
507
|
|
|
$this->loadThemeOptions(); |
508
|
|
|
|
509
|
|
|
if (allowedTo(['profile_extra_own', 'profile_extra_any'])) |
510
|
|
|
{ |
511
|
|
|
$profileFields = new ProfileFields(); |
512
|
|
|
$profileFields->loadCustomFields($this->_memID, 'theme'); |
513
|
|
|
} |
514
|
|
|
|
515
|
|
|
theme()->getTemplates()->load('ProfileOptions'); |
516
|
|
|
|
517
|
|
|
$context['sub_template'] = 'edit_options'; |
518
|
|
|
$context['page_desc'] = $txt['theme_info']; |
519
|
|
|
|
520
|
|
|
// Set up profile look and layout, call 'integrate_themepick_profile_fields' hook |
521
|
|
|
$fields = self::getFields('theme'); |
522
|
|
|
setupProfileContext($fields['fields'], $fields['hook']); |
523
|
|
|
} |
524
|
|
|
|
525
|
|
|
/** |
526
|
|
|
* Choose a theme from a list of those that are available |
527
|
|
|
* |
528
|
|
|
* What it does: |
529
|
|
|
* |
530
|
|
|
* - Uses the Themes template. (pick sub template.) |
531
|
|
|
* - Accessed with ?action=admin;area=theme;sa=pick. |
532
|
|
|
* - Allows previewing of the theme and variants |
533
|
|
|
*/ |
534
|
|
|
public function action_pick() |
535
|
|
|
{ |
536
|
|
|
global $txt, $context, $modSettings, $scripturl; |
537
|
|
|
|
538
|
|
|
checkSession('get'); |
539
|
|
|
|
540
|
|
|
// Basics |
541
|
|
|
Txt::load('ManageThemes'); |
542
|
|
|
theme()->getTemplates()->load('ProfileOptions'); |
543
|
|
|
require_once(SUBSDIR . '/Themes.subs.php'); |
544
|
|
|
|
545
|
|
|
// Note JS values will be in post via the form, JS enabled they will be in get via link button |
546
|
|
|
$_SESSION['theme'] = 0; |
547
|
|
|
$_SESSION['id_variant'] = 0; |
548
|
|
|
$save = $this->_req->getPost('save'); |
549
|
|
|
$u = $this->_req->getQuery('u', 'intval'); |
|
|
|
|
550
|
|
|
$themePicked = $this->_req->getQuery('th', 'intval'); |
551
|
|
|
$variant = $this->_req->getQuery('vrt', 'cleanhtml'); |
552
|
|
|
|
553
|
|
|
// Build the link tree |
554
|
|
|
$context['breadcrumbs'][] = [ |
555
|
|
|
'url' => $scripturl . '?action=profile;sa=pick;u=' . $this->_memID, |
556
|
|
|
'name' => $txt['theme_pick'], |
557
|
|
|
]; |
558
|
|
|
$context['default_theme_id'] = $modSettings['theme_default']; |
559
|
|
|
|
560
|
|
|
// Saving a theme/variant cause JS doesn't work - pretend it did ;) |
561
|
|
|
if (isset($save)) |
562
|
|
|
{ |
563
|
|
|
// Which theme? |
564
|
|
|
foreach ($save as $k => $v) |
565
|
|
|
{ |
566
|
|
|
$themePicked = (int) $k; |
567
|
|
|
} |
568
|
|
|
|
569
|
|
|
if (isset($this->_req->post->vrt[$k])) |
|
|
|
|
570
|
|
|
{ |
571
|
|
|
$variant = $this->_req->post->vrt[$k]; |
572
|
|
|
} |
573
|
|
|
} |
574
|
|
|
|
575
|
|
|
// Have we made a decision, or are we just previewing? |
576
|
|
|
if (isset($themePicked)) |
577
|
|
|
{ |
578
|
|
|
// Save the chosen theme. |
579
|
|
|
require_once(SUBSDIR . '/Members.subs.php'); |
580
|
|
|
updateMemberData($this->_memID, ['id_theme' => $themePicked]); |
581
|
|
|
|
582
|
|
|
// Did they pick a variant as well? |
583
|
|
|
if (!empty($variant)) |
584
|
|
|
{ |
585
|
|
|
updateThemeOptions([$themePicked, $this->_memID, 'theme_variant', $variant]); |
586
|
|
|
Cache::instance()->remove('theme_settings-' . $themePicked . ':' . $this->_memID); |
587
|
|
|
$_SESSION['id_variant'] = 0; |
588
|
|
|
} |
589
|
|
|
|
590
|
|
|
redirectexit('action=profile;area=theme'); |
591
|
|
|
} |
592
|
|
|
|
593
|
|
|
$context['current_member'] = $this->_memID; |
594
|
|
|
$current_theme = (int) $this->_profile['theme']; |
595
|
|
|
|
596
|
|
|
// Get all theme name and descriptions. |
597
|
|
|
[$context['available_themes'], $guest_theme] = availableThemes($current_theme, $context['current_member']); |
598
|
|
|
|
599
|
|
|
// As long as we're not doing the default theme... |
600
|
|
|
if ($guest_theme !== 0) |
601
|
|
|
{ |
602
|
|
|
$context['available_themes'][0] = $context['available_themes'][$guest_theme]; |
603
|
|
|
} |
604
|
|
|
|
605
|
|
|
$context['available_themes'][0]['id'] = 0; |
606
|
|
|
$context['available_themes'][0]['name'] = $txt['theme_forum_default']; |
607
|
|
|
$context['available_themes'][0]['selected'] = $current_theme === 0; |
608
|
|
|
$context['available_themes'][0]['description'] = $txt['theme_global_description']; |
609
|
|
|
|
610
|
|
|
ksort($context['available_themes']); |
611
|
|
|
|
612
|
|
|
$context['page_title'] = $txt['theme_pick']; |
613
|
|
|
$context['sub_template'] = 'pick'; |
614
|
|
|
} |
615
|
|
|
|
616
|
|
|
/** |
617
|
|
|
* Display the notification settings for the user and allow changes. |
618
|
|
|
*/ |
619
|
|
|
public function action_notification() |
620
|
|
|
{ |
621
|
|
|
global $txt, $context; |
622
|
|
|
|
623
|
|
|
theme()->getTemplates()->load('ProfileOptions'); |
624
|
|
|
|
625
|
|
|
require_once(SUBSDIR . '/Profile.subs.php'); |
626
|
|
|
|
627
|
|
|
$subActions = [ |
628
|
|
|
'settings' => [$this, 'action_editNotificationSettings'], |
629
|
|
|
'boards' => [$this, 'action_editNotificationBoards'], |
630
|
|
|
'topics' => [$this, 'action_editNotificationTopics'] |
631
|
|
|
]; |
632
|
|
|
|
633
|
|
|
// Set a subaction |
634
|
|
|
$action = new Action('notification_actions'); |
635
|
|
|
$subAction = $action->initialize($subActions, 'settings'); |
636
|
|
|
|
637
|
|
|
// Create the header for the template. |
638
|
|
|
$context[$context['profile_menu_name']]['object']->prepareTabData([ |
639
|
|
|
'title' => $txt['notify_settings'], |
640
|
|
|
'description' => $txt['notification_info'], |
641
|
|
|
'class' => 'i-contact', |
642
|
|
|
]); |
643
|
|
|
|
644
|
|
|
// Pass on to the actual function. |
645
|
|
|
$action->dispatch($subAction); |
646
|
|
|
} |
647
|
|
|
|
648
|
|
|
/** |
649
|
|
|
* Generate the users existing notification options and allow for updates |
650
|
|
|
*/ |
651
|
|
|
public function action_editNotificationSettings() |
652
|
|
|
{ |
653
|
|
|
global $context; |
654
|
|
|
|
655
|
|
|
// Show the list of notification types and how they can subscribe to them |
656
|
|
|
$context['mention_types'] = getMemberNotificationsProfile($this->_memID); |
657
|
|
|
|
658
|
|
|
// What options are set? |
659
|
|
|
$context['member']['notify_announcements'] = $this->_profile['notify_announcements']; |
660
|
|
|
$context['member']['notify_send_body'] = $this->_profile['notify_send_body']; |
661
|
|
|
$context['member']['notify_types'] = $this->_profile['notify_types']; |
662
|
|
|
$context['member']['notify_regularity'] = $this->_profile['notify_regularity']; |
663
|
|
|
$context['member']['notify_from'] = $this->_profile['notify_from']; |
664
|
|
|
|
665
|
|
|
$this->loadThemeOptions(); |
666
|
|
|
} |
667
|
|
|
|
668
|
|
|
/** |
669
|
|
|
* Generate the users existing board notification list. |
670
|
|
|
* Loads data into $context to be displayed wth template_board_notification_list |
671
|
|
|
*/ |
672
|
|
|
public function action_editNotificationBoards() |
673
|
|
|
{ |
674
|
|
|
global $txt, $scripturl, $context; |
675
|
|
|
|
676
|
|
|
require_once(SUBSDIR . '/Boards.subs.php'); |
677
|
|
|
|
678
|
|
|
$context['mention_types'] = getMemberNotificationsProfile($this->_memID); |
679
|
|
|
unset($context['sub_template']); |
680
|
|
|
|
681
|
|
|
// Fine, start with the board list. |
682
|
|
|
$listOptions = [ |
683
|
|
|
'id' => 'board_notification_list', |
684
|
|
|
'width' => '100%', |
685
|
|
|
'no_items_label' => $txt['notifications_boards_none'] . '<br /><br />' . $txt['notifications_boards_howto'], |
686
|
|
|
'no_items_align' => 'left', |
687
|
|
|
'base_href' => $scripturl . '?action=profile;u=' . $this->_memID . ';area=notification', |
688
|
|
|
'default_sort_col' => 'board_name', |
689
|
|
|
'get_items' => [ |
690
|
|
|
'function' => fn($start, $items_per_page, $sort, $memID) => $this->list_getBoardNotifications($start, $items_per_page, $sort, $memID), |
691
|
|
|
'params' => [ |
692
|
|
|
$this->_memID, |
693
|
|
|
], |
694
|
|
|
], |
695
|
|
|
'columns' => [ |
696
|
|
|
'board_name' => [ |
697
|
|
|
'header' => [ |
698
|
|
|
'value' => $txt['notifications_boards'], |
699
|
|
|
'class' => 'lefttext', |
700
|
|
|
], |
701
|
|
|
'data' => [ |
702
|
|
|
'function' => static function ($board) { |
703
|
|
|
global $txt; |
704
|
|
|
|
705
|
|
|
$link = $board['link']; |
706
|
|
|
if ($board['new']) |
707
|
|
|
{ |
708
|
|
|
$link .= ' <a href="' . $board['href'] . '" class="new_posts">' . $txt['new'] . '</a>'; |
709
|
|
|
} |
710
|
|
|
|
711
|
|
|
return $link; |
712
|
|
|
}, |
713
|
|
|
], |
714
|
|
|
'sort' => [ |
715
|
|
|
'default' => 'name', |
716
|
|
|
'reverse' => 'name DESC', |
717
|
|
|
], |
718
|
|
|
], |
719
|
|
|
'delete' => [ |
720
|
|
|
'header' => [ |
721
|
|
|
'value' => '<input type="checkbox" onclick="invertAll(this, this.form);" />', |
722
|
|
|
'class' => 'centertext', |
723
|
|
|
'style' => 'width:4%;', |
724
|
|
|
], |
725
|
|
|
'data' => [ |
726
|
|
|
'sprintf' => [ |
727
|
|
|
'format' => '<input type="checkbox" name="notify_boards[]" value="%1$d" %2$s />', |
728
|
|
|
'params' => [ |
729
|
|
|
'id' => false, |
730
|
|
|
'checked' => false, |
731
|
|
|
], |
732
|
|
|
], |
733
|
|
|
'class' => 'centertext', |
734
|
|
|
], |
735
|
|
|
], |
736
|
|
|
], |
737
|
|
|
'form' => [ |
738
|
|
|
'href' => $scripturl . '?action=profile;area=notification;sa=boards', |
739
|
|
|
'include_sort' => true, |
740
|
|
|
'include_start' => true, |
741
|
|
|
'hidden_fields' => [ |
742
|
|
|
'u' => $this->_memID, |
743
|
|
|
'sa' => $context['menu_item_selected'], |
744
|
|
|
$context['session_var'] => $context['session_id'], |
745
|
|
|
], |
746
|
|
|
'token' => $context['token_check'], |
747
|
|
|
], |
748
|
|
|
'additional_rows' => [ |
749
|
|
|
[ |
750
|
|
|
'class' => 'submitbutton', |
751
|
|
|
'position' => 'bottom_of_list', |
752
|
|
|
'value' => ' |
753
|
|
|
<input type="submit" name="edit_notify_boards" value="' . $txt['notifications_boards_update'] . '" /> |
754
|
|
|
<input type="hidden" name="save" value="save" />', |
755
|
|
|
], |
756
|
|
|
[ |
757
|
|
|
'position' => 'after_title', |
758
|
|
|
'value' => getBoardNotificationsCount($this->_memID) == 0 ? $txt['notifications_boards_none'] . '<br />' . $txt['notifications_boards_howto'] : $txt['notifications_boards_current'], |
759
|
|
|
], |
760
|
|
|
], |
761
|
|
|
]; |
762
|
|
|
|
763
|
|
|
// Create the board notification list. |
764
|
|
|
createList($listOptions); |
765
|
|
|
} |
766
|
|
|
|
767
|
|
|
/** |
768
|
|
|
* Generate the users existing topic notification list. |
769
|
|
|
* Loads data into $context to be displayed wth template_topic_notification_list |
770
|
|
|
*/ |
771
|
|
|
public function action_editNotificationTopics() |
772
|
|
|
{ |
773
|
|
|
global $txt, $scripturl, $context, $modSettings; |
774
|
|
|
|
775
|
|
|
require_once(SUBSDIR . '/Topic.subs.php'); |
776
|
|
|
|
777
|
|
|
$context['mention_types'] = getMemberNotificationsProfile($this->_memID); |
778
|
|
|
unset($context['sub_template']); |
779
|
|
|
|
780
|
|
|
// Now do the topic notifications. |
781
|
|
|
$listOptions = [ |
782
|
|
|
'id' => 'topic_notification_list', |
783
|
|
|
'width' => '100%', |
784
|
|
|
'items_per_page' => $modSettings['defaultMaxMessages'], |
785
|
|
|
'no_items_label' => $txt['notifications_topics_none'] . '<br /><br />' . $txt['notifications_topics_howto'], |
786
|
|
|
'no_items_align' => 'left', |
787
|
|
|
'base_href' => $scripturl . '?action=profile;u=' . $this->_memID . ';area=notification', |
788
|
|
|
'default_sort_col' => 'last_post', |
789
|
|
|
'get_items' => [ |
790
|
|
|
'function' => fn($start, $items_per_page, $sort, $memID) => $this->list_getTopicNotifications($start, $items_per_page, $sort, $memID), |
791
|
|
|
'params' => [ |
792
|
|
|
$this->_memID, |
793
|
|
|
], |
794
|
|
|
], |
795
|
|
|
'get_count' => [ |
796
|
|
|
'function' => fn($memID) => $this->list_getTopicNotificationCount($memID), |
797
|
|
|
'params' => [ |
798
|
|
|
$this->_memID, |
799
|
|
|
], |
800
|
|
|
], |
801
|
|
|
'columns' => [ |
802
|
|
|
'subject' => [ |
803
|
|
|
'header' => [ |
804
|
|
|
'value' => $txt['notifications_topics'], |
805
|
|
|
'class' => 'lefttext', |
806
|
|
|
], |
807
|
|
|
'data' => [ |
808
|
|
|
'function' => static function ($topic) { |
809
|
|
|
global $txt; |
810
|
|
|
|
811
|
|
|
$link = $topic['link']; |
812
|
|
|
if ($topic['new']) |
813
|
|
|
{ |
814
|
|
|
$link .= ' <a href="' . $topic['new_href'] . '" class="new_posts">' . $txt['new'] . '</a>'; |
815
|
|
|
} |
816
|
|
|
|
817
|
|
|
return $link . ('<br /><span class="smalltext"><em>' . $txt['in'] . ' ' . $topic['board_link'] . '</em></span>'); |
818
|
|
|
}, |
819
|
|
|
], |
820
|
|
|
'sort' => [ |
821
|
|
|
'default' => 'ms.subject', |
822
|
|
|
'reverse' => 'ms.subject DESC', |
823
|
|
|
], |
824
|
|
|
], |
825
|
|
|
'started_by' => [ |
826
|
|
|
'header' => [ |
827
|
|
|
'value' => $txt['started_by'], |
828
|
|
|
'class' => 'lefttext', |
829
|
|
|
], |
830
|
|
|
'data' => [ |
831
|
|
|
'db' => 'poster_link', |
832
|
|
|
], |
833
|
|
|
'sort' => [ |
834
|
|
|
'default' => 'real_name_col', |
835
|
|
|
'reverse' => 'real_name_col DESC', |
836
|
|
|
], |
837
|
|
|
], |
838
|
|
|
'last_post' => [ |
839
|
|
|
'header' => [ |
840
|
|
|
'value' => $txt['last_post'], |
841
|
|
|
'class' => 'lefttext', |
842
|
|
|
], |
843
|
|
|
'data' => [ |
844
|
|
|
'sprintf' => [ |
845
|
|
|
'format' => '<span class="smalltext">%1$s<br />' . $txt['by'] . ' %2$s</span>', |
846
|
|
|
'params' => [ |
847
|
|
|
'updated' => false, |
848
|
|
|
'poster_updated_link' => false, |
849
|
|
|
], |
850
|
|
|
], |
851
|
|
|
], |
852
|
|
|
'sort' => [ |
853
|
|
|
'default' => 'ml.id_msg DESC', |
854
|
|
|
'reverse' => 'ml.id_msg', |
855
|
|
|
], |
856
|
|
|
], |
857
|
|
|
'delete' => [ |
858
|
|
|
'header' => [ |
859
|
|
|
'value' => '<input type="checkbox" onclick="invertAll(this, this.form);" />', |
860
|
|
|
'class' => 'centertext', |
861
|
|
|
'style' => 'width:4%;', |
862
|
|
|
], |
863
|
|
|
'data' => [ |
864
|
|
|
'sprintf' => [ |
865
|
|
|
'format' => '<input type="checkbox" name="notify_topics[]" value="%1$d" />', |
866
|
|
|
'params' => [ |
867
|
|
|
'id' => false, |
868
|
|
|
], |
869
|
|
|
], |
870
|
|
|
'class' => 'centertext', |
871
|
|
|
], |
872
|
|
|
], |
873
|
|
|
], |
874
|
|
|
'form' => [ |
875
|
|
|
'href' => $scripturl . '?action=profile;area=notification', |
876
|
|
|
'include_sort' => true, |
877
|
|
|
'include_start' => true, |
878
|
|
|
'hidden_fields' => [ |
879
|
|
|
'u' => $this->_memID, |
880
|
|
|
'sa' => $context['menu_item_selected'], |
881
|
|
|
$context['session_var'] => $context['session_id'], |
882
|
|
|
], |
883
|
|
|
'token' => $context['token_check'], |
884
|
|
|
], |
885
|
|
|
'additional_rows' => [ |
886
|
|
|
[ |
887
|
|
|
'class' => 'submitbutton', |
888
|
|
|
'position' => 'bottom_of_list', |
889
|
|
|
'value' => ' |
890
|
|
|
<input type="submit" name="edit_notify_topics" value="' . $txt['notifications_update'] . '" /> |
891
|
|
|
<input type="hidden" name="save" value="save" />', |
892
|
|
|
], |
893
|
|
|
], |
894
|
|
|
]; |
895
|
|
|
|
896
|
|
|
// Create the topic notification list. |
897
|
|
|
createList($listOptions); |
898
|
|
|
} |
899
|
|
|
|
900
|
|
|
/** |
901
|
|
|
* Callback for createList() in action_notification() |
902
|
|
|
* |
903
|
|
|
* @param int $start The item to start with (for pagination purposes) |
904
|
|
|
* @param int $items_per_page The number of items to show per page |
905
|
|
|
* @param string $sort A string indicating how to sort the results |
906
|
|
|
* @param int $memID id_member |
907
|
|
|
* |
908
|
|
|
* @return array array of board notifications |
909
|
|
|
* @uses template_ignoreboards() |
910
|
|
|
*/ |
911
|
|
|
public function list_getBoardNotifications($start, $items_per_page, $sort, $memID) |
912
|
|
|
{ |
913
|
|
|
// Return boards you see and their notification status for the list |
914
|
|
|
return boardNotifications($sort, $memID); |
915
|
|
|
} |
916
|
|
|
|
917
|
|
|
/** |
918
|
|
|
* Callback for createList() in action_notification() |
919
|
|
|
* |
920
|
|
|
* @param int $start The item to start with (for pagination purposes) |
921
|
|
|
* @param int $items_per_page The number of items to show per page |
922
|
|
|
* @param string $sort A string indicating how to sort the results |
923
|
|
|
* @param int $memID id_member |
924
|
|
|
* |
925
|
|
|
* @return array array of topic notifications |
926
|
|
|
*/ |
927
|
|
|
public function list_getTopicNotifications($start, $items_per_page, $sort, $memID) |
928
|
|
|
{ |
929
|
|
|
// Topic notifications, for the list |
930
|
|
|
return topicNotifications($start, $items_per_page, $sort, $memID); |
931
|
|
|
} |
932
|
|
|
|
933
|
|
|
/** |
934
|
|
|
* Callback for createList() in action_notification() |
935
|
|
|
* |
936
|
|
|
* - Retrieve topic notifications count. |
937
|
|
|
* |
938
|
|
|
* @param int $memID id_member the id of the member who's notifications we are loading |
939
|
|
|
* @return int |
940
|
|
|
*/ |
941
|
|
|
public function list_getTopicNotificationCount($memID) |
942
|
|
|
{ |
943
|
|
|
// Topic notifications count, for the list |
944
|
|
|
return topicNotificationCount($memID); |
945
|
|
|
} |
946
|
|
|
|
947
|
|
|
/** |
948
|
|
|
* Allows the user to see the list of their ignored boards. |
949
|
|
|
* (and un-ignore them) |
950
|
|
|
*/ |
951
|
|
|
public function action_ignoreboards() |
952
|
|
|
{ |
953
|
|
|
global $context, $modSettings, $cur_profile; |
954
|
|
|
|
955
|
|
|
// Have the admins enabled this option? |
956
|
|
|
if (empty($modSettings['allow_ignore_boards'])) |
957
|
|
|
{ |
958
|
|
|
throw new Exception('ignoreboards_disallowed', 'user'); |
959
|
|
|
} |
960
|
|
|
|
961
|
|
|
theme()->getTemplates()->load('ProfileOptions'); |
962
|
|
|
|
963
|
|
|
$context['sub_template'] = 'ignoreboards'; |
964
|
|
|
require_once(SUBSDIR . '/Boards.subs.php'); |
965
|
|
|
$context += getBoardList(['not_redirection' => true, 'ignore' => empty($cur_profile['ignore_boards']) ? [] : explode(',', $cur_profile['ignore_boards'])]); |
966
|
|
|
|
967
|
|
|
// Include a list of boards per category for easy toggling. |
968
|
|
|
foreach ($context['categories'] as $cat => &$category) |
969
|
|
|
{ |
970
|
|
|
$context['boards_in_category'][$cat] = count($category['boards']); |
971
|
|
|
$category['child_ids'] = array_keys($category['boards']); |
972
|
|
|
} |
973
|
|
|
|
974
|
|
|
$this->loadThemeOptions(); |
975
|
|
|
} |
976
|
|
|
|
977
|
|
|
/** |
978
|
|
|
* Function to allow the user to choose group membership etc... |
979
|
|
|
*/ |
980
|
|
|
public function action_groupMembership() |
981
|
|
|
{ |
982
|
|
|
global $txt, $context; |
983
|
|
|
|
984
|
|
|
theme()->getTemplates()->load('ProfileOptions'); |
985
|
|
|
$context['sub_template'] = 'groupMembership'; |
986
|
|
|
|
987
|
|
|
$curMember = $this->_profile; |
988
|
|
|
$context['primary_group'] = (int) $curMember['id_group']; |
989
|
|
|
$msgName = $this->_req->getQuery('msg', 'trim'); |
990
|
|
|
|
991
|
|
|
// Can they manage groups? |
992
|
|
|
$context['can_manage_membergroups'] = allowedTo('manage_membergroups'); |
993
|
|
|
$context['can_manage_protected'] = allowedTo('admin_forum'); |
994
|
|
|
$context['can_edit_primary'] = $context['can_manage_protected']; |
995
|
|
|
$context['update_message'] = isset($msgName, $txt['group_membership_msg_' . $msgName]) ? $txt['group_membership_msg_' . $msgName] : ''; |
996
|
|
|
|
997
|
|
|
// Get all the groups this user is a member of. |
998
|
|
|
$groups = array_map('intval', explode(',', $curMember['additional_groups'])); |
|
|
|
|
999
|
|
|
$groups[] = (int) $curMember['id_group']; |
1000
|
|
|
|
1001
|
|
|
// Ensure the query doesn't croak! |
1002
|
|
|
if (empty($groups)) |
1003
|
|
|
{ |
1004
|
|
|
$groups = [0]; |
1005
|
|
|
} |
1006
|
|
|
|
1007
|
|
|
// Just to be sure... |
1008
|
|
|
$groups = array_map('intval', $groups); |
1009
|
|
|
|
1010
|
|
|
// Get all the membergroups they can join. |
1011
|
|
|
require_once(SUBSDIR . '/ProfileOptions.subs.php'); |
1012
|
|
|
$context['groups'] = loadMembergroupsJoin($groups, $this->_memID); |
1013
|
|
|
|
1014
|
|
|
// Add registered members on the end. |
1015
|
|
|
$context['groups']['member'][0] = [ |
1016
|
|
|
'id' => 0, |
1017
|
|
|
'name' => $txt['regular_members'], |
1018
|
|
|
'desc' => $txt['regular_members_desc'], |
1019
|
|
|
'type' => 0, |
1020
|
|
|
'is_primary' => $context['primary_group'] === 0, |
1021
|
|
|
'can_be_primary' => true, |
1022
|
|
|
'can_leave' => 0, |
1023
|
|
|
]; |
1024
|
|
|
|
1025
|
|
|
// No changing primary one unless you have enough groups! |
1026
|
|
|
if (count($context['groups']['member']) < 2) |
1027
|
|
|
{ |
1028
|
|
|
$context['can_edit_primary'] = false; |
1029
|
|
|
} |
1030
|
|
|
|
1031
|
|
|
// In the special case that someone is requesting membership of a group, setup some special context vars. |
1032
|
|
|
$groupRequest = $this->_req->getQuery('request', 'intval'); |
1033
|
|
|
if (!isset($groupRequest, $context['groups']['available'][$groupRequest])) |
1034
|
|
|
{ |
1035
|
|
|
return; |
1036
|
|
|
} |
1037
|
|
|
|
1038
|
|
|
if ($context['groups']['available'][$groupRequest]['type'] !== 2) |
1039
|
|
|
{ |
1040
|
|
|
return; |
1041
|
|
|
} |
1042
|
|
|
|
1043
|
|
|
$context['group_request'] = $context['groups']['available'][$groupRequest]; |
1044
|
|
|
} |
1045
|
|
|
|
1046
|
|
|
/** |
1047
|
|
|
* This function actually makes all the group changes |
1048
|
|
|
* |
1049
|
|
|
* @return string |
1050
|
|
|
* @throws Exception no_access |
1051
|
|
|
*/ |
1052
|
|
|
public function action_groupMembership2() |
1053
|
|
|
{ |
1054
|
|
|
global $context, $modSettings, $scripturl, $language; |
1055
|
|
|
|
1056
|
|
|
// Let's be extra cautious... |
1057
|
|
|
if (!$context['user']['is_owner'] || empty($modSettings['show_group_membership'])) |
1058
|
|
|
{ |
1059
|
|
|
isAllowedTo('manage_membergroups'); |
1060
|
|
|
} |
1061
|
|
|
|
1062
|
|
|
$group_id = $this->_req->getPost('gid', 'intval', $this->_req->getQuery('gid', 'intval', null)); |
1063
|
|
|
|
1064
|
|
|
if (!isset($group_id) && !isset($this->_req->post->primary)) |
1065
|
|
|
{ |
1066
|
|
|
throw new Exception('no_access', false); |
1067
|
|
|
} |
1068
|
|
|
|
1069
|
|
|
// GID may be from a link or a form |
1070
|
|
|
checkSession(isset($this->_req->query->gid) ? 'get' : 'post'); |
1071
|
|
|
|
1072
|
|
|
require_once(SUBSDIR . '/Membergroups.subs.php'); |
1073
|
|
|
|
1074
|
|
|
$context['can_manage_membergroups'] = allowedTo('manage_membergroups'); |
1075
|
|
|
$context['can_manage_protected'] = allowedTo('admin_forum'); |
1076
|
|
|
|
1077
|
|
|
// By default, the new primary is the old one. |
1078
|
|
|
$newPrimary = (int) $this->_profile['id_group']; |
1079
|
|
|
$addGroups = array_flip(explode(',', $this->_profile['additional_groups'])); |
1080
|
|
|
$canChangePrimary = (int) $this->_profile['id_group'] === 0; |
1081
|
|
|
$changeType = isset($this->_req->post->primary) ? 'primary' : (isset($this->_req->post->req) ? 'request' : 'free'); |
1082
|
|
|
|
1083
|
|
|
// One way or another, we have a target group in mind... |
1084
|
|
|
$group_id = $group_id ?? (int) $this->_req->post->primary; |
1085
|
|
|
$foundTarget = $changeType === 'primary' && $group_id === 0; |
1086
|
|
|
|
1087
|
|
|
// Sanity check!! |
1088
|
|
|
if ($group_id === 1) |
1089
|
|
|
{ |
1090
|
|
|
isAllowedTo('admin_forum'); |
1091
|
|
|
} |
1092
|
|
|
|
1093
|
|
|
// What ever we are doing, we need to determine if changing primary is possible! |
1094
|
|
|
$groups_details = membergroupsById([$group_id, $this->_profile['id_group']], 0, true); |
1095
|
|
|
|
1096
|
|
|
// Protected groups require proper permissions! |
1097
|
|
|
if ($group_id !== 1 && $groups_details[$group_id]['group_type'] === 1) |
1098
|
|
|
{ |
1099
|
|
|
isAllowedTo('admin_forum'); |
1100
|
|
|
} |
1101
|
|
|
|
1102
|
|
|
foreach ($groups_details as $row) |
1103
|
|
|
{ |
1104
|
|
|
// Is this the new group? |
1105
|
|
|
if ($row['id_group'] === $group_id) |
1106
|
|
|
{ |
1107
|
|
|
$foundTarget = true; |
1108
|
|
|
$group_name = $row['group_name']; |
1109
|
|
|
|
1110
|
|
|
// Does the group type match what we're doing - are we trying to request a non-requestable group? |
1111
|
|
|
if ($changeType === 'request' && $row['group_type'] !== 2) |
1112
|
|
|
{ |
1113
|
|
|
throw new Exception('no_access', false); |
1114
|
|
|
} |
1115
|
|
|
|
1116
|
|
|
// What about leaving a requestable group we are not a member of? |
1117
|
|
|
if ($changeType === 'free' && $row['group_type'] === 2 && $this->_profile['id_group'] !== $row['id_group'] && !isset($addGroups[$row['id_group']])) |
1118
|
|
|
{ |
1119
|
|
|
throw new Exception('no_access', false); |
1120
|
|
|
} |
1121
|
|
|
|
1122
|
|
|
if ($changeType === 'free' && $row['group_type'] !== 3 && $row['group_type'] !== 2) |
1123
|
|
|
{ |
1124
|
|
|
throw new Exception('no_access', false); |
1125
|
|
|
} |
1126
|
|
|
|
1127
|
|
|
// We can't change the primary group if this is hidden! |
1128
|
|
|
if ((int) $row['hidden'] === 2) |
1129
|
|
|
{ |
1130
|
|
|
$canChangePrimary = false; |
1131
|
|
|
} |
1132
|
|
|
} |
1133
|
|
|
|
1134
|
|
|
// If this is their old primary, can we change it? |
1135
|
|
|
if ($row['id_group'] === $this->_profile['id_group'] && ($row['group_type'] > 1 || $context['can_manage_membergroups']) && $canChangePrimary) |
1136
|
|
|
{ |
1137
|
|
|
$canChangePrimary = true; |
1138
|
|
|
} |
1139
|
|
|
|
1140
|
|
|
// If we are not doing a force primary move, don't do it automatically if current primary is not 0. |
1141
|
|
|
if ($changeType !== 'primary' && $this->_profile['id_group'] !== 0) |
1142
|
|
|
{ |
1143
|
|
|
$canChangePrimary = false; |
1144
|
|
|
} |
1145
|
|
|
|
1146
|
|
|
// If this is the one we are acting on, can we even act? |
1147
|
|
|
if ((!$context['can_manage_protected'] && $row['group_type'] === 1) || (!$context['can_manage_membergroups'] && $row['group_type'] === 0)) |
1148
|
|
|
{ |
1149
|
|
|
$canChangePrimary = false; |
1150
|
|
|
} |
1151
|
|
|
} |
1152
|
|
|
|
1153
|
|
|
// Didn't find the target? |
1154
|
|
|
if (!$foundTarget) |
1155
|
|
|
{ |
1156
|
|
|
throw new Exception('no_access', false); |
1157
|
|
|
} |
1158
|
|
|
|
1159
|
|
|
// Final security check, don't allow users to promote themselves to admin. |
1160
|
|
|
require_once(SUBSDIR . '/ProfileOptions.subs.php'); |
1161
|
|
|
if ($context['can_manage_membergroups'] && !allowedTo('admin_forum')) |
1162
|
|
|
{ |
1163
|
|
|
$disallow = checkMembergroupChange($group_id); |
1164
|
|
|
if ($disallow) |
1165
|
|
|
{ |
1166
|
|
|
isAllowedTo('admin_forum'); |
1167
|
|
|
} |
1168
|
|
|
} |
1169
|
|
|
|
1170
|
|
|
// If we're requesting, add the note then return. |
1171
|
|
|
if ($changeType === 'request') |
1172
|
|
|
{ |
1173
|
|
|
if (logMembergroupRequest($group_id, $this->_memID)) |
1174
|
|
|
{ |
1175
|
|
|
throw new Exception('profile_error_already_requested_group'); |
1176
|
|
|
} |
1177
|
|
|
|
1178
|
|
|
// Email all group moderators etc. |
1179
|
|
|
require_once(SUBSDIR . '/Mail.subs.php'); |
1180
|
|
|
|
1181
|
|
|
// Do we have any group moderators? |
1182
|
|
|
require_once(SUBSDIR . '/Membergroups.subs.php'); |
1183
|
|
|
$moderators = array_keys(getGroupModerators($group_id)); |
1184
|
|
|
|
1185
|
|
|
// Otherwise, this is the backup! |
1186
|
|
|
if (empty($moderators)) |
1187
|
|
|
{ |
1188
|
|
|
require_once(SUBSDIR . '/Members.subs.php'); |
1189
|
|
|
$moderators = membersAllowedTo('manage_membergroups'); |
1190
|
|
|
} |
1191
|
|
|
|
1192
|
|
|
if (!empty($moderators)) |
1193
|
|
|
{ |
1194
|
|
|
require_once(SUBSDIR . '/Members.subs.php'); |
1195
|
|
|
$members = getBasicMemberData($moderators, ['preferences' => true, 'sort' => 'lngfile']); |
1196
|
|
|
|
1197
|
|
|
foreach ($members as $member) |
1198
|
|
|
{ |
1199
|
|
|
if ((int) $member['notify_types'] !== 4) |
1200
|
|
|
{ |
1201
|
|
|
continue; |
1202
|
|
|
} |
1203
|
|
|
|
1204
|
|
|
// Check whether they are interested. |
1205
|
|
|
if (!empty($member['mod_prefs'])) |
1206
|
|
|
{ |
1207
|
|
|
[, , $pref_binary] = explode('|', $member['mod_prefs']); |
1208
|
|
|
if (!($pref_binary & 4)) |
1209
|
|
|
{ |
1210
|
|
|
continue; |
1211
|
|
|
} |
1212
|
|
|
} |
1213
|
|
|
|
1214
|
|
|
$replacements = [ |
1215
|
|
|
'RECPNAME' => $member['member_name'], |
1216
|
|
|
'APPYNAME' => $this->_profile['member_name'], |
1217
|
|
|
'GROUPNAME' => $group_name, |
1218
|
|
|
'REASON' => $this->_req->post->reason, |
1219
|
|
|
'MODLINK' => $scripturl . '?action=moderate;area=groups;sa=requests', |
1220
|
|
|
]; |
1221
|
|
|
|
1222
|
|
|
$emaildata = loadEmailTemplate('request_membership', $replacements, empty($member['lngfile']) || empty($modSettings['userLanguage']) ? $language : $member['lngfile']); |
1223
|
|
|
sendmail($member['email_address'], $emaildata['subject'], $emaildata['body'], null, null, false, 2); |
1224
|
|
|
} |
1225
|
|
|
} |
1226
|
|
|
|
1227
|
|
|
return $changeType; |
1228
|
|
|
} |
1229
|
|
|
|
1230
|
|
|
// Otherwise, we are leaving/joining a group. |
1231
|
|
|
if ($changeType === 'free') |
1232
|
|
|
{ |
1233
|
|
|
// Are we leaving? |
1234
|
|
|
if ($this->_profile['id_group'] === $group_id || isset($addGroups[$group_id])) |
1235
|
|
|
{ |
1236
|
|
|
if ($this->_profile['id_group'] === $group_id) |
1237
|
|
|
{ |
1238
|
|
|
$newPrimary = 0; |
1239
|
|
|
} |
1240
|
|
|
else |
1241
|
|
|
{ |
1242
|
|
|
unset($addGroups[$group_id]); |
1243
|
|
|
} |
1244
|
|
|
} |
1245
|
|
|
// ... if not, must be joining. |
1246
|
|
|
elseif ($canChangePrimary) |
1247
|
|
|
{ |
1248
|
|
|
// Can we change the primary, and do we want to? |
1249
|
|
|
if ($this->_profile['id_group'] !== 0) |
1250
|
|
|
{ |
1251
|
|
|
$addGroups[$this->_profile['id_group']] = -1; |
1252
|
|
|
} |
1253
|
|
|
|
1254
|
|
|
$newPrimary = $group_id; |
1255
|
|
|
} |
1256
|
|
|
// Otherwise it's an additional group... |
1257
|
|
|
else |
1258
|
|
|
{ |
1259
|
|
|
$addGroups[$group_id] = -1; |
1260
|
|
|
} |
1261
|
|
|
} |
1262
|
|
|
// Finally, we must be setting the primary. |
1263
|
|
|
elseif ($canChangePrimary) |
1264
|
|
|
{ |
1265
|
|
|
if ($this->_profile['id_group'] !== 0) |
1266
|
|
|
{ |
1267
|
|
|
$addGroups[$this->_profile['id_group']] = -1; |
1268
|
|
|
} |
1269
|
|
|
|
1270
|
|
|
if (isset($addGroups[$group_id])) |
1271
|
|
|
{ |
1272
|
|
|
unset($addGroups[$group_id]); |
1273
|
|
|
} |
1274
|
|
|
|
1275
|
|
|
$newPrimary = $group_id; |
1276
|
|
|
} |
1277
|
|
|
|
1278
|
|
|
// Finally, we can make the changes! |
1279
|
|
|
foreach (array_keys($addGroups) as $id) |
1280
|
|
|
{ |
1281
|
|
|
if (empty($id)) |
1282
|
|
|
{ |
1283
|
|
|
unset($addGroups[$id]); |
1284
|
|
|
} |
1285
|
|
|
} |
1286
|
|
|
|
1287
|
|
|
$addGroups = implode(',', array_flip($addGroups)); |
1288
|
|
|
|
1289
|
|
|
// Ensure that we don't cache permissions if the group is changing. |
1290
|
|
|
if ($context['user']['is_owner']) |
1291
|
|
|
{ |
1292
|
|
|
$_SESSION['mc']['time'] = 0; |
1293
|
|
|
} |
1294
|
|
|
else |
1295
|
|
|
{ |
1296
|
|
|
updateSettings(['settings_updated' => time()]); |
1297
|
|
|
} |
1298
|
|
|
|
1299
|
|
|
require_once(SUBSDIR . '/Members.subs.php'); |
1300
|
|
|
updateMemberData($this->_memID, ['id_group' => $newPrimary, 'additional_groups' => $addGroups]); |
1301
|
|
|
|
1302
|
|
|
return $changeType; |
1303
|
|
|
} |
1304
|
|
|
} |
1305
|
|
|
|
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 theid
property of an instance of theAccount
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.