remove_theme()   A
last analyzed

Complexity

Conditions 4
Paths 3

Size

Total Lines 60
Code Lines 24

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 24
c 0
b 0
f 0
nop 1
dl 0
loc 60
rs 9.536
nc 3

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/**
4
 * Helper file for handling themes.
5
 *
6
 * Simple Machines Forum (SMF)
7
 *
8
 * @package SMF
9
 * @author Simple Machines https://www.simplemachines.org
10
 * @copyright 2022 Simple Machines and individual contributors
11
 * @license https://www.simplemachines.org/about/smf/license.php BSD
12
 *
13
 * @version 2.1.0
14
 */
15
16
if (!defined('SMF'))
17
	die('No direct access...');
18
19
/**
20
 * Gets a single theme's info.
21
 *
22
 * @param int $id The theme ID to get the info from.
23
 * @param string[] $variables
24
 * @return array The theme info as an array.
25
 */
26
function get_single_theme($id, array $variables = array())
27
{
28
	global $smcFunc, $modSettings;
29
30
	// No data, no fun!
31
	if (empty($id))
32
		return false;
33
34
	// Make sure $id is an int.
35
	$id = (int) $id;
36
37
	// Make changes if you really want it.
38
	call_integration_hook('integrate_get_single_theme', array(&$variables, $id));
39
40
	$single = array(
41
		'id' => $id,
42
	);
43
44
	// Make our known/enable themes a little easier to work with.
45
	$knownThemes = !empty($modSettings['knownThemes']) ? explode(',', $modSettings['knownThemes']) : array();
46
	$enableThemes = !empty($modSettings['enableThemes']) ? explode(',', $modSettings['enableThemes']) : array();
47
48
	$request = $smcFunc['db_query']('', '
49
		SELECT id_theme, variable, value
50
		FROM {db_prefix}themes
51
		WHERE id_theme = ({int:id_theme})
52
			AND id_member = {int:no_member}' . (!empty($variables) ? '
53
			AND variable IN ({array_string:variables})' : ''),
54
		array(
55
			'variables' => $variables,
56
			'id_theme' => $id,
57
			'no_member' => 0,
58
		)
59
	);
60
61
	while ($row = $smcFunc['db_fetch_assoc']($request))
62
	{
63
		$single[$row['variable']] = $row['value'];
64
65
		// Fix the path and tell if its a valid one.
66
		if ($row['variable'] == 'theme_dir')
67
		{
68
			$single['theme_dir'] = realpath($row['value']);
69
			$single['valid_path'] = file_exists($row['value']) && is_dir($row['value']);
70
		}
71
	}
72
73
	// Is this theme installed and enabled?
74
	$single['known'] = in_array($single['id'], $knownThemes);
75
	$single['enable'] = in_array($single['id'], $enableThemes);
76
77
	// It should at least return if the theme is a known one or if its enable.
78
	return $single;
79
}
80
81
/**
82
 * Loads and returns all installed themes.
83
 *
84
 * Stores all themes on $context['themes'] for easier use.
85
 *
86
 * $modSettings['knownThemes'] stores themes that the user is able to select.
87
 *
88
 * @param bool $enable_only Whether to fetch only enabled themes. Default is false.
89
 */
