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 2022 Simple Machines and individual contributors |
||
11 | * @license https://www.simplemachines.org/about/smf/license.php BSD |
||
12 | * |
||
13 | * @version 2.1.0 |
||
14 | */ |
||
15 | |||
16 | if (!defined('SMF')) |
||
17 | die('No direct access...'); |
||
18 | |||
19 | /** |
||
20 | * 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'))) |
||
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 | if (is_dir($packagesdir . '/' . $context['filename'])) |
||
898 | $context['package_sha256_hash'] = ''; |
||
899 | else |
||
900 | $context['package_sha256_hash'] = hash_file('sha256', $packagesdir . '/' . $context['filename']); |
||
901 | $packageInfo['filename'] = $context['filename']; |
||
902 | |||
903 | // Set the type of extraction... |
||
904 | $context['extract_type'] = isset($packageInfo['type']) ? $packageInfo['type'] : 'modification'; |
||
905 | |||
906 | // Create a backup file to roll back to! (but if they do this more than once, don't run it a zillion times.) |
||
907 | if (!empty($modSettings['package_make_full_backups']) && (!isset($_SESSION['last_backup_for']) || $_SESSION['last_backup_for'] != $context['filename'] . ($context['uninstalling'] ? '$$' : '$'))) |
||
908 | { |
||
909 | $_SESSION['last_backup_for'] = $context['filename'] . ($context['uninstalling'] ? '$$' : '$'); |
||
910 | $result = package_create_backup(($context['uninstalling'] ? 'backup_' : 'before_') . strtok($context['filename'], '.')); |
||
911 | if (!$result) |
||
912 | fatal_lang_error('could_not_package_backup', false); |
||
913 | } |
||
914 | |||
915 | // The mod isn't installed.... unless proven otherwise. |
||
916 | $context['is_installed'] = false; |
||
917 | |||
918 | // Is it actually installed? |
||
919 | $request = $smcFunc['db_query']('', ' |
||
920 | SELECT version, themes_installed, db_changes |
||
921 | FROM {db_prefix}log_packages |
||
922 | WHERE package_id = {string:current_package} |
||
923 | AND install_state != {int:not_installed} |
||
924 | ORDER BY time_installed DESC |
||
925 | LIMIT 1', |
||
926 | array( |
||
927 | 'not_installed' => 0, |
||
928 | 'current_package' => $packageInfo['id'], |
||
929 | ) |
||
930 | ); |
||
931 | while ($row = $smcFunc['db_fetch_assoc']($request)) |
||
932 | { |
||
933 | $old_themes = explode(',', $row['themes_installed']); |
||
934 | $old_version = $row['version']; |
||
935 | $db_changes = empty($row['db_changes']) ? array() : $smcFunc['json_decode']($row['db_changes'], true); |
||
936 | } |
||
937 | $smcFunc['db_free_result']($request); |
||
938 | |||
939 | // Wait, it's not installed yet! |
||
940 | // @todo Replace with a better error message! |
||
941 | if (!isset($old_version) && $context['uninstalling']) |
||
942 | { |
||
943 | deltree($packagesdir . '/temp'); |
||
944 | fatal_error('Hacker?', false); |
||
945 | } |
||
946 | // Uninstalling? |
||
947 | elseif ($context['uninstalling']) |
||
948 | { |
||
949 | $install_log = parsePackageInfo($packageInfo['xml'], false, 'uninstall'); |
||
950 | |||
951 | // Gadzooks! There's no uninstaller at all!? |
||
952 | if (empty($install_log)) |
||
953 | fatal_lang_error('package_uninstall_cannot', false); |
||
954 | |||
955 | // They can only uninstall from what it was originally installed into. |
||
956 | foreach ($theme_paths as $id => $data) |
||
957 | if ($id != 1 && !in_array($id, $old_themes)) |
||
958 | unset($theme_paths[$id]); |
||
959 | |||
960 | $context['keep_url'] = $scripturl . '?action=admin;area=packages;sa=browse;' . $context['session_var'] . '=' . $context['session_id']; |
||
961 | $context['remove_url'] = $scripturl . '?action=admin;area=packages;sa=remove;package=' . $context['filename'] . ';' . $context['session_var'] . '=' . $context['session_id']; |
||
962 | } |
||
963 | elseif (isset($old_version) && $old_version != $packageInfo['version']) |
||
964 | { |
||
965 | // Look for an upgrade... |
||
966 | $install_log = parsePackageInfo($packageInfo['xml'], false, 'upgrade', $old_version); |
||
967 | |||
968 | // There was no upgrade.... |
||
969 | if (empty($install_log)) |
||
970 | $context['is_installed'] = true; |
||
971 | else |
||
972 | { |
||
973 | // Upgrade previous themes only! |
||
974 | foreach ($theme_paths as $id => $data) |
||
975 | if ($id != 1 && !in_array($id, $old_themes)) |
||
976 | unset($theme_paths[$id]); |
||
977 | } |
||
978 | } |
||
979 | elseif (isset($old_version) && $old_version == $packageInfo['version']) |
||
980 | $context['is_installed'] = true; |
||
981 | |||
982 | if (!isset($old_version) || $context['is_installed']) |
||
983 | $install_log = parsePackageInfo($packageInfo['xml'], false, 'install'); |
||
984 | |||
985 | $context['install_finished'] = false; |
||
986 | |||
987 | // @todo Make a log of any errors that occurred and output them? |
||
988 | |||
989 | if (!empty($install_log)) |
||
990 | { |
||
991 | $failed_steps = array(); |
||
992 | $failed_count = 0; |
||
993 | |||
994 | foreach ($install_log as $action) |
||
995 | { |
||
996 | $failed_count++; |
||
997 | |||
998 | if ($action['type'] == 'modification' && !empty($action['filename'])) |
||
999 | { |
||
1000 | if ($action['boardmod']) |
||
1001 | $mod_actions = parseBoardMod(file_get_contents($packagesdir . '/temp/' . $context['base_path'] . $action['filename']), false, $action['reverse'], $theme_paths); |
||
1002 | else |
||
1003 | $mod_actions = parseModification(file_get_contents($packagesdir . '/temp/' . $context['base_path'] . $action['filename']), false, $action['reverse'], $theme_paths); |
||
1004 | |||
1005 | // Any errors worth noting? |
||
1006 | foreach ($mod_actions as $key => $modAction) |
||
1007 | { |
||
1008 | if ($modAction['type'] == 'failure') |
||
1009 | $failed_steps[] = array( |
||
1010 | 'file' => $modAction['filename'], |
||
1011 | 'large_step' => $failed_count, |
||
1012 | 'sub_step' => $key, |
||
1013 | 'theme' => 1, |
||
1014 | ); |
||
1015 | |||
1016 | // Gather the themes we installed into. |
||
1017 | if (!empty($modAction['is_custom'])) |
||
1018 | $themes_installed[] = $modAction['is_custom']; |
||
1019 | } |
||
1020 | } |
||
1021 | elseif ($action['type'] == 'code' && !empty($action['filename'])) |
||
1022 | { |
||
1023 | // This is just here as reference for what is available. |
||
1024 | global $txt, $boarddir, $sourcedir, $modSettings, $context, $settings, $smcFunc; |
||
1025 | |||
1026 | // Now include the file and be done with it ;). |
||
1027 | if (file_exists($packagesdir . '/temp/' . $context['base_path'] . $action['filename'])) |
||
1028 | require($packagesdir . '/temp/' . $context['base_path'] . $action['filename']); |
||
1029 | } |
||
1030 | elseif ($action['type'] == 'credits') |
||
1031 | { |
||
1032 | // Time to build the billboard |
||
1033 | $credits_tag = array( |
||
1034 | 'url' => $action['url'], |
||
1035 | 'license' => $action['license'], |
||
1036 | 'licenseurl' => $action['licenseurl'], |
||
1037 | 'copyright' => $action['copyright'], |
||
1038 | 'title' => $action['title'], |
||
1039 | ); |
||
1040 | } |
||
1041 | elseif ($action['type'] == 'hook' && isset($action['hook'], $action['function'])) |
||
1042 | { |
||
1043 | // Set the system to ignore hooks, but only if it wasn't changed before. |
||
1044 | if (!isset($context['ignore_hook_errors'])) |
||
1045 | $context['ignore_hook_errors'] = true; |
||
1046 | |||
1047 | if ($action['reverse']) |
||
1048 | remove_integration_function($action['hook'], $action['function'], true, $action['include_file'], $action['object']); |
||
1049 | else |
||
1050 | add_integration_function($action['hook'], $action['function'], true, $action['include_file'], $action['object']); |
||
1051 | } |
||
1052 | // Only do the database changes on uninstall if requested. |
||
1053 | elseif ($action['type'] == 'database' && !empty($action['filename']) && (!$context['uninstalling'] || !empty($_POST['do_db_changes']))) |
||
1054 | { |
||
1055 | // These can also be there for database changes. |
||
1056 | global $txt, $boarddir, $sourcedir, $modSettings, $context, $settings, $smcFunc; |
||
1057 | global $db_package_log; |
||
1058 | |||
1059 | // We'll likely want the package specific database functionality! |
||
1060 | db_extend('packages'); |
||
1061 | |||
1062 | // Let the file work its magic ;) |
||
1063 | if (file_exists($packagesdir . '/temp/' . $context['base_path'] . $action['filename'])) |
||
1064 | require($packagesdir . '/temp/' . $context['base_path'] . $action['filename']); |
||
1065 | } |
||
1066 | // Handle a redirect... |
||
1067 | elseif ($action['type'] == 'redirect' && !empty($action['redirect_url'])) |
||
1068 | { |
||
1069 | $context['redirect_url'] = $action['redirect_url']; |
||
1070 | $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']); |
||
1071 | $context['redirect_timeout'] = empty($action['redirect_timeout']) ? 5 : (int) ceil($action['redirect_timeout'] / 1000); |
||
1072 | if (!empty($action['parse_bbc'])) |
||
1073 | { |
||
1074 | require_once($sourcedir . '/Subs-Post.php'); |
||
1075 | $context['redirect_text'] = preg_replace('~\[[/]?html\]~i', '', $context['redirect_text']); |
||
1076 | preparsecode($context['redirect_text']); |
||
1077 | $context['redirect_text'] = parse_bbc($context['redirect_text']); |
||
1078 | } |
||
1079 | |||
1080 | // Parse out a couple of common urls. |
||
1081 | $urls = array( |
||
1082 | '$boardurl' => $boardurl, |
||
1083 | '$scripturl' => $scripturl, |
||
1084 | '$session_var' => $context['session_var'], |
||
1085 | '$session_id' => $context['session_id'], |
||
1086 | ); |
||
1087 | |||
1088 | $context['redirect_url'] = strtr($context['redirect_url'], $urls); |
||
1089 | } |
||
1090 | } |
||
1091 | |||
1092 | package_flush_cache(); |
||
1093 | |||
1094 | // See if this is already installed, and change it's state as required. |
||
1095 | $request = $smcFunc['db_query']('', ' |
||
1096 | SELECT package_id, install_state, db_changes |
||
1097 | FROM {db_prefix}log_packages |
||
1098 | WHERE install_state != {int:not_installed} |
||
1099 | AND package_id = {string:current_package} |
||
1100 | ' . ($context['install_id'] ? ' AND id_install = {int:install_id} ' : '') . ' |
||
1101 | ORDER BY time_installed DESC |
||
1102 | LIMIT 1', |
||
1103 | array( |
||
1104 | 'not_installed' => 0, |
||
1105 | 'install_id' => $context['install_id'], |
||
1106 | 'current_package' => $packageInfo['id'], |
||
1107 | ) |
||
1108 | ); |
||
1109 | $is_upgrade = false; |
||
1110 | while ($row = $smcFunc['db_fetch_assoc']($request)) |
||
1111 | { |
||
1112 | // Uninstalling? |
||
1113 | if ($context['uninstalling']) |
||
1114 | { |
||
1115 | $smcFunc['db_query']('', ' |
||
1116 | UPDATE {db_prefix}log_packages |
||
1117 | SET install_state = {int:not_installed}, member_removed = {string:member_name}, |
||
1118 | id_member_removed = {int:current_member}, time_removed = {int:current_time}, sha256_hash = {string:package_hash} |
||
1119 | WHERE package_id = {string:package_id} |
||
1120 | AND id_install = {int:install_id}', |
||
1121 | array( |
||
1122 | 'current_member' => $user_info['id'], |
||
1123 | 'not_installed' => 0, |
||
1124 | 'current_time' => time(), |
||
1125 | 'package_id' => $row['package_id'], |
||
1126 | 'member_name' => $user_info['name'], |
||
1127 | 'install_id' => $context['install_id'], |
||
1128 | 'package_hash' => $context['package_sha256_hash'], |
||
1129 | ) |
||
1130 | ); |
||
1131 | } |
||
1132 | // Otherwise must be an upgrade. |
||
1133 | else |
||
1134 | { |
||
1135 | $is_upgrade = true; |
||
1136 | $old_db_changes = empty($row['db_changes']) ? array() : $smcFunc['json_decode']($row['db_changes'], true); |
||
1137 | |||
1138 | // Mark the old version as uninstalled |
||
1139 | $smcFunc['db_query']('', ' |
||
1140 | UPDATE {db_prefix}log_packages |
||
1141 | SET install_state = {int:not_installed}, member_removed = {string:member_name}, |
||
1142 | id_member_removed = {int:current_member}, time_removed = {int:current_time}, sha256_hash = {string:package_hash} |
||
1143 | WHERE package_id = {string:package_id} |
||
1144 | AND version = {string:old_version}', |
||
1145 | array( |
||
1146 | 'current_member' => $user_info['id'], |
||
1147 | 'not_installed' => 0, |
||
1148 | 'current_time' => time(), |
||
1149 | 'package_id' => $row['package_id'], |
||
1150 | 'member_name' => $user_info['name'], |
||
1151 | 'old_version' => $old_version, |
||
0 ignored issues
–
show
Comprehensibility
Best Practice
introduced
by
![]() |
|||
1152 | 'package_hash' => $context['package_sha256_hash'], |
||
1153 | ) |
||
1154 | ); |
||
1155 | } |
||
1156 | } |
||
1157 | |||
1158 | // Assuming we're not uninstalling, add the entry. |
||
1159 | if (!$context['uninstalling']) |
||
1160 | { |
||
1161 | // Reload the settings table for mods that have altered them upon installation |
||
1162 | reloadSettings(); |
||
1163 | |||
1164 | // Any db changes from older version? |
||
1165 | if (!empty($old_db_changes)) |
||
1166 | $db_package_log = empty($db_package_log) ? $old_db_changes : array_merge($old_db_changes, $db_package_log); |
||
1167 | |||
1168 | // If there are some database changes we might want to remove then filter them out. |
||
1169 | if (!empty($db_package_log)) |
||
1170 | { |
||
1171 | // We're really just checking for entries which are create table AND add columns (etc). |
||
1172 | $tables = array(); |
||
1173 | |||
1174 | usort($db_package_log, function($a, $b) |
||
1175 | { |
||
1176 | if ($a[0] == $b[0]) |
||
1177 | return 0; |
||
1178 | return $a[0] == 'remove_table' ? -1 : 1; |
||
1179 | }); |
||
1180 | |||
1181 | foreach ($db_package_log as $k => $log) |
||
1182 | { |
||
1183 | if ($log[0] == 'remove_table') |
||
1184 | $tables[] = $log[1]; |
||
1185 | elseif (in_array($log[1], $tables)) |
||
1186 | unset($db_package_log[$k]); |
||
1187 | } |
||
1188 | $db_changes = $smcFunc['json_encode']($db_package_log); |
||
1189 | } |
||
1190 | else |
||
1191 | $db_changes = ''; |
||
1192 | |||
1193 | // What themes did we actually install? |
||
1194 | $themes_installed = array_unique($themes_installed); |
||
1195 | $themes_installed = implode(',', $themes_installed); |
||
1196 | |||
1197 | // What failed steps? |
||
1198 | $failed_step_insert = $smcFunc['json_encode']($failed_steps); |
||
1199 | |||
1200 | // Un-sanitize things before we insert them... |
||
1201 | $keys = array('filename', 'name', 'id', 'version'); |
||
1202 | foreach ($keys as $key) |
||
1203 | { |
||
1204 | // Yay for variable variables... |
||
1205 | ${"package_$key"} = un_htmlspecialchars($packageInfo[$key]); |
||
1206 | } |
||
1207 | |||
1208 | // Credits tag? |
||
1209 | $credits_tag = (empty($credits_tag)) ? '' : $smcFunc['json_encode']($credits_tag); |
||
1210 | $smcFunc['db_insert']('', |
||
1211 | '{db_prefix}log_packages', |
||
1212 | array( |
||
1213 | 'filename' => 'string', 'name' => 'string', 'package_id' => 'string', 'version' => 'string', |
||
1214 | 'id_member_installed' => 'int', 'member_installed' => 'string', 'time_installed' => 'int', |
||
1215 | 'install_state' => 'int', 'failed_steps' => 'string', 'themes_installed' => 'string', |
||
1216 | 'member_removed' => 'int', 'db_changes' => 'string', 'credits' => 'string', |
||
1217 | 'sha256_hash' => 'string', |
||
1218 | ), |
||
1219 | array( |
||
1220 | $package_filename, $package_name, $package_id, $package_version, |
||
1221 | $user_info['id'], $user_info['name'], time(), |
||
1222 | $is_upgrade ? 2 : 1, $failed_step_insert, $themes_installed, |
||
1223 | 0, $db_changes, $credits_tag, $context['package_sha256_hash'] |
||
1224 | ), |
||
1225 | array('id_install') |
||
1226 | ); |
||
1227 | } |
||
1228 | $smcFunc['db_free_result']($request); |
||
1229 | |||
1230 | $context['install_finished'] = true; |
||
1231 | } |
||
1232 | |||
1233 | // If there's database changes - and they want them removed - let's do it last! |
||
1234 | if (!empty($db_changes) && !empty($_POST['do_db_changes'])) |
||
1235 | { |
||
1236 | // We're gonna be needing the package db functions! |
||
1237 | db_extend('packages'); |
||
1238 | |||
1239 | foreach ($db_changes as $change) |
||
1240 | { |
||
1241 | if ($change[0] == 'remove_table' && isset($change[1])) |
||
1242 | $smcFunc['db_drop_table']($change[1]); |
||
1243 | elseif ($change[0] == 'remove_column' && isset($change[2])) |
||
1244 | $smcFunc['db_remove_column']($change[1], $change[2]); |
||
1245 | elseif ($change[0] == 'remove_index' && isset($change[2])) |
||
1246 | $smcFunc['db_remove_index']($change[1], $change[2]); |
||
1247 | } |
||
1248 | } |
||
1249 | |||
1250 | // Clean house... get rid of the evidence ;). |
||
1251 | if (file_exists($packagesdir . '/temp')) |
||
1252 | deltree($packagesdir . '/temp'); |
||
1253 | |||
1254 | // Log what we just did. |
||
1255 | logAction($context['uninstalling'] ? 'uninstall_package' : (!empty($is_upgrade) ? 'upgrade_package' : 'install_package'), array('package' => $smcFunc['htmlspecialchars']($packageInfo['name']), 'version' => $smcFunc['htmlspecialchars']($packageInfo['version'])), 'admin'); |
||
1256 | |||
1257 | // Just in case, let's clear the whole cache and any minimized CSS and JS to avoid anything going up the swanny. |
||
1258 | clean_cache(); |
||
1259 | deleteAllMinified(); |
||
1260 | |||
1261 | foreach (array('css_files', 'javascript_files') as $file_type) |
||
1262 | { |
||
1263 | foreach ($context[$file_type] as $id => $file) |
||
1264 | { |
||
1265 | if (isset($file['filePath']) && !file_exists($file['filePath'])) |
||
1266 | unset($context[$file_type][$id]); |
||
1267 | } |
||
1268 | } |
||
1269 | |||
1270 | // Restore file permissions? |
||
1271 | create_chmod_control(array(), array(), true); |
||
1272 | } |
||
1273 | |||
1274 | /** |
||
1275 | * List the files in a package. |
||
1276 | */ |
||
1277 | function PackageList() |
||
1278 | { |
||
1279 | global $txt, $scripturl, $context, $sourcedir, $packagesdir; |
||
1280 | |||
1281 | require_once($sourcedir . '/Subs-Package.php'); |
||
1282 | |||
1283 | // No package? Show him or her the door. |
||
1284 | if (!isset($_REQUEST['package']) || $_REQUEST['package'] == '') |
||
1285 | redirectexit('action=admin;area=packages'); |
||
1286 | |||
1287 | $context['linktree'][] = array( |
||
1288 | 'url' => $scripturl . '?action=admin;area=packages;sa=list;package=' . $_REQUEST['package'], |
||
1289 | 'name' => $txt['list_file'] |
||
1290 | ); |
||
1291 | $context['page_title'] .= ' - ' . $txt['list_file']; |
||
1292 | $context['sub_template'] = 'list'; |
||
1293 | |||
1294 | // The filename... |
||
1295 | $context['filename'] = $_REQUEST['package']; |
||
1296 | |||
1297 | // Let the unpacker do the work. |
||
1298 | if (is_file($packagesdir . '/' . $context['filename'])) |
||
1299 | $context['files'] = read_tgz_file($packagesdir . '/' . $context['filename'], null); |
||
1300 | elseif (is_dir($packagesdir . '/' . $context['filename'])) |
||
1301 | $context['files'] = listtree($packagesdir . '/' . $context['filename']); |
||
1302 | } |
||
1303 | |||
1304 | /** |
||
1305 | * Display one of the files in a package. |
||
1306 | */ |
||
1307 | function ExamineFile() |
||
1308 | { |
||
1309 | global $txt, $scripturl, $context, $sourcedir, $packagesdir, $smcFunc; |
||
1310 | |||
1311 | require_once($sourcedir . '/Subs-Package.php'); |
||
1312 | |||
1313 | // No package? Show him or her the door. |
||
1314 | if (!isset($_REQUEST['package']) || $_REQUEST['package'] == '') |
||
1315 | redirectexit('action=admin;area=packages'); |
||
1316 | |||
1317 | // No file? Show him or her the door. |
||
1318 | if (!isset($_REQUEST['file']) || $_REQUEST['file'] == '') |
||
1319 | redirectexit('action=admin;area=packages'); |
||
1320 | |||
1321 | $_REQUEST['package'] = preg_replace('~[\.]+~', '.', strtr($_REQUEST['package'], array('/' => '_', '\\' => '_'))); |
||
1322 | $_REQUEST['file'] = preg_replace('~[\.]+~', '.', $_REQUEST['file']); |
||
1323 | |||
1324 | if (isset($_REQUEST['raw'])) |
||
1325 | { |
||
1326 | if (is_file($packagesdir . '/' . $_REQUEST['package'])) |
||
1327 | echo read_tgz_file($packagesdir . '/' . $_REQUEST['package'], $_REQUEST['file'], true); |
||
1328 | elseif (is_dir($packagesdir . '/' . $_REQUEST['package'])) |
||
1329 | echo file_get_contents($packagesdir . '/' . $_REQUEST['package'] . '/' . $_REQUEST['file']); |
||
1330 | |||
1331 | obExit(false); |
||
1332 | } |
||
1333 | |||
1334 | $context['linktree'][count($context['linktree']) - 1] = array( |
||
1335 | 'url' => $scripturl . '?action=admin;area=packages;sa=list;package=' . $_REQUEST['package'], |
||
1336 | 'name' => $txt['package_examine_file'] |
||
1337 | ); |
||
1338 | $context['page_title'] .= ' - ' . $txt['package_examine_file']; |
||
1339 | $context['sub_template'] = 'examine'; |
||
1340 | |||
1341 | // The filename... |
||
1342 | $context['package'] = $_REQUEST['package']; |
||
1343 | $context['filename'] = $_REQUEST['file']; |
||
1344 | |||
1345 | // Let the unpacker do the work.... but make sure we handle images properly. |
||
1346 | if (in_array(strtolower(strrchr($_REQUEST['file'], '.')), array('.bmp', '.gif', '.jpeg', '.jpg', '.png'))) |
||
1347 | $context['filedata'] = '<img src="' . $scripturl . '?action=admin;area=packages;sa=examine;package=' . $_REQUEST['package'] . ';file=' . $_REQUEST['file'] . ';raw" alt="' . $_REQUEST['file'] . '">'; |
||
1348 | else |
||
1349 | { |
||
1350 | if (is_file($packagesdir . '/' . $_REQUEST['package'])) |
||
1351 | $context['filedata'] = $smcFunc['htmlspecialchars'](read_tgz_file($packagesdir . '/' . $_REQUEST['package'], $_REQUEST['file'], true)); |
||
1352 | elseif (is_dir($packagesdir . '/' . $_REQUEST['package'])) |
||
1353 | $context['filedata'] = $smcFunc['htmlspecialchars'](file_get_contents($packagesdir . '/' . $_REQUEST['package'] . '/' . $_REQUEST['file'])); |
||
1354 | |||
1355 | if (strtolower(strrchr($_REQUEST['file'], '.')) == '.php') |
||
1356 | $context['filedata'] = highlight_php_code($context['filedata']); |
||
1357 | } |
||
1358 | } |
||
1359 | |||
1360 | /** |
||
1361 | * Delete a package. |
||
1362 | */ |
||
1363 | function PackageRemove() |
||
1364 | { |
||
1365 | global $scripturl, $packagesdir; |
||
1366 | |||
1367 | // Check it. |
||
1368 | checkSession('get'); |
||
1369 | |||
1370 | // Ack, don't allow deletion of arbitrary files here, could become a security hole somehow! |
||
1371 | if (!isset($_GET['package']) || $_GET['package'] == 'index.php' || $_GET['package'] == 'backups') |
||
1372 | redirectexit('action=admin;area=packages;sa=browse'); |
||
1373 | $_GET['package'] = preg_replace('~[\.]+~', '.', strtr($_GET['package'], array('/' => '_', '\\' => '_'))); |
||
1374 | |||
1375 | // Can't delete what's not there. |
||
1376 | 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) != '.') |
||
1377 | { |
||
1378 | create_chmod_control(array($packagesdir . '/' . $_GET['package']), array('destination_url' => $scripturl . '?action=admin;area=packages;sa=remove;package=' . $_GET['package'], 'crash_on_error' => true)); |
||
1379 | |||
1380 | if (is_dir($packagesdir . '/' . $_GET['package'])) |
||
1381 | deltree($packagesdir . '/' . $_GET['package']); |
||
1382 | else |
||
1383 | { |
||
1384 | smf_chmod($packagesdir . '/' . $_GET['package'], 0777); |
||
1385 | unlink($packagesdir . '/' . $_GET['package']); |
||
1386 | } |
||
1387 | } |
||
1388 | |||
1389 | redirectexit('action=admin;area=packages;sa=browse'); |
||
1390 | } |
||
1391 | |||
1392 | /** |
||
1393 | * Browse a list of installed packages. |
||
1394 | */ |
||
1395 | function PackageBrowse() |
||
1396 | { |
||
1397 | global $txt, $scripturl, $context, $sourcedir, $smcFunc; |
||
1398 | |||
1399 | $context['page_title'] .= ' - ' . $txt['browse_packages']; |
||
1400 | |||
1401 | $context['forum_version'] = SMF_FULL_VERSION; |
||
1402 | $context['available_packages'] = 0; |
||
1403 | $context['modification_types'] = array('modification', 'avatar', 'language', 'unknown'); |
||
1404 | |||
1405 | call_integration_hook('integrate_modification_types'); |
||
1406 | |||
1407 | require_once($sourcedir . '/Subs-List.php'); |
||
1408 | |||
1409 | foreach ($context['modification_types'] as $type) |
||
1410 | { |
||
1411 | // Use the standard templates for showing this. |
||
1412 | $listOptions = array( |
||
1413 | 'id' => 'packages_lists_' . $type, |
||
1414 | 'title' => $txt[$type . '_package'], |
||
1415 | 'no_items_label' => $txt['no_packages'], |
||
1416 | 'get_items' => array( |
||
1417 | 'function' => 'list_getPackages', |
||
1418 | 'params' => array($type), |
||
1419 | ), |
||
1420 | 'base_href' => $scripturl . '?action=admin;area=packages;sa=browse;type=' . $type, |
||
1421 | 'default_sort_col' => 'id' . $type, |
||
1422 | 'columns' => array( |
||
1423 | 'id' . $type => array( |
||
1424 | 'header' => array( |
||
1425 | 'value' => $txt['package_id'], |
||
1426 | 'style' => 'width: 52px;', |
||
1427 | ), |
||
1428 | 'data' => array( |
||
1429 | 'db' => 'sort_id', |
||
1430 | ), |
||
1431 | 'sort' => array( |
||
1432 | 'default' => 'sort_id', |
||
1433 | 'reverse' => 'sort_id' |
||
1434 | ), |
||
1435 | ), |
||
1436 | 'mod_name' . $type => array( |
||
1437 | 'header' => array( |
||
1438 | 'value' => $txt['mod_name'], |
||
1439 | 'style' => 'width: 25%;', |
||
1440 | ), |
||
1441 | 'data' => array( |
||
1442 | 'db' => 'name', |
||
1443 | ), |
||
1444 | 'sort' => array( |
||
1445 | 'default' => 'name', |
||
1446 | 'reverse' => 'name', |
||
1447 | ), |
||
1448 | ), |
||
1449 | 'version' . $type => array( |
||
1450 | 'header' => array( |
||
1451 | 'value' => $txt['mod_version'], |
||
1452 | ), |
||
1453 | 'data' => array( |
||
1454 | 'db' => 'version', |
||
1455 | ), |
||
1456 | 'sort' => array( |
||
1457 | 'default' => 'version', |
||
1458 | 'reverse' => 'version', |
||
1459 | ), |
||
1460 | ), |
||
1461 | 'time_installed' . $type => array( |
||
1462 | 'header' => array( |
||
1463 | 'value' => $txt['mod_installed_time'], |
||
1464 | ), |
||
1465 | 'data' => array( |
||
1466 | 'function' => function($package) use ($txt) |
||
1467 | { |
||
1468 | return !empty($package['time_installed']) |
||
1469 | ? timeformat($package['time_installed']) |
||
1470 | : $txt['not_applicable']; |
||
1471 | }, |
||
1472 | 'class' => 'smalltext', |
||
1473 | ), |
||
1474 | 'sort' => array( |
||
1475 | 'default' => 'time_installed', |
||
1476 | 'reverse' => 'time_installed', |
||
1477 | ), |
||
1478 | ), |
||
1479 | 'operations' . $type => array( |
||
1480 | 'header' => array( |
||
1481 | 'value' => '', |
||
1482 | ), |
||
1483 | 'data' => array( |
||
1484 | 'function' => function($package) use ($context, $scripturl, $txt, $type) |
||
1485 | { |
||
1486 | $return = ''; |
||
1487 | |||
1488 | if ($package['can_uninstall']) |
||
1489 | $return = ' |
||
1490 | <a href="' . $scripturl . '?action=admin;area=packages;sa=uninstall;package=' . $package['filename'] . ';pid=' . $package['installed_id'] . '" class="button floatnone">' . (isset($txt['uninstall_' . $type]) ? $txt['uninstall_' . $type] : $txt['uninstall']) . '</a>'; |
||
1491 | elseif ($package['can_emulate_uninstall']) |
||
1492 | $return = ' |
||
1493 | <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>'; |
||
1494 | elseif ($package['can_upgrade']) |
||
1495 | $return = ' |
||
1496 | <a href="' . $scripturl . '?action=admin;area=packages;sa=install;package=' . $package['filename'] . '" class="button floatnone">' . $txt['package_upgrade'] . '</a>'; |
||
1497 | elseif ($package['can_install']) |
||
1498 | $return = ' |
||
1499 | <a href="' . $scripturl . '?action=admin;area=packages;sa=install;package=' . $package['filename'] . '" class="button floatnone">' . $txt['install_' . $type] . '</a>'; |
||
1500 | elseif ($package['can_emulate_install']) |
||
1501 | $return = ' |
||
1502 | <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>'; |
||
1503 | |||
1504 | return $return . ' |
||
1505 | <a href="' . $scripturl . '?action=admin;area=packages;sa=list;package=' . $package['filename'] . '" class="button floatnone">' . $txt['list_files'] . '</a> |
||
1506 | <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>'; |
||
1507 | }, |
||
1508 | 'class' => 'righttext', |
||
1509 | ), |
||
1510 | ), |
||
1511 | ), |
||
1512 | ); |
||
1513 | |||
1514 | createList($listOptions); |
||
1515 | } |
||
1516 | |||
1517 | $context['sub_template'] = 'browse'; |
||
1518 | $context['default_list'] = 'packages_lists'; |
||
1519 | |||
1520 | $get_versions = $smcFunc['db_query']('', ' |
||
1521 | SELECT data FROM {db_prefix}admin_info_files WHERE filename={string:versionsfile} AND path={string:smf}', |
||
1522 | array( |
||
1523 | 'versionsfile' => 'latest-versions.txt', |
||
1524 | 'smf' => '/smf/', |
||
1525 | ) |
||
1526 | ); |
||
1527 | |||
1528 | $data = $smcFunc['db_fetch_assoc']($get_versions); |
||
1529 | $smcFunc['db_free_result']($get_versions); |
||
1530 | |||
1531 | // Decode the data. |
||
1532 | $items = $smcFunc['json_decode']($data['data'], true); |
||
1533 | |||
1534 | $context['emulation_versions'] = preg_replace('~^SMF ~', '', $items); |
||
1535 | |||
1536 | // Current SMF version, which is selected by default |
||
1537 | $context['default_version'] = SMF_VERSION; |
||
1538 | |||
1539 | if (!in_array($context['default_version'], $context['emulation_versions'])) |
||
1540 | { |
||
1541 | $context['emulation_versions'][] = $context['default_version']; |
||
1542 | } |
||
1543 | |||
1544 | // Version we're currently emulating, if any |
||
1545 | $context['selected_version'] = preg_replace('~^SMF ~', '', $context['forum_version']); |
||
1546 | } |
||
1547 | |||
1548 | /** |
||
1549 | * Get a listing of all the packages |
||
1550 | * |
||
1551 | * Determines if the package is a mod, avatar, or language package and |
||
1552 | * groups it accordingly. If a package is not recognised as one of the |
||
1553 | * above, it is then put into a special group, "unknown". |
||
1554 | * |
||
1555 | * Determines whether the package has been installed or not by |
||
1556 | * checking it against {@link loadInstalledPackages()}. |
||
1557 | * |
||
1558 | * @param int $start The item to start with (not used here) |
||
1559 | * @param int $items_per_page The number of items to show per page (not used here) |
||
1560 | * @param string $sort A string indicating how to sort the results |
||
1561 | * @param string $params Type of packages |
||
1562 | * @return array An array of information about the packages |
||
1563 | */ |
||
1564 | function list_getPackages($start, $items_per_page, $sort, $params) |
||
1565 | { |
||
1566 | global $scripturl, $packagesdir, $context; |
||
1567 | static $installed_mods; |
||
1568 | |||
1569 | $packages = array(); |
||
1570 | $column = array(); |
||
1571 | |||
1572 | // We need the packages directory to be writable for this. |
||
1573 | if (!@is_writable($packagesdir)) |
||
1574 | create_chmod_control(array($packagesdir), array('destination_url' => $scripturl . '?action=admin;area=packages', 'crash_on_error' => true)); |
||
1575 | |||
1576 | $the_version = SMF_VERSION; |
||
1577 | |||
1578 | // Here we have a little code to help those who class themselves as something of gods, version emulation ;) |
||
1579 | if (isset($_GET['version_emulate']) && strtr($_GET['version_emulate'], array('SMF ' => '')) == $the_version) |
||
1580 | { |
||
1581 | unset($_SESSION['version_emulate']); |
||
1582 | } |
||
1583 | elseif (isset($_GET['version_emulate'])) |
||
1584 | { |
||
1585 | if (($_GET['version_emulate'] === 0 || $_GET['version_emulate'] === SMF_FULL_VERSION) && isset($_SESSION['version_emulate'])) |
||
1586 | unset($_SESSION['version_emulate']); |
||
1587 | elseif ($_GET['version_emulate'] !== 0) |
||
1588 | $_SESSION['version_emulate'] = strtr($_GET['version_emulate'], array('-' => ' ', '+' => ' ', 'SMF ' => '')); |
||
1589 | } |
||
1590 | if (!empty($_SESSION['version_emulate'])) |
||
1591 | { |
||
1592 | $context['forum_version'] = 'SMF ' . $_SESSION['version_emulate']; |
||
1593 | $the_version = $_SESSION['version_emulate']; |
||
1594 | } |
||
1595 | if (isset($_SESSION['single_version_emulate'])) |
||
1596 | unset($_SESSION['single_version_emulate']); |
||
1597 | |||
1598 | if (empty($installed_mods)) |
||
1599 | { |
||
1600 | $instmods = loadInstalledPackages(); |
||
1601 | $installed_mods = array(); |
||
1602 | // Look through the list of installed mods... |
||
1603 | foreach ($instmods as $installed_mod) |
||
1604 | $installed_mods[$installed_mod['package_id']] = array( |
||
1605 | 'id' => $installed_mod['id'], |
||
1606 | 'version' => $installed_mod['version'], |
||
1607 | 'time_installed' => $installed_mod['time_installed'], |
||
1608 | ); |
||
1609 | |||
1610 | // Get a list of all the ids installed, so the latest packages won't include already installed ones. |
||
1611 | $context['installed_mods'] = array_keys($installed_mods); |
||
1612 | } |
||
1613 | |||
1614 | if ($dir = @opendir($packagesdir)) |
||
1615 | { |
||
1616 | $dirs = array(); |
||
1617 | $sort_id = array( |
||
1618 | 'modification' => 1, |
||
1619 | 'avatar' => 1, |
||
1620 | 'language' => 1, |
||
1621 | 'unknown' => 1, |
||
1622 | ); |
||
1623 | call_integration_hook('integrate_packages_sort_id', array(&$sort_id, &$packages)); |
||
1624 | |||
1625 | while ($package = readdir($dir)) |
||
1626 | { |
||
1627 | 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')) |
||
1628 | continue; |
||
1629 | |||
1630 | // Skip directories or files that are named the same. |
||
1631 | if (is_dir($packagesdir . '/' . $package)) |
||
1632 | { |
||
1633 | if (in_array($package, $dirs)) |
||
1634 | continue; |
||
1635 | $dirs[] = $package; |
||
1636 | } |
||
1637 | elseif (substr(strtolower($package), -7) == '.tar.gz') |
||
1638 | { |
||
1639 | if (in_array(substr($package, 0, -7), $dirs)) |
||
1640 | continue; |
||
1641 | $dirs[] = substr($package, 0, -7); |
||
1642 | } |
||
1643 | elseif (substr(strtolower($package), -4) == '.zip' || substr(strtolower($package), -4) == '.tgz') |
||
1644 | { |
||
1645 | if (in_array(substr($package, 0, -4), $dirs)) |
||
1646 | continue; |
||
1647 | $dirs[] = substr($package, 0, -4); |
||
1648 | } |
||
1649 | |||
1650 | $packageInfo = getPackageInfo($package); |
||
1651 | if (!is_array($packageInfo)) |
||
1652 | continue; |
||
1653 | |||
1654 | if (!empty($packageInfo)) |
||
1655 | { |
||
1656 | if (!isset($sort_id[$packageInfo['type']])) |
||
1657 | $packageInfo['sort_id'] = $sort_id['unknown']; |
||
1658 | else |
||
1659 | $packageInfo['sort_id'] = $sort_id[$packageInfo['type']]; |
||
1660 | |||
1661 | $packageInfo['time_installed'] = 0; |
||
1662 | $packageInfo['is_installed'] = isset($installed_mods[$packageInfo['id']]); |
||
1663 | if ($packageInfo['is_installed']) |
||
1664 | { |
||
1665 | $packageInfo['is_current'] = $installed_mods[$packageInfo['id']]['version'] == $packageInfo['version']; |
||
1666 | $packageInfo['is_newer'] = $installed_mods[$packageInfo['id']]['version'] > $packageInfo['version']; |
||
1667 | $packageInfo['installed_id'] = $installed_mods[$packageInfo['id']]['id']; |
||
1668 | if ($packageInfo['is_current']) |
||
1669 | $packageInfo['time_installed'] = $installed_mods[$packageInfo['id']]['time_installed']; |
||
1670 | } |
||
1671 | |||
1672 | $packageInfo['can_install'] = false; |
||
1673 | $packageInfo['can_uninstall'] = false; |
||
1674 | $packageInfo['can_upgrade'] = false; |
||
1675 | $packageInfo['can_emulate_install'] = false; |
||
1676 | $packageInfo['can_emulate_uninstall'] = false; |
||
1677 | |||
1678 | // This package is currently NOT installed. Check if it can be. |
||
1679 | if (!$packageInfo['is_installed'] && $packageInfo['xml']->exists('install')) |
||
1680 | { |
||
1681 | // Check if there's an install for *THIS* version of SMF. |
||
1682 | $installs = $packageInfo['xml']->set('install'); |
||
1683 | foreach ($installs as $install) |
||
1684 | { |
||
1685 | if (!$install->exists('@for') || matchPackageVersion($the_version, $install->fetch('@for'))) |
||
1686 | { |
||
1687 | // Okay, this one is good to go. |
||
1688 | $packageInfo['can_install'] = true; |
||
1689 | break; |
||
1690 | } |
||
1691 | } |
||
1692 | |||
1693 | // no install found for this version, lets see if one exists for another |
||
1694 | if ($packageInfo['can_install'] === false && $install->exists('@for') && empty($_SESSION['version_emulate'])) |
||
1695 | { |
||
1696 | $reset = true; |
||
1697 | |||
1698 | // Get the highest install version that is available from the package |
||
1699 | foreach ($installs as $install) |
||
1700 | { |
||
1701 | $packageInfo['can_emulate_install'] = matchHighestPackageVersion($install->fetch('@for'), $reset, $the_version); |
||
1702 | $reset = false; |
||
1703 | } |
||
1704 | } |
||
1705 | } |
||
1706 | // An already installed, but old, package. Can we upgrade it? |
||
1707 | elseif ($packageInfo['is_installed'] && !$packageInfo['is_current'] && $packageInfo['xml']->exists('upgrade')) |
||
1708 | { |
||
1709 | $upgrades = $packageInfo['xml']->set('upgrade'); |
||
1710 | |||
1711 | // First go through, and check against the current version of SMF. |
||
1712 | foreach ($upgrades as $upgrade) |
||
1713 | { |
||
1714 | // Even if it is for this SMF, is it for the installed version of the mod? |
||
1715 | if (!$upgrade->exists('@for') || matchPackageVersion($the_version, $upgrade->fetch('@for'))) |
||
1716 | if (!$upgrade->exists('@from') || matchPackageVersion($installed_mods[$packageInfo['id']]['version'], $upgrade->fetch('@from'))) |
||
1717 | { |
||
1718 | $packageInfo['can_upgrade'] = true; |
||
1719 | break; |
||
1720 | } |
||
1721 | } |
||
1722 | } |
||
1723 | // Note that it has to be the current version to be uninstallable. Shucks. |
||
1724 | elseif ($packageInfo['is_installed'] && $packageInfo['is_current'] && $packageInfo['xml']->exists('uninstall')) |
||
1725 | { |
||
1726 | $uninstalls = $packageInfo['xml']->set('uninstall'); |
||
1727 | |||
1728 | // Can we find any uninstallation methods that work for this SMF version? |
||
1729 | foreach ($uninstalls as $uninstall) |
||
1730 | { |
||
1731 | if (!$uninstall->exists('@for') || matchPackageVersion($the_version, $uninstall->fetch('@for'))) |
||
1732 | { |
||
1733 | $packageInfo['can_uninstall'] = true; |
||
1734 | break; |
||
1735 | } |
||
1736 | } |
||
1737 | |||
1738 | // no uninstall found for this version, lets see if one exists for another |
||
1739 | if ($packageInfo['can_uninstall'] === false && $uninstall->exists('@for') && empty($_SESSION['version_emulate'])) |
||
1740 | { |
||
1741 | $reset = true; |
||
1742 | |||
1743 | // Get the highest install version that is available from the package |
||
1744 | foreach ($uninstalls as $uninstall) |
||
1745 | { |
||
1746 | $packageInfo['can_emulate_uninstall'] = matchHighestPackageVersion($uninstall->fetch('@for'), $reset, $the_version); |
||
1747 | $reset = false; |
||
1748 | } |
||
1749 | } |
||
1750 | } |
||
1751 | |||
1752 | // Save some memory by not passing the xmlArray object into context. |
||
1753 | unset($packageInfo['xml']); |
||
1754 | |||
1755 | if (isset($sort_id[$packageInfo['type']]) && $params == $packageInfo['type']) |
||
1756 | { |
||
1757 | $column[] = $packageInfo[$sort]; |
||
1758 | $sort_id[$packageInfo['type']]++; |
||
1759 | $packages[] = $packageInfo; |
||
1760 | } |
||
1761 | elseif (!isset($sort_id[$packageInfo['type']]) && $params == 'unknown') |
||
1762 | { |
||
1763 | $column[] = $packageInfo[$sort]; |
||
1764 | $packageInfo['sort_id'] = $sort_id['unknown']; |
||
1765 | $sort_id['unknown']++; |
||
1766 | $packages[] = $packageInfo; |
||
1767 | } |
||
1768 | } |
||
1769 | } |
||
1770 | closedir($dir); |
||
1771 | } |
||
1772 | $context['available_packages'] += count($packages); |
||
1773 | array_multisort( |
||
1774 | $column, |
||
1775 | isset($_GET['desc']) ? SORT_DESC : SORT_ASC, |
||
1776 | $packages |
||
1777 | ); |
||
1778 | |||
1779 | return $packages; |
||
1780 | } |
||
1781 | |||
1782 | /** |
||
1783 | * Used when a temp FTP access is needed to package functions |
||
1784 | */ |
||
1785 | function PackageOptions() |
||
1786 | { |
||
1787 | global $txt, $context, $modSettings, $smcFunc; |
||
1788 | |||
1789 | if (isset($_POST['save'])) |
||
1790 | { |
||
1791 | checkSession(); |
||
1792 | |||
1793 | updateSettings(array( |
||
1794 | 'package_server' => trim($smcFunc['htmlspecialchars']($_POST['pack_server'])), |
||
1795 | 'package_port' => trim($smcFunc['htmlspecialchars']($_POST['pack_port'])), |
||
1796 | 'package_username' => trim($smcFunc['htmlspecialchars']($_POST['pack_user'])), |
||
1797 | 'package_make_backups' => !empty($_POST['package_make_backups']), |
||
1798 | 'package_make_full_backups' => !empty($_POST['package_make_full_backups']) |
||
1799 | )); |
||
1800 | $_SESSION['adm-save'] = true; |
||
1801 | |||
1802 | redirectexit('action=admin;area=packages;sa=options'); |
||
1803 | } |
||
1804 | |||
1805 | if (preg_match('~^/home\d*/([^/]+?)/public_html~', $_SERVER['DOCUMENT_ROOT'], $match)) |
||
1806 | $default_username = $match[1]; |
||
1807 | else |
||
1808 | $default_username = ''; |
||
1809 | |||
1810 | $context['page_title'] = $txt['package_settings']; |
||
1811 | $context['sub_template'] = 'install_options'; |
||
1812 | |||
1813 | $context['package_ftp_server'] = isset($modSettings['package_server']) ? $modSettings['package_server'] : 'localhost'; |
||
1814 | $context['package_ftp_port'] = isset($modSettings['package_port']) ? $modSettings['package_port'] : '21'; |
||
1815 | $context['package_ftp_username'] = isset($modSettings['package_username']) ? $modSettings['package_username'] : $default_username; |
||
1816 | $context['package_make_backups'] = !empty($modSettings['package_make_backups']); |
||
1817 | $context['package_make_full_backups'] = !empty($modSettings['package_make_full_backups']); |
||
1818 | |||
1819 | if (!empty($_SESSION['adm-save'])) |
||
1820 | { |
||
1821 | $context['saved_successful'] = true; |
||
1822 | unset ($_SESSION['adm-save']); |
||
1823 | } |
||
1824 | } |
||
1825 | |||
1826 | /** |
||
1827 | * List operations |
||
1828 | */ |
||
1829 | function ViewOperations() |
||
1830 | { |
||
1831 | global $context, $txt, $sourcedir, $packagesdir, $smcFunc, $modSettings, $settings; |
||
1832 | |||
1833 | // Can't be in here buddy. |
||
1834 | isAllowedTo('admin_forum'); |
||
1835 | |||
1836 | // We need to know the operation key for the search and replace, mod file looking at, is it a board mod? |
||
1837 | if (!isset($_REQUEST['operation_key'], $_REQUEST['filename']) && !is_numeric($_REQUEST['operation_key'])) |
||
1838 | fatal_lang_error('operation_invalid', 'general'); |
||
1839 | |||
1840 | // Load the required file. |
||
1841 | require_once($sourcedir . '/Subs-Package.php'); |
||
1842 | |||
1843 | // Uninstalling the mod? |
||
1844 | $reverse = isset($_REQUEST['reverse']) ? true : false; |
||
1845 | |||
1846 | // Get the base name. |
||
1847 | $context['filename'] = preg_replace('~[\.]+~', '.', $_REQUEST['package']); |
||
1848 | |||
1849 | // We need to extract this again. |
||
1850 | if (is_file($packagesdir . '/' . $context['filename'])) |
||
1851 | { |
||
1852 | $context['extracted_files'] = read_tgz_file($packagesdir . '/' . $context['filename'], $packagesdir . '/temp'); |
||
1853 | |||
1854 | if ($context['extracted_files'] && !file_exists($packagesdir . '/temp/package-info.xml')) |
||
1855 | foreach ($context['extracted_files'] as $file) |
||
1856 | if (basename($file['filename']) == 'package-info.xml') |
||
1857 | { |
||
1858 | $context['base_path'] = dirname($file['filename']) . '/'; |
||
1859 | break; |
||
1860 | } |
||
1861 | |||
1862 | if (!isset($context['base_path'])) |
||
1863 | $context['base_path'] = ''; |
||
1864 | } |
||
1865 | elseif (is_dir($packagesdir . '/' . $context['filename'])) |
||
1866 | { |
||
1867 | copytree($packagesdir . '/' . $context['filename'], $packagesdir . '/temp'); |
||
1868 | $context['extracted_files'] = listtree($packagesdir . '/temp'); |
||
1869 | $context['base_path'] = ''; |
||
1870 | } |
||
1871 | |||
1872 | // Load up any custom themes we may want to install into... |
||
1873 | $request = $smcFunc['db_query']('', ' |
||
1874 | SELECT id_theme, variable, value |
||
1875 | FROM {db_prefix}themes |
||
1876 | WHERE (id_theme = {int:default_theme} OR id_theme IN ({array_int:known_theme_list})) |
||
1877 | AND variable IN ({string:name}, {string:theme_dir})', |
||
1878 | array( |
||
1879 | 'known_theme_list' => explode(',', $modSettings['knownThemes']), |
||
1880 | 'default_theme' => 1, |
||
1881 | 'name' => 'name', |
||
1882 | 'theme_dir' => 'theme_dir', |
||
1883 | ) |
||
1884 | ); |
||
1885 | $theme_paths = array(); |
||
1886 | while ($row = $smcFunc['db_fetch_assoc']($request)) |
||
1887 | $theme_paths[$row['id_theme']][$row['variable']] = $row['value']; |
||
1888 | $smcFunc['db_free_result']($request); |
||
1889 | |||
1890 | // If we're viewing uninstall operations, only consider themes that |
||
1891 | // the package is actually installed into. |
||
1892 | if (isset($_REQUEST['reverse']) && !empty($_REQUEST['install_id'])) |
||
1893 | { |
||
1894 | $install_id = (int) $_REQUEST['install_id']; |
||
1895 | if ($install_id > 0) |
||
1896 | { |
||
1897 | $old_themes = array(); |
||
1898 | $request = $smcFunc['db_query']('', ' |
||
1899 | SELECT themes_installed |
||
1900 | FROM {db_prefix}log_packages |
||
1901 | WHERE id_install = {int:install_id}', |
||
1902 | array( |
||
1903 | 'install_id' => $install_id, |
||
1904 | ) |
||
1905 | ); |
||
1906 | |||
1907 | if ($smcFunc['db_num_rows']($request) == 1) |
||
1908 | { |
||
1909 | list ($old_themes) = $smcFunc['db_fetch_row']($request); |
||
1910 | $old_themes = explode(',', $old_themes); |
||
1911 | |||
1912 | foreach ($theme_paths as $id => $data) |
||
1913 | if ($id != 1 && !in_array($id, $old_themes)) |
||
1914 | unset($theme_paths[$id]); |
||
1915 | } |
||
1916 | $smcFunc['db_free_result']($request); |
||
1917 | } |
||
1918 | } |
||
1919 | |||
1920 | // Boardmod? |
||
1921 | if (isset($_REQUEST['boardmod'])) |
||
1922 | $mod_actions = parseBoardMod(@file_get_contents($packagesdir . '/temp/' . $context['base_path'] . $_REQUEST['filename']), true, $reverse, $theme_paths); |
||
1923 | else |
||
1924 | $mod_actions = parseModification(@file_get_contents($packagesdir . '/temp/' . $context['base_path'] . $_REQUEST['filename']), true, $reverse, $theme_paths); |
||
1925 | |||
1926 | // Ok lets get the content of the file. |
||
1927 | $context['operations'] = array( |
||
1928 | 'search' => strtr($smcFunc['htmlspecialchars']($mod_actions[$_REQUEST['operation_key']]['search_original']), array('[' => '[', ']' => ']')), |
||
1929 | 'replace' => strtr($smcFunc['htmlspecialchars']($mod_actions[$_REQUEST['operation_key']]['replace_original']), array('[' => '[', ']' => ']')), |
||
1930 | 'position' => $mod_actions[$_REQUEST['operation_key']]['position'], |
||
1931 | ); |
||
1932 | |||
1933 | // Let's do some formatting... |
||
1934 | $operation_text = $context['operations']['position'] == 'replace' ? 'operation_replace' : ($context['operations']['position'] == 'before' ? 'operation_after' : 'operation_before'); |
||
1935 | $context['operations']['search'] = parse_bbc('[code=' . $txt['operation_find'] . ']' . ($context['operations']['position'] == 'end' ? '?>' : $context['operations']['search']) . '[/code]'); |
||
1936 | $context['operations']['replace'] = parse_bbc('[code=' . $txt[$operation_text] . ']' . $context['operations']['replace'] . '[/code]'); |
||
1937 | |||
1938 | // No layers |
||
1939 | $context['template_layers'] = array(); |
||
1940 | $context['sub_template'] = 'view_operations'; |
||
1941 | |||
1942 | // We only want to load these three JavaScript files. |
||
1943 | $context['javascript_files'] = array_intersect_key( |
||
1944 | $context['javascript_files'], |
||
1945 | [ |
||
1946 | 'smf_script_js' => true, |
||
1947 | 'smf_jquery_js' => true |
||
1948 | ] |
||
1949 | ); |
||
1950 | |||
1951 | // Since the alerts code is loaded very late in the process, it must be disabled seperately. |
||
1952 | $settings['disable_files'] = ['smf_alerts']; |
||
1953 | } |
||
1954 | |||
1955 | /** |
||
1956 | * Allow the admin to reset permissions on files. |
||
1957 | */ |
||
1958 | function PackagePermissions() |
||
1959 | { |
||
1960 | global $context, $txt, $modSettings, $boarddir, $sourcedir, $cachedir, $smcFunc, $package_ftp; |
||
1961 | |||
1962 | // Let's try and be good, yes? |
||
1963 | checkSession('get'); |
||
1964 | |||
1965 | // If we're restoring permissions this is just a pass through really. |
||
1966 | if (isset($_GET['restore'])) |
||
1967 | { |
||
1968 | create_chmod_control(array(), array(), true); |
||
1969 | fatal_lang_error('no_access', false); |
||
1970 | } |
||
1971 | |||
1972 | // This is a memory eat. |
||
1973 | setMemoryLimit('128M'); |
||
1974 | @set_time_limit(600); |
||
1975 | |||
1976 | // Load up some FTP stuff. |
||
1977 | create_chmod_control(); |
||
1978 | |||
1979 | if (empty($package_ftp) && !isset($_POST['skip_ftp'])) |
||
1980 | { |
||
1981 | require_once($sourcedir . '/Class-Package.php'); |
||
1982 | $ftp = new ftp_connection(null); |
||
1983 | list ($username, $detect_path, $found_path) = $ftp->detect_path($boarddir); |
||
1984 | |||
1985 | $context['package_ftp'] = array( |
||
1986 | 'server' => isset($modSettings['package_server']) ? $modSettings['package_server'] : 'localhost', |
||
1987 | 'port' => isset($modSettings['package_port']) ? $modSettings['package_port'] : '21', |
||
1988 | 'username' => empty($username) ? (isset($modSettings['package_username']) ? $modSettings['package_username'] : '') : $username, |
||
1989 | 'path' => $detect_path, |
||
1990 | 'form_elements_only' => true, |
||
1991 | ); |
||
1992 | } |
||
1993 | else |
||
1994 | $context['ftp_connected'] = true; |
||
1995 | |||
1996 | // Define the template. |
||
1997 | $context['page_title'] = $txt['package_file_perms']; |
||
1998 | $context['sub_template'] = 'file_permissions'; |
||
1999 | |||
2000 | // Define what files we're interested in, as a tree. |
||
2001 | $context['file_tree'] = array( |
||
2002 | strtr($boarddir, array('\\' => '/')) => array( |
||
2003 | 'type' => 'dir', |
||
2004 | 'contents' => array( |
||
2005 | 'agreement.txt' => array( |
||
2006 | 'type' => 'file', |
||
2007 | 'writable_on' => 'standard', |
||
2008 | ), |
||
2009 | 'Settings.php' => array( |
||
2010 | 'type' => 'file', |
||
2011 | 'writable_on' => 'restrictive', |
||
2012 | ), |
||
2013 | 'Settings_bak.php' => array( |
||
2014 | 'type' => 'file', |
||
2015 | 'writable_on' => 'restrictive', |
||
2016 | ), |
||
2017 | 'attachments' => array( |
||
2018 | 'type' => 'dir', |
||
2019 | 'writable_on' => 'restrictive', |
||
2020 | ), |
||
2021 | 'avatars' => array( |
||
2022 | 'type' => 'dir', |
||
2023 | 'writable_on' => 'standard', |
||
2024 | ), |
||
2025 | 'cache' => array( |
||
2026 | 'type' => 'dir', |
||
2027 | 'writable_on' => 'restrictive', |
||
2028 | ), |
||
2029 | 'custom_avatar_dir' => array( |
||
2030 | 'type' => 'dir', |
||
2031 | 'writable_on' => 'restrictive', |
||
2032 | ), |
||
2033 | 'Smileys' => array( |
||
2034 | 'type' => 'dir_recursive', |
||
2035 | 'writable_on' => 'standard', |
||
2036 | ), |
||
2037 | 'Sources' => array( |
||
2038 | 'type' => 'dir_recursive', |
||
2039 | 'list_contents' => true, |
||
2040 | 'writable_on' => 'standard', |
||
2041 | 'contents' => array( |
||
2042 | 'tasks' => array( |
||
2043 | 'type' => 'dir', |
||
2044 | 'list_contents' => true, |
||
2045 | ), |
||
2046 | ), |
||
2047 | ), |
||
2048 | 'Themes' => array( |
||
2049 | 'type' => 'dir_recursive', |
||
2050 | 'writable_on' => 'standard', |
||
2051 | 'contents' => array( |
||
2052 | 'default' => array( |
||
2053 | 'type' => 'dir_recursive', |
||
2054 | 'list_contents' => true, |
||
2055 | 'contents' => array( |
||
2056 | 'languages' => array( |
||
2057 | 'type' => 'dir', |
||
2058 | 'list_contents' => true, |
||
2059 | ), |
||
2060 | ), |
||
2061 | ), |
||
2062 | ), |
||
2063 | ), |
||
2064 | 'Packages' => array( |
||
2065 | 'type' => 'dir', |
||
2066 | 'writable_on' => 'standard', |
||
2067 | 'contents' => array( |
||
2068 | 'temp' => array( |
||
2069 | 'type' => 'dir', |
||
2070 | ), |
||
2071 | 'backup' => array( |
||
2072 | 'type' => 'dir', |
||
2073 | ), |
||
2074 | ), |
||
2075 | ), |
||
2076 | ), |
||
2077 | ), |
||
2078 | ); |
||
2079 | |||
2080 | // Directories that can move. |
||
2081 | if (substr($sourcedir, 0, strlen($boarddir)) != $boarddir) |
||
2082 | { |
||
2083 | unset($context['file_tree'][strtr($boarddir, array('\\' => '/'))]['contents']['Sources']); |
||
2084 | $context['file_tree'][strtr($sourcedir, array('\\' => '/'))] = array( |
||
2085 | 'type' => 'dir', |
||
2086 | 'list_contents' => true, |
||
2087 | 'writable_on' => 'standard', |
||
2088 | ); |
||
2089 | } |
||
2090 | |||
2091 | // Moved the cache? |
||
2092 | if (substr($cachedir, 0, strlen($boarddir)) != $boarddir) |
||
2093 | { |
||
2094 | unset($context['file_tree'][strtr($boarddir, array('\\' => '/'))]['contents']['cache']); |
||
2095 | $context['file_tree'][strtr($cachedir, array('\\' => '/'))] = array( |
||
2096 | 'type' => 'dir', |
||
2097 | 'list_contents' => false, |
||
2098 | 'writable_on' => 'restrictive', |
||
2099 | ); |
||
2100 | } |
||
2101 | |||
2102 | // Are we using multiple attachment directories? |
||
2103 | if (!empty($modSettings['currentAttachmentUploadDir'])) |
||
2104 | { |
||
2105 | unset($context['file_tree'][strtr($boarddir, array('\\' => '/'))]['contents']['attachments']); |
||
2106 | |||
2107 | if (!is_array($modSettings['attachmentUploadDir'])) |
||
2108 | $modSettings['attachmentUploadDir'] = $smcFunc['json_decode']($modSettings['attachmentUploadDir'], true); |
||
2109 | |||
2110 | // @todo Should we suggest non-current directories be read only? |
||
2111 | foreach ($modSettings['attachmentUploadDir'] as $dir) |
||
2112 | $context['file_tree'][strtr($dir, array('\\' => '/'))] = array( |
||
2113 | 'type' => 'dir', |
||
2114 | 'writable_on' => 'restrictive', |
||
2115 | ); |
||
2116 | } |
||
2117 | elseif (substr($modSettings['attachmentUploadDir'], 0, strlen($boarddir)) != $boarddir) |
||
2118 | { |
||
2119 | unset($context['file_tree'][strtr($boarddir, array('\\' => '/'))]['contents']['attachments']); |
||
2120 | $context['file_tree'][strtr($modSettings['attachmentUploadDir'], array('\\' => '/'))] = array( |
||
2121 | 'type' => 'dir', |
||
2122 | 'writable_on' => 'restrictive', |
||
2123 | ); |
||
2124 | } |
||
2125 | |||
2126 | if (substr($modSettings['smileys_dir'], 0, strlen($boarddir)) != $boarddir) |
||
2127 | { |
||
2128 | unset($context['file_tree'][strtr($boarddir, array('\\' => '/'))]['contents']['Smileys']); |
||
2129 | $context['file_tree'][strtr($modSettings['smileys_dir'], array('\\' => '/'))] = array( |
||
2130 | 'type' => 'dir_recursive', |
||
2131 | 'writable_on' => 'standard', |
||
2132 | ); |
||
2133 | } |
||
2134 | if (substr($modSettings['avatar_directory'], 0, strlen($boarddir)) != $boarddir) |
||
2135 | { |
||
2136 | unset($context['file_tree'][strtr($boarddir, array('\\' => '/'))]['contents']['avatars']); |
||
2137 | $context['file_tree'][strtr($modSettings['avatar_directory'], array('\\' => '/'))] = array( |
||
2138 | 'type' => 'dir', |
||
2139 | 'writable_on' => 'standard', |
||
2140 | ); |
||
2141 | } |
||
2142 | if (isset($modSettings['custom_avatar_dir']) && substr($modSettings['custom_avatar_dir'], 0, strlen($boarddir)) != $boarddir) |
||
2143 | { |
||
2144 | unset($context['file_tree'][strtr($boarddir, array('\\' => '/'))]['contents']['custom_avatar_dir']); |
||
2145 | $context['file_tree'][strtr($modSettings['custom_avatar_dir'], array('\\' => '/'))] = array( |
||
2146 | 'type' => 'dir', |
||
2147 | 'writable_on' => 'restrictive', |
||
2148 | ); |
||
2149 | } |
||
2150 | |||
2151 | // Load up any custom themes. |
||
2152 | $request = $smcFunc['db_query']('', ' |
||
2153 | SELECT value |
||
2154 | FROM {db_prefix}themes |
||
2155 | WHERE id_theme > {int:default_theme_id} |
||
2156 | AND id_member = {int:guest_id} |
||
2157 | AND variable = {string:theme_dir} |
||
2158 | ORDER BY value ASC', |
||
2159 | array( |
||
2160 | 'default_theme_id' => 1, |
||
2161 | 'guest_id' => 0, |
||
2162 | 'theme_dir' => 'theme_dir', |
||
2163 | ) |
||
2164 | ); |
||
2165 | while ($row = $smcFunc['db_fetch_assoc']($request)) |
||
2166 | { |
||
2167 | if (substr(strtolower(strtr($row['value'], array('\\' => '/'))), 0, strlen($boarddir) + 7) == strtolower(strtr($boarddir, array('\\' => '/')) . '/Themes')) |
||
2168 | $context['file_tree'][strtr($boarddir, array('\\' => '/'))]['contents']['Themes']['contents'][substr($row['value'], strlen($boarddir) + 8)] = array( |
||
2169 | 'type' => 'dir_recursive', |
||
2170 | 'list_contents' => true, |
||
2171 | 'contents' => array( |
||
2172 | 'languages' => array( |
||
2173 | 'type' => 'dir', |
||
2174 | 'list_contents' => true, |
||
2175 | ), |
||
2176 | ), |
||
2177 | ); |
||
2178 | else |
||
2179 | { |
||
2180 | $context['file_tree'][strtr($row['value'], array('\\' => '/'))] = array( |
||
2181 | 'type' => 'dir_recursive', |
||
2182 | 'list_contents' => true, |
||
2183 | 'contents' => array( |
||
2184 | 'languages' => array( |
||
2185 | 'type' => 'dir', |
||
2186 | 'list_contents' => true, |
||
2187 | ), |
||
2188 | ), |
||
2189 | ); |
||
2190 | } |
||
2191 | } |
||
2192 | $smcFunc['db_free_result']($request); |
||
2193 | |||
2194 | // If we're submitting then let's move on to another function to keep things cleaner.. |
||
2195 | if (isset($_POST['action_changes'])) |
||
2196 | return PackagePermissionsAction(); |
||
2197 | |||
2198 | $context['look_for'] = array(); |
||
2199 | // Are we looking for a particular tree - normally an expansion? |
||
2200 | if (!empty($_REQUEST['find'])) |
||
2201 | $context['look_for'][] = base64_decode($_REQUEST['find']); |
||
2202 | // Only that tree? |
||
2203 | $context['only_find'] = isset($_GET['xml']) && !empty($_REQUEST['onlyfind']) ? $_REQUEST['onlyfind'] : ''; |
||
2204 | if ($context['only_find']) |
||
2205 | $context['look_for'][] = $context['only_find']; |
||
2206 | |||
2207 | // Have we got a load of back-catalogue trees to expand from a submit etc? |
||
2208 | if (!empty($_GET['back_look'])) |
||
2209 | { |
||
2210 | $potententialTrees = $smcFunc['json_decode'](base64_decode($_GET['back_look']), true); |
||
2211 | foreach ($potententialTrees as $tree) |
||
2212 | $context['look_for'][] = $tree; |
||
2213 | } |
||
2214 | // ... maybe posted? |
||
2215 | if (!empty($_POST['back_look'])) |
||
2216 | $context['only_find'] = array_merge($context['only_find'], $_POST['back_look']); |
||
2217 | |||
2218 | $context['back_look_data'] = base64_encode($smcFunc['json_encode'](array_slice($context['look_for'], 0, 15))); |
||
2219 | |||
2220 | // Are we finding more files than first thought? |
||
2221 | $context['file_offset'] = !empty($_REQUEST['fileoffset']) ? (int) $_REQUEST['fileoffset'] : 0; |
||
2222 | // Don't list more than this many files in a directory. |
||
2223 | $context['file_limit'] = 150; |
||
2224 | |||
2225 | // How many levels shall we show? |
||
2226 | $context['default_level'] = empty($context['only_find']) ? 2 : 25; |
||
2227 | |||
2228 | // This will be used if we end up catching XML data. |
||
2229 | $context['xml_data'] = array( |
||
2230 | 'roots' => array( |
||
2231 | 'identifier' => 'root', |
||
2232 | 'children' => array( |
||
2233 | array( |
||
2234 | 'value' => preg_replace('~[^A-Za-z0-9_\-=:]~', ':-:', $context['only_find']), |
||
2235 | ), |
||
2236 | ), |
||
2237 | ), |
||
2238 | 'folders' => array( |
||
2239 | 'identifier' => 'folder', |
||
2240 | 'children' => array(), |
||
2241 | ), |
||
2242 | ); |
||
2243 | |||
2244 | foreach ($context['file_tree'] as $path => $data) |
||
2245 | { |
||
2246 | // Run this directory. |
||
2247 | if (file_exists($path) && (empty($context['only_find']) || substr($context['only_find'], 0, strlen($path)) == $path)) |
||
2248 | { |
||
2249 | // Get the first level down only. |
||
2250 | fetchPerms__recursive($path, $context['file_tree'][$path], 1); |
||
2251 | $context['file_tree'][$path]['perms'] = array( |
||
2252 | 'chmod' => @is_writable($path), |
||
2253 | 'perms' => @fileperms($path), |
||
2254 | ); |
||
2255 | } |
||
2256 | else |
||
2257 | unset($context['file_tree'][$path]); |
||
2258 | } |
||
2259 | |||
2260 | // Is this actually xml? |
||
2261 | if (isset($_GET['xml'])) |
||
2262 | { |
||
2263 | loadTemplate('Xml'); |
||
2264 | $context['sub_template'] = 'generic_xml'; |
||
2265 | $context['template_layers'] = array(); |
||
2266 | } |
||
2267 | } |
||
2268 | |||
2269 | /** |
||
2270 | * Checkes the permissions of all the areas that will be affected by the package |
||
2271 | * |
||
2272 | * @param string $path The path to the directiory to check permissions for |
||
2273 | * @param array $data An array of data about the directory |
||
2274 | * @param int $level How far deep to go |
||
2275 | */ |
||
2276 | function fetchPerms__recursive($path, &$data, $level) |
||
2277 | { |
||
2278 | global $context; |
||
2279 | |||
2280 | $isLikelyPath = false; |
||
2281 | foreach ($context['look_for'] as $possiblePath) |
||
2282 | if (substr($possiblePath, 0, strlen($path)) == $path) |
||
2283 | $isLikelyPath = true; |
||
2284 | |||
2285 | // Is this where we stop? |
||
2286 | if (isset($_GET['xml']) && !empty($context['look_for']) && !$isLikelyPath) |
||
2287 | return; |
||
2288 | elseif ($level > $context['default_level'] && !$isLikelyPath) |
||
2289 | return; |
||
2290 | |||
2291 | // Are we actually interested in saving this data? |
||
2292 | $save_data = empty($context['only_find']) || $context['only_find'] == $path; |
||
2293 | |||
2294 | // @todo Shouldn't happen - but better error message? |
||
2295 | if (!is_dir($path)) |
||
2296 | fatal_lang_error('no_access', false); |
||
2297 | |||
2298 | // This is where we put stuff we've found for sorting. |
||
2299 | $foundData = array( |
||
2300 | 'files' => array(), |
||
2301 | 'folders' => array(), |
||
2302 | ); |
||
2303 | |||
2304 | $dh = opendir($path); |
||
2305 | while ($entry = readdir($dh)) |
||
2306 | { |
||
2307 | // Some kind of file? |
||
2308 | if (is_file($path . '/' . $entry)) |
||
2309 | { |
||
2310 | // Are we listing PHP files in this directory? |
||
2311 | if ($save_data && !empty($data['list_contents']) && substr($entry, -4) == '.php') |
||
2312 | $foundData['files'][$entry] = true; |
||
2313 | // A file we were looking for. |
||
2314 | elseif ($save_data && isset($data['contents'][$entry])) |
||
2315 | $foundData['files'][$entry] = true; |
||
2316 | } |
||
2317 | // It's a directory - we're interested one way or another, probably... |
||
2318 | elseif ($entry != '.' && $entry != '..') |
||
2319 | { |
||
2320 | // Going further? |
||
2321 | 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')))) |
||
2322 | { |
||
2323 | if (!isset($data['contents'][$entry])) |
||
2324 | $foundData['folders'][$entry] = 'dir_recursive'; |
||
2325 | else |
||
2326 | $foundData['folders'][$entry] = true; |
||
2327 | |||
2328 | // If this wasn't expected inherit the recusiveness... |
||
2329 | if (!isset($data['contents'][$entry])) |
||
2330 | // We need to do this as we will be going all recursive. |
||
2331 | $data['contents'][$entry] = array( |
||
2332 | 'type' => 'dir_recursive', |
||
2333 | ); |
||
2334 | |||
2335 | // Actually do the recursive stuff... |
||
2336 | fetchPerms__recursive($path . '/' . $entry, $data['contents'][$entry], $level + 1); |
||
2337 | } |
||
2338 | // Maybe it is a folder we are not descending into. |
||
2339 | elseif (isset($data['contents'][$entry])) |
||
2340 | $foundData['folders'][$entry] = true; |
||
2341 | // Otherwise we stop here. |
||
2342 | } |
||
2343 | } |
||
2344 | closedir($dh); |
||
2345 | |||
2346 | // Nothing to see here? |
||
2347 | if (!$save_data) |
||
2348 | return; |
||
2349 | |||
2350 | // Now actually add the data, starting with the folders. |
||
2351 | ksort($foundData['folders']); |
||
2352 | foreach ($foundData['folders'] as $folder => $type) |
||
2353 | { |
||
2354 | $additional_data = array( |
||
2355 | 'perms' => array( |
||
2356 | 'chmod' => @is_writable($path . '/' . $folder), |
||
2357 | 'perms' => @fileperms($path . '/' . $folder), |
||
2358 | ), |
||
2359 | ); |
||
2360 | if ($type !== true) |
||
2361 | $additional_data['type'] = $type; |
||
2362 | |||
2363 | // If there's an offset ignore any folders in XML mode. |
||
2364 | if (isset($_GET['xml']) && $context['file_offset'] == 0) |
||
2365 | { |
||
2366 | $context['xml_data']['folders']['children'][] = array( |
||
2367 | 'attributes' => array( |
||
2368 | 'writable' => $additional_data['perms']['chmod'] ? 1 : 0, |
||
2369 | 'permissions' => substr(sprintf('%o', $additional_data['perms']['perms']), -4), |
||
2370 | 'folder' => 1, |
||
2371 | 'path' => $context['only_find'], |
||
2372 | 'level' => $level, |
||
2373 | 'more' => 0, |
||
2374 | 'offset' => $context['file_offset'], |
||
2375 | 'my_ident' => preg_replace('~[^A-Za-z0-9_\-=:]~', ':-:', $context['only_find'] . '/' . $folder), |
||
2376 | 'ident' => preg_replace('~[^A-Za-z0-9_\-=:]~', ':-:', $context['only_find']), |
||
2377 | ), |
||
2378 | 'value' => $folder, |
||
2379 | ); |
||
2380 | } |
||
2381 | elseif (!isset($_GET['xml'])) |
||
2382 | { |
||
2383 | if (isset($data['contents'][$folder])) |
||
2384 | $data['contents'][$folder] = array_merge($data['contents'][$folder], $additional_data); |
||
2385 | else |
||
2386 | $data['contents'][$folder] = $additional_data; |
||
2387 | } |
||
2388 | } |
||
2389 | |||
2390 | // Now we want to do a similar thing with files. |
||
2391 | ksort($foundData['files']); |
||
2392 | $counter = -1; |
||
2393 | foreach ($foundData['files'] as $file => $dummy) |
||
2394 | { |
||
2395 | $counter++; |
||
2396 | |||
2397 | // Have we reached our offset? |
||
2398 | if ($context['file_offset'] > $counter) |
||
2399 | continue; |
||
2400 | // Gone too far? |
||
2401 | if ($counter > ($context['file_offset'] + $context['file_limit'])) |
||
2402 | continue; |
||
2403 | |||
2404 | $additional_data = array( |
||
2405 | 'perms' => array( |
||
2406 | 'chmod' => @is_writable($path . '/' . $file), |
||
2407 | 'perms' => @fileperms($path . '/' . $file), |
||
2408 | ), |
||
2409 | ); |
||
2410 | |||
2411 | // XML? |
||
2412 | if (isset($_GET['xml'])) |
||
2413 | { |
||
2414 | $context['xml_data']['folders']['children'][] = array( |
||
2415 | 'attributes' => array( |
||
2416 | 'writable' => $additional_data['perms']['chmod'] ? 1 : 0, |
||
2417 | 'permissions' => substr(sprintf('%o', $additional_data['perms']['perms']), -4), |
||
2418 | 'folder' => 0, |
||
2419 | 'path' => $context['only_find'], |
||
2420 | 'level' => $level, |
||
2421 | 'more' => $counter == ($context['file_offset'] + $context['file_limit']) ? 1 : 0, |
||
2422 | 'offset' => $context['file_offset'], |
||
2423 | 'my_ident' => preg_replace('~[^A-Za-z0-9_\-=:]~', ':-:', $context['only_find'] . '/' . $file), |
||
2424 | 'ident' => preg_replace('~[^A-Za-z0-9_\-=:]~', ':-:', $context['only_find']), |
||
2425 | ), |
||
2426 | 'value' => $file, |
||
2427 | ); |
||
2428 | } |
||
2429 | elseif ($counter != ($context['file_offset'] + $context['file_limit'])) |
||
2430 | { |
||
2431 | if (isset($data['contents'][$file])) |
||
2432 | $data['contents'][$file] = array_merge($data['contents'][$file], $additional_data); |
||
2433 | else |
||
2434 | $data['contents'][$file] = $additional_data; |
||
2435 | } |
||
2436 | } |
||
2437 | } |
||
2438 | |||
2439 | /** |
||
2440 | * Actually action the permission changes they want. |
||
2441 | */ |
||
2442 | function PackagePermissionsAction() |
||
2443 | { |
||
2444 | global $smcFunc, $context, $txt, $package_ftp; |
||
2445 | |||
2446 | umask(0); |
||
2447 | |||
2448 | $timeout_limit = 5; |
||
2449 | |||
2450 | $context['method'] = $_POST['method'] == 'individual' ? 'individual' : 'predefined'; |
||
2451 | $context['sub_template'] = 'action_permissions'; |
||
2452 | $context['page_title'] = $txt['package_file_perms_applying']; |
||
2453 | $context['back_look_data'] = isset($_POST['back_look']) ? $_POST['back_look'] : array(); |
||
2454 | |||
2455 | // Skipping use of FTP? |
||
2456 | if (empty($package_ftp)) |
||
2457 | $context['skip_ftp'] = true; |
||
2458 | |||
2459 | // 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. |
||
2460 | if ($context['method'] == 'individual') |
||
2461 | { |
||
2462 | // Only these path roots are legal. |
||
2463 | $legal_roots = array_keys($context['file_tree']); |
||
2464 | $context['custom_value'] = (int) $_POST['custom_value']; |
||
2465 | |||
2466 | // Continuing? |
||
2467 | if (isset($_POST['toProcess'])) |
||
2468 | $_POST['permStatus'] = $smcFunc['json_decode'](base64_decode($_POST['toProcess']), true); |
||
2469 | |||
2470 | if (isset($_POST['permStatus'])) |
||
2471 | { |
||
2472 | $context['to_process'] = array(); |
||
2473 | $validate_custom = false; |
||
2474 | foreach ($_POST['permStatus'] as $path => $status) |
||
2475 | { |
||
2476 | // Nothing to see here? |
||
2477 | if ($status == 'no_change') |
||
2478 | continue; |
||
2479 | $legal = false; |
||
2480 | foreach ($legal_roots as $root) |
||
2481 | if (substr($path, 0, strlen($root)) == $root) |
||
2482 | $legal = true; |
||
2483 | |||
2484 | if (!$legal) |
||
2485 | continue; |
||
2486 | |||
2487 | // Check it exists. |
||
2488 | if (!file_exists($path)) |
||
2489 | continue; |
||
2490 | |||
2491 | if ($status == 'custom') |
||
2492 | $validate_custom = true; |
||
2493 | |||
2494 | // Now add it. |
||
2495 | $context['to_process'][$path] = $status; |
||
2496 | } |
||
2497 | $context['total_items'] = isset($_POST['totalItems']) ? (int) $_POST['totalItems'] : count($context['to_process']); |
||
2498 | |||
2499 | // Make sure the chmod status is valid? |
||
2500 | if ($validate_custom) |
||
2501 | { |
||
2502 | if (preg_match('~^[4567][4567][4567]$~', $context['custom_value']) == false) |
||
2503 | fatal_error($txt['chmod_value_invalid']); |
||
2504 | } |
||
2505 | |||
2506 | // Nothing to do? |
||
2507 | if (empty($context['to_process'])) |
||
2508 | 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']); |
||
2509 | } |
||
2510 | // Should never get here, |
||
2511 | else |
||
2512 | fatal_lang_error('no_access', false); |
||
2513 | |||
2514 | // Setup the custom value. |
||
2515 | $custom_value = octdec('0' . $context['custom_value']); |
||
2516 | |||
2517 | // Start processing items. |
||
2518 | foreach ($context['to_process'] as $path => $status) |
||
2519 | { |
||
2520 | if (in_array($status, array('execute', 'writable', 'read'))) |
||
2521 | package_chmod($path, $status); |
||
2522 | elseif ($status == 'custom' && !empty($custom_value)) |
||
2523 | { |
||
2524 | // Use FTP if we have it. |
||
2525 | if (!empty($package_ftp) && !empty($_SESSION['pack_ftp'])) |
||
2526 | { |
||
2527 | $ftp_file = strtr($path, array($_SESSION['pack_ftp']['root'] => '')); |
||
2528 | $package_ftp->chmod($ftp_file, $custom_value); |
||
2529 | } |
||
2530 | else |
||
2531 | smf_chmod($path, $custom_value); |
||
2532 | } |
||
2533 | |||
2534 | // This fish is fried... |
||
2535 | unset($context['to_process'][$path]); |
||
2536 | |||
2537 | // See if we're out of time? |
||
2538 | if ((time() - TIME_START) > $timeout_limit) |
||
2539 | { |
||
2540 | // Prepare template usage for to_process. |
||
2541 | $context['to_process_encode'] = base64_encode($smcFunc['json_encode']($context['to_process'])); |
||
2542 | |||
2543 | return false; |
||
2544 | } |
||
2545 | } |
||
2546 | |||
2547 | // Prepare template usage for to_process. |
||
2548 | $context['to_process_encode'] = base64_encode($smcFunc['json_encode']($context['to_process'])); |
||
2549 | } |
||
2550 | // If predefined this is a little different. |
||
2551 | else |
||
2552 | { |
||
2553 | $context['predefined_type'] = isset($_POST['predefined']) ? $_POST['predefined'] : 'restricted'; |
||
2554 | |||
2555 | $context['total_items'] = isset($_POST['totalItems']) ? (int) $_POST['totalItems'] : 0; |
||
2556 | $context['directory_list'] = isset($_POST['dirList']) ? $smcFunc['json_decode'](base64_decode($_POST['dirList']), true) : array(); |
||
2557 | |||
2558 | $context['file_offset'] = isset($_POST['fileOffset']) ? (int) $_POST['fileOffset'] : 0; |
||
2559 | |||
2560 | // Haven't counted the items yet? |
||
2561 | if (empty($context['total_items'])) |
||
2562 | { |
||
2563 | /** |
||
2564 | * Counts all the directories under a given path |
||
2565 | * |
||
2566 | * @param string $dir |
||
2567 | * @return integer |
||
2568 | */ |
||
2569 | function count_directories__recursive($dir) |
||
2570 | { |
||
2571 | global $context; |
||
2572 | |||
2573 | $count = 0; |
||
2574 | $dh = @opendir($dir); |
||
2575 | while ($entry = readdir($dh)) |
||
2576 | { |
||
2577 | if ($entry != '.' && $entry != '..' && is_dir($dir . '/' . $entry)) |
||
2578 | { |
||
2579 | $context['directory_list'][$dir . '/' . $entry] = 1; |
||
2580 | $count++; |
||
2581 | $count += count_directories__recursive($dir . '/' . $entry); |
||
2582 | } |
||
2583 | } |
||
2584 | closedir($dh); |
||
2585 | |||
2586 | return $count; |
||
2587 | } |
||
2588 | |||
2589 | foreach ($context['file_tree'] as $path => $data) |
||
2590 | { |
||
2591 | if (is_dir($path)) |
||
2592 | { |
||
2593 | $context['directory_list'][$path] = 1; |
||
2594 | $context['total_items'] += count_directories__recursive($path); |
||
2595 | $context['total_items']++; |
||
2596 | } |
||
2597 | } |
||
2598 | } |
||
2599 | |||
2600 | // Have we built up our list of special files? |
||
2601 | if (!isset($_POST['specialFiles']) && $context['predefined_type'] != 'free') |
||
2602 | { |
||
2603 | $context['special_files'] = array(); |
||
2604 | |||
2605 | /** |
||
2606 | * Builds a list of special files recursively for a given path |
||
2607 | * |
||
2608 | * @param string $path |
||
2609 | * @param array $data |
||
2610 | */ |
||
2611 | function build_special_files__recursive($path, &$data) |
||
2612 | { |
||
2613 | global $context; |
||
2614 | |||
2615 | if (!empty($data['writable_on'])) |
||
2616 | if ($context['predefined_type'] == 'standard' || $data['writable_on'] == 'restrictive') |
||
2617 | $context['special_files'][$path] = 1; |
||
2618 | |||
2619 | if (!empty($data['contents'])) |
||
2620 | foreach ($data['contents'] as $name => $contents) |
||
2621 | build_special_files__recursive($path . '/' . $name, $contents); |
||
2622 | } |
||
2623 | |||
2624 | foreach ($context['file_tree'] as $path => $data) |
||
2625 | build_special_files__recursive($path, $data); |
||
2626 | } |
||
2627 | // Free doesn't need special files. |
||
2628 | elseif ($context['predefined_type'] == 'free') |
||
2629 | $context['special_files'] = array(); |
||
2630 | else |
||
2631 | $context['special_files'] = $smcFunc['json_decode'](base64_decode($_POST['specialFiles']), true); |
||
2632 | |||
2633 | // Now we definitely know where we are, we need to go through again doing the chmod! |
||
2634 | foreach ($context['directory_list'] as $path => $dummy) |
||
2635 | { |
||
2636 | // Do the contents of the directory first. |
||
2637 | $dh = @opendir($path); |
||
2638 | $file_count = 0; |
||
2639 | $dont_chmod = false; |
||
2640 | while ($entry = readdir($dh)) |
||
2641 | { |
||
2642 | $file_count++; |
||
2643 | // Actually process this file? |
||
2644 | if (!$dont_chmod && !is_dir($path . '/' . $entry) && (empty($context['file_offset']) || $context['file_offset'] < $file_count)) |
||
2645 | { |
||
2646 | $status = $context['predefined_type'] == 'free' || isset($context['special_files'][$path . '/' . $entry]) ? 'writable' : 'execute'; |
||
2647 | package_chmod($path . '/' . $entry, $status); |
||
2648 | } |
||
2649 | |||
2650 | // See if we're out of time? |
||
2651 | if (!$dont_chmod && (time() - TIME_START) > $timeout_limit) |
||
2652 | { |
||
2653 | $dont_chmod = true; |
||
2654 | // Don't do this again. |
||
2655 | $context['file_offset'] = $file_count; |
||
2656 | } |
||
2657 | } |
||
2658 | closedir($dh); |
||
2659 | |||
2660 | // If this is set it means we timed out half way through. |
||
2661 | if ($dont_chmod) |
||
2662 | { |
||
2663 | $context['total_files'] = $file_count; |
||
2664 | $context['directory_list_encode'] = base64_encode($smcFunc['json_encode']($context['directory_list'])); |
||
2665 | $context['special_files_encode'] = base64_encode($smcFunc['json_encode']($context['special_files'])); |
||
2666 | return false; |
||
2667 | } |
||
2668 | |||
2669 | // Do the actual directory. |
||
2670 | $status = $context['predefined_type'] == 'free' || isset($context['special_files'][$path]) ? 'writable' : 'execute'; |
||
2671 | package_chmod($path, $status); |
||
2672 | |||
2673 | // We've finished the directory so no file offset, and no record. |
||
2674 | $context['file_offset'] = 0; |
||
2675 | unset($context['directory_list'][$path]); |
||
2676 | |||
2677 | // See if we're out of time? |
||
2678 | if ((time() - TIME_START) > $timeout_limit) |
||
2679 | { |
||
2680 | // Prepare this for usage on templates. |
||
2681 | $context['directory_list_encode'] = base64_encode($smcFunc['json_encode']($context['directory_list'])); |
||
2682 | $context['special_files_encode'] = base64_encode($smcFunc['json_encode']($context['special_files'])); |
||
2683 | |||
2684 | return false; |
||
2685 | } |
||
2686 | } |
||
2687 | |||
2688 | // Prepare this for usage on templates. |
||
2689 | $context['directory_list_encode'] = base64_encode($smcFunc['json_encode']($context['directory_list'])); |
||
2690 | $context['special_files_encode'] = base64_encode($smcFunc['json_encode']($context['special_files'])); |
||
2691 | } |
||
2692 | |||
2693 | // If we're here we are done! |
||
2694 | 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']); |
||
2695 | } |
||
2696 | |||
2697 | /** |
||
2698 | * Test an FTP connection. |
||
2699 | */ |
||
2700 | function PackageFTPTest() |
||
2701 | { |
||
2702 | global $context, $txt, $package_ftp; |
||
2703 | |||
2704 | checkSession('get'); |
||
2705 | |||
2706 | // Try to make the FTP connection. |
||
2707 | create_chmod_control(array(), array('force_find_error' => true)); |
||
2708 | |||
2709 | // Deal with the template stuff. |
||
2710 | loadTemplate('Xml'); |
||
2711 | $context['sub_template'] = 'generic_xml'; |
||
2712 | $context['template_layers'] = array(); |
||
2713 | |||
2714 | // Define the return data, this is simple. |
||
2715 | $context['xml_data'] = array( |
||
2716 | 'results' => array( |
||
2717 | 'identifier' => 'result', |
||
2718 | 'children' => array( |
||
2719 | array( |
||
2720 | 'attributes' => array( |
||
2721 | 'success' => !empty($package_ftp) ? 1 : 0, |
||
2722 | ), |
||
2723 | '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']), |
||
2724 | ), |
||
2725 | ), |
||
2726 | ), |
||
2727 | ); |
||
2728 | } |
||
2729 | |||
2730 | ?> |