Failed Conditions
Branch release-2.1 (4e22cf)
by Rick
06:39
created

Packages.php ➔ build_special_files__recursive()   B

Complexity

Conditions 6

Size

Total Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 6
dl 0
loc 12
rs 8
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * This file is the main Package Manager.
5
 *
6
 * Simple Machines Forum (SMF)
7
 *
8
 * @package SMF
9
 * @author Simple Machines http://www.simplemachines.org
10
 * @copyright 2017 Simple Machines and individual contributors
11
 * @license http://www.simplemachines.org/about/smf/license.php BSD
12
 *
13
 * @version 2.1 Beta 4
14
 */
15
16
if (!defined('SMF'))
17
	die('No direct access...');
18
19
/**
20
 * This is the notoriously defunct package manager..... :/.
21
 */
22
function Packages()
23
{
24
	global $txt, $sourcedir, $context;
25
26
	// @todo Remove this!
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...
27
	if (isset($_GET['get']) || isset($_GET['pgdownload']))
28
	{
29
		require_once($sourcedir . '/PackageGet.php');
30
		return PackageGet();
31
	}
32
33
	isAllowedTo('admin_forum');
34
35
	// Load all the basic stuff.
36
	require_once($sourcedir . '/Subs-Package.php');
37
	loadLanguage('Packages');
38
	loadTemplate('Packages', 'admin');
39
40
	$context['page_title'] = $txt['package'];
41
42
	// Delegation makes the world... that is, the package manager go 'round.
43
	$subActions = array(
44
		'browse' => 'PackageBrowse',
45
		'remove' => 'PackageRemove',
46
		'list' => 'PackageList',
47
		'ftptest' => 'PackageFTPTest',
48
		'install' => 'PackageInstallTest',
49
		'install2' => 'PackageInstall',
50
		'uninstall' => 'PackageInstallTest',
51
		'uninstall2' => 'PackageInstall',
52
		'options' => 'PackageOptions',
53
		'perms' => 'PackagePermissions',
54
		'flush' => 'FlushInstall',
55
		'examine' => 'ExamineFile',
56
		'showoperations' => 'ViewOperations',
57
	);
58
59
	// Work out exactly who it is we are calling.
60
	if (isset($_REQUEST['sa']) && isset($subActions[$_REQUEST['sa']]))
61
		$context['sub_action'] = $_REQUEST['sa'];
62
	else
63
		$context['sub_action'] = 'browse';
64
65
	// Set up some tabs...
66
	$context[$context['admin_menu_name']]['tab_data'] = array(
67
		'title' => $txt['package_manager'],
68
		// @todo 'help' => 'registrations',
0 ignored issues
show
Unused Code Comprehensibility introduced by
45% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
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...
69
		'description' => $txt['package_manager_desc'],
70
		'tabs' => array(
71
			'browse' => array(
72
			),
73
			'packageget' => array(
74
				'description' => $txt['download_packages_desc'],
75
			),
76
			'perms' => array(
77
				'description' => $txt['package_file_perms_desc'],
78
			),
79
			'options' => array(
80
				'description' => $txt['package_install_options_desc'],
81
			),
82
		),
83
	);
84
85
	if ($context['sub_action'] == 'browse')
86
		loadJavaScriptFile('suggest.js', array('defer' => false), 'smf_suggest');
87
88
	call_integration_hook('integrate_manage_packages', array(&$subActions));
89
90
	// Call the function we're handing control to.
91
	call_helper($subActions[$context['sub_action']]);
92
}
93
94
/**
95
 * Test install a package.
96
 */
