Issues (1686)

sources/ElkArte/AdminController/ManageFeatures.php (1 issue)

Labels
Severity
1
<?php
2
3
/**
4
 * Manage features and options administration page.
5
 *
6
 * @package   ElkArte Forum
7
 * @copyright ElkArte Forum contributors
8
 * @license   BSD http://opensource.org/licenses/BSD-3-Clause (see accompanying LICENSE.txt file)
9
 *
10
 * This file contains code covered by:
11
 * copyright: 2011 Simple Machines (http://www.simplemachines.org)
12
 *
13
 * @version 2.0 dev
14
 *
15
 */
16
17
namespace ElkArte\AdminController;
18
19
use BBC\ParserWrapper;
20
use DateTimeZone;
21
use ElkArte\AbstractController;
22
use ElkArte\Action;
23
use ElkArte\Exceptions\Exception;
24
use ElkArte\Helper\DataValidator;
25
use ElkArte\Helper\Util;
26
use ElkArte\Hooks;
27
use ElkArte\Languages\Txt;
28
use ElkArte\Mentions\MentionType\AbstractNotificationMessage;
29
use ElkArte\MetadataIntegrate;
30
use ElkArte\Notifications\Notifications;
31
use ElkArte\SettingsForm\SettingsForm;
32
33
/**
34
 * Manage features and options administration page.
35
 *
36
 * This controller handles the pages which allow the admin
37
 * to see and change the basic feature settings of their site.
38 2
 */
