Db::applyMasks()   A
last analyzed

Complexity

Conditions 5
Paths 5

Size

Total Lines 28
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 5

Importance

Changes 0
Metric Value
cc 5
eloc 13
nc 5
nop 3
dl 0
loc 28
ccs 10
cts 10
cp 1
crap 5
rs 9.5222
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * This class handles display, edit, save, of forum settings that are saved to the database
5
 *
6
 * @package   ElkArte Forum
7
 * @copyright ElkArte Forum contributors
8
 * @license   BSD http://opensource.org/licenses/BSD-3-Clause (see accompanying LICENSE.txt file)
9
 *
10
 * This file contains code covered by:
11
 * copyright: 2011 Simple Machines (http://www.simplemachines.org)
12
 *
13
 * @version 2.0 Beta 1
14
 *
15
 */
16
17
namespace ElkArte\SettingsForm\SettingsFormAdapter;
18
19
use BBC\ParserWrapper;
20
use ElkArte\Helper\DataValidator;
21
use ElkArte\Helper\Util;
22
use ElkArte\Languages\Txt;
23
24
/**
25
 * Class Db
26
 *
27
 * @package ElkArte\SettingsForm\SettingsFormAdapter
28
 */
29
class Db extends Adapter
30
{
31
	/**
32
	 * Helper method, it sets up the context for database settings.
33 8
	 */
34
	public function prepare()
35 8
	{
36
		global $modSettings;
37 8
38
		Txt::load('Help');
39 8
40
		foreach ($this->configVars as $configVar)
41
		{
42 8
			// HR?
43
			if (!is_array($configVar))
44 4
			{
45
				$this->context[] = $configVar;
46
			}
47
			else
48
			{
49 8
				// If it has no name, it doesn't have any purpose!
50
				if (empty($configVar[1]))
51
				{
52
					continue;
53
				}
54
55 8
				// Special case for inline permissions
56
				if ($configVar[0] === 'permissions' && !allowedTo('manage_permissions'))
57
				{
58
					continue;
59
				}
60 8
61 8
				$this->context[$configVar[1]] = [
62 8
					'type' => $configVar[0],
63
					'size' => !empty($configVar[2]) && !is_array($configVar[2]) ? $configVar[2] : (in_array($configVar[0], ['int', 'float']) ? 6 : 0),
64 8
					'data' => [],
65 8
					'name' => str_replace(' ', '_', $configVar[1]),
66
					'value' => isset($modSettings[$configVar[1]]) ? ($configVar[0] === 'select' ? $modSettings[$configVar[1]] : htmlspecialchars($modSettings[$configVar[1]], ENT_COMPAT, 'UTF-8')) : (in_array($configVar[0], ['int', 'float']) ? 0 : ''),
67 8
					'disabled' => false,
68 8
					'invalid' => !empty($configVar['invalid']),
69
					'javascript' => '',
70 8
				];
71
				foreach (['helptext', 'message', 'preinput', 'postinput', 'icon'] as $k)
72 8
				{
73
					if (isset($configVar[$k]))
74 6
					{
75
						$this->context[$configVar[1]][$k] = $configVar[$k];
76
					}
77
				}
78 8
79
				$this->context[$configVar[1]]['label'] = $this->prepareLabel($configVar);
80
81 8
				// If this is a select box handle any data.
82
				$this->handleSelect($configVar);
83
84 8
				// Revert masks if necessary
85
				$this->context[$configVar[1]]['value'] = $this->revertMasks($configVar, $this->context[$configVar[1]]['value']);
86
87 8
				// Finally, allow overrides - and some final cleanups.
88
				$this->allowOverrides($configVar);
89
			}
90
		}
91
92 8
		// If we have inline permissions, we need to prep them.
93
		$this->init_inline_permissions();
94
95 8
		// What about any BBC selection boxes?
96
		$this->initBbcChoices();
97 8
98 8
		$this->prepareContext();
99
	}
100
101
	/**
102
	 * Create the config var label value
103
	 *
104
	 * @param array $configVar
105 8
	 */
106
	private function prepareLabel($configVar)
107 8
	{
108
		global $txt;
109
110 8
		// See if there are any labels that might fit?
111
		if (isset($configVar['text_label']))
112 4
		{
113
			return $configVar['text_label'];
114 4
		}
115
116
		return $txt[$configVar[1]] ?? $txt['setting_' . $configVar[1]] ?? $txt['groups_' . $configVar[1]] ?? $configVar[1];
117
	}
118 4
119
	/**
120
	 * @param array $configVar
121
	 */
122 4
	private function handleSelect(array $configVar): void
123
	{
124
		if (!empty($configVar[2]) && is_array($configVar[2]))
125
		{
126
			// If we allow multiple selections, we need to adjust a few things.
127
			if ($configVar[0] === 'select' && !empty($configVar['multiple']))
128 4
			{
129
				$this->context[$configVar[1]]['name'] .= '[]';
130 8
				$this->context[$configVar[1]]['value'] = empty($this->context[$configVar[1]]['value']) ? [] : Util::unserialize($this->context[$configVar[1]]['value']);
131
			}
132
133
			// If it's associative
134
			if (isset($configVar[2][0]) && is_array($configVar[2][0]))
135 8
			{
136
				$this->context[$configVar[1]]['data'] = $configVar[2];
137 8
			}
138
			else
139
			{
140 4
				foreach ($configVar[2] as $key => $item)
141
				{
142 4
					$this->context[$configVar[1]]['data'][] = [$key, $item];
143 4
				}
144
			}
145
		}
146
	}
147 4
148
	/**
149
	 * @param array $configVar
150
	 * @param string $str
151
	 *
152
	 * @return string|array
153 4
	 */
154
	private function revertMasks(array $configVar, $str): string|array
155 4
	{
156
		$known_rules = [
157
			'nohtml' => 'htmlspecialchars_decode[' . ENT_NOQUOTES . ']',
158
		];
159 8
160
		return $this->applyMasks($configVar, $str, $known_rules);
161
	}
162
163
	/**
164
	 * @param array $configVar
165
	 * @param string $str
166
	 * @param array $known_rules
167 8
	 *
168
	 * @return string|array
169
	 */
170 8
	private function applyMasks(array $configVar, $str, $known_rules): string|array
171
	{
172
		if (isset($configVar['mask']))
173 8
		{
174
			$rules = [];
175
			if (!is_array($configVar['mask']))
176
			{
177
				$configVar['mask'] = [$configVar['mask']];
178
			}
179
180
			foreach ($configVar['mask'] as $mask)
181
			{
182
				$rules[$configVar[1]][] = $known_rules[$mask] ?? $mask;
183 10
			}
184
185 10
			if (!empty($rules))
186
			{
187 6
				$rules[$configVar[1]] = implode('|', $rules[$configVar[1]]);
188 6
189
				$validator = new DataValidator();
190 4
				$validator->sanitation_rules($rules);
191
				$validator->validate([$configVar[1] => $str]);
192 6
193
				return $validator->{$configVar[1]};
194 6
			}
195
		}
196 6
197
		return $str;
198 6
	}
199
200 6
	/**
201 6
	 * @param array $configVar
202 6
	 */
203
	private function allowOverrides(array $configVar): void
204 6
	{
205
		global $txt, $helptxt;
206
207
		foreach ($configVar as $k => $v)
208 8
		{
209
			if (!is_numeric($k))
210
			{
211
				// Any on events like onchange, onclick, onfocus ....
212
				if (str_starts_with($k, 'on'))
213
				{
214 8
					$this->context[$configVar[1]]['javascript'] .= ' ' . $k . '="' . $v . '"';
215
				}
216 8
				else
217
				{
218 8
					$this->context[$configVar[1]][$k] = $v;
219
				}
220 8
			}
221
		}
222 8
223
		if (isset($configVar['message'], $txt[$configVar['message']]))
224
		{
225
			$this->context[$configVar[1]]['message'] = $txt[$configVar['message']];
226
		}
227
228 8
		// Is help available for this field based on its name?
229
		if (!isset($helptxt[$configVar[1]]))
230
		{
231
			return;
232 8
		}
233
234
		if (isset($configVar['helptext']))
235
		{
236 8
			return;
237
		}
238 4
239
		$this->context[$configVar[1]]['helptext'] = $configVar[1];
240 8
	}
241
242
	/**
243
	 * Initialize inline permissions settings.
244
	 */
245 8
	private function init_inline_permissions(): void
246
	{
247 8
		global $context;
248
249 8
		$inlinePermissions = array_filter($this->configVars,
250
			static fn($configVar) => isset($configVar[0]) && $configVar[0] === 'permissions'
251 8
		);
252 8
253
		if (empty($inlinePermissions))
254
		{
255 8
			return;
256
		}
257 4
258
		$permissionsForm = new InlinePermissions();
259
		$permissionsForm->setExcludedGroups($context['permissions_excluded'] ?? []);
260 4
		$permissionsForm->setPermissions($inlinePermissions);
261 4
		$permissionsForm->prepare();
262 4
	}
263 4
264 4
	/**
265
	 * Initialize a list of available BB codes.
266
	 */
267
	private function initBbcChoices(): void
268
	{
269 8
		global $helptxt, $modSettings;
270
271 8
		$bbcChoice = array_filter($this->configVars,
272
			static fn($configVar) => isset($configVar[0]) && $configVar[0] === 'bbc'
273 8
		);
274
		if (empty($bbcChoice))
275 8
		{
276 8
			return;
277
		}
278 8
279
		// What are the options, eh?
280 4
		$codes = ParserWrapper::instance()->getCodes();
281
		$bbcTags = $codes->getTags();
282
		$bbcTags = array_unique($bbcTags);
283
284 4
		$bbc_sections = [];
285 4
		foreach ($bbcTags as $tag)
286 4
		{
287 4
			$bbc_sections[] = [
288 4
				'tag' => $tag,
289
				// @todo  'tag_' . ?
290 4
				'show_help' => isset($helptxt[$tag]),
291 4
			];
292
		}
293 4
294
		// Now put whatever BBC options we may have into context too!
295
		foreach ($bbcChoice as $configVar)
296
		{
297
			$disabled = empty($modSettings['bbc_disabled_' . $configVar[1]]);
298 4
			$this->context[$configVar[1]] = array_merge_recursive([
299
				'disabled_tags' => $disabled ? [] : $modSettings['bbc_disabled_' . $configVar[1]],
300 4
				'all_selected' => $disabled,
301 4
				'data' => $bbc_sections,
302 4
			], $this->context[$configVar[1]]);
303 4
		}
304 4
	}
305 4
306
	/**
307 4
	 * Helper method for saving database settings.
308
	 */
309
	public function save()
310
	{
311
		[$setArray] = $this->sanitizeVars();
312 4
		$inlinePermissions = [];
313
314 4
		foreach ($this->configVars as $var)
315 4
		{
316
			if (!isset($var[1]) || (!isset($this->configValues[$var[1]]) && $var[0] !== 'permissions'))
317 4
			{
318
				continue;
319 4
			}
320
321 4
			// Permissions?
322
			if ($var[0] === 'permissions')
323
			{
324 4
				$inlinePermissions[] = $var;
325
			}
326 3
		}
327
328
		if (!empty($setArray))
329
		{
330 4
			// Just in case we cached this.
331
			$setArray['settings_updated'] = time();
332
333 4
			updateSettings($setArray);
334
		}
335 4
336
		// If we have inline permissions, we need to save them.
337
		if (!empty($inlinePermissions) && allowedTo('manage_permissions'))
338
		{
339 4
			$permissionsForm = new InlinePermissions();
340
			$permissionsForm->setPermissions($inlinePermissions);
341 2
			$permissionsForm->save();
342 2
		}
343 2
	}
344
345 4
	/**
346
	 * Cast all the config vars as defined
347
	 *
348
	 * @return array
349
	 */
350
	protected function sanitizeVars(): array
351
	{
352 6
		$setTypes = [];
353
		$setArray = [];
354 6
		foreach ($this->configVars as $var)
355 6
		{
356 6
			if (!isset($var[1]) || (!isset($this->configValues[$var[1]]) && $var[0] !== 'check'))
357
			{
358 6
				continue;
359
			}
360 4
361
			$setTypes[$var[1]] = $var[1];
362 6
			switch ($var[0])
363 6
			{
364
				case 'check':
365 6
					$setTypes[$var[1]] = 'int';
366 4
					$setArray[$var[1]] = (int) !empty($this->configValues[$var[1]]);
367 4
					break;
368 4
				case 'select':
369 4
					// Select boxes!
370
					$setTypes[$var[1]] = 'string';
371 2
372
					if (empty($var['multiple']) && array_key_exists($this->configValues[$var[1]], $var[2]))
373 2
					{
374
						$setArray[$var[1]] = $this->configValues[$var[1]];
375 2
					}
376
					elseif (!empty($var['multiple']))
377 2
					{
378
						// For security purposes we validate this line by line.
379
						$setArray[$var[1]] = serialize(array_intersect($this->configValues[$var[1]], array_keys($var[2])));
380 2
					}
381
382 2
					break;
383 4
				case 'int':
384
					// Integers!
385 2
					$setArray[$var[1]] = (int) $this->configValues[$var[1]];
386 2
					break;
387 4
				case 'float':
388
					// Floating point!
389 2
					$setArray[$var[1]] = (float) $this->configValues[$var[1]];
390 2
					break;
391 4
				case 'text':
392 2
				case 'color':
393 2
				case 'url':
394
				case 'email':
395 4
				case 'search':
396 4
				case 'date':
397 4
				case 'large_text':
398 2
					// Text!
399
					$setTypes[$var[1]] = 'string';
400 2
					$setArray[$var[1]] = $this->setMasks($var, $this->configValues[$var[1]]);
401 2
					break;
402
				case 'password':
403 2
					// Passwords!
404
					$setTypes[$var[1]] = 'string';
405 2
					if (isset($this->configValues[$var[1]][1]) && $this->configValues[$var[1]][0] === $this->configValues[$var[1]][1])
406 2
					{
407
						$setArray[$var[1]] = $this->configValues[$var[1]][0];
408 2
					}
409 2
410 4
					break;
411
				case 'bbc':
412
					// BBC.
413
					$setTypes[$var[1]] = 'string';
414 6
					$setArray[$var[1]] = $this->setBbcChoices($var);
415
					break;
416
			}
417
		}
418
419
		return [$setArray, $setTypes];
420
	}
421
422
	/**
423 4
	 * @param array $configVar
424
	 * @param string $str
425
	 *
426 4
	 * @return string
427 4
	 */
428 4
	private function setMasks(array $configVar, $str): string
429
	{
430
		$known_rules = [
431 4
			'nohtml' => '\\ElkArte\\Helper\\Util::htmlspecialchars[' . ENT_QUOTES . ']',
432
			'email' => 'valid_email',
433
			'url' => 'valid_url',
434
		];
435
436
		return $this->applyMasks($configVar, $str, $known_rules);
437
	}
438
439 2
	/**
440
	 * @param mixed $var
441 2
	 *
442 2
	 * @return string
443
	 */
444 2
	private function setBbcChoices($var): string
445
	{
446 2
		$codes = ParserWrapper::instance()->getCodes();
447
		$bbcTags = $codes->getTags();
448
449
		if (!isset($this->configValues[$var[1] . '_enabledTags']))
450
		{
451
			$this->configValues[$var[1] . '_enabledTags'] = [];
452
		}
453 2
		elseif (!is_array($this->configValues[$var[1] . '_enabledTags']))
454
		{
455
			$this->configValues[$var[1] . '_enabledTags'] = [$this->configValues[$var[1] . '_enabledTags']];
456
		}
457
458
		return implode(',', array_diff($bbcTags, $this->configValues[$var[1] . '_enabledTags']));
459
	}
460
}
461