97
function PackageInstallTest()
98
{
99
	global $boarddir, $txt, $context, $scripturl, $sourcedir, $packagesdir, $modSettings, $smcFunc, $settings;
100
101
	// You have to specify a file!!
102
	if (!isset($_REQUEST['package']) || $_REQUEST['package'] == '')
103
		redirectexit('action=admin;area=packages');
104
	$context['filename'] = preg_replace('~[\.]+~', '.', $_REQUEST['package']);
105
106
	// Do we have an existing id, for uninstalls and the like.
107
	$context['install_id'] = isset($_REQUEST['pid']) ? (int) $_REQUEST['pid'] : 0;
108
109
	require_once($sourcedir . '/Subs-Package.php');
110
111
	// Load up the package FTP information?
112
	create_chmod_control();
113
114
	// Make sure temp directory exists and is empty.
115
	if (file_exists($packagesdir . '/temp'))
116
		deltree($packagesdir . '/temp', false);
117
118
	if (!mktree($packagesdir . '/temp', 0755))
119
	{
120
		deltree($packagesdir . '/temp', false);
121
		if (!mktree($packagesdir . '/temp', 0777))
122
		{
123
			deltree($packagesdir . '/temp', false);
124
			create_chmod_control(array($packagesdir . '/temp/delme.tmp'), array('destination_url' => $scripturl . '?action=admin;area=packages;sa=' . $_REQUEST['sa'] . ';package=' . $_REQUEST['package'], 'crash_on_error' => true));
125
126
			deltree($packagesdir . '/temp', false);
127
			if (!mktree($packagesdir . '/temp', 0777))
128
				fatal_lang_error('package_cant_download', false);
129
		}
130
	}
131
132
	$context['uninstalling'] = $_REQUEST['sa'] == 'uninstall';
133
134
	// Change our last link tree item for more information on this Packages area.
135
	$context['linktree'][count($context['linktree']) - 1] = array(
136
		'url' => $scripturl . '?action=admin;area=packages;sa=browse',
137
		'name' => $context['uninstalling'] ? $txt['package_uninstall_actions'] : $txt['install_actions']
138
	);
139
	$context['page_title'] .= ' - ' . ($context['uninstalling'] ? $txt['package_uninstall_actions'] : $txt['install_actions']);
140
141
	$context['sub_template'] = 'view_package';
142
143
	if (!file_exists($packagesdir . '/' . $context['filename']))
144
	{
145
		deltree($packagesdir . '/temp');
146
		fatal_lang_error('package_no_file', false);
147
	}
148
149
	// Extract the files so we can get things like the readme, etc.
150 View Code Duplication
	if (is_file($packagesdir . '/' . $context['filename']))
151
	{
152
		$context['extracted_files'] = read_tgz_file($packagesdir . '/' . $context['filename'], $packagesdir . '/temp');
153
154
		if ($context['extracted_files'] && !file_exists($packagesdir . '/temp/package-info.xml'))
155
			foreach ($context['extracted_files'] as $file)
156
				if (basename($file['filename']) == 'package-info.xml')
157
				{
158
					$context['base_path'] = dirname($file['filename']) . '/';
159
					break;
160
				}
161
162
		if (!isset($context['base_path']))
163
			$context['base_path'] = '';
164
	}
165
	elseif (is_dir($packagesdir . '/' . $context['filename']))
166
	{
167
		copytree($packagesdir . '/' . $context['filename'], $packagesdir . '/temp');
168
		$context['extracted_files'] = listtree($packagesdir . '/temp');
169
		$context['base_path'] = '';
170
	}
171
	else
172
		fatal_lang_error('no_access', false);
173
174
	// Load up any custom themes we may want to install into...
175
	$request = $smcFunc['db_query']('', '
176
		SELECT id_theme, variable, value
177
		FROM {db_prefix}themes
178
		WHERE (id_theme = {int:default_theme} OR id_theme IN ({array_int:known_theme_list}))
179
			AND variable IN ({string:name}, {string:theme_dir})',
180
		array(
181
			'known_theme_list' => explode(',', $modSettings['knownThemes']),
182
			'default_theme' => 1,
183
			'name' => 'name',
184
			'theme_dir' => 'theme_dir',
185
		)
186
	);
187
	$theme_paths = array();
188
	while ($row = $smcFunc['db_fetch_assoc']($request))
189
		$theme_paths[$row['id_theme']][$row['variable']] = $row['value'];
190
	$smcFunc['db_free_result']($request);
191
192
	// Get the package info...
193
	$packageInfo = getPackageInfo($context['filename']);
194
195
	if (!is_array($packageInfo))
196
		fatal_lang_error($packageInfo);
197
198
	$packageInfo['filename'] = $context['filename'];
199
	$context['package_name'] = isset($packageInfo['name']) ? $packageInfo['name'] : $context['filename'];
200
201
	// Set the type of extraction...
202
	$context['extract_type'] = isset($packageInfo['type']) ? $packageInfo['type'] : 'modification';
203
204
	// The mod isn't installed.... unless proven otherwise.
205
	$context['is_installed'] = false;
206
207
	// See if it is installed?
208
	$request = $smcFunc['db_query']('', '
209
		SELECT version, themes_installed, db_changes
210
		FROM {db_prefix}log_packages
211
		WHERE package_id = {string:current_package}
212
			AND install_state != {int:not_installed}
213
		ORDER BY time_installed DESC
214
		LIMIT 1',
215
		array(
216
			'not_installed'	=> 0,
217
			'current_package' => $packageInfo['id'],
218
		)
219
	);
220
221 View Code Duplication
	while ($row = $smcFunc['db_fetch_assoc']($request))
222
	{
223
		$old_themes = explode(',', $row['themes_installed']);
224
		$old_version = $row['version'];
225
		$db_changes = empty($row['db_changes']) ? array() : $smcFunc['json_decode']($row['db_changes'], true);
226
	}
227
	$smcFunc['db_free_result']($request);
228
229
	$context['database_changes'] = array();
230
	if (isset($packageInfo['uninstall']['database']))
231
		$context['database_changes'][] = $txt['execute_database_changes'] . ' - ' . $packageInfo['uninstall']['database'];
232
	elseif (!empty($db_changes))
233
	{
234
		foreach ($db_changes as $change)
235
		{
236
			if (isset($change[2]) && isset($txt['package_db_' . $change[0]]))
237
				$context['database_changes'][] = sprintf($txt['package_db_' . $change[0]], $change[1], $change[2]);
238
			elseif (isset($txt['package_db_' . $change[0]]))
239
				$context['database_changes'][] = sprintf($txt['package_db_' . $change[0]], $change[1]);
240
			else
241
				$context['database_changes'][] = $change[0] . '-' . $change[1] . (isset($change[2]) ? '-' . $change[2] : '');
242
		}
243
	}
244
245
	// Uninstalling?
246
	if ($context['uninstalling'])
247
	{
248
		// Wait, it's not installed yet!
249
		if (!isset($old_version) && $context['uninstalling'])
250
		{
251
			deltree($packagesdir . '/temp');
252
			fatal_lang_error('package_cant_uninstall', false);
253
		}
254
255
		$actions = parsePackageInfo($packageInfo['xml'], true, 'uninstall');
256
257
		// Gadzooks!  There's no uninstaller at all!?
258
		if (empty($actions))
259
		{
260
			deltree($packagesdir . '/temp');
261
			fatal_lang_error('package_uninstall_cannot', false);
262
		}
263
264
		// Can't edit the custom themes it's edited if you're uninstalling, they must be removed.
265
		$context['themes_locked'] = true;
266
267
		// Only let them uninstall themes it was installed into.
268
		foreach ($theme_paths as $id => $data)
269
			if ($id != 1 && !in_array($id, $old_themes))
0 ignored issues
show
Bug introduced by
The variable $old_themes 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...
270
				unset($theme_paths[$id]);
271
	}
272 View Code Duplication
	elseif (isset($old_version) && $old_version != $packageInfo['version'])
273
	{
274
		// Look for an upgrade...
275
		$actions = parsePackageInfo($packageInfo['xml'], true, 'upgrade', $old_version);
276
277
		// There was no upgrade....
278
		if (empty($actions))
279
			$context['is_installed'] = true;
280
		else
281
		{
282
			// Otherwise they can only upgrade themes from the first time around.
283
			foreach ($theme_paths as $id => $data)
284
				if ($id != 1 && !in_array($id, $old_themes))
285
					unset($theme_paths[$id]);
286
		}
287
	}
288
	elseif (isset($old_version) && $old_version == $packageInfo['version'])
289
		$context['is_installed'] = true;
290
291 View Code Duplication
	if (!isset($old_version) || $context['is_installed'])
292
		$actions = parsePackageInfo($packageInfo['xml'], true, 'install');
293
294
	$context['actions'] = array();
295
	$context['ftp_needed'] = false;
296
	$context['has_failure'] = false;
297
	$chmod_files = array();
298
299
	// no actions found, return so we can display an error
300
	if (empty($actions))
301
		return;
302
303
	// This will hold data about anything that can be installed in other themes.
304
	$themeFinds = array(
305
		'candidates' => array(),
306
		'other_themes' => array(),
307
	);
308
309
	// Now prepare things for the template.
310
	foreach ($actions as $action)
311
	{
312
		// Not failed until proven otherwise.
313
		$failed = false;
314
		$thisAction = array();
315
316
		if ($action['type'] == 'chmod')
317
		{
318
			$chmod_files[] = $action['filename'];
319
			continue;
320
		}
321
		elseif ($action['type'] == 'readme' || $action['type'] == 'license')
322
		{
323
			$type = 'package_' . $action['type'];
324 View Code Duplication
			if (file_exists($packagesdir . '/temp/' . $context['base_path'] . $action['filename']))
325
				$context[$type] = $smcFunc['htmlspecialchars'](trim(file_get_contents($packagesdir . '/temp/' . $context['base_path'] . $action['filename']), "\n\r"));
326
			elseif (file_exists($action['filename']))
327
				$context[$type] = $smcFunc['htmlspecialchars'](trim(file_get_contents($action['filename']), "\n\r"));
328
329
			if (!empty($action['parse_bbc']))
330
			{
331
				require_once($sourcedir . '/Subs-Post.php');
332
				$context[$type] = preg_replace('~\[[/]?html\]~i', '', $context[$type]);
333
				preparsecode($context[$type]);
334
				$context[$type] = parse_bbc($context[$type]);
335
			}
336
			else
337
				$context[$type] = nl2br($context[$type]);
338
339
			continue;
340
		}
341
		// Don't show redirects.
342
		elseif ($action['type'] == 'redirect')
343
			continue;
344
		elseif ($action['type'] == 'error')
345
		{
346
			$context['has_failure'] = true;
347
			if (isset($action['error_msg']) && isset($action['error_var']))
348
				$context['failure_details'] = sprintf($txt['package_will_fail_' . $action['error_msg']], $action['error_var']);
349
			elseif (isset($action['error_msg']))
350
				$context['failure_details'] = isset($txt['package_will_fail_' . $action['error_msg']]) ? $txt['package_will_fail_' . $action['error_msg']] : $action['error_msg'];
351
		}
352
		elseif ($action['type'] == 'modification')
353
		{
354
			if (!file_exists($packagesdir . '/temp/' . $context['base_path'] . $action['filename']))
355
			{
356
				$context['has_failure'] = true;
357
358
				$context['actions'][] = array(
359
					'type' => $txt['execute_modification'],
360
					'action' => $smcFunc['htmlspecialchars'](strtr($action['filename'], array($boarddir => '.'))),
361
					'description' => $txt['package_action_missing'],
362
					'failed' => true,
363
				);
364
			}
365
			else
366
			{
367
368 View Code Duplication
				if ($action['boardmod'])
369
					$mod_actions = parseBoardMod(@file_get_contents($packagesdir . '/temp/' . $context['base_path'] . $action['filename']), true, $action['reverse'], $theme_paths);
370
				else
371
					$mod_actions = parseModification(@file_get_contents($packagesdir . '/temp/' . $context['base_path'] . $action['filename']), true, $action['reverse'], $theme_paths);
372
373
				if (count($mod_actions) == 1 && isset($mod_actions[0]) && $mod_actions[0]['type'] == 'error' && $mod_actions[0]['filename'] == '-')
374
					$mod_actions[0]['filename'] = $action['filename'];
375
376
				foreach ($mod_actions as $key => $mod_action)
377
				{
378
					// Lets get the last section of the file name.
379 View Code Duplication
					if (isset($mod_action['filename']) && substr($mod_action['filename'], -13) != '.template.php')
380
						$actual_filename = strtolower(substr(strrchr($mod_action['filename'], '/'), 1) . '||' . $action['filename']);
381
					elseif (isset($mod_action['filename']) && preg_match('~([\w]*)/([\w]*)\.template\.php$~', $mod_action['filename'], $matches))
382
						$actual_filename = strtolower($matches[1] . '/' . $matches[2] . '.template.php' . '||' . $action['filename']);
383
					else
384
						$actual_filename = $key;
385
386
					if ($mod_action['type'] == 'opened')
387
						$failed = false;
388
					elseif ($mod_action['type'] == 'failure')
389
					{
390
						if (empty($mod_action['is_custom']))
391
							$context['has_failure'] = true;
392
						$failed = true;
393
					}
394
					elseif ($mod_action['type'] == 'chmod')
395
					{
396
						$chmod_files[] = $mod_action['filename'];
397
					}
398
					elseif ($mod_action['type'] == 'saved')
399
					{
400
						if (!empty($mod_action['is_custom']))
401
						{
402
							if (!isset($context['theme_actions'][$mod_action['is_custom']]))
403
								$context['theme_actions'][$mod_action['is_custom']] = array(
404
									'name' => $theme_paths[$mod_action['is_custom']]['name'],
405
									'actions' => array(),
406
									'has_failure' => $failed,
407
								);
408
							else
409
								$context['theme_actions'][$mod_action['is_custom']]['has_failure'] |= $failed;
410
411
							$context['theme_actions'][$mod_action['is_custom']]['actions'][$actual_filename] = array(
412
								'type' => $txt['execute_modification'],
413
								'action' => $smcFunc['htmlspecialchars'](strtr($mod_action['filename'], array($boarddir => '.'))),
414
								'description' => $failed ? $txt['package_action_failure'] : $txt['package_action_success'],
415
								'failed' => $failed,
416
							);
417
						}
418
						elseif (!isset($context['actions'][$actual_filename]))
419
						{
420
							$context['actions'][$actual_filename] = array(
421
								'type' => $txt['execute_modification'],
422
								'action' => $smcFunc['htmlspecialchars'](strtr($mod_action['filename'], array($boarddir => '.'))),
423
								'description' => $failed ? $txt['package_action_failure'] : $txt['package_action_success'],
424
								'failed' => $failed,
425
							);
426
						}
427
						else
428
						{
429
								$context['actions'][$actual_filename]['failed'] |= $failed;
430
								$context['actions'][$actual_filename]['description'] = $context['actions'][$actual_filename]['failed'] ? $txt['package_action_failure'] : $txt['package_action_success'];
431
						}
432
					}
433 View Code Duplication
					elseif ($mod_action['type'] == 'skipping')
434
					{
435
						$context['actions'][$actual_filename] = array(
436
							'type' => $txt['execute_modification'],
437
							'action' => $smcFunc['htmlspecialchars'](strtr($mod_action['filename'], array($boarddir => '.'))),
438
							'description' => $txt['package_action_skipping']
439
						);
440
					}
441
					elseif ($mod_action['type'] == 'missing' && empty($mod_action['is_custom']))
442
					{
443
						$context['has_failure'] = true;
444
						$context['actions'][$actual_filename] = array(
445
							'type' => $txt['execute_modification'],
446
							'action' => $smcFunc['htmlspecialchars'](strtr($mod_action['filename'], array($boarddir => '.'))),
447
							'description' => $txt['package_action_missing'],
448
							'failed' => true,
449
						);
450
					}
451 View Code Duplication
					elseif ($mod_action['type'] == 'error')
452
						$context['actions'][$actual_filename] = array(
453
							'type' => $txt['execute_modification'],
454
							'action' => $smcFunc['htmlspecialchars'](strtr($mod_action['filename'], array($boarddir => '.'))),
455
							'description' => $txt['package_action_error'],
456
							'failed' => true,
457
						);
458
				}
459
460
				// We need to loop again just to get the operations down correctly.
461
				foreach ($mod_actions as $operation_key => $mod_action)
462
				{
463
					// Lets get the last section of the file name.
464 View Code Duplication
					if (isset($mod_action['filename']) && substr($mod_action['filename'], -13) != '.template.php')
465
						$actual_filename = strtolower(substr(strrchr($mod_action['filename'], '/'), 1) . '||' . $action['filename']);
466
					elseif (isset($mod_action['filename']) && preg_match('~([\w]*)/([\w]*)\.template\.php$~', $mod_action['filename'], $matches))
467
						$actual_filename = strtolower($matches[1] . '/' . $matches[2] . '.template.php' . '||' . $action['filename']);
468
					else
469
						$actual_filename = $key;
0 ignored issues
show
Bug introduced by
The variable $key seems to be defined by a foreach iteration on line 376. Are you sure the iterator is never empty, otherwise this variable is not defined?

It seems like you are relying on a variable being defined by an iteration:

foreach ($a as $b) {
}

// $b is defined here only if $a has elements, for example if $a is array()
// then $b would not be defined here. To avoid that, we recommend to set a
// default value for $b.


// Better
$b = 0; // or whatever default makes sense in your context
foreach ($a as $b) {
}

// $b is now guaranteed to be defined here.
Loading history...
470
471
					// We just need it for actual parse changes.
472
					if (!in_array($mod_action['type'], array('error', 'result', 'opened', 'saved', 'end', 'missing', 'skipping', 'chmod')))
473
					{
474
						if (empty($mod_action['is_custom']))
475
							$context['actions'][$actual_filename]['operations'][] = array(
476
								'type' => $txt['execute_modification'],
477
								'action' => $smcFunc['htmlspecialchars'](strtr($mod_action['filename'], array($boarddir => '.'))),
478
								'description' => $mod_action['failed'] ? $txt['package_action_failure'] : $txt['package_action_success'],
479
								'position' => $mod_action['position'],
480
								'operation_key' => $operation_key,
481
								'filename' => $action['filename'],
482
								'is_boardmod' => $action['boardmod'],
483
								'failed' => $mod_action['failed'],
484
								'ignore_failure' => !empty($mod_action['ignore_failure']),
485
							);
486
487
						// Themes are under the saved type.
488
						if (isset($mod_action['is_custom']) && isset($context['theme_actions'][$mod_action['is_custom']]))
489
							$context['theme_actions'][$mod_action['is_custom']]['actions'][$actual_filename]['operations'][] = array(
490
								'type' => $txt['execute_modification'],
491
								'action' => $smcFunc['htmlspecialchars'](strtr($mod_action['filename'], array($boarddir => '.'))),
492
								'description' => $mod_action['failed'] ? $txt['package_action_failure'] : $txt['package_action_success'],
493
								'position' => $mod_action['position'],
494
								'operation_key' => $operation_key,
495
								'filename' => $action['filename'],
496
								'is_boardmod' => $action['boardmod'],
497
								'failed' => $mod_action['failed'],
498
								'ignore_failure' => !empty($mod_action['ignore_failure']),
499
							);
500
					}
501
				}
502
			}
503
		}
504 View Code Duplication
		elseif ($action['type'] == 'code')
505
		{
506
			$thisAction = array(
507
				'type' => $txt['execute_code'],
508
				'action' => $smcFunc['htmlspecialchars']($action['filename']),
509
			);
510
		}
511 View Code Duplication
		elseif ($action['type'] == 'database')
512
		{
513
			$thisAction = array(
514
				'type' => $txt['execute_database_changes'],
515
				'action' => $smcFunc['htmlspecialchars']($action['filename']),
516
			);
517
		}
518
		elseif (in_array($action['type'], array('create-dir', 'create-file')))
519
		{
520
			$thisAction = array(
521
				'type' => $txt['package_create'] . ' ' . ($action['type'] == 'create-dir' ? $txt['package_tree'] : $txt['package_file']),
522
				'action' => $smcFunc['htmlspecialchars'](strtr($action['destination'], array($boarddir => '.')))
523
			);
524
		}
525
		elseif ($action['type'] == 'hook')
526
		{
527
			$action['description'] = !isset($action['hook'], $action['function']) ? $txt['package_action_failure'] : $txt['package_action_success'];
528
529
			if (!isset($action['hook'], $action['function']))
530
				$context['has_failure'] = true;
531
532
			$thisAction = array(
533
				'type' => $action['reverse'] ? $txt['execute_hook_remove'] : $txt['execute_hook_add'],
534
				'action' => sprintf($txt['execute_hook_action' . ($action['reverse'] ? '_inverse' : '')], $smcFunc['htmlspecialchars']($action['hook'])),
535
			);
536
		}
537
		elseif ($action['type'] == 'credits')
538
		{
539
			$thisAction = array(
540
				'type' => $txt['execute_credits_add'],
541
				'action' => sprintf($txt['execute_credits_action'], $smcFunc['htmlspecialchars']($action['title'])),
542
			);
543
		}
544
		elseif ($action['type'] == 'requires')
545
		{
546
			$installed = false;
547
			$version = true;
548
549
			// package missing required values?
550
			if (!isset($action['id']))
551
				$context['has_failure'] = true;
552
			else
553
			{
554
				// See if this dependancy is installed
555
				$request = $smcFunc['db_query']('', '
556
					SELECT version
557
					FROM {db_prefix}log_packages
558
					WHERE package_id = {string:current_package}
559
						AND install_state != {int:not_installed}
560
					ORDER BY time_installed DESC
561
					LIMIT 1',
562
					array(
563
						'not_installed'	=> 0,
564
						'current_package' => $action['id'],
565
					)
566
				);
567
				$installed = ($smcFunc['db_num_rows']($request) !== 0);
568
				if ($installed)
569
					list ($version) = $smcFunc['db_fetch_row']($request);
570
				$smcFunc['db_free_result']($request);
571
572
				// do a version level check (if requested) in the most basic way
573
				$version = (isset($action['version']) ? $version == $action['version'] : true);
574
			}
575
576
			// Set success or failure information
577
			$action['description'] = ($installed && $version) ? $txt['package_action_success'] : $txt['package_action_failure'];
578
			$context['has_failure'] = !($installed && $version);
579
580
			$thisAction = array(
581
				'type' => $txt['package_requires'],
582
				'action' => $txt['package_check_for'] . ' ' . $action['id'] . (isset($action['version']) ? (' / ' . ($version ? $action['version'] : '<span class="error">' . $action['version'] . '</span>')) : ''),
583
			);
584
		}
585
		elseif (in_array($action['type'], array('require-dir', 'require-file')))
586
		{
587
			// Do this one...
588
			$thisAction = array(
589
				'type' => $txt['package_extract'] . ' ' . ($action['type'] == 'require-dir' ? $txt['package_tree'] : $txt['package_file']),
590
				'action' => $smcFunc['htmlspecialchars'](strtr($action['destination'], array($boarddir => '.')))
591
			);
592
593
			// Could this be theme related?
594
			if (!empty($action['unparsed_destination']) && preg_match('~^\$(languagedir|languages_dir|imagesdir|themedir|themes_dir)~i', $action['unparsed_destination'], $matches))
595
			{
596
				// Is the action already stated?
597
				$theme_action = !empty($action['theme_action']) && in_array($action['theme_action'], array('no', 'yes', 'auto')) ? $action['theme_action'] : 'auto';
598
				// If it's not auto do we think we have something we can act upon?
599 View Code Duplication
				if ($theme_action != 'auto' && !in_array($matches[1], array('languagedir', 'languages_dir', 'imagesdir', 'themedir')))
600
					$theme_action = '';
601
				// ... or if it's auto do we even want to do anything?
602
				elseif ($theme_action == 'auto' && $matches[1] != 'imagesdir')
603
					$theme_action = '';
604
605
				// So, we still want to do something?
606 View Code Duplication
				if ($theme_action != '')
607
					$themeFinds['candidates'][] = $action;
608
				// Otherwise is this is going into another theme record it.
609
				elseif ($matches[1] == 'themes_dir')
610
					$themeFinds['other_themes'][] = strtolower(strtr(parse_path($action['unparsed_destination']), array('\\' => '/')) . '/' . basename($action['filename']));
611
			}
612
		}
613
		elseif (in_array($action['type'], array('move-dir', 'move-file')))
614
			$thisAction = array(
615
				'type' => $txt['package_move'] . ' ' . ($action['type'] == 'move-dir' ? $txt['package_tree'] : $txt['package_file']),
616
				'action' => $smcFunc['htmlspecialchars'](strtr($action['source'], array($boarddir => '.'))) . ' => ' . $smcFunc['htmlspecialchars'](strtr($action['destination'], array($boarddir => '.')))
617
			);
618
		elseif (in_array($action['type'], array('remove-dir', 'remove-file')))
619
		{
620
			$thisAction = array(
621
				'type' => $txt['package_delete'] . ' ' . ($action['type'] == 'remove-dir' ? $txt['package_tree'] : $txt['package_file']),
622
				'action' => $smcFunc['htmlspecialchars'](strtr($action['filename'], array($boarddir => '.')))
623
			);
624
625
			// Could this be theme related?
626
			if (!empty($action['unparsed_filename']) && preg_match('~^\$(languagedir|languages_dir|imagesdir|themedir|themes_dir)~i', $action['unparsed_filename'], $matches))
627
			{
628
629
				// Is the action already stated?
630
				$theme_action = !empty($action['theme_action']) && in_array($action['theme_action'], array('no', 'yes', 'auto')) ? $action['theme_action'] : 'auto';
631
				$action['unparsed_destination'] = $action['unparsed_filename'];
632
633
				// If it's not auto do we think we have something we can act upon?
634 View Code Duplication
				if ($theme_action != 'auto' && !in_array($matches[1], array('languagedir', 'languages_dir', 'imagesdir', 'themedir')))
635
					$theme_action = '';
636
				// ... or if it's auto do we even want to do anything?
637
				elseif ($theme_action == 'auto' && $matches[1] != 'imagesdir')
638
					$theme_action = '';
639
640
				// So, we still want to do something?
641 View Code Duplication
				if ($theme_action != '')
642
					$themeFinds['candidates'][] = $action;
643
				// Otherwise is this is going into another theme record it.
644
				elseif ($matches[1] == 'themes_dir')
645
					$themeFinds['other_themes'][] = strtolower(strtr(parse_path($action['unparsed_filename']), array('\\' => '/')) . '/' . basename($action['filename']));
646
			}
647
		}
648
649
		if (empty($thisAction))
650
			continue;
651
652
		if (!in_array($action['type'], array('hook', 'credits')))
653
		{
654
			if ($context['uninstalling'])
655
				$file = in_array($action['type'], array('remove-dir', 'remove-file')) ? $action['filename'] : $packagesdir . '/temp/' . $context['base_path'] . $action['filename'];
656
			else
657
				$file = $packagesdir . '/temp/' . $context['base_path'] . $action['filename'];
658
		}
659
660
		// Don't fail if a file/directory we're trying to create doesn't exist...
661
		if (isset($action['filename']) && !file_exists($file) && !in_array($action['type'], array('create-dir', 'create-file')))
0 ignored issues
show
Bug introduced by
The variable $file 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...
662
		{
663
			$context['has_failure'] = true;
664
665
			$thisAction += array(
666
				'description' => $txt['package_action_missing'],
667
				'failed' => true,
668
			);
669
		}
670
671
		// @todo None given?
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...
672 View Code Duplication
		if (empty($thisAction['description']))
673
			$thisAction['description'] = isset($action['description']) ? $action['description'] : '';
674
675
		$context['actions'][] = $thisAction;
676
	}
677
678
	// Have we got some things which we might want to do "multi-theme"?
679
	if (!empty($themeFinds['candidates']))
680
	{
681
		foreach ($themeFinds['candidates'] as $action_data)
682
		{
683
			// Get the part of the file we'll be dealing with.
684
			preg_match('~^\$(languagedir|languages_dir|imagesdir|themedir)(\\|/)*(.+)*~i', $action_data['unparsed_destination'], $matches);
685
686
			if ($matches[1] == 'imagesdir')
687
				$path = '/' . basename($settings['default_images_url']);
688
			elseif ($matches[1] == 'languagedir' || $matches[1] == 'languages_dir')
689
				$path = '/languages';
690
			else
691
				$path = '';
692
693
			if (!empty($matches[3]))
694
				$path .= $matches[3];
695
696
			if (!$context['uninstalling'])
697
				$path .= '/' . basename($action_data['filename']);
698
699
			// Loop through each custom theme to note it's candidacy!
700
			foreach ($theme_paths as $id => $theme_data)
701
			{
702
				if (isset($theme_data['theme_dir']) && $id != 1)
703
				{
704
					$real_path = $theme_data['theme_dir'] . $path;
705
706
					// Confirm that we don't already have this dealt with by another entry.
707
					if (!in_array(strtolower(strtr($real_path, array('\\' => '/'))), $themeFinds['other_themes']))
708
					{
709
						// Check if we will need to chmod this.
710
						if (!mktree(dirname($real_path), false))
0 ignored issues
show
Documentation introduced by
false is of type boolean, but the function expects a integer.

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...
711
						{
712
							$temp = dirname($real_path);
713
							while (!file_exists($temp) && strlen($temp) > 1)
714
								$temp = dirname($temp);
715
							$chmod_files[] = $temp;
716
						}
717
718
						if ($action_data['type'] == 'require-dir' && !is_writable($real_path) && (file_exists($real_path) || !is_writable(dirname($real_path))))
719
							$chmod_files[] = $real_path;
720
721
						if (!isset($context['theme_actions'][$id]))
722
							$context['theme_actions'][$id] = array(
723
								'name' => $theme_data['name'],
724
								'actions' => array(),
725
							);
726
727
						if ($context['uninstalling'])
728
							$context['theme_actions'][$id]['actions'][] = array(
729
								'type' => $txt['package_delete'] . ' ' . ($action_data['type'] == 'require-dir' ? $txt['package_tree'] : $txt['package_file']),
730
								'action' => strtr($real_path, array('\\' => '/', $boarddir => '.')),
731
								'description' => '',
732
								'value' => base64_encode($smcFunc['json_encode'](array('type' => $action_data['type'], 'orig' => $action_data['filename'], 'future' => $real_path, 'id' => $id))),
733
								'not_mod' => true,
734
							);
735
						else
736
							$context['theme_actions'][$id]['actions'][] = array(
737
								'type' => $txt['package_extract'] . ' ' . ($action_data['type'] == 'require-dir' ? $txt['package_tree'] : $txt['package_file']),
738
								'action' => strtr($real_path, array('\\' => '/', $boarddir => '.')),
739
								'description' => '',
740
								'value' => base64_encode($smcFunc['json_encode'](array('type' => $action_data['type'], 'orig' => $action_data['destination'], 'future' => $real_path, 'id' => $id))),
741
								'not_mod' => true,
742
							);
743
					}
744
				}
745
			}
746
		}
747
	}
748
749
	// Trash the cache... which will also check permissions for us!
750
	package_flush_cache(true);
751
752
	if (file_exists($packagesdir . '/temp'))
753
		deltree($packagesdir . '/temp');
754
755
	if (!empty($chmod_files))
756
	{
757
		$ftp_status = create_chmod_control($chmod_files);
758
		$context['ftp_needed'] = !empty($ftp_status['files']['notwritable']) && !empty($context['package_ftp']);
759
	}
760
761
	$context['post_url'] = $scripturl . '?action=admin;area=packages;sa=' . ($context['uninstalling'] ? 'uninstall' : 'install') . ($context['ftp_needed'] ? '' : '2') . ';package=' . $context['filename'] . ';pid=' . $context['install_id'];
762
	checkSubmitOnce('register');
763
}
764
765
/**
766
 * Apply another type of (avatar, language, etc.) package.
767
 */