39
class ManageFeatures extends AbstractController
40
{
41 2
	/**
42 2
	 * Pre Dispatch, called before other methods.
43
	 */
44
	public function pre_dispatch()
45
	{
46
		// We need this in few places so it's easier to have it loaded here
47
		require_once(SUBSDIR . '/ManageFeatures.subs.php');
48
	}
49
50
	/**
51
	 * This function passes control through to the relevant tab.
52
	 *
53
	 * @event integrate_sa_modify_features Use to add new Configuration tabs
54
	 * @see AbstractController::action_index()
55
	 * @uses Help, ManageSettings languages
56
	 * @uses sub_template show_settings
57
	 */
58
	public function action_index()
59
	{
60
		global $context, $txt, $settings;
61
62
		// Often Helpful
63
		Txt::load('Help+ManageSettings+Mentions');
64
65
		// All the actions we know about.  These must exist in loadMenu() of the admin controller.
66
		$subActions = array(
67
			'basic' => array(
68
				'controller' => $this,
69
				'function' => 'action_basicSettings_display',
70
				'permission' => 'admin_forum'
71
			),
72
			'layout' => array(
73
				'controller' => $this,
74
				'function' => 'action_layoutSettings_display',
75
				'permission' => 'admin_forum'
76
			),
77
			'pwa' => array(
78
				'controller' => $this,
79
				'function' => 'action_pwaSettings_display',
80
				'enabled' => true,
81
				'permission' => 'admin_forum'
82
			),
83
			'karma' => array(
84
				'controller' => $this,
85
				'function' => 'action_karmaSettings_display',
86
				'enabled' => featureEnabled('k'),
87
				'permission' => 'admin_forum'
88
			),
89
			'pmsettings' => array(
90
				'controller' => $this,
91
				'function' => 'action_pmsettings',
92
				'permission' => 'admin_forum'
93
			),
94
			'likes' => array(
95
				'controller' => $this,
96
				'function' => 'action_likesSettings_display',
97
				'enabled' => featureEnabled('l'),
98
				'permission' => 'admin_forum'
99
			),
100
			'mention' => array(
101
				'controller' => $this,
102
				'function' => 'action_notificationsSettings_display',
103
				'permission' => 'admin_forum'
104
			),
105
			'sig' => array(
106
				'controller' => $this,
107
				'function' => 'action_signatureSettings_display',
108
				'permission' => 'admin_forum'
109
			),
110
			'profile' => array(
111
				'controller' => $this,
112
				'function' => 'action_profile',
113
				'enabled' => featureEnabled('cp'),
114
				'permission' => 'admin_forum'
115
			),
116
			'profileedit' => array(
117
				'controller' => $this,
118
				'function' => 'action_profileedit',
119
				'permission' => 'admin_forum'
120
			),
121
		);
122
123
		// Set up the action control
124
		$action = new Action('modify_features');
125
126
		// By default, do the basic settings, call integrate_sa_modify_features
127
		$subAction = $action->initialize($subActions, 'basic');
128
129
		// Some final pieces for the template
130
		$context['sub_template'] = 'show_settings';
131
		$context['sub_action'] = $subAction;
132
		$context['page_title'] = $txt['modSettings_title'];
133
134
		// Load up all the tabs...
135
		$context[$context['admin_menu_name']]['object']->prepareTabData([
136
			'title' => 'modSettings_title',
137
			'help' => 'featuresettings',
138
			'description' => sprintf($txt['modSettings_desc'], getUrl('admin', ['action' => 'admin', 'area' => 'theme', 'sa' => 'list', 'th' => $settings['theme_id'], '{session_data}'])),
139
			// All valid $subActions will be added, here you just specify any special tab data
140
			'tabs' => [
141
				'mention' => [
142
					'description' => $txt['mentions_settings_desc'],
143
				],
144
				'sig' => [
145
					'description' => $txt['signature_settings_desc'],
146
				],
147
				'profile' => [
148
					'description' => $txt['custom_profile_desc'],
149
				],
150
				'pwa' => [
151
					'description' => $txt['pwa_settings_desc'],
152
				],
153
			],
154
		]);
155
156
		// Call the right function for this sub-action.
157
		$action->dispatch($subAction);
158
	}
159
160
	/**
161
	 * Config array for changing the basic forum settings
162
	 *
163
	 * - Accessed from ?action=admin;area=featuresettings;sa=basic;
164
	 *
165
	 * @event integrate_save_basic_settings
166
	 */
167
	public function action_basicSettings_display()
168
	{
169
		global $txt, $context, $modSettings;
170
171
		// Initialize the form
172
		$settingsForm = new SettingsForm(SettingsForm::DB_ADAPTER);
173
174
		// Initialize it with our settings
175
		$settingsForm->setConfigVars($this->_basicSettings());
176
177
		theme()->addJavascriptVar(['txt_invalid_response' => $txt['ajax_bad_response']], true);
178
179
		// Saving?
180
		if (isset($this->_req->query->save))
181
		{
182
			checkSession();
183
184
			// Prevent absurd boundaries here - make it a day tops.
185
			if (isset($this->_req->post->lastActive))
186
			{
187
				$this->_req->post->lastActive = min((int) $this->_req->post->lastActive, 1440);
188
			}
189
190
			call_integration_hook('integrate_save_basic_settings');
191
192
			// Microdata needs to enable its integration
193
			if ($this->_req->isSet('metadata_enabled'))
194
			{
195
				Hooks::instance()->enableIntegration(MetadataIntegrate::class);
196
			}
197
			else
198
			{
199
				Hooks::instance()->disableIntegration(MetadataIntegrate::class);
200
			}
201
202
			// If they have changed Hive settings, lets clear them to avoid issues
203
			if (empty($modSettings['minify_css_js']) !== empty($this->_req->post->minify_css_js))
204
			{
205
				theme()->cleanHives();
206
			}
207
208
			$settingsForm->setConfigValues((array) $this->_req->post);
209
			$settingsForm->save();
210
211
			writeLog();
212
			redirectexit('action=admin;area=featuresettings;sa=basic');
213
		}
214
215
		if (isset($this->_req->post->cleanhives) && $this->getApi() === 'json')
216
		{
217
			$clean_hives_result = theme()->cleanHives();
218 2
219
			setJsonTemplate();
220 2
			$context['json_data'] = array(
221
				'success' => $clean_hives_result,
222
				'response' => $clean_hives_result ? $txt['clean_hives_sucess'] : $txt['clean_hives_failed']
223
			);
224 2
225
			return;
226
		}
227
228
		$context['post_url'] = getUrl('admin', ['action' => 'admin', 'area' => 'featuresettings', 'sa' => 'basic', 'save']);
229 2
		$context['settings_title'] = $txt['mods_cat_features'];
230
231 2
		$settingsForm->prepare();
232
	}
233 2
234
	/**
235 2
	 * Return basic feature settings.
236 2
	 *
237 2
	 * @event integrate_modify_basic_settings Adds to General features and Options
238
	 */
239
	private function _basicSettings()
240 2
	{
241
		global $txt;
242 2
243
		$config_vars = array(
244
			// Basic stuff, titles, permissions...
245 2
			array('check', 'allow_guestAccess'),
246 2
			array('check', 'enable_buddylist'),
247
			array('check', 'allow_editDisplayName'),
248
			array('check', 'allow_hideOnline'),
249
			array('check', 'titlesEnable'),
250 2
			'',
251
			// Javascript and CSS options
252
			array('select', 'jquery_source', array('auto' => $txt['jquery_auto'], 'local' => $txt['jquery_local'], 'cdn' => $txt['jquery_cdn'])),
253
			array('check', 'minify_css_js', 'postinput' => '<a href="#" id="clean_hives" class="linkbutton">' . $txt['clean_hives'] . '</a>'),
254 2
			'',
255
			// Number formatting, timezones.
256
			array('text', 'time_format'),
257
			array('float', 'time_offset', 'subtext' => $txt['setting_time_offset_note'], 6, 'postinput' => $txt['hours']),
258 2
			'default_timezone' => array('select', 'default_timezone', array()),
259 2
			'',
260
			// Who's online?
261
			array('check', 'who_enabled'),
262
			array('int', 'lastActive', 6, 'postinput' => $txt['minutes']),
263
			'',
264
			// Statistics.
265
			array('check', 'trackStats'),
266 2
			array('check', 'hitStats'),
267
			'',
268 2
			// Option-ish things... miscellaneous sorta.
269
			array('check', 'metadata_enabled'),
270
			array('check', 'allow_disableAnnounce'),
271
			array('check', 'disallow_sendBody'),
272 2
			array('select', 'enable_contactform', array('disabled' => $txt['contact_form_disabled'], 'registration' => $txt['contact_form_registration'], 'menu' => $txt['contact_form_menu'])),
273
		);
274 2
275
		// Get all the time zones.
276
		$all_zones = DateTimeZone::listIdentifiers();
277
		if (empty($all_zones))
278
		{
279
			unset($config_vars['default_timezone']);
280
		}
281
		else
282
		{
283
			// Make sure we set the value to the same as the printed value.
284
			foreach ($all_zones as $zone)
285
			{
286
				$config_vars['default_timezone'][2][$zone] = $zone;
287
			}
288
		}
289
290
		theme()->addInlineJavascript('
291
			document.getElementById("clean_hives").addEventListener("click", function(event) {return cleanHives(event);});', ['defer' => true]);
0 ignored issues
show
array('defer' => true) of type array<string,true> is incompatible with the type boolean expected by parameter $defer of ElkArte\Themes\Theme::addInlineJavascript(). ( Ignorable by Annotation )

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

291
			document.getElementById("clean_hives").addEventListener("click", function(event) {return cleanHives(event);});', /** @scrutinizer ignore-type */ ['defer' => true]);
Loading history...
292
293
		call_integration_hook('integrate_modify_basic_settings', array(&$config_vars));
294
295
		return $config_vars;
296
	}
297
298
	/**
299
	 * Allows modifying the global layout settings in the forum
300
	 *
301
	 * - Accessed through ?action=admin;area=featuresettings;sa=layout;
302
	 *
303
	 * @event integrate_save_layout_settings
304
	 */
305
	public function action_layoutSettings_display()
306
	{
307
		global $txt, $context, $modSettings;
308
309
		// Initialize the form
310
		$settingsForm = new SettingsForm(SettingsForm::DB_ADAPTER);
311
312
		// Initialize it with our settings
313
		$settingsForm->setConfigVars($this->_layoutSettings());
314
315
		// Saving?
316
		if (isset($this->_req->query->save))
317
		{
318
			// Setting a custom frontpage, set the hook to the FrontpageInterface of the controller
319
			if (!empty($this->_req->post->front_page))
320
			{
321
				// Addons may have left this blank
322
				$modSettings['front_page'] = empty($modSettings['front_page']) ? 'MessageIndex_Controller' : $modSettings['front_page'];
323
324
				$front_page = (string) $this->_req->post->front_page;
325
				if (
326
					class_exists($modSettings['front_page'])
327
					&& in_array('validateFrontPageOptions', get_class_methods($modSettings['front_page']))
328
					&& !$front_page::validateFrontPageOptions($this->_req->post)
329
				)
330 2
				{
331
					$this->_req->post->front_page = '';
332 2
				}
333
			}
334 2
335 2
			checkSession();
336
337
			call_integration_hook('integrate_save_layout_settings');
338 2
339
			$settingsForm->setConfigValues((array) $this->_req->post);
340
			$settingsForm->save();
341 2
			writeLog();
342
343 2
			redirectexit('action=admin;area=featuresettings;sa=layout');
344
		}
345
346 2
		$context['post_url'] = getUrl('admin', ['action' => 'admin', 'area' => 'featuresettings', 'sa' => 'layout', 'save']);
347
		$context['settings_title'] = $txt['mods_cat_layout'];
348 2
349 2
		$settingsForm->prepare();
350 2
	}
351
352
	/**
353
	 * Return layout settings.
354
	 *
355 2
	 * @event integrate_modify_layout_settings Adds options to Configuration->Layout
356
	 */
357 2
	private function _layoutSettings()
358
	{
359
		global $txt;
360
361
		$config_vars = array_merge(getFrontPageControllers(), array(
362
			'',
363
			// Pagination stuff.
364
			array('check', 'compactTopicPagesEnable'),
365
			array('int', 'compactTopicPagesContiguous', 'subtext' => str_replace(' ', '&nbsp;', '"3" ' . $txt['to_display'] . ': <strong>1 ... 4 [5] 6 ... 9</strong>') . '<br />' . str_replace(' ', '&nbsp;', '"5" ' . $txt['to_display'] . ': <strong>1 ... 3 4 [5] 6 7 ... 9</strong>')),
366
			array('int', 'defaultMaxMembers'),
367
			array('check', 'displayMemberNames'),
368
			'',
369
			// Stuff that just is everywhere - today, search, online, etc.
370
			array('select', 'todayMod', array($txt['today_disabled'], $txt['today_only'], $txt['yesterday_today'], $txt['relative_time'])),
371
			array('check', 'onlineEnable'),
372
			array('check', 'enableVBStyleLogin'),
373
			'',
374
			// Automagic image resizing.
375
			array('int', 'max_image_width', 'subtext' => $txt['zero_for_no_limit']),
376
			array('int', 'max_image_height', 'subtext' => $txt['zero_for_no_limit']),
377
			'',
378
			// This is like debugging sorta.
379
			array('check', 'timeLoadPageEnable'),
380
		));
381
382
		call_integration_hook('integrate_modify_layout_settings', array(&$config_vars));
383
384
		return $config_vars;
385
	}
386
387
	/**
388
	 * Display configuration settings page for progressive web application settings.
389
	 *
390
	 * - Accessed from ?action=admin;area=featuresettings;sa=pwa;
391
	 *
392
	 * @event integrate_save_pwa_settings
393
	 */
394
	public function action_pwaSettings_display()
395
	{
396
		global $txt, $context;
397
398
		// Initialize the form
399
		$settingsForm = new SettingsForm(SettingsForm::DB_ADAPTER);
400 2
401
		// Initialize it with our settings
402 2
		$settingsForm->setConfigVars($this->_pwaSettings());
403
404
		// Saving, lots of checks then
405
		if (isset($this->_req->query->save))
406 2
		{
407 2
			checkSession();
408
409 2
			call_integration_hook('integrate_save_pwa_settings');
410 2
411
			// Don't allow it to be enabled if we don't have SSL
412
			$canUse = detectServer()->supportsSSL();
413 2
			if (!$canUse)
414
			{
415
				$this->_req->post->pwa_enabled = 0;
416
			}
417
418
			// And you must enable this if PWA is enabled
419
			if ($this->_req->getPost('pwa_enabled', 'intval') === 1)
420 2
			{
421
				$this->_req->post->pwa_manifest_enabled = 1;
422 2
			}
423
424
			$validator = new DataValidator();
425
			$validation_rules = [
426
				'pwa_theme_color' => 'valid_color',
427
				'pwa_background_color' => 'valid_color',
428
				'pwa_short_name' => 'max_length[12]'
429
			];
430
431
			// Only check the rest if they entered something.
432
			$valid_urls = ['pwa_small_icon', 'pwa_large_icon', 'favicon_icon', 'apple_touch_icon'];
433
			foreach ($valid_urls as $url)
434
			{
435
				if ($this->_req->getPost($url, 'trim') !== '')
436
				{
437
					$validation_rules[$url] = 'valid_url';
438
				}
439
			}
440
			$validator->validation_rules($validation_rules);
441
442
			if (!$validator->validate($this->_req->post))
443
			{
444
				// Some input error, lets tell them what is wrong
445
				$context['error_type'] = 'minor';
446
				$context['settings_message'] = [];
447
				foreach ($validator->validation_errors() as $error)
448
				{
449
					$context['settings_message'][] = $error;
450
				}
451
			}
452
			else
453
			{
454
				$settingsForm->setConfigValues((array) $this->_req->post);
455
				$settingsForm->save();
456
				redirectexit('action=admin;area=featuresettings;sa=pwa');
457
			}
458
		}
459
460
		$context['post_url'] = getUrl('admin', ['action' => 'admin', 'area' => 'featuresettings', 'sa' => 'pwa', 'save']);
461
		$context['settings_title'] = $txt['pwa_settings'];
462
		theme()->addInlineJavascript('
463
			pwaPreview("pwa_small_icon");
464
			pwaPreview("pwa_large_icon");
465 2
			pwaPreview("favicon_icon");
466
			pwaPreview("apple_touch_icon");', true);
467 2
468
		$settingsForm->prepare();
469
	}
470
471 2
	/**
472 2
	 * Return PWA settings.
473
	 *
474 2
	 * @event integrate_modify_karma_settings Adds to Configuration->Pwa
475 2
	 */
476
	private function _pwaSettings()
477
	{
478
		global $txt;
479 2
480
		// PWA requires SSL
481
		$canUse = detectServer()->supportsSSL();
482
483 2
		$config_vars = array(
484
			// PWA - On or off?
485 2
			array('check', 'pwa_enabled', 'disabled' => !$canUse, 'invalid' => !$canUse, 'postinput' => !$canUse ? $txt['pwa_disabled'] : ''),
486
			'',
487
			array('check', 'pwa_manifest_enabled', 'helptext' => $txt['pwa_manifest_enabled_desc']),
488
			array('text', 'pwa_short_name', 12, 'mask' => 'nohtml', 'helptext' => $txt['pwa_short_name_desc'], 'maxlength' => 12),
489
			array('color', 'pwa_theme_color', 'helptext' => $txt['pwa_theme_color_desc']),
490
			array('color', 'pwa_background_color', 'helptext' => $txt['pwa_background_color_desc']),
491
			'',
492
			array('url', 'pwa_small_icon', 'size' => 40, 'helptext' => $txt['pwa_small_icon_desc'], 'onchange' => "pwaPreview('pwa_small_icon');"),
493
			array('url', 'pwa_large_icon', 'size' => 40, 'helptext' => $txt['pwa_large_icon_desc'], 'onchange' => "pwaPreview('pwa_large_icon');"),
494
			array('title', 'other_icons_title'),
495
			array('url', 'favicon_icon', 'size' => 40, 'helptext' => $txt['favicon_icon_desc'], 'onchange' => "pwaPreview('favicon_icon');"),
496
			array('url', 'apple_touch_icon', 'size' => 40, 'helptext' => $txt['apple_touch_icon_desc'], 'onchange' => "pwaPreview('apple_touch_icon');"),
497
		);
498
499
		call_integration_hook('integrate_modify_pwa_settings', array(&$config_vars));
500
501
		return $config_vars;
502
	}
503
504
	/**
505
	 * Display configuration settings page for karma settings.
506
	 *
507
	 * - Accessed from ?action=admin;area=featuresettings;sa=karma;
508
	 *
509
	 * @event integrate_save_karma_settings
510
	 */
511
	public function action_karmaSettings_display()
512
	{
513
		global $txt, $context;
514
515
		// Initialize the form
516
		$settingsForm = new SettingsForm(SettingsForm::DB_ADAPTER);
517
518
		// Initialize it with our settings
519
		$settingsForm->setConfigVars($this->_karmaSettings());
520
521
		// Saving?
522
		if (isset($this->_req->query->save))
523
		{
524
			checkSession();
525
526
			call_integration_hook('integrate_save_karma_settings');
527
528
			$settingsForm->setConfigValues((array) $this->_req->post);
529
			$settingsForm->save();
530
			redirectexit('action=admin;area=featuresettings;sa=karma');
531
		}
532
533
		$context['post_url'] = getUrl('admin', ['action' => 'admin', 'area' => 'featuresettings', 'sa' => 'karma', 'save']);
534
		$context['settings_title'] = $txt['karma'];
535
536
		$settingsForm->prepare();
537
	}
538
539
	/**
540
	 * Return karma settings.
541
	 *
542
	 * @event integrate_modify_karma_settings Adds to Configuration->Karma
543
	 */
544
	private function _karmaSettings()
545
	{
546
		global $txt;
547
548
		$config_vars = array(
549
			// Karma - On or off?
550
			array('select', 'karmaMode', explode('|', $txt['karma_options'])),
551
			'',
552
			// Who can do it.... and who is restricted by time limits?
553
			array('int', 'karmaMinPosts', 6, 'postinput' => $txt['manageposts_posts']),
554
			array('float', 'karmaWaitTime', 6, 'postinput' => $txt['hours']),
555
			array('check', 'karmaTimeRestrictAdmins'),
556
			array('check', 'karmaDisableSmite'),
557
			'',
558
			// What does it look like?  [smite]?
559
			array('text', 'karmaLabel'),
560
			array('text', 'karmaApplaudLabel', 'mask' => 'nohtml'),
561
			array('text', 'karmaSmiteLabel', 'mask' => 'nohtml'),
562
		);
563
564
		call_integration_hook('integrate_modify_karma_settings', array(&$config_vars));
565
566
		return $config_vars;
567
	}
568
569
	/**
570
	 * Display configuration settings page for likes settings.
571
	 *
572
	 * - Accessed from ?action=admin;area=featuresettings;sa=likes;
573
	 *
574
	 * @event integrate_save_likes_settings
575
	 */
576
	public function action_likesSettings_display()
577
	{
578
		global $txt, $context;
579
580
		// Initialize the form
581
		$settingsForm = new SettingsForm(SettingsForm::DB_ADAPTER);
582
583
		// Initialize it with our settings
584
		$settingsForm->setConfigVars($this->_likesSettings());
585
586
		// Saving?
587
		if (isset($this->_req->query->save))
588
		{
589
			checkSession();
590
591
			call_integration_hook('integrate_save_likes_settings');
592
593
			$settingsForm->setConfigValues((array) $this->_req->post);
594
			$settingsForm->save();
595
			redirectexit('action=admin;area=featuresettings;sa=likes');
596
		}
597
598
		$context['post_url'] = getUrl('admin', ['action' => 'admin', 'area' => 'featuresettings', 'sa' => 'likes', 'save']);
599
		$context['settings_title'] = $txt['likes'];
600
601
		$settingsForm->prepare();
602
	}
603
604
	/**
605 2
	 * Return likes settings.
606
	 *
607 2
	 * @event integrate_modify_likes_settings Adds to Configuration->Likes
608
	 */
609 2
	private function _likesSettings()
610 2
	{
611
		global $txt;
612
613
		$config_vars = array(
614 2
			// Likes - On or off?
615
			array('check', 'likes_enabled'),
616
			'',
617
			// Who can do it.... and who is restricted by count limits?
618 2
			array('int', 'likeMinPosts', 6, 'postinput' => $txt['manageposts_posts']),
619 2
			array('int', 'likeWaitTime', 6, 'postinput' => $txt['minutes']),
620 2
			array('int', 'likeWaitCount', 6),
621
			array('check', 'likeRestrictAdmins'),
622 2
			array('check', 'likeAllowSelf'),
623
			array('check', 'useLikesNotViews'),
624
			'',
625
			array('int', 'likeDisplayLimit', 6)
626
		);
627
628
		call_integration_hook('integrate_modify_likes_settings', array(&$config_vars));
629
630
		return $config_vars;
631
	}
632
633
	/**
634
	 * Initializes the mentions settings admin page.
635 2
	 *
636
	 * - Accessed from ?action=admin;area=featuresettings;sa=mention;
637 2
	 *
638
	 * @event integrate_save_modify_mention_settings
639
	 */
640
	public function action_notificationsSettings_display()
641
	{
642
		global $txt, $context, $modSettings;
643
644
		Txt::load('Mentions');
645
646
		// Instantiate the form
647
		$settingsForm = new SettingsForm(SettingsForm::DB_ADAPTER);
648
649
		// Initialize it with our settings
650
		$settingsForm->setConfigVars($this->_notificationsSettings());
651
652
		// Some context stuff
653
		$context['page_title'] = $txt['mentions_settings'];
654
		$context['sub_template'] = 'show_settings';
655
656
		// Saving the settings?
657
		if (isset($this->_req->query->save))
658
		{
659
			checkSession();
660
661
			call_integration_hook('integrate_save_modify_mention_settings');
662
663
			if (!empty($this->_req->post->mentions_enabled))
664
			{
665
				enableModules('mentions', array('post', 'display'));
666
			}
667
			else
668
			{
669
				disableModules('mentions', array('post', 'display'));
670
			}
671
672
			if (!empty($modSettings['hidden_notification_methods']))
673
			{
674
				foreach ($modSettings['hidden_notification_methods'] as $class)
675
				{
676
					$this->_req->post->notifications[$class::getType()] = $class::getSettings();
677
				}
678
			}
679
680
			if (empty($this->_req->post->notifications))
681
			{
682
				$notification_methods = serialize(array());
683
			}
684
			else
685
			{
686
				$notification_methods = [];
687
				foreach ($this->_req->post->notifications as $type => $notification)
688
				{
689
					if (!empty($notification['enable']))
690
					{
691
						$defaults = $notification['default'] ?? [];
692
						unset($notification['enable'], $notification['default']);
693
						foreach ($notification as $k => $v)
694
						{
695
							$notification[$k] = in_array($k, $defaults) ? Notifications::DEFAULT_LEVEL : $v;
696
						}
697
698
						$notification_methods[$type] = $notification;
699
					}
700
				}
701
702
				$notification_methods = serialize($notification_methods);
703
			}
704
705
			require_once(SUBSDIR . '/Mentions.subs.php');
706
			$enabled_mentions = array();
707
			$current_settings = Util::unserialize($modSettings['notification_methods']);
708
709
			// Fist hide what was visible
710
			$modules_toggle = array('enable' => array(), 'disable' => array());
711
			foreach ($current_settings as $type => $val)
712
			{
713
				if (!isset($this->_req->post->notifications[$type]))
714
				{
715
					toggleMentionsVisibility($type, false);
716
					$modules_toggle['disable'][] = $type;
717
				}
718
			}
719
720
			// Then make visible what was hidden, but only if there is anything
721
			if (!empty($this->_req->post->notifications))
722
			{
723
				foreach ($this->_req->post->notifications as $type => $val)
724
				{
725
					if (!isset($current_settings[$type]))
726
					{
727
						toggleMentionsVisibility($type, true);
728
						$modules_toggle['enable'][] = $type;
729
					}
730
				}
731
732
				$enabled_mentions = array_keys($this->_req->post->notifications);
733
			}
734
735
			// Let's just keep it active, there are too many reasons it should be.
736
			require_once(SUBSDIR . '/ScheduledTasks.subs.php');
737
			toggleTaskStatusByName('user_access_mentions', true);
738
739
			// Disable or enable modules as needed
740
			foreach ($modules_toggle as $action => $toggles)
741
			{
742
				if (!empty($toggles))
743
				{
744
					// The modules associated with the notification (mentionmem, likes, etc) area
745
					$modules = getMentionsModules($toggles);
746
747
					// The action will either be enable to disable
748
					$function = $action . 'Modules';
749
750
					// Something like enableModule('mentions', array('post', 'display');
751
					foreach ($modules as $key => $val)
752
					{
753
						$function($key, $val);
754
					}
755
				}
756
			}
757
758
			updateSettings(array('enabled_mentions' => implode(',', array_unique($enabled_mentions)), 'notification_methods' => $notification_methods));
759
			$settingsForm->setConfigValues((array) $this->_req->post);
760
			$settingsForm->save();
761
			redirectexit('action=admin;area=featuresettings;sa=mention');
762
		}
763
764
		// Prepare the settings for display
765
		$context['post_url'] = getUrl('admin', ['action' => 'admin', 'area' => 'featuresettings', 'sa' => 'mention', 'save']);
766
		$settingsForm->prepare();
767
	}
768
769
	/**
770
	 * Return mentions settings.
771 2
	 *
772
	 * @event integrate_modify_mention_settings Adds to Configuration->Mentions
773 2
	 */
774
	private function _notificationsSettings()
775
	{
776
		global $txt, $modSettings;
777 2
778 2
		Txt::load('Profile+UserNotifications');
779
		loadJavascriptFile('ext/jquery.multiselect.min.js');
780 2
		theme()->addInlineJavascript('
781 2
			$(\'.select_multiple\').multiselect({\'language_strings\': {\'Select all\': ' . JavaScriptEscape($txt['notify_select_all']) . '}});
782 2
			document.addEventListener("DOMContentLoaded", function() {
783
                 prepareNotificationOptions();
784 2
			});', true);
785 2
		loadCSSFile('multiselect.css');
786
787 2
		// The mentions settings
788 2
		$config_vars = array(
789 2
			array('title', 'mentions_settings'),
790
			array('check', 'mentions_enabled'),
791
		);
792 2
793
		$notification_methods = Notifications::instance()->getNotifiers();
794 2
		$notification_classes = getAvailableNotifications();
795 2
		$current_settings = unserialize($modSettings['notification_methods'], ['allowed_classes' => false]);
796 2
797
		foreach ($notification_classes as $class)
798
		{
799 2
			// The canUse can be set by each notifier based on conditions, default is true;
800
			/* @var $class AbstractNotificationMessage */
801 2
			if ($class::canUse() === false)
802 2
			{
803 2
				continue;
804 2
			}
805
806
			if ($class::hasHiddenInterface() === true)
807
			{
808 2
				$modSettings['hidden_notification_methods'][] = $class;
809
				continue;
810 2
			}
811
812
			// Set up config enable/disable setting for all notifications.
813
			$title = strtolower($class::getType());
814
			$config_vars[] = array('title', 'setting_' . $title);
815
			$config_vars[] = array('check', 'notifications[' . $title . '][enable]', 'text_label' => $txt['setting_notify_enable_this']);
816
			$modSettings['notifications[' . $title . '][enable]'] = !empty($current_settings[$title]);
817
			$default_values = [];
818
			$is_default = [];
819
820
			// If its enabled, show all the available ways, like email, notify, weekly ...
821
			foreach (array_keys($notification_methods) as $method_name)
822
			{
823
				$method_name = strtolower($method_name);
824
825
				// Are they excluding any, like don't let mailfail be allowed to send email !
826
				if ($class::isNotAllowed($method_name))
827
				{
828
					continue;
829
				}
830
831
				$config_vars[] = array('check', 'notifications[' . $title . '][' . $method_name . ']', 'text_label' => $txt['notify_' . $method_name]);
832
				$modSettings['notifications[' . $title . '][' . $method_name . ']'] = !empty($current_settings[$title][$method_name]);
833
				$default_values[] = [$method_name, $txt['notify_' . $method_name]];
834
				if (empty($current_settings[$title][$method_name]))
835
				{
836
					continue;
837
				}
838
839
				if ((int) $current_settings[$title][$method_name] !== Notifications::DEFAULT_LEVEL)
840
				{
841
					continue;
842
				}
843
844
				$is_default[] = $method_name;
845
			}
846
847
			$config_vars[] = array('select', 'notifications[' . $title . '][default]', $default_values, 'text_label' => $txt['default_active'], 'multiple' => true, 'value' => $is_default);
848
			$modSettings['notifications[' . $title . '][default]'] = $is_default;
849
		}
850
851
		call_integration_hook('integrate_modify_mention_settings', array(&$config_vars));
852
853
		return $config_vars;
854
	}
855
856
	/**
857
	 * Display configuration settings for signatures on forum.
858
	 *
859
	 * - Accessed from ?action=admin;area=featuresettings;sa=sig;
860
	 *
861
	 * @event integrate_save_signature_settings
862
	 */
863
	public function action_signatureSettings_display()
864
	{
865
		global $context, $txt, $modSettings;
866
867
		// Initialize the form
868
		$settingsForm = new SettingsForm(SettingsForm::DB_ADAPTER);
869
870
		// Initialize it with our settings
871
		$settingsForm->setConfigVars($this->_signatureSettings());
872
873
		// Setup the template.
874
		$context['page_title'] = $txt['signature_settings'];
875
		$context['sub_template'] = 'show_settings';
876
877
		// Disable the max smileys option if we don't allow smileys at all!
878
		theme()->addInlineJavascript('
879
			document.getElementById(\'signature_max_smileys\').disabled = !document.getElementById(\'signature_allow_smileys\').checked;', true);
880
881
		// Load all the signature settings.
882
		[$sig_limits, $sig_bbc] = explode(':', $modSettings['signature_settings']);
883
		$sig_limits = explode(',', $sig_limits);
884
		$disabledTags = empty($sig_bbc) ? array() : explode(',', $sig_bbc);
885
886
		// @todo temporary since it does not work, and seriously why would you do this?
887
		$disabledTags[] = 'footnote';
888
889
		// Applying to ALL signatures?!!
890
		if (isset($this->_req->query->apply))
891
		{
892
			// Security!
893
			checkSession('get');
894
895
			// This is horrid - but I suppose some people will want the option to do it.
896
			$applied_sigs = $this->_req->getQuery('step', 'intval', 0);
897
			updateAllSignatures($applied_sigs);
898
899
			$settings_applied = true;
900
		}
901
902
		$context['signature_settings'] = array(
903
			'enable' => $sig_limits[0] ?? 0,
904
			'max_length' => $sig_limits[1] ?? 0,
905
			'max_lines' => $sig_limits[2] ?? 0,
906
			'max_images' => $sig_limits[3] ?? 0,
907
			'allow_smileys' => isset($sig_limits[4]) && $sig_limits[4] == -1 ? 0 : 1,
908
			'max_smileys' => isset($sig_limits[4]) && $sig_limits[4] != -1 ? $sig_limits[4] : 0,
909
			'max_image_width' => $sig_limits[5] ?? 0,
910
			'max_image_height' => $sig_limits[6] ?? 0,
911
			'max_font_size' => $sig_limits[7] ?? 0,
912
			'repetition_guests' => $sig_limits[8] ?? 0,
913
			'repetition_members' => $sig_limits[9] ?? 0,
914
		);
915
916
		// Temporarily make each setting a modSetting!
917
		foreach ($context['signature_settings'] as $key => $value)
918
		{
919
			$modSettings['signature_' . $key] = $value;
920
		}
921
922
		// Make sure we check the right tags!
923
		$modSettings['bbc_disabled_signature_bbc'] = $disabledTags;
924
925
		// Saving?
926
		if (isset($this->_req->query->save))
927
		{
928
			checkSession();
929
930
			// Clean up the tag stuff!
931
			$codes = ParserWrapper::instance()->getCodes();
932
			$bbcTags = $codes->getTags();
933
934
			$signature_bbc_enabledTags = $this->_req->getPost('signature_bbc_enabledTags', null, []);
935
			if (!is_array($signature_bbc_enabledTags))
936
			{
937
				$signature_bbc_enabledTags = array($signature_bbc_enabledTags);
938
			}
939
940
			$this->_req->post->signature_bbc_enabledTags = $signature_bbc_enabledTags;
941
942
			$sig_limits = array();
943
			foreach (array_keys($context['signature_settings']) as $key)
944
			{
945
				if ($key === 'allow_smileys')
946
				{
947
					continue;
948
				}
949
				if ($key === 'max_smileys' && empty($this->_req->post->signature_allow_smileys))
950
				{
951
					$sig_limits[] = -1;
952
				}
953
				else
954
				{
955
					$current_key = $this->_req->getPost('signature_' . $key, 'intval');
956
					$sig_limits[] = empty($current_key) ? 0 : max(1, $current_key);
957
				}
958
			}
959
960
			call_integration_hook('integrate_save_signature_settings', array(&$sig_limits, &$bbcTags));
961
962
			$this->_req->post->signature_settings = implode(',', $sig_limits) . ':' . implode(',', array_diff($bbcTags, $this->_req->post->signature_bbc_enabledTags));
963
964
			// Even though we have practically no settings let's keep the convention going!
965
			$save_vars = array();
966
			$save_vars[] = array('text', 'signature_settings');
967
968
			$settingsForm->setConfigVars($save_vars);
969
			$settingsForm->setConfigValues((array) $this->_req->post);
970
			$settingsForm->save();
971
			redirectexit('action=admin;area=featuresettings;sa=sig');
972
		}
973
974
		$context['post_url'] = getUrl('admin', ['action' => 'admin', 'area' => 'featuresettings', 'sa' => 'sig', 'save']);
975
		$context['settings_title'] = $txt['signature_settings'];
976
		$context['settings_message'] = empty($settings_applied) ? sprintf($txt['signature_settings_warning'], getUrl('admin', ['action' => 'admin', 'area' => 'featuresettings', 'sa' => 'sig', 'apply', '{session_data}'])) : $txt['signature_settings_applied'];
977
978
		$settingsForm->prepare();
979
	}
980
981
	/**
982
	 * Return signature settings.
983
	 *
984
	 * - Used in admin center search and settings form
985
	 *
986
	 * @event integrate_modify_signature_settings Adds options to Signature Settings
987
	 */
988
	private function _signatureSettings()
989
	{
990
		global $txt;
991
992
		$config_vars = array(
993
			// Are signatures even enabled?
994
			array('check', 'signature_enable'),
995
			'',
996
			// Tweaking settings!
997
			array('int', 'signature_max_length', 'subtext' => $txt['zero_for_no_limit']),
998
			array('int', 'signature_max_lines', 'subtext' => $txt['zero_for_no_limit']),
999
			array('int', 'signature_max_font_size', 'subtext' => $txt['zero_for_no_limit']),
1000
			array('check', 'signature_allow_smileys', 'onclick' => "document.getElementById('signature_max_smileys').disabled = !this.checked;"),
1001
			array('int', 'signature_max_smileys', 'subtext' => $txt['zero_for_no_limit']),
1002
			array('select', 'signature_repetition_guests',
1003
				array(
1004
					$txt['signature_always'],
1005
					$txt['signature_onlyfirst'],
1006
					$txt['signature_never'],
1007
				),
1008
			),
1009
			array('select', 'signature_repetition_members',
1010
				array(
1011
					$txt['signature_always'],
1012
					$txt['signature_onlyfirst'],
1013
					$txt['signature_never'],
1014
				),
1015
			),
1016
			'',
1017
			// Image settings.
1018
			array('int', 'signature_max_images', 'subtext' => $txt['signature_max_images_note']),
1019
			array('int', 'signature_max_image_width', 'subtext' => $txt['zero_for_no_limit']),
1020
			array('int', 'signature_max_image_height', 'subtext' => $txt['zero_for_no_limit']),
1021
			'',
1022
			array('bbc', 'signature_bbc'),
1023
		);
1024
1025
		call_integration_hook('integrate_modify_signature_settings', array(&$config_vars));
1026
1027
		return $config_vars;
1028
	}
1029
1030
	/**
1031
	 * Show all the custom profile fields available to the user.
1032
	 *
1033
	 * - Allows for drag/drop sorting of custom profile fields
1034
	 * - Accessed with ?action=admin;area=featuresettings;sa=profile
1035
	 *
1036
	 * @uses sub template show_custom_profile
1037
	 */
1038
	public function action_profile()
1039
	{
1040
		global $txt, $context;
1041
1042
		theme()->getTemplates()->load('ManageFeatures');
1043
		$context['page_title'] = $txt['custom_profile_title'];
1044
		$context['sub_template'] = 'show_custom_profile';
1045
1046
		// What about standard fields they can tweak?
1047
		$standard_fields = array('website', 'posts', 'warning_status', 'date_registered', 'action');
1048
1049
		// What fields can't you put on the registration page?
1050
		$context['fields_no_registration'] = array('posts', 'warning_status', 'date_registered', 'action');
1051
1052
		// Are we saving any standard field changes?
1053
		if (isset($this->_req->post->save))
1054
		{
1055
			checkSession();
1056
			validateToken('admin-scp');
1057
1058
			$changes = array();
1059
1060
			// Do the active ones first.
1061
			$disable_fields = array_flip($standard_fields);
1062
			if (!empty($this->_req->post->active))
1063
			{
1064
				foreach ($this->_req->post->active as $value)
1065
				{
1066
					if (isset($disable_fields[$value]))
1067
					{
1068
						unset($disable_fields[$value]);
1069
					}
1070
				}
1071
			}
1072
1073
			// What we have left!
1074
			$changes['disabled_profile_fields'] = empty($disable_fields) ? '' : implode(',', array_keys($disable_fields));
1075
1076
			// Things we want to show on registration?
1077
			$reg_fields = array();
1078
			if (!empty($this->_req->post->reg))
1079
			{
1080
				foreach ($this->_req->post->reg as $value)
1081
				{
1082
					if (!in_array($value, $standard_fields))
1083
					{
1084
						continue;
1085
					}
1086
1087
					if (isset($disable_fields[$value]))
1088
					{
1089
						continue;
1090
					}
1091
1092
					$reg_fields[] = $value;
1093
				}
1094
			}
1095
1096
			// What we have left!
1097
			$changes['registration_fields'] = empty($reg_fields) ? '' : implode(',', $reg_fields);
1098
1099
			updateSettings($changes);
1100
		}
1101
1102
		createToken('admin-scp');
1103
1104
		// Create a listing for all our standard fields
1105
		$listOptions = array(
1106
			'id' => 'standard_profile_fields',
1107
			'title' => $txt['standard_profile_title'],
1108
			'base_href' => getUrl('admin', ['action' => 'admin', 'area' => 'featuresettings', 'sa' => 'profile']),
1109
			'get_items' => array(
1110
				'function' => 'list_getProfileFields',
1111
				'params' => array(
1112
					true,
1113
				),
1114
			),
1115
			'columns' => array(
1116
				'field' => array(
1117
					'header' => array(
1118
						'value' => $txt['standard_profile_field'],
1119
					),
1120
					'data' => array(
1121
						'db' => 'label',
1122
						'style' => 'width: 60%;',
1123
					),
1124
				),
1125
				'active' => array(
1126
					'header' => array(
1127
						'value' => $txt['custom_edit_active'],
1128
						'class' => 'centertext',
1129
					),
1130
					'data' => array(
1131
						'function' => static function ($rowData) {
1132
							$isChecked = $rowData['disabled'] ? '' : ' checked="checked"';
1133
							$onClickHandler = $rowData['can_show_register'] ? sprintf('onclick="document.getElementById(\'reg_%1$s\').disabled = !this.checked;"', $rowData['id']) : '';
1134
1135
							return sprintf('<input type="checkbox" name="active[]" id="active_%1$s" value="%1$s" class="input_check" %2$s %3$s />', $rowData['id'], $isChecked, $onClickHandler);
1136
						},
1137
						'style' => 'width: 20%;',
1138
						'class' => 'centertext',
1139
					),
1140
				),
1141
				'show_on_registration' => array(
1142
					'header' => array(
1143
						'value' => $txt['custom_edit_registration'],
1144
						'class' => 'centertext',
1145
					),
1146
					'data' => array(
1147
						'function' => static function ($rowData) {
1148
							$isChecked = $rowData['on_register'] && !$rowData['disabled'] ? ' checked="checked"' : '';
1149
							$isDisabled = $rowData['can_show_register'] ? '' : ' disabled="disabled"';
1150
1151
							return sprintf('<input type="checkbox" name="reg[]" id="reg_%1$s" value="%1$s" class="input_check" %2$s %3$s />', $rowData['id'], $isChecked, $isDisabled);
1152
						},
1153
						'style' => 'width: 20%;',
1154
						'class' => 'centertext',
1155
					),
1156
				),
1157
			),
1158
			'form' => array(
1159
				'href' => getUrl('admin', ['action' => 'admin', 'area' => 'featuresettings', 'sa' => 'profile']),
1160
				'name' => 'standardProfileFields',
1161
				'token' => 'admin-scp',
1162
			),
1163
			'additional_rows' => array(
1164
				array(
1165
					'position' => 'below_table_data',
1166
					'value' => '<input type="submit" name="save" value="' . $txt['save'] . '" class="right_submit" />',
1167
				),
1168
			),
1169
		);
1170
		createList($listOptions);
1171
1172
		// And now we do the same for all of our custom ones
1173
		$token = createToken('admin-sort');
1174
		$listOptions = array(
1175
			'id' => 'custom_profile_fields',
1176
			'title' => $txt['custom_profile_title'],
1177
			'base_href' => getUrl('admin', ['action' => 'admin', 'area' => 'featuresettings', 'sa' => 'profile']),
1178
			'default_sort_col' => 'vieworder',
1179
			'no_items_label' => $txt['custom_profile_none'],
1180
			'items_per_page' => 25,
1181
			'sortable' => true,
1182
			'get_items' => array(
1183
				'function' => 'list_getProfileFields',
1184
				'params' => array(
1185
					false,
1186
				),
1187
			),
1188
			'get_count' => array(
1189
				'function' => 'list_getProfileFieldSize',
1190
			),
1191
			'columns' => array(
1192
				'vieworder' => array(
1193
					'header' => array(
1194
						'value' => '',
1195
						'class' => 'hide',
1196
					),
1197
					'data' => array(
1198
						'db' => 'vieworder',
1199
						'class' => 'hide',
1200
					),
1201
					'sort' => array(
1202
						'default' => 'vieworder',
1203
					),
1204
				),
1205
				'field_name' => array(
1206
					'header' => array(
1207
						'value' => $txt['custom_profile_fieldname'],
1208
					),
1209
					'data' => array(
1210
						'function' => static fn($rowData) => sprintf('<a href="%1$s">%2$s</a><div class="smalltext">%3$s</div>', getUrl('admin', ['action' => 'admin', 'area' => 'featuresettings', 'sa' => 'profileedit', 'fid' => (int) $rowData['id_field']]), $rowData['field_name'], $rowData['field_desc']),
1211
						'style' => 'width: 65%;',
1212
					),
1213
					'sort' => array(
1214
						'default' => 'field_name',
1215
						'reverse' => 'field_name DESC',
1216
					),
1217
				),
1218
				'field_type' => array(
1219
					'header' => array(
1220
						'value' => $txt['custom_profile_fieldtype'],
1221
					),
1222
					'data' => array(
1223
						'function' => static function ($rowData) {
1224
							global $txt;
1225
1226
							$textKey = sprintf('custom_profile_type_%1$s', $rowData['field_type']);
1227
1228
							return $txt[$textKey] ?? $textKey;
1229
						},
1230
						'style' => 'width: 10%;',
1231
					),
1232
					'sort' => array(
1233
						'default' => 'field_type',
1234
						'reverse' => 'field_type DESC',
1235
					),
1236
				),
1237
				'cust' => array(
1238
					'header' => array(
1239
						'value' => $txt['custom_profile_active'],
1240
						'class' => 'centertext',
1241
					),
1242
					'data' => array(
1243
						'function' => static function ($rowData) {
1244
							$isChecked = $rowData['active'] === '1' ? ' checked="checked"' : '';
1245
1246
							return sprintf('<input type="checkbox" name="cust[]" id="cust_%1$s" value="%1$s" class="input_check"%2$s />', $rowData['id_field'], $isChecked);
1247
						},
1248
						'style' => 'width: 8%;',
1249
						'class' => 'centertext',
1250
					),
1251
					'sort' => array(
1252
						'default' => 'active DESC',
1253
						'reverse' => 'active',
1254
					),
1255
				),
1256
				'placement' => array(
1257
					'header' => array(
1258
						'value' => $txt['custom_profile_placement'],
1259
					),
1260
					'data' => array(
1261
						'function' => static function ($rowData) {
1262
							global $txt;
1263
1264
							$placement = 'custom_profile_placement_';
1265
							switch ((int) $rowData['placement'])
1266
							{
1267
								case 0:
1268
									$placement .= 'standard';
1269
									break;
1270
								case 1:
1271
									$placement .= 'withicons';
1272
									break;
1273
								case 2:
1274
									$placement .= 'abovesignature';
1275
									break;
1276
								case 3:
1277
									$placement .= 'aboveicons';
1278
									break;
1279
							}
1280
1281
							return $txt[$placement];
1282
						},
1283
						'style' => 'width: 5%;',
1284
					),
1285
					'sort' => array(
1286
						'default' => 'placement DESC',
1287
						'reverse' => 'placement',
1288
					),
1289
				),
1290
				'modify' => array(
1291
					'data' => array(
1292
						'sprintf' => array(
1293
							'format' => '<a href="' . getUrl('admin', ['action' => 'admin', 'area' => 'featuresettings', 'sa' => 'profileedit']) . ';fid=%1$s">' . $txt['modify'] . '</a>',
1294
							'params' => array(
1295
								'id_field' => false,
1296
							),
1297
						),
1298
						'style' => 'width: 5%;',
1299
					),
1300
				),
1301
			),
1302
			'form' => array(
1303
				'href' => getUrl('admin', ['action' => 'admin', 'area' => 'featuresettings', 'sa' => 'profileedit']),
1304
				'name' => 'customProfileFields',
1305
				'token' => 'admin-scp',
1306
			),
1307
			'additional_rows' => array(
1308
				array(
1309
					'class' => 'submitbutton flow_flex_additional_row',
1310
					'position' => 'below_table_data',
1311
					'value' => '
1312
						<input type="submit" name="onoff" value="' . $txt['save'] . '" />
1313
						<input type="submit" name="new" value="' . $txt['custom_profile_make_new'] . '" />',
1314
				),
1315
				array(
1316
					'position' => 'top_of_list',
1317
					'value' => '<p class="infobox">' . $txt['custom_profile_sort'] . '</p>',
1318
				),
1319
			),
1320
			'javascript' => '
1321
				$().elkSortable({
1322
					sa: "profileorder",
1323
					error: "' . $txt['admin_order_error'] . '",
1324
					title: "' . $txt['admin_order_title'] . '",
1325
					placeholder: "ui-state-highlight",
1326
					href: "?action=admin;area=featuresettings;sa=profile",
1327
					token: {token_var: "' . $token['admin-sort_token_var'] . '", token_id: "' . $token['admin-sort_token'] . '"}
1328
				});
1329
			',
1330
		);
1331
1332
		createList($listOptions);
1333
	}
1334
1335
	/**
1336
	 * Edit some profile fields?
1337
	 *
1338
	 * - Accessed with ?action=admin;area=featuresettings;sa=profileedit
1339
	 *
1340
	 * @uses sub template edit_profile_field
1341
	 */
1342
	public function action_profileedit()
1343
	{
1344
		global $txt, $context;
1345
1346
		theme()->getTemplates()->load('ManageFeatures');
1347
1348
		// Sort out the context!
1349
		$context['fid'] = $this->_req->getQuery('fid', 'intval', 0);
1350
		$context[$context['admin_menu_name']]['current_subsection'] = 'profile';
1351
		$context['page_title'] = $context['fid'] ? $txt['custom_edit_title'] : $txt['custom_add_title'];
1352
		$context['sub_template'] = 'edit_profile_field';
1353
1354
		// Any errors messages to show?
1355
		if (isset($this->_req->query->msg))
1356
		{
1357
			Txt::load('Errors');
1358
1359
			if (isset($txt['custom_option_' . $this->_req->query->msg]))
1360
			{
1361
				$context['custom_option__error'] = $txt['custom_option_' . $this->_req->query->msg];
1362
			}
1363
		}
1364
1365
		// Load the profile language for section names.
1366
		Txt::load('Profile');
1367
1368
		// Load up the profile field, if one was supplied
1369
		if ($context['fid'])
1370
		{
1371
			$context['field'] = getProfileField($context['fid']);
1372
		}
1373
1374
		// Setup the default values as needed.
1375
		if (empty($context['field']))
1376
		{
1377
			$context['field'] = array(
1378
				'name' => '',
1379
				'colname' => '???',
1380
				'desc' => '',
1381
				'profile_area' => 'forumprofile',
1382
				'reg' => false,
1383
				'display' => false,
1384
				'memberlist' => false,
1385
				'type' => 'text',
1386
				'max_length' => 255,
1387
				'rows' => 4,
1388
				'cols' => 30,
1389
				'bbc' => false,
1390
				'default_check' => false,
1391
				'default_select' => '',
1392
				'default_value' => '',
1393
				'options' => array('', '', ''),
1394
				'active' => true,
1395
				'private' => false,
1396
				'can_search' => false,
1397
				'mask' => 'nohtml',
1398
				'regex' => '',
1399
				'enclose' => '',
1400
				'placement' => 0,
1401
			);
1402
		}
1403
1404
		// All the javascript for this page... everything else is in admin.js
1405
		theme()->addJavascriptVar(array('startOptID' => count($context['field']['options'])));
1406
		theme()->addInlineJavascript('updateInputBoxes();', true);
1407
1408
		// Are we toggling which ones are active?
1409
		if (isset($this->_req->post->onoff))
1410
		{
1411
			checkSession();
1412
			validateToken('admin-scp');
1413
1414
			// Enable and disable custom fields as required.
1415
			$enabled = array(0);
1416
			if (isset($this->_req->post->cust) && is_array($this->_req->post->cust))
1417
			{
1418
				foreach ($this->_req->post->cust as $id)
1419
				{
1420
					$enabled[] = (int) $id;
1421
				}
1422
			}
1423
1424
			updateRenamedProfileStatus($enabled);
1425
		}
1426
		// Are we saving?
1427
		elseif (isset($this->_req->post->save))
1428
		{
1429
			checkSession();
1430
			validateToken('admin-ecp');
1431
1432
			// Everyone needs a name - even the (bracket) unknown...
1433
			if (trim($this->_req->post->field_name) === '')
1434
			{
1435
				redirectexit('action=admin;area=featuresettings;sa=profileedit;fid=' . $this->_req->query->fid . ';msg=need_name');
1436
			}
1437
1438
			// Regex you say?  Do a very basic test to see if the pattern is valid
1439
			if (!empty($this->_req->post->regex) && @preg_match($this->_req->post->regex, 'dummy') === false)
1440
			{
1441
				redirectexit('action=admin;area=featuresettings;sa=profileedit;fid=' . $this->_req->query->fid . ';msg=regex_error');
1442
			}
1443
1444
			$this->_req->post->field_name = $this->_req->getPost('field_name', '\\ElkArte\\Helper\\Util::htmlspecialchars');
1445
			$this->_req->post->field_desc = $this->_req->getPost('field_desc', '\\ElkArte\\Helper\\Util::htmlspecialchars');
1446
1447
			$rows = isset($this->_req->post->rows) ? (int) $this->_req->post->rows : 4;
1448
			$cols = isset($this->_req->post->cols) ? (int) $this->_req->post->cols : 30;
1449
1450
			// Checkboxes...
1451
			$show_reg = $this->_req->getPost('reg', 'intval', 0);
1452
			$show_display = isset($this->_req->post->display) ? 1 : 0;
1453
			$show_memberlist = isset($this->_req->post->memberlist) ? 1 : 0;
1454
			$bbc = isset($this->_req->post->bbc) ? 1 : 0;
1455
			$show_profile = $this->_req->post->profile_area;
1456
			$active = isset($this->_req->post->active) ? 1 : 0;
1457
			$private = $this->_req->getPost('private', 'intval', 0);
1458
			$can_search = isset($this->_req->post->can_search) ? 1 : 0;
1459
1460
			// Some masking stuff...
1461
			$mask = $this->_req->getPost('mask', 'strval', '');
1462
			if ($mask === 'regex' && isset($this->_req->post->regex))
1463
			{
1464
				$mask .= $this->_req->post->regex;
1465
			}
1466
1467
			$field_length = $this->_req->getPost('max_length', 'intval', 255);
1468
			$enclose = $this->_req->getPost('enclose', 'strval', '');
1469
			$placement = $this->_req->getPost('placement', 'intval', 0);
1470
1471
			// Select options?
1472
			$field_options = '';
1473
			$newOptions = array();
1474
1475
			// Set default
1476
			$default = '';
1477
1478
			switch ($this->_req->post->field_type)
1479
			{
1480
				case 'check':
1481
					$default = isset($this->_req->post->default_check) ? 1 : '';
1482
					break;
1483
				case 'select':
1484
				case 'radio':
1485
					if (!empty($this->_req->post->select_option))
1486
					{
1487
						foreach ($this->_req->post->select_option as $k => $v)
1488
						{
1489
							// Clean, clean, clean...
1490
							$v = Util::htmlspecialchars($v);
1491
							$v = strtr($v, array(',' => ''));
1492
1493
							// Nada, zip, etc...
1494
							if (trim($v) === '')
1495
							{
1496
								continue;
1497
							}
1498
1499
							// Otherwise, save it boy.
1500
							$field_options .= $v . ',';
1501
1502
							// This is just for working out what happened with old options...
1503
							$newOptions[$k] = $v;
1504
1505
							// Is it default?
1506
							if (!isset($this->_req->post->default_select))
1507
							{
1508
								continue;
1509
							}
1510
1511
							if ($this->_req->post->default_select != $k)
1512
							{
1513
								continue;
1514
							}
1515
1516
							$default = $v;
1517
						}
1518
1519
						if (isset($_POST['default_select']) && $_POST['default_select'] === 'no_default')
1520
						{
1521
							$default = 'no_default';
1522
						}
1523
1524
						$field_options = substr($field_options, 0, -1);
1525
					}
1526
1527
					break;
1528
				default:
1529
					$default = $this->_req->post->default_value ?? '';
1530
			}
1531
1532
			// Come up with the unique name?
1533
			if (empty($context['fid']))
1534
			{
1535
				$colname = Util::substr(strtr($this->_req->post->field_name, array(' ' => '')), 0, 6);
1536
				preg_match('~([\w_-]+)~', $colname, $matches);
1537
1538 2
				// If there is nothing to the name, then let's start our own - for foreign languages etc.
1539
				if (isset($matches[1]))
1540 2
				{
1541
					$colname = 'cust_' . strtolower($matches[1]);
1542
					$initial_colname = 'cust_' . strtolower($matches[1]);
1543
				}
1544
				else
1545
				{
1546 2
					$colname = 'cust_' . mt_rand(1, 999999);
1547
					$initial_colname = 'cust_' . mt_rand(1, 999999);
1548 2
				}
1549
1550
				$unique = ensureUniqueProfileField($colname, $initial_colname);
1551
1552
				// Still not a unique column name? Leave it up to the user, then.
1553
				if (!$unique)
1554 2
				{
1555
					throw new Exception('custom_option_not_unique');
1556 2
				}
1557
1558
				// And create a new field
1559
				$new_field = array(
1560
					'col_name' => $colname,
1561
					'field_name' => $this->_req->post->field_name,
1562 2
					'field_desc' => $this->_req->post->field_desc,
1563
					'field_type' => $this->_req->post->field_type,
1564 2
					'field_length' => $field_length,
1565
					'field_options' => $field_options,
1566
					'show_reg' => $show_reg,
1567
					'show_display' => $show_display,
1568
					'show_memberlist' => $show_memberlist,
1569
					'show_profile' => $show_profile,
1570 2
					'private' => $private,
1571
					'active' => $active,
1572 2
					'default_value' => $default,
1573
					'rows' => $rows,
1574
					'cols' => $cols,
1575
					'can_search' => $can_search,
1576
					'bbc' => $bbc,
1577
					'mask' => $mask,
1578 2
					'enclose' => $enclose,
1579
					'placement' => $placement,
1580 2
					'vieworder' => list_getProfileFieldSize() + 1,
1581
				);
1582
				addProfileField($new_field);
1583
			}
1584
			// Work out what to do with the user data otherwise...
1585
			else
1586
			{
1587
				// Anything going to check or select is pointless keeping - as is anything coming from check!
1588
				if (($this->_req->post->field_type === 'check' && $context['field']['type'] !== 'check')
1589
					|| (($this->_req->post->field_type === 'select' || $this->_req->post->field_type === 'radio') && $context['field']['type'] !== 'select' && $context['field']['type'] !== 'radio')
1590
					|| ($context['field']['type'] === 'check' && $this->_req->post->field_type !== 'check'))
1591
				{
1592
					deleteProfileFieldUserData($context['field']['colname']);
1593
				}
1594
				// Otherwise - if the select is edited may need to adjust!
1595
				elseif ($this->_req->post->field_type === 'select' || $this->_req->post->field_type === 'radio')
1596
				{
1597
					$optionChanges = $context['field']['options'];
1598
					$takenKeys = array();
1599
1600
					// Work out what's changed!
1601
					foreach ($optionChanges as $k => $option)
1602
					{
1603
						if (trim($option) === '')
1604
						{
1605
							continue;
1606
						}
1607
1608
						// Still exists?
1609
						if (in_array($option, $newOptions))
1610
						{
1611
							$takenKeys[] = $k;
1612
						}
1613
					}
1614
1615
					// Finally - have we renamed it - or is it really gone?
1616
					foreach ($optionChanges as $k => $option)
1617
					{
1618
						// Just been renamed?
1619
						if (in_array($k, $takenKeys))
1620
						{
1621
							continue;
1622
						}
1623
1624
						if (empty($newOptions[$k]))
1625
						{
1626
							continue;
1627
						}
1628
1629
						updateRenamedProfileField($k, $newOptions, $context['field']['colname'], $option);
1630
					}
1631
				}
1632
1633
				// @todo Maybe we should adjust based on new text length limits?
1634
1635
				// And finally update an existing field
1636
				$field_data = array(
1637
					'field_length' => $field_length,
1638
					'show_reg' => $show_reg,
1639
					'show_display' => $show_display,
1640
					'show_memberlist' => $show_memberlist,
1641
					'private' => $private,
1642
					'active' => $active,
1643
					'can_search' => $can_search,
1644
					'bbc' => $bbc,
1645
					'current_field' => $context['fid'],
1646
					'field_name' => $this->_req->post->field_name,
1647
					'field_desc' => $this->_req->post->field_desc,
1648
					'field_type' => $this->_req->post->field_type,
1649
					'field_options' => $field_options,
1650
					'show_profile' => $show_profile,
1651
					'default_value' => $default,
1652
					'mask' => $mask,
1653
					'enclose' => $enclose,
1654
					'placement' => $placement,
1655
					'rows' => $rows,
1656
					'cols' => $cols,
1657
				);
1658
1659
				updateProfileField($field_data);
1660
1661
				// Just clean up any old selects - these are a pain!
1662
				if (($this->_req->post->field_type == 'select' || $this->_req->post->field_type == 'radio') && !empty($newOptions))
1663
				{
1664
					deleteOldProfileFieldSelects($newOptions, $context['field']['colname']);
1665
				}
1666
			}
1667
		}
1668
		// Deleting?
1669
		elseif (isset($this->_req->post->delete) && $context['field']['colname'])
1670
		{
1671
			checkSession();
1672
			validateToken('admin-ecp');
1673
1674
			// Delete the old data first, then the field.
1675
			deleteProfileFieldUserData($context['field']['colname']);
1676
			deleteProfileField($context['fid']);
1677
		}
1678
1679
		// Rebuild display cache etc.
1680
		if (isset($this->_req->post->delete) || isset($this->_req->post->save) || isset($this->_req->post->onoff))
1681
		{
1682
			checkSession();
1683
1684
			// Update the display cache
1685
			updateDisplayCache();
1686
			redirectexit('action=admin;area=featuresettings;sa=profile');
1687
		}
1688
1689
		createToken('admin-ecp');
1690
	}
1691
1692
	/**
1693
	 * Editing personal messages settings
1694
	 *
1695
	 * - Accessed with ?action=admin;area=featuresettings;sa=pmsettings
1696
	 *
1697
	 * @event integrate_save_pmsettings_settings
1698
	 */
1699
	public function action_pmsettings()
1700
	{
1701
		global $txt, $context;
1702
1703
		// Initialize the form
1704
		$settingsForm = new SettingsForm(SettingsForm::DB_ADAPTER);
1705
1706
		// Initialize it with our settings
1707
		$settingsForm->setConfigVars($this->_pmSettings());
1708
1709
		require_once(SUBSDIR . '/PersonalMessage.subs.php');
1710
		Txt::load('ManageMembers');
1711
1712
		$context['pm_limits'] = loadPMLimits();
1713
1714
		// Saving?
1715
		if (isset($this->_req->query->save))
1716
		{
1717
			checkSession();
1718
1719
			require_once(SUBSDIR . '/Membergroups.subs.php');
1720
			foreach ($context['pm_limits'] as $group_id => $group)
1721
			{
1722
				if (!isset($this->_req->post->group[$group_id]))
1723
				{
1724
					continue;
1725
				}
1726
1727
				if ($this->_req->post->group[$group_id] == $group['max_messages'])
1728
				{
1729
					continue;
1730
				}
1731
1732
				updateMembergroupProperties(array('current_group' => $group_id, 'max_messages' => $this->_req->post->group[$group_id]));
1733
			}
1734
1735
			call_integration_hook('integrate_save_pmsettings_settings');
1736
1737
			$settingsForm->setConfigValues((array) $this->_req->post);
1738
			$settingsForm->save();
1739
			redirectexit('action=admin;area=featuresettings;sa=pmsettings');
1740
		}
1741
1742
		$context['post_url'] = getUrl('admin', ['action' => 'admin', 'area' => 'featuresettings', 'sa' => 'pmsettings', 'save']);
1743
		$context['settings_title'] = $txt['personal_messages'];
1744
1745
		$settingsForm->prepare();
1746
	}
1747
1748
	/**
1749
	 * Return pm settings.
1750
	 *
1751
	 * - Used in admin center search and settings form
1752
	 *
1753
	 * @event integrate_modify_pmsettings_settings Adds / Modifies PM Settings
1754
	 */
1755
	private function _pmSettings()
1756
	{
1757
		global $txt;
1758
1759
		$config_vars = array(
1760
			// Reporting of personal messages?
1761
			array('check', 'enableReportPM'),
1762
			// Inline permissions.
1763
			array('permissions', 'pm_send'),
1764
			// PM Settings
1765
			array('title', 'antispam_PM'),
1766
			'pm1' => array('int', 'max_pm_recipients', 'postinput' => $txt['max_pm_recipients_note']),
1767
			'pm2' => array('int', 'pm_posts_verification', 'postinput' => $txt['pm_posts_verification_note']),
1768
			'pm3' => array('int', 'pm_posts_per_hour', 'postinput' => $txt['pm_posts_per_hour_note']),
1769
			array('title', 'membergroups_max_messages'),
1770
			array('desc', 'membergroups_max_messages_desc'),
1771
			array('callback', 'pm_limits'),
1772
		);
1773
1774
		call_integration_hook('integrate_modify_pmsettings_settings', array(&$config_vars));
1775
1776
		return $config_vars;
1777
	}
1778
1779
	/**
1780
	 * Public method to return the basic settings, used for admin search
1781
	 */
1782
	public function basicSettings_search()
1783
	{
1784
		return $this->_basicSettings();
1785
	}
1786
1787
	/**
1788
	 * Public method to return the layout settings, used for admin search
1789
	 */
1790
	public function layoutSettings_search()
1791
	{
1792
		return $this->_layoutSettings();
1793
	}
1794
1795
	/**
1796
	 * Public method to return the karma settings, used for admin search
1797
	 */
1798
	public function karmaSettings_search()
1799
	{
1800
		global $modSettings;
1801
1802
		// Karma - On or off?
1803
		if (empty($modSettings['karmaMode']))
1804
		{
1805
			return ['check', 'dummy_karma'];
1806
		}
1807
1808
		return $this->_karmaSettings();
1809
	}
1810
1811
	/**
1812
	 * Public method to return the likes settings, used for admin search
1813
	 */
1814
	public function likesSettings_search()
1815
	{
1816
		global $modSettings;
1817
1818
		// Likes - On or off?
1819
		if (empty($modSettings['enable_likes']))
1820
		{
1821
			return ['check', 'dummy_likes'];
1822
		}
1823
1824
		return $this->_likesSettings();
1825
	}
1826
1827
	/**
1828
	 * Public method to return the mention settings, used for admin search
1829
	 */
1830
	public function mentionSettings_search()
1831
	{
1832
		return $this->_notificationsSettings();
1833
	}
1834
1835
	/**
1836
	 * Public method to return the signature settings, used for admin search
1837
	 */
1838
	public function signatureSettings_search()
1839
	{
1840
		return $this->_signatureSettings();
1841
	}
1842
1843
	/**
1844
	 * Public method to return the PM settings, used for admin search
1845
	 */
1846
	public function pmSettings_search()
1847
	{
1848
		return $this->_pmSettings();
1849
	}
1850
}
1851