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 https://www.simplemachines.org |
||
10 | * @copyright 2020 Simple Machines and individual contributors |
||
11 | * @license https://www.simplemachines.org/about/smf/license.php BSD |
||
12 | * |
||
13 | * @version 2.1 RC2 |
||
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! |
||
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', |
||
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, 'minimize' => true), '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 | 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 | // Get our validation info. |
||
205 | $context['validation_tests'] = package_validate_installtest(array( |
||
206 | 'file_name' => $packagesdir . '/' . $context['filename'], |
||
207 | 'custom_id' => !empty($packageInfo['id']) ? $packageInfo['id'] : '', |
||
208 | 'custom_type' => $context['extract_type'] |
||
209 | )); |
||
210 | |||
211 | // The mod isn't installed.... unless proven otherwise. |
||
212 | $context['is_installed'] = false; |
||
213 | |||
214 | // See if it is installed? |
||
215 | $request = $smcFunc['db_query']('', ' |
||
216 | SELECT version, themes_installed, db_changes |
||
217 | FROM {db_prefix}log_packages |
||
218 | WHERE package_id = {string:current_package} |
||
219 | AND install_state != {int:not_installed} |
||
220 | ORDER BY time_installed DESC |
||
221 | LIMIT 1', |
||
222 | array( |
||
223 | 'not_installed' => 0, |
||
224 | 'current_package' => $packageInfo['id'], |
||
225 | ) |
||
226 | ); |
||
227 | |||
228 | while ($row = $smcFunc['db_fetch_assoc']($request)) |
||
229 | { |
||
230 | $old_themes = explode(',', $row['themes_installed']); |
||
231 | $old_version = $row['version']; |
||
232 | $db_changes = empty($row['db_changes']) ? array() : $smcFunc['json_decode']($row['db_changes'], true); |
||
233 | } |
||
234 | $smcFunc['db_free_result']($request); |
||
235 | |||
236 | $context['database_changes'] = array(); |
||
237 | if (isset($packageInfo['uninstall']['database'])) |
||
238 | $context['database_changes'][] = sprintf($txt['package_db_code'], $packageInfo['uninstall']['database']); |
||
239 | if (!empty($db_changes)) |
||
240 | { |
||
241 | foreach ($db_changes as $change) |
||
242 | { |
||
243 | if (isset($change[2]) && isset($txt['package_db_' . $change[0]])) |
||
244 | $context['database_changes'][] = sprintf($txt['package_db_' . $change[0]], $change[1], $change[2]); |
||
245 | elseif (isset($txt['package_db_' . $change[0]])) |
||
246 | $context['database_changes'][] = sprintf($txt['package_db_' . $change[0]], $change[1]); |
||
247 | else |
||
248 | $context['database_changes'][] = $change[0] . '-' . $change[1] . (isset($change[2]) ? '-' . $change[2] : ''); |
||
249 | } |
||
250 | } |
||
251 | |||
252 | // Uninstalling? |
||
253 | if ($context['uninstalling']) |
||
254 | { |
||
255 | // Wait, it's not installed yet! |
||
256 | if (!isset($old_version) && $context['uninstalling']) |
||
257 | { |
||
258 | deltree($packagesdir . '/temp'); |
||
259 | fatal_lang_error('package_cant_uninstall', false); |
||
260 | } |
||
261 | |||
262 | $actions = parsePackageInfo($packageInfo['xml'], true, 'uninstall'); |
||
263 | |||
264 | // Gadzooks! There's no uninstaller at all!? |
||
265 | if (empty($actions)) |
||
266 | { |
||
267 | deltree($packagesdir . '/temp'); |
||
268 | fatal_lang_error('package_uninstall_cannot', false); |
||
269 | } |
||
270 | |||
271 | // Can't edit the custom themes it's edited if you're uninstalling, they must be removed. |
||
272 | $context['themes_locked'] = true; |
||
273 | |||
274 | // Only let them uninstall themes it was installed into. |
||
275 | foreach ($theme_paths as $id => $data) |
||
276 | if ($id != 1 && !in_array($id, $old_themes)) |
||
277 | unset($theme_paths[$id]); |
||
278 | } |
||
279 | elseif (isset($old_version) && $old_version != $packageInfo['version']) |
||
280 | { |
||
281 | // Look for an upgrade... |
||
282 | $actions = parsePackageInfo($packageInfo['xml'], true, 'upgrade', $old_version); |
||
283 | |||
284 | // There was no upgrade.... |
||
285 | if (empty($actions)) |
||
286 | $context['is_installed'] = true; |
||
287 | else |
||
288 | { |
||
289 | // Otherwise they can only upgrade themes from the first time around. |
||
290 | foreach ($theme_paths as $id => $data) |
||
291 | if ($id != 1 && !in_array($id, $old_themes)) |
||
292 | unset($theme_paths[$id]); |
||
293 | } |
||
294 | } |
||
295 | elseif (isset($old_version) && $old_version == $packageInfo['version']) |
||
296 | $context['is_installed'] = true; |
||
297 | |||
298 | if (!isset($old_version) || $context['is_installed']) |
||
299 | $actions = parsePackageInfo($packageInfo['xml'], true, 'install'); |
||
300 | |||
301 | $context['actions'] = array(); |
||
302 | $context['ftp_needed'] = false; |
||
303 | $context['has_failure'] = false; |
||
304 | $chmod_files = array(); |
||
305 | |||
306 | // no actions found, return so we can display an error |
||
307 | if (empty($actions)) |
||
308 | return; |
||
309 | |||
310 | // This will hold data about anything that can be installed in other themes. |
||
311 | $themeFinds = array( |
||
312 | 'candidates' => array(), |
||
313 | 'other_themes' => array(), |
||
314 | ); |
||
315 | |||
316 | // Now prepare things for the template. |
||
317 | foreach ($actions as $action) |
||
318 | { |
||
319 | // Not failed until proven otherwise. |
||
320 | $failed = false; |
||
321 | $thisAction = array(); |
||
322 | |||
323 | if ($action['type'] == 'chmod') |
||
324 | { |
||
325 | $chmod_files[] = $action['filename']; |
||
326 | continue; |
||
327 | } |
||
328 | elseif ($action['type'] == 'readme' || $action['type'] == 'license') |
||
329 | { |
||
330 | $type = 'package_' . $action['type']; |
||
331 | if (file_exists($packagesdir . '/temp/' . $context['base_path'] . $action['filename'])) |
||
332 | $context[$type] = $smcFunc['htmlspecialchars'](trim(file_get_contents($packagesdir . '/temp/' . $context['base_path'] . $action['filename']), "\n\r")); |
||
333 | elseif (file_exists($action['filename'])) |
||
334 | $context[$type] = $smcFunc['htmlspecialchars'](trim(file_get_contents($action['filename']), "\n\r")); |
||
335 | |||
336 | if (!empty($action['parse_bbc'])) |
||
337 | { |
||
338 | require_once($sourcedir . '/Subs-Post.php'); |
||
339 | $context[$type] = preg_replace('~\[[/]?html\]~i', '', $context[$type]); |
||
340 | preparsecode($context[$type]); |
||
341 | $context[$type] = parse_bbc($context[$type]); |
||
342 | } |
||
343 | else |
||
344 | $context[$type] = nl2br($context[$type]); |
||
345 | |||
346 | continue; |
||
347 | } |
||
348 | // Don't show redirects. |
||
349 | elseif ($action['type'] == 'redirect') |
||
350 | continue; |
||
351 | elseif ($action['type'] == 'error') |
||
352 | { |
||
353 | $context['has_failure'] = true; |
||
354 | if (isset($action['error_msg']) && isset($action['error_var'])) |
||
355 | $context['failure_details'] = sprintf($txt['package_will_fail_' . $action['error_msg']], $action['error_var']); |
||
356 | elseif (isset($action['error_msg'])) |
||
357 | $context['failure_details'] = isset($txt['package_will_fail_' . $action['error_msg']]) ? $txt['package_will_fail_' . $action['error_msg']] : $action['error_msg']; |
||
358 | } |
||
359 | elseif ($action['type'] == 'modification') |
||
360 | { |
||
361 | if (!file_exists($packagesdir . '/temp/' . $context['base_path'] . $action['filename'])) |
||
362 | { |
||
363 | $context['has_failure'] = true; |
||
364 | |||
365 | $context['actions'][] = array( |
||
366 | 'type' => $txt['execute_modification'], |
||
367 | 'action' => $smcFunc['htmlspecialchars'](strtr($action['filename'], array($boarddir => '.'))), |
||
368 | 'description' => $txt['package_action_missing'], |
||
369 | 'failed' => true, |
||
370 | ); |
||
371 | } |
||
372 | else |
||
373 | { |
||
374 | if ($action['boardmod']) |
||
375 | $mod_actions = parseBoardMod(@file_get_contents($packagesdir . '/temp/' . $context['base_path'] . $action['filename']), true, $action['reverse'], $theme_paths); |
||
376 | else |
||
377 | $mod_actions = parseModification(@file_get_contents($packagesdir . '/temp/' . $context['base_path'] . $action['filename']), true, $action['reverse'], $theme_paths); |
||
378 | |||
379 | if (count($mod_actions) == 1 && isset($mod_actions[0]) && $mod_actions[0]['type'] == 'error' && $mod_actions[0]['filename'] == '-') |
||
380 | $mod_actions[0]['filename'] = $action['filename']; |
||
381 | |||
382 | foreach ($mod_actions as $key => $mod_action) |
||
383 | { |
||
384 | // Lets get the last section of the file name. |
||
385 | if (isset($mod_action['filename']) && substr($mod_action['filename'], -13) != '.template.php') |
||
386 | $actual_filename = strtolower(substr(strrchr($mod_action['filename'], '/'), 1) . '||' . $action['filename']); |
||
387 | elseif (isset($mod_action['filename']) && preg_match('~([\w]*)/([\w]*)\.template\.php$~', $mod_action['filename'], $matches)) |
||
388 | $actual_filename = strtolower($matches[1] . '/' . $matches[2] . '.template.php' . '||' . $action['filename']); |
||
389 | else |
||
390 | $actual_filename = $key; |
||
391 | |||
392 | if ($mod_action['type'] == 'opened') |
||
393 | $failed = false; |
||
394 | elseif ($mod_action['type'] == 'failure') |
||
395 | { |
||
396 | if (empty($mod_action['is_custom'])) |
||
397 | $context['has_failure'] = true; |
||
398 | $failed = true; |
||
399 | } |
||
400 | elseif ($mod_action['type'] == 'chmod') |
||
401 | { |
||
402 | $chmod_files[] = $mod_action['filename']; |
||
403 | } |
||
404 | elseif ($mod_action['type'] == 'saved') |
||
405 | { |
||
406 | if (!empty($mod_action['is_custom'])) |
||
407 | { |
||
408 | if (!isset($context['theme_actions'][$mod_action['is_custom']])) |
||
409 | $context['theme_actions'][$mod_action['is_custom']] = array( |
||
410 | 'name' => $theme_paths[$mod_action['is_custom']]['name'], |
||
411 | 'actions' => array(), |
||
412 | 'has_failure' => $failed, |
||
413 | ); |
||
414 | else |
||
415 | $context['theme_actions'][$mod_action['is_custom']]['has_failure'] |= $failed; |
||
416 | |||
417 | $context['theme_actions'][$mod_action['is_custom']]['actions'][$actual_filename] = array( |
||
418 | 'type' => $txt['execute_modification'], |
||
419 | 'action' => $smcFunc['htmlspecialchars'](strtr($mod_action['filename'], array($boarddir => '.'))), |
||
420 | 'description' => $failed ? $txt['package_action_failure'] : $txt['package_action_success'], |
||
421 | 'failed' => $failed, |
||
422 | ); |
||
423 | } |
||
424 | elseif (!isset($context['actions'][$actual_filename])) |
||
425 | { |
||
426 | $context['actions'][$actual_filename] = array( |
||
427 | 'type' => $txt['execute_modification'], |
||
428 | 'action' => $smcFunc['htmlspecialchars'](strtr($mod_action['filename'], array($boarddir => '.'))), |
||
429 | 'description' => $failed ? $txt['package_action_failure'] : $txt['package_action_success'], |
||
430 | 'failed' => $failed, |
||
431 | ); |
||
432 | } |
||
433 | else |
||
434 | { |
||
435 | $context['actions'][$actual_filename]['failed'] |= $failed; |
||
436 | $context['actions'][$actual_filename]['description'] = $context['actions'][$actual_filename]['failed'] ? $txt['package_action_failure'] : $txt['package_action_success']; |
||
437 | } |
||
438 | } |
||
439 | elseif ($mod_action['type'] == 'skipping') |
||
440 | { |
||
441 | $context['actions'][$actual_filename] = array( |
||
442 | 'type' => $txt['execute_modification'], |
||
443 | 'action' => $smcFunc['htmlspecialchars'](strtr($mod_action['filename'], array($boarddir => '.'))), |
||
444 | 'description' => $txt['package_action_skipping'] |
||
445 | ); |
||
446 | } |
||
447 | elseif ($mod_action['type'] == 'missing' && empty($mod_action['is_custom'])) |
||
448 | { |
||
449 | $context['has_failure'] = true; |
||
450 | $context['actions'][$actual_filename] = array( |
||
451 | 'type' => $txt['execute_modification'], |
||
452 | 'action' => $smcFunc['htmlspecialchars'](strtr($mod_action['filename'], array($boarddir => '.'))), |
||
453 | 'description' => $txt['package_action_missing'], |
||
454 | 'failed' => true, |
||
455 | ); |
||
456 | } |
||
457 | elseif ($mod_action['type'] == 'error') |
||
458 | $context['actions'][$actual_filename] = array( |
||
459 | 'type' => $txt['execute_modification'], |
||
460 | 'action' => $smcFunc['htmlspecialchars'](strtr($mod_action['filename'], array($boarddir => '.'))), |
||
461 | 'description' => $txt['package_action_error'], |
||
462 | 'failed' => true, |
||
463 | ); |
||
464 | } |
||
465 | |||
466 | // We need to loop again just to get the operations down correctly. |
||
467 | foreach ($mod_actions as $operation_key => $mod_action) |
||
468 | { |
||
469 | // Lets get the last section of the file name. |
||
470 | if (isset($mod_action['filename']) && substr($mod_action['filename'], -13) != '.template.php') |
||
471 | $actual_filename = strtolower(substr(strrchr($mod_action['filename'], '/'), 1) . '||' . $action['filename']); |
||
472 | elseif (isset($mod_action['filename']) && preg_match('~([\w]*)/([\w]*)\.template\.php$~', $mod_action['filename'], $matches)) |
||
473 | $actual_filename = strtolower($matches[1] . '/' . $matches[2] . '.template.php' . '||' . $action['filename']); |
||
474 | else |
||
475 | $actual_filename = $key; |
||
476 | |||
477 | // We just need it for actual parse changes. |
||
478 | if (!in_array($mod_action['type'], array('error', 'result', 'opened', 'saved', 'end', 'missing', 'skipping', 'chmod'))) |
||
479 | { |
||
480 | if (empty($mod_action['is_custom'])) |
||
481 | $context['actions'][$actual_filename]['operations'][] = array( |
||
482 | 'type' => $txt['execute_modification'], |
||
483 | 'action' => $smcFunc['htmlspecialchars'](strtr($mod_action['filename'], array($boarddir => '.'))), |
||
484 | 'description' => $mod_action['failed'] ? $txt['package_action_failure'] : $txt['package_action_success'], |
||
485 | 'position' => $mod_action['position'], |
||
486 | 'operation_key' => $operation_key, |
||
487 | 'filename' => $action['filename'], |
||
488 | 'is_boardmod' => $action['boardmod'], |
||
489 | 'failed' => $mod_action['failed'], |
||
490 | 'ignore_failure' => !empty($mod_action['ignore_failure']), |
||
491 | ); |
||
492 | |||
493 | // Themes are under the saved type. |
||
494 | if (isset($mod_action['is_custom']) && isset($context['theme_actions'][$mod_action['is_custom']])) |
||
495 | $context['theme_actions'][$mod_action['is_custom']]['actions'][$actual_filename]['operations'][] = array( |
||
496 | 'type' => $txt['execute_modification'], |
||
497 | 'action' => $smcFunc['htmlspecialchars'](strtr($mod_action['filename'], array($boarddir => '.'))), |
||
498 | 'description' => $mod_action['failed'] ? $txt['package_action_failure'] : $txt['package_action_success'], |
||
499 | 'position' => $mod_action['position'], |
||
500 | 'operation_key' => $operation_key, |
||
501 | 'filename' => $action['filename'], |
||
502 | 'is_boardmod' => $action['boardmod'], |
||
503 | 'failed' => $mod_action['failed'], |
||
504 | 'ignore_failure' => !empty($mod_action['ignore_failure']), |
||
505 | ); |
||
506 | } |
||
507 | } |
||
508 | } |
||
509 | } |
||
510 | elseif ($action['type'] == 'code') |
||
511 | { |
||
512 | $thisAction = array( |
||
513 | 'type' => $txt['execute_code'], |
||
514 | 'action' => $smcFunc['htmlspecialchars']($action['filename']), |
||
515 | ); |
||
516 | } |
||
517 | elseif ($action['type'] == 'database' && !$context['uninstalling']) |
||
518 | { |
||
519 | $thisAction = array( |
||
520 | 'type' => $txt['execute_database_changes'], |
||
521 | 'action' => $smcFunc['htmlspecialchars']($action['filename']), |
||
522 | ); |
||
523 | } |
||
524 | elseif (in_array($action['type'], array('create-dir', 'create-file'))) |
||
525 | { |
||
526 | $thisAction = array( |
||
527 | 'type' => $txt['package_create'] . ' ' . ($action['type'] == 'create-dir' ? $txt['package_tree'] : $txt['package_file']), |
||
528 | 'action' => $smcFunc['htmlspecialchars'](strtr($action['destination'], array($boarddir => '.'))) |
||
529 | ); |
||
530 | } |
||
531 | elseif ($action['type'] == 'hook') |
||
532 | { |
||
533 | $action['description'] = !isset($action['hook'], $action['function']) ? $txt['package_action_failure'] : $txt['package_action_success']; |
||
534 | |||
535 | if (!isset($action['hook'], $action['function'])) |
||
536 | $context['has_failure'] = true; |
||
537 | |||
538 | $thisAction = array( |
||
539 | 'type' => $action['reverse'] ? $txt['execute_hook_remove'] : $txt['execute_hook_add'], |
||
540 | 'action' => sprintf($txt['execute_hook_action' . ($action['reverse'] ? '_inverse' : '')], $smcFunc['htmlspecialchars']($action['hook'])), |
||
541 | ); |
||
542 | } |
||
543 | elseif ($action['type'] == 'credits') |
||
544 | { |
||
545 | $thisAction = array( |
||
546 | 'type' => $txt['execute_credits_add'], |
||
547 | 'action' => sprintf($txt['execute_credits_action'], $smcFunc['htmlspecialchars']($action['title'])), |
||
548 | ); |
||
549 | } |
||
550 | elseif ($action['type'] == 'requires') |
||
551 | { |
||
552 | $installed = false; |
||
553 | $version = true; |
||
554 | |||
555 | // package missing required values? |
||
556 | if (!isset($action['id'])) |
||
557 | $context['has_failure'] = true; |
||
558 | else |
||
559 | { |
||
560 | // See if this dependancy is installed |
||
561 | $request = $smcFunc['db_query']('', ' |
||
562 | SELECT version |
||
563 | FROM {db_prefix}log_packages |
||
564 | WHERE package_id = {string:current_package} |
||
565 | AND install_state != {int:not_installed} |
||
566 | ORDER BY time_installed DESC |
||
567 | LIMIT 1', |
||
568 | array( |
||
569 | 'not_installed' => 0, |
||
570 | 'current_package' => $action['id'], |
||
571 | ) |
||
572 | ); |
||
573 | $installed = ($smcFunc['db_num_rows']($request) !== 0); |
||
574 | if ($installed) |
||
575 | list ($version) = $smcFunc['db_fetch_row']($request); |
||
576 | $smcFunc['db_free_result']($request); |
||
577 | |||
578 | // do a version level check (if requested) in the most basic way |
||
579 | $version = (isset($action['version']) ? $version == $action['version'] : true); |
||
580 | } |
||
581 | |||
582 | // Set success or failure information |
||
583 | $action['description'] = ($installed && $version) ? $txt['package_action_success'] : $txt['package_action_failure']; |
||
584 | $context['has_failure'] = !($installed && $version); |
||
585 | |||
586 | $thisAction = array( |
||
587 | 'type' => $txt['package_requires'], |
||
588 | 'action' => $txt['package_check_for'] . ' ' . $action['id'] . (isset($action['version']) ? (' / ' . ($version ? $action['version'] : '<span class="error">' . $action['version'] . '</span>')) : ''), |
||
589 | ); |
||
590 | } |
||
591 | elseif (in_array($action['type'], array('require-dir', 'require-file'))) |
||
592 | { |
||
593 | // Do this one... |
||
594 | $thisAction = array( |
||
595 | 'type' => $txt['package_extract'] . ' ' . ($action['type'] == 'require-dir' ? $txt['package_tree'] : $txt['package_file']), |
||
596 | 'action' => $smcFunc['htmlspecialchars'](strtr($action['destination'], array($boarddir => '.'))) |
||
597 | ); |
||
598 | |||
599 | // Could this be theme related? |
||
600 | if (!empty($action['unparsed_destination']) && preg_match('~^\$(languagedir|languages_dir|imagesdir|themedir|themes_dir)~i', $action['unparsed_destination'], $matches)) |
||
601 | { |
||
602 | // Is the action already stated? |
||
603 | $theme_action = !empty($action['theme_action']) && in_array($action['theme_action'], array('no', 'yes', 'auto')) ? $action['theme_action'] : 'auto'; |
||
604 | // If it's not auto do we think we have something we can act upon? |
||
605 | if ($theme_action != 'auto' && !in_array($matches[1], array('languagedir', 'languages_dir', 'imagesdir', 'themedir'))) |
||
606 | $theme_action = ''; |
||
607 | // ... or if it's auto do we even want to do anything? |
||
608 | elseif ($theme_action == 'auto' && $matches[1] != 'imagesdir') |
||
609 | $theme_action = ''; |
||
610 | |||
611 | // So, we still want to do something? |
||
612 | if ($theme_action != '') |
||
613 | $themeFinds['candidates'][] = $action; |
||
614 | // Otherwise is this is going into another theme record it. |
||
615 | elseif ($matches[1] == 'themes_dir') |
||
616 | $themeFinds['other_themes'][] = strtolower(strtr(parse_path($action['unparsed_destination']), array('\\' => '/')) . '/' . basename($action['filename'])); |
||
617 | } |
||
618 | } |
||
619 | elseif (in_array($action['type'], array('move-dir', 'move-file'))) |
||
620 | $thisAction = array( |
||
621 | 'type' => $txt['package_move'] . ' ' . ($action['type'] == 'move-dir' ? $txt['package_tree'] : $txt['package_file']), |
||
622 | 'action' => $smcFunc['htmlspecialchars'](strtr($action['source'], array($boarddir => '.'))) . ' => ' . $smcFunc['htmlspecialchars'](strtr($action['destination'], array($boarddir => '.'))) |
||
623 | ); |
||
624 | elseif (in_array($action['type'], array('remove-dir', 'remove-file'))) |
||
625 | { |
||
626 | $thisAction = array( |
||
627 | 'type' => $txt['package_delete'] . ' ' . ($action['type'] == 'remove-dir' ? $txt['package_tree'] : $txt['package_file']), |
||
628 | 'action' => $smcFunc['htmlspecialchars'](strtr($action['filename'], array($boarddir => '.'))) |
||
629 | ); |
||
630 | |||
631 | // Could this be theme related? |
||
632 | if (!empty($action['unparsed_filename']) && preg_match('~^\$(languagedir|languages_dir|imagesdir|themedir|themes_dir)~i', $action['unparsed_filename'], $matches)) |
||
633 | { |
||
634 | // Is the action already stated? |
||
635 | $theme_action = !empty($action['theme_action']) && in_array($action['theme_action'], array('no', 'yes', 'auto')) ? $action['theme_action'] : 'auto'; |
||
636 | $action['unparsed_destination'] = $action['unparsed_filename']; |
||
637 | |||
638 | // If it's not auto do we think we have something we can act upon? |
||
639 | if ($theme_action != 'auto' && !in_array($matches[1], array('languagedir', 'languages_dir', 'imagesdir', 'themedir'))) |
||
640 | $theme_action = ''; |
||
641 | // ... or if it's auto do we even want to do anything? |
||
642 | elseif ($theme_action == 'auto' && $matches[1] != 'imagesdir') |
||
643 | $theme_action = ''; |
||
644 | |||
645 | // So, we still want to do something? |
||
646 | if ($theme_action != '') |
||
647 | $themeFinds['candidates'][] = $action; |
||
648 | // Otherwise is this is going into another theme record it. |
||
649 | elseif ($matches[1] == 'themes_dir') |
||
650 | $themeFinds['other_themes'][] = strtolower(strtr(parse_path($action['unparsed_filename']), array('\\' => '/')) . '/' . basename($action['filename'])); |
||
651 | } |
||
652 | } |
||
653 | |||
654 | if (empty($thisAction)) |
||
655 | continue; |
||
656 | |||
657 | if (!in_array($action['type'], array('hook', 'credits'))) |
||
658 | { |
||
659 | if ($context['uninstalling']) |
||
660 | $file = in_array($action['type'], array('remove-dir', 'remove-file')) ? $action['filename'] : $packagesdir . '/temp/' . $context['base_path'] . $action['filename']; |
||
661 | else |
||
662 | $file = $packagesdir . '/temp/' . $context['base_path'] . $action['filename']; |
||
663 | } |
||
664 | |||
665 | // Don't fail if a file/directory we're trying to create doesn't exist... |
||
666 | if (isset($action['filename']) && !file_exists($file) && !in_array($action['type'], array('create-dir', 'create-file'))) |
||
0 ignored issues
–
show
Comprehensibility
Best Practice
introduced
by
![]() |
|||
667 | { |
||
668 | $context['has_failure'] = true; |
||
669 | |||
670 | $thisAction += array( |
||
671 | 'description' => $txt['package_action_missing'], |
||
672 | 'failed' => true, |
||
673 | ); |
||
674 | } |
||
675 | |||
676 | // @todo None given? |
||
677 | if (empty($thisAction['description'])) |
||
678 | $thisAction['description'] = isset($action['description']) ? $action['description'] : ''; |
||
679 | |||
680 | $context['actions'][] = $thisAction; |
||
681 | } |
||
682 | |||
683 | // Have we got some things which we might want to do "multi-theme"? |
||
684 | if (!empty($themeFinds['candidates'])) |
||
685 | { |
||
686 | foreach ($themeFinds['candidates'] as $action_data) |
||
687 | { |
||
688 | // Get the part of the file we'll be dealing with. |
||
689 | preg_match('~^\$(languagedir|languages_dir|imagesdir|themedir)(\\|/)*(.+)*~i', $action_data['unparsed_destination'], $matches); |
||
690 | |||
691 | if ($matches[1] == 'imagesdir') |
||
692 | $path = '/' . basename($settings['default_images_url']); |
||
693 | elseif ($matches[1] == 'languagedir' || $matches[1] == 'languages_dir') |
||
694 | $path = '/languages'; |
||
695 | else |
||
696 | $path = ''; |
||
697 | |||
698 | if (!empty($matches[3])) |
||
699 | $path .= $matches[3]; |
||
700 | |||
701 | if (!$context['uninstalling']) |
||
702 | $path .= '/' . basename($action_data['filename']); |
||
703 | |||
704 | // Loop through each custom theme to note it's candidacy! |
||
705 | foreach ($theme_paths as $id => $theme_data) |
||
706 | { |
||
707 | if (isset($theme_data['theme_dir']) && $id != 1) |
||
708 | { |
||
709 | $real_path = $theme_data['theme_dir'] . $path; |
||
710 | |||
711 | // Confirm that we don't already have this dealt with by another entry. |
||
712 | if (!in_array(strtolower(strtr($real_path, array('\\' => '/'))), $themeFinds['other_themes'])) |
||
713 | { |
||
714 | // Check if we will need to chmod this. |
||
715 | if (!mktree(dirname($real_path), false)) |
||
716 | { |
||
717 | $temp = dirname($real_path); |
||
718 | while (!file_exists($temp) && strlen($temp) > 1) |
||
719 | $temp = dirname($temp); |
||
720 | $chmod_files[] = $temp; |
||
721 | } |
||
722 | |||
723 | if ($action_data['type'] == 'require-dir' && !is_writable($real_path) && (file_exists($real_path) || !is_writable(dirname($real_path)))) |
||
724 | $chmod_files[] = $real_path; |
||
725 | |||
726 | if (!isset($context['theme_actions'][$id])) |
||
727 | $context['theme_actions'][$id] = array( |
||
728 | 'name' => $theme_data['name'], |
||
729 | 'actions' => array(), |
||
730 | ); |
||
731 | |||
732 | if ($context['uninstalling']) |
||
733 | $context['theme_actions'][$id]['actions'][] = array( |
||
734 | 'type' => $txt['package_delete'] . ' ' . ($action_data['type'] == 'require-dir' ? $txt['package_tree'] : $txt['package_file']), |
||
735 | 'action' => strtr($real_path, array('\\' => '/', $boarddir => '.')), |
||
736 | 'description' => '', |
||
737 | 'value' => base64_encode($smcFunc['json_encode'](array('type' => $action_data['type'], 'orig' => $action_data['filename'], 'future' => $real_path, 'id' => $id))), |
||
738 | 'not_mod' => true, |
||
739 | ); |
||
740 | else |
||
741 | $context['theme_actions'][$id]['actions'][] = array( |
||
742 | 'type' => $txt['package_extract'] . ' ' . ($action_data['type'] == 'require-dir' ? $txt['package_tree'] : $txt['package_file']), |
||
743 | 'action' => strtr($real_path, array('\\' => '/', $boarddir => '.')), |
||
744 | 'description' => '', |
||
745 | 'value' => base64_encode($smcFunc['json_encode'](array('type' => $action_data['type'], 'orig' => $action_data['destination'], 'future' => $real_path, 'id' => $id))), |
||
746 | 'not_mod' => true, |
||
747 | ); |
||
748 | } |
||
749 | } |
||
750 | } |
||
751 | } |
||
752 | } |
||
753 | |||
754 | // Trash the cache... which will also check permissions for us! |
||
755 | package_flush_cache(true); |
||
756 | |||
757 | if (file_exists($packagesdir . '/temp')) |
||
758 | deltree($packagesdir . '/temp'); |
||
759 | |||
760 | if (!empty($chmod_files)) |
||
761 | { |
||
762 | $ftp_status = create_chmod_control($chmod_files); |
||
763 | $context['ftp_needed'] = !empty($ftp_status['files']['notwritable']) && !empty($context['package_ftp']); |
||
764 | } |
||
765 | |||
766 | $context['post_url'] = $scripturl . '?action=admin;area=packages;sa=' . ($context['uninstalling'] ? 'uninstall' : 'install') . ($context['ftp_needed'] ? '' : '2') . ';package=' . $context['filename'] . ';pid=' . $context['install_id']; |
||
767 | checkSubmitOnce('register'); |
||
768 | } |
||
769 | |||
770 | /** |
||
771 | * Apply another type of (avatar, language, etc.) package. |
||
772 | */ |
||
773 | function PackageInstall() |
||
774 | { |
||
775 | global $txt, $context, $boardurl, $scripturl, $sourcedir, $packagesdir, $modSettings; |
||
776 | global $user_info, $smcFunc; |
||
777 | |||
778 | // Make sure we don't install this mod twice. |
||
779 | checkSubmitOnce('check'); |
||
780 | checkSession(); |
||
781 | |||
782 | // If there's no file, what are we installing? |
||
783 | if (!isset($_REQUEST['package']) || $_REQUEST['package'] == '') |
||
784 | redirectexit('action=admin;area=packages'); |
||
785 | $context['filename'] = $_REQUEST['package']; |
||
786 | |||
787 | // If this is an uninstall, we'll have an id. |
||
788 | $context['install_id'] = isset($_REQUEST['pid']) ? (int) $_REQUEST['pid'] : 0; |
||
789 | |||
790 | require_once($sourcedir . '/Subs-Package.php'); |
||
791 | |||
792 | // @todo Perhaps do it in steps, if necessary? |
||
793 | |||
794 | $context['uninstalling'] = $_REQUEST['sa'] == 'uninstall2'; |
||
795 | |||
796 | // Set up the linktree for other. |
||
797 | $context['linktree'][count($context['linktree']) - 1] = array( |
||
798 | 'url' => $scripturl . '?action=admin;area=packages;sa=browse', |
||
799 | 'name' => $context['uninstalling'] ? $txt['uninstall'] : $txt['extracting'] |
||
800 | ); |
||
801 | $context['page_title'] .= ' - ' . ($context['uninstalling'] ? $txt['uninstall'] : $txt['extracting']); |
||
802 | |||
803 | $context['sub_template'] = 'extract_package'; |
||
804 | |||
805 | if (!file_exists($packagesdir . '/' . $context['filename'])) |
||
806 | fatal_lang_error('package_no_file', false); |
||
807 | |||
808 | // Load up the package FTP information? |
||
809 | create_chmod_control(array(), array('destination_url' => $scripturl . '?action=admin;area=packages;sa=' . $_REQUEST['sa'] . ';package=' . $_REQUEST['package'])); |
||
810 | |||
811 | // Make sure temp directory exists and is empty! |
||
812 | if (file_exists($packagesdir . '/temp')) |
||
813 | deltree($packagesdir . '/temp', false); |
||
814 | else |
||
815 | mktree($packagesdir . '/temp', 0777); |
||
816 | |||
817 | // Let the unpacker do the work. |
||
818 | if (is_file($packagesdir . '/' . $context['filename'])) |
||
819 | { |
||
820 | $context['extracted_files'] = read_tgz_file($packagesdir . '/' . $context['filename'], $packagesdir . '/temp'); |
||
821 | |||
822 | if (!file_exists($packagesdir . '/temp/package-info.xml')) |
||
823 | foreach ($context['extracted_files'] as $file) |
||
824 | if (basename($file['filename']) == 'package-info.xml') |
||
825 | { |
||
826 | $context['base_path'] = dirname($file['filename']) . '/'; |
||
827 | break; |
||
828 | } |
||
829 | |||
830 | if (!isset($context['base_path'])) |
||
831 | $context['base_path'] = ''; |
||
832 | } |
||
833 | elseif (is_dir($packagesdir . '/' . $context['filename'])) |
||
834 | { |
||
835 | copytree($packagesdir . '/' . $context['filename'], $packagesdir . '/temp'); |
||
836 | $context['extracted_files'] = listtree($packagesdir . '/temp'); |
||
837 | $context['base_path'] = ''; |
||
838 | } |
||
839 | else |
||
840 | fatal_lang_error('no_access', false); |
||
841 | |||
842 | // Are we installing this into any custom themes? |
||
843 | $custom_themes = array(1); |
||
844 | $known_themes = explode(',', $modSettings['knownThemes']); |
||
845 | if (!empty($_POST['custom_theme'])) |
||
846 | { |
||
847 | foreach ($_POST['custom_theme'] as $tid) |
||
848 | if (in_array($tid, $known_themes)) |
||
849 | $custom_themes[] = (int) $tid; |
||
850 | } |
||
851 | |||
852 | // Now load up the paths of the themes that we need to know about. |
||
853 | $request = $smcFunc['db_query']('', ' |
||
854 | SELECT id_theme, variable, value |
||
855 | FROM {db_prefix}themes |
||
856 | WHERE id_theme IN ({array_int:custom_themes}) |
||
857 | AND variable IN ({string:name}, {string:theme_dir})', |
||
858 | array( |
||
859 | 'custom_themes' => $custom_themes, |
||
860 | 'name' => 'name', |
||
861 | 'theme_dir' => 'theme_dir', |
||
862 | ) |
||
863 | ); |
||
864 | $theme_paths = array(); |
||
865 | $themes_installed = array(1); |
||
866 | |||
867 | while ($row = $smcFunc['db_fetch_assoc']($request)) |
||
868 | $theme_paths[$row['id_theme']][$row['variable']] = $row['value']; |
||
869 | |||
870 | $smcFunc['db_free_result']($request); |
||
871 | |||
872 | // Are there any theme copying that we want to take place? |
||
873 | $context['theme_copies'] = array( |
||
874 | 'require-file' => array(), |
||
875 | 'require-dir' => array(), |
||
876 | ); |
||
877 | if (!empty($_POST['theme_changes'])) |
||
878 | { |
||
879 | foreach ($_POST['theme_changes'] as $change) |
||
880 | { |
||
881 | if (empty($change)) |
||
882 | continue; |
||
883 | $theme_data = $smcFunc['json_decode'](base64_decode($change), true); |
||
884 | if (empty($theme_data['type'])) |
||
885 | continue; |
||
886 | |||
887 | $themes_installed[] = $theme_data['id']; |
||
888 | $context['theme_copies'][$theme_data['type']][$theme_data['orig']][] = $theme_data['future']; |
||
889 | } |
||
890 | } |
||
891 | |||
892 | // Get the package info... |
||
893 | $packageInfo = getPackageInfo($context['filename']); |
||
894 | if (!is_array($packageInfo)) |
||
895 | fatal_lang_error($packageInfo); |
||
896 | |||
897 | $context['package_sha256_hash'] = hash_file('sha256', $packagesdir . '/' . $context['filename']); |
||
898 | $packageInfo['filename'] = $context['filename']; |
||
899 | |||
900 | // Set the type of extraction... |
||
901 | $context['extract_type'] = isset($packageInfo['type']) ? $packageInfo['type'] : 'modification'; |
||
902 | |||
903 | // Create a backup file to roll back to! (but if they do this more than once, don't run it a zillion times.) |
||
904 | if (!empty($modSettings['package_make_full_backups']) && (!isset($_SESSION['last_backup_for']) || $_SESSION['last_backup_for'] != $context['filename'] . ($context['uninstalling'] ? '$$' : '$'))) |
||
905 | { |
||
906 | $_SESSION['last_backup_for'] = $context['filename'] . ($context['uninstalling'] ? '$$' : '$'); |
||
907 | $result = package_create_backup(($context['uninstalling'] ? 'backup_' : 'before_') . strtok($context['filename'], '.')); |
||
908 | if (!$result) |
||
909 | fatal_lang_error('could_not_package_backup', false); |
||
910 | } |
||
911 | |||
912 | // The mod isn't installed.... unless proven otherwise. |
||
913 | $context['is_installed'] = false; |
||
914 | |||
915 | // Is it actually installed? |
||
916 | $request = $smcFunc['db_query']('', ' |
||
917 | SELECT version, themes_installed, db_changes |
||
918 | FROM {db_prefix}log_packages |
||
919 | WHERE package_id = {string:current_package} |
||
920 | AND install_state != {int:not_installed} |
||
921 | ORDER BY time_installed DESC |
||
922 | LIMIT 1', |
||
923 | array( |
||
924 | 'not_installed' => 0, |
||
925 | 'current_package' => $packageInfo['id'], |
||
926 | ) |
||
927 | ); |
||
928 | while ($row = $smcFunc['db_fetch_assoc']($request)) |
||
929 | { |
||
930 | $old_themes = explode(',', $row['themes_installed']); |
||
931 | $old_version = $row['version']; |
||
932 | $db_changes = empty($row['db_changes']) ? array() : $smcFunc['json_decode']($row['db_changes'], true); |
||
933 | } |
||
934 | $smcFunc['db_free_result']($request); |
||
935 | |||
936 | // Wait, it's not installed yet! |
||
937 | // @todo Replace with a better error message! |
||
938 | if (!isset($old_version) && $context['uninstalling']) |
||
939 | { |
||
940 | deltree($packagesdir . '/temp'); |
||
941 | fatal_error('Hacker?', false); |
||
942 | } |
||
943 | // Uninstalling? |
||
944 | elseif ($context['uninstalling']) |
||
945 | { |
||
946 | $install_log = parsePackageInfo($packageInfo['xml'], false, 'uninstall'); |
||
947 | |||
948 | // Gadzooks! There's no uninstaller at all!? |
||
949 | if (empty($install_log)) |
||
950 | fatal_lang_error('package_uninstall_cannot', false); |
||
951 | |||
952 | // They can only uninstall from what it was originally installed into. |
||
953 | foreach ($theme_paths as $id => $data) |
||
954 | if ($id != 1 && !in_array($id, $old_themes)) |
||
955 | unset($theme_paths[$id]); |
||
956 | } |
||
957 | elseif (isset($old_version) && $old_version != $packageInfo['version']) |
||
958 | { |
||
959 | // Look for an upgrade... |
||
960 | $install_log = parsePackageInfo($packageInfo['xml'], false, 'upgrade', $old_version); |
||
961 | |||
962 | // There was no upgrade.... |
||
963 | if (empty($install_log)) |
||
964 | $context['is_installed'] = true; |
||
965 | else |
||
966 | { |
||
967 | // Upgrade previous themes only! |
||
968 | foreach ($theme_paths as $id => $data) |
||
969 | if ($id != 1 && !in_array($id, $old_themes)) |
||
970 | unset($theme_paths[$id]); |
||
971 | } |
||
972 | } |
||
973 | elseif (isset($old_version) && $old_version == $packageInfo['version']) |
||
974 | $context['is_installed'] = true; |
||
975 | |||
976 | if (!isset($old_version) || $context['is_installed']) |
||
977 | $install_log = parsePackageInfo($packageInfo['xml'], false, 'install'); |
||
978 | |||
979 | $context['install_finished'] = false; |
||
980 | |||
981 | // @todo Make a log of any errors that occurred and output them? |
||
982 | |||
983 | if (!empty($install_log)) |
||
984 | { |
||
985 | $failed_steps = array(); |
||
986 | $failed_count = 0; |
||
987 | |||
988 | foreach ($install_log as $action) |
||
989 | { |
||
990 | $failed_count++; |
||
991 | |||
992 | if ($action['type'] == 'modification' && !empty($action['filename'])) |
||
993 | { |
||
994 | if ($action['boardmod']) |
||
995 | $mod_actions = parseBoardMod(file_get_contents($packagesdir . '/temp/' . $context['base_path'] . $action['filename']), false, $action['reverse'], $theme_paths); |
||
996 | else |
||
997 | $mod_actions = parseModification(file_get_contents($packagesdir . '/temp/' . $context['base_path'] . $action['filename']), false, $action['reverse'], $theme_paths); |
||
998 | |||
999 | // Any errors worth noting? |
||
1000 | foreach ($mod_actions as $key => $modAction) |
||
1001 | { |
||
1002 | if ($modAction['type'] == 'failure') |
||
1003 | $failed_steps[] = array( |
||
1004 | 'file' => $modAction['filename'], |
||
1005 | 'large_step' => $failed_count, |
||
1006 | 'sub_step' => $key, |
||
1007 | 'theme' => 1, |
||
1008 | ); |
||
1009 | |||
1010 | // Gather the themes we installed into. |
||
1011 | if (!empty($modAction['is_custom'])) |
||
1012 | $themes_installed[] = $modAction['is_custom']; |
||
1013 | } |
||
1014 | } |
||
1015 | elseif ($action['type'] == 'code' && !empty($action['filename'])) |
||
1016 | { |
||
1017 | // This is just here as reference for what is available. |
||
1018 | global $txt, $boarddir, $sourcedir, $modSettings, $context, $settings, $smcFunc; |
||
1019 | |||
1020 | // Now include the file and be done with it ;). |
||
1021 | if (file_exists($packagesdir . '/temp/' . $context['base_path'] . $action['filename'])) |
||
1022 | require($packagesdir . '/temp/' . $context['base_path'] . $action['filename']); |
||
1023 | } |
||
1024 | elseif ($action['type'] == 'credits') |
||
1025 | { |
||
1026 | // Time to build the billboard |
||
1027 | $credits_tag = array( |
||
1028 | 'url' => $action['url'], |
||
1029 | 'license' => $action['license'], |
||
1030 | 'licenseurl' => $action['licenseurl'], |
||
1031 | 'copyright' => $action['copyright'], |
||
1032 | 'title' => $action['title'], |
||
1033 | ); |
||
1034 | } |
||
1035 | elseif ($action['type'] == 'hook' && isset($action['hook'], $action['function'])) |
||
1036 | { |
||
1037 | // Set the system to ignore hooks, but only if it wasn't changed before. |
||
1038 | if (!isset($context['ignore_hook_errors'])) |
||
1039 | $context['ignore_hook_errors'] = true; |
||
1040 | |||
1041 | if ($action['reverse']) |
||
1042 | remove_integration_function($action['hook'], $action['function'], true, $action['include_file'], $action['object']); |
||
1043 | else |
||
1044 | add_integration_function($action['hook'], $action['function'], true, $action['include_file'], $action['object']); |
||
1045 | } |
||
1046 | // Only do the database changes on uninstall if requested. |
||
1047 | elseif ($action['type'] == 'database' && !empty($action['filename']) && (!$context['uninstalling'] || !empty($_POST['do_db_changes']))) |
||
1048 | { |
||
1049 | // These can also be there for database changes. |
||
1050 | global $txt, $boarddir, $sourcedir, $modSettings, $context, $settings, $smcFunc; |
||
1051 | global $db_package_log; |
||
1052 | |||
1053 | // We'll likely want the package specific database functionality! |
||
1054 | db_extend('packages'); |
||
1055 | |||
1056 | // Let the file work its magic ;) |
||
1057 | if (file_exists($packagesdir . '/temp/' . $context['base_path'] . $action['filename'])) |
||
1058 | require($packagesdir . '/temp/' . $context['base_path'] . $action['filename']); |
||
1059 | } |
||
1060 | // Handle a redirect... |
||
1061 | elseif ($action['type'] == 'redirect' && !empty($action['redirect_url'])) |
||
1062 | { |
||
1063 | $context['redirect_url'] = $action['redirect_url']; |
||
1064 | $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']); |
||
1065 | $context['redirect_timeout'] = empty($action['redirect_timeout']) ? 5 : (int) ceil($action['redirect_timeout'] / 1000); |
||
1066 | if (!empty($action['parse_bbc'])) |
||
1067 | { |
||
1068 | require_once($sourcedir . '/Subs-Post.php'); |
||
1069 | $context['redirect_text'] = preg_replace('~\[[/]?html\]~i', '', $context['redirect_text']); |
||
1070 | preparsecode($context['redirect_text']); |
||
1071 | $context['redirect_text'] = parse_bbc($context['redirect_text']); |
||
1072 | } |
||
1073 | |||
1074 | // Parse out a couple of common urls. |
||
1075 | $urls = array( |
||
1076 | '$boardurl' => $boardurl, |
||
1077 | '$scripturl' => $scripturl, |
||
1078 | '$session_var' => $context['session_var'], |
||
1079 | '$session_id' => $context['session_id'], |
||
1080 | ); |
||
1081 | |||
1082 | $context['redirect_url'] = strtr($context['redirect_url'], $urls); |
||
1083 | } |
||
1084 | } |
||
1085 | |||
1086 | package_flush_cache(); |
||
1087 | |||
1088 | // See if this is already installed, and change it's state as required. |
||
1089 | $request = $smcFunc['db_query']('', ' |
||
1090 | SELECT package_id, install_state, db_changes |
||
1091 | FROM {db_prefix}log_packages |
||
1092 | WHERE install_state != {int:not_installed} |
||
1093 | AND package_id = {string:current_package} |
||
1094 | ' . ($context['install_id'] ? ' AND id_install = {int:install_id} ' : '') . ' |
||
1095 | ORDER BY time_installed DESC |
||
1096 | LIMIT 1', |
||
1097 | array( |
||
1098 | 'not_installed' => 0, |
||
1099 | 'install_id' => $context['install_id'], |
||
1100 | 'current_package' => $packageInfo['id'], |
||
1101 | ) |
||
1102 | ); |
||
1103 | $is_upgrade = false; |
||
1104 | while ($row = $smcFunc['db_fetch_assoc']($request)) |
||
1105 | { |
||
1106 | // Uninstalling? |
||
1107 | if ($context['uninstalling']) |
||
1108 | { |
||
1109 | $smcFunc['db_query']('', ' |
||
1110 | UPDATE {db_prefix}log_packages |
||
1111 | SET install_state = {int:not_installed}, member_removed = {string:member_name}, |
||
1112 | id_member_removed = {int:current_member}, time_removed = {int:current_time}, sha256_hash = {string:package_hash} |
||
1113 | WHERE package_id = {string:package_id} |
||
1114 | AND id_install = {int:install_id}', |
||
1115 | array( |
||
1116 | 'current_member' => $user_info['id'], |
||
1117 | 'not_installed' => 0, |
||
1118 | 'current_time' => time(), |
||
1119 | 'package_id' => $row['package_id'], |
||
1120 | 'member_name' => $user_info['name'], |
||
1121 | 'install_id' => $context['install_id'], |
||
1122 | 'package_hash' => $context['package_sha256_hash'], |
||
1123 | ) |
||
1124 | ); |
||
1125 | } |
||
1126 | // Otherwise must be an upgrade. |
||
1127 | else |
||
1128 | { |
||
1129 | $is_upgrade = true; |
||
1130 | $old_db_changes = empty($row['db_changes']) ? array() : $smcFunc['json_decode']($row['db_changes'], true); |
||
1131 | |||
1132 | // Mark the old version as uninstalled |
||
1133 | $smcFunc['db_query']('', ' |
||
1134 | UPDATE {db_prefix}log_packages |
||
1135 | SET install_state = {int:not_installed}, member_removed = {string:member_name}, |
||
1136 | id_member_removed = {int:current_member}, time_removed = {int:current_time}, sha256_hash = {string:package_hash} |
||
1137 | WHERE package_id = {string:package_id} |
||
1138 | AND version = {string:old_version}', |
||
1139 | array( |
||
1140 | 'current_member' => $user_info['id'], |
||
1141 | 'not_installed' => 0, |
||
1142 | 'current_time' => time(), |
||
1143 | 'package_id' => $row['package_id'], |
||
1144 | 'member_name' => $user_info['name'], |
||
1145 | 'old_version' => $old_version, |
||
1146 | 'package_hash' => $context['package_sha256_hash'], |
||
1147 | ) |
||
1148 | ); |
||
1149 | } |
||
1150 | } |
||
1151 | |||
1152 | // Assuming we're not uninstalling, add the entry. |
||
1153 | if (!$context['uninstalling']) |
||
1154 | { |
||
1155 | // Reload the settings table for mods that have altered them upon installation |
||
1156 | reloadSettings(); |
||
1157 | |||
1158 | // Any db changes from older version? |
||
1159 | if (!empty($old_db_changes)) |
||
1160 | $db_package_log = empty($db_package_log) ? $old_db_changes : array_merge($old_db_changes, $db_package_log); |
||
1161 | |||
1162 | // If there are some database changes we might want to remove then filter them out. |
||
1163 | if (!empty($db_package_log)) |
||
1164 | { |
||
1165 | // We're really just checking for entries which are create table AND add columns (etc). |
||
1166 | $tables = array(); |
||
1167 | |||
1168 | usort($db_package_log, function($a, $b) |
||
1169 | { |
||
1170 | if ($a[0] == $b[0]) |
||
1171 | return 0; |
||
1172 | return $a[0] == 'remove_table' ? -1 : 1; |
||
1173 | }); |
||
1174 | |||
1175 | foreach ($db_package_log as $k => $log) |
||
1176 | { |
||
1177 | if ($log[0] == 'remove_table') |
||
1178 | $tables[] = $log[1]; |
||
1179 | elseif (in_array($log[1], $tables)) |
||
1180 | unset($db_package_log[$k]); |
||
1181 | } |
||
1182 | $db_changes = $smcFunc['json_encode']($db_package_log); |
||
1183 | } |
||
1184 | else |
||
1185 | $db_changes = ''; |
||
1186 | |||
1187 | // What themes did we actually install? |
||
1188 | $themes_installed = array_unique($themes_installed); |
||
1189 | $themes_installed = implode(',', $themes_installed); |
||
1190 | |||
1191 | // What failed steps? |
||
1192 | $failed_step_insert = $smcFunc['json_encode']($failed_steps); |
||
1193 | |||
1194 | // Un-sanitize things before we insert them... |
||
1195 | $keys = array('filename', 'name', 'id', 'version'); |
||
1196 | foreach ($keys as $key) |
||
1197 | { |
||
1198 | // Yay for variable variables... |
||
1199 | ${"package_$key"} = un_htmlspecialchars($packageInfo[$key]); |
||
1200 | } |
||
1201 | |||
1202 | // Credits tag? |
||
1203 | $credits_tag = (empty($credits_tag)) ? '' : $smcFunc['json_encode']($credits_tag); |
||
1204 | $smcFunc['db_insert']('', |
||
1205 | '{db_prefix}log_packages', |
||
1206 | array( |
||
1207 | 'filename' => 'string', 'name' => 'string', 'package_id' => 'string', 'version' => 'string', |
||
1208 | 'id_member_installed' => 'int', 'member_installed' => 'string', 'time_installed' => 'int', |
||
1209 | 'install_state' => 'int', 'failed_steps' => 'string', 'themes_installed' => 'string', |
||
1210 | 'member_removed' => 'int', 'db_changes' => 'string', 'credits' => 'string', |
||
1211 | 'sha256_hash' => 'string', |
||
1212 | ), |
||
1213 | array( |
||
1214 | $package_filename, $package_name, $package_id, $package_version, |
||
0 ignored issues
–
show
Comprehensibility
Best Practice
introduced
by
Comprehensibility
Best Practice
introduced
by
Comprehensibility
Best Practice
introduced
by
Comprehensibility
Best Practice
introduced
by
|
|||
1215 | $user_info['id'], $user_info['name'], time(), |
||
1216 | $is_upgrade ? 2 : 1, $failed_step_insert, $themes_installed, |
||
1217 | 0, $db_changes, $credits_tag, $context['package_sha256_hash'] |
||
1218 | ), |
||
1219 | array('id_install') |
||
1220 | ); |
||
1221 | } |
||
1222 | $smcFunc['db_free_result']($request); |
||
1223 | |||
1224 | $context['install_finished'] = true; |
||
1225 | } |
||
1226 | |||
1227 | // If there's database changes - and they want them removed - let's do it last! |
||
1228 | if (!empty($db_changes) && !empty($_POST['do_db_changes'])) |
||
1229 | { |
||
1230 | // We're gonna be needing the package db functions! |
||
1231 | db_extend('packages'); |
||
1232 | |||
1233 | foreach ($db_changes as $change) |
||
1234 | { |
||
1235 | if ($change[0] == 'remove_table' && isset($change[1])) |
||
1236 | $smcFunc['db_drop_table']($change[1]); |
||
1237 | elseif ($change[0] == 'remove_column' && isset($change[2])) |
||
1238 | $smcFunc['db_remove_column']($change[1], $change[2]); |
||
1239 | elseif ($change[0] == 'remove_index' && isset($change[2])) |
||
1240 | $smcFunc['db_remove_index']($change[1], $change[2]); |
||
1241 | } |
||
1242 | } |
||
1243 | |||
1244 | // Clean house... get rid of the evidence ;). |
||
1245 | if (file_exists($packagesdir . '/temp')) |
||
1246 | deltree($packagesdir . '/temp'); |
||
1247 | |||
1248 | // Log what we just did. |
||
1249 | logAction($context['uninstalling'] ? 'uninstall_package' : (!empty($is_upgrade) ? 'upgrade_package' : 'install_package'), array('package' => $smcFunc['htmlspecialchars']($packageInfo['name']), 'version' => $smcFunc['htmlspecialchars']($packageInfo['version'])), 'admin'); |
||
1250 | |||
1251 | // Just in case, let's clear the whole cache and any minimized CSS and JS to avoid anything going up the swanny. |
||
1252 | clean_cache(); |
||
1253 | deleteAllMinified(); |
||
1254 | |||
1255 | // Restore file permissions? |
||
1256 | create_chmod_control(array(), array(), true); |
||
1257 | } |
||
1258 | |||
1259 | /** |
||
1260 | * List the files in a package. |
||
1261 | */ |
||
1262 | function PackageList() |
||
1263 | { |
||
1264 | global $txt, $scripturl, $context, $sourcedir, $packagesdir; |
||
1265 | |||
1266 | require_once($sourcedir . '/Subs-Package.php'); |
||
1267 | |||
1268 | // No package? Show him or her the door. |
||
1269 | if (!isset($_REQUEST['package']) || $_REQUEST['package'] == '') |
||
1270 | redirectexit('action=admin;area=packages'); |
||
1271 | |||
1272 | $context['linktree'][] = array( |
||
1273 | 'url' => $scripturl . '?action=admin;area=packages;sa=list;package=' . $_REQUEST['package'], |
||
1274 | 'name' => $txt['list_file'] |
||
1275 | ); |
||
1276 | $context['page_title'] .= ' - ' . $txt['list_file']; |
||
1277 | $context['sub_template'] = 'list'; |
||
1278 | |||
1279 | // The filename... |
||
1280 | $context['filename'] = $_REQUEST['package']; |
||
1281 | |||
1282 | // Let the unpacker do the work. |
||
1283 | if (is_file($packagesdir . '/' . $context['filename'])) |
||
1284 | $context['files'] = read_tgz_file($packagesdir . '/' . $context['filename'], null); |
||
1285 | elseif (is_dir($packagesdir . '/' . $context['filename'])) |
||
1286 | $context['files'] = listtree($packagesdir . '/' . $context['filename']); |
||
1287 | } |
||
1288 | |||
1289 | /** |
||
1290 | * Display one of the files in a package. |
||
1291 | */ |
||
1292 | function ExamineFile() |
||
1293 | { |
||
1294 | global $txt, $scripturl, $context, $sourcedir, $packagesdir, $smcFunc; |
||
1295 | |||
1296 | require_once($sourcedir . '/Subs-Package.php'); |
||
1297 | |||
1298 | // No package? Show him or her the door. |
||
1299 | if (!isset($_REQUEST['package']) || $_REQUEST['package'] == '') |
||
1300 | redirectexit('action=admin;area=packages'); |
||
1301 | |||
1302 | // No file? Show him or her the door. |
||
1303 | if (!isset($_REQUEST['file']) || $_REQUEST['file'] == '') |
||
1304 | redirectexit('action=admin;area=packages'); |
||
1305 | |||
1306 | $_REQUEST['package'] = preg_replace('~[\.]+~', '.', strtr($_REQUEST['package'], array('/' => '_', '\\' => '_'))); |
||
1307 | $_REQUEST['file'] = preg_replace('~[\.]+~', '.', $_REQUEST['file']); |
||
1308 | |||
1309 | if (isset($_REQUEST['raw'])) |
||
1310 | { |
||
1311 | if (is_file($packagesdir . '/' . $_REQUEST['package'])) |
||
1312 | echo read_tgz_file($packagesdir . '/' . $_REQUEST['package'], $_REQUEST['file'], true); |
||
1313 | elseif (is_dir($packagesdir . '/' . $_REQUEST['package'])) |
||
1314 | echo file_get_contents($packagesdir . '/' . $_REQUEST['package'] . '/' . $_REQUEST['file']); |
||
1315 | |||
1316 | obExit(false); |
||
1317 | } |
||
1318 | |||
1319 | $context['linktree'][count($context['linktree']) - 1] = array( |
||
1320 | 'url' => $scripturl . '?action=admin;area=packages;sa=list;package=' . $_REQUEST['package'], |
||
1321 | 'name' => $txt['package_examine_file'] |
||
1322 | ); |
||
1323 | $context['page_title'] .= ' - ' . $txt['package_examine_file']; |
||
1324 | $context['sub_template'] = 'examine'; |
||
1325 | |||
1326 | // The filename... |
||
1327 | $context['package'] = $_REQUEST['package']; |
||
1328 | $context['filename'] = $_REQUEST['file']; |
||
1329 | |||
1330 | // Let the unpacker do the work.... but make sure we handle images properly. |
||
1331 | if (in_array(strtolower(strrchr($_REQUEST['file'], '.')), array('.bmp', '.gif', '.jpeg', '.jpg', '.png'))) |
||
1332 | $context['filedata'] = '<img src="' . $scripturl . '?action=admin;area=packages;sa=examine;package=' . $_REQUEST['package'] . ';file=' . $_REQUEST['file'] . ';raw" alt="' . $_REQUEST['file'] . '">'; |
||
1333 | else |
||
1334 | { |
||
1335 | if (is_file($packagesdir . '/' . $_REQUEST['package'])) |
||
1336 | $context['filedata'] = $smcFunc['htmlspecialchars'](read_tgz_file($packagesdir . '/' . $_REQUEST['package'], $_REQUEST['file'], true)); |
||
1337 | elseif (is_dir($packagesdir . '/' . $_REQUEST['package'])) |
||
1338 | $context['filedata'] = $smcFunc['htmlspecialchars'](file_get_contents($packagesdir . '/' . $_REQUEST['package'] . '/' . $_REQUEST['file'])); |
||
1339 | |||
1340 | if (strtolower(strrchr($_REQUEST['file'], '.')) == '.php') |
||
1341 | $context['filedata'] = highlight_php_code($context['filedata']); |
||
1342 | } |
||
1343 | } |
||
1344 | |||
1345 | /** |
||
1346 | * Delete a package. |
||
1347 | */ |
||
1348 | function PackageRemove() |
||
1349 | { |
||
1350 | global $scripturl, $packagesdir; |
||
1351 | |||
1352 | // Check it. |
||
1353 | checkSession('get'); |
||
1354 | |||
1355 | // Ack, don't allow deletion of arbitrary files here, could become a security hole somehow! |
||
1356 | if (!isset($_GET['package']) || $_GET['package'] == 'index.php' || $_GET['package'] == 'backups') |
||
1357 | redirectexit('action=admin;area=packages;sa=browse'); |
||
1358 | $_GET['package'] = preg_replace('~[\.]+~', '.', strtr($_GET['package'], array('/' => '_', '\\' => '_'))); |
||
1359 | |||
1360 | // Can't delete what's not there. |
||
1361 | 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) != '.') |
||
1362 | { |
||
1363 | create_chmod_control(array($packagesdir . '/' . $_GET['package']), array('destination_url' => $scripturl . '?action=admin;area=packages;sa=remove;package=' . $_GET['package'], 'crash_on_error' => true)); |
||
1364 | |||
1365 | if (is_dir($packagesdir . '/' . $_GET['package'])) |
||
1366 | deltree($packagesdir . '/' . $_GET['package']); |
||
1367 | else |
||
1368 | { |
||
1369 | smf_chmod($packagesdir . '/' . $_GET['package'], 0777); |
||
1370 | unlink($packagesdir . '/' . $_GET['package']); |
||
1371 | } |
||
1372 | } |
||
1373 | |||
1374 | redirectexit('action=admin;area=packages;sa=browse'); |
||
1375 | } |
||
1376 | |||
1377 | /** |
||
1378 | * Browse a list of installed packages. |
||
1379 | */ |
||
1380 | function PackageBrowse() |
||
1381 | { |
||
1382 | global $txt, $scripturl, $context, $sourcedir, $smcFunc; |
||
1383 | |||
1384 | $context['page_title'] .= ' - ' . $txt['browse_packages']; |
||
1385 | |||
1386 | $context['forum_version'] = SMF_FULL_VERSION; |
||
1387 | $context['available_packages'] = 0; |
||
1388 | $context['modification_types'] = array('modification', 'avatar', 'language', 'unknown'); |
||
1389 | |||
1390 | call_integration_hook('integrate_modification_types'); |
||
1391 | |||
1392 | require_once($sourcedir . '/Subs-List.php'); |
||
1393 | |||
1394 | foreach ($context['modification_types'] as $type) |
||
1395 | { |
||
1396 | // Use the standard templates for showing this. |
||
1397 | $listOptions = array( |
||
1398 | 'id' => 'packages_lists_' . $type, |
||
1399 | 'title' => $txt[$type . '_package'], |
||
1400 | 'no_items_label' => $txt['no_packages'], |
||
1401 | 'get_items' => array( |
||
1402 | 'function' => 'list_getPackages', |
||
1403 | 'params' => array('type' => $type), |
||
1404 | ), |
||
1405 | 'base_href' => $scripturl . '?action=admin;area=packages;sa=browse;type=' . $type, |
||
1406 | 'default_sort_col' => 'id' . $type, |
||
1407 | 'columns' => array( |
||
1408 | 'id' . $type => array( |
||
1409 | 'header' => array( |
||
1410 | 'value' => $txt['package_id'], |
||
1411 | 'style' => 'width: 40px;', |
||
1412 | ), |
||
1413 | 'data' => array( |
||
1414 | 'db' => 'sort_id', |
||
1415 | ), |
||
1416 | 'sort' => array( |
||
1417 | 'default' => 'sort_id', |
||
1418 | 'reverse' => 'sort_id' |
||
1419 | ), |
||
1420 | ), |
||
1421 | 'mod_name' . $type => array( |
||
1422 | 'header' => array( |
||
1423 | 'value' => $txt['mod_name'], |
||
1424 | 'style' => 'width: 25%;', |
||
1425 | ), |
||
1426 | 'data' => array( |
||
1427 | 'db' => 'name', |
||
1428 | ), |
||
1429 | 'sort' => array( |
||
1430 | 'default' => 'name', |
||
1431 | 'reverse' => 'name', |
||
1432 | ), |
||
1433 | ), |
||
1434 | 'version' . $type => array( |
||
1435 | 'header' => array( |
||
1436 | 'value' => $txt['mod_version'], |
||
1437 | ), |
||
1438 | 'data' => array( |
||
1439 | 'db' => 'version', |
||
1440 | ), |
||
1441 | 'sort' => array( |
||
1442 | 'default' => 'version', |
||
1443 | 'reverse' => 'version', |
||
1444 | ), |
||
1445 | ), |
||
1446 | 'time_installed' . $type => array( |
||
1447 | 'header' => array( |
||
1448 | 'value' => $txt['mod_installed_time'], |
||
1449 | ), |
||
1450 | 'data' => array( |
||
1451 | 'function' => function($package) use ($txt) |
||
1452 | { |
||
1453 | return !empty($package['time_installed']) |
||
1454 | ? timeformat($package['time_installed']) |
||
1455 | : $txt['not_applicable']; |
||
1456 | }, |
||
1457 | 'class' => 'smalltext', |
||
1458 | ), |
||
1459 | 'sort' => array( |
||
1460 | 'default' => 'time_installed', |
||
1461 | 'reverse' => 'time_installed', |
||
1462 | ), |
||
1463 | ), |
||
1464 | 'operations' . $type => array( |
||
1465 | 'header' => array( |
||
1466 | 'value' => '', |
||
1467 | ), |
||
1468 | 'data' => array( |
||
1469 | 'function' => function($package) use ($context, $scripturl, $txt) |
||
1470 | { |
||
1471 | $return = ''; |
||
1472 | |||
1473 | if ($package['can_uninstall']) |
||
1474 | $return = ' |
||
1475 | <a href="' . $scripturl . '?action=admin;area=packages;sa=uninstall;package=' . $package['filename'] . ';pid=' . $package['installed_id'] . '" class="button floatnone">' . $txt['uninstall'] . '</a>'; |
||
1476 | elseif ($package['can_emulate_uninstall']) |
||
1477 | $return = ' |
||
1478 | <a href="' . $scripturl . '?action=admin;area=packages;sa=uninstall;ve=' . $package['can_emulate_uninstall'] . ';package=' . $package['filename'] . ';pid=' . $package['installed_id'] . '" class="button floatnone">' . $txt['package_emulate_uninstall'] . ' ' . $package['can_emulate_uninstall'] . '</a>'; |
||
1479 | elseif ($package['can_upgrade']) |
||
1480 | $return = ' |
||
1481 | <a href="' . $scripturl . '?action=admin;area=packages;sa=install;package=' . $package['filename'] . '" class="button" floatnone>' . $txt['package_upgrade'] . '</a>'; |
||
1482 | elseif ($package['can_install']) |
||
1483 | $return = ' |
||
1484 | <a href="' . $scripturl . '?action=admin;area=packages;sa=install;package=' . $package['filename'] . '" class="button floatnone">' . $txt['install_mod'] . '</a>'; |
||
1485 | elseif ($package['can_emulate_install']) |
||
1486 | $return = ' |
||
1487 | <a href="' . $scripturl . '?action=admin;area=packages;sa=install;ve=' . $package['can_emulate_install'] . ';package=' . $package['filename'] . '" class="button floatnone">' . $txt['package_emulate_install'] . ' ' . $package['can_emulate_install'] . '</a>'; |
||
1488 | |||
1489 | return $return . ' |
||
1490 | <a href="' . $scripturl . '?action=admin;area=packages;sa=list;package=' . $package['filename'] . '" class="button floatnone">' . $txt['list_files'] . '</a> |
||
1491 | <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' : '') . ' floatnone">' . $txt['package_delete'] . '</a>'; |
||
1492 | }, |
||
1493 | 'class' => 'righttext', |
||
1494 | ), |
||
1495 | ), |
||
1496 | ), |
||
1497 | ); |
||
1498 | |||
1499 | createList($listOptions); |
||
1500 | } |
||
1501 | |||
1502 | $context['sub_template'] = 'browse'; |
||
1503 | $context['default_list'] = 'packages_lists'; |
||
1504 | |||
1505 | $get_versions = $smcFunc['db_query']('', ' |
||
1506 | SELECT data FROM {db_prefix}admin_info_files WHERE filename={string:versionsfile} AND path={string:smf}', |
||
1507 | array( |
||
1508 | 'versionsfile' => 'latest-versions.txt', |
||
1509 | 'smf' => '/smf/', |
||
1510 | ) |
||
1511 | ); |
||
1512 | |||
1513 | $data = $smcFunc['db_fetch_assoc']($get_versions); |
||
1514 | $smcFunc['db_free_result']($get_versions); |
||
1515 | |||
1516 | // Decode the data. |
||
1517 | $items = $smcFunc['json_decode']($data['data'], true); |
||
1518 | |||
1519 | $context['emulation_versions'] = preg_replace('~^SMF ~', '', $items); |
||
1520 | |||
1521 | // Current SMF version, which is selected by default |
||
1522 | $context['default_version'] = SMF_VERSION; |
||
1523 | |||
1524 | $context['emulation_versions'][] = $context['default_version']; |
||
1525 | |||
1526 | // Version we're currently emulating, if any |
||
1527 | $context['selected_version'] = preg_replace('~^SMF ~', '', $context['forum_version']); |
||
1528 | } |
||
1529 | |||
1530 | /** |
||
1531 | * Get a listing of all the packages |
||
1532 | * |
||
1533 | * Determines if the package is a mod, avatar, or language package and |
||
1534 | * groups it accordingly. If a package is not recognised as one of the |
||
1535 | * above, it is then put into a special group, "unknown". |
||
1536 | * |
||
1537 | * Determines whether the package has been installed or not by |
||
1538 | * checking it against {@link loadInstalledPackages()}. |
||
1539 | * |
||
1540 | * @param int $start The item to start with (not used here) |
||
1541 | * @param int $items_per_page The number of items to show per page (not used here) |
||
1542 | * @param string $sort A string indicating how to sort the results |
||
1543 | * @param string $params Type of packages |
||
1544 | * @return array An array of information about the packages |
||
1545 | */ |
||
1546 | function list_getPackages($start, $items_per_page, $sort, $params) |
||
1547 | { |
||
1548 | global $scripturl, $packagesdir, $context; |
||
1549 | static $installed_mods; |
||
1550 | |||
1551 | $packages = array(); |
||
1552 | $column = array(); |
||
1553 | |||
1554 | // We need the packages directory to be writable for this. |
||
1555 | if (!@is_writable($packagesdir)) |
||
1556 | create_chmod_control(array($packagesdir), array('destination_url' => $scripturl . '?action=admin;area=packages', 'crash_on_error' => true)); |
||
1557 | |||
1558 | $the_version = SMF_VERSION; |
||
1559 | |||
1560 | // Here we have a little code to help those who class themselves as something of gods, version emulation ;) |
||
1561 | if (isset($_GET['version_emulate']) && strtr($_GET['version_emulate'], array('SMF ' => '')) == $the_version) |
||
1562 | { |
||
1563 | unset($_SESSION['version_emulate']); |
||
1564 | } |
||
1565 | elseif (isset($_GET['version_emulate'])) |
||
1566 | { |
||
1567 | if (($_GET['version_emulate'] === 0 || $_GET['version_emulate'] === SMF_FULL_VERSION) && isset($_SESSION['version_emulate'])) |
||
1568 | unset($_SESSION['version_emulate']); |
||
1569 | elseif ($_GET['version_emulate'] !== 0) |
||
1570 | $_SESSION['version_emulate'] = strtr($_GET['version_emulate'], array('-' => ' ', '+' => ' ', 'SMF ' => '')); |
||
1571 | } |
||
1572 | if (!empty($_SESSION['version_emulate'])) |
||
1573 | { |
||
1574 | $context['forum_version'] = 'SMF ' . $_SESSION['version_emulate']; |
||
1575 | $the_version = $_SESSION['version_emulate']; |
||
1576 | } |
||
1577 | if (isset($_SESSION['single_version_emulate'])) |
||
1578 | unset($_SESSION['single_version_emulate']); |
||
1579 | |||
1580 | if (empty($installed_mods)) |
||
1581 | { |
||
1582 | $instmods = loadInstalledPackages(); |
||
1583 | $installed_mods = array(); |
||
1584 | // Look through the list of installed mods... |
||
1585 | foreach ($instmods as $installed_mod) |
||
1586 | $installed_mods[$installed_mod['package_id']] = array( |
||
1587 | 'id' => $installed_mod['id'], |
||
1588 | 'version' => $installed_mod['version'], |
||
1589 | 'time_installed' => $installed_mod['time_installed'], |
||
1590 | ); |
||
1591 | |||
1592 | // Get a list of all the ids installed, so the latest packages won't include already installed ones. |
||
1593 | $context['installed_mods'] = array_keys($installed_mods); |
||
1594 | } |
||
1595 | |||
1596 | if ($dir = @opendir($packagesdir)) |
||
1597 | { |
||
1598 | $dirs = array(); |
||
1599 | $sort_id = array( |
||
1600 | 'modification' => 1, |
||
1601 | 'avatar' => 1, |
||
1602 | 'language' => 1, |
||
1603 | 'unknown' => 1, |
||
1604 | ); |
||
1605 | call_integration_hook('integrate_packages_sort_id', array(&$sort_id, &$packages)); |
||
1606 | |||
1607 | while ($package = readdir($dir)) |
||
1608 | { |
||
1609 | 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')) |
||
1610 | continue; |
||
1611 | |||
1612 | // Skip directories or files that are named the same. |
||
1613 | if (is_dir($packagesdir . '/' . $package)) |
||
1614 | { |
||
1615 | if (in_array($package, $dirs)) |
||
1616 | continue; |
||
1617 | $dirs[] = $package; |
||
1618 | } |
||
1619 | elseif (substr(strtolower($package), -7) == '.tar.gz') |
||
1620 | { |
||
1621 | if (in_array(substr($package, 0, -7), $dirs)) |
||
1622 | continue; |
||
1623 | $dirs[] = substr($package, 0, -7); |
||
1624 | } |
||
1625 | elseif (substr(strtolower($package), -4) == '.zip' || substr(strtolower($package), -4) == '.tgz') |
||
1626 | { |
||
1627 | if (in_array(substr($package, 0, -4), $dirs)) |
||
1628 | continue; |
||
1629 | $dirs[] = substr($package, 0, -4); |
||
1630 | } |
||
1631 | |||
1632 | $packageInfo = getPackageInfo($package); |
||
1633 | if (!is_array($packageInfo)) |
||
1634 | continue; |
||
1635 | |||
1636 | if (!empty($packageInfo)) |
||
1637 | { |
||
1638 | if (!isset($sort_id[$packageInfo['type']])) |
||
1639 | $packageInfo['sort_id'] = $sort_id['unknown']; |
||
1640 | else |
||
1641 | $packageInfo['sort_id'] = $sort_id[$packageInfo['type']]; |
||
1642 | |||
1643 | $packageInfo['time_installed'] = 0; |
||
1644 | $packageInfo['is_installed'] = isset($installed_mods[$packageInfo['id']]); |
||
1645 | if ($packageInfo['is_installed']) |
||
1646 | { |
||
1647 | $packageInfo['is_current'] = $installed_mods[$packageInfo['id']]['version'] == $packageInfo['version']; |
||
1648 | $packageInfo['is_newer'] = $installed_mods[$packageInfo['id']]['version'] > $packageInfo['version']; |
||
1649 | $packageInfo['installed_id'] = $installed_mods[$packageInfo['id']]['id']; |
||
1650 | if ($packageInfo['is_current']) |
||
1651 | $packageInfo['time_installed'] = $installed_mods[$packageInfo['id']]['time_installed']; |
||
1652 | } |
||
1653 | |||
1654 | $packageInfo['can_install'] = false; |
||
1655 | $packageInfo['can_uninstall'] = false; |
||
1656 | $packageInfo['can_upgrade'] = false; |
||
1657 | $packageInfo['can_emulate_install'] = false; |
||
1658 | $packageInfo['can_emulate_uninstall'] = false; |
||
1659 | |||
1660 | // This package is currently NOT installed. Check if it can be. |
||
1661 | if (!$packageInfo['is_installed'] && $packageInfo['xml']->exists('install')) |
||
1662 | { |
||
1663 | // Check if there's an install for *THIS* version of SMF. |
||
1664 | $installs = $packageInfo['xml']->set('install'); |
||
1665 | foreach ($installs as $install) |
||
1666 | { |
||
1667 | if (!$install->exists('@for') || matchPackageVersion($the_version, $install->fetch('@for'))) |
||
1668 | { |
||
1669 | // Okay, this one is good to go. |
||
1670 | $packageInfo['can_install'] = true; |
||
1671 | break; |
||
1672 | } |
||
1673 | } |
||
1674 | |||
1675 | // no install found for this version, lets see if one exists for another |
||
1676 | if ($packageInfo['can_install'] === false && $install->exists('@for') && empty($_SESSION['version_emulate'])) |
||
1677 | { |
||
1678 | $reset = true; |
||
1679 | |||
1680 | // Get the highest install version that is available from the package |
||
1681 | foreach ($installs as $install) |
||
1682 | { |
||
1683 | $packageInfo['can_emulate_install'] = matchHighestPackageVersion($install->fetch('@for'), $reset, $the_version); |
||
1684 | $reset = false; |
||
1685 | } |
||
1686 | } |
||
1687 | } |
||
1688 | // An already installed, but old, package. Can we upgrade it? |
||
1689 | elseif ($packageInfo['is_installed'] && !$packageInfo['is_current'] && $packageInfo['xml']->exists('upgrade')) |
||
1690 | { |
||
1691 | $upgrades = $packageInfo['xml']->set('upgrade'); |
||
1692 | |||
1693 | // First go through, and check against the current version of SMF. |
||
1694 | foreach ($upgrades as $upgrade) |
||
1695 | { |
||
1696 | // Even if it is for this SMF, is it for the installed version of the mod? |
||
1697 | if (!$upgrade->exists('@for') || matchPackageVersion($the_version, $upgrade->fetch('@for'))) |
||
1698 | if (!$upgrade->exists('@from') || matchPackageVersion($installed_mods[$packageInfo['id']]['version'], $upgrade->fetch('@from'))) |
||
1699 | { |
||
1700 | $packageInfo['can_upgrade'] = true; |
||
1701 | break; |
||
1702 | } |
||
1703 | } |
||
1704 | } |
||
1705 | // Note that it has to be the current version to be uninstallable. Shucks. |
||
1706 | elseif ($packageInfo['is_installed'] && $packageInfo['is_current'] && $packageInfo['xml']->exists('uninstall')) |
||
1707 | { |
||
1708 | $uninstalls = $packageInfo['xml']->set('uninstall'); |
||
1709 | |||
1710 | // Can we find any uninstallation methods that work for this SMF version? |
||
1711 | foreach ($uninstalls as $uninstall) |
||
1712 | { |
||
1713 | if (!$uninstall->exists('@for') || matchPackageVersion($the_version, $uninstall->fetch('@for'))) |
||
1714 | { |
||
1715 | $packageInfo['can_uninstall'] = true; |
||
1716 | break; |
||
1717 | } |
||
1718 | } |
||
1719 | |||
1720 | // no uninstall found for this version, lets see if one exists for another |
||
1721 | if ($packageInfo['can_uninstall'] === false && $uninstall->exists('@for') && empty($_SESSION['version_emulate'])) |
||
1722 | { |
||
1723 | $reset = true; |
||
1724 | |||
1725 | // Get the highest install version that is available from the package |
||
1726 | foreach ($uninstalls as $uninstall) |
||
1727 | { |
||
1728 | $packageInfo['can_emulate_uninstall'] = matchHighestPackageVersion($uninstall->fetch('@for'), $reset, $the_version); |
||
1729 | $reset = false; |
||
1730 | } |
||
1731 | } |
||
1732 | } |
||
1733 | |||
1734 | // Save some memory by not passing the xmlArray object into context. |
||
1735 | unset($packageInfo['xml']); |
||
1736 | |||
1737 | if (isset($sort_id[$packageInfo['type']]) && $params == $packageInfo['type']) |
||
1738 | { |
||
1739 | $column[] = $packageInfo[$sort]; |
||
1740 | $sort_id[$packageInfo['type']]++; |
||
1741 | $packages[] = $packageInfo; |
||
1742 | } |
||
1743 | elseif (!isset($sort_id[$packageInfo['type']]) && $params == 'unknown') |
||
1744 | { |
||
1745 | $column[] = $packageInfo[$sort]; |
||
1746 | $packageInfo['sort_id'] = $sort_id['unknown']; |
||
1747 | $sort_id['unknown']++; |
||
1748 | $packages[] = $packageInfo; |
||
1749 | } |
||
1750 | } |
||
1751 | } |
||
1752 | closedir($dir); |
||
1753 | } |
||
1754 | $context['available_packages'] += count($packages); |
||
1755 | array_multisort( |
||
1756 | $column, |
||
1757 | isset($_GET['desc']) ? SORT_DESC : SORT_ASC, |
||
1758 | $packages |
||
1759 | ); |
||
1760 | |||
1761 | return $packages; |
||
1762 | } |
||
1763 | |||
1764 | /** |
||
1765 | * Used when a temp FTP access is needed to package functions |
||
1766 | */ |
||
1767 | function PackageOptions() |
||
1768 | { |
||
1769 | global $txt, $context, $modSettings, $smcFunc; |
||
1770 | |||
1771 | if (isset($_POST['save'])) |
||
1772 | { |
||
1773 | checkSession(); |
||
1774 | |||
1775 | updateSettings(array( |
||
1776 | 'package_server' => trim($smcFunc['htmlspecialchars']($_POST['pack_server'])), |
||
1777 | 'package_port' => trim($smcFunc['htmlspecialchars']($_POST['pack_port'])), |
||
1778 | 'package_username' => trim($smcFunc['htmlspecialchars']($_POST['pack_user'])), |
||
1779 | 'package_make_backups' => !empty($_POST['package_make_backups']), |
||
1780 | 'package_make_full_backups' => !empty($_POST['package_make_full_backups']) |
||
1781 | )); |
||
1782 | $_SESSION['adm-save'] = true; |
||
1783 | |||
1784 | redirectexit('action=admin;area=packages;sa=options'); |
||
1785 | } |
||
1786 | |||
1787 | if (preg_match('~^/home\d*/([^/]+?)/public_html~', $_SERVER['DOCUMENT_ROOT'], $match)) |
||
1788 | $default_username = $match[1]; |
||
1789 | else |
||
1790 | $default_username = ''; |
||
1791 | |||
1792 | $context['page_title'] = $txt['package_settings']; |
||
1793 | $context['sub_template'] = 'install_options'; |
||
1794 | |||
1795 | $context['package_ftp_server'] = isset($modSettings['package_server']) ? $modSettings['package_server'] : 'localhost'; |
||
1796 | $context['package_ftp_port'] = isset($modSettings['package_port']) ? $modSettings['package_port'] : '21'; |
||
1797 | $context['package_ftp_username'] = isset($modSettings['package_username']) ? $modSettings['package_username'] : $default_username; |
||
1798 | $context['package_make_backups'] = !empty($modSettings['package_make_backups']); |
||
1799 | $context['package_make_full_backups'] = !empty($modSettings['package_make_full_backups']); |
||
1800 | |||
1801 | if (!empty($_SESSION['adm-save'])) |
||
1802 | { |
||
1803 | $context['saved_successful'] = true; |
||
1804 | unset ($_SESSION['adm-save']); |
||
1805 | } |
||
1806 | } |
||
1807 | |||
1808 | /** |
||
1809 | * List operations |
||
1810 | */ |
||
1811 | function ViewOperations() |
||
1812 | { |
||
1813 | global $context, $txt, $sourcedir, $packagesdir, $smcFunc, $modSettings; |
||
1814 | |||
1815 | // Can't be in here buddy. |
||
1816 | isAllowedTo('admin_forum'); |
||
1817 | |||
1818 | // We need to know the operation key for the search and replace, mod file looking at, is it a board mod? |
||
1819 | if (!isset($_REQUEST['operation_key'], $_REQUEST['filename']) && !is_numeric($_REQUEST['operation_key'])) |
||
1820 | fatal_lang_error('operation_invalid', 'general'); |
||
1821 | |||
1822 | // Load the required file. |
||
1823 | require_once($sourcedir . '/Subs-Package.php'); |
||
1824 | |||
1825 | // Uninstalling the mod? |
||
1826 | $reverse = isset($_REQUEST['reverse']) ? true : false; |
||
1827 | |||
1828 | // Get the base name. |
||
1829 | $context['filename'] = preg_replace('~[\.]+~', '.', $_REQUEST['package']); |
||
1830 | |||
1831 | // We need to extract this again. |
||
1832 | if (is_file($packagesdir . '/' . $context['filename'])) |
||
1833 | { |
||
1834 | $context['extracted_files'] = read_tgz_file($packagesdir . '/' . $context['filename'], $packagesdir . '/temp'); |
||
1835 | |||
1836 | if ($context['extracted_files'] && !file_exists($packagesdir . '/temp/package-info.xml')) |
||
1837 | foreach ($context['extracted_files'] as $file) |
||
1838 | if (basename($file['filename']) == 'package-info.xml') |
||
1839 | { |
||
1840 | $context['base_path'] = dirname($file['filename']) . '/'; |
||
1841 | break; |
||
1842 | } |
||
1843 | |||
1844 | if (!isset($context['base_path'])) |
||
1845 | $context['base_path'] = ''; |
||
1846 | } |
||
1847 | elseif (is_dir($packagesdir . '/' . $context['filename'])) |
||
1848 | { |
||
1849 | copytree($packagesdir . '/' . $context['filename'], $packagesdir . '/temp'); |
||
1850 | $context['extracted_files'] = listtree($packagesdir . '/temp'); |
||
1851 | $context['base_path'] = ''; |
||
1852 | } |
||
1853 | |||
1854 | // Load up any custom themes we may want to install into... |
||
1855 | $request = $smcFunc['db_query']('', ' |
||
1856 | SELECT id_theme, variable, value |
||
1857 | FROM {db_prefix}themes |
||
1858 | WHERE (id_theme = {int:default_theme} OR id_theme IN ({array_int:known_theme_list})) |
||
1859 | AND variable IN ({string:name}, {string:theme_dir})', |
||
1860 | array( |
||
1861 | 'known_theme_list' => explode(',', $modSettings['knownThemes']), |
||
1862 | 'default_theme' => 1, |
||
1863 | 'name' => 'name', |
||
1864 | 'theme_dir' => 'theme_dir', |
||
1865 | ) |
||
1866 | ); |
||
1867 | $theme_paths = array(); |
||
1868 | while ($row = $smcFunc['db_fetch_assoc']($request)) |
||
1869 | $theme_paths[$row['id_theme']][$row['variable']] = $row['value']; |
||
1870 | $smcFunc['db_free_result']($request); |
||
1871 | |||
1872 | // If we're viewing uninstall operations, only consider themes that |
||
1873 | // the package is actually installed into. |
||
1874 | if (isset($_REQUEST['reverse']) && !empty($_REQUEST['install_id'])) |
||
1875 | { |
||
1876 | $install_id = (int) $_REQUEST['install_id']; |
||
1877 | if ($install_id > 0) |
||
1878 | { |
||
1879 | $old_themes = array(); |
||
1880 | $request = $smcFunc['db_query']('', ' |
||
1881 | SELECT themes_installed |
||
1882 | FROM {db_prefix}log_packages |
||
1883 | WHERE id_install = {int:install_id}', |
||
1884 | array( |
||
1885 | 'install_id' => $install_id, |
||
1886 | ) |
||
1887 | ); |
||
1888 | |||
1889 | if ($smcFunc['db_num_rows']($request) == 1) |
||
1890 | { |
||
1891 | list ($old_themes) = $smcFunc['db_fetch_row']($request); |
||
1892 | $old_themes = explode(',', $old_themes); |
||
1893 | |||
1894 | foreach ($theme_paths as $id => $data) |
||
1895 | if ($id != 1 && !in_array($id, $old_themes)) |
||
1896 | unset($theme_paths[$id]); |
||
1897 | } |
||
1898 | $smcFunc['db_free_result']($request); |
||
1899 | } |
||
1900 | } |
||
1901 | |||
1902 | // Boardmod? |
||
1903 | if (isset($_REQUEST['boardmod'])) |
||
1904 | $mod_actions = parseBoardMod(@file_get_contents($packagesdir . '/temp/' . $context['base_path'] . $_REQUEST['filename']), true, $reverse, $theme_paths); |
||
1905 | else |
||
1906 | $mod_actions = parseModification(@file_get_contents($packagesdir . '/temp/' . $context['base_path'] . $_REQUEST['filename']), true, $reverse, $theme_paths); |
||
1907 | |||
1908 | // Ok lets get the content of the file. |
||
1909 | $context['operations'] = array( |
||
1910 | 'search' => strtr($smcFunc['htmlspecialchars']($mod_actions[$_REQUEST['operation_key']]['search_original']), array('[' => '[', ']' => ']')), |
||
1911 | 'replace' => strtr($smcFunc['htmlspecialchars']($mod_actions[$_REQUEST['operation_key']]['replace_original']), array('[' => '[', ']' => ']')), |
||
1912 | 'position' => $mod_actions[$_REQUEST['operation_key']]['position'], |
||
1913 | ); |
||
1914 | |||
1915 | // Let's do some formatting... |
||
1916 | $operation_text = $context['operations']['position'] == 'replace' ? 'operation_replace' : ($context['operations']['position'] == 'before' ? 'operation_after' : 'operation_before'); |
||
1917 | $context['operations']['search'] = parse_bbc('[code=' . $txt['operation_find'] . ']' . ($context['operations']['position'] == 'end' ? '?>' : $context['operations']['search']) . '[/code]'); |
||
1918 | $context['operations']['replace'] = parse_bbc('[code=' . $txt[$operation_text] . ']' . $context['operations']['replace'] . '[/code]'); |
||
1919 | |||
1920 | // No layers |
||
1921 | $context['template_layers'] = array(); |
||
1922 | $context['sub_template'] = 'view_operations'; |
||
1923 | } |
||
1924 | |||
1925 | /** |
||
1926 | * Allow the admin to reset permissions on files. |
||
1927 | */ |
||
1928 | function PackagePermissions() |
||
1929 | { |
||
1930 | global $context, $txt, $modSettings, $boarddir, $sourcedir, $cachedir, $smcFunc, $package_ftp; |
||
1931 | |||
1932 | // Let's try and be good, yes? |
||
1933 | checkSession('get'); |
||
1934 | |||
1935 | // If we're restoring permissions this is just a pass through really. |
||
1936 | if (isset($_GET['restore'])) |
||
1937 | { |
||
1938 | create_chmod_control(array(), array(), true); |
||
1939 | fatal_lang_error('no_access', false); |
||
1940 | } |
||
1941 | |||
1942 | // This is a memory eat. |
||
1943 | setMemoryLimit('128M'); |
||
1944 | @set_time_limit(600); |
||
1945 | |||
1946 | // Load up some FTP stuff. |
||
1947 | create_chmod_control(); |
||
1948 | |||
1949 | if (empty($package_ftp) && !isset($_POST['skip_ftp'])) |
||
1950 | { |
||
1951 | require_once($sourcedir . '/Class-Package.php'); |
||
1952 | $ftp = new ftp_connection(null); |
||
1953 | list ($username, $detect_path, $found_path) = $ftp->detect_path($boarddir); |
||
1954 | |||
1955 | $context['package_ftp'] = array( |
||
1956 | 'server' => isset($modSettings['package_server']) ? $modSettings['package_server'] : 'localhost', |
||
1957 | 'port' => isset($modSettings['package_port']) ? $modSettings['package_port'] : '21', |
||
1958 | 'username' => empty($username) ? (isset($modSettings['package_username']) ? $modSettings['package_username'] : '') : $username, |
||
1959 | 'path' => $detect_path, |
||
1960 | 'form_elements_only' => true, |
||
1961 | ); |
||
1962 | } |
||
1963 | else |
||
1964 | $context['ftp_connected'] = true; |
||
1965 | |||
1966 | // Define the template. |
||
1967 | $context['page_title'] = $txt['package_file_perms']; |
||
1968 | $context['sub_template'] = 'file_permissions'; |
||
1969 | |||
1970 | // Define what files we're interested in, as a tree. |
||
1971 | $context['file_tree'] = array( |
||
1972 | strtr($boarddir, array('\\' => '/')) => array( |
||
1973 | 'type' => 'dir', |
||
1974 | 'contents' => array( |
||
1975 | 'agreement.txt' => array( |
||
1976 | 'type' => 'file', |
||
1977 | 'writable_on' => 'standard', |
||
1978 | ), |
||
1979 | 'Settings.php' => array( |
||
1980 | 'type' => 'file', |
||
1981 | 'writable_on' => 'restrictive', |
||
1982 | ), |
||
1983 | 'Settings_bak.php' => array( |
||
1984 | 'type' => 'file', |
||
1985 | 'writable_on' => 'restrictive', |
||
1986 | ), |
||
1987 | 'attachments' => array( |
||
1988 | 'type' => 'dir', |
||
1989 | 'writable_on' => 'restrictive', |
||
1990 | ), |
||
1991 | 'avatars' => array( |
||
1992 | 'type' => 'dir', |
||
1993 | 'writable_on' => 'standard', |
||
1994 | ), |
||
1995 | 'cache' => array( |
||
1996 | 'type' => 'dir', |
||
1997 | 'writable_on' => 'restrictive', |
||
1998 | ), |
||
1999 | 'custom_avatar_dir' => array( |
||
2000 | 'type' => 'dir', |
||
2001 | 'writable_on' => 'restrictive', |
||
2002 | ), |
||
2003 | 'Smileys' => array( |
||
2004 | 'type' => 'dir_recursive', |
||
2005 | 'writable_on' => 'standard', |
||
2006 | ), |
||
2007 | 'Sources' => array( |
||
2008 | 'type' => 'dir_recursive', |
||
2009 | 'list_contents' => true, |
||
2010 | 'writable_on' => 'standard', |
||
2011 | 'contents' => array( |
||
2012 | 'tasks' => array( |
||
2013 | 'type' => 'dir', |
||
2014 | 'list_contents' => true, |
||
2015 | ), |
||
2016 | ), |
||
2017 | ), |
||
2018 | 'Themes' => array( |
||
2019 | 'type' => 'dir_recursive', |
||
2020 | 'writable_on' => 'standard', |
||
2021 | 'contents' => array( |
||
2022 | 'default' => array( |
||
2023 | 'type' => 'dir_recursive', |
||
2024 | 'list_contents' => true, |
||
2025 | 'contents' => array( |
||
2026 | 'languages' => array( |
||
2027 | 'type' => 'dir', |
||
2028 | 'list_contents' => true, |
||
2029 | ), |
||
2030 | ), |
||
2031 | ), |
||
2032 | ), |
||
2033 | ), |
||
2034 | 'Packages' => array( |
||
2035 | 'type' => 'dir', |
||
2036 | 'writable_on' => 'standard', |
||
2037 | 'contents' => array( |
||
2038 | 'temp' => array( |
||
2039 | 'type' => 'dir', |
||
2040 | ), |
||
2041 | 'backup' => array( |
||
2042 | 'type' => 'dir', |
||
2043 | ), |
||
2044 | ), |
||
2045 | ), |
||
2046 | ), |
||
2047 | ), |
||
2048 | ); |
||
2049 | |||
2050 | // Directories that can move. |
||
2051 | if (substr($sourcedir, 0, strlen($boarddir)) != $boarddir) |
||
2052 | { |
||
2053 | unset($context['file_tree'][strtr($boarddir, array('\\' => '/'))]['contents']['Sources']); |
||
2054 | $context['file_tree'][strtr($sourcedir, array('\\' => '/'))] = array( |
||
2055 | 'type' => 'dir', |
||
2056 | 'list_contents' => true, |
||
2057 | 'writable_on' => 'standard', |
||
2058 | ); |
||
2059 | } |
||
2060 | |||
2061 | // Moved the cache? |
||
2062 | if (substr($cachedir, 0, strlen($boarddir)) != $boarddir) |
||
2063 | { |
||
2064 | unset($context['file_tree'][strtr($boarddir, array('\\' => '/'))]['contents']['cache']); |
||
2065 | $context['file_tree'][strtr($cachedir, array('\\' => '/'))] = array( |
||
2066 | 'type' => 'dir', |
||
2067 | 'list_contents' => false, |
||
2068 | 'writable_on' => 'restrictive', |
||
2069 | ); |
||
2070 | } |
||
2071 | |||
2072 | // Are we using multiple attachment directories? |
||
2073 | if (!empty($modSettings['currentAttachmentUploadDir'])) |
||
2074 | { |
||
2075 | unset($context['file_tree'][strtr($boarddir, array('\\' => '/'))]['contents']['attachments']); |
||
2076 | |||
2077 | if (!is_array($modSettings['attachmentUploadDir'])) |
||
2078 | $modSettings['attachmentUploadDir'] = $smcFunc['json_decode']($modSettings['attachmentUploadDir'], true); |
||
2079 | |||
2080 | // @todo Should we suggest non-current directories be read only? |
||
2081 | foreach ($modSettings['attachmentUploadDir'] as $dir) |
||
2082 | $context['file_tree'][strtr($dir, array('\\' => '/'))] = array( |
||
2083 | 'type' => 'dir', |
||
2084 | 'writable_on' => 'restrictive', |
||
2085 | ); |
||
2086 | } |
||
2087 | elseif (substr($modSettings['attachmentUploadDir'], 0, strlen($boarddir)) != $boarddir) |
||
2088 | { |
||
2089 | unset($context['file_tree'][strtr($boarddir, array('\\' => '/'))]['contents']['attachments']); |
||
2090 | $context['file_tree'][strtr($modSettings['attachmentUploadDir'], array('\\' => '/'))] = array( |
||
2091 | 'type' => 'dir', |
||
2092 | 'writable_on' => 'restrictive', |
||
2093 | ); |
||
2094 | } |
||
2095 | |||
2096 | if (substr($modSettings['smileys_dir'], 0, strlen($boarddir)) != $boarddir) |
||
2097 | { |
||
2098 | unset($context['file_tree'][strtr($boarddir, array('\\' => '/'))]['contents']['Smileys']); |
||
2099 | $context['file_tree'][strtr($modSettings['smileys_dir'], array('\\' => '/'))] = array( |
||
2100 | 'type' => 'dir_recursive', |
||
2101 | 'writable_on' => 'standard', |
||
2102 | ); |
||
2103 | } |
||
2104 | if (substr($modSettings['avatar_directory'], 0, strlen($boarddir)) != $boarddir) |
||
2105 | { |
||
2106 | unset($context['file_tree'][strtr($boarddir, array('\\' => '/'))]['contents']['avatars']); |
||
2107 | $context['file_tree'][strtr($modSettings['avatar_directory'], array('\\' => '/'))] = array( |
||
2108 | 'type' => 'dir', |
||
2109 | 'writable_on' => 'standard', |
||
2110 | ); |
||
2111 | } |
||
2112 | if (isset($modSettings['custom_avatar_dir']) && substr($modSettings['custom_avatar_dir'], 0, strlen($boarddir)) != $boarddir) |
||
2113 | { |
||
2114 | unset($context['file_tree'][strtr($boarddir, array('\\' => '/'))]['contents']['custom_avatar_dir']); |
||
2115 | $context['file_tree'][strtr($modSettings['custom_avatar_dir'], array('\\' => '/'))] = array( |
||
2116 | 'type' => 'dir', |
||
2117 | 'writable_on' => 'restrictive', |
||
2118 | ); |
||
2119 | } |
||
2120 | |||
2121 | // Load up any custom themes. |
||
2122 | $request = $smcFunc['db_query']('', ' |
||
2123 | SELECT value |
||
2124 | FROM {db_prefix}themes |
||
2125 | WHERE id_theme > {int:default_theme_id} |
||
2126 | AND id_member = {int:guest_id} |
||
2127 | AND variable = {string:theme_dir} |
||
2128 | ORDER BY value ASC', |
||
2129 | array( |
||
2130 | 'default_theme_id' => 1, |
||
2131 | 'guest_id' => 0, |
||
2132 | 'theme_dir' => 'theme_dir', |
||
2133 | ) |
||
2134 | ); |
||
2135 | while ($row = $smcFunc['db_fetch_assoc']($request)) |
||
2136 | { |
||
2137 | if (substr(strtolower(strtr($row['value'], array('\\' => '/'))), 0, strlen($boarddir) + 7) == strtolower(strtr($boarddir, array('\\' => '/')) . '/Themes')) |
||
2138 | $context['file_tree'][strtr($boarddir, array('\\' => '/'))]['contents']['Themes']['contents'][substr($row['value'], strlen($boarddir) + 8)] = array( |
||
2139 | 'type' => 'dir_recursive', |
||
2140 | 'list_contents' => true, |
||
2141 | 'contents' => array( |
||
2142 | 'languages' => array( |
||
2143 | 'type' => 'dir', |
||
2144 | 'list_contents' => true, |
||
2145 | ), |
||
2146 | ), |
||
2147 | ); |
||
2148 | else |
||
2149 | { |
||
2150 | $context['file_tree'][strtr($row['value'], array('\\' => '/'))] = array( |
||
2151 | 'type' => 'dir_recursive', |
||
2152 | 'list_contents' => true, |
||
2153 | 'contents' => array( |
||
2154 | 'languages' => array( |
||
2155 | 'type' => 'dir', |
||
2156 | 'list_contents' => true, |
||
2157 | ), |
||
2158 | ), |
||
2159 | ); |
||
2160 | } |
||
2161 | } |
||
2162 | $smcFunc['db_free_result']($request); |
||
2163 | |||
2164 | // If we're submitting then let's move on to another function to keep things cleaner.. |
||
2165 | if (isset($_POST['action_changes'])) |
||
2166 | return PackagePermissionsAction(); |
||
2167 | |||
2168 | $context['look_for'] = array(); |
||
2169 | // Are we looking for a particular tree - normally an expansion? |
||
2170 | if (!empty($_REQUEST['find'])) |
||
2171 | $context['look_for'][] = base64_decode($_REQUEST['find']); |
||
2172 | // Only that tree? |
||
2173 | $context['only_find'] = isset($_GET['xml']) && !empty($_REQUEST['onlyfind']) ? $_REQUEST['onlyfind'] : ''; |
||
2174 | if ($context['only_find']) |
||
2175 | $context['look_for'][] = $context['only_find']; |
||
2176 | |||
2177 | // Have we got a load of back-catalogue trees to expand from a submit etc? |
||
2178 | if (!empty($_GET['back_look'])) |
||
2179 | { |
||
2180 | $potententialTrees = $smcFunc['json_decode'](base64_decode($_GET['back_look']), true); |
||
2181 | foreach ($potententialTrees as $tree) |
||
2182 | $context['look_for'][] = $tree; |
||
2183 | } |
||
2184 | // ... maybe posted? |
||
2185 | if (!empty($_POST['back_look'])) |
||
2186 | $context['only_find'] = array_merge($context['only_find'], $_POST['back_look']); |
||
2187 | |||
2188 | $context['back_look_data'] = base64_encode($smcFunc['json_encode'](array_slice($context['look_for'], 0, 15))); |
||
2189 | |||
2190 | // Are we finding more files than first thought? |
||
2191 | $context['file_offset'] = !empty($_REQUEST['fileoffset']) ? (int) $_REQUEST['fileoffset'] : 0; |
||
2192 | // Don't list more than this many files in a directory. |
||
2193 | $context['file_limit'] = 150; |
||
2194 | |||
2195 | // How many levels shall we show? |
||
2196 | $context['default_level'] = empty($context['only_find']) ? 2 : 25; |
||
2197 | |||
2198 | // This will be used if we end up catching XML data. |
||
2199 | $context['xml_data'] = array( |
||
2200 | 'roots' => array( |
||
2201 | 'identifier' => 'root', |
||
2202 | 'children' => array( |
||
2203 | array( |
||
2204 | 'value' => preg_replace('~[^A-Za-z0-9_\-=:]~', ':-:', $context['only_find']), |
||
2205 | ), |
||
2206 | ), |
||
2207 | ), |
||
2208 | 'folders' => array( |
||
2209 | 'identifier' => 'folder', |
||
2210 | 'children' => array(), |
||
2211 | ), |
||
2212 | ); |
||
2213 | |||
2214 | foreach ($context['file_tree'] as $path => $data) |
||
2215 | { |
||
2216 | // Run this directory. |
||
2217 | if (file_exists($path) && (empty($context['only_find']) || substr($context['only_find'], 0, strlen($path)) == $path)) |
||
2218 | { |
||
2219 | // Get the first level down only. |
||
2220 | fetchPerms__recursive($path, $context['file_tree'][$path], 1); |
||
2221 | $context['file_tree'][$path]['perms'] = array( |
||
2222 | 'chmod' => @is_writable($path), |
||
2223 | 'perms' => @fileperms($path), |
||
2224 | ); |
||
2225 | } |
||
2226 | else |
||
2227 | unset($context['file_tree'][$path]); |
||
2228 | } |
||
2229 | |||
2230 | // Is this actually xml? |
||
2231 | if (isset($_GET['xml'])) |
||
2232 | { |
||
2233 | loadTemplate('Xml'); |
||
2234 | $context['sub_template'] = 'generic_xml'; |
||
2235 | $context['template_layers'] = array(); |
||
2236 | } |
||
2237 | } |
||
2238 | |||
2239 | /** |
||
2240 | * Checkes the permissions of all the areas that will be affected by the package |
||
2241 | * |
||
2242 | * @param string $path The path to the directiory to check permissions for |
||
2243 | * @param array $data An array of data about the directory |
||
2244 | * @param int $level How far deep to go |
||
2245 | */ |
||
2246 | function fetchPerms__recursive($path, &$data, $level) |
||
2247 | { |
||
2248 | global $context; |
||
2249 | |||
2250 | $isLikelyPath = false; |
||
2251 | foreach ($context['look_for'] as $possiblePath) |
||
2252 | if (substr($possiblePath, 0, strlen($path)) == $path) |
||
2253 | $isLikelyPath = true; |
||
2254 | |||
2255 | // Is this where we stop? |
||
2256 | if (isset($_GET['xml']) && !empty($context['look_for']) && !$isLikelyPath) |
||
2257 | return; |
||
2258 | elseif ($level > $context['default_level'] && !$isLikelyPath) |
||
2259 | return; |
||
2260 | |||
2261 | // Are we actually interested in saving this data? |
||
2262 | $save_data = empty($context['only_find']) || $context['only_find'] == $path; |
||
2263 | |||
2264 | // @todo Shouldn't happen - but better error message? |
||
2265 | if (!is_dir($path)) |
||
2266 | fatal_lang_error('no_access', false); |
||
2267 | |||
2268 | // This is where we put stuff we've found for sorting. |
||
2269 | $foundData = array( |
||
2270 | 'files' => array(), |
||
2271 | 'folders' => array(), |
||
2272 | ); |
||
2273 | |||
2274 | $dh = opendir($path); |
||
2275 | while ($entry = readdir($dh)) |
||
2276 | { |
||
2277 | // Some kind of file? |
||
2278 | if (is_file($path . '/' . $entry)) |
||
2279 | { |
||
2280 | // Are we listing PHP files in this directory? |
||
2281 | if ($save_data && !empty($data['list_contents']) && substr($entry, -4) == '.php') |
||
2282 | $foundData['files'][$entry] = true; |
||
2283 | // A file we were looking for. |
||
2284 | elseif ($save_data && isset($data['contents'][$entry])) |
||
2285 | $foundData['files'][$entry] = true; |
||
2286 | } |
||
2287 | // It's a directory - we're interested one way or another, probably... |
||
2288 | elseif ($entry != '.' && $entry != '..') |
||
2289 | { |
||
2290 | // Going further? |
||
2291 | 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')))) |
||
2292 | { |
||
2293 | if (!isset($data['contents'][$entry])) |
||
2294 | $foundData['folders'][$entry] = 'dir_recursive'; |
||
2295 | else |
||
2296 | $foundData['folders'][$entry] = true; |
||
2297 | |||
2298 | // If this wasn't expected inherit the recusiveness... |
||
2299 | if (!isset($data['contents'][$entry])) |
||
2300 | // We need to do this as we will be going all recursive. |
||
2301 | $data['contents'][$entry] = array( |
||
2302 | 'type' => 'dir_recursive', |
||
2303 | ); |
||
2304 | |||
2305 | // Actually do the recursive stuff... |
||
2306 | fetchPerms__recursive($path . '/' . $entry, $data['contents'][$entry], $level + 1); |
||
2307 | } |
||
2308 | // Maybe it is a folder we are not descending into. |
||
2309 | elseif (isset($data['contents'][$entry])) |
||
2310 | $foundData['folders'][$entry] = true; |
||
2311 | // Otherwise we stop here. |
||
2312 | } |
||
2313 | } |
||
2314 | closedir($dh); |
||
2315 | |||
2316 | // Nothing to see here? |
||
2317 | if (!$save_data) |
||
2318 | return; |
||
2319 | |||
2320 | // Now actually add the data, starting with the folders. |
||
2321 | ksort($foundData['folders']); |
||
2322 | foreach ($foundData['folders'] as $folder => $type) |
||
2323 | { |
||
2324 | $additional_data = array( |
||
2325 | 'perms' => array( |
||
2326 | 'chmod' => @is_writable($path . '/' . $folder), |
||
2327 | 'perms' => @fileperms($path . '/' . $folder), |
||
2328 | ), |
||
2329 | ); |
||
2330 | if ($type !== true) |
||
2331 | $additional_data['type'] = $type; |
||
2332 | |||
2333 | // If there's an offset ignore any folders in XML mode. |
||
2334 | if (isset($_GET['xml']) && $context['file_offset'] == 0) |
||
2335 | { |
||
2336 | $context['xml_data']['folders']['children'][] = array( |
||
2337 | 'attributes' => array( |
||
2338 | 'writable' => $additional_data['perms']['chmod'] ? 1 : 0, |
||
2339 | 'permissions' => substr(sprintf('%o', $additional_data['perms']['perms']), -4), |
||
2340 | 'folder' => 1, |
||
2341 | 'path' => $context['only_find'], |
||
2342 | 'level' => $level, |
||
2343 | 'more' => 0, |
||
2344 | 'offset' => $context['file_offset'], |
||
2345 | 'my_ident' => preg_replace('~[^A-Za-z0-9_\-=:]~', ':-:', $context['only_find'] . '/' . $folder), |
||
2346 | 'ident' => preg_replace('~[^A-Za-z0-9_\-=:]~', ':-:', $context['only_find']), |
||
2347 | ), |
||
2348 | 'value' => $folder, |
||
2349 | ); |
||
2350 | } |
||
2351 | elseif (!isset($_GET['xml'])) |
||
2352 | { |
||
2353 | if (isset($data['contents'][$folder])) |
||
2354 | $data['contents'][$folder] = array_merge($data['contents'][$folder], $additional_data); |
||
2355 | else |
||
2356 | $data['contents'][$folder] = $additional_data; |
||
2357 | } |
||
2358 | } |
||
2359 | |||
2360 | // Now we want to do a similar thing with files. |
||
2361 | ksort($foundData['files']); |
||
2362 | $counter = -1; |
||
2363 | foreach ($foundData['files'] as $file => $dummy) |
||
2364 | { |
||
2365 | $counter++; |
||
2366 | |||
2367 | // Have we reached our offset? |
||
2368 | if ($context['file_offset'] > $counter) |
||
2369 | continue; |
||
2370 | // Gone too far? |
||
2371 | if ($counter > ($context['file_offset'] + $context['file_limit'])) |
||
2372 | continue; |
||
2373 | |||
2374 | $additional_data = array( |
||
2375 | 'perms' => array( |
||
2376 | 'chmod' => @is_writable($path . '/' . $file), |
||
2377 | 'perms' => @fileperms($path . '/' . $file), |
||
2378 | ), |
||
2379 | ); |
||
2380 | |||
2381 | // XML? |
||
2382 | if (isset($_GET['xml'])) |
||
2383 | { |
||
2384 | $context['xml_data']['folders']['children'][] = array( |
||
2385 | 'attributes' => array( |
||
2386 | 'writable' => $additional_data['perms']['chmod'] ? 1 : 0, |
||
2387 | 'permissions' => substr(sprintf('%o', $additional_data['perms']['perms']), -4), |
||
2388 | 'folder' => 0, |
||
2389 | 'path' => $context['only_find'], |
||
2390 | 'level' => $level, |
||
2391 | 'more' => $counter == ($context['file_offset'] + $context['file_limit']) ? 1 : 0, |
||
2392 | 'offset' => $context['file_offset'], |
||
2393 | 'my_ident' => preg_replace('~[^A-Za-z0-9_\-=:]~', ':-:', $context['only_find'] . '/' . $file), |
||
2394 | 'ident' => preg_replace('~[^A-Za-z0-9_\-=:]~', ':-:', $context['only_find']), |
||
2395 | ), |
||
2396 | 'value' => $file, |
||
2397 | ); |
||
2398 | } |
||
2399 | elseif ($counter != ($context['file_offset'] + $context['file_limit'])) |
||
2400 | { |
||
2401 | if (isset($data['contents'][$file])) |
||
2402 | $data['contents'][$file] = array_merge($data['contents'][$file], $additional_data); |
||
2403 | else |
||
2404 | $data['contents'][$file] = $additional_data; |
||
2405 | } |
||
2406 | } |
||
2407 | } |
||
2408 | |||
2409 | /** |
||
2410 | * Actually action the permission changes they want. |
||
2411 | */ |
||
2412 | function PackagePermissionsAction() |
||
2413 | { |
||
2414 | global $smcFunc, $context, $txt, $package_ftp; |
||
2415 | |||
2416 | umask(0); |
||
2417 | |||
2418 | $timeout_limit = 5; |
||
2419 | |||
2420 | $context['method'] = $_POST['method'] == 'individual' ? 'individual' : 'predefined'; |
||
2421 | $context['sub_template'] = 'action_permissions'; |
||
2422 | $context['page_title'] = $txt['package_file_perms_applying']; |
||
2423 | $context['back_look_data'] = isset($_POST['back_look']) ? $_POST['back_look'] : array(); |
||
2424 | |||
2425 | // Skipping use of FTP? |
||
2426 | if (empty($package_ftp)) |
||
2427 | $context['skip_ftp'] = true; |
||
2428 | |||
2429 | // 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. |
||
2430 | if ($context['method'] == 'individual') |
||
2431 | { |
||
2432 | // Only these path roots are legal. |
||
2433 | $legal_roots = array_keys($context['file_tree']); |
||
2434 | $context['custom_value'] = (int) $_POST['custom_value']; |
||
2435 | |||
2436 | // Continuing? |
||
2437 | if (isset($_POST['toProcess'])) |
||
2438 | $_POST['permStatus'] = $smcFunc['json_decode'](base64_decode($_POST['toProcess']), true); |
||
2439 | |||
2440 | if (isset($_POST['permStatus'])) |
||
2441 | { |
||
2442 | $context['to_process'] = array(); |
||
2443 | $validate_custom = false; |
||
2444 | foreach ($_POST['permStatus'] as $path => $status) |
||
2445 | { |
||
2446 | // Nothing to see here? |
||
2447 | if ($status == 'no_change') |
||
2448 | continue; |
||
2449 | $legal = false; |
||
2450 | foreach ($legal_roots as $root) |
||
2451 | if (substr($path, 0, strlen($root)) == $root) |
||
2452 | $legal = true; |
||
2453 | |||
2454 | if (!$legal) |
||
2455 | continue; |
||
2456 | |||
2457 | // Check it exists. |
||
2458 | if (!file_exists($path)) |
||
2459 | continue; |
||
2460 | |||
2461 | if ($status == 'custom') |
||
2462 | $validate_custom = true; |
||
2463 | |||
2464 | // Now add it. |
||
2465 | $context['to_process'][$path] = $status; |
||
2466 | } |
||
2467 | $context['total_items'] = isset($_POST['totalItems']) ? (int) $_POST['totalItems'] : count($context['to_process']); |
||
2468 | |||
2469 | // Make sure the chmod status is valid? |
||
2470 | if ($validate_custom) |
||
2471 | { |
||
2472 | if (preg_match('~^[4567][4567][4567]$~', $context['custom_value']) == false) |
||
2473 | fatal_error($txt['chmod_value_invalid']); |
||
2474 | } |
||
2475 | |||
2476 | // Nothing to do? |
||
2477 | if (empty($context['to_process'])) |
||
2478 | 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']); |
||
2479 | } |
||
2480 | // Should never get here, |
||
2481 | else |
||
2482 | fatal_lang_error('no_access', false); |
||
2483 | |||
2484 | // Setup the custom value. |
||
2485 | $custom_value = octdec('0' . $context['custom_value']); |
||
2486 | |||
2487 | // Start processing items. |
||
2488 | foreach ($context['to_process'] as $path => $status) |
||
2489 | { |
||
2490 | if (in_array($status, array('execute', 'writable', 'read'))) |
||
2491 | package_chmod($path, $status); |
||
2492 | elseif ($status == 'custom' && !empty($custom_value)) |
||
2493 | { |
||
2494 | // Use FTP if we have it. |
||
2495 | if (!empty($package_ftp) && !empty($_SESSION['pack_ftp'])) |
||
2496 | { |
||
2497 | $ftp_file = strtr($path, array($_SESSION['pack_ftp']['root'] => '')); |
||
2498 | $package_ftp->chmod($ftp_file, $custom_value); |
||
2499 | } |
||
2500 | else |
||
2501 | smf_chmod($path, $custom_value); |
||
2502 | } |
||
2503 | |||
2504 | // This fish is fried... |
||
2505 | unset($context['to_process'][$path]); |
||
2506 | |||
2507 | // See if we're out of time? |
||
2508 | if ((time() - TIME_START) > $timeout_limit) |
||
2509 | { |
||
2510 | // Prepare template usage for to_process. |
||
2511 | $context['to_process_encode'] = base64_encode($smcFunc['json_encode']($context['to_process'])); |
||
2512 | |||
2513 | return false; |
||
2514 | } |
||
2515 | } |
||
2516 | |||
2517 | // Prepare template usage for to_process. |
||
2518 | $context['to_process_encode'] = base64_encode($smcFunc['json_encode']($context['to_process'])); |
||
2519 | } |
||
2520 | // If predefined this is a little different. |
||
2521 | else |
||
2522 | { |
||
2523 | $context['predefined_type'] = isset($_POST['predefined']) ? $_POST['predefined'] : 'restricted'; |
||
2524 | |||
2525 | $context['total_items'] = isset($_POST['totalItems']) ? (int) $_POST['totalItems'] : 0; |
||
2526 | $context['directory_list'] = isset($_POST['dirList']) ? $smcFunc['json_decode'](base64_decode($_POST['dirList']), true) : array(); |
||
2527 | |||
2528 | $context['file_offset'] = isset($_POST['fileOffset']) ? (int) $_POST['fileOffset'] : 0; |
||
2529 | |||
2530 | // Haven't counted the items yet? |
||
2531 | if (empty($context['total_items'])) |
||
2532 | { |
||
2533 | /** |
||
2534 | * Counts all the directories under a given path |
||
2535 | * |
||
2536 | * @param string $dir |
||
2537 | * @return integer |
||
2538 | */ |
||
2539 | function count_directories__recursive($dir) |
||
2540 | { |
||
2541 | global $context; |
||
2542 | |||
2543 | $count = 0; |
||
2544 | $dh = @opendir($dir); |
||
2545 | while ($entry = readdir($dh)) |
||
2546 | { |
||
2547 | if ($entry != '.' && $entry != '..' && is_dir($dir . '/' . $entry)) |
||
2548 | { |
||
2549 | $context['directory_list'][$dir . '/' . $entry] = 1; |
||
2550 | $count++; |
||
2551 | $count += count_directories__recursive($dir . '/' . $entry); |
||
2552 | } |
||
2553 | } |
||
2554 | closedir($dh); |
||
2555 | |||
2556 | return $count; |
||
2557 | } |
||
2558 | |||
2559 | foreach ($context['file_tree'] as $path => $data) |
||
2560 | { |
||
2561 | if (is_dir($path)) |
||
2562 | { |
||
2563 | $context['directory_list'][$path] = 1; |
||
2564 | $context['total_items'] += count_directories__recursive($path); |
||
2565 | $context['total_items']++; |
||
2566 | } |
||
2567 | } |
||
2568 | } |
||
2569 | |||
2570 | // Have we built up our list of special files? |
||
2571 | if (!isset($_POST['specialFiles']) && $context['predefined_type'] != 'free') |
||
2572 | { |
||
2573 | $context['special_files'] = array(); |
||
2574 | |||
2575 | /** |
||
2576 | * Builds a list of special files recursively for a given path |
||
2577 | * |
||
2578 | * @param string $path |
||
2579 | * @param array $data |
||
2580 | */ |
||
2581 | function build_special_files__recursive($path, &$data) |
||
2582 | { |
||
2583 | global $context; |
||
2584 | |||
2585 | if (!empty($data['writable_on'])) |
||
2586 | if ($context['predefined_type'] == 'standard' || $data['writable_on'] == 'restrictive') |
||
2587 | $context['special_files'][$path] = 1; |
||
2588 | |||
2589 | if (!empty($data['contents'])) |
||
2590 | foreach ($data['contents'] as $name => $contents) |
||
2591 | build_special_files__recursive($path . '/' . $name, $contents); |
||
2592 | } |
||
2593 | |||
2594 | foreach ($context['file_tree'] as $path => $data) |
||
2595 | build_special_files__recursive($path, $data); |
||
2596 | } |
||
2597 | // Free doesn't need special files. |
||
2598 | elseif ($context['predefined_type'] == 'free') |
||
2599 | $context['special_files'] = array(); |
||
2600 | else |
||
2601 | $context['special_files'] = $smcFunc['json_decode'](base64_decode($_POST['specialFiles']), true); |
||
2602 | |||
2603 | // Now we definitely know where we are, we need to go through again doing the chmod! |
||
2604 | foreach ($context['directory_list'] as $path => $dummy) |
||
2605 | { |
||
2606 | // Do the contents of the directory first. |
||
2607 | $dh = @opendir($path); |
||
2608 | $file_count = 0; |
||
2609 | $dont_chmod = false; |
||
2610 | while ($entry = readdir($dh)) |
||
2611 | { |
||
2612 | $file_count++; |
||
2613 | // Actually process this file? |
||
2614 | if (!$dont_chmod && !is_dir($path . '/' . $entry) && (empty($context['file_offset']) || $context['file_offset'] < $file_count)) |
||
2615 | { |
||
2616 | $status = $context['predefined_type'] == 'free' || isset($context['special_files'][$path . '/' . $entry]) ? 'writable' : 'execute'; |
||
2617 | package_chmod($path . '/' . $entry, $status); |
||
2618 | } |
||
2619 | |||
2620 | // See if we're out of time? |
||
2621 | if (!$dont_chmod && (time() - TIME_START) > $timeout_limit) |
||
2622 | { |
||
2623 | $dont_chmod = true; |
||
2624 | // Don't do this again. |
||
2625 | $context['file_offset'] = $file_count; |
||
2626 | } |
||
2627 | } |
||
2628 | closedir($dh); |
||
2629 | |||
2630 | // If this is set it means we timed out half way through. |
||
2631 | if ($dont_chmod) |
||
2632 | { |
||
2633 | $context['total_files'] = $file_count; |
||
2634 | $context['directory_list_encode'] = base64_encode($smcFunc['json_encode']($context['directory_list'])); |
||
2635 | $context['special_files_encode'] = base64_encode($smcFunc['json_encode']($context['special_files'])); |
||
2636 | return false; |
||
2637 | } |
||
2638 | |||
2639 | // Do the actual directory. |
||
2640 | $status = $context['predefined_type'] == 'free' || isset($context['special_files'][$path]) ? 'writable' : 'execute'; |
||
2641 | package_chmod($path, $status); |
||
2642 | |||
2643 | // We've finished the directory so no file offset, and no record. |
||
2644 | $context['file_offset'] = 0; |
||
2645 | unset($context['directory_list'][$path]); |
||
2646 | |||
2647 | // See if we're out of time? |
||
2648 | if ((time() - TIME_START) > $timeout_limit) |
||
2649 | { |
||
2650 | // Prepare this for usage on templates. |
||
2651 | $context['directory_list_encode'] = base64_encode($smcFunc['json_encode']($context['directory_list'])); |
||
2652 | $context['special_files_encode'] = base64_encode($smcFunc['json_encode']($context['special_files'])); |
||
2653 | |||
2654 | return false; |
||
2655 | } |
||
2656 | } |
||
2657 | |||
2658 | // Prepare this for usage on templates. |
||
2659 | $context['directory_list_encode'] = base64_encode($smcFunc['json_encode']($context['directory_list'])); |
||
2660 | $context['special_files_encode'] = base64_encode($smcFunc['json_encode']($context['special_files'])); |
||
2661 | } |
||
2662 | |||
2663 | // If we're here we are done! |
||
2664 | 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']); |
||
2665 | } |
||
2666 | |||
2667 | /** |
||
2668 | * Test an FTP connection. |
||
2669 | */ |
||
2670 | function PackageFTPTest() |
||
2671 | { |
||
2672 | global $context, $txt, $package_ftp; |
||
2673 | |||
2674 | checkSession('get'); |
||
2675 | |||
2676 | // Try to make the FTP connection. |
||
2677 | create_chmod_control(array(), array('force_find_error' => true)); |
||
2678 | |||
2679 | // Deal with the template stuff. |
||
2680 | loadTemplate('Xml'); |
||
2681 | $context['sub_template'] = 'generic_xml'; |
||
2682 | $context['template_layers'] = array(); |
||
2683 | |||
2684 | // Define the return data, this is simple. |
||
2685 | $context['xml_data'] = array( |
||
2686 | 'results' => array( |
||
2687 | 'identifier' => 'result', |
||
2688 | 'children' => array( |
||
2689 | array( |
||
2690 | 'attributes' => array( |
||
2691 | 'success' => !empty($package_ftp) ? 1 : 0, |
||
2692 | ), |
||
2693 | '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']), |
||
2694 | ), |
||
2695 | ), |
||
2696 | ), |
||
2697 | ); |
||
2698 | } |
||
2699 | |||
2700 | ?> |