768
function PackageInstall()
769
{
770
	global $txt, $context, $boardurl, $scripturl, $sourcedir, $packagesdir, $modSettings;
771
	global $user_info, $smcFunc;
772
773
	// Make sure we don't install this mod twice.
774
	checkSubmitOnce('check');
775
	checkSession();
776
777
	// If there's no file, what are we installing?
778
	if (!isset($_REQUEST['package']) || $_REQUEST['package'] == '')
779
		redirectexit('action=admin;area=packages');
780
	$context['filename'] = $_REQUEST['package'];
781
782
	// If this is an uninstall, we'll have an id.
783
	$context['install_id'] = isset($_REQUEST['pid']) ? (int) $_REQUEST['pid'] : 0;
784
785
	require_once($sourcedir . '/Subs-Package.php');
786
787
	// @todo Perhaps do it in steps, if necessary?
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...
788
789
	$context['uninstalling'] = $_REQUEST['sa'] == 'uninstall2';
790
791
	// Set up the linktree for other.
792
	$context['linktree'][count($context['linktree']) - 1] = array(
793
		'url' => $scripturl . '?action=admin;area=packages;sa=browse',
794
		'name' => $context['uninstalling'] ? $txt['uninstall'] : $txt['extracting']
795
	);
796
	$context['page_title'] .= ' - ' . ($context['uninstalling'] ? $txt['uninstall'] : $txt['extracting']);
797
798
	$context['sub_template'] = 'extract_package';
799
800
	if (!file_exists($packagesdir . '/' . $context['filename']))
801
		fatal_lang_error('package_no_file', false);
802
803
	// Load up the package FTP information?
804
	create_chmod_control(array(), array('destination_url' => $scripturl . '?action=admin;area=packages;sa=' . $_REQUEST['sa'] . ';package=' . $_REQUEST['package']));
805
806
	// Make sure temp directory exists and is empty!
807
	if (file_exists($packagesdir . '/temp'))
808
		deltree($packagesdir . '/temp', false);
809
	else
810
		mktree($packagesdir . '/temp', 0777);
811
812
	// Let the unpacker do the work.
813 View Code Duplication
	if (is_file($packagesdir . '/' . $context['filename']))
814
	{
815
		$context['extracted_files'] = read_tgz_file($packagesdir . '/' . $context['filename'], $packagesdir . '/temp');
816
817
		if (!file_exists($packagesdir . '/temp/package-info.xml'))
818
			foreach ($context['extracted_files'] as $file)
0 ignored issues
show
Bug introduced by
The expression $context['extracted_files'] of type array|false is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
819
				if (basename($file['filename']) == 'package-info.xml')
820
				{
821
					$context['base_path'] = dirname($file['filename']) . '/';
822
					break;
823
				}
824
825
		if (!isset($context['base_path']))
826
			$context['base_path'] = '';
827
	}
828
	elseif (is_dir($packagesdir . '/' . $context['filename']))
829
	{
830
		copytree($packagesdir . '/' . $context['filename'], $packagesdir . '/temp');
831
		$context['extracted_files'] = listtree($packagesdir . '/temp');
832
		$context['base_path'] = '';
833
	}
834
	else
835
		fatal_lang_error('no_access', false);
836
837
	// Are we installing this into any custom themes?
838
	$custom_themes = array(1);
839
	$known_themes = explode(',', $modSettings['knownThemes']);
840
	if (!empty($_POST['custom_theme']))
841
	{
842
		foreach ($_POST['custom_theme'] as $tid)
843
			if (in_array($tid, $known_themes))
844
				$custom_themes[] = (int) $tid;
845
	}
846
847
	// Now load up the paths of the themes that we need to know about.
848
	$request = $smcFunc['db_query']('', '
849
		SELECT id_theme, variable, value
850
		FROM {db_prefix}themes
851
		WHERE id_theme IN ({array_int:custom_themes})
852
			AND variable IN ({string:name}, {string:theme_dir})',
853
		array(
854
			'custom_themes' => $custom_themes,
855
			'name' => 'name',
856
			'theme_dir' => 'theme_dir',
857
		)
858
	);
859
	$theme_paths = array();
860
	$themes_installed = array(1);
861
	while ($row = $smcFunc['db_fetch_assoc']($request))
862
		$theme_paths[$row['id_theme']][$row['variable']] = $row['value'];
863
	$smcFunc['db_free_result']($request);
864
865
	// Are there any theme copying that we want to take place?
866
	$context['theme_copies'] = array(
867
		'require-file' => array(),
868
		'require-dir' => array(),
869
	);
870
	if (!empty($_POST['theme_changes']))
871
	{
872
		foreach ($_POST['theme_changes'] as $change)
873
		{
874
			if (empty($change))
875
				continue;
876
			$theme_data = $smcFunc['json_decode'](base64_decode($change), true);
877
			if (empty($theme_data['type']))
878
				continue;
879
880
			$themes_installed[] = $theme_data['id'];
881
			$context['theme_copies'][$theme_data['type']][$theme_data['orig']][] = $theme_data['future'];
882
		}
883
	}
884
885
	// Get the package info...
886
	$packageInfo = getPackageInfo($context['filename']);
887
	if (!is_array($packageInfo))
888
		fatal_lang_error($packageInfo);
889
890
	$packageInfo['filename'] = $context['filename'];
891
892
	// Set the type of extraction...
893
	$context['extract_type'] = isset($packageInfo['type']) ? $packageInfo['type'] : 'modification';
894
895
	// Create a backup file to roll back to! (but if they do this more than once, don't run it a zillion times.)
896
	if (!empty($modSettings['package_make_full_backups']) && (!isset($_SESSION['last_backup_for']) || $_SESSION['last_backup_for'] != $context['filename'] . ($context['uninstalling'] ? '$$' : '$')))
897
	{
898
		$_SESSION['last_backup_for'] = $context['filename'] . ($context['uninstalling'] ? '$$' : '$');
899
		$result = package_create_backup(($context['uninstalling'] ? 'backup_' : 'before_') . strtok($context['filename'], '.'));
900
		if (!$result)
901
			fatal_lang_error('could_not_package_backup', false);
902
	}
903
904
	// The mod isn't installed.... unless proven otherwise.
905
	$context['is_installed'] = false;
906
907
	// Is it actually installed?
908
	$request = $smcFunc['db_query']('', '
909
		SELECT version, themes_installed, db_changes
910
		FROM {db_prefix}log_packages
911
		WHERE package_id = {string:current_package}
912
			AND install_state != {int:not_installed}
913
		ORDER BY time_installed DESC
914
		LIMIT 1',
915
		array(
916
			'not_installed'	=> 0,
917
			'current_package' => $packageInfo['id'],
918
		)
919
	);
920 View Code Duplication
	while ($row = $smcFunc['db_fetch_assoc']($request))
921
	{
922
		$old_themes = explode(',', $row['themes_installed']);
923
		$old_version = $row['version'];
924
		$db_changes = empty($row['db_changes']) ? array() : $smcFunc['json_decode']($row['db_changes'], true);
925
	}
926
	$smcFunc['db_free_result']($request);
927
928
	// Wait, it's not installed yet!
929
	// @todo Replace with a better error message!
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...
930
	if (!isset($old_version) && $context['uninstalling'])
931
	{
932
		deltree($packagesdir . '/temp');
933
		fatal_error('Hacker?', false);
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...
934
	}
935
	// Uninstalling?
936
	elseif ($context['uninstalling'])
937
	{
938
		$install_log = parsePackageInfo($packageInfo['xml'], false, 'uninstall');
939
940
		// Gadzooks!  There's no uninstaller at all!?
941
		if (empty($install_log))
942
			fatal_lang_error('package_uninstall_cannot', false);
943
944
		// They can only uninstall from what it was originally installed into.
945
		foreach ($theme_paths as $id => $data)
946
			if ($id != 1 && !in_array($id, $old_themes))
0 ignored issues
show
Bug introduced by
The variable $old_themes 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...
947
				unset($theme_paths[$id]);
948
	}
949 View Code Duplication
	elseif (isset($old_version) && $old_version != $packageInfo['version'])
950
	{
951
		// Look for an upgrade...
952
		$install_log = parsePackageInfo($packageInfo['xml'], false, 'upgrade', $old_version);
953
954
		// There was no upgrade....
955
		if (empty($install_log))
956
			$context['is_installed'] = true;
957
		else
958
		{
959
			// Upgrade previous themes only!
960
			foreach ($theme_paths as $id => $data)
961
				if ($id != 1 && !in_array($id, $old_themes))
962
					unset($theme_paths[$id]);
963
		}
964
	}
965
	elseif (isset($old_version) && $old_version == $packageInfo['version'])
966
		$context['is_installed'] = true;
967
968 View Code Duplication
	if (!isset($old_version) || $context['is_installed'])
969
		$install_log = parsePackageInfo($packageInfo['xml'], false, 'install');
970
971
	$context['install_finished'] = false;
972
973
	// @todo Make a log of any errors that occurred and output them?
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...
974
975
	if (!empty($install_log))
976
	{
977
		$failed_steps = array();
978
		$failed_count = 0;
979
980
		foreach ($install_log as $action)
981
		{
982
			$failed_count++;
983
984
			if ($action['type'] == 'modification' && !empty($action['filename']))
985
			{
986 View Code Duplication
				if ($action['boardmod'])
987
					$mod_actions = parseBoardMod(file_get_contents($packagesdir . '/temp/' . $context['base_path'] . $action['filename']), false, $action['reverse'], $theme_paths);
988
				else
989
					$mod_actions = parseModification(file_get_contents($packagesdir . '/temp/' . $context['base_path'] . $action['filename']), false, $action['reverse'], $theme_paths);
990
991
				// Any errors worth noting?
992
				foreach ($mod_actions as $key => $modAction)
993
				{
994
					if ($modAction['type'] == 'failure')
995
						$failed_steps[] = array(
996
							'file' => $modAction['filename'],
997
							'large_step' => $failed_count,
998
							'sub_step' => $key,
999
							'theme' => 1,
1000
						);
1001
1002
					// Gather the themes we installed into.
1003
					if (!empty($modAction['is_custom']))
1004
						$themes_installed[] = $modAction['is_custom'];
1005
				}
1006
			}
1007
			elseif ($action['type'] == 'code' && !empty($action['filename']))
1008
			{
1009
				// This is just here as reference for what is available.
1010
				global $txt, $boarddir, $sourcedir, $modSettings, $context, $settings, $forum_version, $smcFunc;
1011
1012
				// Now include the file and be done with it ;).
1013 View Code Duplication
				if (file_exists($packagesdir . '/temp/' . $context['base_path'] . $action['filename']))
1014
					require($packagesdir . '/temp/' . $context['base_path'] . $action['filename']);
1015
			}
1016 View Code Duplication
			elseif ($action['type'] == 'credits')
1017
			{
1018
				// Time to build the billboard
1019
				$credits_tag = array(
1020
					'url' => $action['url'],
1021
					'license' => $action['license'],
1022
					'licenseurl' => $action['licenseurl'],
1023
					'copyright' => $action['copyright'],
1024
					'title' => $action['title'],
1025
				);
1026
			}
1027
			elseif ($action['type'] == 'hook' && isset($action['hook'], $action['function']))
1028
			{
1029
				if ($action['reverse'])
1030
					remove_integration_function($action['hook'], $action['function'], true, $action['include_file'], $action['object']);
1031
				else
1032
					add_integration_function($action['hook'], $action['function'], true, $action['include_file'], $action['object']);
1033
			}
1034
			// Only do the database changes on uninstall if requested.
1035
			elseif ($action['type'] == 'database' && !empty($action['filename']) && (!$context['uninstalling'] || !empty($_POST['do_db_changes'])))
1036
			{
1037
				// These can also be there for database changes.
1038
				global $txt, $boarddir, $sourcedir, $modSettings, $context, $settings, $forum_version, $smcFunc;
1039
				global $db_package_log;
1040
1041
				// We'll likely want the package specific database functionality!
1042
				db_extend('packages');
1043
1044
				// Let the file work its magic ;)
1045 View Code Duplication
				if (file_exists($packagesdir . '/temp/' . $context['base_path'] . $action['filename']))
1046
					require($packagesdir . '/temp/' . $context['base_path'] . $action['filename']);
1047
			}
1048
			// Handle a redirect...
1049
			elseif ($action['type'] == 'redirect' && !empty($action['redirect_url']))
1050
			{
1051
				$context['redirect_url'] = $action['redirect_url'];
1052
				$context['redirect_text'] = !empty($action['filename']) && file_exists($packagesdir . '/temp/' . $context['base_path'] . $action['filename']) ? $smcFunc['htmlspecialchars'](file_get_contents($packagesdir . '/temp/' . $context['base_path'] . $action['filename'])) : ($context['uninstalling'] ? $txt['package_uninstall_done'] : $txt['package_installed_done']);
1053
				$context['redirect_timeout'] = $action['redirect_timeout'];
1054
				if (!empty($action['parse_bbc']))
1055
				{
1056
					require_once($sourcedir . '/Subs-Post.php');
1057
					$context['redirect_text'] = preg_replace('~\[[/]?html\]~i', '', $context['redirect_text']);
1058
					preparsecode($context['redirect_text']);
1059
					$context['redirect_text'] = parse_bbc($context['redirect_text']);
1060
				}
1061
1062
				// Parse out a couple of common urls.
1063
				$urls = array(
1064
					'$boardurl' => $boardurl,
1065
					'$scripturl' => $scripturl,
1066
					'$session_var' => $context['session_var'],
1067
					'$session_id' => $context['session_id'],
1068
				);
1069
1070
				$context['redirect_url'] = strtr($context['redirect_url'], $urls);
1071
			}
1072
		}
1073
1074
		package_flush_cache();
1075
1076
		// See if this is already installed, and change it's state as required.
1077
		$request = $smcFunc['db_query']('', '
1078
			SELECT package_id, install_state, db_changes
1079
			FROM {db_prefix}log_packages
1080
			WHERE install_state != {int:not_installed}
1081
				AND package_id = {string:current_package}
1082
				' . ($context['install_id'] ? ' AND id_install = {int:install_id} ' : '') . '
1083
			ORDER BY time_installed DESC
1084
			LIMIT 1',
1085
			array(
1086
				'not_installed' => 0,
1087
				'install_id' => $context['install_id'],
1088
				'current_package' => $packageInfo['id'],
1089
			)
1090
		);
1091
		$is_upgrade = false;
1092
		while ($row = $smcFunc['db_fetch_assoc']($request))
1093
		{
1094
			// Uninstalling?
1095
			if ($context['uninstalling'])
1096
			{
1097
				$smcFunc['db_query']('', '
1098
					UPDATE {db_prefix}log_packages
1099
					SET install_state = {int:not_installed}, member_removed = {string:member_name}, id_member_removed = {int:current_member},
1100
						time_removed = {int:current_time}
1101
					WHERE package_id = {string:package_id}
1102
						AND id_install = {int:install_id}',
1103
					array(
1104
						'current_member' => $user_info['id'],
1105
						'not_installed' => 0,
1106
						'current_time' => time(),
1107
						'package_id' => $row['package_id'],
1108
						'member_name' => $user_info['name'],
1109
						'install_id' => $context['install_id'],
1110
					)
1111
				);
1112
			}
1113
			// Otherwise must be an upgrade.
1114
			else
1115
			{
1116
				$is_upgrade = true;
1117
				$old_db_changes = empty($row['db_changes']) ? array() : $smcFunc['json_decode']($row['db_changes'], true);
1118
			}
1119
		}
1120
1121
		// Assuming we're not uninstalling, add the entry.
1122
		if (!$context['uninstalling'])
1123
		{
1124
			// Reload the settings table for mods that have altered them upon installation
1125
			reloadSettings();
1126
1127
			// Any db changes from older version?
1128
			if (!empty($old_db_changes))
1129
				$db_package_log = empty($db_package_log) ? $old_db_changes : array_merge($old_db_changes, $db_package_log);
1130
1131
			// If there are some database changes we might want to remove then filter them out.
1132
			if (!empty($db_package_log))
1133
			{
1134
				// We're really just checking for entries which are create table AND add columns (etc).
1135
				$tables = array();
1136
				/**
1137
				 * Table sorting function used in usort
1138
				 *
1139
				 * @param array $a
1140
				 * @param array $b
1141
				 * @return int
1142
				 */
1143
				function sort_table_first($a, $b)
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $a. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
Comprehensibility introduced by
Avoid variables with short names like $b. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
1144
				{
1145
					if ($a[0] == $b[0])
1146
						return 0;
1147
					return $a[0] == 'remove_table' ? -1 : 1;
1148
				}
1149
				usort($db_package_log, 'sort_table_first');
1150
				foreach ($db_package_log as $k => $log)
1151
				{
1152
					if ($log[0] == 'remove_table')
1153
						$tables[] = $log[1];
1154
					elseif (in_array($log[1], $tables))
1155
						unset($db_package_log[$k]);
1156
				}
1157
				$db_changes = $smcFunc['json_encode']($db_package_log);
1158
			}
1159
			else
1160
				$db_changes = '';
1161
1162
			// What themes did we actually install?
1163
			$themes_installed = array_unique($themes_installed);
1164
			$themes_installed = implode(',', $themes_installed);
1165
1166
			// What failed steps?
1167
			$failed_step_insert = $smcFunc['json_encode']($failed_steps);
1168
1169
			// Un-sanitize things before we insert them...
1170
			$keys = array('filename', 'name', 'id', 'version');
1171
			foreach ($keys as $key)
1172
			{
1173
				// Yay for variable variables...
1174
				${"package_$key"} = un_htmlspecialchars($packageInfo[$key]);
0 ignored issues
show
Coding Style Best Practice introduced by
As per coding-style, please use concatenation or sprintf for the variable $key instead of interpolation.

It is generally a best practice as it is often more readable to use concatenation instead of interpolation for variables inside strings.

// Instead of
$x = "foo $bar $baz";

// Better use either
$x = "foo " . $bar . " " . $baz;
$x = sprintf("foo %s %s", $bar, $baz);
Loading history...
1175
			}
1176
1177
			// Credits tag?
1178
			$credits_tag = (empty($credits_tag)) ? '' : $smcFunc['json_encode']($credits_tag);
1179
			$smcFunc['db_insert']('',
1180
				'{db_prefix}log_packages',
1181
				array(
1182
					'filename' => 'string', 'name' => 'string', 'package_id' => 'string', 'version' => 'string',
1183
					'id_member_installed' => 'int', 'member_installed' => 'string', 'time_installed' => 'int',
1184
					'install_state' => 'int', 'failed_steps' => 'string', 'themes_installed' => 'string',
1185
					'member_removed' => 'int', 'db_changes' => 'string', 'credits' => 'string',
1186
				),
1187
				array(
1188
					$package_filename, $package_name, $package_id, $package_version,
0 ignored issues
show
Bug introduced by
The variable $package_filename 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...
Bug introduced by
The variable $package_name 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...
Bug introduced by
The variable $package_id 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...
Bug introduced by
The variable $package_version 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...
1189
					$user_info['id'], $user_info['name'], time(),
1190
					$is_upgrade ? 2 : 1, $failed_step_insert, $themes_installed,
1191
					0, $db_changes, $credits_tag,
1192
				),
1193
				array('id_install')
1194
			);
1195
		}
1196
		$smcFunc['db_free_result']($request);
1197
1198
		$context['install_finished'] = true;
1199
	}
1200
1201
	// If there's database changes - and they want them removed - let's do it last!
1202
	if (!empty($db_changes) && !empty($_POST['do_db_changes']))
1203
	{
1204
		// We're gonna be needing the package db functions!
1205
		db_extend('packages');
1206
1207
		foreach ($db_changes as $change)
1208
		{
1209
			if ($change[0] == 'remove_table' && isset($change[1]))
1210
				$smcFunc['db_drop_table']($change[1]);
1211 View Code Duplication
			elseif ($change[0] == 'remove_column' && isset($change[2]))
1212
				$smcFunc['db_remove_column']($change[1], $change[2]);
1213 View Code Duplication
			elseif ($change[0] == 'remove_index' && isset($change[2]))
1214
				$smcFunc['db_remove_index']($change[1], $change[2]);
1215
		}
1216
	}
1217
1218
	// Clean house... get rid of the evidence ;).
1219
	if (file_exists($packagesdir . '/temp'))
1220
		deltree($packagesdir . '/temp');
1221
1222
	// Log what we just did.
1223
	logAction($context['uninstalling'] ? 'uninstall_package' : (!empty($is_upgrade) ? 'upgrade_package' : 'install_package'), array('package' => $smcFunc['htmlspecialchars']($packageInfo['name']), 'version' => $smcFunc['htmlspecialchars']($packageInfo['version'])), 'admin');
1224
1225
	// Just in case, let's clear the whole cache to avoid anything going up the swanny.
1226
	clean_cache();
1227
1228
	// Restore file permissions?
1229
	create_chmod_control(array(), array(), true);
1230
}
1231
1232
/**
1233
 * List the files in a package.
1234
 */