90
function get_all_themes($enable_only = false)
91
{
92
	global $modSettings, $context, $smcFunc;
93
94
	// Make our known/enable themes a little easier to work with.
95
	$knownThemes = !empty($modSettings['knownThemes']) ? explode(',', $modSettings['knownThemes']) : array();
96
	$enableThemes = !empty($modSettings['enableThemes']) ? explode(',', $modSettings['enableThemes']) : array();
97
98
	// List of all possible themes values.
99
	$themeValues = array(
100
		'theme_dir',
101
		'images_url',
102
		'theme_url',
103
		'name',
104
		'theme_layers',
105
		'theme_templates',
106
		'version',
107
		'install_for',
108
		'based_on',
109
	);
110
111
	// Make changes if you really want it.
112
	call_integration_hook('integrate_get_all_themes', array(&$themeValues, $enable_only));
113
114
	// So, what is it going to be?
115
	$query_where = $enable_only ? $enableThemes : $knownThemes;
116
117
	// Perform the query as requested.
118
	$request = $smcFunc['db_query']('', '
119
		SELECT id_theme, variable, value
120
		FROM {db_prefix}themes
121
		WHERE variable IN ({array_string:theme_values})
122
			AND id_theme IN ({array_string:query_where})
123
			AND id_member = {int:no_member}',
124
		array(
125
			'query_where' => $query_where,
126
			'theme_values' => $themeValues,
127
			'no_member' => 0,
128
		)
129
	);
130
131
	$context['themes'] = array();
132
133
	while ($row = $smcFunc['db_fetch_assoc']($request))
134
	{
135
		if (!isset($context['themes'][$row['id_theme']]))
136
			$context['themes'][$row['id_theme']] = array(
137
				'id' => (int) $row['id_theme'],
138
				'known' => in_array($row['id_theme'], $knownThemes),
139
				'enable' => in_array($row['id_theme'], $enableThemes)
140
			);
141
142
		// Fix the path and tell if its a valid one.
143
		if ($row['variable'] == 'theme_dir')
144
		{
145
			$row['value'] = realpath($row['value']);
146
			$context['themes'][$row['id_theme']]['valid_path'] = file_exists($row['value']) && is_dir($row['value']);
147
		}
148
		$context['themes'][$row['id_theme']][$row['variable']] = $row['value'];
149
	}
150
151
	$smcFunc['db_free_result']($request);
152
}
153
154
/**
155
 * Loads and returns all installed themes.
156
 *
157
 * Stores all themes on $context['themes'] for easier use.
158
 *
159
 * $modSettings['knownThemes'] stores themes that the user is able to select.
160
 */
161
function get_installed_themes()
162
{
163
	global $modSettings, $context, $smcFunc;
164
165
	// Make our known/enable themes a little easier to work with.
166
	$knownThemes = !empty($modSettings['knownThemes']) ? explode(',', $modSettings['knownThemes']) : array();
167
	$enableThemes = !empty($modSettings['enableThemes']) ? explode(',', $modSettings['enableThemes']) : array();
168
169
	// List of all possible themes values.
170
	$themeValues = array(
171
		'theme_dir',
172
		'images_url',
173
		'theme_url',
174
		'name',
175
		'theme_layers',
176
		'theme_templates',
177
		'version',
178
		'install_for',
179
		'based_on',
180
	);
181
182
	// Make changes if you really want it.
183
	call_integration_hook('integrate_get_installed_themes', array(&$themeValues));
184
185
	// Perform the query as requested.
186
	$request = $smcFunc['db_query']('', '
187
		SELECT id_theme, variable, value
188
		FROM {db_prefix}themes
189
		WHERE variable IN ({array_string:theme_values})
190
			AND id_member = {int:no_member}',
191
		array(
192
			'theme_values' => $themeValues,
193
			'no_member' => 0,
194
		)
195
	);
196
197
	$context['themes'] = array();
198
199
	while ($row = $smcFunc['db_fetch_assoc']($request))
200
	{
201
		if (!isset($context['themes'][$row['id_theme']]))
202
			$context['themes'][$row['id_theme']] = array(
203
				'id' => (int) $row['id_theme'],
204
				'known' => in_array($row['id_theme'], $knownThemes),
205
				'enable' => in_array($row['id_theme'], $enableThemes)
206
			);
207
208
		// Fix the path and tell if its a valid one.
209
		if ($row['variable'] == 'theme_dir')
210
		{
211
			$row['value'] = realpath($row['value']);
212
			$context['themes'][$row['id_theme']]['valid_path'] = file_exists($row['value']) && is_dir($row['value']);
213
		}
214
		$context['themes'][$row['id_theme']][$row['variable']] = $row['value'];
215
	}
216
217
	$smcFunc['db_free_result']($request);
218
}
219
220
/**
221
 * Reads an .xml file and returns the data as an array
222
 *
223
 * Removes the entire theme if the .xml file couldn't be found or read.
224
 *
225
 * @param string $path The absolute path to the xml file.
226
 * @return array An array with all the info extracted from the xml file.
227
 */
