Completed
Pull Request — release-2.1 (#4892)
by Mathias
13:10 queued 03:24
created

ManageSettings.php ➔ ModifyPrivacySettings()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 32

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
nc 3
nop 1
dl 0
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
	);
70
71
	loadGeneralSettingParameters($subActions, 'basic');
72
73
	// Load up all the tabs...
74
	$context[$context['admin_menu_name']]['tab_data'] = array(
75
		'title' => $txt['modSettings_title'],
76
		'help' => 'featuresettings',
77
		'description' => sprintf($txt['modSettings_desc'], $settings['theme_id'], $context['session_id'], $context['session_var']),
78
		'tabs' => array(
79
			'basic' => array(
80
			),
81
			'bbc' => array(
82
				'description' => $txt['manageposts_bbc_settings_description'],
83
			),
84
			'layout' => array(
85
			),
86
			'sig' => array(
87
				'description' => $txt['signature_settings_desc'],
88
			),
89
			'profile' => array(
90
				'description' => $txt['custom_profile_desc'],
91
			),
92
			'likes' => array(
93
			),
94
			'mentions' => array(
95
			),
96
			'alerts' => array(
97
				'description' => $txt['notifications_desc'],
98
			),
99
			'privacy' => array(
100
				'label' => $txt['privacy'],
101
			),
102
		),
103
	);
104
105
	call_integration_hook('integrate_modify_features', array(&$subActions));
106
107
	// Call the right function for this sub-action.
108
	call_helper($subActions[$_REQUEST['sa']]);
109
}
110
111
/**
112
 * This my friend, is for all the mod authors out there.
113
 */
114
function ModifyModSettings()
115
{
116
	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...
117
118
	$context['page_title'] = $txt['admin_modifications'];
119
120
	$subActions = array(
121
		'general' => 'ModifyGeneralModSettings',
122
		// Mod authors, once again, if you have a whole section to add do it AFTER this line, and keep a comma at the end.
123
	);
124
125
	// Make it easier for mods to add new areas.
126
	call_integration_hook('integrate_modify_modifications', array(&$subActions));
127
128
	loadGeneralSettingParameters($subActions, 'general');
129
130
	// Load up all the tabs...
131
	$context[$context['admin_menu_name']]['tab_data'] = array(
132
		'title' => $txt['admin_modifications'],
133
		'help' => 'modsettings',
134
		'description' => $txt['modification_settings_desc'],
135
		'tabs' => array(
136
			'general' => array(
137
			),
138
		),
139
	);
140
141
	// Call the right function for this sub-action.
142
	call_helper($subActions[$_REQUEST['sa']]);
143
}
144
145
/**
146
 * Config array for changing the basic forum settings
147
 * Accessed  from ?action=admin;area=featuresettings;sa=basic;
148
 *
149
 * @param bool $return_config Whether or not to return the config_vars array (used for admin search)
150
 * @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...
151
 */
152
function ModifyBasicSettings($return_config = false)
153
{
154
	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...
155
156
	// We need to know if personal text is enabled, and if it's in the registration fields option.
157
	// If admins have set it up as an on-registration thing, they can't set a default value (because it'll never be used)
158
	$disabled_fields = isset($modSettings['disabled_profile_fields']) ? explode(',', $modSettings['disabled_profile_fields']) : array();
159
	$reg_fields = isset($modSettings['registration_fields']) ? explode(',', $modSettings['registration_fields']) : array();
160
	$can_personal_text = !in_array('personal_text', $disabled_fields) && !in_array('personal_text', $reg_fields);
161
162
	$config_vars = array(
163
			// Big Options... polls, sticky, bbc....
164
			array('select', 'pollMode', array($txt['disable_polls'], $txt['enable_polls'], $txt['polls_as_topics'])),
165
		'',
166
			// Basic stuff, titles, flash, permissions...
167
			array('check', 'allow_guestAccess'),
168
			array('check', 'enable_buddylist'),
169
			array('check', 'allow_hideOnline'),
170
			array('check', 'titlesEnable'),
171
			array('text', 'default_personal_text', 'subtext' => $txt['default_personal_text_note'], 'disabled' => !$can_personal_text),
172
			array('check', 'topic_move_any'),
173
			array('int', 'defaultMaxListItems', 'step' => 1, 'min' => 1, 'max' => 999),
174
		'',
175
			// Jquery source
176
			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;}'),
177
			array('text', 'jquery_custom', 'disabled' => isset($modSettings['jquery_source']) && $modSettings['jquery_source'] != 'custom', 'size' => 75),
178
		'',
179
			// css and js minification.
180
			array('check', 'minimize_files'),
181
		'',
182
			// SEO stuff
183
			array('check', 'queryless_urls', 'subtext' => '<strong>' . $txt['queryless_urls_note'] . '</strong>'),
184
			array('text', 'meta_keywords', 'subtext' => $txt['meta_keywords_note'], 'size' => 50),
185
		'',
186
			// Number formatting, timezones.
187
			array('text', 'time_format'),
188
			array('float', 'time_offset', 'subtext' => $txt['setting_time_offset_note'], 6, 'postinput' => $txt['hours'], 'step' => 0.25, 'min' => -23.5, 'max' => 23.5),
189
			'default_timezone' => array('select', 'default_timezone', array()),
190
			array('text', 'timezone_priority_countries', 'subtext' => $txt['setting_timezone_priority_countries_note']),
191
		'',
192
			// Who's online?
193
			array('check', 'who_enabled'),
194
			array('int', 'lastActive', 6, 'postinput' => $txt['minutes']),
195
		'',
196
			// Statistics.
197
			array('check', 'trackStats'),
198
			array('check', 'hitStats'),
199
		'',
200
			// Option-ish things... miscellaneous sorta.
201
			array('check', 'allow_disableAnnounce'),
202
			array('check', 'disallow_sendBody'),
203
		'',
204
			// Alerts stuff
205
			array('check', 'enable_ajax_alerts'),
206
	);
207
208
	// Get all the time zones.
209
	if (function_exists('timezone_identifiers_list') && function_exists('date_default_timezone_set'))
210
	{
211
		$all_zones = timezone_identifiers_list();
212
		// Make sure we set the value to the same as the printed value.
213
		foreach ($all_zones as $zone)
214
			$config_vars['default_timezone'][2][$zone] = $zone;
215
	}
216
	else
217
		unset($config_vars['default_timezone']);
218
219
	call_integration_hook('integrate_modify_basic_settings', array(&$config_vars));
220
221
	if ($return_config)
222
		return $config_vars;
223
224
	// Saving?
225
	if (isset($_GET['save']))
226
	{
227
		checkSession();
228
229
		// Prevent absurd boundaries here - make it a day tops.
230
		if (isset($_POST['lastActive']))
231
			$_POST['lastActive'] = min((int) $_POST['lastActive'], 1440);
232
233
		call_integration_hook('integrate_save_basic_settings');
234
235
		saveDBSettings($config_vars);
236
		$_SESSION['adm-save'] = true;
237
238
		// Do a bit of housekeeping
239
		if (empty($_POST['minimize_files']))
240
			deleteAllMinified();
241
242
		writeLog();
243
		redirectexit('action=admin;area=featuresettings;sa=basic');
244
	}
245
246
	$context['post_url'] = $scripturl . '?action=admin;area=featuresettings;save;sa=basic';
247
	$context['settings_title'] = $txt['mods_cat_features'];
248
249
	prepareDBSettingContext($config_vars);
250
}
251
252
/**
253
 * Set a few Bulletin Board Code settings. It loads a list of Bulletin Board Code tags to allow disabling tags.
254
 * Requires the admin_forum permission.
255
 * Accessed from ?action=admin;area=featuresettings;sa=bbc.
256
 *
257
 * @param bool $return_config Whether or not to return the config_vars array (used for admin search)
258
 * @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...
259
 * @uses Admin template, edit_bbc_settings sub-template.
260
 */