1235
function PackageList()
1236
{
1237
	global $txt, $scripturl, $context, $sourcedir, $packagesdir;
1238
1239
	require_once($sourcedir . '/Subs-Package.php');
1240
1241
	// No package?  Show him or her the door.
1242
	if (!isset($_REQUEST['package']) || $_REQUEST['package'] == '')
1243
		redirectexit('action=admin;area=packages');
1244
1245
	$context['linktree'][] = array(
1246
		'url' => $scripturl . '?action=admin;area=packages;sa=list;package=' . $_REQUEST['package'],
1247
		'name' => $txt['list_file']
1248
	);
1249
	$context['page_title'] .= ' - ' . $txt['list_file'];
1250
	$context['sub_template'] = 'list';
1251
1252
	// The filename...
1253
	$context['filename'] = $_REQUEST['package'];
1254
1255
	// Let the unpacker do the work.
1256
	if (is_file($packagesdir . '/' . $context['filename']))
1257
		$context['files'] = read_tgz_file($packagesdir . '/' . $context['filename'], null);
1258
	elseif (is_dir($packagesdir . '/' . $context['filename']))
1259
		$context['files'] = listtree($packagesdir . '/' . $context['filename']);
1260
}
1261
1262
/**
1263
 * Display one of the files in a package.
1264
 */
1265
function ExamineFile()
1266
{
1267
	global $txt, $scripturl, $context, $sourcedir, $packagesdir, $smcFunc;
1268
1269
	require_once($sourcedir . '/Subs-Package.php');
1270
1271
	// No package?  Show him or her the door.
1272
	if (!isset($_REQUEST['package']) || $_REQUEST['package'] == '')
1273
		redirectexit('action=admin;area=packages');
1274
1275
	// No file?  Show him or her the door.
1276
	if (!isset($_REQUEST['file']) || $_REQUEST['file'] == '')
1277
		redirectexit('action=admin;area=packages');
1278
1279
	$_REQUEST['package'] = preg_replace('~[\.]+~', '.', strtr($_REQUEST['package'], array('/' => '_', '\\' => '_')));
1280
	$_REQUEST['file'] = preg_replace('~[\.]+~', '.', $_REQUEST['file']);
1281
1282
	if (isset($_REQUEST['raw']))
1283
	{
1284
		if (is_file($packagesdir . '/' . $_REQUEST['package']))
1285
			echo read_tgz_file($packagesdir . '/' . $_REQUEST['package'], $_REQUEST['file'], true);
1286
		elseif (is_dir($packagesdir . '/' . $_REQUEST['package']))
1287
			echo file_get_contents($packagesdir . '/' . $_REQUEST['package'] . '/' . $_REQUEST['file']);
1288
1289
		obExit(false);
1290
	}
1291
1292
	$context['linktree'][count($context['linktree']) - 1] = array(
1293
		'url' => $scripturl . '?action=admin;area=packages;sa=list;package=' . $_REQUEST['package'],
1294
		'name' => $txt['package_examine_file']
1295
	);
1296
	$context['page_title'] .= ' - ' . $txt['package_examine_file'];
1297
	$context['sub_template'] = 'examine';
1298
1299
	// The filename...
1300
	$context['package'] = $_REQUEST['package'];
1301
	$context['filename'] = $_REQUEST['file'];
1302
1303
	// Let the unpacker do the work.... but make sure we handle images properly.
1304
	if (in_array(strtolower(strrchr($_REQUEST['file'], '.')), array('.bmp', '.gif', '.jpeg', '.jpg', '.png')))
1305
		$context['filedata'] = '<img src="' . $scripturl . '?action=admin;area=packages;sa=examine;package=' . $_REQUEST['package'] . ';file=' . $_REQUEST['file'] . ';raw" alt="' . $_REQUEST['file'] . '">';
1306
	else
1307
	{
1308
		if (is_file($packagesdir . '/' . $_REQUEST['package']))
1309
			$context['filedata'] = $smcFunc['htmlspecialchars'](read_tgz_file($packagesdir . '/' . $_REQUEST['package'], $_REQUEST['file'], true));
1310
		elseif (is_dir($packagesdir . '/' . $_REQUEST['package']))
1311
			$context['filedata'] = $smcFunc['htmlspecialchars'](file_get_contents($packagesdir . '/' . $_REQUEST['package'] . '/' . $_REQUEST['file']));
1312
1313
		if (strtolower(strrchr($_REQUEST['file'], '.')) == '.php')
1314
			$context['filedata'] = highlight_php_code($context['filedata']);
1315
	}
1316
}
1317
1318
/**
1319
 * Delete a package.
1320
 */
