Passed
Push — development ( f9d3d6...da8715 )
by Spuds
01:09 queued 25s
created

ManageFeatures::_pwaSettings()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 26
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2.3149

Importance

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

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