261
function ModifyBBCSettings($return_config = false)
262
{
263
	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...
264
265
	$config_vars = array(
266
			// Main tweaks
267
			array('check', 'enableBBC'),
268
			array('check', 'enableBBC', 0, 'onchange' => 'toggleBBCDisabled(\'disabledBBC\', !this.checked);'),
269
			array('check', 'enablePostHTML'),
270
			array('check', 'autoLinkUrls'),
271
		'',
272
			array('bbc', 'disabledBBC'),
273
	);
274
275
	$context['settings_post_javascript'] = '
276
		toggleBBCDisabled(\'disabledBBC\', ' . (empty($modSettings['enableBBC']) ? 'true' : 'false') . ');';
277
278
	call_integration_hook('integrate_modify_bbc_settings', array(&$config_vars));
279
280
	if ($return_config)
281
		return $config_vars;
282
283
	// Setup the template.
284
	require_once($sourcedir . '/ManageServer.php');
285
	$context['sub_template'] = 'show_settings';
286
	$context['page_title'] = $txt['manageposts_bbc_settings_title'];
287
288
	// Make sure we check the right tags!
289
	$modSettings['bbc_disabled_disabledBBC'] = empty($modSettings['disabledBBC']) ? array() : explode(',', $modSettings['disabledBBC']);
290
291
	// Saving?
292
	if (isset($_GET['save']))
293
	{
294
		checkSession();
295
296
		// Clean up the tags.
297
		$bbcTags = array();
298
		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...
299
			$bbcTags[] = $tag['tag'];
300
301 View Code Duplication
		if (!isset($_POST['disabledBBC_enabledTags']))
302
			$_POST['disabledBBC_enabledTags'] = array();
303
		elseif (!is_array($_POST['disabledBBC_enabledTags']))
304
			$_POST['disabledBBC_enabledTags'] = array($_POST['disabledBBC_enabledTags']);
305
		// Work out what is actually disabled!
306
		$_POST['disabledBBC'] = implode(',', array_diff($bbcTags, $_POST['disabledBBC_enabledTags']));
307
308
		call_integration_hook('integrate_save_bbc_settings', array($bbcTags));
309
310
		saveDBSettings($config_vars);
311
		$_SESSION['adm-save'] = true;
312
		redirectexit('action=admin;area=featuresettings;sa=bbc');
313
	}
314
315
	$context['post_url'] = $scripturl . '?action=admin;area=featuresettings;save;sa=bbc';
316
	$context['settings_title'] = $txt['manageposts_bbc_settings_title'];
317
318
	prepareDBSettingContext($config_vars);
319
}
320
321
/**
322
 * Allows modifying the global layout settings in the forum
323
 * Accessed through ?action=admin;area=featuresettings;sa=layout;
324
 *
325
 * @param bool $return_config Whether or not to return the config_vars array (used for admin search)
326
 * @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...
327
 */
328
function ModifyLayoutSettings($return_config = false)
329
{
330
	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...
331
332
	$config_vars = array(
333
			// Pagination stuff.
334
			array('check', 'compactTopicPagesEnable'),
335
			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>'),
336
			array('int', 'defaultMaxMembers'),
337
		'',
338
			// Stuff that just is everywhere - today, search, online, etc.
339
			array('select', 'todayMod', array($txt['today_disabled'], $txt['today_only'], $txt['yesterday_today'])),
340
			array('check', 'onlineEnable'),
341
		'',
342
			// This is like debugging sorta.
343
			array('check', 'timeLoadPageEnable'),
344
	);
345
346
	call_integration_hook('integrate_layout_settings', array(&$config_vars));
347
348
	if ($return_config)
349
		return $config_vars;
350
351
	// Saving?
352 View Code Duplication
	if (isset($_GET['save']))
353
	{
354
		checkSession();
355
356
		call_integration_hook('integrate_save_layout_settings');
357
358
		saveDBSettings($config_vars);
359
		$_SESSION['adm-save'] = true;
360
		writeLog();
361
362
		redirectexit('action=admin;area=featuresettings;sa=layout');
363
	}
364
365
	$context['post_url'] = $scripturl . '?action=admin;area=featuresettings;save;sa=layout';
366
	$context['settings_title'] = $txt['mods_cat_layout'];
367
368
	prepareDBSettingContext($config_vars);
369
}
370
371
/**
372
 * Config array for changing like settings
373
 * Accessed  from ?action=admin;area=featuresettings;sa=likes;
374
 *
375
 * @param bool $return_config Whether or not to return the config_vars array
376
 * @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...
377
 */
378 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...
379
{
380
	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...
381
382
	$config_vars = array(
383
		array('check', 'enable_likes'),
384
		array('permissions', 'likes_like'),
385
	);
386
387
	call_integration_hook('integrate_likes_settings', array(&$config_vars));
388
389
	if ($return_config)
390
		return $config_vars;
391
392
	// Saving?
393
	if (isset($_GET['save']))
394
	{
395
		checkSession();
396
397
		call_integration_hook('integrate_save_likes_settings');
398
399
		saveDBSettings($config_vars);
400
		$_SESSION['adm-save'] = true;
401
		redirectexit('action=admin;area=featuresettings;sa=likes');
402
	}
403
404
	$context['post_url'] = $scripturl . '?action=admin;area=featuresettings;save;sa=likes';
405
	$context['settings_title'] = $txt['likes'];
406
407
	prepareDBSettingContext($config_vars);
408
}
409
410
/**
411
 * Config array for changing like settings
412
 * Accessed  from ?action=admin;area=featuresettings;sa=mentions;
413
 *
414
 * @param bool $return_config Whether or not to return the config_vars array (used for admin search)
415
 * @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...
416
 */
417 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...
418
{
419
	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...
420
421
	$config_vars = array(
422
		array('check', 'enable_mentions'),
423
		array('permissions', 'mention'),
424
	);
425
426
	call_integration_hook('integrate_mentions_settings', array(&$config_vars));
427
428
	if ($return_config)
429
		return $config_vars;
430
431
	// Saving?
432
	if (isset($_GET['save']))
433
	{
434
		checkSession();
435
436
		call_integration_hook('integrate_save_mentions_settings');
437
438
		saveDBSettings($config_vars);
439
		$_SESSION['adm-save'] = true;
440
		redirectexit('action=admin;area=featuresettings;sa=mentions');
441
	}
442
443
	$context['post_url'] = $scripturl . '?action=admin;area=featuresettings;save;sa=mentions';
444
	$context['settings_title'] = $txt['mentions'];
445
446
	prepareDBSettingContext($config_vars);
447
}
448
449
/**
450
 * Moderation type settings - although there are fewer than we have you believe ;)
451
 *
452
 * @param bool $return_config Whether or not to return the config_vars array (used for admin search)
453
 * @return void|array Returns nothing or returns the $config_vars array if $return_config is true
454
 */
455
function ModifyWarningSettings($return_config = false)
456
{
457
	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...
458
459
	// You need to be an admin to edit settings!
460
	isAllowedTo('admin_forum');
461
462
	loadLanguage('Help');
463
	loadLanguage('ManageSettings');
464
465
	// We need the existing ones for this
466
	list ($currently_enabled, $modSettings['user_limit'], $modSettings['warning_decrement']) = explode(',', $modSettings['warning_settings']);
467
468
	$config_vars = array(
469
			// Warning system?
470
			'enable' => array('check', 'warning_enable'),
471
	);
472
473
	if (!empty($modSettings['warning_settings']) && $currently_enabled)
474
		$config_vars += array(
475
			'',
476
				array('int', 'warning_watch', 'subtext' => $txt['setting_warning_watch_note'] . ' ' . $txt['zero_to_disable']),
477
				'moderate' => array('int', 'warning_moderate', 'subtext' => $txt['setting_warning_moderate_note'] . ' ' . $txt['zero_to_disable']),
478
				array('int', 'warning_mute', 'subtext' => $txt['setting_warning_mute_note'] . ' ' . $txt['zero_to_disable']),
479
				'rem1' => array('int', 'user_limit', 'subtext' => $txt['setting_user_limit_note']),
480
				'rem2' => array('int', 'warning_decrement', 'subtext' => $txt['setting_warning_decrement_note'] . ' ' . $txt['zero_to_disable']),
481
				array('permissions', 'view_warning'),
482
		);
483
484
	call_integration_hook('integrate_warning_settings', array(&$config_vars));
485
486
	if ($return_config)
487
		return $config_vars;
488
489
	// Cannot use moderation if post moderation is not enabled.
490
	if (!$modSettings['postmod_active'])
491
		unset($config_vars['moderate']);
492
493
	// Will need the utility functions from here.
494
	require_once($sourcedir . '/ManageServer.php');
495
496
	// Saving?
497
	if (isset($_GET['save']))
498
	{
499
		checkSession();
500
501
		// Make sure these don't have an effect.
502
		if (!$currently_enabled && empty($_POST['warning_enable']))
503
		{
504
			$_POST['warning_watch'] = 0;
505
			$_POST['warning_moderate'] = 0;
506
			$_POST['warning_mute'] = 0;
507
		}
508
		// If it was disabled and we're enabling it now, set some sane defaults.
509
		elseif (!$currently_enabled && !empty($_POST['warning_enable']))
510
		{
511
			// Need to add these, these weren't there before...
512
			$vars = array(
513
				'warning_watch' => 10,
514
				'warning_mute' => 60,
515
			);
516
			if ($modSettings['postmod_active'])
517
				$vars['warning_moderate'] = 35;
518
519
			foreach ($vars as $var => $value)
520
			{
521
				$config_vars[] = array('int', $var);
522
				$_POST[$var] = $value;
523
			}
524
		}
525
		else
526
		{
527
			$_POST['warning_watch'] = min($_POST['warning_watch'], 100);
528
			$_POST['warning_moderate'] = $modSettings['postmod_active'] ? min($_POST['warning_moderate'], 100) : 0;
529
			$_POST['warning_mute'] = min($_POST['warning_mute'], 100);
530
		}
531
532
		// We might not have these already depending on how we got here.
533
		$_POST['user_limit'] = isset($_POST['user_limit']) ? (int) $_POST['user_limit'] : $modSettings['user_limit'];
534
		$_POST['warning_decrement'] = isset($_POST['warning_decrement']) ? (int) $_POST['warning_decrement'] : $modSettings['warning_decrement'];
535
536
		// Fix the warning setting array!
537
		$_POST['warning_settings'] = (!empty($_POST['warning_enable']) ? 1 : 0) . ',' . min(100, $_POST['user_limit']) . ',' . min(100, $_POST['warning_decrement']);
538
		$save_vars = $config_vars;
539
		$save_vars[] = array('text', 'warning_settings');
540
		unset($save_vars['enable'], $save_vars['rem1'], $save_vars['rem2']);
541
542
		call_integration_hook('integrate_save_warning_settings', array(&$save_vars));
543
544
		saveDBSettings($save_vars);
545
		$_SESSION['adm-save'] = true;
546
		redirectexit('action=admin;area=warnings');
547
	}
548
549
	// We actually store lots of these together - for efficiency.
550
	list ($modSettings['warning_enable'], $modSettings['user_limit'], $modSettings['warning_decrement']) = explode(',', $modSettings['warning_settings']);
551
552
	$context['sub_template'] = 'show_settings';
553
	$context['post_url'] = $scripturl . '?action=admin;area=warnings;save';
554
	$context['settings_title'] = $txt['warnings'];
555
	$context['page_title'] = $txt['warnings'];
556
557
	$context[$context['admin_menu_name']]['tab_data'] = array(
558
		'title' => $txt['warnings'],
559
		'help' => '',
560
		'description' => $txt['warnings_desc'],
561
	);
562
563
	prepareDBSettingContext($config_vars);
564
}
565
566
/**
567
 * Let's try keep the spam to a minimum ah Thantos?
568
 * @param bool $return_config Whether or not to return the config_vars array (used for admin search)
569
 * @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...
570
 */
571
function ModifyAntispamSettings($return_config = false)
572
{
573
	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...
574
575
	loadLanguage('Help');
576
	loadLanguage('ManageSettings');
577
578
	// Generate a sample registration image.
579
	$context['use_graphic_library'] = in_array('gd', get_loaded_extensions());
580
	$context['verification_image_href'] = $scripturl . '?action=verificationcode;rand=' . md5(mt_rand());
581
582
	$config_vars = array(
583
				array('check', 'reg_verification'),
584
				array('check', 'search_enable_captcha'),
585
				// This, my friend, is a cheat :p
586
				'guest_verify' => array('check', 'guests_require_captcha', 'subtext' => $txt['setting_guests_require_captcha_desc']),
587
				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;}'),
588
			'',
589
			// PM Settings
590
				'pm1' => array('int', 'max_pm_recipients', 'subtext' => $txt['max_pm_recipients_note']),
591
				'pm2' => array('int', 'pm_posts_verification', 'subtext' => $txt['pm_posts_verification_note']),
592
				'pm3' => array('int', 'pm_posts_per_hour', 'subtext' => $txt['pm_posts_per_hour_note']),
593
			// Visual verification.
594
			array('title', 'configure_verification_means'),
595
			array('desc', 'configure_verification_means_desc'),
596
				'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();' : ''),
597
			// reCAPTCHA
598
			array('title', 'recaptcha_configure'),
599
			array('desc', 'recaptcha_configure_desc', 'class' => 'windowbg'),
600
				array('check', 'recaptcha_enabled', 'subtext' => $txt['recaptcha_enable_desc']),
601
				array('text', 'recaptcha_site_key', 'subtext' => $txt['recaptcha_site_key_desc']),
602
				array('text', 'recaptcha_secret_key', 'subtext' => $txt['recaptcha_secret_key_desc']),
603
				array('select', 'recaptcha_theme', array('light' => $txt['recaptcha_theme_light'], 'dark' => $txt['recaptcha_theme_dark'])),
604
			// Clever Thomas, who is looking sheepy now? Not I, the mighty sword swinger did say.
605
			array('title', 'setup_verification_questions'),
606
			array('desc', 'setup_verification_questions_desc'),
607
				array('int', 'qa_verification_number', 'subtext' => $txt['setting_qa_verification_number_desc']),
608
				array('callback', 'question_answer_list'),
609
	);
610
611
	call_integration_hook('integrate_spam_settings', array(&$config_vars));
612
613
	if ($return_config)
614
		return $config_vars;
615
616
	// You need to be an admin to edit settings!
617
	isAllowedTo('admin_forum');
618
619
	// Firstly, figure out what languages we're dealing with, and do a little processing for the form's benefit.
620
	getLanguages();
621
	$context['qa_languages'] = array();
622
	foreach ($context['languages'] as $lang_id => $lang)
623
	{
624
		$lang_id = strtr($lang_id, array('-utf8' => ''));
625
		$lang['name'] = strtr($lang['name'], array('-utf8' => ''));
626
		$context['qa_languages'][$lang_id] = $lang;
627
	}
628
629
	// Secondly, load any questions we currently have.
630
	$context['question_answers'] = array();
631
	$request = $smcFunc['db_query']('', '
632
		SELECT id_question, lngfile, question, answers
633
		FROM {db_prefix}qanda'
634
	);
635
	while ($row = $smcFunc['db_fetch_assoc']($request))
636
	{
637
		$lang = strtr($row['lngfile'], array('-utf8' => ''));
638
		$context['question_answers'][$row['id_question']] = array(
639
			'lngfile' => $lang,
640
			'question' => $row['question'],
641
			'answers' => $smcFunc['json_decode']($row['answers'], true),
642
		);
643
		$context['qa_by_lang'][$lang][] = $row['id_question'];
644
	}