228
function get_theme_info($path)
229
{
230
	global $smcFunc, $sourcedir, $txt, $scripturl, $context;
231
	global $explicit_images;
232
233
	if (empty($path))
234
		return false;
235
236
	$xml_data = array();
237
	$explicit_images = false;
238
239
	// Perhaps they are trying to install a mod, lets tell them nicely this is the wrong function.
240
	if (file_exists($path . '/package-info.xml'))
241
	{
242
		loadLanguage('Errors');
243
244
		// We need to delete the dir otherwise the next time you try to install a theme you will get the same error.
245
		remove_dir($path);
246
247
		$txt['package_get_error_is_mod'] = str_replace('{MANAGEMODURL}', $scripturl . '?action=admin;area=packages;' . $context['session_var'] . '=' . $context['session_id'], $txt['package_get_error_is_mod']);
248
		fatal_lang_error('package_theme_upload_error_broken', false, $txt['package_get_error_is_mod']);
249
	}
250
251
	// Parse theme-info.xml into an xmlArray.
252
	require_once($sourcedir . '/Class-Package.php');
253
	$theme_info_xml = new xmlArray(file_get_contents($path . '/theme_info.xml'));
254
255
	// Error message, there isn't any valid info.
256
	if (!$theme_info_xml->exists('theme-info[0]'))
257
	{
258
		remove_dir($path);
259
		fatal_lang_error('package_get_error_packageinfo_corrupt', false);
260
	}
261
262
	// Check for compatibility with 2.1 or greater.
263
	if (!$theme_info_xml->exists('theme-info/install'))
264
	{
265
		remove_dir($path);
266
		fatal_lang_error('package_get_error_theme_not_compatible', false, SMF_FULL_VERSION);
267
	}
268
269
	// So, we have an install tag which is cool and stuff but we also need to check it and match your current SMF version...
270
	$the_version = SMF_VERSION;
271
	$install_versions = $theme_info_xml->fetch('theme-info/install/@for');
272
273
	// The theme isn't compatible with the current SMF version.
274
	require_once($sourcedir . '/Subs-Package.php');
275
	if (!$install_versions || !matchPackageVersion($the_version, $install_versions))
276
	{
277
		remove_dir($path);
278
		fatal_lang_error('package_get_error_theme_not_compatible', false, SMF_FULL_VERSION);
279
	}
280
281
	$theme_info_xml = $theme_info_xml->to_array('theme-info[0]');
282
283
	$xml_elements = array(
284
		'theme_layers' => 'layers',
285
		'theme_templates' => 'templates',
286
		'based_on' => 'based-on',
287
		'version' => 'version',
288
	);
289
290
	// Assign the values to be stored.
291
	foreach ($xml_elements as $var => $name)
292
		if (!empty($theme_info_xml[$name]))
293
			$xml_data[$var] = $theme_info_xml[$name];
294
295
	// Add the supported versions.
296
	$xml_data['install_for'] = $install_versions;
297
298
	// Overwrite the default images folder.
299
	if (!empty($theme_info_xml['images']))
300
	{
301
		$xml_data['images_url'] = $path . '/' . $theme_info_xml['images'];
302
		$explicit_images = true;
303
	}
304
305
	if (!empty($theme_info_xml['extra']))
306
		$xml_data += $smcFunc['json_decode']($theme_info_xml['extra'], true);
307
308
	return $xml_data;
309
}
310
311
/**
312
 * Inserts a theme's data to the DataBase.
313
 *
314
 * Ends execution with fatal_lang_error() if an error appears.
315
 *
316
 * @param array $to_install An array containing all values to be stored into the DB.
317
 * @return int The newly created theme ID.
318
 */
