Completed
Pull Request — release-2.1 (#4892)
by Mathias
09:58
created

ManageSettings.php ➔ ModifyPrivacySettings()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 32

Duplication

Lines 10
Ratio 31.25 %

Importance

Changes 0
Metric Value
cc 3
nc 3
nop 1
dl 10
loc 32
rs 9.408
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * This file is here to make it easier for installed mods to have
5
 * settings and options.
6
 *
7
 * Simple Machines Forum (SMF)
8
 *
9
 * @package SMF
10
 * @author Simple Machines http://www.simplemachines.org
11
 * @copyright 2018 Simple Machines and individual contributors
12
 * @license http://www.simplemachines.org/about/smf/license.php BSD
13
 *
14
 * @version 2.1 Beta 4
15
 */
16
17
if (!defined('SMF'))
18
	die('No direct access...');
19
20
/**
21
 * This function makes sure the requested subaction does exists, if it doesn't, it sets a default action or.
22
 *
23
 * @param array $subActions An array containing all possible subactions.
24
 * @param string $defaultAction The default action to be called if no valid subaction was found.
0 ignored issues
show
Documentation introduced by
Should the type for parameter $defaultAction not be string|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
25
 */
26
function loadGeneralSettingParameters($subActions = array(), $defaultAction = null)
27
{
28
	global $context, $sourcedir;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
29
30
	// You need to be an admin to edit settings!
31
	isAllowedTo('admin_forum');
32
33
	loadLanguage('Help');
34
	loadLanguage('ManageSettings');
35
36
	// Will need the utility functions from here.
37
	require_once($sourcedir . '/ManageServer.php');
38
39
	$context['sub_template'] = 'show_settings';
40
41
	// If no fallback was specified, use the first subaction.
42
	$defaultAction = $defaultAction ?: key($subActions);
43
44
	// I want...
45
	$_REQUEST['sa'] = isset($_REQUEST['sa'], $subActions[$_REQUEST['sa']]) ? $_REQUEST['sa'] : $defaultAction;
46
	$context['sub_action'] = $_REQUEST['sa'];
47
}
48
49
/**
50
 * This function passes control through to the relevant tab.
51
 */
52
function ModifyFeatureSettings()
53
{
54
	global $context, $txt, $settings;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
55
56
	$context['page_title'] = $txt['modSettings_title'];
57
58
	$subActions = array(
59
		'basic' => 'ModifyBasicSettings',
60
		'bbc' => 'ModifyBBCSettings',
61
		'layout' => 'ModifyLayoutSettings',
62
		'sig' => 'ModifySignatureSettings',
63
		'profile' => 'ShowCustomProfiles',
64
		'profileedit' => 'EditCustomProfiles',
65
		'likes' => 'ModifyLikesSettings',
66
		'mentions' => 'ModifyMentionsSettings',
67
		'alerts' => 'ModifyAlertsSettings',
68
		'privacy' => 'ModifyPrivacySettings',
69
		'policy' => 'ModifyPolicySettings',
70
	);
71
72
	loadGeneralSettingParameters($subActions, 'basic');
73
74
	// Load up all the tabs...
75
	$context[$context['admin_menu_name']]['tab_data'] = array(
76
		'title' => $txt['modSettings_title'],
77
		'help' => 'featuresettings',
78
		'description' => sprintf($txt['modSettings_desc'], $settings['theme_id'], $context['session_id'], $context['session_var']),
79
		'tabs' => array(
80
			'basic' => array(
81
			),
82
			'bbc' => array(
83
				'description' => $txt['manageposts_bbc_settings_description'],
84
			),
85
			'layout' => array(
86
			),
87
			'sig' => array(
88
				'description' => $txt['signature_settings_desc'],
89
			),
90
			'profile' => array(
91
				'description' => $txt['custom_profile_desc'],
92
			),
93
			'likes' => array(
94
			),
95
			'mentions' => array(
96
			),
97
			'alerts' => array(
98
				'description' => $txt['notifications_desc'],
99
			),
100
			'privacy' => array(
101
			),
102
			'policy' => array(
103
			),
104
		),
105
	);
106
107
	call_integration_hook('integrate_modify_features', array(&$subActions));
108
109
	// Call the right function for this sub-action.
110
	call_helper($subActions[$_REQUEST['sa']]);
111
}
112
113
/**
114
 * This my friend, is for all the mod authors out there.
115
 */
116
function ModifyModSettings()
117
{
118
	global $context, $txt;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
119
120
	$context['page_title'] = $txt['admin_modifications'];
121
122
	$subActions = array(
123
		'general' => 'ModifyGeneralModSettings',
124
		// Mod authors, once again, if you have a whole section to add do it AFTER this line, and keep a comma at the end.
125
	);
126
127
	// Make it easier for mods to add new areas.
128
	call_integration_hook('integrate_modify_modifications', array(&$subActions));
129
130
	loadGeneralSettingParameters($subActions, 'general');
131
132
	// Load up all the tabs...
133
	$context[$context['admin_menu_name']]['tab_data'] = array(
134
		'title' => $txt['admin_modifications'],
135
		'help' => 'modsettings',
136
		'description' => $txt['modification_settings_desc'],
137
		'tabs' => array(
138
			'general' => array(
139
			),
140
		),
141
	);
142
143
	// Call the right function for this sub-action.
144
	call_helper($subActions[$_REQUEST['sa']]);
145
}
146
147
/**
148
 * Config array for changing the basic forum settings
149
 * Accessed  from ?action=admin;area=featuresettings;sa=basic;
150
 *
151
 * @param bool $return_config Whether or not to return the config_vars array (used for admin search)
152
 * @return void|array Returns nothing or returns the $config_vars array if $return_config is true
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use array<*,string|string[]|...ring|array>|array>|null.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
153
 */
154
function ModifyBasicSettings($return_config = false)
155
{
156
	global $txt, $scripturl, $context, $modSettings;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
157
158
	// We need to know if personal text is enabled, and if it's in the registration fields option.
159
	// If admins have set it up as an on-registration thing, they can't set a default value (because it'll never be used)
160
	$disabled_fields = isset($modSettings['disabled_profile_fields']) ? explode(',', $modSettings['disabled_profile_fields']) : array();
161
	$reg_fields = isset($modSettings['registration_fields']) ? explode(',', $modSettings['registration_fields']) : array();
162
	$can_personal_text = !in_array('personal_text', $disabled_fields) && !in_array('personal_text', $reg_fields);
163
164
	$config_vars = array(
165
			// Big Options... polls, sticky, bbc....
166
			array('select', 'pollMode', array($txt['disable_polls'], $txt['enable_polls'], $txt['polls_as_topics'])),
167
		'',
168
			// Basic stuff, titles, flash, permissions...
169
			array('check', 'allow_guestAccess'),
170
			array('check', 'enable_buddylist'),
171
			array('check', 'allow_hideOnline'),
172
			array('check', 'titlesEnable'),
173
			array('text', 'default_personal_text', 'subtext' => $txt['default_personal_text_note'], 'disabled' => !$can_personal_text),
174
			array('check', 'topic_move_any'),
175
			array('int', 'defaultMaxListItems', 'step' => 1, 'min' => 1, 'max' => 999),
176
		'',
177
			// Jquery source
178
			array('select', 'jquery_source', array('auto' => $txt['jquery_auto'], 'local' => $txt['jquery_local'], 'cdn' => $txt['jquery_cdn'], 'custom' => $txt['jquery_custom']), 'onchange' => 'if (this.value == \'custom\'){document.getElementById(\'jquery_custom\').disabled = false; } else {document.getElementById(\'jquery_custom\').disabled = true;}'),
179
			array('text', 'jquery_custom', 'disabled' => isset($modSettings['jquery_source']) && $modSettings['jquery_source'] != 'custom', 'size' => 75),
180
		'',
181
			// css and js minification.
182
			array('check', 'minimize_files'),
183
		'',
184
			// SEO stuff
185
			array('check', 'queryless_urls', 'subtext' => '<strong>' . $txt['queryless_urls_note'] . '</strong>'),
186
			array('text', 'meta_keywords', 'subtext' => $txt['meta_keywords_note'], 'size' => 50),
187
		'',
188
			// Number formatting, timezones.
189
			array('text', 'time_format'),
190
			array('float', 'time_offset', 'subtext' => $txt['setting_time_offset_note'], 6, 'postinput' => $txt['hours'], 'step' => 0.25, 'min' => -23.5, 'max' => 23.5),
191
			'default_timezone' => array('select', 'default_timezone', array()),
192
			array('text', 'timezone_priority_countries', 'subtext' => $txt['setting_timezone_priority_countries_note']),
193
		'',
194
			// Who's online?
195
			array('check', 'who_enabled'),
196
			array('int', 'lastActive', 6, 'postinput' => $txt['minutes']),
197
		'',
198
			// Statistics.
199
			array('check', 'trackStats'),
200
			array('check', 'hitStats'),
201
		'',
202
			// Option-ish things... miscellaneous sorta.
203
			array('check', 'allow_disableAnnounce'),
204
			array('check', 'disallow_sendBody'),
205
		'',
206
			// Alerts stuff
207
			array('check', 'enable_ajax_alerts'),
208
	);
209
210
	// Get all the time zones.
211
	if (function_exists('timezone_identifiers_list') && function_exists('date_default_timezone_set'))
212
	{
213
		$all_zones = timezone_identifiers_list();
214
		// Make sure we set the value to the same as the printed value.
215
		foreach ($all_zones as $zone)
216
			$config_vars['default_timezone'][2][$zone] = $zone;
217
	}
218
	else
219
		unset($config_vars['default_timezone']);
220
221
	call_integration_hook('integrate_modify_basic_settings', array(&$config_vars));
222
223
	if ($return_config)
224
		return $config_vars;
225
226
	// Saving?
227
	if (isset($_GET['save']))
228
	{
229
		checkSession();
230
231
		// Prevent absurd boundaries here - make it a day tops.
232
		if (isset($_POST['lastActive']))
233
			$_POST['lastActive'] = min((int) $_POST['lastActive'], 1440);
234
235
		call_integration_hook('integrate_save_basic_settings');
236
237
		saveDBSettings($config_vars);
238
		$_SESSION['adm-save'] = true;
239
240
		// Do a bit of housekeeping
241
		if (empty($_POST['minimize_files']))
242
			deleteAllMinified();
243
244
		writeLog();
245
		redirectexit('action=admin;area=featuresettings;sa=basic');
246
	}
247
248
	$context['post_url'] = $scripturl . '?action=admin;area=featuresettings;save;sa=basic';
249
	$context['settings_title'] = $txt['mods_cat_features'];
250
251
	prepareDBSettingContext($config_vars);
252
}
253
254
/**
255
 * Set a few Bulletin Board Code settings. It loads a list of Bulletin Board Code tags to allow disabling tags.
256
 * Requires the admin_forum permission.
257
 * Accessed from ?action=admin;area=featuresettings;sa=bbc.
258
 *
259
 * @param bool $return_config Whether or not to return the config_vars array (used for admin search)
260
 * @return void|array Returns nothing or returns the $config_vars array if $return_config is true
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use array<string[]|array<*,s...g|integer>|string>|null.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
261
 * @uses Admin template, edit_bbc_settings sub-template.
262
 */
263
function ModifyBBCSettings($return_config = false)
264
{
265
	global $context, $txt, $modSettings, $scripturl, $sourcedir;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
266
267
	$config_vars = array(
268
			// Main tweaks
269
			array('check', 'enableBBC'),
270
			array('check', 'enableBBC', 0, 'onchange' => 'toggleBBCDisabled(\'disabledBBC\', !this.checked);'),
271
			array('check', 'enablePostHTML'),
272
			array('check', 'autoLinkUrls'),
273
		'',
274
			array('bbc', 'disabledBBC'),
275
	);
276
277
	$context['settings_post_javascript'] = '
278
		toggleBBCDisabled(\'disabledBBC\', ' . (empty($modSettings['enableBBC']) ? 'true' : 'false') . ');';
279
280
	call_integration_hook('integrate_modify_bbc_settings', array(&$config_vars));
281
282
	if ($return_config)
283
		return $config_vars;
284
285
	// Setup the template.
286
	require_once($sourcedir . '/ManageServer.php');
287
	$context['sub_template'] = 'show_settings';
288
	$context['page_title'] = $txt['manageposts_bbc_settings_title'];
289
290
	// Make sure we check the right tags!
291
	$modSettings['bbc_disabled_disabledBBC'] = empty($modSettings['disabledBBC']) ? array() : explode(',', $modSettings['disabledBBC']);
292
293
	// Saving?
294
	if (isset($_GET['save']))
295
	{
296
		checkSession();
297
298
		// Clean up the tags.
299
		$bbcTags = array();
300
		foreach (parse_bbc(false) as $tag)
0 ignored issues
show
Documentation introduced by
false is of type boolean, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
Bug introduced by
The expression parse_bbc(false) of type string is not traversable.
Loading history...
301
			$bbcTags[] = $tag['tag'];
302
303 View Code Duplication
		if (!isset($_POST['disabledBBC_enabledTags']))
304
			$_POST['disabledBBC_enabledTags'] = array();
305
		elseif (!is_array($_POST['disabledBBC_enabledTags']))
306
			$_POST['disabledBBC_enabledTags'] = array($_POST['disabledBBC_enabledTags']);
307
		// Work out what is actually disabled!
308
		$_POST['disabledBBC'] = implode(',', array_diff($bbcTags, $_POST['disabledBBC_enabledTags']));
309
310
		call_integration_hook('integrate_save_bbc_settings', array($bbcTags));
311
312
		saveDBSettings($config_vars);
313
		$_SESSION['adm-save'] = true;
314
		redirectexit('action=admin;area=featuresettings;sa=bbc');
315
	}
316
317
	$context['post_url'] = $scripturl . '?action=admin;area=featuresettings;save;sa=bbc';
318
	$context['settings_title'] = $txt['manageposts_bbc_settings_title'];
319
320
	prepareDBSettingContext($config_vars);
321
}
322
323
/**
324
 * Allows modifying the global layout settings in the forum
325
 * Accessed through ?action=admin;area=featuresettings;sa=layout;
326
 *
327
 * @param bool $return_config Whether or not to return the config_vars array (used for admin search)
328
 * @return void|array Returns nothing or returns the $config_vars array if $return_config is true
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use array<string[]|array<str...ray<string|array>>|null.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
329
 */
330
function ModifyLayoutSettings($return_config = false)
331
{
332
	global $txt, $scripturl, $context;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
333
334
	$config_vars = array(
335
			// Pagination stuff.
336
			array('check', 'compactTopicPagesEnable'),
337
			array('int', 'compactTopicPagesContiguous', null, $txt['contiguous_page_display'] . '<div class="smalltext">' . 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>') . '</div>'),
338
			array('int', 'defaultMaxMembers'),
339
		'',
340
			// Stuff that just is everywhere - today, search, online, etc.
341
			array('select', 'todayMod', array($txt['today_disabled'], $txt['today_only'], $txt['yesterday_today'])),
342
			array('check', 'onlineEnable'),
343
		'',
344
			// This is like debugging sorta.
345
			array('check', 'timeLoadPageEnable'),
346
	);
347
348
	call_integration_hook('integrate_layout_settings', array(&$config_vars));
349
350
	if ($return_config)
351
		return $config_vars;
352
353
	// Saving?
354 View Code Duplication
	if (isset($_GET['save']))
355
	{
356
		checkSession();
357
358
		call_integration_hook('integrate_save_layout_settings');
359
360
		saveDBSettings($config_vars);
361
		$_SESSION['adm-save'] = true;
362
		writeLog();
363
364
		redirectexit('action=admin;area=featuresettings;sa=layout');
365
	}
366
367
	$context['post_url'] = $scripturl . '?action=admin;area=featuresettings;save;sa=layout';
368
	$context['settings_title'] = $txt['mods_cat_layout'];
369
370
	prepareDBSettingContext($config_vars);
371
}
372
373
/**
374
 * Config array for changing like settings
375
 * Accessed  from ?action=admin;area=featuresettings;sa=likes;
376
 *
377
 * @param bool $return_config Whether or not to return the config_vars array
378
 * @return void|array Returns nothing or returns the $config_vars array if $return_config is true
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use string[][]|null.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
379
 */
380 View Code Duplication
function ModifyLikesSettings($return_config = false)
0 ignored issues
show
Duplication introduced by
This function seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
381
{
382
	global $txt, $scripturl, $context;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
383
384
	$config_vars = array(
385
		array('check', 'enable_likes'),
386
		array('permissions', 'likes_like'),
387
	);
388
389
	call_integration_hook('integrate_likes_settings', array(&$config_vars));
390
391
	if ($return_config)
392
		return $config_vars;
393
394
	// Saving?
395
	if (isset($_GET['save']))
396
	{
397
		checkSession();
398
399
		call_integration_hook('integrate_save_likes_settings');
400
401
		saveDBSettings($config_vars);
402
		$_SESSION['adm-save'] = true;
403
		redirectexit('action=admin;area=featuresettings;sa=likes');
404
	}
405
406
	$context['post_url'] = $scripturl . '?action=admin;area=featuresettings;save;sa=likes';
407
	$context['settings_title'] = $txt['likes'];
408
409
	prepareDBSettingContext($config_vars);
410
}
411
412
/**
413
 * Config array for changing like settings
414
 * Accessed  from ?action=admin;area=featuresettings;sa=mentions;
415
 *
416
 * @param bool $return_config Whether or not to return the config_vars array (used for admin search)
417
 * @return void|array Returns nothing or returns the $config_vars array if $return_config is true
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use string[][]|null.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
418
 */
419 View Code Duplication
function ModifyMentionsSettings($return_config = false)
0 ignored issues
show
Duplication introduced by
This function seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
420
{
421
	global $txt, $scripturl, $context;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
422
423
	$config_vars = array(
424
		array('check', 'enable_mentions'),
425
		array('permissions', 'mention'),
426
	);
427
428
	call_integration_hook('integrate_mentions_settings', array(&$config_vars));
429
430
	if ($return_config)
431
		return $config_vars;
432
433
	// Saving?
434
	if (isset($_GET['save']))
435
	{
436
		checkSession();
437
438
		call_integration_hook('integrate_save_mentions_settings');
439
440
		saveDBSettings($config_vars);
441
		$_SESSION['adm-save'] = true;
442
		redirectexit('action=admin;area=featuresettings;sa=mentions');
443
	}
444
445
	$context['post_url'] = $scripturl . '?action=admin;area=featuresettings;save;sa=mentions';
446
	$context['settings_title'] = $txt['mentions'];
447
448
	prepareDBSettingContext($config_vars);
449
}
450
451
/**
452
 * Moderation type settings - although there are fewer than we have you believe ;)
453
 *
454
 * @param bool $return_config Whether or not to return the config_vars array (used for admin search)
455
 * @return void|array Returns nothing or returns the $config_vars array if $return_config is true
456
 */
457
function ModifyWarningSettings($return_config = false)
458
{
459
	global $txt, $scripturl, $context, $modSettings, $sourcedir;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
460
461
	// You need to be an admin to edit settings!
462
	isAllowedTo('admin_forum');
463
464
	loadLanguage('Help');
465
	loadLanguage('ManageSettings');
466
467
	// We need the existing ones for this
468
	list ($currently_enabled, $modSettings['user_limit'], $modSettings['warning_decrement']) = explode(',', $modSettings['warning_settings']);
469
470
	$config_vars = array(
471
			// Warning system?
472
			'enable' => array('check', 'warning_enable'),
473
	);
474
475
	if (!empty($modSettings['warning_settings']) && $currently_enabled)
476
		$config_vars += array(
477
			'',
478
				array('int', 'warning_watch', 'subtext' => $txt['setting_warning_watch_note'] . ' ' . $txt['zero_to_disable']),
479
				'moderate' => array('int', 'warning_moderate', 'subtext' => $txt['setting_warning_moderate_note'] . ' ' . $txt['zero_to_disable']),
480
				array('int', 'warning_mute', 'subtext' => $txt['setting_warning_mute_note'] . ' ' . $txt['zero_to_disable']),
481
				'rem1' => array('int', 'user_limit', 'subtext' => $txt['setting_user_limit_note']),
482
				'rem2' => array('int', 'warning_decrement', 'subtext' => $txt['setting_warning_decrement_note'] . ' ' . $txt['zero_to_disable']),
483
				array('permissions', 'view_warning'),
484
		);
485
486
	call_integration_hook('integrate_warning_settings', array(&$config_vars));
487
488
	if ($return_config)
489
		return $config_vars;
490
491
	// Cannot use moderation if post moderation is not enabled.
492
	if (!$modSettings['postmod_active'])
493
		unset($config_vars['moderate']);
494
495
	// Will need the utility functions from here.
496
	require_once($sourcedir . '/ManageServer.php');
497
498
	// Saving?
499
	if (isset($_GET['save']))
500
	{
501
		checkSession();
502
503
		// Make sure these don't have an effect.
504
		if (!$currently_enabled && empty($_POST['warning_enable']))
505
		{
506
			$_POST['warning_watch'] = 0;
507
			$_POST['warning_moderate'] = 0;
508
			$_POST['warning_mute'] = 0;
509
		}
510
		// If it was disabled and we're enabling it now, set some sane defaults.
511
		elseif (!$currently_enabled && !empty($_POST['warning_enable']))
512
		{
513
			// Need to add these, these weren't there before...
514
			$vars = array(
515
				'warning_watch' => 10,
516
				'warning_mute' => 60,
517
			);
518
			if ($modSettings['postmod_active'])
519
				$vars['warning_moderate'] = 35;
520
521
			foreach ($vars as $var => $value)
522
			{
523
				$config_vars[] = array('int', $var);
524
				$_POST[$var] = $value;
525
			}
526
		}
527
		else
528
		{
529
			$_POST['warning_watch'] = min($_POST['warning_watch'], 100);
530
			$_POST['warning_moderate'] = $modSettings['postmod_active'] ? min($_POST['warning_moderate'], 100) : 0;
531
			$_POST['warning_mute'] = min($_POST['warning_mute'], 100);
532
		}
533
534
		// We might not have these already depending on how we got here.
535
		$_POST['user_limit'] = isset($_POST['user_limit']) ? (int) $_POST['user_limit'] : $modSettings['user_limit'];
536
		$_POST['warning_decrement'] = isset($_POST['warning_decrement']) ? (int) $_POST['warning_decrement'] : $modSettings['warning_decrement'];
537
538
		// Fix the warning setting array!
539
		$_POST['warning_settings'] = (!empty($_POST['warning_enable']) ? 1 : 0) . ',' . min(100, $_POST['user_limit']) . ',' . min(100, $_POST['warning_decrement']);
540
		$save_vars = $config_vars;
541
		$save_vars[] = array('text', 'warning_settings');
542
		unset($save_vars['enable'], $save_vars['rem1'], $save_vars['rem2']);
543
544
		call_integration_hook('integrate_save_warning_settings', array(&$save_vars));
545
546
		saveDBSettings($save_vars);
547
		$_SESSION['adm-save'] = true;
548
		redirectexit('action=admin;area=warnings');
549
	}
550
551
	// We actually store lots of these together - for efficiency.
552
	list ($modSettings['warning_enable'], $modSettings['user_limit'], $modSettings['warning_decrement']) = explode(',', $modSettings['warning_settings']);
553
554
	$context['sub_template'] = 'show_settings';
555
	$context['post_url'] = $scripturl . '?action=admin;area=warnings;save';
556
	$context['settings_title'] = $txt['warnings'];
557
	$context['page_title'] = $txt['warnings'];
558
559
	$context[$context['admin_menu_name']]['tab_data'] = array(
560
		'title' => $txt['warnings'],
561
		'help' => '',
562
		'description' => $txt['warnings_desc'],
563
	);
564
565
	prepareDBSettingContext($config_vars);
566
}
567
568
/**
569
 * Let's try keep the spam to a minimum ah Thantos?
570
 * @param bool $return_config Whether or not to return the config_vars array (used for admin search)
571
 * @return void|array Returns nothing or returns the $config_vars array if $return_config is true
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use array<*,string[]|array|s...ray<string|array>>|null.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
572
 */
573
function ModifyAntispamSettings($return_config = false)
574
{
575
	global $txt, $scripturl, $context, $modSettings, $smcFunc, $language, $sourcedir;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
576
577
	loadLanguage('Help');
578
	loadLanguage('ManageSettings');
579
580
	// Generate a sample registration image.
581
	$context['use_graphic_library'] = in_array('gd', get_loaded_extensions());
582
	$context['verification_image_href'] = $scripturl . '?action=verificationcode;rand=' . md5(mt_rand());
583
584
	$config_vars = array(
585
				array('check', 'reg_verification'),
586
				array('check', 'search_enable_captcha'),
587
				// This, my friend, is a cheat :p
588
				'guest_verify' => array('check', 'guests_require_captcha', 'subtext' => $txt['setting_guests_require_captcha_desc']),
589
				array('int', 'posts_require_captcha', 'subtext' => $txt['posts_require_captcha_desc'], 'onchange' => 'if (this.value > 0){ document.getElementById(\'guests_require_captcha\').checked = true; document.getElementById(\'guests_require_captcha\').disabled = true;} else {document.getElementById(\'guests_require_captcha\').disabled = false;}'),
590
			'',
591
			// PM Settings
592
				'pm1' => array('int', 'max_pm_recipients', 'subtext' => $txt['max_pm_recipients_note']),
593
				'pm2' => array('int', 'pm_posts_verification', 'subtext' => $txt['pm_posts_verification_note']),
594
				'pm3' => array('int', 'pm_posts_per_hour', 'subtext' => $txt['pm_posts_per_hour_note']),
595
			// Visual verification.
596
			array('title', 'configure_verification_means'),
597
			array('desc', 'configure_verification_means_desc'),
598
				'vv' => array('select', 'visual_verification_type', array($txt['setting_image_verification_off'], $txt['setting_image_verification_vsimple'], $txt['setting_image_verification_simple'], $txt['setting_image_verification_medium'], $txt['setting_image_verification_high'], $txt['setting_image_verification_extreme']), 'subtext' => $txt['setting_visual_verification_type_desc'], 'onchange' => $context['use_graphic_library'] ? 'refreshImages();' : ''),
599
			// reCAPTCHA
600
			array('title', 'recaptcha_configure'),
601
			array('desc', 'recaptcha_configure_desc', 'class' => 'windowbg'),
602
				array('check', 'recaptcha_enabled', 'subtext' => $txt['recaptcha_enable_desc']),
603
				array('text', 'recaptcha_site_key', 'subtext' => $txt['recaptcha_site_key_desc']),
604
				array('text', 'recaptcha_secret_key', 'subtext' => $txt['recaptcha_secret_key_desc']),
605
				array('select', 'recaptcha_theme', array('light' => $txt['recaptcha_theme_light'], 'dark' => $txt['recaptcha_theme_dark'])),
606
			// Clever Thomas, who is looking sheepy now? Not I, the mighty sword swinger did say.
607
			array('title', 'setup_verification_questions'),
608
			array('desc', 'setup_verification_questions_desc'),
609
				array('int', 'qa_verification_number', 'subtext' => $txt['setting_qa_verification_number_desc']),
610
				array('callback', 'question_answer_list'),
611
	);
612
613
	call_integration_hook('integrate_spam_settings', array(&$config_vars));
614
615
	if ($return_config)
616
		return $config_vars;
617
618
	// You need to be an admin to edit settings!
619
	isAllowedTo('admin_forum');
620
621
	// Firstly, figure out what languages we're dealing with, and do a little processing for the form's benefit.
622
	getLanguages();
623
	$context['qa_languages'] = array();
624
	foreach ($context['languages'] as $lang_id => $lang)
625
	{
626
		$lang_id = strtr($lang_id, array('-utf8' => ''));
627
		$lang['name'] = strtr($lang['name'], array('-utf8' => ''));
628
		$context['qa_languages'][$lang_id] = $lang;
629
	}
630
631
	// Secondly, load any questions we currently have.
632
	$context['question_answers'] = array();
633
	$request = $smcFunc['db_query']('', '
634
		SELECT id_question, lngfile, question, answers
635
		FROM {db_prefix}qanda'
636
	);
637
	while ($row = $smcFunc['db_fetch_assoc']($request))
638
	{
639
		$lang = strtr($row['lngfile'], array('-utf8' => ''));
640
		$context['question_answers'][$row['id_question']] = array(
641
			'lngfile' => $lang,
642
			'question' => $row['question'],
643
			'answers' => $smcFunc['json_decode']($row['answers'], true),
644
		);
645
		$context['qa_by_lang'][$lang][] = $row['id_question'];
646
	}
647
648
	if (empty($context['qa_by_lang'][strtr($language, array('-utf8' => ''))]) && !empty($context['question_answers']))
649
	{
650
		if (empty($context['settings_insert_above']))
651
			$context['settings_insert_above'] = '';
652
653
		$context['settings_insert_above'] .= '<div class="noticebox">' . sprintf($txt['question_not_defined'], $context['languages'][$language]['name']) . '</div>';
654
	}
655
656
	// Thirdly, push some JavaScript for the form to make it work.
657
	addInlineJavaScript('
658
	var nextrow = ' . (!empty($context['question_answers']) ? max(array_keys($context['question_answers'])) + 1 : 1) . ';
659
	$(".qa_link a").click(function() {
660
		var id = $(this).parent().attr("id").substring(6);
661
		$("#qa_fs_" + id).show();
662
		$(this).parent().hide();
663
	});
664
	$(".qa_fieldset legend a").click(function() {
665
		var id = $(this).closest("fieldset").attr("id").substring(6);
666
		$("#qa_dt_" + id).show();
667
		$(this).closest("fieldset").hide();
668
	});
669
	$(".qa_add_question a").click(function() {
670
		var id = $(this).closest("fieldset").attr("id").substring(6);
671
		$(\'<dt><input type="text" name="question[\' + id + \'][\' + nextrow + \']" value="" size="50" class="verification_question"></dt><dd><input type="text" name="answer[\' + id + \'][\' + nextrow + \'][]" value="" size="50" class="verification_answer" / ><div class="qa_add_answer"><a href="javascript:void(0);">[ \' + ' . JavaScriptEscape($txt['setup_verification_add_answer']) . ' + \' ]</a></div></dd>\').insertBefore($(this).parent());
672
		nextrow++;
673
	});
674
	$(".qa_add_answer a").click(function() {
675
		var attr = $(this).closest("dd").find(".verification_answer:last").attr("name");
676
		$(\'<input type="text" name="\' + attr + \'" value="" size="50" class="verification_answer">\').insertBefore($(this).closest("div"));
677
		return false;
678
	});
679
	$("#qa_dt_' . strtr($language, array('-utf8' => '')) . ' a").click();', true);
680
681
	// Will need the utility functions from here.
682
	require_once($sourcedir . '/ManageServer.php');
683
684
	// Saving?
685
	if (isset($_GET['save']))
686
	{
687
		checkSession();
688
689
		// Fix PM settings.
690
		$_POST['pm_spam_settings'] = (int) $_POST['max_pm_recipients'] . ',' . (int) $_POST['pm_posts_verification'] . ',' . (int) $_POST['pm_posts_per_hour'];
691
692
		// Hack in guest requiring verification!
693
		if (empty($_POST['posts_require_captcha']) && !empty($_POST['guests_require_captcha']))
694
			$_POST['posts_require_captcha'] = -1;
695
696
		$save_vars = $config_vars;
697
		unset($save_vars['pm1'], $save_vars['pm2'], $save_vars['pm3'], $save_vars['guest_verify']);
698
699
		$save_vars[] = array('text', 'pm_spam_settings');
700
701
		// Handle verification questions.
702
		$changes = array(
703
			'insert' => array(),
704
			'replace' => array(),
705
			'delete' => array(),
706
		);
707
		$qs_per_lang = array();
708
		foreach ($context['qa_languages'] as $lang_id => $dummy)
709
		{
710
			// If we had some questions for this language before, but don't now, delete everything from that language.
711
			if ((!isset($_POST['question'][$lang_id]) || !is_array($_POST['question'][$lang_id])) && !empty($context['qa_by_lang'][$lang_id]))
712
				$changes['delete'] = array_merge($questions['delete'], $context['qa_by_lang'][$lang_id]);
0 ignored issues
show
Bug introduced by
The variable $questions does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
713
714
			// Now step through and see if any existing questions no longer exist.
715
			if (!empty($context['qa_by_lang'][$lang_id]))
716
				foreach ($context['qa_by_lang'][$lang_id] as $q_id)
717
					if (empty($_POST['question'][$lang_id][$q_id]))
718
						$changes['delete'][] = $q_id;
719
720
			// Now let's see if there are new questions or ones that need updating.
721
			if (isset($_POST['question'][$lang_id]))
722
			{
723
				foreach ($_POST['question'][$lang_id] as $q_id => $question)
724
				{
725
					// Ignore junky ids.
726
					$q_id = (int) $q_id;
727
					if ($q_id <= 0)
728
						continue;
729
730
					// Check the question isn't empty (because they want to delete it?)
731 View Code Duplication
					if (empty($question) || trim($question) == '')
732
					{
733
						if (isset($context['question_answers'][$q_id]))
734
							$changes['delete'][] = $q_id;
735
						continue;
736
					}
737
					$question = $smcFunc['htmlspecialchars'](trim($question));
738
739
					// Get the answers. Firstly check there actually might be some.
740
					if (!isset($_POST['answer'][$lang_id][$q_id]) || !is_array($_POST['answer'][$lang_id][$q_id]))
741
					{
742
						if (isset($context['question_answers'][$q_id]))
743
							$changes['delete'][] = $q_id;
744
						continue;
745
					}
746
					// Now get them and check that they might be viable.
747
					$answers = array();
748
					foreach ($_POST['answer'][$lang_id][$q_id] as $answer)
749
						if (!empty($answer) && trim($answer) !== '')
750
							$answers[] = $smcFunc['htmlspecialchars'](trim($answer));
751 View Code Duplication
					if (empty($answers))
752
					{
753
						if (isset($context['question_answers'][$q_id]))
754
							$changes['delete'][] = $q_id;
755
						continue;
756
					}
757
					$answers = $smcFunc['json_encode']($answers);
758
759
					// At this point we know we have a question and some answers. What are we doing with it?
760
					if (!isset($context['question_answers'][$q_id]))
761
					{
762
						// New question. Now, we don't want to randomly consume ids, so we'll set those, rather than trusting the browser's supplied ids.
763
						$changes['insert'][] = array($lang_id, $question, $answers);
764
					}
765
					else
766
					{
767
						// It's an existing question. Let's see what's changed, if anything.
768
						if ($lang_id != $context['question_answers'][$q_id]['lngfile'] || $question != $context['question_answers'][$q_id]['question'] || $answers != $context['question_answers'][$q_id]['answers'])
769
							$changes['replace'][$q_id] = array('lngfile' => $lang_id, 'question' => $question, 'answers' => $answers);
770
					}
771
772
					if (!isset($qs_per_lang[$lang_id]))
773
						$qs_per_lang[$lang_id] = 0;
774
					$qs_per_lang[$lang_id]++;
775
				}
776
			}
777
		}
778
779
		// OK, so changes?
780
		if (!empty($changes['delete']))
781
		{
782
			$smcFunc['db_query']('', '
783
				DELETE FROM {db_prefix}qanda
784
				WHERE id_question IN ({array_int:questions})',
785
				array(
786
					'questions' => $changes['delete'],
787
				)
788
			);
789
		}
790
791
		if (!empty($changes['replace']))
792
		{
793
			foreach ($changes['replace'] as $q_id => $question)
794
			{
795
				$smcFunc['db_query']('', '
796
					UPDATE {db_prefix}qanda
797
					SET lngfile = {string:lngfile},
798
						question = {string:question},
799
						answers = {string:answers}
800
					WHERE id_question = {int:id_question}',
801
					array(
802
						'id_question' => $q_id,
803
						'lngfile' => $question['lngfile'],
804
						'question' => $question['question'],
805
						'answers' => $question['answers'],
806
					)
807
				);
808
			}
809
		}
810
811
		if (!empty($changes['insert']))
812
		{
813
			$smcFunc['db_insert']('insert',
814
				'{db_prefix}qanda',
815
				array('lngfile' => 'string-50', 'question' => 'string-255', 'answers' => 'string-65534'),
816
				$changes['insert'],
817
				array('id_question')
818
			);
819
		}
820
821
		// Lastly, the count of messages needs to be no more than the lowest number of questions for any one language.
822
		$count_questions = empty($qs_per_lang) ? 0 : min($qs_per_lang);
823
		if (empty($count_questions) || $_POST['qa_verification_number'] > $count_questions)
824
			$_POST['qa_verification_number'] = $count_questions;
825
826
		call_integration_hook('integrate_save_spam_settings', array(&$save_vars));
827
828
		// Now save.
829
		saveDBSettings($save_vars);
830
		$_SESSION['adm-save'] = true;
831
832
		cache_put_data('verificationQuestions', null, 300);
833
834
		redirectexit('action=admin;area=antispam');
835
	}
836
837
	$character_range = array_merge(range('A', 'H'), array('K', 'M', 'N', 'P', 'R'), range('T', 'Y'));
838
	$_SESSION['visual_verification_code'] = '';
839
	for ($i = 0; $i < 6; $i++)
840
		$_SESSION['visual_verification_code'] .= $character_range[array_rand($character_range)];
841
842
	// Some javascript for CAPTCHA.
843
	$context['settings_post_javascript'] = '';
844
	if ($context['use_graphic_library'])
845
		$context['settings_post_javascript'] .= '
846
		function refreshImages()
847
		{
848
			var imageType = document.getElementById(\'visual_verification_type\').value;
849
			document.getElementById(\'verification_image\').src = \'' . $context['verification_image_href'] . ';type=\' + imageType;
850
		}';
851
852
	// Show the image itself, or text saying we can't.
853
	if ($context['use_graphic_library'])
854
		$config_vars['vv']['postinput'] = '<br><img src="' . $context['verification_image_href'] . ';type=' . (empty($modSettings['visual_verification_type']) ? 0 : $modSettings['visual_verification_type']) . '" alt="' . $txt['setting_image_verification_sample'] . '" id="verification_image"><br>';
855
	else
856
		$config_vars['vv']['postinput'] = '<br><span class="smalltext">' . $txt['setting_image_verification_nogd'] . '</span>';
857
858
	// Hack for PM spam settings.
859
	list ($modSettings['max_pm_recipients'], $modSettings['pm_posts_verification'], $modSettings['pm_posts_per_hour']) = explode(',', $modSettings['pm_spam_settings']);
860
861
	// Hack for guests requiring verification.
862
	$modSettings['guests_require_captcha'] = !empty($modSettings['posts_require_captcha']);
863
	$modSettings['posts_require_captcha'] = !isset($modSettings['posts_require_captcha']) || $modSettings['posts_require_captcha'] == -1 ? 0 : $modSettings['posts_require_captcha'];
864
865
	// Some minor javascript for the guest post setting.
866
	if ($modSettings['posts_require_captcha'])
867
		$context['settings_post_javascript'] .= '
868
		document.getElementById(\'guests_require_captcha\').disabled = true;';
869
870
	// And everything else.
871
	$context['post_url'] = $scripturl . '?action=admin;area=antispam;save';
872
	$context['settings_title'] = $txt['antispam_Settings'];
873
	$context['page_title'] = $txt['antispam_title'];
874
	$context['sub_template'] = 'show_settings';
875
876
	$context[$context['admin_menu_name']]['tab_data'] = array(
877
		'title' => $txt['antispam_title'],
878
		'description' => $txt['antispam_Settings_desc'],
879
	);
880
881
	prepareDBSettingContext($config_vars);
882
}
883
884
/**
885
 * You'll never guess what this function does...
886
 *
887
 * @param bool $return_config Whether or not to return the config_vars array (used for admin search)
888
 * @return void|array Returns nothing or returns the $config_vars array if $return_config is true
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use array<string[]|string|array|array<*,string>>|null.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
889
 */
890
function ModifySignatureSettings($return_config = false)
891
{
892
	global $context, $txt, $modSettings, $sig_start, $smcFunc, $scripturl;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
893
894
	$config_vars = array(
895
			// Are signatures even enabled?
896
			array('check', 'signature_enable'),
897
		'',
898
			// Tweaking settings!
899
			array('int', 'signature_max_length', 'subtext' => $txt['zero_for_no_limit']),
900
			array('int', 'signature_max_lines', 'subtext' => $txt['zero_for_no_limit']),
901
			array('int', 'signature_max_font_size', 'subtext' => $txt['zero_for_no_limit']),
902
			array('check', 'signature_allow_smileys', 'onclick' => 'document.getElementById(\'signature_max_smileys\').disabled = !this.checked;'),
903
			array('int', 'signature_max_smileys', 'subtext' => $txt['zero_for_no_limit']),
904
		'',
905
			// Image settings.
906
			array('int', 'signature_max_images', 'subtext' => $txt['signature_max_images_note']),
907
			array('int', 'signature_max_image_width', 'subtext' => $txt['zero_for_no_limit']),
908
			array('int', 'signature_max_image_height', 'subtext' => $txt['zero_for_no_limit']),
909
		'',
910
			array('bbc', 'signature_bbc'),
911
	);
912
913
	call_integration_hook('integrate_signature_settings', array(&$config_vars));
914
915
	if ($return_config)
916
		return $config_vars;
917
918
	// Setup the template.
919
	$context['page_title'] = $txt['signature_settings'];
920
	$context['sub_template'] = 'show_settings';
921
922
	// Disable the max smileys option if we don't allow smileys at all!
923
	$context['settings_post_javascript'] = 'document.getElementById(\'signature_max_smileys\').disabled = !document.getElementById(\'signature_allow_smileys\').checked;';
924
925
	// Load all the signature settings.
926
	list ($sig_limits, $sig_bbc) = explode(':', $modSettings['signature_settings']);
927
	$sig_limits = explode(',', $sig_limits);
928
	$disabledTags = !empty($sig_bbc) ? explode(',', $sig_bbc) : array();
929
930
	// Applying to ALL signatures?!!
931
	if (isset($_GET['apply']))
932
	{
933
		// Security!
934
		checkSession('get');
935
936
		$sig_start = time();
937
		// This is horrid - but I suppose some people will want the option to do it.
938
		$_GET['step'] = isset($_GET['step']) ? (int) $_GET['step'] : 0;
939
		$done = false;
940
941
		$request = $smcFunc['db_query']('', '
942
			SELECT MAX(id_member)
943
			FROM {db_prefix}members',
944
			array(
945
			)
946
		);
947
		list ($context['max_member']) = $smcFunc['db_fetch_row']($request);
948
		$smcFunc['db_free_result']($request);
949
950
		while (!$done)
951
		{
952
			$changes = array();
953
954
			$request = $smcFunc['db_query']('', '
955
				SELECT id_member, signature
956
				FROM {db_prefix}members
957
				WHERE id_member BETWEEN {int:step} AND {int:step} + 49
958
					AND id_group != {int:admin_group}
959
					AND FIND_IN_SET({int:admin_group}, additional_groups) = 0',
960
				array(
961
					'admin_group' => 1,
962
					'step' => $_GET['step'],
963
				)
964
			);
965
			while ($row = $smcFunc['db_fetch_assoc']($request))
966
			{
967
				// Apply all the rules we can realistically do.
968
				$sig = strtr($row['signature'], array('<br>' => "\n"));
969
970
				// Max characters...
971
				if (!empty($sig_limits[1]))
972
					$sig = $smcFunc['substr']($sig, 0, $sig_limits[1]);
973
				// Max lines...
974
				if (!empty($sig_limits[2]))
975
				{
976
					$count = 0;
977
					for ($i = 0; $i < strlen($sig); $i++)
978
					{
979
						if ($sig[$i] == "\n")
980
						{
981
							$count++;
982
							if ($count >= $sig_limits[2])
983
								$sig = substr($sig, 0, $i) . strtr(substr($sig, $i), array("\n" => ' '));
984
						}
985
					}
986
				}
987
988
				if (!empty($sig_limits[7]) && preg_match_all('~\[size=([\d\.]+)?(px|pt|em|x-large|larger)~i', $sig, $matches) !== false && isset($matches[2]))
989
				{
990
					foreach ($matches[1] as $ind => $size)
991
					{
992
						$limit_broke = 0;
993
						// Attempt to allow all sizes of abuse, so to speak.
994 View Code Duplication
						if ($matches[2][$ind] == 'px' && $size > $sig_limits[7])
995
							$limit_broke = $sig_limits[7] . 'px';
996
						elseif ($matches[2][$ind] == 'pt' && $size > ($sig_limits[7] * 0.75))
997
							$limit_broke = ((int) $sig_limits[7] * 0.75) . 'pt';
998
						elseif ($matches[2][$ind] == 'em' && $size > ((float) $sig_limits[7] / 16))
999
							$limit_broke = ((float) $sig_limits[7] / 16) . 'em';
1000
						elseif ($matches[2][$ind] != 'px' && $matches[2][$ind] != 'pt' && $matches[2][$ind] != 'em' && $sig_limits[7] < 18)
1001
							$limit_broke = 'large';
1002
1003
						if ($limit_broke)
1004
							$sig = str_replace($matches[0][$ind], '[size=' . $sig_limits[7] . 'px', $sig);
1005
					}
1006
				}
1007
1008
				// Stupid images - this is stupidly, stupidly challenging.
1009
				if ((!empty($sig_limits[3]) || !empty($sig_limits[5]) || !empty($sig_limits[6])))
1010
				{
1011
					$replaces = array();
1012
					$img_count = 0;
1013
					// Get all BBC tags...
1014
					preg_match_all('~\[img(\s+width=([\d]+))?(\s+height=([\d]+))?(\s+width=([\d]+))?\s*\](?:<br>)*([^<">]+?)(?:<br>)*\[/img\]~i', $sig, $matches);
1015
					// ... and all HTML ones.
1016
					preg_match_all('~&lt;img\s+src=(?:&quot;)?((?:http://|ftp://|https://|ftps://).+?)(?:&quot;)?(?:\s+alt=(?:&quot;)?(.*?)(?:&quot;)?)?(?:\s?/)?&gt;~i', $sig, $matches2, PREG_PATTERN_ORDER);
1017
					// And stick the HTML in the BBC.
1018 View Code Duplication
					if (!empty($matches2))
1019
					{
1020
						foreach ($matches2[0] as $ind => $dummy)
1021
						{
1022
							$matches[0][] = $matches2[0][$ind];
1023
							$matches[1][] = '';
1024
							$matches[2][] = '';
1025
							$matches[3][] = '';
1026
							$matches[4][] = '';
1027
							$matches[5][] = '';
1028
							$matches[6][] = '';
1029
							$matches[7][] = $matches2[1][$ind];
1030
						}
1031
					}
1032
					// Try to find all the images!
1033
					if (!empty($matches))
1034
					{
1035
						$image_count_holder = array();
1036
						foreach ($matches[0] as $key => $image)
1037
						{
1038
							$width = -1; $height = -1;
0 ignored issues
show
Coding Style introduced by
It is generally recommended to place each PHP statement on a line by itself.

Let’s take a look at an example:

// Bad
$a = 5; $b = 6; $c = 7;

// Good
$a = 5;
$b = 6;
$c = 7;
Loading history...
1039
							$img_count++;
1040
							// Too many images?
1041
							if (!empty($sig_limits[3]) && $img_count > $sig_limits[3])
1042
							{
1043
								// If we've already had this before we only want to remove the excess.
1044
								if (isset($image_count_holder[$image]))
1045
								{
1046
									$img_offset = -1;
1047
									$rep_img_count = 0;
1048
									while ($img_offset !== false)
1049
									{
1050
										$img_offset = strpos($sig, $image, $img_offset + 1);
1051
										$rep_img_count++;
1052
										if ($rep_img_count > $image_count_holder[$image])
1053
										{
1054
											// Only replace the excess.
1055
											$sig = substr($sig, 0, $img_offset) . str_replace($image, '', substr($sig, $img_offset));
1056
											// Stop looping.
1057
											$img_offset = false;
1058
										}
1059
									}
1060
								}
1061
								else
1062
									$replaces[$image] = '';
1063
1064
								continue;
1065
							}
1066
1067
							// Does it have predefined restraints? Width first.
1068 View Code Duplication
							if ($matches[6][$key])
1069
								$matches[2][$key] = $matches[6][$key];
1070 View Code Duplication
							if ($matches[2][$key] && $sig_limits[5] && $matches[2][$key] > $sig_limits[5])
1071
							{
1072
								$width = $sig_limits[5];
1073
								$matches[4][$key] = $matches[4][$key] * ($width / $matches[2][$key]);
1074
							}
1075
							elseif ($matches[2][$key])
1076
								$width = $matches[2][$key];
1077
							// ... and height.
1078 View Code Duplication
							if ($matches[4][$key] && $sig_limits[6] && $matches[4][$key] > $sig_limits[6])
1079
							{
1080
								$height = $sig_limits[6];
1081
								if ($width != -1)
1082
									$width = $width * ($height / $matches[4][$key]);
1083
							}
1084
							elseif ($matches[4][$key])
1085
								$height = $matches[4][$key];
1086
1087
							// If the dimensions are still not fixed - we need to check the actual image.
1088 View Code Duplication
							if (($width == -1 && $sig_limits[5]) || ($height == -1 && $sig_limits[6]))
1089
							{
1090
								$sizes = url_image_size($matches[7][$key]);
1091
								if (is_array($sizes))
1092
								{
1093
									// Too wide?
1094
									if ($sizes[0] > $sig_limits[5] && $sig_limits[5])
1095
									{
1096
										$width = $sig_limits[5];
1097
										$sizes[1] = $sizes[1] * ($width / $sizes[0]);
1098
									}
1099
									// Too high?
1100
									if ($sizes[1] > $sig_limits[6] && $sig_limits[6])
1101
									{
1102
										$height = $sig_limits[6];
1103
										if ($width == -1)
1104
											$width = $sizes[0];
1105
										$width = $width * ($height / $sizes[1]);
1106
									}
1107
									elseif ($width != -1)
1108
										$height = $sizes[1];
1109
								}
1110
							}
1111
1112
							// Did we come up with some changes? If so remake the string.
1113 View Code Duplication
							if ($width != -1 || $height != -1)
1114
							{
1115
								$replaces[$image] = '[img' . ($width != -1 ? ' width=' . round($width) : '') . ($height != -1 ? ' height=' . round($height) : '') . ']' . $matches[7][$key] . '[/img]';
1116
							}
1117
1118
							// Record that we got one.
1119
							$image_count_holder[$image] = isset($image_count_holder[$image]) ? $image_count_holder[$image] + 1 : 1;
1120
						}
1121
						if (!empty($replaces))
1122
							$sig = str_replace(array_keys($replaces), array_values($replaces), $sig);
1123
					}
1124
				}
1125
				// Try to fix disabled tags.
1126
				if (!empty($disabledTags))
1127
				{
1128
					$sig = preg_replace('~\[(?:' . implode('|', $disabledTags) . ').+?\]~i', '', $sig);
1129
					$sig = preg_replace('~\[/(?:' . implode('|', $disabledTags) . ')\]~i', '', $sig);
1130
				}
1131
1132
				$sig = strtr($sig, array("\n" => '<br>'));
1133
				call_integration_hook('integrate_apply_signature_settings', array(&$sig, $sig_limits, $disabledTags));
1134
				if ($sig != $row['signature'])
1135
					$changes[$row['id_member']] = $sig;
1136
			}
1137
			if ($smcFunc['db_num_rows']($request) == 0)
1138
				$done = true;
1139
			$smcFunc['db_free_result']($request);
1140
1141
			// Do we need to delete what we have?
1142 View Code Duplication
			if (!empty($changes))
1143
			{
1144
				foreach ($changes as $id => $sig)
1145
					$smcFunc['db_query']('', '
1146
						UPDATE {db_prefix}members
1147
						SET signature = {string:signature}
1148
						WHERE id_member = {int:id_member}',
1149
						array(
1150
							'id_member' => $id,
1151
							'signature' => $sig,
1152
						)
1153
					);
1154
			}
1155
1156
			$_GET['step'] += 50;
1157
			if (!$done)
1158
				pauseSignatureApplySettings();
1159
		}
1160
		$settings_applied = true;
1161
	}
1162
1163
	$context['signature_settings'] = array(
1164
		'enable' => isset($sig_limits[0]) ? $sig_limits[0] : 0,
1165
		'max_length' => isset($sig_limits[1]) ? $sig_limits[1] : 0,
1166
		'max_lines' => isset($sig_limits[2]) ? $sig_limits[2] : 0,
1167
		'max_images' => isset($sig_limits[3]) ? $sig_limits[3] : 0,
1168
		'allow_smileys' => isset($sig_limits[4]) && $sig_limits[4] == -1 ? 0 : 1,
1169
		'max_smileys' => isset($sig_limits[4]) && $sig_limits[4] != -1 ? $sig_limits[4] : 0,
1170
		'max_image_width' => isset($sig_limits[5]) ? $sig_limits[5] : 0,
1171
		'max_image_height' => isset($sig_limits[6]) ? $sig_limits[6] : 0,
1172
		'max_font_size' => isset($sig_limits[7]) ? $sig_limits[7] : 0,
1173
	);
1174
1175
	// Temporarily make each setting a modSetting!
1176
	foreach ($context['signature_settings'] as $key => $value)
1177
		$modSettings['signature_' . $key] = $value;
1178
1179
	// Make sure we check the right tags!
1180
	$modSettings['bbc_disabled_signature_bbc'] = $disabledTags;
1181
1182
	// Saving?
1183
	if (isset($_GET['save']))
1184
	{
1185
		checkSession();
1186
1187
		// Clean up the tag stuff!
1188
		$bbcTags = array();
1189
		foreach (parse_bbc(false) as $tag)
0 ignored issues
show
Documentation introduced by
false is of type boolean, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
Bug introduced by
The expression parse_bbc(false) of type string is not traversable.
Loading history...
1190
			$bbcTags[] = $tag['tag'];
1191
1192 View Code Duplication
		if (!isset($_POST['signature_bbc_enabledTags']))
1193
			$_POST['signature_bbc_enabledTags'] = array();
1194
		elseif (!is_array($_POST['signature_bbc_enabledTags']))
1195
			$_POST['signature_bbc_enabledTags'] = array($_POST['signature_bbc_enabledTags']);
1196
1197
		$sig_limits = array();
1198
		foreach ($context['signature_settings'] as $key => $value)
1199
		{
1200
			if ($key == 'allow_smileys')
1201
				continue;
1202
			elseif ($key == 'max_smileys' && empty($_POST['signature_allow_smileys']))
1203
				$sig_limits[] = -1;
1204
			else
1205
				$sig_limits[] = !empty($_POST['signature_' . $key]) ? max(1, (int) $_POST['signature_' . $key]) : 0;
1206
		}
1207
1208
		call_integration_hook('integrate_save_signature_settings', array(&$sig_limits, &$bbcTags));
1209
1210
		$_POST['signature_settings'] = implode(',', $sig_limits) . ':' . implode(',', array_diff($bbcTags, $_POST['signature_bbc_enabledTags']));
1211
1212
		// Even though we have practically no settings let's keep the convention going!
1213
		$save_vars = array();
1214
		$save_vars[] = array('text', 'signature_settings');
1215
1216
		saveDBSettings($save_vars);
1217
		$_SESSION['adm-save'] = true;
1218
		redirectexit('action=admin;area=featuresettings;sa=sig');
1219
	}
1220
1221
	$context['post_url'] = $scripturl . '?action=admin;area=featuresettings;save;sa=sig';
1222
	$context['settings_title'] = $txt['signature_settings'];
1223
1224
	$context['settings_message'] = !empty($settings_applied) ? '<div class="infobox">' . $txt['signature_settings_applied'] . '</div>' : '<p class="centertext">' . sprintf($txt['signature_settings_warning'], $context['session_id'], $context['session_var']) . '</p>';
1225
1226
	prepareDBSettingContext($config_vars);
1227
}
1228
1229
/**
1230
 * Just pause the signature applying thing.
1231
 */
1232
function pauseSignatureApplySettings()
1233
{
1234
	global $context, $txt, $sig_start;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
1235
1236
	// Try get more time...
1237
	@set_time_limit(600);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
1238
	if (function_exists('apache_reset_timeout'))
1239
		@apache_reset_timeout();
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
1240
1241
	// Have we exhausted all the time we allowed?
1242
	if (time() - array_sum(explode(' ', $sig_start)) < 3)
1243
		return;
1244
1245
	$context['continue_get_data'] = '?action=admin;area=featuresettings;sa=sig;apply;step=' . $_GET['step'] . ';' . $context['session_var'] . '=' . $context['session_id'];
1246
	$context['page_title'] = $txt['not_done_title'];
1247
	$context['continue_post_data'] = '';
1248
	$context['continue_countdown'] = '2';
1249
	$context['sub_template'] = 'not_done';
1250
1251
	// Specific stuff to not break this template!
1252
	$context[$context['admin_menu_name']]['current_subsection'] = 'sig';
1253
1254
	// Get the right percent.
1255
	$context['continue_percent'] = round(($_GET['step'] / $context['max_member']) * 100);
1256
1257
	// Never more than 100%!
1258
	$context['continue_percent'] = min($context['continue_percent'], 100);
1259
1260
	obExit();
1261
}
1262
1263
/**
1264
 * Show all the custom profile fields available to the user.
1265
 */
1266
function ShowCustomProfiles()
1267
{
1268
	global $txt, $scripturl, $context;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
1269
	global $sourcedir;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
1270
1271
	$context['page_title'] = $txt['custom_profile_title'];
1272
	$context['sub_template'] = 'show_custom_profile';
1273
1274
	// What about standard fields they can tweak?
1275
	$standard_fields = array('website', 'personal_text', 'timezone', 'posts', 'warning_status');
1276
	// What fields can't you put on the registration page?
1277
	$context['fields_no_registration'] = array('posts', 'warning_status');
1278
1279
	// Are we saving any standard field changes?
1280
	if (isset($_POST['save']))
1281
	{
1282
		checkSession();
1283
		validateToken('admin-scp');
1284
1285
		// Do the active ones first.
1286
		$disable_fields = array_flip($standard_fields);
1287
		if (!empty($_POST['active']))
1288
		{
1289
			foreach ($_POST['active'] as $value)
1290
				if (isset($disable_fields[$value]))
1291
					unset($disable_fields[$value]);
1292
		}
1293
		// What we have left!
1294
		$changes['disabled_profile_fields'] = empty($disable_fields) ? '' : implode(',', array_keys($disable_fields));
0 ignored issues
show
Coding Style Comprehensibility introduced by
$changes was never initialized. Although not strictly required by PHP, it is generally a good practice to add $changes = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
1295
1296
		// Things we want to show on registration?
1297
		$reg_fields = array();
1298
		if (!empty($_POST['reg']))
1299
		{
1300
			foreach ($_POST['reg'] as $value)
1301
				if (in_array($value, $standard_fields) && !isset($disable_fields[$value]))
1302
					$reg_fields[] = $value;
1303
		}
1304
		// What we have left!
1305
		$changes['registration_fields'] = empty($reg_fields) ? '' : implode(',', $reg_fields);
1306
1307
		$_SESSION['adm-save'] = true;
1308
		if (!empty($changes))
1309
			updateSettings($changes);
1310
	}
1311
1312
	createToken('admin-scp');
1313
1314
	// Need to know the max order for custom fields
1315
	$context['custFieldsMaxOrder'] = custFieldsMaxOrder();
1316
1317
	require_once($sourcedir . '/Subs-List.php');
1318
1319
	$listOptions = array(
1320
		'id' => 'standard_profile_fields',
1321
		'title' => $txt['standard_profile_title'],
1322
		'base_href' => $scripturl . '?action=admin;area=featuresettings;sa=profile',
1323
		'get_items' => array(
1324
			'function' => 'list_getProfileFields',
1325
			'params' => array(
1326
				true,
1327
			),
1328
		),
1329
		'columns' => array(
1330
			'field' => array(
1331
				'header' => array(
1332
					'value' => $txt['standard_profile_field'],
1333
				),
1334
				'data' => array(
1335
					'db' => 'label',
1336
					'style' => 'width: 60%;',
1337
				),
1338
			),
1339
			'active' => array(
1340
				'header' => array(
1341
					'value' => $txt['custom_edit_active'],
1342
					'class' => 'centercol',
1343
				),
1344
				'data' => array(
1345 View Code Duplication
					'function' => function ($rowData)
1346
					{
1347
						$isChecked = $rowData['disabled'] ? '' : ' checked';
1348
						$onClickHandler = $rowData['can_show_register'] ? sprintf(' onclick="document.getElementById(\'reg_%1$s\').disabled = !this.checked;"', $rowData['id']) : '';
1349
						return sprintf('<input type="checkbox" name="active[]" id="active_%1$s" value="%1$s" %2$s%3$s>', $rowData['id'], $isChecked, $onClickHandler);
1350
					},
1351
					'style' => 'width: 20%;',
1352
					'class' => 'centercol',
1353
				),
1354
			),
1355
			'show_on_registration' => array(
1356
				'header' => array(
1357
					'value' => $txt['custom_edit_registration'],
1358
					'class' => 'centercol',
1359
				),
1360
				'data' => array(
1361 View Code Duplication
					'function' => function ($rowData)
1362
					{
1363
						$isChecked = $rowData['on_register'] && !$rowData['disabled'] ? ' checked' : '';
1364
						$isDisabled = $rowData['can_show_register'] ? '' : ' disabled';
1365
						return sprintf('<input type="checkbox" name="reg[]" id="reg_%1$s" value="%1$s" %2$s%3$s>', $rowData['id'], $isChecked, $isDisabled);
1366
					},
1367
					'style' => 'width: 20%;',
1368
					'class' => 'centercol',
1369
				),
1370
			),
1371
		),
1372
		'form' => array(
1373
			'href' => $scripturl . '?action=admin;area=featuresettings;sa=profile',
1374
			'name' => 'standardProfileFields',
1375
			'token' => 'admin-scp',
1376
		),
1377
		'additional_rows' => array(
1378
			array(
1379
				'position' => 'below_table_data',
1380
				'value' => '<input type="submit" name="save" value="' . $txt['save'] . '" class="button">',
1381
			),
1382
		),
1383
	);
1384
	createList($listOptions);
1385
1386
	$listOptions = array(
1387
		'id' => 'custom_profile_fields',
1388
		'title' => $txt['custom_profile_title'],
1389
		'base_href' => $scripturl . '?action=admin;area=featuresettings;sa=profile',
1390
		'default_sort_col' => 'field_order',
1391
		'no_items_label' => $txt['custom_profile_none'],
1392
		'items_per_page' => 25,
1393
		'get_items' => array(
1394
			'function' => 'list_getProfileFields',
1395
			'params' => array(
1396
				false,
1397
			),
1398
		),
1399
		'get_count' => array(
1400
			'function' => 'list_getProfileFieldSize',
1401
		),
1402
		'columns' => array(
1403
			'field_order' => array(
1404
				'header' => array(
1405
					'value' => $txt['custom_profile_fieldorder'],
1406
				),
1407
				'data' => array(
1408
					'function' => function ($rowData) use ($context, $txt, $scripturl)
1409
					{
1410
						$return = '<p class="centertext bold_text">'. $rowData['field_order'] .'<br>';
1411
1412 View Code Duplication
						if ($rowData['field_order'] > 1)
1413
							$return .= '<a href="' . $scripturl . '?action=admin;area=featuresettings;sa=profileedit;fid=' . $rowData['id_field'] . ';move=up"><span class="toggle_up" title="'. $txt['custom_edit_order_move'] .' '. $txt['custom_edit_order_up'] .'"></span></a>';
1414
1415 View Code Duplication
						if ($rowData['field_order'] < $context['custFieldsMaxOrder'])
1416
							$return .= '<a href="' . $scripturl . '?action=admin;area=featuresettings;sa=profileedit;fid=' . $rowData['id_field'] . ';move=down"><span class="toggle_down" title="'. $txt['custom_edit_order_move'] .' '. $txt['custom_edit_order_down'] .'"></span></a>';
1417
1418
						$return .= '</p>';
1419
1420
						return $return;
1421
					},
1422
					'style' => 'width: 12%;',
1423
				),
1424
				'sort' => array(
1425
					'default' => 'field_order',
1426
					'reverse' => 'field_order DESC',
1427
				),
1428
			),
1429
			'field_name' => array(
1430
				'header' => array(
1431
					'value' => $txt['custom_profile_fieldname'],
1432
				),
1433
				'data' => array(
1434
					'function' => function ($rowData) use ($scripturl)
1435
					{
1436
						return sprintf('<a href="%1$s?action=admin;area=featuresettings;sa=profileedit;fid=%2$d">%3$s</a><div class="smalltext">%4$s</div>', $scripturl, $rowData['id_field'], $rowData['field_name'], $rowData['field_desc']);
1437
					},
1438
					'style' => 'width: 62%;',
1439
				),
1440
				'sort' => array(
1441
					'default' => 'field_name',
1442
					'reverse' => 'field_name DESC',
1443
				),
1444
			),
1445
			'field_type' => array(
1446
				'header' => array(
1447
					'value' => $txt['custom_profile_fieldtype'],
1448
				),
1449
				'data' => array(
1450
					'function' => function ($rowData) use ($txt)
1451
					{
1452
						$textKey = sprintf('custom_profile_type_%1$s', $rowData['field_type']);
1453
						return isset($txt[$textKey]) ? $txt[$textKey] : $textKey;
1454
					},
1455
					'style' => 'width: 15%;',
1456
					'class' => 'hidden',
1457
				),
1458
				'sort' => array(
1459
					'default' => 'field_type',
1460
					'reverse' => 'field_type DESC',
1461
				),
1462
			),
1463
			'active' => array(
1464
				'header' => array(
1465
					'value' => $txt['custom_profile_active'],
1466
				),
1467
				'data' => array(
1468
					'function' => function ($rowData) use ($txt)
1469
					{
1470
						return $rowData['active'] ? $txt['yes'] : $txt['no'];
1471
					},
1472
					'style' => 'width: 8%;',
1473
					'class' => 'hidden',
1474
				),
1475
				'sort' => array(
1476
					'default' => 'active DESC',
1477
					'reverse' => 'active',
1478
				),
1479
			),
1480
			'placement' => array(
1481
				'header' => array(
1482
					'value' => $txt['custom_profile_placement'],
1483
				),
1484
				'data' => array(
1485
					'function' => function ($rowData)
1486
					{
1487
						global $txt, $context;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
1488
1489
						return $txt['custom_profile_placement_' . (empty($rowData['placement']) ? 'standard' : $context['cust_profile_fields_placement'][$rowData['placement']])];
1490
					},
1491
					'style' => 'width: 8%;',
1492
					'class' => 'hidden',
1493
				),
1494
				'sort' => array(
1495
					'default' => 'placement DESC',
1496
					'reverse' => 'placement',
1497
				),
1498
			),
1499
			'show_on_registration' => array(
1500
				'data' => array(
1501
					'sprintf' => array(
1502
						'format' => '<a href="' . $scripturl . '?action=admin;area=featuresettings;sa=profileedit;fid=%1$s">' . $txt['modify'] . '</a>',
1503
						'params' => array(
1504
							'id_field' => false,
1505
						),
1506
					),
1507
					'style' => 'width: 15%;',
1508
				),
1509
			),
1510
		),
1511
		'form' => array(
1512
			'href' => $scripturl . '?action=admin;area=featuresettings;sa=profileedit',
1513
			'name' => 'customProfileFields',
1514
		),
1515
		'additional_rows' => array(
1516
			array(
1517
				'position' => 'below_table_data',
1518
				'value' => '<input type="submit" name="new" value="' . $txt['custom_profile_make_new'] . '" class="button">',
1519
			),
1520
		),
1521
	);
1522
	createList($listOptions);
1523
1524
	// There are two different ways we could get to this point. To keep it simple, they both do
1525
	// the same basic thing.
1526
	if (isset($_SESSION['adm-save']))
1527
	{
1528
		$context['saved_successful'] = true;
1529
		unset ($_SESSION['adm-save']);
1530
	}
1531
}
1532
1533
/**
1534
 * Callback for createList().
1535
 * @param int $start The item to start with (used for pagination purposes)
1536
 * @param int $items_per_page The number of items to display per page
1537
 * @param string $sort A string indicating how to sort the results
1538
 * @param bool $standardFields Whether or not to include standard fields as well
1539
 * @return array An array of info about the various profile fields
1540
 */
1541
function list_getProfileFields($start, $items_per_page, $sort, $standardFields)
1542
{
1543
	global $txt, $modSettings, $smcFunc;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
1544
1545
	$list = array();
1546
1547
	if ($standardFields)
1548
	{
1549
		$standard_fields = array('website', 'personal_text', 'timezone', 'posts', 'warning_status');
1550
		$fields_no_registration = array('posts', 'warning_status');
1551
		$disabled_fields = isset($modSettings['disabled_profile_fields']) ? explode(',', $modSettings['disabled_profile_fields']) : array();
1552
		$registration_fields = isset($modSettings['registration_fields']) ? explode(',', $modSettings['registration_fields']) : array();
1553
1554
		foreach ($standard_fields as $field)
1555
			$list[] = array(
1556
				'id' => $field,
1557
				'label' => isset($txt['standard_profile_field_' . $field]) ? $txt['standard_profile_field_' . $field] : (isset($txt[$field]) ? $txt[$field] : $field),
1558
				'disabled' => in_array($field, $disabled_fields),
1559
				'on_register' => in_array($field, $registration_fields) && !in_array($field, $fields_no_registration),
1560
				'can_show_register' => !in_array($field, $fields_no_registration),
1561
			);
1562
	}
1563
	else
1564
	{
1565
		// Load all the fields.
1566
		$request = $smcFunc['db_query']('', '
1567
			SELECT id_field, col_name, field_name, field_desc, field_type, field_order, active, placement
1568
			FROM {db_prefix}custom_fields
1569
			ORDER BY {raw:sort}
1570
			LIMIT {int:start}, {int:items_per_page}',
1571
			array(
1572
				'sort' => $sort,
1573
				'start' => $start,
1574
				'items_per_page' => $items_per_page,
1575
			)
1576
		);
1577
		while ($row = $smcFunc['db_fetch_assoc']($request))
1578
			$list[] = $row;
1579
		$smcFunc['db_free_result']($request);
1580
	}
1581
1582
	return $list;
1583
}
1584
1585
/**
1586
 * Callback for createList().
1587
 * @return int The total number of custom profile fields
1588
 */
1589
function list_getProfileFieldSize()
1590
{
1591
	global $smcFunc;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
1592
1593
	$request = $smcFunc['db_query']('', '
1594
		SELECT COUNT(*)
1595
		FROM {db_prefix}custom_fields',
1596
		array(
1597
		)
1598
	);
1599
1600
	list ($numProfileFields) = $smcFunc['db_fetch_row']($request);
1601
	$smcFunc['db_free_result']($request);
1602
1603
	return $numProfileFields;
1604
}
1605
1606
/**
1607
 * Edit some profile fields?
1608
 */
1609
function EditCustomProfiles()
1610
{
1611
	global $txt, $scripturl, $context, $smcFunc;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
1612
1613
	// Sort out the context!
1614
	$context['fid'] = isset($_GET['fid']) ? (int) $_GET['fid'] : 0;
1615
	$context[$context['admin_menu_name']]['current_subsection'] = 'profile';
1616
	$context['page_title'] = $context['fid'] ? $txt['custom_edit_title'] : $txt['custom_add_title'];
1617
	$context['sub_template'] = 'edit_profile_field';
1618
1619
	// Load the profile language for section names.
1620
	loadLanguage('Profile');
1621
1622
	// There's really only a few places we can go...
1623
	$move_to = array('up', 'down');
1624
1625
	// We need this for both moving and saving so put it right here.
1626
	$order_count = custFieldsMaxOrder();
1627
1628
	if ($context['fid'])
1629
	{
1630
		$request = $smcFunc['db_query']('', '
1631
			SELECT
1632
				id_field, col_name, field_name, field_desc, field_type, field_order, field_length, field_options,
1633
				show_reg, show_display, show_mlist, show_profile, private, active, default_value, can_search,
1634
				bbc, mask, enclose, placement
1635
			FROM {db_prefix}custom_fields
1636
			WHERE id_field = {int:current_field}',
1637
			array(
1638
				'current_field' => $context['fid'],
1639
			)
1640
		);
1641
		$context['field'] = array();
1642
		while ($row = $smcFunc['db_fetch_assoc']($request))
1643
		{
1644
			if ($row['field_type'] == 'textarea')
1645
				@list ($rows, $cols) = @explode(',', $row['default_value']);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
1646
			else
1647
			{
1648
				$rows = 3;
1649
				$cols = 30;
1650
			}
1651
1652
			$context['field'] = array(
1653
				'name' => $row['field_name'],
1654
				'desc' => $row['field_desc'],
1655
				'col_name' => $row['col_name'],
1656
				'profile_area' => $row['show_profile'],
1657
				'reg' => $row['show_reg'],
1658
				'display' => $row['show_display'],
1659
				'mlist' => $row['show_mlist'],
1660
				'type' => $row['field_type'],
1661
				'order' => $row['field_order'],
1662
				'max_length' => $row['field_length'],
1663
				'rows' => $rows,
1664
				'cols' => $cols,
1665
				'bbc' => $row['bbc'] ? true : false,
1666
				'default_check' => $row['field_type'] == 'check' && $row['default_value'] ? true : false,
1667
				'default_select' => $row['field_type'] == 'select' || $row['field_type'] == 'radio' ? $row['default_value'] : '',
1668
				'options' => strlen($row['field_options']) > 1 ? explode(',', $row['field_options']) : array('', '', ''),
1669
				'active' => $row['active'],
1670
				'private' => $row['private'],
1671
				'can_search' => $row['can_search'],
1672
				'mask' => $row['mask'],
1673
				'regex' => substr($row['mask'], 0, 5) == 'regex' ? substr($row['mask'], 5) : '',
1674
				'enclose' => $row['enclose'],
1675
				'placement' => $row['placement'],
1676
			);
1677
		}
1678
		$smcFunc['db_free_result']($request);
1679
	}
1680
1681
	// Setup the default values as needed.
1682
	if (empty($context['field']))
1683
		$context['field'] = array(
1684
			'name' => '',
1685
			'col_name' => '???',
1686
			'desc' => '',
1687
			'profile_area' => 'forumprofile',
1688
			'reg' => false,
1689
			'display' => false,
1690
			'mlist' => false,
1691
			'type' => 'text',
1692
			'order' => 0,
1693
			'max_length' => 255,
1694
			'rows' => 4,
1695
			'cols' => 30,
1696
			'bbc' => false,
1697
			'default_check' => false,
1698
			'default_select' => '',
1699
			'options' => array('', '', ''),
1700
			'active' => true,
1701
			'private' => false,
1702
			'can_search' => false,
1703
			'mask' => 'nohtml',
1704
			'regex' => '',
1705
			'enclose' => '',
1706
			'placement' => 0,
1707
		);
1708
1709
	// Are we moving it?
1710
	if (isset($_GET['move']) && in_array($smcFunc['htmlspecialchars']($_GET['move']), $move_to))
1711
	{
1712
		// Down is the new up.
1713
		$new_order = ($_GET['move'] == 'up' ? ($context['field']['order'] - 1) : ($context['field']['order'] + 1));
1714
1715
		// Is this a valid position?
1716
		if ($new_order <= 0 || $new_order > $order_count)
1717
			redirectexit('action=admin;area=featuresettings;sa=profile'); // @todo implement an error handler
0 ignored issues
show
Coding Style Best Practice introduced by
Comments for TODO tasks are often forgotten in the code; it might be better to use a dedicated issue tracker.
Loading history...
1718
1719
		// All good, proceed.
1720
		$smcFunc['db_query']('','
1721
			UPDATE {db_prefix}custom_fields
1722
			SET field_order = {int:old_order}
1723
			WHERE field_order = {int:new_order}',
1724
			array(
1725
				'new_order' => $new_order,
1726
				'old_order' => $context['field']['order'],
1727
			)
1728
		);
1729
		$smcFunc['db_query']('','
1730
			UPDATE {db_prefix}custom_fields
1731
			SET field_order = {int:new_order}
1732
			WHERE id_field = {int:id_field}',
1733
			array(
1734
				'new_order' => $new_order,
1735
				'id_field' => $context['fid'],
1736
			)
1737
		);
1738
		redirectexit('action=admin;area=featuresettings;sa=profile'); // @todo perhaps a nice confirmation message, dunno.
0 ignored issues
show
Coding Style Best Practice introduced by
Comments for TODO tasks are often forgotten in the code; it might be better to use a dedicated issue tracker.
Loading history...
1739
	}
1740
1741
	// Are we saving?
1742
	if (isset($_POST['save']))
1743
	{
1744
		checkSession();
1745
		validateToken('admin-ecp');
1746
1747
		// Everyone needs a name - even the (bracket) unknown...
1748
		if (trim($_POST['field_name']) == '')
1749
			redirectexit($scripturl . '?action=admin;area=featuresettings;sa=profileedit;fid=' . $_GET['fid'] . ';msg=need_name');
1750
1751
		// Regex you say?  Do a very basic test to see if the pattern is valid
1752
		if (!empty($_POST['regex']) && @preg_match($_POST['regex'], 'dummy') === false)
1753
			redirectexit($scripturl . '?action=admin;area=featuresettings;sa=profileedit;fid=' . $_GET['fid'] . ';msg=regex_error');
1754
1755
		$_POST['field_name'] = $smcFunc['htmlspecialchars']($_POST['field_name']);
1756
		$_POST['field_desc'] = $smcFunc['htmlspecialchars']($_POST['field_desc']);
1757
1758
		// Checkboxes...
1759
		$show_reg = isset($_POST['reg']) ? (int) $_POST['reg'] : 0;
1760
		$show_display = isset($_POST['display']) ? 1 : 0;
1761
		$show_mlist = isset($_POST['mlist']) ? 1 : 0;
1762
		$bbc = isset($_POST['bbc']) ? 1 : 0;
1763
		$show_profile = $_POST['profile_area'];
1764
		$active = isset($_POST['active']) ? 1 : 0;
1765
		$private = isset($_POST['private']) ? (int) $_POST['private'] : 0;
1766
		$can_search = isset($_POST['can_search']) ? 1 : 0;
1767
1768
		// Some masking stuff...
1769
		$mask = isset($_POST['mask']) ? $_POST['mask'] : '';
1770
		if ($mask == 'regex' && isset($_POST['regex']))
1771
			$mask .= $_POST['regex'];
1772
1773
		$field_length = isset($_POST['max_length']) ? (int) $_POST['max_length'] : 255;
1774
		$enclose = isset($_POST['enclose']) ? $_POST['enclose'] : '';
1775
		$placement = isset($_POST['placement']) ? (int) $_POST['placement'] : 0;
1776
1777
		// Select options?
1778
		$field_options = '';
1779
		$newOptions = array();
1780
		$default = isset($_POST['default_check']) && $_POST['field_type'] == 'check' ? 1 : '';
1781
		if (!empty($_POST['select_option']) && ($_POST['field_type'] == 'select' || $_POST['field_type'] == 'radio'))
1782
		{
1783
			foreach ($_POST['select_option'] as $k => $v)
1784
			{
1785
				// Clean, clean, clean...
1786
				$v = $smcFunc['htmlspecialchars']($v);
1787
				$v = strtr($v, array(',' => ''));
1788
1789
				// Nada, zip, etc...
1790
				if (trim($v) == '')
1791
					continue;
1792
1793
				// Otherwise, save it boy.
1794
				$field_options .= $v . ',';
1795
				// This is just for working out what happened with old options...
1796
				$newOptions[$k] = $v;
1797
1798
				// Is it default?
1799
				if (isset($_POST['default_select']) && $_POST['default_select'] == $k)
1800
					$default = $v;
1801
			}
1802
			$field_options = substr($field_options, 0, -1);
1803
		}
1804
1805
		// Text area has default has dimensions
1806
		if ($_POST['field_type'] == 'textarea')
1807
			$default = (int) $_POST['rows'] . ',' . (int) $_POST['cols'];
1808
1809
		// Come up with the unique name?
1810
		if (empty($context['fid']))
1811
		{
1812
			$col_name = $smcFunc['substr'](strtr($_POST['field_name'], array(' ' => '')), 0, 6);
1813
			preg_match('~([\w\d_-]+)~', $col_name, $matches);
1814
1815
			// If there is nothing to the name, then let's start out own - for foreign languages etc.
1816
			if (isset($matches[1]))
1817
				$col_name = $initial_col_name = 'cust_' . strtolower($matches[1]);
1818
			else
1819
				$col_name = $initial_col_name = 'cust_' . mt_rand(1, 9999);
1820
1821
			// Make sure this is unique.
1822
			$current_fields = array();
1823
			$request = $smcFunc['db_query']('', '
1824
				SELECT id_field, col_name
1825
				FROM {db_prefix}custom_fields');
1826
			while ($row = $smcFunc['db_fetch_assoc']($request))
1827
				$current_fields[$row['id_field']] = $row['col_name'];
1828
			$smcFunc['db_free_result']($request);
1829
1830
			$unique = false;
1831
			for ($i = 0; !$unique && $i < 9; $i ++)
1832
			{
1833
				if (!in_array($col_name, $current_fields))
1834
					$unique = true;
1835
				else
1836
					$col_name = $initial_col_name . $i;
1837
			}
1838
1839
			// Still not a unique column name? Leave it up to the user, then.
1840
			if (!$unique)
1841
				fatal_lang_error('custom_option_not_unique');
1842
		}
1843
		// Work out what to do with the user data otherwise...
1844
		else
1845
		{
1846
			// Anything going to check or select is pointless keeping - as is anything coming from check!
1847
			if (($_POST['field_type'] == 'check' && $context['field']['type'] != 'check')
1848
				|| (($_POST['field_type'] == 'select' || $_POST['field_type'] == 'radio') && $context['field']['type'] != 'select' && $context['field']['type'] != 'radio')
1849
				|| ($context['field']['type'] == 'check' && $_POST['field_type'] != 'check'))
1850
			{
1851
				$smcFunc['db_query']('', '
1852
					DELETE FROM {db_prefix}themes
1853
					WHERE variable = {string:current_column}
1854
						AND id_member > {int:no_member}',
1855
					array(
1856
						'no_member' => 0,
1857
						'current_column' => $context['field']['col_name'],
1858
					)
1859
				);
1860
			}
1861
			// Otherwise - if the select is edited may need to adjust!
1862
			elseif ($_POST['field_type'] == 'select' || $_POST['field_type'] == 'radio')
1863
			{
1864
				$optionChanges = array();
1865
				$takenKeys = array();
1866
				// Work out what's changed!
1867
				foreach ($context['field']['options'] as $k => $option)
1868
				{
1869
					if (trim($option) == '')
1870
						continue;
1871
1872
					// Still exists?
1873
					if (in_array($option, $newOptions))
1874
					{
1875
						$takenKeys[] = $k;
1876
						continue;
1877
					}
1878
				}
1879
1880
				// Finally - have we renamed it - or is it really gone?
1881
				foreach ($optionChanges as $k => $option)
1882
				{
1883
					// Just been renamed?
1884
					if (!in_array($k, $takenKeys) && !empty($newOptions[$k]))
1885
						$smcFunc['db_query']('', '
1886
							UPDATE {db_prefix}themes
1887
							SET value = {string:new_value}
1888
							WHERE variable = {string:current_column}
1889
								AND value = {string:old_value}
1890
								AND id_member > {int:no_member}',
1891
							array(
1892
								'no_member' => 0,
1893
								'new_value' => $newOptions[$k],
1894
								'current_column' => $context['field']['col_name'],
1895
								'old_value' => $option,
1896
							)
1897
						);
1898
				}
1899
			}
1900
			// @todo Maybe we should adjust based on new text length limits?
0 ignored issues
show
Coding Style Best Practice introduced by
Comments for TODO tasks are often forgotten in the code; it might be better to use a dedicated issue tracker.
Loading history...
1901
		}
1902
1903
		// Do the insertion/updates.
1904
		if ($context['fid'])
1905
		{
1906
			$smcFunc['db_query']('', '
1907
				UPDATE {db_prefix}custom_fields
1908
				SET
1909
					field_name = {string:field_name}, field_desc = {string:field_desc},
1910
					field_type = {string:field_type}, field_length = {int:field_length},
1911
					field_options = {string:field_options}, show_reg = {int:show_reg},
1912
					show_display = {int:show_display}, show_mlist = {int:show_mlist}, show_profile = {string:show_profile},
1913
					private = {int:private}, active = {int:active}, default_value = {string:default_value},
1914
					can_search = {int:can_search}, bbc = {int:bbc}, mask = {string:mask},
1915
					enclose = {string:enclose}, placement = {int:placement}
1916
				WHERE id_field = {int:current_field}',
1917
				array(
1918
					'field_length' => $field_length,
1919
					'show_reg' => $show_reg,
1920
					'show_display' => $show_display,
1921
					'show_mlist' => $show_mlist,
1922
					'private' => $private,
1923
					'active' => $active,
1924
					'can_search' => $can_search,
1925
					'bbc' => $bbc,
1926
					'current_field' => $context['fid'],
1927
					'field_name' => $_POST['field_name'],
1928
					'field_desc' => $_POST['field_desc'],
1929
					'field_type' => $_POST['field_type'],
1930
					'field_options' => $field_options,
1931
					'show_profile' => $show_profile,
1932
					'default_value' => $default,
1933
					'mask' => $mask,
1934
					'enclose' => $enclose,
1935
					'placement' => $placement,
1936
				)
1937
			);
1938
1939
			// Just clean up any old selects - these are a pain!
1940
			if (($_POST['field_type'] == 'select' || $_POST['field_type'] == 'radio') && !empty($newOptions))
1941
				$smcFunc['db_query']('', '
1942
					DELETE FROM {db_prefix}themes
1943
					WHERE variable = {string:current_column}
1944
						AND value NOT IN ({array_string:new_option_values})
1945
						AND id_member > {int:no_member}',
1946
					array(
1947
						'no_member' => 0,
1948
						'new_option_values' => $newOptions,
1949
						'current_column' => $context['field']['col_name'],
1950
					)
1951
				);
1952
		}
1953
		else
1954
		{
1955
			// Gotta figure it out the order.
1956
			$new_order = $order_count > 1 ? ($order_count + 1) : 1;
1957
1958
			$smcFunc['db_insert']('',
1959
				'{db_prefix}custom_fields',
1960
				array(
1961
					'col_name' => 'string', 'field_name' => 'string', 'field_desc' => 'string',
1962
					'field_type' => 'string', 'field_length' => 'string', 'field_options' => 'string', 'field_order' => 'int',
1963
					'show_reg' => 'int', 'show_display' => 'int', 'show_mlist' => 'int', 'show_profile' => 'string',
1964
					'private' => 'int', 'active' => 'int', 'default_value' => 'string', 'can_search' => 'int',
1965
					'bbc' => 'int', 'mask' => 'string', 'enclose' => 'string', 'placement' => 'int',
1966
				),
1967
				array(
1968
					$col_name, $_POST['field_name'], $_POST['field_desc'],
0 ignored issues
show
Bug introduced by
The variable $col_name does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
1969
					$_POST['field_type'], $field_length, $field_options, $new_order,
1970
					$show_reg, $show_display, $show_mlist, $show_profile,
1971
					$private, $active, $default, $can_search,
1972
					$bbc, $mask, $enclose, $placement,
1973
				),
1974
				array('id_field')
1975
			);
1976
		}
1977
	}
1978
	// Deleting?
1979
	elseif (isset($_POST['delete']) && $context['field']['col_name'])
1980
	{
1981
		checkSession();
1982
		validateToken('admin-ecp');
1983
1984
		// Delete the user data first.
1985
		$smcFunc['db_query']('', '
1986
			DELETE FROM {db_prefix}themes
1987
			WHERE variable = {string:current_column}
1988
				AND id_member > {int:no_member}',
1989
			array(
1990
				'no_member' => 0,
1991
				'current_column' => $context['field']['col_name'],
1992
			)
1993
		);
1994
		// Finally - the field itself is gone!
1995
		$smcFunc['db_query']('', '
1996
			DELETE FROM {db_prefix}custom_fields
1997
			WHERE id_field = {int:current_field}',
1998
			array(
1999
				'current_field' => $context['fid'],
2000
			)
2001
		);
2002
2003
		// Re-arrange the order.
2004
		$smcFunc['db_query']('','
2005
			UPDATE {db_prefix}custom_fields
2006
			SET field_order = field_order - 1
2007
			WHERE field_order > {int:current_order}',
2008
			array(
2009
				'current_order' => $context['field']['order'],
2010
			)
2011
		);
2012
	}
2013
2014
	// Rebuild display cache etc.
2015
	if (isset($_POST['delete']) || isset($_POST['save']))
2016
	{
2017
		checkSession();
2018
2019
		$request = $smcFunc['db_query']('', '
2020
			SELECT col_name, field_name, field_type, field_order, bbc, enclose, placement, show_mlist, field_options
2021
			FROM {db_prefix}custom_fields
2022
			WHERE show_display = {int:is_displayed}
2023
				AND active = {int:active}
2024
				AND private != {int:not_owner_only}
2025
				AND private != {int:not_admin_only}
2026
			ORDER BY field_order',
2027
			array(
2028
				'is_displayed' => 1,
2029
				'active' => 1,
2030
				'not_owner_only' => 2,
2031
				'not_admin_only' => 3,
2032
			)
2033
		);
2034
2035
		$fields = array();
2036
		while ($row = $smcFunc['db_fetch_assoc']($request))
2037
		{
2038
			$fields[] = array(
2039
				'col_name' => strtr($row['col_name'], array('|' => '', ';' => '')),
2040
				'title' => strtr($row['field_name'], array('|' => '', ';' => '')),
2041
				'type' => $row['field_type'],
2042
				'order' => $row['field_order'],
2043
				'bbc' => $row['bbc'] ? '1' : '0',
2044
				'placement' => !empty($row['placement']) ? $row['placement'] : '0',
2045
				'enclose' => !empty($row['enclose']) ? $row['enclose'] : '',
2046
				'mlist' => $row['show_mlist'],
2047
				'options' => (!empty($row['field_options']) ? explode(',', $row['field_options']) : array()),
2048
			);
2049
		}
2050
		$smcFunc['db_free_result']($request);
2051
2052
		updateSettings(array('displayFields' => $smcFunc['json_encode']($fields)));
2053
		$_SESSION['adm-save'] = true;
2054
		redirectexit('action=admin;area=featuresettings;sa=profile');
2055
	}
2056
2057
	createToken('admin-ecp');
2058
}
2059
2060
/**
2061
 * Returns the maximum field_order value for the custom fields
2062
 * @return int The maximum value of field_order from the custom_fields table
2063
 */
2064
function custFieldsMaxOrder()
2065
{
2066
	global $smcFunc;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
2067
2068
	// Gotta know the order limit
2069
	$result = $smcFunc['db_query']('', '
2070
			SELECT MAX(field_order)
2071
			FROM {db_prefix}custom_fields',
2072
			array()
2073
		);
2074
2075
	list ($order_count) = $smcFunc['db_fetch_row']($result);
2076
	$smcFunc['db_free_result']($result);
2077
2078
	return (int) $order_count;
2079
}
2080
2081
/**
2082
 * Allow to edit the settings on the pruning screen.
2083
 * @param bool $return_config Whether or not to return the config_vars array (used for admin search)
2084
 * @return void|array Returns nothing or returns the $config_vars array if $return_config is true
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use array<*,array<*,string>|...ng[]|string|array>|null.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
2085
 */
2086
function ModifyLogSettings($return_config = false)
2087
{
2088
	global $txt, $scripturl, $sourcedir, $context, $modSettings;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
2089
2090
	// Make sure we understand what's going on.
2091
	loadLanguage('ManageSettings');
2092
2093
	$context['page_title'] = $txt['log_settings'];
2094
2095
	$config_vars = array(
2096
			array('check', 'modlog_enabled', 'help' => 'modlog'),
2097
			array('check', 'adminlog_enabled', 'help' => 'adminlog'),
2098
			array('check', 'userlog_enabled', 'help' => 'userlog'),
2099
			// The error log is a wonderful thing.
2100
			array('title', 'errlog'),
2101
			array('desc', 'error_log_desc'),
2102
			array('check', 'enableErrorLogging'),
2103
			array('check', 'enableErrorQueryLogging'),
2104
			array('check', 'log_ban_hits'),
2105
			// Even do the pruning?
2106
			array('title', 'pruning_title'),
2107
			array('desc', 'pruning_desc'),
2108
			// The array indexes are there so we can remove/change them before saving.
2109
			'pruningOptions' => array('check', 'pruningOptions'),
2110
		'',
2111
			// Various logs that could be pruned.
2112
			array('int', 'pruneErrorLog', 'postinput' => $txt['days_word'], 'subtext' => $txt['zero_to_disable']), // Error log.
2113
			array('int', 'pruneModLog', 'postinput' => $txt['days_word'], 'subtext' => $txt['zero_to_disable']), // Moderation log.
2114
			array('int', 'pruneBanLog', 'postinput' => $txt['days_word'], 'subtext' => $txt['zero_to_disable']), // Ban hit log.
2115
			array('int', 'pruneReportLog', 'postinput' => $txt['days_word'], 'subtext' => $txt['zero_to_disable']), // Report to moderator log.
2116
			array('int', 'pruneScheduledTaskLog', 'postinput' => $txt['days_word'], 'subtext' => $txt['zero_to_disable']), // Log of the scheduled tasks and how long they ran.
2117
			array('int', 'pruneSpiderHitLog', 'postinput' => $txt['days_word'], 'subtext' => $txt['zero_to_disable']), // Log of the scheduled tasks and how long they ran.
2118
			// If you add any additional logs make sure to add them after this point.  Additionally, make sure you add them to the weekly scheduled task.
2119
			// Mod Developers: Do NOT use the pruningOptions master variable for this as SMF Core may overwrite your setting in the future!
2120
	);
2121
2122
	// We want to be toggling some of these for a nice user experience. If you want to add yours to the list of those magically hidden when the 'pruning' option is off, add to this.
2123
	$prune_toggle = array('pruneErrorLog', 'pruneModLog', 'pruneBanLog', 'pruneReportLog', 'pruneScheduledTaskLog', 'pruneSpiderHitLog');
2124
2125
	call_integration_hook('integrate_prune_settings', array(&$config_vars, &$prune_toggle, false));
2126
2127
	$prune_toggle_dt = array();
2128
	foreach ($prune_toggle as $item)
2129
		$prune_toggle_dt[] = 'setting_' . $item;
2130
2131
	if ($return_config)
2132
		return $config_vars;
2133
2134
	addInlineJavaScript('
2135
	function togglePruned()
2136
	{
2137
		var newval = $("#pruningOptions").prop("checked");
2138
		$("#' . implode(', #', $prune_toggle) . '").closest("dd").toggle(newval);
2139
		$("#' . implode(', #', $prune_toggle_dt) . '").closest("dt").toggle(newval);
2140
	};
2141
	togglePruned();
2142
	$("#pruningOptions").click(function() { togglePruned(); });', true);
2143
2144
	// We'll need this in a bit.
2145
	require_once($sourcedir . '/ManageServer.php');
2146
2147
	// Saving?
2148
	if (isset($_GET['save']))
2149
	{
2150
		checkSession();
2151
2152
		// Because of the excitement attached to combining pruning log items, we need to duplicate everything here.
2153
		$savevar = array(
2154
			array('check', 'modlog_enabled'),
2155
			array('check', 'adminlog_enabled'),
2156
			array('check', 'userlog_enabled'),
2157
			array('check', 'enableErrorLogging'),
2158
			array('check', 'enableErrorQueryLogging'),
2159
			array('check', 'log_ban_hits'),
2160
			array('text', 'pruningOptions')
2161
		);
2162
2163
		call_integration_hook('integrate_prune_settings', array(&$savevar, &$prune_toggle, true));
2164
2165
		if (!empty($_POST['pruningOptions']))
2166
		{
2167
			$vals = array();
2168
			foreach ($config_vars as $index => $dummy)
2169
			{
2170
				if (!is_array($dummy) || $index == 'pruningOptions' || !in_array($dummy[1], $prune_toggle))
2171
					continue;
2172
2173
				$vals[] = empty($_POST[$dummy[1]]) || $_POST[$dummy[1]] < 0 ? 0 : (int) $_POST[$dummy[1]];
2174
			}
2175
			$_POST['pruningOptions'] = implode(',', $vals);
2176
		}
2177
		else
2178
			$_POST['pruningOptions'] = '';
2179
2180
		saveDBSettings($savevar);
2181
		$_SESSION['adm-save'] = true;
2182
		redirectexit('action=admin;area=logs;sa=settings');
2183
	}
2184
2185
	$context['post_url'] = $scripturl . '?action=admin;area=logs;save;sa=settings';
2186
	$context['settings_title'] = $txt['log_settings'];
2187
	$context['sub_template'] = 'show_settings';
2188
2189
	// Get the actual values
2190
	if (!empty($modSettings['pruningOptions']))
2191
		@list ($modSettings['pruneErrorLog'], $modSettings['pruneModLog'], $modSettings['pruneBanLog'], $modSettings['pruneReportLog'], $modSettings['pruneScheduledTaskLog'], $modSettings['pruneSpiderHitLog']) = explode(',', $modSettings['pruningOptions']);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
2192
	else
2193
		$modSettings['pruneErrorLog'] = $modSettings['pruneModLog'] = $modSettings['pruneBanLog'] = $modSettings['pruneReportLog'] = $modSettings['pruneScheduledTaskLog'] = $modSettings['pruneSpiderHitLog'] = 0;
2194
2195
	prepareDBSettingContext($config_vars);
2196
}
2197
2198
/**
2199
 * If you have a general mod setting to add stick it here.
2200
 *
2201
 * @param bool $return_config Whether or not to return the config_vars array (used for admin search)
2202
 * @return void|array Returns nothing or returns the $config_vars array if $return_config is true
2203
 */
2204
function ModifyGeneralModSettings($return_config = false)
2205
{
2206
	global $txt, $scripturl, $context;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
2207
2208
	$config_vars = array(
2209
		// Mod authors, add any settings UNDER this line. Include a comma at the end of the line and don't remove this statement!!
2210
	);
2211
2212
	// Make it even easier to add new settings.
2213
	call_integration_hook('integrate_general_mod_settings', array(&$config_vars));
2214
2215
	if ($return_config)
2216
		return $config_vars;
2217
2218
	$context['post_url'] = $scripturl . '?action=admin;area=modsettings;save;sa=general';
2219
	$context['settings_title'] = $txt['mods_cat_modifications_misc'];
2220
2221
	// No removing this line you, dirty unwashed mod authors. :p
2222
	if (empty($config_vars))
2223
	{
2224
		$context['settings_save_dont_show'] = true;
2225
		$context['settings_message'] = '<div class="centertext">' . $txt['modification_no_misc_settings'] . '</div>';
2226
2227
		return prepareDBSettingContext($config_vars);
2228
	}
2229
2230
	// Saving?
2231 View Code Duplication
	if (isset($_GET['save']))
2232
	{
2233
		checkSession();
2234
2235
		$save_vars = $config_vars;
2236
2237
		call_integration_hook('integrate_save_general_mod_settings', array(&$save_vars));
2238
2239
		// This line is to help mod authors do a search/add after if you want to add something here. Keyword: FOOT TAPPING SUCKS!
2240
		saveDBSettings($save_vars);
2241
2242
		// This line is to remind mod authors that it's nice to let the users know when something has been saved.
2243
		$_SESSION['adm-save'] = true;
2244
2245
		// This line is to help mod authors do a search/add after if you want to add something here. Keyword: I LOVE TEA!
2246
		redirectexit('action=admin;area=modsettings;sa=general');
2247
	}
2248
2249
	// This line is to help mod authors do a search/add after if you want to add something here. Keyword: RED INK IS FOR TEACHERS AND THOSE WHO LIKE PAIN!
2250
	prepareDBSettingContext($config_vars);
2251
}
2252
2253
/**
2254
 * Handles modifying the alerts settings
2255
 */
2256
function ModifyAlertsSettings()
2257
{
2258
	global $context, $modSettings, $sourcedir, $txt;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
2259
2260
	// Dummy settings for the template...
2261
	$modSettings['allow_disableAnnounce'] = false;
2262
	$context['user']['is_owner'] = false;
2263
	$context['member'] = array();
2264
	$context['id_member'] = 0;
2265
	$context['menu_item_selected'] = 'alerts';
2266
	$context['token_check'] = 'noti-admin';
2267
2268
	// Specify our action since we'll want to post back here instead of the profile
2269
	$context['action'] = 'action=admin;area=featuresettings;sa=alerts;'. $context['session_var'] .'='. $context['session_id'];
2270
2271
	loadTemplate('Profile');
2272
	loadLanguage('Profile');
2273
2274
	include_once($sourcedir . '/Profile-Modify.php');
2275
	alert_configuration(0);
2276
2277
	$context['page_title'] = $txt['notify_settings'];
2278
2279
	// Override the description
2280
	$context['description'] = $txt['notifications_desc'];
2281
	$context['sub_template'] = 'alert_configuration';
2282
}
2283
2284
/**
2285
 * Config array for changing privacy settings
2286
 * Accessed  from ?action=admin;area=featuresettings;sa=privacy;
2287
 *
2288
 * @param bool $return_config Whether or not to return the config_vars array
2289
 * @return void|array Returns nothing or returns the $config_vars array if $return_config is true
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use string[][]|null.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
2290
 */
2291
function ModifyPrivacySettings($return_config = false)
2292
{
2293
	global $txt, $scripturl, $context;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
2294
2295
	$config_vars = array(
2296
		array('check', 'enable_privacy_userexport'),
2297
		array('permissions', 'privacy_userexport_own'),
2298
		array('permissions', 'privacy_userexport_others'),
2299
	);
2300
2301
	call_integration_hook('integrate_privacy_settings', array(&$config_vars));
2302
2303
	if ($return_config)
2304
		return $config_vars;
2305
2306
	// Saving?
2307 View Code Duplication
	if (isset($_GET['save']))
2308
	{
2309
		checkSession();
2310
2311
		call_integration_hook('integrate_save_privacy_settings');
2312
2313
		saveDBSettings($config_vars);
2314
		$_SESSION['adm-save'] = true;
2315
		redirectexit('action=admin;area=featuresettings;sa=privacy');
2316
	}
2317
2318
	$context['post_url'] = $scripturl . '?action=admin;area=featuresettings;save;sa=privacy';
2319
	$context['settings_title'] = $txt['privacy'];
2320
2321
	prepareDBSettingContext($config_vars);
2322
}
2323
2324
/**
2325
 * Config array for changing policy settings
2326
 * Accessed  from ?action=admin;area=featuresettings;sa=policy;
2327
 *
2328
 * @param bool $return_config Whether or not to return the config_vars array
2329
 * @return void|array Returns nothing or returns the $config_vars array if $return_config is true
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use string[][]|null.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
2330
 */
2331
function ModifyPolicySettings($return_config = false)
2332
{
2333
	global $txt, $scripturl, $context, $sourcedir, $modSettings, $smcFunc;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
2334
	
2335
	// Needed for the WYSIWYG editor.
2336
	require_once($sourcedir . '/Subs-Editor.php');
2337
	$context['sub_template'] = 'edit_policy';
2338
	$context['page_title'] = $txt['policy_management'];
2339
2340
	$config_vars = array(
2341
		array('check', 'enable_policy_function'),
2342
		array('text', 'policy_text'),
2343
	);
2344
	
2345
	$currentVersion = !empty($modSettings['policy_version']) ? substr($modSettings['policy_version'], 11) : 0;
2346
2347
	// Now create the editor.
2348
	$editorOptions = array(
2349
		'id' => 'policy_text',
2350
		'value' => !empty($modSettings['policy_text' . $currentVersion]) ? $modSettings['policy_text' . $currentVersion] : '',
2351
		'height' => '250px',
2352
		'width' => '100%',
2353
		'labels' => array(
2354
			'post_button' => $txt['policy_save'],
2355
		),
2356
		'preview_type' => 2,
2357
		'required' => true,
2358
	);
2359
	create_control_richedit($editorOptions);
2360
	// Store the ID for old compatibility.
2361
	$context['post_box_name'] = $editorOptions['id'];
2362
	
2363
	call_integration_hook('integrate_policy_settings', array(&$config_vars));
2364
	
2365
	$request = $smcFunc['db_query']('', '
2366
		SELECT count( case when th.value is null then 1 end) "not",
2367
			count( case when th.value is not null and th.value != {string:policy_version} then 1 end) "old",
2368
			count( case when th.value = {string:policy_version} then 1 end) "fresh"
2369
		FROM smf_members mem
2370
		LEFT JOIN smf_themes th ON (mem.id_member = th.id_member AND th.id_theme = 1 AND th.variable = {string:policy_approved})',
2371
		array(
2372
			'policy_version' => 'policy_text' . $currentVersion,
2373
			'policy_approved' => 'policy_approved',
2374
		)
2375
	);
2376
	
2377
	list ($context['policy']['not'], $context['policy']['old'], $context['policy']['fresh']) = $smcFunc['db_fetch_row']($request);
2378
	$smcFunc['db_free_result']($request);
2379
2380
	if ($return_config)
2381
		return $config_vars;
2382
2383
	// Saving?
2384
	if (isset($_GET['save']))
2385
	{
2386
		checkSession();
2387
2388
		call_integration_hook('integrate_save_policy_settings');
2389
2390
		if (!empty($_REQUEST['save_new_policy']))
2391
		{
2392
			$currentVersion++;
2393
			$config_vars[] = array('text', 'policy_text'.$currentVersion);
2394
			$_POST['policy_text'.$currentVersion] = $_REQUEST['policy_text'];
2395
			$config_vars[] = array('text', 'policy_version');
2396
			$_POST['policy_version'] = 'policy_text'.$currentVersion;
2397
			unset($config_vars[1]);
2398
		}
2399
		elseif (!empty($_REQUEST['update_policy']))
2400
		{
2401
			$config_vars[] = array('text', 'policy_text'.$currentVersion);
2402
			$_POST['policy_text'.$currentVersion] = $_REQUEST['policy_text'];
2403
			$config_vars[] = array('text', 'policy_version');
2404
			$_POST['policy_version'] = 'policy_text'.$currentVersion;
2405
			unset($config_vars[1]);
2406
		}
2407
		saveDBSettings($config_vars);
2408
		$_SESSION['adm-save'] = true;
2409
		redirectexit('action=admin;area=featuresettings;sa=policy');
2410
	}
2411
2412
	$context['post_url'] = $scripturl . '?action=admin;area=featuresettings;save;sa=policy';
2413
	$context['settings_title'] = $txt['privacy'];
2414
2415
	prepareDBSettingContext($config_vars);
2416
}
2417
2418
?>