1321
function PackageRemove()
1322
{
1323
	global $scripturl, $packagesdir;
1324
1325
	// Check it.
1326
	checkSession('get');
1327
1328
	// Ack, don't allow deletion of arbitrary files here, could become a security hole somehow!
1329
	if (!isset($_GET['package']) || $_GET['package'] == 'index.php' || $_GET['package'] == 'backups')
1330
		redirectexit('action=admin;area=packages;sa=browse');
1331
	$_GET['package'] = preg_replace('~[\.]+~', '.', strtr($_GET['package'], array('/' => '_', '\\' => '_')));
1332
1333
	// Can't delete what's not there.
1334
	if (file_exists($packagesdir . '/' . $_GET['package']) && (substr($_GET['package'], -4) == '.zip' || substr($_GET['package'], -4) == '.tgz' || substr($_GET['package'], -7) == '.tar.gz' || is_dir($packagesdir . '/' . $_GET['package'])) && $_GET['package'] != 'backups' && substr($_GET['package'], 0, 1) != '.')
1335
	{
1336
		create_chmod_control(array($packagesdir . '/' . $_GET['package']), array('destination_url' => $scripturl . '?action=admin;area=packages;sa=remove;package=' . $_GET['package'], 'crash_on_error' => true));
1337
1338
		if (is_dir($packagesdir . '/' . $_GET['package']))
1339
			deltree($packagesdir . '/' . $_GET['package']);
1340
		else
1341
		{
1342
			smf_chmod($packagesdir . '/' . $_GET['package'], 0777);
1343
			unlink($packagesdir . '/' . $_GET['package']);
1344
		}
1345
	}
1346
1347
	redirectexit('action=admin;area=packages;sa=browse');
1348
}
1349
1350
/**
1351
 * Browse a list of installed packages.
1352
 */
1353
function PackageBrowse()
1354
{
1355
	global $txt, $scripturl, $context, $forum_version, $sourcedir, $smcFunc;
1356
1357
	$context['page_title'] .= ' - ' . $txt['browse_packages'];
1358
1359
	$context['forum_version'] = $forum_version;
1360
	$context['modification_types'] = array('modification', 'avatar', 'language', 'unknown');
1361
1362
	call_integration_hook('integrate_modification_types');
1363
1364
	require_once($sourcedir . '/Subs-List.php');
1365
1366
	foreach ($context['modification_types'] as $type)
1367
	{
1368
		// Use the standard templates for showing this.
1369
		$listOptions = array(
1370
			'id' => 'packages_lists_' . $type,
1371
			'title' => $txt[$type . '_package'],
1372
			'no_items_label' => $txt['no_packages'],
1373
			'get_items' => array(
1374
				'function' => 'list_getPackages',
1375
				'params' => array('type' => $type),
1376
			),
1377
			'base_href' => $scripturl . '?action=admin;area=packages;sa=browse;type=' . $type,
1378
			'default_sort_col' => 'id' . $type,
1379
			'columns' => array(
1380
				'id' . $type => array(
1381
					'header' => array(
1382
						'value' => $txt['package_id'],
1383
						'style' => 'width: 40px;',
1384
					),
1385
					'data' => array(
1386 View Code Duplication
						'function' => function($package_md5) use ($type, &$context)
1387
						{
1388
							if (isset($context['available_' . $type . ''][$package_md5]))
1389
								return $context['available_' . $type . ''][$package_md5]['sort_id'];
1390
						},
1391
					),
1392
					'sort' => array(
1393
						'default' => 'sort_id',
1394
						'reverse' => 'sort_id'
1395
					),
1396
				),
1397
				'mod_name' . $type => array(
1398
					'header' => array(
1399
						'value' => $txt['mod_name'],
1400
						'style' => 'width: 25%;',
1401
					),
1402
					'data' => array(
1403 View Code Duplication
						'function' => function($package_md5) use ($type, &$context)
1404
						{
1405
							if (isset($context['available_' . $type . ''][$package_md5]))
1406
								return $context['available_' . $type . ''][$package_md5]['name'];
1407
						},
1408
					),
1409
					'sort' => array(
1410
						'default' => 'name',
1411
						'reverse' => 'name',
1412
					),
1413
				),
1414
				'version' . $type => array(
1415
					'header' => array(
1416
						'value' => $txt['mod_version'],
1417
					),
1418
					'data' => array(
1419 View Code Duplication
						'function' => function($package_md5) use ($type, &$context)
1420
						{
1421
							if (isset($context['available_' . $type . ''][$package_md5]))
1422
								return $context['available_' . $type . ''][$package_md5]['version'];
1423
						},
1424
					),
1425
					'sort' => array(
1426
						'default' => 'version',
1427
						'reverse' => 'version',
1428
					),
1429
				),
1430
				'time_installed' . $type => array(
1431
					'header' => array(
1432
						'value' => $txt['mod_installed_time'],
1433
					),
1434
					'data' => array(
1435
						'function' => function($package_md5) use ($type, $txt, &$context)
1436
						{
1437
							if (isset($context['available_' . $type . ''][$package_md5]))
1438
								return !empty($context['available_' . $type . ''][$package_md5]['time_installed']) ? timeformat($context['available_' . $type . ''][$package_md5]['time_installed']) : $txt['not_applicable'];
1439
						},
1440
						'class' => 'smalltext',
1441
					),
1442
					'sort' => array(
1443
						'default' => 'time_installed',
1444
						'reverse' => 'time_installed',
1445
					),
1446
				),
1447
				'operations' . $type => array(
1448
					'header' => array(
1449
						'value' => '',
1450
					),
1451
					'data' => array(
1452
						'function' => function($package_md5) use ($type, &$context, $scripturl, $txt)
1453
						{
1454
							if (!isset($context['available_' . $type . ''][$package_md5]))
1455
								return '';
1456
1457
							// Rewrite shortcut
1458
							$package = $context['available_' . $type . ''][$package_md5];
1459
							$return = '';
1460
1461
							if ($package['can_uninstall'])
1462
								$return = '
1463
									<a href="' . $scripturl . '?action=admin;area=packages;sa=uninstall;package=' . $package['filename'] . ';pid=' . $package['installed_id'] . '" class="button">' . $txt['uninstall'] . '</a>';
1464
							elseif ($package['can_emulate_uninstall'])
1465
								$return = '
1466
									<a href="' . $scripturl . '?action=admin;area=packages;sa=uninstall;ve=' . $package['can_emulate_uninstall'] . ';package=' . $package['filename'] . ';pid=' . $package['installed_id'] . '" class="button">' . $txt['package_emulate_uninstall'] . ' ' . $package['can_emulate_uninstall'] . '</a>';
1467 View Code Duplication
							elseif ($package['can_upgrade'])
1468
								$return = '
1469
									<a href="' . $scripturl . '?action=admin;area=packages;sa=install;package=' . $package['filename'] . '" class="button">' . $txt['package_upgrade'] . '</a>';
1470 View Code Duplication
							elseif ($package['can_install'])
1471
								$return = '
1472
									<a href="' . $scripturl . '?action=admin;area=packages;sa=install;package=' . $package['filename'] . '" class="button">' . $txt['install_mod'] . '</a>';
1473
							elseif ($package['can_emulate_install'])
1474
								$return = '
1475
									<a href="' . $scripturl . '?action=admin;area=packages;sa=install;ve=' . $package['can_emulate_install'] . ';package=' . $package['filename'] . '" class="button">' . $txt['package_emulate_install'] . ' ' . $package['can_emulate_install'] . '</a>';
1476
1477
							return $return . '
1478
									<a href="' . $scripturl . '?action=admin;area=packages;sa=list;package=' . $package['filename'] . '" class="button">' . $txt['list_files'] . '</a>
1479
									<a href="' . $scripturl . '?action=admin;area=packages;sa=remove;package=' . $package['filename'] . ';' . $context['session_var'] . '=' . $context['session_id'] . '"' . ($package['is_installed'] && $package['is_current'] ? ' data-confirm="' . $txt['package_delete_bad'] . '"' : '') . ' class="button' . ($package['is_installed'] && $package['is_current'] ? ' you_sure' : '') . '">' . $txt['package_delete'] . '</a>';
1480
						},
1481
						'class' => 'righttext',
1482
					),
1483
				),
1484
			),
1485
		);
1486
1487
		createList($listOptions);
1488
	}
1489
1490
	$context['sub_template'] = 'browse';
1491
	$context['default_list'] = 'packages_lists';
1492
1493
	// Empty lists for now.
1494
	$context['available_mods'] = array();
1495
	$context['available_avatars'] = array();
1496
	$context['available_languages'] = array();
1497
	$context['available_other'] = array();
1498
	$context['available_all'] = array();
1499
1500
	$get_versions = $smcFunc['db_query']('', '
1501
		SELECT data FROM {db_prefix}admin_info_files WHERE filename={string:versionsfile} AND path={string:smf}',
1502
		array(
1503
			'versionsfile' => 'latest-versions.txt',
1504
			'smf' => '/smf/',
1505
		)
1506
	);
1507
1508
	$data = $smcFunc['db_fetch_assoc']($get_versions);
1509
	$smcFunc['db_free_result']($get_versions);
1510
1511
	// Decode the data.
1512
	$items = $smcFunc['json_decode']($data['data'], true);
1513
1514
	$context['emulation_versions'] = preg_replace('~^SMF ~', '', $items);
1515
1516
	// Current SMF version, which is selected by default
1517
	$context['default_version'] = preg_replace('~^SMF ~', '', $forum_version);
1518
1519
	// Version we're currently emulating, if any
1520
	$context['selected_version'] = preg_replace('~^SMF ~', '', $context['forum_version']);
1521
}
1522
1523
/**
1524
 * Get a listing of all the packages
1525
 * Determines if the package is a mod, avatar, language package
1526
 * Determines if the package has been installed or not
1527
 *
1528
 * @param int $start The item to start with (not used here)
1529
 * @param int $items_per_page The number of items to show per page (not used here)
1530
 * @param string $sort A string indicating how to sort the results
1531
 * @param string? $params A key for the $packages array
0 ignored issues
show
Documentation introduced by
The doc-type string? could not be parsed: Unknown type name "string?" at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
1532
 * @return array An array of information about the packages
1533
 */