319
function theme_install($to_install = array())
320
{
321
	global $smcFunc, $context, $modSettings;
322
	global $settings, $explicit_images;
323
324
	// External use? no problem!
325
	if (!empty($to_install))
326
		$context['to_install'] = $to_install;
327
328
	// One last check.
329
	if (empty($context['to_install']['theme_dir']) || basename($context['to_install']['theme_dir']) == 'Themes')
330
		fatal_lang_error('theme_install_invalid_dir', false);
331
332
	// OK, is this a newer version of an already installed theme?
333
	if (!empty($context['to_install']['version']))
334
	{
335
		$request = $smcFunc['db_query']('', '
336
			SELECT id_theme
337
			FROM {db_prefix}themes
338
			WHERE id_member = {int:no_member}
339
				AND variable = {literal:name}
340
				AND value LIKE {string:name_value}
341
			LIMIT 1',
342
			array(
343
				'no_member' => 0,
344
				'name_value' => '%' . $context['to_install']['name'] . '%',
345
			)
346
		);
347
348
		list ($id_to_update) = $smcFunc['db_fetch_row']($request);
349
		$smcFunc['db_free_result']($request);
350
		$to_update = get_single_theme($id_to_update, array('version'));
351
352
		// Got something, lets figure it out what to do next.
353
		if (!empty($id_to_update) && !empty($to_update['version']))
354
			switch (compareVersions($context['to_install']['version'], $to_update['version']))
355
			{
356
				case 1: // Got a newer version, update the old entry.
357
					$smcFunc['db_query']('', '
358
						UPDATE {db_prefix}themes
359
						SET value = {string:new_value}
360
						WHERE variable = {literal:version}
361
							AND id_theme = {int:id_theme}',
362
						array(
363
							'new_value' => $context['to_install']['version'],
364
							'id_theme' => $id_to_update,
365
						)
366
					);
367
368
					// Done with the update, tell the user about it.
369
					$context['to_install']['updated'] = true;
370
371
					return $id_to_update;
372
373
				case 0: // This is exactly the same theme.
374
				case -1: // The one being installed is older than the one already installed.
375
				default: // Any other possible result.
376
					fatal_lang_error('package_get_error_theme_no_new_version', false, array($context['to_install']['version'], $to_update['version']));
377
			}
378
	}
379
380
	if (!empty($context['to_install']['based_on']))
381
	{
382
		// No need for elaborated stuff when the theme is based on the default one.
383
		if ($context['to_install']['based_on'] == 'default')
384
		{
385
			$context['to_install']['theme_url'] = $settings['default_theme_url'];
386
			$context['to_install']['images_url'] = $settings['default_images_url'];
387
		}
388
389
		// Custom theme based on another custom theme, lets get some info.
390
		elseif ($context['to_install']['based_on'] != '')
391
		{
392
			$context['to_install']['based_on'] = preg_replace('~[^A-Za-z0-9\-_ ]~', '', $context['to_install']['based_on']);
393
394
			// Get the theme info first.
395
			$request = $smcFunc['db_query']('', '
396
				SELECT id_theme
397
				FROM {db_prefix}themes
398
				WHERE id_member = {int:no_member}
399
					AND (value LIKE {string:based_on} OR value LIKE {string:based_on_path})
400
				LIMIT 1',
401
				array(
402
					'no_member' => 0,
403
					'based_on' => '%/' . $context['to_install']['based_on'],
404
					'based_on_path' => '%' . "\\" . $context['to_install']['based_on'],
405
				)
406
			);
407
408
			list ($id_based_on) = $smcFunc['db_fetch_row']($request);
409
			$smcFunc['db_free_result']($request);
410
			$temp = get_single_theme($id_based_on, array('theme_dir', 'images_url', 'theme_url'));
411
412
			// Found the based on theme info, add it to the current one being installed.
413
			if (!empty($temp))
414
			{
415
				$context['to_install']['base_theme_url'] = $temp['theme_url'];
416
				$context['to_install']['base_theme_dir'] = $temp['theme_dir'];
417
418
				if (empty($explicit_images) && !empty($context['to_install']['base_theme_url']))
419
					$context['to_install']['theme_url'] = $context['to_install']['base_theme_url'];
420
			}
421
422
			// Nope, sorry, couldn't find any theme already installed.
423
			else
424
				fatal_lang_error('package_get_error_theme_no_based_on_found', false, $context['to_install']['based_on']);
425
		}
426
427
		unset($context['to_install']['based_on']);
428
	}
429
430
	// Find the newest id_theme.
431
	$result = $smcFunc['db_query']('', '
432
		SELECT MAX(id_theme)
433
		FROM {db_prefix}themes',
434
		array(
435
		)
436
	);
437
	list ($id_theme) = $smcFunc['db_fetch_row']($result);
438
	$smcFunc['db_free_result']($result);
439
440
	// This will be theme number...
441
	$id_theme++;
442
443
	// Last minute changes? although, the actual array is a context value you might want to use the new ID.
444
	call_integration_hook('integrate_theme_install', array(&$context['to_install'], $id_theme));
445
446
	$inserts = array();
447
	foreach ($context['to_install'] as $var => $val)
448
		$inserts[] = array($id_theme, $var, $val);
449
450
	if (!empty($inserts))
451
		$smcFunc['db_insert']('insert',
452
			'{db_prefix}themes',
453
			array('id_theme' => 'int', 'variable' => 'string-255', 'value' => 'string-65534'),
454
			$inserts,
455
			array('id_theme', 'variable')
456
		);
457
458
	// Update the known and enable Theme's settings.
459
	$known = strtr($modSettings['knownThemes'] . ',' . $id_theme, array(',,' => ','));
460
	$enable = strtr($modSettings['enableThemes'] . ',' . $id_theme, array(',,' => ','));
461
	updateSettings(array('knownThemes' => $known, 'enableThemes' => $enable));
462
463
	return $id_theme;
464
}
465
466
/**
467
 * Removes a directory from the themes dir.
468
 *
469
 * This is a recursive function, it will call itself if there are subdirs inside the main directory.
470
 *
471
 * @param string $path The absolute path to the directory to be removed
472
 * @return bool true when success, false on error.
473
 */