645
646
	if (empty($context['qa_by_lang'][strtr($language, array('-utf8' => ''))]) && !empty($context['question_answers']))
647
	{
648
		if (empty($context['settings_insert_above']))
649
			$context['settings_insert_above'] = '';
650
651
		$context['settings_insert_above'] .= '<div class="noticebox">' . sprintf($txt['question_not_defined'], $context['languages'][$language]['name']) . '</div>';
652
	}
653
654
	// Thirdly, push some JavaScript for the form to make it work.
655
	addInlineJavaScript('
656
	var nextrow = ' . (!empty($context['question_answers']) ? max(array_keys($context['question_answers'])) + 1 : 1) . ';
657
	$(".qa_link a").click(function() {
658
		var id = $(this).parent().attr("id").substring(6);
659
		$("#qa_fs_" + id).show();
660
		$(this).parent().hide();
661
	});
662
	$(".qa_fieldset legend a").click(function() {
663
		var id = $(this).closest("fieldset").attr("id").substring(6);
664
		$("#qa_dt_" + id).show();
665
		$(this).closest("fieldset").hide();
666
	});
667
	$(".qa_add_question a").click(function() {
668
		var id = $(this).closest("fieldset").attr("id").substring(6);
669
		$(\'<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());
670
		nextrow++;
671
	});
672
	$(".qa_add_answer a").click(function() {
673
		var attr = $(this).closest("dd").find(".verification_answer:last").attr("name");
674
		$(\'<input type="text" name="\' + attr + \'" value="" size="50" class="verification_answer">\').insertBefore($(this).closest("div"));
675
		return false;
676
	});
677
	$("#qa_dt_' . strtr($language, array('-utf8' => '')) . ' a").click();', true);
678
679
	// Will need the utility functions from here.
680
	require_once($sourcedir . '/ManageServer.php');
681
682
	// Saving?
683
	if (isset($_GET['save']))
684
	{
685
		checkSession();
686
687
		// Fix PM settings.
688
		$_POST['pm_spam_settings'] = (int) $_POST['max_pm_recipients'] . ',' . (int) $_POST['pm_posts_verification'] . ',' . (int) $_POST['pm_posts_per_hour'];
689
690
		// Hack in guest requiring verification!
691
		if (empty($_POST['posts_require_captcha']) && !empty($_POST['guests_require_captcha']))
692
			$_POST['posts_require_captcha'] = -1;
693
694
		$save_vars = $config_vars;
695
		unset($save_vars['pm1'], $save_vars['pm2'], $save_vars['pm3'], $save_vars['guest_verify']);
696
697
		$save_vars[] = array('text', 'pm_spam_settings');
698
699
		// Handle verification questions.
700
		$changes = array(
701
			'insert' => array(),
702
			'replace' => array(),
703
			'delete' => array(),
704
		);
705
		$qs_per_lang = array();
706
		foreach ($context['qa_languages'] as $lang_id => $dummy)
707
		{
708
			// If we had some questions for this language before, but don't now, delete everything from that language.
709
			if ((!isset($_POST['question'][$lang_id]) || !is_array($_POST['question'][$lang_id])) && !empty($context['qa_by_lang'][$lang_id]))
710
				$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...
711
712
			// Now step through and see if any existing questions no longer exist.
713
			if (!empty($context['qa_by_lang'][$lang_id]))
714
				foreach ($context['qa_by_lang'][$lang_id] as $q_id)
715
					if (empty($_POST['question'][$lang_id][$q_id]))
716
						$changes['delete'][] = $q_id;
717
718
			// Now let's see if there are new questions or ones that need updating.
719
			if (isset($_POST['question'][$lang_id]))
720
			{
721
				foreach ($_POST['question'][$lang_id] as $q_id => $question)
722
				{
723
					// Ignore junky ids.
724
					$q_id = (int) $q_id;
725
					if ($q_id <= 0)
726
						continue;
727
728
					// Check the question isn't empty (because they want to delete it?)
729 View Code Duplication
					if (empty($question) || trim($question) == '')
730
					{
731
						if (isset($context['question_answers'][$q_id]))
732
							$changes['delete'][] = $q_id;
733
						continue;
734
					}
735
					$question = $smcFunc['htmlspecialchars'](trim($question));
736
737
					// Get the answers. Firstly check there actually might be some.
738
					if (!isset($_POST['answer'][$lang_id][$q_id]) || !is_array($_POST['answer'][$lang_id][$q_id]))
739
					{
740
						if (isset($context['question_answers'][$q_id]))
741
							$changes['delete'][] = $q_id;
742
						continue;
743
					}
744
					// Now get them and check that they might be viable.
745
					$answers = array();
746
					foreach ($_POST['answer'][$lang_id][$q_id] as $answer)
747
						if (!empty($answer) && trim($answer) !== '')
748
							$answers[] = $smcFunc['htmlspecialchars'](trim($answer));
749 View Code Duplication
					if (empty($answers))
750
					{
751
						if (isset($context['question_answers'][$q_id]))
752
							$changes['delete'][] = $q_id;
753
						continue;
754
					}
755
					$answers = $smcFunc['json_encode']($answers);
756
757
					// At this point we know we have a question and some answers. What are we doing with it?
758
					if (!isset($context['question_answers'][$q_id]))
759
					{
760
						// New question. Now, we don't want to randomly consume ids, so we'll set those, rather than trusting the browser's supplied ids.
761
						$changes['insert'][] = array($lang_id, $question, $answers);
762
					}
763
					else
764
					{
765
						// It's an existing question. Let's see what's changed, if anything.
766
						if ($lang_id != $context['question_answers'][$q_id]['lngfile'] || $question != $context['question_answers'][$q_id]['question'] || $answers != $context['question_answers'][$q_id]['answers'])
767
							$changes['replace'][$q_id] = array('lngfile' => $lang_id, 'question' => $question, 'answers' => $answers);
768
					}
769
770
					if (!isset($qs_per_lang[$lang_id]))
771
						$qs_per_lang[$lang_id] = 0;
772
					$qs_per_lang[$lang_id]++;
773
				}
774
			}
775
		}
776
777
		// OK, so changes?
778
		if (!empty($changes['delete']))
779
		{
780
			$smcFunc['db_query']('', '
781
				DELETE FROM {db_prefix}qanda
782
				WHERE id_question IN ({array_int:questions})',
783
				array(
784
					'questions' => $changes['delete'],
785
				)
786
			);
787
		}
788
789
		if (!empty($changes['replace']))
790
		{
791
			foreach ($changes['replace'] as $q_id => $question)
792
			{
793
				$smcFunc['db_query']('', '
794
					UPDATE {db_prefix}qanda
795
					SET lngfile = {string:lngfile},
796
						question = {string:question},
797
						answers = {string:answers}
798
					WHERE id_question = {int:id_question}',
799
					array(
800
						'id_question' => $q_id,
801
						'lngfile' => $question['lngfile'],
802
						'question' => $question['question'],
803
						'answers' => $question['answers'],
804
					)
805
				);
806
			}
807
		}
808
809
		if (!empty($changes['insert']))
810
		{
811
			$smcFunc['db_insert']('insert',
812
				'{db_prefix}qanda',
813
				array('lngfile' => 'string-50', 'question' => 'string-255', 'answers' => 'string-65534'),
814
				$changes['insert'],
815
				array('id_question')
816
			);
817
		}
818
819
		// Lastly, the count of messages needs to be no more than the lowest number of questions for any one language.
820
		$count_questions = empty($qs_per_lang) ? 0 : min($qs_per_lang);
821
		if (empty($count_questions) || $_POST['qa_verification_number'] > $count_questions)
822
			$_POST['qa_verification_number'] = $count_questions;
823
824
		call_integration_hook('integrate_save_spam_settings', array(&$save_vars));
825
826
		// Now save.
827
		saveDBSettings($save_vars);
828
		$_SESSION['adm-save'] = true;
829
830
		cache_put_data('verificationQuestions', null, 300);
831
832
		redirectexit('action=admin;area=antispam');
833
	}
834
835
	$character_range = array_merge(range('A', 'H'), array('K', 'M', 'N', 'P', 'R'), range('T', 'Y'));
836
	$_SESSION['visual_verification_code'] = '';
837
	for ($i = 0; $i < 6; $i++)
838
		$_SESSION['visual_verification_code'] .= $character_range[array_rand($character_range)];
839
840
	// Some javascript for CAPTCHA.
841
	$context['settings_post_javascript'] = '';
842
	if ($context['use_graphic_library'])
843
		$context['settings_post_javascript'] .= '
844
		function refreshImages()
845
		{
846
			var imageType = document.getElementById(\'visual_verification_type\').value;
847
			document.getElementById(\'verification_image\').src = \'' . $context['verification_image_href'] . ';type=\' + imageType;
848
		}';
849
850
	// Show the image itself, or text saying we can't.
851
	if ($context['use_graphic_library'])
852
		$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>';
853
	else
854
		$config_vars['vv']['postinput'] = '<br><span class="smalltext">' . $txt['setting_image_verification_nogd'] . '</span>';
855
856
	// Hack for PM spam settings.
857
	list ($modSettings['max_pm_recipients'], $modSettings['pm_posts_verification'], $modSettings['pm_posts_per_hour']) = explode(',', $modSettings['pm_spam_settings']);
858
859
	// Hack for guests requiring verification.
860
	$modSettings['guests_require_captcha'] = !empty($modSettings['posts_require_captcha']);
861
	$modSettings['posts_require_captcha'] = !isset($modSettings['posts_require_captcha']) || $modSettings['posts_require_captcha'] == -1 ? 0 : $modSettings['posts_require_captcha'];
862
863
	// Some minor javascript for the guest post setting.
864
	if ($modSettings['posts_require_captcha'])
865
		$context['settings_post_javascript'] .= '
866
		document.getElementById(\'guests_require_captcha\').disabled = true;';
867
868
	// And everything else.
869
	$context['post_url'] = $scripturl . '?action=admin;area=antispam;save';
870
	$context['settings_title'] = $txt['antispam_Settings'];
871
	$context['page_title'] = $txt['antispam_title'];
872
	$context['sub_template'] = 'show_settings';
873
874
	$context[$context['admin_menu_name']]['tab_data'] = array(
875
		'title' => $txt['antispam_title'],
876
		'description' => $txt['antispam_Settings_desc'],
877
	);
878
879
	prepareDBSettingContext($config_vars);
880
}
881
882
/**
883
 * You'll never guess what this function does...
884
 *
885
 * @param bool $return_config Whether or not to return the config_vars array (used for admin search)
886
 * @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...
887
 */
888
function ModifySignatureSettings($return_config = false)
889
{
890
	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...
891
892
	$config_vars = array(
893
			// Are signatures even enabled?
894
			array('check', 'signature_enable'),
895
		'',
896
			// Tweaking settings!
897
			array('int', 'signature_max_length', 'subtext' => $txt['zero_for_no_limit']),
898
			array('int', 'signature_max_lines', 'subtext' => $txt['zero_for_no_limit']),
899
			array('int', 'signature_max_font_size', 'subtext' => $txt['zero_for_no_limit']),
900
			array('check', 'signature_allow_smileys', 'onclick' => 'document.getElementById(\'signature_max_smileys\').disabled = !this.checked;'),
901
			array('int', 'signature_max_smileys', 'subtext' => $txt['zero_for_no_limit']),
902
		'',
903
			// Image settings.
904
			array('int', 'signature_max_images', 'subtext' => $txt['signature_max_images_note']),
905
			array('int', 'signature_max_image_width', 'subtext' => $txt['zero_for_no_limit']),
906
			array('int', 'signature_max_image_height', 'subtext' => $txt['zero_for_no_limit']),
907
		'',
908
			array('bbc', 'signature_bbc'),
909
	);
910
911
	call_integration_hook('integrate_signature_settings', array(&$config_vars));
912
913
	if ($return_config)
914
		return $config_vars;
915
916
	// Setup the template.
917
	$context['page_title'] = $txt['signature_settings'];
918
	$context['sub_template'] = 'show_settings';
919
920
	// Disable the max smileys option if we don't allow smileys at all!
921
	$context['settings_post_javascript'] = 'document.getElementById(\'signature_max_smileys\').disabled = !document.getElementById(\'signature_allow_smileys\').checked;';
922
923
	// Load all the signature settings.
924
	list ($sig_limits, $sig_bbc) = explode(':', $modSettings['signature_settings']);
925
	$sig_limits = explode(',', $sig_limits);
926
	$disabledTags = !empty($sig_bbc) ? explode(',', $sig_bbc) : array();
927
928
	// Applying to ALL signatures?!!
929
	if (isset($_GET['apply']))
930
	{
931
		// Security!
932
		checkSession('get');
933
934
		$sig_start = time();
935
		// This is horrid - but I suppose some people will want the option to do it.
936
		$_GET['step'] = isset($_GET['step']) ? (int) $_GET['step'] : 0;
937
		$done = false;
938
939
		$request = $smcFunc['db_query']('', '
940
			SELECT MAX(id_member)
941
			FROM {db_prefix}members',
942
			array(
943
			)
944
		);
945
		list ($context['max_member']) = $smcFunc['db_fetch_row']($request);
946
		$smcFunc['db_free_result']($request);
947
948
		while (!$done)
949
		{
950
			$changes = array();
951
952
			$request = $smcFunc['db_query']('', '
953
				SELECT id_member, signature
954
				FROM {db_prefix}members
955
				WHERE id_member BETWEEN {int:step} AND {int:step} + 49
956
					AND id_group != {int:admin_group}
957
					AND FIND_IN_SET({int:admin_group}, additional_groups) = 0',
958
				array(
959
					'admin_group' => 1,
960
					'step' => $_GET['step'],
961
				)
962
			);
963
			while ($row = $smcFunc['db_fetch_assoc']($request))
964
			{
965
				// Apply all the rules we can realistically do.
966
				$sig = strtr($row['signature'], array('<br>' => "\n"));
967
968
				// Max characters...
969
				if (!empty($sig_limits[1]))
970
					$sig = $smcFunc['substr']($sig, 0, $sig_limits[1]);
971
				// Max lines...
972
				if (!empty($sig_limits[2]))
973
				{
974
					$count = 0;
975
					for ($i = 0; $i < strlen($sig); $i++)
976
					{
977
						if ($sig[$i] == "\n")
978
						{
979
							$count++;
980
							if ($count >= $sig_limits[2])
981
								$sig = substr($sig, 0, $i) . strtr(substr($sig, $i), array("\n" => ' '));
982
						}
983
					}
984
				}
985
986
				if (!empty($sig_limits[7]) && preg_match_all('~\[size=([\d\.]+)?(px|pt|em|x-large|larger)~i', $sig, $matches) !== false && isset($matches[2]))
987
				{
988
					foreach ($matches[1] as $ind => $size)
989
					{
990
						$limit_broke = 0;
991
						// Attempt to allow all sizes of abuse, so to speak.
992 View Code Duplication
						if ($matches[2][$ind] == 'px' && $size > $sig_limits[7])
993
							$limit_broke = $sig_limits[7] . 'px';
994
						elseif ($matches[2][$ind] == 'pt' && $size > ($sig_limits[7] * 0.75))
995
							$limit_broke = ((int) $sig_limits[7] * 0.75) . 'pt';
996
						elseif ($matches[2][$ind] == 'em' && $size > ((float) $sig_limits[7] / 16))
997
							$limit_broke = ((float) $sig_limits[7] / 16) . 'em';
998
						elseif ($matches[2][$ind] != 'px' && $matches[2][$ind] != 'pt' && $matches[2][$ind] != 'em' && $sig_limits[7] < 18)
999
							$limit_broke = 'large';
1000
1001
						if ($limit_broke)
1002
							$sig = str_replace($matches[0][$ind], '[size=' . $sig_limits[7] . 'px', $sig);
1003
					}
1004
				}
1005
1006
				// Stupid images - this is stupidly, stupidly challenging.
1007
				if ((!empty($sig_limits[3]) || !empty($sig_limits[5]) || !empty($sig_limits[6])))
1008
				{
1009
					$replaces = array();
1010
					$img_count = 0;
1011
					// Get all BBC tags...
1012
					preg_match_all('~\[img(\s+width=([\d]+))?(\s+height=([\d]+))?(\s+width=([\d]+))?\s*\](?:<br>)*([^<">]+?)(?:<br>)*\[/img\]~i', $sig, $matches);
1013
					// ... and all HTML ones.
1014
					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);
1015
					// And stick the HTML in the BBC.
1016 View Code Duplication
					if (!empty($matches2))
1017
					{
1018
						foreach ($matches2[0] as $ind => $dummy)
1019
						{
1020
							$matches[0][] = $matches2[0][$ind];
1021
							$matches[1][] = '';
1022
							$matches[2][] = '';
1023
							$matches[3][] = '';
1024
							$matches[4][] = '';
1025
							$matches[5][] = '';
1026
							$matches[6][] = '';
1027
							$matches[7][] = $matches2[1][$ind];
1028
						}
1029
					}
1030
					// Try to find all the images!
1031
					if (!empty($matches))
1032
					{
1033
						$image_count_holder = array();
1034
						foreach ($matches[0] as $key => $image)
1035
						{
1036
							$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...
1037
							$img_count++;
1038
							// Too many images?
1039
							if (!empty($sig_limits[3]) && $img_count > $sig_limits[3])
1040
							{
1041
								// If we've already had this before we only want to remove the excess.
1042
								if (isset($image_count_holder[$image]))
1043
								{
1044
									$img_offset = -1;
1045
									$rep_img_count = 0;
1046
									while ($img_offset !== false)
1047
									{
1048
										$img_offset = strpos($sig, $image, $img_offset + 1);
1049
										$rep_img_count++;
1050
										if ($rep_img_count > $image_count_holder[$image])
1051
										{
1052
											// Only replace the excess.
1053
											$sig = substr($sig, 0, $img_offset) . str_replace($image, '', substr($sig, $img_offset));
1054
											// Stop looping.
1055
											$img_offset = false;
1056
										}
1057
									}
1058
								}
1059
								else
1060
									$replaces[$image] = '';
1061
1062
								continue;
1063
							}
1064
1065
							// Does it have predefined restraints? Width first.
1066 View Code Duplication
							if ($matches[6][$key])
1067
								$matches[2][$key] = $matches[6][$key];
1068 View Code Duplication
							if ($matches[2][$key] && $sig_limits[5] && $matches[2][$key] > $sig_limits[5])
1069
							{
1070
								$width = $sig_limits[5];
1071
								$matches[4][$key] = $matches[4][$key] * ($width / $matches[2][$key]);
1072
							}
1073
							elseif ($matches[2][$key])
1074
								$width = $matches[2][$key];
1075
							// ... and height.
1076 View Code Duplication
							if ($matches[4][$key] && $sig_limits[6] && $matches[4][$key] > $sig_limits[6])
1077
							{
1078
								$height = $sig_limits[6];
1079
								if ($width != -1)
1080
									$width = $width * ($height / $matches[4][$key]);
1081
							}
1082
							elseif ($matches[4][$key])
1083
								$height = $matches[4][$key];
1084
1085
							// If the dimensions are still not fixed - we need to check the actual image.
1086 View Code Duplication
							if (($width == -1 && $sig_limits[5]) || ($height == -1 && $sig_limits[6]))
1087
							{
1088
								$sizes = url_image_size($matches[7][$key]);
1089
								if (is_array($sizes))
1090
								{
1091
									// Too wide?
1092
									if ($sizes[0] > $sig_limits[5] && $sig_limits[5])
1093
									{
1094
										$width = $sig_limits[5];
1095
										$sizes[1] = $sizes[1] * ($width / $sizes[0]);
1096
									}
1097
									// Too high?
1098
									if ($sizes[1] > $sig_limits[6] && $sig_limits[6])
1099
									{
1100
										$height = $sig_limits[6];
1101
										if ($width == -1)
1102
											$width = $sizes[0];
1103
										$width = $width * ($height / $sizes[1]);
1104
									}
1105
									elseif ($width != -1)
1106
										$height = $sizes[1];
1107
								}
1108
							}
1109
1110
							// Did we come up with some changes? If so remake the string.
1111 View Code Duplication
							if ($width != -1 || $height != -1)
1112
							{
1113
								$replaces[$image] = '[img' . ($width != -1 ? ' width=' . round($width) : '') . ($height != -1 ? ' height=' . round($height) : '') . ']' . $matches[7][$key] . '[/img]';
1114
							}
1115
1116
							// Record that we got one.
1117
							$image_count_holder[$image] = isset($image_count_holder[$image]) ? $image_count_holder[$image] + 1 : 1;
1118
						}
1119
						if (!empty($replaces))
1120
							$sig = str_replace(array_keys($replaces), array_values($replaces), $sig);
1121
					}
1122
				}
1123
				// Try to fix disabled tags.
1124
				if (!empty($disabledTags))
1125
				{
1126
					$sig = preg_replace('~\[(?:' . implode('|', $disabledTags) . ').+?\]~i', '', $sig);
1127
					$sig = preg_replace('~\[/(?:' . implode('|', $disabledTags) . ')\]~i', '', $sig);
1128
				}
1129
1130
				$sig = strtr($sig, array("\n" => '<br>'));
1131
				call_integration_hook('integrate_apply_signature_settings', array(&$sig, $sig_limits, $disabledTags));
1132
				if ($sig != $row['signature'])
1133
					$changes[$row['id_member']] = $sig;
1134
			}
1135
			if ($smcFunc['db_num_rows']($request) == 0)
1136
				$done = true;
1137
			$smcFunc['db_free_result']($request);
1138
1139
			// Do we need to delete what we have?
1140 View Code Duplication
			if (!empty($changes))
1141
			{
1142
				foreach ($changes as $id => $sig)
1143
					$smcFunc['db_query']('', '
1144
						UPDATE {db_prefix}members
1145
						SET signature = {string:signature}
1146
						WHERE id_member = {int:id_member}',
1147
						array(
1148
							'id_member' => $id,
1149
							'signature' => $sig,
1150
						)
1151
					);
1152
			}
1153
1154
			$_GET['step'] += 50;
1155
			if (!$done)
1156
				pauseSignatureApplySettings();
1157
		}
1158
		$settings_applied = true;
1159
	}
1160
1161
	$context['signature_settings'] = array(
1162
		'enable' => isset($sig_limits[0]) ? $sig_limits[0] : 0,
1163
		'max_length' => isset($sig_limits[1]) ? $sig_limits[1] : 0,
1164
		'max_lines' => isset($sig_limits[2]) ? $sig_limits[2] : 0,
1165
		'max_images' => isset($sig_limits[3]) ? $sig_limits[3] : 0,
1166
		'allow_smileys' => isset($sig_limits[4]) && $sig_limits[4] == -1 ? 0 : 1,
1167
		'max_smileys' => isset($sig_limits[4]) && $sig_limits[4] != -1 ? $sig_limits[4] : 0,
1168
		'max_image_width' => isset($sig_limits[5]) ? $sig_limits[5] : 0,
1169
		'max_image_height' => isset($sig_limits[6]) ? $sig_limits[6] : 0,
1170
		'max_font_size' => isset($sig_limits[7]) ? $sig_limits[7] : 0,
1171
	);
1172
1173
	// Temporarily make each setting a modSetting!
1174
	foreach ($context['signature_settings'] as $key => $value)
1175
		$modSettings['signature_' . $key] = $value;
1176
1177
	// Make sure we check the right tags!
1178
	$modSettings['bbc_disabled_signature_bbc'] = $disabledTags;
1179
1180
	// Saving?
1181
	if (isset($_GET['save']))
1182
	{
1183
		checkSession();
1184
1185
		// Clean up the tag stuff!
1186
		$bbcTags = array();
1187
		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...
1188
			$bbcTags[] = $tag['tag'];
1189
1190 View Code Duplication
		if (!isset($_POST['signature_bbc_enabledTags']))
1191
			$_POST['signature_bbc_enabledTags'] = array();
1192
		elseif (!is_array($_POST['signature_bbc_enabledTags']))
1193
			$_POST['signature_bbc_enabledTags'] = array($_POST['signature_bbc_enabledTags']);
1194
1195
		$sig_limits = array();
1196
		foreach ($context['signature_settings'] as $key => $value)
1197
		{
1198
			if ($key == 'allow_smileys')
1199
				continue;
1200
			elseif ($key == 'max_smileys' && empty($_POST['signature_allow_smileys']))
1201
				$sig_limits[] = -1;
1202
			else
1203
				$sig_limits[] = !empty($_POST['signature_' . $key]) ? max(1, (int) $_POST['signature_' . $key]) : 0;
1204
		}
1205
1206
		call_integration_hook('integrate_save_signature_settings', array(&$sig_limits, &$bbcTags));
1207
1208
		$_POST['signature_settings'] = implode(',', $sig_limits) . ':' . implode(',', array_diff($bbcTags, $_POST['signature_bbc_enabledTags']));
1209
1210
		// Even though we have practically no settings let's keep the convention going!
1211
		$save_vars = array();
1212
		$save_vars[] = array('text', 'signature_settings');
1213
1214
		saveDBSettings($save_vars);
1215
		$_SESSION['adm-save'] = true;
1216
		redirectexit('action=admin;area=featuresettings;sa=sig');
1217
	}
1218
1219
	$context['post_url'] = $scripturl . '?action=admin;area=featuresettings;save;sa=sig';
1220
	$context['settings_title'] = $txt['signature_settings'];
1221
1222
	$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>';
1223
1224
	prepareDBSettingContext($config_vars);
1225
}
1226
1227
/**
1228
 * Just pause the signature applying thing.
1229
 */
1230
function pauseSignatureApplySettings()
1231
{
1232
	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...
1233
1234
	// Try get more time...
1235
	@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...
1236
	if (function_exists('apache_reset_timeout'))
1237
		@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...
1238
1239
	// Have we exhausted all the time we allowed?
1240
	if (time() - array_sum(explode(' ', $sig_start)) < 3)
1241
		return;
1242
1243
	$context['continue_get_data'] = '?action=admin;area=featuresettings;sa=sig;apply;step=' . $_GET['step'] . ';' . $context['session_var'] . '=' . $context['session_id'];
1244
	$context['page_title'] = $txt['not_done_title'];
1245
	$context['continue_post_data'] = '';
1246
	$context['continue_countdown'] = '2';
1247
	$context['sub_template'] = 'not_done';
1248
1249
	// Specific stuff to not break this template!
1250
	$context[$context['admin_menu_name']]['current_subsection'] = 'sig';
1251
1252
	// Get the right percent.
1253
	$context['continue_percent'] = round(($_GET['step'] / $context['max_member']) * 100);
1254
1255
	// Never more than 100%!
1256
	$context['continue_percent'] = min($context['continue_percent'], 100);
1257
1258
	obExit();
1259
}
1260
1261
/**
1262
 * Show all the custom profile fields available to the user.
1263
 */
1264
function ShowCustomProfiles()
1265
{
1266
	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...
1267
	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...
1268
1269
	$context['page_title'] = $txt['custom_profile_title'];
1270
	$context['sub_template'] = 'show_custom_profile';
1271
1272
	// What about standard fields they can tweak?
1273
	$standard_fields = array('website', 'personal_text', 'timezone', 'posts', 'warning_status');
1274
	// What fields can't you put on the registration page?
1275
	$context['fields_no_registration'] = array('posts', 'warning_status');
1276
1277
	// Are we saving any standard field changes?
1278
	if (isset($_POST['save']))
1279
	{
1280
		checkSession();
1281
		validateToken('admin-scp');
1282
1283
		// Do the active ones first.
1284
		$disable_fields = array_flip($standard_fields);
1285
		if (!empty($_POST['active']))
1286
		{
1287
			foreach ($_POST['active'] as $value)
1288
				if (isset($disable_fields[$value]))
1289
					unset($disable_fields[$value]);
1290
		}
1291
		// What we have left!
1292
		$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...
1293
1294
		// Things we want to show on registration?
1295
		$reg_fields = array();
1296
		if (!empty($_POST['reg']))
1297
		{
1298
			foreach ($_POST['reg'] as $value)
1299
				if (in_array($value, $standard_fields) && !isset($disable_fields[$value]))
1300
					$reg_fields[] = $value;
1301
		}
1302
		// What we have left!
1303
		$changes['registration_fields'] = empty($reg_fields) ? '' : implode(',', $reg_fields);
1304
1305
		$_SESSION['adm-save'] = true;
1306
		if (!empty($changes))
1307
			updateSettings($changes);
1308
	}
1309
1310
	createToken('admin-scp');
1311
1312
	// Need to know the max order for custom fields
1313
	$context['custFieldsMaxOrder'] = custFieldsMaxOrder();
1314
1315
	require_once($sourcedir . '/Subs-List.php');
1316
1317
	$listOptions = array(
1318
		'id' => 'standard_profile_fields',
1319
		'title' => $txt['standard_profile_title'],
1320
		'base_href' => $scripturl . '?action=admin;area=featuresettings;sa=profile',
1321
		'get_items' => array(
1322
			'function' => 'list_getProfileFields',
1323
			'params' => array(
1324
				true,
1325
			),
1326
		),
1327
		'columns' => array(
1328
			'field' => array(
1329
				'header' => array(
1330
					'value' => $txt['standard_profile_field'],
1331
				),
1332
				'data' => array(
1333
					'db' => 'label',
1334
					'style' => 'width: 60%;',
1335
				),
1336
			),
1337
			'active' => array(
1338
				'header' => array(
1339
					'value' => $txt['custom_edit_active'],
1340
					'class' => 'centercol',
1341
				),
1342
				'data' => array(
1343 View Code Duplication
					'function' => function ($rowData)
1344
					{
1345
						$isChecked = $rowData['disabled'] ? '' : ' checked';
1346
						$onClickHandler = $rowData['can_show_register'] ? sprintf(' onclick="document.getElementById(\'reg_%1$s\').disabled = !this.checked;"', $rowData['id']) : '';
1347
						return sprintf('<input type="checkbox" name="active[]" id="active_%1$s" value="%1$s" %2$s%3$s>', $rowData['id'], $isChecked, $onClickHandler);
1348
					},
1349
					'style' => 'width: 20%;',
1350
					'class' => 'centercol',
1351
				),
1352
			),
1353
			'show_on_registration' => array(
1354
				'header' => array(
1355
					'value' => $txt['custom_edit_registration'],
1356
					'class' => 'centercol',
1357
				),
1358
				'data' => array(
1359 View Code Duplication
					'function' => function ($rowData)
1360
					{
1361
						$isChecked = $rowData['on_register'] && !$rowData['disabled'] ? ' checked' : '';
1362
						$isDisabled = $rowData['can_show_register'] ? '' : ' disabled';
1363
						return sprintf('<input type="checkbox" name="reg[]" id="reg_%1$s" value="%1$s" %2$s%3$s>', $rowData['id'], $isChecked, $isDisabled);
1364
					},
1365
					'style' => 'width: 20%;',
1366
					'class' => 'centercol',
1367
				),
1368
			),
1369
		),
1370
		'form' => array(
1371
			'href' => $scripturl . '?action=admin;area=featuresettings;sa=profile',
1372
			'name' => 'standardProfileFields',
1373
			'token' => 'admin-scp',
1374
		),
1375
		'additional_rows' => array(
1376
			array(
1377
				'position' => 'below_table_data',
1378
				'value' => '<input type="submit" name="save" value="' . $txt['save'] . '" class="button">',
1379
			),
1380
		),
1381
	);
1382
	createList($listOptions);
1383
1384
	$listOptions = array(
1385
		'id' => 'custom_profile_fields',
1386
		'title' => $txt['custom_profile_title'],
1387
		'base_href' => $scripturl . '?action=admin;area=featuresettings;sa=profile',
1388
		'default_sort_col' => 'field_order',
1389
		'no_items_label' => $txt['custom_profile_none'],
1390
		'items_per_page' => 25,
1391
		'get_items' => array(
1392
			'function' => 'list_getProfileFields',
1393
			'params' => array(
1394
				false,
1395
			),
1396
		),
1397
		'get_count' => array(
1398
			'function' => 'list_getProfileFieldSize',
1399
		),
1400
		'columns' => array(
1401
			'field_order' => array(
1402
				'header' => array(
1403
					'value' => $txt['custom_profile_fieldorder'],
1404
				),
1405
				'data' => array(
1406
					'function' => function ($rowData) use ($context, $txt, $scripturl)
1407
					{
1408
						$return = '<p class="centertext bold_text">'. $rowData['field_order'] .'<br>';
1409
1410 View Code Duplication
						if ($rowData['field_order'] > 1)
1411
							$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>';
1412
1413 View Code Duplication
						if ($rowData['field_order'] < $context['custFieldsMaxOrder'])
1414
							$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>';
1415
1416
						$return .= '</p>';
1417
1418
						return $return;
1419
					},
1420
					'style' => 'width: 12%;',
1421
				),
1422
				'sort' => array(
1423
					'default' => 'field_order',
1424
					'reverse' => 'field_order DESC',
1425
				),
1426
			),
1427
			'field_name' => array(
1428
				'header' => array(
1429
					'value' => $txt['custom_profile_fieldname'],
1430
				),
1431
				'data' => array(
1432
					'function' => function ($rowData) use ($scripturl)
1433
					{
1434
						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']);
1435
					},
1436
					'style' => 'width: 62%;',
1437
				),
1438
				'sort' => array(
1439
					'default' => 'field_name',
1440
					'reverse' => 'field_name DESC',
1441
				),
1442
			),
1443
			'field_type' => array(
1444
				'header' => array(
1445
					'value' => $txt['custom_profile_fieldtype'],
1446
				),
1447
				'data' => array(
1448
					'function' => function ($rowData) use ($txt)
1449
					{
1450
						$textKey = sprintf('custom_profile_type_%1$s', $rowData['field_type']);
1451
						return isset($txt[$textKey]) ? $txt[$textKey] : $textKey;
1452
					},
1453
					'style' => 'width: 15%;',
1454
					'class' => 'hidden',
1455
				),
1456
				'sort' => array(
1457
					'default' => 'field_type',
1458
					'reverse' => 'field_type DESC',
1459
				),
1460
			),
1461
			'active' => array(
1462
				'header' => array(
1463
					'value' => $txt['custom_profile_active'],
1464
				),
1465
				'data' => array(
1466
					'function' => function ($rowData) use ($txt)
1467
					{
1468
						return $rowData['active'] ? $txt['yes'] : $txt['no'];
1469
					},
1470
					'style' => 'width: 8%;',
1471
					'class' => 'hidden',
1472
				),
1473
				'sort' => array(
1474
					'default' => 'active DESC',
1475
					'reverse' => 'active',
1476
				),
1477
			),
1478
			'placement' => array(
1479
				'header' => array(
1480
					'value' => $txt['custom_profile_placement'],
1481
				),
1482
				'data' => array(
1483
					'function' => function ($rowData)
1484
					{
1485
						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...
1486
1487
						return $txt['custom_profile_placement_' . (empty($rowData['placement']) ? 'standard' : $context['cust_profile_fields_placement'][$rowData['placement']])];
1488
					},
1489
					'style' => 'width: 8%;',
1490
					'class' => 'hidden',
1491
				),
1492
				'sort' => array(
1493
					'default' => 'placement DESC',
1494
					'reverse' => 'placement',
1495
				),
1496
			),
1497
			'show_on_registration' => array(
1498
				'data' => array(
1499
					'sprintf' => array(
1500
						'format' => '<a href="' . $scripturl . '?action=admin;area=featuresettings;sa=profileedit;fid=%1$s">' . $txt['modify'] . '</a>',
1501
						'params' => array(
1502
							'id_field' => false,
1503
						),
1504
					),
1505
					'style' => 'width: 15%;',
1506
				),
1507
			),
1508
		),
1509
		'form' => array(
1510
			'href' => $scripturl . '?action=admin;area=featuresettings;sa=profileedit',
1511
			'name' => 'customProfileFields',
1512
		),
1513
		'additional_rows' => array(
1514
			array(
1515
				'position' => 'below_table_data',
1516
				'value' => '<input type="submit" name="new" value="' . $txt['custom_profile_make_new'] . '" class="button">',
1517
			),
1518
		),
1519
	);
1520
	createList($listOptions);
1521
1522
	// There are two different ways we could get to this point. To keep it simple, they both do
1523
	// the same basic thing.
1524
	if (isset($_SESSION['adm-save']))
1525
	{
1526
		$context['saved_successful'] = true;
1527
		unset ($_SESSION['adm-save']);
1528
	}
1529
}
1530
1531
/**
1532
 * Callback for createList().
1533
 * @param int $start The item to start with (used for pagination purposes)
1534
 * @param int $items_per_page The number of items to display per page
1535
 * @param string $sort A string indicating how to sort the results
1536
 * @param bool $standardFields Whether or not to include standard fields as well
1537
 * @return array An array of info about the various profile fields
1538
 */
1539
function list_getProfileFields($start, $items_per_page, $sort, $standardFields)
1540
{
1541
	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...
1542
1543
	$list = array();
1544
1545
	if ($standardFields)
1546
	{
1547
		$standard_fields = array('website', 'personal_text', 'timezone', 'posts', 'warning_status');
1548
		$fields_no_registration = array('posts', 'warning_status');
1549
		$disabled_fields = isset($modSettings['disabled_profile_fields']) ? explode(',', $modSettings['disabled_profile_fields']) : array();
1550
		$registration_fields = isset($modSettings['registration_fields']) ? explode(',', $modSettings['registration_fields']) : array();
1551
1552
		foreach ($standard_fields as $field)
1553
			$list[] = array(
1554
				'id' => $field,
1555
				'label' => isset($txt['standard_profile_field_' . $field]) ? $txt['standard_profile_field_' . $field] : (isset($txt[$field]) ? $txt[$field] : $field),
1556
				'disabled' => in_array($field, $disabled_fields),
1557
				'on_register' => in_array($field, $registration_fields) && !in_array($field, $fields_no_registration),
1558
				'can_show_register' => !in_array($field, $fields_no_registration),
1559
			);
1560
	}
1561
	else
1562
	{
1563
		// Load all the fields.
1564
		$request = $smcFunc['db_query']('', '
1565
			SELECT id_field, col_name, field_name, field_desc, field_type, field_order, active, placement
1566
			FROM {db_prefix}custom_fields
1567
			ORDER BY {raw:sort}
1568
			LIMIT {int:start}, {int:items_per_page}',
1569
			array(
1570
				'sort' => $sort,
1571
				'start' => $start,
1572
				'items_per_page' => $items_per_page,
1573
			)
1574
		);
1575
		while ($row = $smcFunc['db_fetch_assoc']($request))
1576
			$list[] = $row;
1577
		$smcFunc['db_free_result']($request);
1578
	}
1579
1580
	return $list;
1581
}
1582
1583
/**
1584
 * Callback for createList().
1585
 * @return int The total number of custom profile fields
1586
 */
1587
function list_getProfileFieldSize()
1588
{
1589
	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...
1590
1591
	$request = $smcFunc['db_query']('', '
1592
		SELECT COUNT(*)
1593
		FROM {db_prefix}custom_fields',
1594
		array(
1595
		)
1596
	);
1597
1598
	list ($numProfileFields) = $smcFunc['db_fetch_row']($request);
1599
	$smcFunc['db_free_result']($request);
1600
1601
	return $numProfileFields;
1602
}
1603
1604
/**
1605
 * Edit some profile fields?
1606
 */
1607
function EditCustomProfiles()
1608
{
1609
	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...
1610
1611
	// Sort out the context!
1612
	$context['fid'] = isset($_GET['fid']) ? (int) $_GET['fid'] : 0;
1613
	$context[$context['admin_menu_name']]['current_subsection'] = 'profile';
1614
	$context['page_title'] = $context['fid'] ? $txt['custom_edit_title'] : $txt['custom_add_title'];
1615
	$context['sub_template'] = 'edit_profile_field';
1616
1617
	// Load the profile language for section names.
1618
	loadLanguage('Profile');
1619
1620
	// There's really only a few places we can go...
1621
	$move_to = array('up', 'down');
1622
1623
	// We need this for both moving and saving so put it right here.
1624
	$order_count = custFieldsMaxOrder();
1625
1626
	if ($context['fid'])
1627
	{
1628
		$request = $smcFunc['db_query']('', '
1629
			SELECT
1630
				id_field, col_name, field_name, field_desc, field_type, field_order, field_length, field_options,
1631
				show_reg, show_display, show_mlist, show_profile, private, active, default_value, can_search,
1632
				bbc, mask, enclose, placement
1633
			FROM {db_prefix}custom_fields
1634
			WHERE id_field = {int:current_field}',
1635
			array(
1636
				'current_field' => $context['fid'],
1637
			)
1638
		);
1639
		$context['field'] = array();
1640
		while ($row = $smcFunc['db_fetch_assoc']($request))
1641
		{
1642
			if ($row['field_type'] == 'textarea')
1643
				@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...
1644
			else
1645
			{
1646
				$rows = 3;
1647
				$cols = 30;
1648
			}
1649
1650
			$context['field'] = array(
1651
				'name' => $row['field_name'],
1652
				'desc' => $row['field_desc'],
1653
				'col_name' => $row['col_name'],
1654
				'profile_area' => $row['show_profile'],
1655
				'reg' => $row['show_reg'],
1656
				'display' => $row['show_display'],
1657
				'mlist' => $row['show_mlist'],
1658
				'type' => $row['field_type'],
1659
				'order' => $row['field_order'],
1660
				'max_length' => $row['field_length'],
1661
				'rows' => $rows,
1662
				'cols' => $cols,
1663
				'bbc' => $row['bbc'] ? true : false,
1664
				'default_check' => $row['field_type'] == 'check' && $row['default_value'] ? true : false,
1665
				'default_select' => $row['field_type'] == 'select' || $row['field_type'] == 'radio' ? $row['default_value'] : '',
1666
				'options' => strlen($row['field_options']) > 1 ? explode(',', $row['field_options']) : array('', '', ''),
1667
				'active' => $row['active'],
1668
				'private' => $row['private'],
1669
				'can_search' => $row['can_search'],
1670
				'mask' => $row['mask'],
1671
				'regex' => substr($row['mask'], 0, 5) == 'regex' ? substr($row['mask'], 5) : '',
1672
				'enclose' => $row['enclose'],
1673
				'placement' => $row['placement'],
1674
			);
1675
		}
1676
		$smcFunc['db_free_result']($request);
1677
	}
1678
1679
	// Setup the default values as needed.
1680
	if (empty($context['field']))
1681
		$context['field'] = array(
1682
			'name' => '',
1683
			'col_name' => '???',
1684
			'desc' => '',
1685
			'profile_area' => 'forumprofile',
1686
			'reg' => false,
1687
			'display' => false,
1688
			'mlist' => false,
1689
			'type' => 'text',
1690
			'order' => 0,
1691
			'max_length' => 255,
1692
			'rows' => 4,
1693
			'cols' => 30,
1694
			'bbc' => false,
1695
			'default_check' => false,
1696
			'default_select' => '',
1697
			'options' => array('', '', ''),
1698
			'active' => true,
1699
			'private' => false,
1700
			'can_search' => false,
1701
			'mask' => 'nohtml',
1702
			'regex' => '',
1703
			'enclose' => '',
1704
			'placement' => 0,
1705
		);
1706
1707
	// Are we moving it?
1708
	if (isset($_GET['move']) && in_array($smcFunc['htmlspecialchars']($_GET['move']), $move_to))
1709
	{
1710
		// Down is the new up.
1711
		$new_order = ($_GET['move'] == 'up' ? ($context['field']['order'] - 1) : ($context['field']['order'] + 1));
1712
1713
		// Is this a valid position?
1714
		if ($new_order <= 0 || $new_order > $order_count)
1715
			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...
1716
1717
		// All good, proceed.
1718
		$smcFunc['db_query']('','
1719
			UPDATE {db_prefix}custom_fields
1720
			SET field_order = {int:old_order}
1721
			WHERE field_order = {int:new_order}',
1722
			array(
1723
				'new_order' => $new_order,
1724
				'old_order' => $context['field']['order'],
1725
			)
1726
		);
1727
		$smcFunc['db_query']('','
1728
			UPDATE {db_prefix}custom_fields
1729
			SET field_order = {int:new_order}
1730
			WHERE id_field = {int:id_field}',
1731
			array(
1732
				'new_order' => $new_order,
1733
				'id_field' => $context['fid'],
1734
			)
1735
		);
1736
		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...
1737
	}
1738
1739
	// Are we saving?
1740
	if (isset($_POST['save']))
1741
	{
1742
		checkSession();
1743
		validateToken('admin-ecp');
1744
1745
		// Everyone needs a name - even the (bracket) unknown...
1746
		if (trim($_POST['field_name']) == '')
1747
			redirectexit($scripturl . '?action=admin;area=featuresettings;sa=profileedit;fid=' . $_GET['fid'] . ';msg=need_name');
1748
1749
		// Regex you say?  Do a very basic test to see if the pattern is valid
1750
		if (!empty($_POST['regex']) && @preg_match($_POST['regex'], 'dummy') === false)
1751
			redirectexit($scripturl . '?action=admin;area=featuresettings;sa=profileedit;fid=' . $_GET['fid'] . ';msg=regex_error');
1752
1753
		$_POST['field_name'] = $smcFunc['htmlspecialchars']($_POST['field_name']);
1754
		$_POST['field_desc'] = $smcFunc['htmlspecialchars']($_POST['field_desc']);
1755
1756
		// Checkboxes...
1757
		$show_reg = isset($_POST['reg']) ? (int) $_POST['reg'] : 0;
1758
		$show_display = isset($_POST['display']) ? 1 : 0;
1759
		$show_mlist = isset($_POST['mlist']) ? 1 : 0;
1760
		$bbc = isset($_POST['bbc']) ? 1 : 0;
1761
		$show_profile = $_POST['profile_area'];
1762
		$active = isset($_POST['active']) ? 1 : 0;
1763
		$private = isset($_POST['private']) ? (int) $_POST['private'] : 0;
1764
		$can_search = isset($_POST['can_search']) ? 1 : 0;
1765
1766
		// Some masking stuff...
1767
		$mask = isset($_POST['mask']) ? $_POST['mask'] : '';
1768
		if ($mask == 'regex' && isset($_POST['regex']))
1769
			$mask .= $_POST['regex'];
1770
1771
		$field_length = isset($_POST['max_length']) ? (int) $_POST['max_length'] : 255;
1772
		$enclose = isset($_POST['enclose']) ? $_POST['enclose'] : '';
1773
		$placement = isset($_POST['placement']) ? (int) $_POST['placement'] : 0;
1774
1775
		// Select options?
1776
		$field_options = '';
1777
		$newOptions = array();
1778
		$default = isset($_POST['default_check']) && $_POST['field_type'] == 'check' ? 1 : '';
1779
		if (!empty($_POST['select_option']) && ($_POST['field_type'] == 'select' || $_POST['field_type'] == 'radio'))
1780
		{
1781
			foreach ($_POST['select_option'] as $k => $v)
1782
			{
1783
				// Clean, clean, clean...
1784
				$v = $smcFunc['htmlspecialchars']($v);
1785
				$v = strtr($v, array(',' => ''));
1786
1787
				// Nada, zip, etc...
1788
				if (trim($v) == '')
1789
					continue;
1790
1791
				// Otherwise, save it boy.
1792
				$field_options .= $v . ',';
1793
				// This is just for working out what happened with old options...
1794
				$newOptions[$k] = $v;
1795
1796
				// Is it default?
1797
				if (isset($_POST['default_select']) && $_POST['default_select'] == $k)
1798
					$default = $v;
1799
			}
1800
			$field_options = substr($field_options, 0, -1);
1801
		}
1802
1803
		// Text area has default has dimensions
1804
		if ($_POST['field_type'] == 'textarea')
1805
			$default = (int) $_POST['rows'] . ',' . (int) $_POST['cols'];
1806
1807
		// Come up with the unique name?
1808
		if (empty($context['fid']))
1809
		{
1810
			$col_name = $smcFunc['substr'](strtr($_POST['field_name'], array(' ' => '')), 0, 6);
1811
			preg_match('~([\w\d_-]+)~', $col_name, $matches);
1812
1813
			// If there is nothing to the name, then let's start out own - for foreign languages etc.
1814
			if (isset($matches[1]))
1815
				$col_name = $initial_col_name = 'cust_' . strtolower($matches[1]);
1816
			else
1817
				$col_name = $initial_col_name = 'cust_' . mt_rand(1, 9999);
1818
1819
			// Make sure this is unique.
1820
			$current_fields = array();
1821
			$request = $smcFunc['db_query']('', '
1822
				SELECT id_field, col_name
1823
				FROM {db_prefix}custom_fields');
1824
			while ($row = $smcFunc['db_fetch_assoc']($request))
1825
				$current_fields[$row['id_field']] = $row['col_name'];
1826
			$smcFunc['db_free_result']($request);
1827
1828
			$unique = false;
1829
			for ($i = 0; !$unique && $i < 9; $i ++)
1830
			{
1831
				if (!in_array($col_name, $current_fields))
1832
					$unique = true;
1833
				else
1834
					$col_name = $initial_col_name . $i;
1835
			}
1836
1837
			// Still not a unique column name? Leave it up to the user, then.
1838
			if (!$unique)
1839
				fatal_lang_error('custom_option_not_unique');
1840
		}
1841
		// Work out what to do with the user data otherwise...
1842
		else
1843
		{
1844
			// Anything going to check or select is pointless keeping - as is anything coming from check!
1845
			if (($_POST['field_type'] == 'check' && $context['field']['type'] != 'check')
1846
				|| (($_POST['field_type'] == 'select' || $_POST['field_type'] == 'radio') && $context['field']['type'] != 'select' && $context['field']['type'] != 'radio')
1847
				|| ($context['field']['type'] == 'check' && $_POST['field_type'] != 'check'))
1848
			{
1849
				$smcFunc['db_query']('', '
1850
					DELETE FROM {db_prefix}themes
1851
					WHERE variable = {string:current_column}
1852
						AND id_member > {int:no_member}',
1853
					array(
1854
						'no_member' => 0,
1855
						'current_column' => $context['field']['col_name'],
1856
					)
1857
				);
1858
			}
1859
			// Otherwise - if the select is edited may need to adjust!
1860
			elseif ($_POST['field_type'] == 'select' || $_POST['field_type'] == 'radio')
1861
			{
1862
				$optionChanges = array();
1863
				$takenKeys = array();
1864
				// Work out what's changed!
1865
				foreach ($context['field']['options'] as $k => $option)
1866
				{
1867
					if (trim($option) == '')
1868
						continue;
1869
1870
					// Still exists?
1871
					if (in_array($option, $newOptions))
1872
					{
1873
						$takenKeys[] = $k;
1874
						continue;
1875
					}
1876
				}
1877
1878
				// Finally - have we renamed it - or is it really gone?
1879
				foreach ($optionChanges as $k => $option)
1880
				{
1881
					// Just been renamed?
1882
					if (!in_array($k, $takenKeys) && !empty($newOptions[$k]))
1883
						$smcFunc['db_query']('', '
1884
							UPDATE {db_prefix}themes
1885
							SET value = {string:new_value}
1886
							WHERE variable = {string:current_column}
1887
								AND value = {string:old_value}
1888
								AND id_member > {int:no_member}',
1889
							array(
1890
								'no_member' => 0,
1891
								'new_value' => $newOptions[$k],
1892
								'current_column' => $context['field']['col_name'],
1893
								'old_value' => $option,
1894
							)
1895
						);
1896
				}
1897
			}
1898
			// @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...
1899
		}
1900
1901
		// Do the insertion/updates.
1902
		if ($context['fid'])
1903
		{
1904
			$smcFunc['db_query']('', '
1905
				UPDATE {db_prefix}custom_fields
1906
				SET
1907
					field_name = {string:field_name}, field_desc = {string:field_desc},
1908
					field_type = {string:field_type}, field_length = {int:field_length},
1909
					field_options = {string:field_options}, show_reg = {int:show_reg},
1910
					show_display = {int:show_display}, show_mlist = {int:show_mlist}, show_profile = {string:show_profile},
1911
					private = {int:private}, active = {int:active}, default_value = {string:default_value},
1912
					can_search = {int:can_search}, bbc = {int:bbc}, mask = {string:mask},
1913
					enclose = {string:enclose}, placement = {int:placement}
1914
				WHERE id_field = {int:current_field}',
1915
				array(
1916
					'field_length' => $field_length,
1917
					'show_reg' => $show_reg,
1918
					'show_display' => $show_display,
1919
					'show_mlist' => $show_mlist,
1920
					'private' => $private,
1921
					'active' => $active,
1922
					'can_search' => $can_search,
1923
					'bbc' => $bbc,
1924
					'current_field' => $context['fid'],
1925
					'field_name' => $_POST['field_name'],
1926
					'field_desc' => $_POST['field_desc'],
1927
					'field_type' => $_POST['field_type'],
1928
					'field_options' => $field_options,
1929
					'show_profile' => $show_profile,
1930
					'default_value' => $default,
1931
					'mask' => $mask,
1932
					'enclose' => $enclose,
1933
					'placement' => $placement,
1934
				)
1935
			);
1936
1937
			// Just clean up any old selects - these are a pain!
1938
			if (($_POST['field_type'] == 'select' || $_POST['field_type'] == 'radio') && !empty($newOptions))
1939
				$smcFunc['db_query']('', '
1940
					DELETE FROM {db_prefix}themes
1941
					WHERE variable = {string:current_column}
1942
						AND value NOT IN ({array_string:new_option_values})
1943
						AND id_member > {int:no_member}',
1944
					array(
1945
						'no_member' => 0,
1946
						'new_option_values' => $newOptions,
1947
						'current_column' => $context['field']['col_name'],
1948
					)
1949
				);
1950
		}
1951
		else
1952
		{
1953
			// Gotta figure it out the order.
1954
			$new_order = $order_count > 1 ? ($order_count + 1) : 1;
1955
1956
			$smcFunc['db_insert']('',
1957
				'{db_prefix}custom_fields',
1958
				array(
1959
					'col_name' => 'string', 'field_name' => 'string', 'field_desc' => 'string',
1960
					'field_type' => 'string', 'field_length' => 'string', 'field_options' => 'string', 'field_order' => 'int',
1961
					'show_reg' => 'int', 'show_display' => 'int', 'show_mlist' => 'int', 'show_profile' => 'string',
1962
					'private' => 'int', 'active' => 'int', 'default_value' => 'string', 'can_search' => 'int',
1963
					'bbc' => 'int', 'mask' => 'string', 'enclose' => 'string', 'placement' => 'int',
1964
				),
1965
				array(
1966
					$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...
1967
					$_POST['field_type'], $field_length, $field_options, $new_order,
1968
					$show_reg, $show_display, $show_mlist, $show_profile,
1969
					$private, $active, $default, $can_search,
1970
					$bbc, $mask, $enclose, $placement,
1971
				),
1972
				array('id_field')
1973
			);
1974
		}
1975
	}
1976
	// Deleting?
1977
	elseif (isset($_POST['delete']) && $context['field']['col_name'])
1978
	{
1979
		checkSession();
1980
		validateToken('admin-ecp');
1981
1982
		// Delete the user data first.
1983
		$smcFunc['db_query']('', '
1984
			DELETE FROM {db_prefix}themes
1985
			WHERE variable = {string:current_column}
1986
				AND id_member > {int:no_member}',
1987
			array(
1988
				'no_member' => 0,
1989
				'current_column' => $context['field']['col_name'],
1990
			)
1991
		);
1992
		// Finally - the field itself is gone!
1993
		$smcFunc['db_query']('', '
1994
			DELETE FROM {db_prefix}custom_fields
1995
			WHERE id_field = {int:current_field}',
1996
			array(
1997
				'current_field' => $context['fid'],
1998
			)
1999
		);
2000
2001
		// Re-arrange the order.
2002
		$smcFunc['db_query']('','
2003
			UPDATE {db_prefix}custom_fields
2004
			SET field_order = field_order - 1
2005
			WHERE field_order > {int:current_order}',
2006
			array(
2007
				'current_order' => $context['field']['order'],
2008
			)
2009
		);
2010
	}
2011
2012
	// Rebuild display cache etc.
2013
	if (isset($_POST['delete']) || isset($_POST['save']))
2014
	{
2015
		checkSession();
2016
2017
		$request = $smcFunc['db_query']('', '
2018
			SELECT col_name, field_name, field_type, field_order, bbc, enclose, placement, show_mlist, field_options
2019
			FROM {db_prefix}custom_fields
2020
			WHERE show_display = {int:is_displayed}
2021
				AND active = {int:active}
2022
				AND private != {int:not_owner_only}
2023
				AND private != {int:not_admin_only}
2024
			ORDER BY field_order',
2025
			array(
2026
				'is_displayed' => 1,
2027
				'active' => 1,
2028
				'not_owner_only' => 2,
2029
				'not_admin_only' => 3,
2030
			)
2031
		);
2032
2033
		$fields = array();
2034
		while ($row = $smcFunc['db_fetch_assoc']($request))
2035
		{
2036
			$fields[] = array(
2037
				'col_name' => strtr($row['col_name'], array('|' => '', ';' => '')),
2038
				'title' => strtr($row['field_name'], array('|' => '', ';' => '')),
2039
				'type' => $row['field_type'],
2040
				'order' => $row['field_order'],
2041
				'bbc' => $row['bbc'] ? '1' : '0',
2042
				'placement' => !empty($row['placement']) ? $row['placement'] : '0',
2043
				'enclose' => !empty($row['enclose']) ? $row['enclose'] : '',
2044
				'mlist' => $row['show_mlist'],
2045
				'options' => (!empty($row['field_options']) ? explode(',', $row['field_options']) : array()),
2046
			);
2047
		}
2048
		$smcFunc['db_free_result']($request);
2049
2050
		updateSettings(array('displayFields' => $smcFunc['json_encode']($fields)));
2051
		$_SESSION['adm-save'] = true;
2052
		redirectexit('action=admin;area=featuresettings;sa=profile');
2053
	}
2054
2055
	createToken('admin-ecp');
2056
}
2057
2058
/**
2059
 * Returns the maximum field_order value for the custom fields
2060
 * @return int The maximum value of field_order from the custom_fields table
2061
 */
2062
function custFieldsMaxOrder()
2063
{
2064
	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...
2065
2066
	// Gotta know the order limit
2067
	$result = $smcFunc['db_query']('', '
2068
			SELECT MAX(field_order)
2069
			FROM {db_prefix}custom_fields',
2070
			array()
2071
		);
2072
2073
	list ($order_count) = $smcFunc['db_fetch_row']($result);
2074
	$smcFunc['db_free_result']($result);
2075
2076
	return (int) $order_count;
2077
}
2078
2079
/**
2080
 * Allow to edit the settings on the pruning screen.
2081
 * @param bool $return_config Whether or not to return the config_vars array (used for admin search)
2082
 * @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...
2083
 */
2084
function ModifyLogSettings($return_config = false)
2085
{
2086
	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...
2087
2088
	// Make sure we understand what's going on.
2089
	loadLanguage('ManageSettings');
2090
2091
	$context['page_title'] = $txt['log_settings'];
2092
2093
	$config_vars = array(
2094
			array('check', 'modlog_enabled', 'help' => 'modlog'),
2095
			array('check', 'adminlog_enabled', 'help' => 'adminlog'),
2096
			array('check', 'userlog_enabled', 'help' => 'userlog'),
2097
			// The error log is a wonderful thing.
2098
			array('title', 'errlog'),
2099
			array('desc', 'error_log_desc'),
2100
			array('check', 'enableErrorLogging'),
2101
			array('check', 'enableErrorQueryLogging'),
2102
			array('check', 'log_ban_hits'),
2103
			// Even do the pruning?
2104
			array('title', 'pruning_title'),
2105
			array('desc', 'pruning_desc'),
2106
			// The array indexes are there so we can remove/change them before saving.
2107
			'pruningOptions' => array('check', 'pruningOptions'),
2108
		'',
2109
			// Various logs that could be pruned.
2110
			array('int', 'pruneErrorLog', 'postinput' => $txt['days_word'], 'subtext' => $txt['zero_to_disable']), // Error log.
2111
			array('int', 'pruneModLog', 'postinput' => $txt['days_word'], 'subtext' => $txt['zero_to_disable']), // Moderation log.
2112
			array('int', 'pruneBanLog', 'postinput' => $txt['days_word'], 'subtext' => $txt['zero_to_disable']), // Ban hit log.
2113
			array('int', 'pruneReportLog', 'postinput' => $txt['days_word'], 'subtext' => $txt['zero_to_disable']), // Report to moderator log.
2114
			array('int', 'pruneScheduledTaskLog', 'postinput' => $txt['days_word'], 'subtext' => $txt['zero_to_disable']), // Log of the scheduled tasks and how long they ran.
2115
			array('int', 'pruneSpiderHitLog', 'postinput' => $txt['days_word'], 'subtext' => $txt['zero_to_disable']), // Log of the scheduled tasks and how long they ran.
2116
			// 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.
2117
			// Mod Developers: Do NOT use the pruningOptions master variable for this as SMF Core may overwrite your setting in the future!
2118
	);
2119
2120
	// 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.
2121
	$prune_toggle = array('pruneErrorLog', 'pruneModLog', 'pruneBanLog', 'pruneReportLog', 'pruneScheduledTaskLog', 'pruneSpiderHitLog');
2122
2123
	call_integration_hook('integrate_prune_settings', array(&$config_vars, &$prune_toggle, false));
2124
2125
	$prune_toggle_dt = array();
2126
	foreach ($prune_toggle as $item)
2127
		$prune_toggle_dt[] = 'setting_' . $item;
2128
2129
	if ($return_config)
2130
		return $config_vars;
2131
2132
	addInlineJavaScript('
2133
	function togglePruned()
2134
	{
2135
		var newval = $("#pruningOptions").prop("checked");
2136
		$("#' . implode(', #', $prune_toggle) . '").closest("dd").toggle(newval);
2137
		$("#' . implode(', #', $prune_toggle_dt) . '").closest("dt").toggle(newval);
2138
	};
2139
	togglePruned();
2140
	$("#pruningOptions").click(function() { togglePruned(); });', true);
2141
2142
	// We'll need this in a bit.
2143
	require_once($sourcedir . '/ManageServer.php');
2144
2145
	// Saving?
2146
	if (isset($_GET['save']))
2147
	{
2148
		checkSession();
2149
2150
		// Because of the excitement attached to combining pruning log items, we need to duplicate everything here.
2151
		$savevar = array(
2152
			array('check', 'modlog_enabled'),
2153
			array('check', 'adminlog_enabled'),
2154
			array('check', 'userlog_enabled'),
2155
			array('check', 'enableErrorLogging'),
2156
			array('check', 'enableErrorQueryLogging'),
2157
			array('check', 'log_ban_hits'),
2158
			array('text', 'pruningOptions')
2159
		);
2160
2161
		call_integration_hook('integrate_prune_settings', array(&$savevar, &$prune_toggle, true));
2162
2163
		if (!empty($_POST['pruningOptions']))
2164
		{
2165
			$vals = array();
2166
			foreach ($config_vars as $index => $dummy)
2167
			{
2168
				if (!is_array($dummy) || $index == 'pruningOptions' || !in_array($dummy[1], $prune_toggle))
2169
					continue;
2170
2171
				$vals[] = empty($_POST[$dummy[1]]) || $_POST[$dummy[1]] < 0 ? 0 : (int) $_POST[$dummy[1]];
2172
			}
2173
			$_POST['pruningOptions'] = implode(',', $vals);
2174
		}
2175
		else
2176
			$_POST['pruningOptions'] = '';
2177
2178
		saveDBSettings($savevar);
2179
		$_SESSION['adm-save'] = true;
2180
		redirectexit('action=admin;area=logs;sa=settings');
2181
	}
2182
2183
	$context['post_url'] = $scripturl . '?action=admin;area=logs;save;sa=settings';
2184
	$context['settings_title'] = $txt['log_settings'];
2185
	$context['sub_template'] = 'show_settings';
2186
2187
	// Get the actual values
2188
	if (!empty($modSettings['pruningOptions']))
2189
		@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...
2190
	else
2191
		$modSettings['pruneErrorLog'] = $modSettings['pruneModLog'] = $modSettings['pruneBanLog'] = $modSettings['pruneReportLog'] = $modSettings['pruneScheduledTaskLog'] = $modSettings['pruneSpiderHitLog'] = 0;
2192
2193
	prepareDBSettingContext($config_vars);
2194
}
2195
2196
/**
2197
 * If you have a general mod setting to add stick it here.
2198
 *
2199
 * @param bool $return_config Whether or not to return the config_vars array (used for admin search)
2200
 * @return void|array Returns nothing or returns the $config_vars array if $return_config is true
2201
 */
2202
function ModifyGeneralModSettings($return_config = false)
2203
{
2204
	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...
2205
2206
	$config_vars = array(
2207
		// Mod authors, add any settings UNDER this line. Include a comma at the end of the line and don't remove this statement!!
2208
	);
2209
2210
	// Make it even easier to add new settings.
2211
	call_integration_hook('integrate_general_mod_settings', array(&$config_vars));
2212
2213
	if ($return_config)
2214
		return $config_vars;
2215
2216
	$context['post_url'] = $scripturl . '?action=admin;area=modsettings;save;sa=general';
2217
	$context['settings_title'] = $txt['mods_cat_modifications_misc'];
2218
2219
	// No removing this line you, dirty unwashed mod authors. :p
2220
	if (empty($config_vars))
2221
	{
2222
		$context['settings_save_dont_show'] = true;
2223
		$context['settings_message'] = '<div class="centertext">' . $txt['modification_no_misc_settings'] . '</div>';
2224
2225
		return prepareDBSettingContext($config_vars);
2226
	}
2227
2228
	// Saving?
2229 View Code Duplication
	if (isset($_GET['save']))
2230
	{
2231
		checkSession();
2232
2233
		$save_vars = $config_vars;
2234
2235
		call_integration_hook('integrate_save_general_mod_settings', array(&$save_vars));
2236
2237
		// This line is to help mod authors do a search/add after if you want to add something here. Keyword: FOOT TAPPING SUCKS!
2238
		saveDBSettings($save_vars);
2239
2240
		// This line is to remind mod authors that it's nice to let the users know when something has been saved.
2241
		$_SESSION['adm-save'] = true;
2242
2243
		// This line is to help mod authors do a search/add after if you want to add something here. Keyword: I LOVE TEA!
2244
		redirectexit('action=admin;area=modsettings;sa=general');
2245
	}
2246
2247
	// 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!
2248
	prepareDBSettingContext($config_vars);
2249
}
2250
2251
/**
2252
 * Handles modifying the alerts settings
2253
 */
2254
function ModifyAlertsSettings()
2255
{
2256
	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...
2257
2258
	// Dummy settings for the template...
2259
	$modSettings['allow_disableAnnounce'] = false;
2260
	$context['user']['is_owner'] = false;
2261
	$context['member'] = array();
2262
	$context['id_member'] = 0;
2263
	$context['menu_item_selected'] = 'alerts';
2264
	$context['token_check'] = 'noti-admin';
2265
2266
	// Specify our action since we'll want to post back here instead of the profile
2267
	$context['action'] = 'action=admin;area=featuresettings;sa=alerts;'. $context['session_var'] .'='. $context['session_id'];
2268
2269
	loadTemplate('Profile');
2270
	loadLanguage('Profile');
2271
2272
	include_once($sourcedir . '/Profile-Modify.php');
2273
	alert_configuration(0);
2274
2275
	$context['page_title'] = $txt['notify_settings'];
2276
2277
	// Override the description
2278
	$context['description'] = $txt['notifications_desc'];
2279
	$context['sub_template'] = 'alert_configuration';
2280
}
2281
2282
/**
2283
 * Config array for changing privacy settings
2284
 * Accessed  from ?action=admin;area=featuresettings;sa=privacy;
2285
 *
2286
 * @param bool $return_config Whether or not to return the config_vars array
2287
 * @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...
2288
 */
2289
function ModifyPrivacySettings($return_config = false)
2290
{
2291
	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...
2292
2293
	$config_vars = array(
2294
		array('check', 'enable_privacy_userexport'),
2295
		array('permissions', 'privacy_userexport_own'),
2296
		array('permissions', 'privacy_userexport_others'),
2297
	);
2298
2299
	call_integration_hook('integrate_privacy_settings', array(&$config_vars));
2300
2301
	if ($return_config)
2302
		return $config_vars;
2303
2304
	// Saving?
2305
	if (isset($_GET['save']))
2306
	{
2307
		checkSession();
2308
2309
		call_integration_hook('integrate_save_privacy_settings');
2310
2311
		saveDBSettings($config_vars);
2312
		$_SESSION['adm-save'] = true;
2313
		redirectexit('action=admin;area=featuresettings;sa=privacy');
2314
	}
2315
2316
	$context['post_url'] = $scripturl . '?action=admin;area=featuresettings;save;sa=privacy';
2317
	$context['settings_title'] = $txt['privacy'];
2318
2319
	prepareDBSettingContext($config_vars);
2320
}
2321
2322
?>