1534
function list_getPackages($start, $items_per_page, $sort, $params)
0 ignored issues
show
Unused Code introduced by
The parameter $start is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $items_per_page is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
1535
{
1536
	global $scripturl, $packagesdir, $context, $forum_version;
1537
	static $packages, $installed_mods;
1538
1539
	// Start things up
1540
	if (!isset($packages[$params]))
1541
		$packages[$params] = array();
1542
1543
	// We need the packages directory to be writable for this.
1544
	if (!@is_writable($packagesdir))
1545
		create_chmod_control(array($packagesdir), array('destination_url' => $scripturl . '?action=admin;area=packages', 'crash_on_error' => true));
1546
1547
	$the_version = strtr($forum_version, array('SMF ' => ''));
1548
1549
	// Here we have a little code to help those who class themselves as something of gods, version emulation ;)
1550
	if (isset($_GET['version_emulate']) && strtr($_GET['version_emulate'], array('SMF ' => '')) == $the_version)
1551
	{
1552
		unset($_SESSION['version_emulate']);
1553
	}
1554
	elseif (isset($_GET['version_emulate']))
1555
	{
1556
		if (($_GET['version_emulate'] === 0 || $_GET['version_emulate'] === $forum_version) && isset($_SESSION['version_emulate']))
1557
			unset($_SESSION['version_emulate']);
1558
		elseif ($_GET['version_emulate'] !== 0)
1559
			$_SESSION['version_emulate'] = strtr($_GET['version_emulate'], array('-' => ' ', '+' => ' ', 'SMF ' => ''));
1560
	}
1561
	if (!empty($_SESSION['version_emulate']))
1562
	{
1563
		$context['forum_version'] = 'SMF ' . $_SESSION['version_emulate'];
1564
		$the_version = $_SESSION['version_emulate'];
1565
	}
1566
	if (isset($_SESSION['single_version_emulate']))
1567
		unset($_SESSION['single_version_emulate']);
1568
1569
	if (empty($installed_mods))
1570
	{
1571
		$instmods = loadInstalledPackages();
1572
		$installed_mods = array();
1573
		// Look through the list of installed mods...
1574
		foreach ($instmods as $installed_mod)
1575
			$installed_mods[$installed_mod['package_id']] = array(
1576
				'id' => $installed_mod['id'],
1577
				'version' => $installed_mod['version'],
1578
				'time_installed' => $installed_mod['time_installed'],
1579
			);
1580
1581
		// Get a list of all the ids installed, so the latest packages won't include already installed ones.
1582
		$context['installed_mods'] = array_keys($installed_mods);
1583
	}
1584
1585
	if (empty($packages))
1586
		foreach ($context['modification_types'] as $type)
0 ignored issues
show
Bug introduced by
The expression $context['modification_types'] of type string|array<integer,integer|string> is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
1587
			$packages[$type] = array();
1588
1589
	if ($dir = @opendir($packagesdir))
1590
	{
1591
		$dirs = array();
1592
		$sort_id = array(
1593
			'mod' => 1,
1594
			'modification' => 1,
1595
			'avatar' => 1,
1596
			'language' => 1,
1597
			'unknown' => 1,
1598
		);
1599
		call_integration_hook('integrate_packages_sort_id', array(&$sort_id, &$packages));
1600
1601
		while ($package = readdir($dir))
1602
		{
1603 View Code Duplication
			if ($package == '.' || $package == '..' || $package == 'temp' || (!(is_dir($packagesdir . '/' . $package) && file_exists($packagesdir . '/' . $package . '/package-info.xml')) && substr(strtolower($package), -7) != '.tar.gz' && substr(strtolower($package), -4) != '.tgz' && substr(strtolower($package), -4) != '.zip'))
1604
				continue;
1605
1606
			$skip = false;
1607
			foreach ($context['modification_types'] as $type)
0 ignored issues
show
Bug introduced by
The expression $context['modification_types'] of type string|array<integer,integer|string> is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
1608
				if (isset($context['available_' . $type][md5($package)]))
1609
					$skip = true;
1610
1611
			if ($skip)
1612
				continue;
1613
1614
			// Skip directories or files that are named the same.
1615
			if (is_dir($packagesdir . '/' . $package))
1616
			{
1617
				if (in_array($package, $dirs))
1618
					continue;
1619
				$dirs[] = $package;
1620
			}
1621
			elseif (substr(strtolower($package), -7) == '.tar.gz')
1622
			{
1623
				if (in_array(substr($package, 0, -7), $dirs))
1624
					continue;
1625
				$dirs[] = substr($package, 0, -7);
1626
			}
1627
			elseif (substr(strtolower($package), -4) == '.zip' || substr(strtolower($package), -4) == '.tgz')
1628
			{
1629
				if (in_array(substr($package, 0, -4), $dirs))
1630
					continue;
1631
				$dirs[] = substr($package, 0, -4);
1632
			}
1633
1634
			$packageInfo = getPackageInfo($package);
1635
			if (!is_array($packageInfo))
1636
				continue;
1637
1638
			if (!empty($packageInfo))
1639
			{
1640
				$packageInfo['installed_id'] = isset($installed_mods[$packageInfo['id']]) ? $installed_mods[$packageInfo['id']]['id'] : 0;
1641
				$packageInfo['time_installed'] = isset($installed_mods[$packageInfo['id']]) ? $installed_mods[$packageInfo['id']]['time_installed'] : 0;
1642
1643
				if (!isset($sort_id[$packageInfo['type']]))
1644
					$packageInfo['sort_id'] = $sort_id['unknown'];
1645
				else
1646
					$packageInfo['sort_id'] = $sort_id[$packageInfo['type']];
1647
1648
				$packageInfo['is_installed'] = isset($installed_mods[$packageInfo['id']]);
1649
				$packageInfo['is_current'] = $packageInfo['is_installed'] && ($installed_mods[$packageInfo['id']]['version'] == $packageInfo['version']);
1650
				$packageInfo['is_newer'] = $packageInfo['is_installed'] && ($installed_mods[$packageInfo['id']]['version'] > $packageInfo['version']);
1651
1652
				$packageInfo['can_install'] = false;
1653
				$packageInfo['can_uninstall'] = false;
1654
				$packageInfo['can_upgrade'] = false;
1655
				$packageInfo['can_emulate_install'] = false;
1656
				$packageInfo['can_emulate_uninstall'] = false;
1657
1658
				// This package is currently NOT installed.  Check if it can be.
1659
				if (!$packageInfo['is_installed'] && $packageInfo['xml']->exists('install'))
1660
				{
1661
					// Check if there's an install for *THIS* version of SMF.
1662
					$installs = $packageInfo['xml']->set('install');
1663 View Code Duplication
					foreach ($installs as $install)
1664
					{
1665
						if (!$install->exists('@for') || matchPackageVersion($the_version, $install->fetch('@for')))
1666
						{
1667
							// Okay, this one is good to go.
1668
							$packageInfo['can_install'] = true;
1669
							break;
1670
						}
1671
					}
1672
1673
					// no install found for this version, lets see if one exists for another
1674 View Code Duplication
					if ($packageInfo['can_install'] === false && $install->exists('@for') && empty($_SESSION['version_emulate']))
0 ignored issues
show
Bug introduced by
The variable $install 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...
1675
					{
1676
						$reset = true;
1677
1678
						// Get the highest install version that is available from the package
1679
						foreach ($installs as $install)
1680
						{
1681
							$packageInfo['can_emulate_install'] = matchHighestPackageVersion($install->fetch('@for'), $reset, $the_version);
1682
							$reset = false;
1683
						}
1684
					}
1685
				}
1686
				// An already installed, but old, package.  Can we upgrade it?
1687
				elseif ($packageInfo['is_installed'] && !$packageInfo['is_current'] && $packageInfo['xml']->exists('upgrade'))
1688
				{
1689
					$upgrades = $packageInfo['xml']->set('upgrade');
1690
1691
					// First go through, and check against the current version of SMF.
1692
					foreach ($upgrades as $upgrade)
1693
					{
1694
						// Even if it is for this SMF, is it for the installed version of the mod?
1695
						if (!$upgrade->exists('@for') || matchPackageVersion($the_version, $upgrade->fetch('@for')))
1696
							if (!$upgrade->exists('@from') || matchPackageVersion($installed_mods[$packageInfo['id']]['version'], $upgrade->fetch('@from')))
1697
							{
1698
								$packageInfo['can_upgrade'] = true;
1699
								break;
1700
							}
1701
					}
1702
				}
1703
				// Note that it has to be the current version to be uninstallable.  Shucks.
1704
				elseif ($packageInfo['is_installed'] && $packageInfo['is_current'] && $packageInfo['xml']->exists('uninstall'))
1705
				{
1706
					$uninstalls = $packageInfo['xml']->set('uninstall');
1707
1708
					// Can we find any uninstallation methods that work for this SMF version?
1709 View Code Duplication
					foreach ($uninstalls as $uninstall)
1710
					{
1711
						if (!$uninstall->exists('@for') || matchPackageVersion($the_version, $uninstall->fetch('@for')))
1712
						{
1713
							$packageInfo['can_uninstall'] = true;
1714
							break;
1715
						}
1716
					}
1717
1718
					// no uninstall found for this version, lets see if one exists for another
1719 View Code Duplication
					if ($packageInfo['can_uninstall'] === false && $uninstall->exists('@for') && empty($_SESSION['version_emulate']))
0 ignored issues
show
Bug introduced by
The variable $uninstall 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...
1720
					{
1721
						$reset = true;
1722
1723
						// Get the highest install version that is available from the package
1724
						foreach ($uninstalls as $uninstall)
1725
						{
1726
							$packageInfo['can_emulate_uninstall'] = matchHighestPackageVersion($uninstall->fetch('@for'), $reset, $the_version);
1727
							$reset = false;
1728
						}
1729
					}
1730
				}
1731
1732
				// Modification.
1733
				if ($packageInfo['type'] == 'modification' || $packageInfo['type'] == 'mod')
1734
				{
1735
					$sort_id['modification']++;
1736
					$sort_id['mod']++;
1737
					$packages['modification'][strtolower($packageInfo[$sort]) . '_' . $sort_id['mod']] = md5($package);
1738
					$context['available_modification'][md5($package)] = $packageInfo;
1739
				}
1740
				// Avatar package.
1741 View Code Duplication
				elseif ($packageInfo['type'] == 'avatar')
1742
				{
1743
					$sort_id[$packageInfo['type']]++;
1744
					$packages['avatar'][strtolower($packageInfo[$sort])] = md5($package);
1745
					$context['available_avatar'][md5($package)] = $packageInfo;
1746
				}
1747
				// Language package.
1748 View Code Duplication
				elseif ($packageInfo['type'] == 'language')
1749
				{
1750
					$sort_id[$packageInfo['type']]++;
1751
					$packages['language'][strtolower($packageInfo[$sort])] = md5($package);
1752
					$context['available_language'][md5($package)] = $packageInfo;
1753
				}
1754
				// This might be a 3rd party section.
1755
				elseif (isset($sort_id[$packageInfo['type']], $packages[$packageInfo['type']], $context['available_' . $packageInfo['type']]))
1756
				{
1757
					$sort_id[$packageInfo['type']]++;
1758
					$packages[$packageInfo['type']][strtolower($packageInfo[$sort])] = md5($package);
1759
					$context['available_' . $packageInfo['type']][md5($package)] = $packageInfo;
1760
				}
1761
				// Other stuff.
1762
				else
1763
				{
1764
					$sort_id['unknown']++;
1765
					$packages['unknown'][strtolower($packageInfo[$sort])] = md5($package);
1766
					$context['available_unknown'][md5($package)] = $packageInfo;
1767
				}
1768
			}
1769
		}
1770
		closedir($dir);
1771
	}
1772
1773
	if (isset($_GET['type']) && $_GET['type'] == $params)
1774
	{
1775
		if (isset($_GET['desc']))
1776
			krsort($packages[$params]);
1777
		else
1778
			ksort($packages[$params]);
1779
	}
1780
1781
	return $packages[$params];
1782
}
1783
1784
/**
1785
 * Used when a temp FTP access is needed to package functions
1786
 */
1787
function PackageOptions()
1788
{
1789
	global $txt, $context, $modSettings, $smcFunc;
1790
1791
	if (isset($_POST['save']))
1792
	{
1793
		checkSession();
1794
1795
		updateSettings(array(
1796
			'package_server' => trim($smcFunc['htmlspecialchars']($_POST['pack_server'])),
1797
			'package_port' => trim($smcFunc['htmlspecialchars']($_POST['pack_port'])),
1798
			'package_username' => trim($smcFunc['htmlspecialchars']($_POST['pack_user'])),
1799
			'package_make_backups' => !empty($_POST['package_make_backups']),
1800
			'package_make_full_backups' => !empty($_POST['package_make_full_backups'])
1801
		));
1802
		$_SESSION['adm-save'] = true;
1803
1804
		redirectexit('action=admin;area=packages;sa=options');
1805
	}
1806
1807
	if (preg_match('~^/home\d*/([^/]+?)/public_html~', $_SERVER['DOCUMENT_ROOT'], $match))
1808
		$default_username = $match[1];
1809
	else
1810
		$default_username = '';
1811
1812
	$context['page_title'] = $txt['package_settings'];
1813
	$context['sub_template'] = 'install_options';
1814
1815
	$context['package_ftp_server'] = isset($modSettings['package_server']) ? $modSettings['package_server'] : 'localhost';
1816
	$context['package_ftp_port'] = isset($modSettings['package_port']) ? $modSettings['package_port'] : '21';
1817
	$context['package_ftp_username'] = isset($modSettings['package_username']) ? $modSettings['package_username'] : $default_username;
1818
	$context['package_make_backups'] = !empty($modSettings['package_make_backups']);
1819
	$context['package_make_full_backups'] = !empty($modSettings['package_make_full_backups']);
1820
1821
	if (!empty($_SESSION['adm-save']))
1822
	{
1823
		$context['saved_successful'] = true;
1824
		unset ($_SESSION['adm-save']);
1825
	}
1826
}
1827
1828
/**
1829
 * List operations
1830
 */
1831
function ViewOperations()
1832
{
1833
	global $context, $txt, $sourcedir, $packagesdir, $smcFunc, $modSettings;
1834
1835
	// Can't be in here buddy.
1836
	isAllowedTo('admin_forum');
1837
1838
	// We need to know the operation key for the search and replace, mod file looking at, is it a board mod?
1839
	if (!isset($_REQUEST['operation_key'], $_REQUEST['filename']) && !is_numeric($_REQUEST['operation_key']))
1840
		fatal_lang_error('operation_invalid', 'general');
1841
1842
	// Load the required file.
1843
	require_once($sourcedir . '/Subs-Package.php');
1844
1845
	// Uninstalling the mod?
1846
	$reverse = isset($_REQUEST['reverse']) ? true : false;
1847
1848
	// Get the base name.
1849
	$context['filename'] = preg_replace('~[\.]+~', '.', $_REQUEST['package']);
1850
1851
	// We need to extract this again.
1852 View Code Duplication
	if (is_file($packagesdir . '/' . $context['filename']))
1853
	{
1854
		$context['extracted_files'] = read_tgz_file($packagesdir . '/' . $context['filename'], $packagesdir . '/temp');
1855
1856
		if ($context['extracted_files'] && !file_exists($packagesdir . '/temp/package-info.xml'))
1857
			foreach ($context['extracted_files'] as $file)
1858
				if (basename($file['filename']) == 'package-info.xml')
1859
				{
1860
					$context['base_path'] = dirname($file['filename']) . '/';
1861
					break;
1862
				}
1863
1864
		if (!isset($context['base_path']))
1865
			$context['base_path'] = '';
1866
	}
1867
	elseif (is_dir($packagesdir . '/' . $context['filename']))
1868
	{
1869
		copytree($packagesdir . '/' . $context['filename'], $packagesdir . '/temp');
1870
		$context['extracted_files'] = listtree($packagesdir . '/temp');
1871
		$context['base_path'] = '';
1872
	}
1873
1874
	// Load up any custom themes we may want to install into...
1875
	$request = $smcFunc['db_query']('', '
1876
		SELECT id_theme, variable, value
1877
		FROM {db_prefix}themes
1878
		WHERE (id_theme = {int:default_theme} OR id_theme IN ({array_int:known_theme_list}))
1879
			AND variable IN ({string:name}, {string:theme_dir})',
1880
		array(
1881
			'known_theme_list' => explode(',', $modSettings['knownThemes']),
1882
			'default_theme' => 1,
1883
			'name' => 'name',
1884
			'theme_dir' => 'theme_dir',
1885
		)
1886
	);
1887
	$theme_paths = array();
1888
	while ($row = $smcFunc['db_fetch_assoc']($request))
1889
		$theme_paths[$row['id_theme']][$row['variable']] = $row['value'];
1890
	$smcFunc['db_free_result']($request);
1891
1892
	// If we're viewing uninstall operations, only consider themes that
1893
	// the package is actually installed into.
1894
	if (isset($_REQUEST['reverse']) && !empty($_REQUEST['install_id']))
1895
	{
1896
		$install_id = (int) $_REQUEST['install_id'];
1897
		if ($install_id > 0)
1898
		{
1899
			$old_themes = array();
1900
			$request = $smcFunc['db_query']('', '
1901
				SELECT themes_installed
1902
				FROM {db_prefix}log_packages
1903
				WHERE id_install = {int:install_id}',
1904
				array(
1905
					'install_id' => $install_id,
1906
				)
1907
			);
1908
1909
			if ($smcFunc['db_num_rows']($request) == 1)
1910
			{
1911
				list ($old_themes) = $smcFunc['db_fetch_row']($request);
1912
				$old_themes = explode(',', $old_themes);
1913
1914
				foreach ($theme_paths as $id => $data)
1915
					if ($id != 1 && !in_array($id, $old_themes))
1916
						unset($theme_paths[$id]);
1917
			}
1918
			$smcFunc['db_free_result']($request);
1919
		}
1920
	}
1921
1922
	// Boardmod?
1923 View Code Duplication
	if (isset($_REQUEST['boardmod']))
1924
		$mod_actions = parseBoardMod(@file_get_contents($packagesdir . '/temp/' . $context['base_path'] . $_REQUEST['filename']), true, $reverse, $theme_paths);
1925
	else
1926
		$mod_actions = parseModification(@file_get_contents($packagesdir . '/temp/' . $context['base_path'] . $_REQUEST['filename']), true, $reverse, $theme_paths);
1927
1928
	// Ok lets get the content of the file.
1929
	$context['operations'] = array(
1930
		'search' => strtr($smcFunc['htmlspecialchars']($mod_actions[$_REQUEST['operation_key']]['search_original']), array('[' => '&#91;', ']' => '&#93;')),
1931
		'replace' => strtr($smcFunc['htmlspecialchars']($mod_actions[$_REQUEST['operation_key']]['replace_original']), array('[' => '&#91;', ']' => '&#93;')),
1932
		'position' => $mod_actions[$_REQUEST['operation_key']]['position'],
1933
	);
1934
1935
	// Let's do some formatting...
1936
	$operation_text = $context['operations']['position'] == 'replace' ? 'operation_replace' : ($context['operations']['position'] == 'before' ? 'operation_after' : 'operation_before');
1937
	$context['operations']['search'] = parse_bbc('[code=' . $txt['operation_find'] . ']' . ($context['operations']['position'] == 'end' ? '?&gt;' : $context['operations']['search']) . '[/code]');
1938
	$context['operations']['replace'] = parse_bbc('[code=' . $txt[$operation_text] . ']' . $context['operations']['replace'] . '[/code]');
1939
1940
	// No layers
1941
	$context['template_layers'] = array();
1942
	$context['sub_template'] = 'view_operations';
1943
}
1944
1945
/**
1946
 * Allow the admin to reset permissions on files.
1947
 */