474
function remove_dir($path)
475
{
476
	if (empty($path))
477
		return false;
478
479
	if (is_dir($path))
480
	{
481
		$objects = scandir($path);
482
483
		foreach ($objects as $object)
484
			if ($object != '.' && $object != '..')
485
			{
486
				if (filetype($path . '/' . $object) == 'dir')
487
					remove_dir($path . '/' . $object);
488
489
				else
490
					unlink($path . '/' . $object);
491
			}
492
	}
493
494
	reset($objects);
495
	rmdir($path);
496
}
497
498
/**
499
 * Removes a theme from the DB, includes all possible places where the theme might be used.
500
 *
501
 * @param int $themeID The theme ID
502
 * @return bool true when success, false on error.
503
 */
504
function remove_theme($themeID)
505
{
506
	global $smcFunc, $modSettings;
507
508
	// Can't delete the default theme, sorry!
509
	if (empty($themeID) || $themeID == 1)
510
		return false;
511
512
	$known = explode(',', $modSettings['knownThemes']);
513
	$enable = explode(',', $modSettings['enableThemes']);
514
515
	// Remove it from the themes table.
516
	$smcFunc['db_query']('', '
517
		DELETE FROM {db_prefix}themes
518
		WHERE id_theme = {int:current_theme}',
519
		array(
520
			'current_theme' => $themeID,
521
		)
522
	);
523
524
	// Update users preferences.
525
	$smcFunc['db_query']('', '
526
		UPDATE {db_prefix}members
527
		SET id_theme = {int:default_theme}
528
		WHERE id_theme = {int:current_theme}',
529
		array(
530
			'default_theme' => 0,
531
			'current_theme' => $themeID,
532
		)
533
	);
534
535
	// Some boards may have it as preferred theme.
536
	$smcFunc['db_query']('', '
537
		UPDATE {db_prefix}boards
538
		SET id_theme = {int:default_theme}
539
		WHERE id_theme = {int:current_theme}',
540
		array(
541
			'default_theme' => 0,
542
			'current_theme' => $themeID,
543
		)
544
	);
545
546
	// Remove it from the list of known themes.
547
	$known = array_diff($known, array($themeID));
548
549
	// And the enable list too.
550
	$enable = array_diff($enable, array($themeID));
551
552
	// Back to good old comma separated string.
553
	$known = strtr(implode(',', $known), array(',,' => ','));
554
	$enable = strtr(implode(',', $enable), array(',,' => ','));
555
556
	// Update the enableThemes list.
557
	updateSettings(array('enableThemes' => $enable, 'knownThemes' => $known));
558
559
	// Fix it if the theme was the overall default theme.
560
	if ($modSettings['theme_guests'] == $themeID)
561
		updateSettings(array('theme_guests' => '1'));
562
563
	return true;
564
}
565
566
/**
567
 * Generates a file listing for a given directory
568
 *
569
 * @param string $path The full path to the directory
570
 * @param string $relative The relative path (relative to the Themes directory)
571
 * @return array An array of information about the files and directories found
572
 */
