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); |
||||
0 ignored issues
–
show
Bug
introduced
by
![]() |
|||||
376 | else |
||||
377 | $mod_actions = parseModification(@file_get_contents($packagesdir . '/temp/' . $context['base_path'] . $action['filename']), true, $action['reverse'], $theme_paths); |
||||
0 ignored issues
–
show
It seems like
@file_get_contents($pack... . $action['filename']) can also be of type false ; however, parameter $file of parseModification() does only seem to accept string , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
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, |
||||
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); |
||||
0 ignored issues
–
show
Are you sure
read_tgz_file($packagesd..._REQUEST['file'], true) of type array|false can be used in echo ?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
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); |
||||
0 ignored issues
–
show
It seems like
@file_get_contents($pack... $_REQUEST['filename']) can also be of type false ; however, parameter $file of parseBoardMod() does only seem to accept string , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
1923 | else |
||||
1924 | $mod_actions = parseModification(@file_get_contents($packagesdir . '/temp/' . $context['base_path'] . $_REQUEST['filename']), true, $reverse, $theme_paths); |
||||
0 ignored issues
–
show
It seems like
@file_get_contents($pack... $_REQUEST['filename']) can also be of type false ; however, parameter $file of parseModification() does only seem to accept string , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
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 | ?> |