1948
function PackagePermissions()
1949
{
1950
	global $context, $txt, $modSettings, $boarddir, $sourcedir, $cachedir, $smcFunc, $package_ftp;
1951
1952
	// Let's try and be good, yes?
1953
	checkSession('get');
1954
1955
	// If we're restoring permissions this is just a pass through really.
1956
	if (isset($_GET['restore']))
1957
	{
1958
		create_chmod_control(array(), array(), true);
1959
		fatal_lang_error('no_access', false);
1960
	}
1961
1962
	// This is a memory eat.
1963
	setMemoryLimit('128M');
1964
	@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...
1965
1966
	// Load up some FTP stuff.
1967
	create_chmod_control();
1968
1969
	if (empty($package_ftp) && !isset($_POST['skip_ftp']))
1970
	{
1971
		require_once($sourcedir . '/Class-Package.php');
1972
		$ftp = new ftp_connection(null);
1973
		list ($username, $detect_path, $found_path) = $ftp->detect_path($boarddir);
0 ignored issues
show
Unused Code introduced by
The assignment to $found_path is unused. Consider omitting it like so list($first,,$third).

This checks looks for assignemnts to variables using the list(...) function, where not all assigned variables are subsequently used.

Consider the following code example.

<?php

function returnThreeValues() {
    return array('a', 'b', 'c');
}

list($a, $b, $c) = returnThreeValues();

print $a . " - " . $c;

Only the variables $a and $c are used. There was no need to assign $b.

Instead, the list call could have been.

list($a,, $c) = returnThreeValues();
Loading history...
1974
1975
		$context['package_ftp'] = array(
1976
			'server' => isset($modSettings['package_server']) ? $modSettings['package_server'] : 'localhost',
1977
			'port' => isset($modSettings['package_port']) ? $modSettings['package_port'] : '21',
1978
			'username' => empty($username) ? (isset($modSettings['package_username']) ? $modSettings['package_username'] : '') : $username,
1979
			'path' => $detect_path,
1980
			'form_elements_only' => true,
1981
		);
1982
	}
1983
	else
1984
		$context['ftp_connected'] = true;
1985
1986
	// Define the template.
1987
	$context['page_title'] = $txt['package_file_perms'];
1988
	$context['sub_template'] = 'file_permissions';
1989
1990
	// Define what files we're interested in, as a tree.
1991
	$context['file_tree'] = array(
1992
		strtr($boarddir, array('\\' => '/')) => array(
1993
			'type' => 'dir',
1994
			'contents' => array(
1995
				'agreement.txt' => array(
1996
					'type' => 'file',
1997
					'writable_on' => 'standard',
1998
				),
1999
				'Settings.php' => array(
2000
					'type' => 'file',
2001
					'writable_on' => 'restrictive',
2002
				),
2003
				'Settings_bak.php' => array(
2004
					'type' => 'file',
2005
					'writable_on' => 'restrictive',
2006
				),
2007
				'attachments' => array(
2008
					'type' => 'dir',
2009
					'writable_on' => 'restrictive',
2010
				),
2011
				'avatars' => array(
2012
					'type' => 'dir',
2013
					'writable_on' => 'standard',
2014
				),
2015
				'cache' => array(
2016
					'type' => 'dir',
2017
					'writable_on' => 'restrictive',
2018
				),
2019
				'custom_avatar_dir' => array(
2020
					'type' => 'dir',
2021
					'writable_on' => 'restrictive',
2022
				),
2023
				'Smileys' => array(
2024
					'type' => 'dir_recursive',
2025
					'writable_on' => 'standard',
2026
				),
2027
				'Sources' => array(
2028
					'type' => 'dir_recursive',
2029
					'list_contents' => true,
2030
					'writable_on' => 'standard',
2031
					'contents' => array(
2032
						'tasks' => array(
2033
							'type' => 'dir',
2034
							'list_contents' => true,
2035
						),
2036
					),
2037
				),
2038
				'Themes' => array(
2039
					'type' => 'dir_recursive',
2040
					'writable_on' => 'standard',
2041
					'contents' => array(
2042
						'default' => array(
2043
							'type' => 'dir_recursive',
2044
							'list_contents' => true,
2045
							'contents' => array(
2046
								'languages' => array(
2047
									'type' => 'dir',
2048
									'list_contents' => true,
2049
								),
2050
							),
2051
						),
2052
					),
2053
				),
2054
				'Packages' => array(
2055
					'type' => 'dir',
2056
					'writable_on' => 'standard',
2057
					'contents' => array(
2058
						'temp' => array(
2059
							'type' => 'dir',
2060
						),
2061
						'backup' => array(
2062
							'type' => 'dir',
2063
						),
2064
					),
2065
				),
2066
			),
2067
		),
2068
	);
2069
2070
	// Directories that can move.
2071
	if (substr($sourcedir, 0, strlen($boarddir)) != $boarddir)
2072
	{
2073
		unset($context['file_tree'][strtr($boarddir, array('\\' => '/'))]['contents']['Sources']);
2074
		$context['file_tree'][strtr($sourcedir, array('\\' => '/'))] = array(
2075
			'type' => 'dir',
2076
			'list_contents' => true,
2077
			'writable_on' => 'standard',
2078
		);
2079
	}
2080
2081
	// Moved the cache?
2082
	if (substr($cachedir, 0, strlen($boarddir)) != $boarddir)
2083
	{
2084
		unset($context['file_tree'][strtr($boarddir, array('\\' => '/'))]['contents']['cache']);
2085
		$context['file_tree'][strtr($cachedir, array('\\' => '/'))] = array(
2086
			'type' => 'dir',
2087
			'list_contents' => false,
2088
			'writable_on' => 'restrictive',
2089
		);
2090
	}
2091
2092
	// Are we using multiple attachment directories?
2093
	if (!empty($modSettings['currentAttachmentUploadDir']))
2094
	{
2095
		unset($context['file_tree'][strtr($boarddir, array('\\' => '/'))]['contents']['attachments']);
2096
2097
		if (!is_array($modSettings['attachmentUploadDir']))
2098
			$modSettings['attachmentUploadDir'] = $smcFunc['json_decode']($modSettings['attachmentUploadDir'], true);
2099
2100
		// @todo Should we suggest non-current directories be read only?
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...
2101
		foreach ($modSettings['attachmentUploadDir'] as $dir)
2102
			$context['file_tree'][strtr($dir, array('\\' => '/'))] = array(
2103
			'type' => 'dir',
2104
			'writable_on' => 'restrictive',
2105
		);
2106
2107
	}
2108 View Code Duplication
	elseif (substr($modSettings['attachmentUploadDir'], 0, strlen($boarddir)) != $boarddir)
2109
	{
2110
		unset($context['file_tree'][strtr($boarddir, array('\\' => '/'))]['contents']['attachments']);
2111
		$context['file_tree'][strtr($modSettings['attachmentUploadDir'], array('\\' => '/'))] = array(
2112
			'type' => 'dir',
2113
			'writable_on' => 'restrictive',
2114
		);
2115
	}
2116
2117 View Code Duplication
	if (substr($modSettings['smileys_dir'], 0, strlen($boarddir)) != $boarddir)
2118
	{
2119
		unset($context['file_tree'][strtr($boarddir, array('\\' => '/'))]['contents']['Smileys']);
2120
		$context['file_tree'][strtr($modSettings['smileys_dir'], array('\\' => '/'))] = array(
2121
			'type' => 'dir_recursive',
2122
			'writable_on' => 'standard',
2123
		);
2124
	}
2125 View Code Duplication
	if (substr($modSettings['avatar_directory'], 0, strlen($boarddir)) != $boarddir)
2126
	{
2127
		unset($context['file_tree'][strtr($boarddir, array('\\' => '/'))]['contents']['avatars']);
2128
		$context['file_tree'][strtr($modSettings['avatar_directory'], array('\\' => '/'))] = array(
2129
			'type' => 'dir',
2130
			'writable_on' => 'standard',
2131
		);
2132
	}
2133 View Code Duplication
	if (isset($modSettings['custom_avatar_dir']) && substr($modSettings['custom_avatar_dir'], 0, strlen($boarddir)) != $boarddir)
2134
	{
2135
		unset($context['file_tree'][strtr($boarddir, array('\\' => '/'))]['contents']['custom_avatar_dir']);
2136
		$context['file_tree'][strtr($modSettings['custom_avatar_dir'], array('\\' => '/'))] = array(
2137
			'type' => 'dir',
2138
			'writable_on' => 'restrictive',
2139
		);
2140
	}
2141
2142
	// Load up any custom themes.
2143
	$request = $smcFunc['db_query']('', '
2144
		SELECT value
2145
		FROM {db_prefix}themes
2146
		WHERE id_theme > {int:default_theme_id}
2147
			AND id_member = {int:guest_id}
2148
			AND variable = {string:theme_dir}
2149
		ORDER BY value ASC',
2150
		array(
2151
			'default_theme_id' => 1,
2152
			'guest_id' => 0,
2153
			'theme_dir' => 'theme_dir',
2154
		)
2155
	);
2156
	while ($row = $smcFunc['db_fetch_assoc']($request))
2157
	{
2158
		if (substr(strtolower(strtr($row['value'], array('\\' => '/'))), 0, strlen($boarddir) + 7) == strtolower(strtr($boarddir, array('\\' => '/')) . '/Themes'))
2159
			$context['file_tree'][strtr($boarddir, array('\\' => '/'))]['contents']['Themes']['contents'][substr($row['value'], strlen($boarddir) + 8)] = array(
2160
				'type' => 'dir_recursive',
2161
				'list_contents' => true,
2162
				'contents' => array(
2163
					'languages' => array(
2164
						'type' => 'dir',
2165
						'list_contents' => true,
2166
					),
2167
				),
2168
			);
2169
		else
2170
		{
2171
			$context['file_tree'][strtr($row['value'], array('\\' => '/'))] = array(
2172
				'type' => 'dir_recursive',
2173
				'list_contents' => true,
2174
				'contents' => array(
2175
					'languages' => array(
2176
						'type' => 'dir',
2177
						'list_contents' => true,
2178
					),
2179
				),
2180
			);
2181
		}
2182
	}
2183
	$smcFunc['db_free_result']($request);
2184
2185
	// If we're submitting then let's move on to another function to keep things cleaner..
2186
	if (isset($_POST['action_changes']))
2187
		return PackagePermissionsAction();
2188
2189
	$context['look_for'] = array();
2190
	// Are we looking for a particular tree - normally an expansion?
2191
	if (!empty($_REQUEST['find']))
2192
		$context['look_for'][] = base64_decode($_REQUEST['find']);
2193
	// Only that tree?
2194
	$context['only_find'] = isset($_GET['xml']) && !empty($_REQUEST['onlyfind']) ? $_REQUEST['onlyfind'] : '';
2195
	if ($context['only_find'])
2196
		$context['look_for'][] = $context['only_find'];
2197
2198
	// Have we got a load of back-catalogue trees to expand from a submit etc?
2199
	if (!empty($_GET['back_look']))
2200
	{
2201
		$potententialTrees = $smcFunc['json_decode'](base64_decode($_GET['back_look']), true);
2202
		foreach ($potententialTrees as $tree)
2203
			$context['look_for'][] = $tree;
2204
	}
2205
	// ... maybe posted?
2206
	if (!empty($_POST['back_look']))
2207
		$context['only_find'] = array_merge($context['only_find'], $_POST['back_look']);
2208
2209
	$context['back_look_data'] = base64_encode($smcFunc['json_encode'](array_slice($context['look_for'], 0, 15)));
2210
2211
	// Are we finding more files than first thought?
2212
	$context['file_offset'] = !empty($_REQUEST['fileoffset']) ? (int) $_REQUEST['fileoffset'] : 0;
2213
	// Don't list more than this many files in a directory.
2214
	$context['file_limit'] = 150;
2215
2216
	// How many levels shall we show?
2217
	$context['default_level'] = empty($context['only_find']) ? 2 : 25;
2218
2219
	// This will be used if we end up catching XML data.
2220
	$context['xml_data'] = array(
2221
		'roots' => array(
2222
			'identifier' => 'root',
2223
			'children' => array(
2224
				array(
2225
					'value' => preg_replace('~[^A-Za-z0-9_\-=:]~', ':-:', $context['only_find']),
2226
				),
2227
			),
2228
		),
2229
		'folders' => array(
2230
			'identifier' => 'folder',
2231
			'children' => array(),
2232
		),
2233
	);
2234
2235
	foreach ($context['file_tree'] as $path => $data)
2236
	{
2237
		// Run this directory.
2238
		if (file_exists($path) && (empty($context['only_find']) || substr($context['only_find'], 0, strlen($path)) == $path))
2239
		{
2240
			// Get the first level down only.
2241
			fetchPerms__recursive($path, $context['file_tree'][$path], 1);
2242
			$context['file_tree'][$path]['perms'] = array(
2243
				'chmod' => @is_writable($path),
2244
				'perms' => @fileperms($path),
2245
			);
2246
		}
2247
		else
2248
			unset($context['file_tree'][$path]);
2249
	}
2250
2251
	// Is this actually xml?
2252
	if (isset($_GET['xml']))
2253
	{
2254
		loadTemplate('Xml');
2255
		$context['sub_template'] = 'generic_xml';
2256
		$context['template_layers'] = array();
2257
	}
2258
}
2259
2260
/**
2261
 * Checkes the permissions of all the areas that will be affected by the package
2262
 *
2263
 * @param string $path The path to the directiory to check permissions for
2264
 * @param array $data An array of data about the directory
2265
 * @param int $level How far deep to go
2266
 */