573
function get_file_listing($path, $relative)
574
{
575
	global $scripturl, $txt, $context;
576
577
	// Is it even a directory?
578
	if (!is_dir($path))
579
		fatal_lang_error('error_invalid_dir', 'critical');
580
581
	$dir = dir($path);
582
	$entries = array();
583
	while ($entry = $dir->read())
584
		$entries[] = $entry;
585
	$dir->close();
586
587
	natcasesort($entries);
588
589
	$listing1 = array();
590
	$listing2 = array();
591
592
	foreach ($entries as $entry)
593
	{
594
		// Skip all dot files, including .htaccess.
595
		if (substr($entry, 0, 1) == '.' || $entry == 'CVS')
596
			continue;
597
598
		if (is_dir($path . '/' . $entry))
599
			$listing1[] = array(
600
				'filename' => $entry,
601
				'is_writable' => is_writable($path . '/' . $entry),
602
				'is_directory' => true,
603
				'is_template' => false,
604
				'is_image' => false,
605
				'is_editable' => false,
606
				'href' => $scripturl . '?action=admin;area=theme;th=' . $_GET['th'] . ';' . $context['session_var'] . '=' . $context['session_id'] . ';sa=edit;directory=' . $relative . $entry,
607
				'size' => '',
608
			);
609
		else
610
		{
611
			$size = filesize($path . '/' . $entry);
612
			if ($size > 2048 || $size == 1024)
613
				$size = comma_format($size / 1024) . ' ' . $txt['themeadmin_edit_kilobytes'];
614
			else
615
				$size = comma_format($size) . ' ' . $txt['themeadmin_edit_bytes'];
616
617
			$listing2[] = array(
618
				'filename' => $entry,
619
				'is_writable' => is_writable($path . '/' . $entry),
620
				'is_directory' => false,
621
				'is_template' => preg_match('~\.template\.php$~', $entry) != 0,
622
				'is_image' => preg_match('~\.(jpg|jpeg|gif|bmp|png)$~', $entry) != 0,
623
				'is_editable' => is_writable($path . '/' . $entry) && preg_match('~\.(php|pl|css|js|vbs|xml|xslt|txt|xsl|html|htm|shtm|shtml|asp|aspx|cgi|py)$~', $entry) != 0,
624
				'href' => $scripturl . '?action=admin;area=theme;th=' . $_GET['th'] . ';' . $context['session_var'] . '=' . $context['session_id'] . ';sa=edit;filename=' . $relative . $entry,
625
				'size' => $size,
626
				'last_modified' => timeformat(filemtime($path . '/' . $entry)),
627
			);
628
		}
629
	}
630
631
	return array_merge($listing1, $listing2);
632
}
633
634
?>