2267
function fetchPerms__recursive($path, &$data, $level)
2268
{
2269
	global $context;
2270
2271
	$isLikelyPath = false;
2272
	foreach ($context['look_for'] as $possiblePath)
2273
		if (substr($possiblePath, 0, strlen($path)) == $path)
2274
			$isLikelyPath = true;
2275
2276
	// Is this where we stop?
2277
	if (isset($_GET['xml']) && !empty($context['look_for']) && !$isLikelyPath)
2278
		return;
2279
	elseif ($level > $context['default_level'] && !$isLikelyPath)
2280
		return;
2281
2282
	// Are we actually interested in saving this data?
2283
	$save_data = empty($context['only_find']) || $context['only_find'] == $path;
2284
2285
	// @todo Shouldn't happen - but better error message?
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...
2286
	if (!is_dir($path))
2287
		fatal_lang_error('no_access', false);
2288
2289
	// This is where we put stuff we've found for sorting.
2290
	$foundData = array(
2291
		'files' => array(),
2292
		'folders' => array(),
2293
	);
2294
2295
	$dh = opendir($path);
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $dh. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
2296
	while ($entry = readdir($dh))
2297
	{
2298
		// Some kind of file?
2299
		if (is_file($path . '/' . $entry))
2300
		{
2301
			// Are we listing PHP files in this directory?
2302
			if ($save_data && !empty($data['list_contents']) && substr($entry, -4) == '.php')
2303
				$foundData['files'][$entry] = true;
2304
			// A file we were looking for.
2305
			elseif ($save_data && isset($data['contents'][$entry]))
2306
				$foundData['files'][$entry] = true;
2307
		}
2308
		// It's a directory - we're interested one way or another, probably...
2309
		elseif ($entry != '.' && $entry != '..')
2310
		{
2311
			// Going further?
2312
			if ((!empty($data['type']) && $data['type'] == 'dir_recursive') || (isset($data['contents'][$entry]) && (!empty($data['contents'][$entry]['list_contents']) || (!empty($data['contents'][$entry]['type']) && $data['contents'][$entry]['type'] == 'dir_recursive'))))
2313
			{
2314
				if (!isset($data['contents'][$entry]))
2315
					$foundData['folders'][$entry] = 'dir_recursive';
2316
				else
2317
					$foundData['folders'][$entry] = true;
2318
2319
				// If this wasn't expected inherit the recusiveness...
2320
				if (!isset($data['contents'][$entry]))
2321
					// We need to do this as we will be going all recursive.
2322
					$data['contents'][$entry] = array(
2323
						'type' => 'dir_recursive',
2324
					);
2325
2326
				// Actually do the recursive stuff...
2327
				fetchPerms__recursive($path . '/' . $entry, $data['contents'][$entry], $level + 1);
2328
			}
2329
			// Maybe it is a folder we are not descending into.
2330
			elseif (isset($data['contents'][$entry]))
2331
				$foundData['folders'][$entry] = true;
2332
			// Otherwise we stop here.
2333
		}
2334
	}
2335
	closedir($dh);
2336
2337
	// Nothing to see here?
2338
	if (!$save_data)
2339
		return;
2340
2341
	// Now actually add the data, starting with the folders.
2342
	ksort($foundData['folders']);
2343
	foreach ($foundData['folders'] as $folder => $type)
2344
	{
2345
		$additional_data = array(
2346
			'perms' => array(
2347
				'chmod' => @is_writable($path . '/' . $folder),
2348
				'perms' => @fileperms($path . '/' . $folder),
2349
			),
2350
		);
2351
		if ($type !== true)
2352
			$additional_data['type'] = $type;
2353
2354
		// If there's an offset ignore any folders in XML mode.
2355
		if (isset($_GET['xml']) && $context['file_offset'] == 0)
2356
		{
2357
			$context['xml_data']['folders']['children'][] = array(
2358
				'attributes' => array(
2359
					'writable' => $additional_data['perms']['chmod'] ? 1 : 0,
2360
					'permissions' => substr(sprintf('%o', $additional_data['perms']['perms']), -4),
2361
					'folder' => 1,
2362
					'path' => $context['only_find'],
2363
					'level' => $level,
2364
					'more' => 0,
2365
					'offset' => $context['file_offset'],
2366
					'my_ident' => preg_replace('~[^A-Za-z0-9_\-=:]~', ':-:', $context['only_find'] . '/' . $folder),
2367
					'ident' => preg_replace('~[^A-Za-z0-9_\-=:]~', ':-:', $context['only_find']),
2368
				),
2369
				'value' => $folder,
2370
			);
2371
		}
2372 View Code Duplication
		elseif (!isset($_GET['xml']))
2373
		{
2374
			if (isset($data['contents'][$folder]))
2375
				$data['contents'][$folder] = array_merge($data['contents'][$folder], $additional_data);
2376
			else
2377
				$data['contents'][$folder] = $additional_data;
2378
		}
2379
	}
2380
2381
	// Now we want to do a similar thing with files.
2382
	ksort($foundData['files']);
2383
	$counter = -1;
2384
	foreach ($foundData['files'] as $file => $dummy)
2385
	{
2386
		$counter++;
2387
2388
		// Have we reached our offset?
2389
		if ($context['file_offset'] > $counter)
2390
			continue;
2391
		// Gone too far?
2392
		if ($counter > ($context['file_offset'] + $context['file_limit']))
2393
			continue;
2394
2395
		$additional_data = array(
2396
			'perms' => array(
2397
				'chmod' => @is_writable($path . '/' . $file),
2398
				'perms' => @fileperms($path . '/' . $file),
2399
			),
2400
		);
2401
2402
		// XML?
2403
		if (isset($_GET['xml']))
2404
		{
2405
			$context['xml_data']['folders']['children'][] = array(
2406
				'attributes' => array(
2407
					'writable' => $additional_data['perms']['chmod'] ? 1 : 0,
2408
					'permissions' => substr(sprintf('%o', $additional_data['perms']['perms']), -4),
2409
					'folder' => 0,
2410
					'path' => $context['only_find'],
2411
					'level' => $level,
2412
					'more' => $counter == ($context['file_offset'] + $context['file_limit']) ? 1 : 0,
2413
					'offset' => $context['file_offset'],
2414
					'my_ident' => preg_replace('~[^A-Za-z0-9_\-=:]~', ':-:', $context['only_find'] . '/' . $file),
2415
					'ident' => preg_replace('~[^A-Za-z0-9_\-=:]~', ':-:', $context['only_find']),
2416
				),
2417
				'value' => $file,
2418
			);
2419
		}
2420
		elseif ($counter != ($context['file_offset'] + $context['file_limit']))
2421
		{
2422 View Code Duplication
			if (isset($data['contents'][$file]))
2423
				$data['contents'][$file] = array_merge($data['contents'][$file], $additional_data);
2424
			else
2425
				$data['contents'][$file] = $additional_data;
2426
		}
2427
	}
2428
}
2429
2430
/**
2431
 * Actually action the permission changes they want.
2432
 */
2433
function PackagePermissionsAction()
2434
{
2435
	global $smcFunc, $context, $txt, $time_start, $package_ftp;
2436
2437
	umask(0);
2438
2439
	$timeout_limit = 5;
2440
2441
	$context['method'] = $_POST['method'] == 'individual' ? 'individual' : 'predefined';
2442
	$context['sub_template'] = 'action_permissions';
2443
	$context['page_title'] = $txt['package_file_perms_applying'];
2444
	$context['back_look_data'] = isset($_POST['back_look']) ? $_POST['back_look'] : array();
2445
2446
	// Skipping use of FTP?
2447
	if (empty($package_ftp))
2448
		$context['skip_ftp'] = true;
2449
2450
	// We'll start off in a good place, security. Make sure that if we're dealing with individual files that they seem in the right place.
2451
	if ($context['method'] == 'individual')
2452
	{
2453
		// Only these path roots are legal.
2454
		$legal_roots = array_keys($context['file_tree']);
2455
		$context['custom_value'] = (int) $_POST['custom_value'];
2456
2457
		// Continuing?
2458
		if (isset($_POST['toProcess']))
2459
			$_POST['permStatus'] = $smcFunc['json_decode'](base64_decode($_POST['toProcess']), true);
2460
2461
		if (isset($_POST['permStatus']))
2462
		{
2463
			$context['to_process'] = array();
2464
			$validate_custom = false;
2465
			foreach ($_POST['permStatus'] as $path => $status)
2466
			{
2467
				// Nothing to see here?
2468
				if ($status == 'no_change')
2469
					continue;
2470
				$legal = false;
2471
				foreach ($legal_roots as $root)
2472
					if (substr($path, 0, strlen($root)) == $root)
2473
						$legal = true;
2474
2475
				if (!$legal)
2476
					continue;
2477
2478
				// Check it exists.
2479
				if (!file_exists($path))
2480
					continue;
2481
2482
				if ($status == 'custom')
2483
					$validate_custom = true;
2484
2485
				// Now add it.
2486
				$context['to_process'][$path] = $status;
2487
			}
2488
			$context['total_items'] = isset($_POST['totalItems']) ? (int) $_POST['totalItems'] : count($context['to_process']);
2489
2490
			// Make sure the chmod status is valid?
2491
			if ($validate_custom)
2492
			{
2493
				if (preg_match('~^[4567][4567][4567]$~', $context['custom_value']) == false)
0 ignored issues
show
Bug Best Practice introduced by
It seems like you are loosely comparing preg_match('~^[4567][456...ontext['custom_value']) of type integer to the boolean false. If you are specifically checking for 0, consider using something more explicit like === 0 instead.
Loading history...
2494
					fatal_error($txt['chmod_value_invalid']);
2495
			}
2496
2497
			// Nothing to do?
2498
			if (empty($context['to_process']))
2499
				redirectexit('action=admin;area=packages;sa=perms' . (!empty($context['back_look_data']) ? ';back_look=' . base64_encode($smcFunc['json_encode']($context['back_look_data'])) : '') . ';' . $context['session_var'] . '=' . $context['session_id']);
2500
		}
2501
		// Should never get here,
2502
		else
2503
			fatal_lang_error('no_access', false);
2504
2505
		// Setup the custom value.
2506
		$custom_value = octdec('0' . $context['custom_value']);
2507
2508
		// Start processing items.
2509
		foreach ($context['to_process'] as $path => $status)
2510
		{
2511
			if (in_array($status, array('execute', 'writable', 'read')))
2512
				package_chmod($path, $status);
2513
			elseif ($status == 'custom' && !empty($custom_value))
2514
			{
2515
				// Use FTP if we have it.
2516
				if (!empty($package_ftp) && !empty($_SESSION['pack_ftp']))
2517
				{
2518
					$ftp_file = strtr($path, array($_SESSION['pack_ftp']['root'] => ''));
2519
					$package_ftp->chmod($ftp_file, $custom_value);
2520
				}
2521
				else
2522
					smf_chmod($path, $custom_value);
2523
			}
2524
2525
			// This fish is fried...
2526
			unset($context['to_process'][$path]);
2527
2528
			// See if we're out of time?
2529
			if (time() - array_sum(explode(' ', $time_start)) > $timeout_limit)
2530
			{
2531
				// Prepare template usage for to_process.
2532
				$context['to_process_encode'] = base64_encode($smcFunc['json_encode']($context['to_process']));
2533
2534
				return false;
2535
			}
2536
		}
2537
2538
		// Prepare template usage for to_process.
2539
		$context['to_process_encode'] = base64_encode($smcFunc['json_encode']($context['to_process']));
2540
	}
2541
	// If predefined this is a little different.
2542
	else
2543
	{
2544
		$context['predefined_type'] = isset($_POST['predefined']) ? $_POST['predefined'] : 'restricted';
2545
2546
		$context['total_items'] = isset($_POST['totalItems']) ? (int) $_POST['totalItems'] : 0;
2547
		$context['directory_list'] = isset($_POST['dirList']) ? $smcFunc['json_decode'](base64_decode($_POST['dirList']), true) : array();
2548
2549
		$context['file_offset'] = isset($_POST['fileOffset']) ? (int) $_POST['fileOffset'] : 0;
2550
2551
		// Haven't counted the items yet?
2552
		if (empty($context['total_items']))
2553
		{
2554
			/**
2555
			 * Counts all the directories under a given path
2556
			 *
2557
			 * @param type $dir
2558
			 * @return integer
2559
			 */
2560
			function count_directories__recursive($dir)
2561
			{
2562
				global $context;
2563
2564
				$count = 0;
2565
				$dh = @opendir($dir);
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $dh. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
2566
				while ($entry = readdir($dh))
2567
				{
2568
					if ($entry != '.' && $entry != '..' && is_dir($dir . '/' . $entry))
2569
					{
2570
						$context['directory_list'][$dir . '/' . $entry] = 1;
2571
						$count++;
2572
						$count += count_directories__recursive($dir . '/' . $entry);
0 ignored issues
show
Documentation introduced by
$dir . '/' . $entry is of type string, but the function expects a object<type>.

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...
2573
					}
2574
				}
2575
				closedir($dh);
2576
2577
				return $count;
2578
			}
2579
2580
			foreach ($context['file_tree'] as $path => $data)
2581
			{
2582
				if (is_dir($path))
2583
				{
2584
					$context['directory_list'][$path] = 1;
2585
					$context['total_items'] += count_directories__recursive($path);
0 ignored issues
show
Documentation introduced by
$path is of type integer|string, but the function expects a object<type>.

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...
2586
					$context['total_items']++;
2587
				}
2588
			}
2589
		}
2590
2591
		// Have we built up our list of special files?
2592
		if (!isset($_POST['specialFiles']) && $context['predefined_type'] != 'free')
2593
		{
2594
			$context['special_files'] = array();
2595
2596
			/**
2597
			 * Builds a list of special files recursively for a given path
2598
			 *
2599
			 * @param type $path
2600
			 * @param type $data
2601
			 */
2602
			function build_special_files__recursive($path, &$data)
2603
			{
2604
				global $context;
2605
2606
				if (!empty($data['writable_on']))
2607
					if ($context['predefined_type'] == 'standard' || $data['writable_on'] == 'restrictive')
2608
						$context['special_files'][$path] = 1;
2609
2610
				if (!empty($data['contents']))
2611
					foreach ($data['contents'] as $name => $contents)
2612
						build_special_files__recursive($path . '/' . $name, $contents);
0 ignored issues
show
Documentation introduced by
$path . '/' . $name is of type string, but the function expects a object<type>.

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...
2613
			}
2614
2615
			foreach ($context['file_tree'] as $path => $data)
2616
				build_special_files__recursive($path, $data);
0 ignored issues
show
Documentation introduced by
$path is of type integer|string, but the function expects a object<type>.

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...
2617
		}
2618
		// Free doesn't need special files.
2619
		elseif ($context['predefined_type'] == 'free')
2620
			$context['special_files'] = array();
2621
		else
2622
			$context['special_files'] = $smcFunc['json_decode'](base64_decode($_POST['specialFiles']), true);
2623
2624
		// Now we definitely know where we are, we need to go through again doing the chmod!
2625
		foreach ($context['directory_list'] as $path => $dummy)
2626
		{
2627
			// Do the contents of the directory first.
2628
			$dh = @opendir($path);
2629
			$file_count = 0;
2630
			$dont_chmod = false;
2631
			while ($entry = readdir($dh))
2632
			{
2633
				$file_count++;
2634
				// Actually process this file?
2635
				if (!$dont_chmod && !is_dir($path . '/' . $entry) && (empty($context['file_offset']) || $context['file_offset'] < $file_count))
2636
				{
2637
					$status = $context['predefined_type'] == 'free' || isset($context['special_files'][$path . '/' . $entry]) ? 'writable' : 'execute';
2638
					package_chmod($path . '/' . $entry, $status);
2639
				}
2640
2641
				// See if we're out of time?
2642
				if (!$dont_chmod && time() - array_sum(explode(' ', $time_start)) > $timeout_limit)
2643
				{
2644
					$dont_chmod = true;
2645
					// Don't do this again.
2646
					$context['file_offset'] = $file_count;
2647
				}
2648
			}
2649
			closedir($dh);
2650
2651
			// If this is set it means we timed out half way through.
2652
			if ($dont_chmod)
2653
			{
2654
				$context['total_files'] = $file_count;
2655
				return false;
2656
			}
2657
2658
			// Do the actual directory.
2659
			$status = $context['predefined_type'] == 'free' || isset($context['special_files'][$path]) ? 'writable' : 'execute';
2660
			package_chmod($path, $status);
2661
2662
			// We've finished the directory so no file offset, and no record.
2663
			$context['file_offset'] = 0;
2664
			unset($context['directory_list'][$path]);
2665
2666
			// See if we're out of time?
2667
			if (time() - array_sum(explode(' ', $time_start)) > $timeout_limit)
2668
			{
2669
				// Prepare this for usage on templates.
2670
				$context['directory_list_encode'] = base64_encode($smcFunc['json_encode']($context['directory_list']));
2671
				$context['special_files_encode'] = base64_encode($smcFunc['json_encode']($context['special_files']));
2672
2673
				return false;
2674
			}
2675
		}
2676
2677
		// Prepare this for usage on templates.
2678
		$context['directory_list_encode'] = base64_encode($smcFunc['json_encode']($context['directory_list']));
2679
		$context['special_files_encode'] = base64_encode($smcFunc['json_encode']($context['special_files']));
2680
	}
2681
2682
	// If we're here we are done!
2683
	redirectexit('action=admin;area=packages;sa=perms' . (!empty($context['back_look_data']) ? ';back_look=' . base64_encode($smcFunc['json_encode']($context['back_look_data'])) : '') . ';' . $context['session_var'] . '=' . $context['session_id']);
2684
}
2685
2686
/**
2687
 * Test an FTP connection.
2688
 */
2689
function PackageFTPTest()
2690
{
2691
	global $context, $txt, $package_ftp;
2692
2693
	checkSession('get');
2694
2695
	// Try to make the FTP connection.
2696
	create_chmod_control(array(), array('force_find_error' => true));
2697
2698
	// Deal with the template stuff.
2699
	loadTemplate('Xml');
2700
	$context['sub_template'] = 'generic_xml';
2701
	$context['template_layers'] = array();
2702
2703
	// Define the return data, this is simple.
2704
	$context['xml_data'] = array(
2705
		'results' => array(
2706
			'identifier' => 'result',
2707
			'children' => array(
2708
				array(
2709
					'attributes' => array(
2710
						'success' => !empty($package_ftp) ? 1 : 0,
2711
					),
2712
					'value' => !empty($package_ftp) ? $txt['package_ftp_test_success'] : (isset($context['package_ftp'], $context['package_ftp']['error']) ? $context['package_ftp']['error'] : $txt['package_ftp_test_failed']),
2713
				),
2714
			),
2715
		),
2716
	);
2717
}
2718
2719
?>