comparison forum/Sources/Packages.php @ 76:e3e11437ecea website

Add forum code
author Chris Cannam
date Sun, 07 Jul 2013 11:25:48 +0200
parents
children
comparison
equal deleted inserted replaced
75:72f59aa7e503 76:e3e11437ecea
1 <?php
2
3 /**
4 * Simple Machines Forum (SMF)
5 *
6 * @package SMF
7 * @author Simple Machines http://www.simplemachines.org
8 * @copyright 2011 Simple Machines
9 * @license http://www.simplemachines.org/about/smf/license.php BSD
10 *
11 * @version 2.0.2
12 */
13
14 if (!defined('SMF'))
15 die('Hacking attempt...');
16
17 /* // !!!
18
19 void Packages()
20 // !!!
21
22 void PackageInstallTest()
23 // !!!
24
25 void PackageInstall()
26 // !!!
27
28 void PackageList()
29 // !!!
30
31 void ExamineFile()
32 // !!!
33
34 void InstalledList()
35 // !!!
36
37 void FlushInstall()
38 // !!!
39
40 void PackageRemove()
41 // !!!
42
43 void PackageBrowse()
44 // !!!
45
46 void PackageOptions()
47 // !!!
48
49 void ViewOperations()
50 // !!!
51 */
52
53 // This is the notoriously defunct package manager..... :/.
54 function Packages()
55 {
56 global $txt, $scripturl, $sourcedir, $context;
57
58 //!!! Remove this!
59 if (isset($_GET['get']) || isset($_GET['pgdownload']))
60 {
61 require_once($sourcedir . '/PackageGet.php');
62 return PackageGet();
63 }
64
65 isAllowedTo('admin_forum');
66
67 // Load all the basic stuff.
68 require_once($sourcedir . '/Subs-Package.php');
69 loadLanguage('Packages');
70 loadTemplate('Packages', 'admin');
71
72 $context['page_title'] = $txt['package'];
73
74 // Delegation makes the world... that is, the package manager go 'round.
75 $subActions = array(
76 'browse' => 'PackageBrowse',
77 'remove' => 'PackageRemove',
78 'list' => 'PackageList',
79 'ftptest' => 'PackageFTPTest',
80 'install' => 'PackageInstallTest',
81 'install2' => 'PackageInstall',
82 'uninstall' => 'PackageInstallTest',
83 'uninstall2' => 'PackageInstall',
84 'installed' => 'InstalledList',
85 'options' => 'PackageOptions',
86 'perms' => 'PackagePermissions',
87 'flush' => 'FlushInstall',
88 'examine' => 'ExamineFile',
89 'showoperations' => 'ViewOperations',
90 );
91
92 // Work out exactly who it is we are calling.
93 if (isset($_REQUEST['sa']) && isset($subActions[$_REQUEST['sa']]))
94 $context['sub_action'] = $_REQUEST['sa'];
95 else
96 $context['sub_action'] = 'browse';
97
98 // Set up some tabs...
99 $context[$context['admin_menu_name']]['tab_data'] = array(
100 'title' => $txt['package_manager'],
101 // !!! 'help' => 'registrations',
102 'description' => $txt['package_manager_desc'],
103 'tabs' => array(
104 'browse' => array(
105 ),
106 'packageget' => array(
107 'description' => $txt['download_packages_desc'],
108 ),
109 'installed' => array(
110 'description' => $txt['installed_packages_desc'],
111 ),
112 'perms' => array(
113 'description' => $txt['package_file_perms_desc'],
114 ),
115 'options' => array(
116 'description' => $txt['package_install_options_ftp_why'],
117 ),
118 ),
119 );
120
121 // Call the function we're handing control to.
122 $subActions[$context['sub_action']]();
123 }
124
125 // Test install a package.
126 function PackageInstallTest()
127 {
128 global $boarddir, $txt, $context, $scripturl, $sourcedir, $modSettings, $smcFunc, $settings;
129
130 // You have to specify a file!!
131 if (!isset($_REQUEST['package']) || $_REQUEST['package'] == '')
132 redirectexit('action=admin;area=packages');
133 $context['filename'] = preg_replace('~[\.]+~', '.', $_REQUEST['package']);
134
135 // Do we have an existing id, for uninstalls and the like.
136 $context['install_id'] = isset($_REQUEST['pid']) ? (int) $_REQUEST['pid'] : 0;
137
138 require_once($sourcedir . '/Subs-Package.php');
139
140 // Load up the package FTP information?
141 create_chmod_control();
142
143 // Make sure temp directory exists and is empty.
144 if (file_exists($boarddir . '/Packages/temp'))
145 deltree($boarddir . '/Packages/temp', false);
146
147 if (!mktree($boarddir . '/Packages/temp', 0755))
148 {
149 deltree($boarddir . '/Packages/temp', false);
150 if (!mktree($boarddir . '/Packages/temp', 0777))
151 {
152 deltree($boarddir . '/Packages/temp', false);
153 create_chmod_control(array($boarddir . '/Packages/temp/delme.tmp'), array('destination_url' => $scripturl . '?action=admin;area=packages;sa=' . $_REQUEST['sa'] . ';package=' . $_REQUEST['package'], 'crash_on_error' => true));
154
155 deltree($boarddir . '/Packages/temp', false);
156 if (!mktree($boarddir . '/Packages/temp', 0777))
157 fatal_lang_error('package_cant_download', false);
158 }
159 }
160
161 $context['uninstalling'] = $_REQUEST['sa'] == 'uninstall';
162
163 // Change our last link tree item for more information on this Packages area.
164 $context['linktree'][count($context['linktree']) - 1] = array(
165 'url' => $scripturl . '?action=admin;area=packages;sa=browse',
166 'name' => $context['uninstalling'] ? $txt['package_uninstall_actions'] : $txt['install_actions']
167 );
168 $context['page_title'] .= ' - ' . ($context['uninstalling'] ? $txt['package_uninstall_actions'] : $txt['install_actions']);
169
170 $context['sub_template'] = 'view_package';
171
172 if (!file_exists($boarddir . '/Packages/' . $context['filename']))
173 {
174 deltree($boarddir . '/Packages/temp');
175 fatal_lang_error('package_no_file', false);
176 }
177
178 // Extract the files so we can get things like the readme, etc.
179 if (is_file($boarddir . '/Packages/' . $context['filename']))
180 {
181 $context['extracted_files'] = read_tgz_file($boarddir . '/Packages/' . $context['filename'], $boarddir . '/Packages/temp');
182
183 if ($context['extracted_files'] && !file_exists($boarddir . '/Packages/temp/package-info.xml'))
184 foreach ($context['extracted_files'] as $file)
185 if (basename($file['filename']) == 'package-info.xml')
186 {
187 $context['base_path'] = dirname($file['filename']) . '/';
188 break;
189 }
190
191 if (!isset($context['base_path']))
192 $context['base_path'] = '';
193 }
194 elseif (is_dir($boarddir . '/Packages/' . $context['filename']))
195 {
196 copytree($boarddir . '/Packages/' . $context['filename'], $boarddir . '/Packages/temp');
197 $context['extracted_files'] = listtree($boarddir . '/Packages/temp');
198 $context['base_path'] = '';
199 }
200 else
201 fatal_lang_error('no_access', false);
202
203 // Load up any custom themes we may want to install into...
204 $request = $smcFunc['db_query']('', '
205 SELECT id_theme, variable, value
206 FROM {db_prefix}themes
207 WHERE (id_theme = {int:default_theme} OR id_theme IN ({array_int:known_theme_list}))
208 AND variable IN ({string:name}, {string:theme_dir})',
209 array(
210 'known_theme_list' => explode(',', $modSettings['knownThemes']),
211 'default_theme' => 1,
212 'name' => 'name',
213 'theme_dir' => 'theme_dir',
214 )
215 );
216 $theme_paths = array();
217 while ($row = $smcFunc['db_fetch_assoc']($request))
218 $theme_paths[$row['id_theme']][$row['variable']] = $row['value'];
219 $smcFunc['db_free_result']($request);
220
221 // Get the package info...
222 $packageInfo = getPackageInfo($context['filename']);
223
224 if (!is_array($packageInfo))
225 fatal_lang_error($packageInfo);
226
227 $packageInfo['filename'] = $context['filename'];
228 $context['package_name'] = isset($packageInfo['name']) ? $packageInfo['name'] : $context['filename'];
229
230 // Set the type of extraction...
231 $context['extract_type'] = isset($packageInfo['type']) ? $packageInfo['type'] : 'modification';
232
233 // The mod isn't installed.... unless proven otherwise.
234 $context['is_installed'] = false;
235
236 // See if it is installed?
237 $request = $smcFunc['db_query']('', '
238 SELECT version, themes_installed, db_changes
239 FROM {db_prefix}log_packages
240 WHERE package_id = {string:current_package}
241 AND install_state != {int:not_installed}
242 ORDER BY time_installed DESC
243 LIMIT 1',
244 array(
245 'not_installed' => 0,
246 'current_package' => $packageInfo['id'],
247 )
248 );
249
250 while ($row = $smcFunc['db_fetch_assoc']($request))
251 {
252 $old_themes = explode(',', $row['themes_installed']);
253 $old_version = $row['version'];
254 $db_changes = empty($row['db_changes']) ? array() : unserialize($row['db_changes']);
255 }
256 $smcFunc['db_free_result']($request);
257
258 $context['database_changes'] = array();
259 if (!empty($db_changes))
260 {
261 foreach ($db_changes as $change)
262 {
263 if (isset($change[2]) && isset($txt['package_db_' . $change[0]]))
264 $context['database_changes'][] = sprintf($txt['package_db_' . $change[0]], $change[1], $change[2]);
265 elseif (isset($txt['package_db_' . $change[0]]))
266 $context['database_changes'][] = sprintf($txt['package_db_' . $change[0]], $change[1]);
267 else
268 $context['database_changes'][] = $change[0] . '-' . $change[1] . (isset($change[2]) ? '-' . $change[2] : '');
269 }
270 }
271
272 // Uninstalling?
273 if ($context['uninstalling'])
274 {
275 // Wait, it's not installed yet!
276 if (!isset($old_version) && $context['uninstalling'])
277 {
278 deltree($boarddir . '/Packages/temp');
279 fatal_lang_error('package_cant_uninstall', false);
280 }
281
282 $actions = parsePackageInfo($packageInfo['xml'], true, 'uninstall');
283
284 // Gadzooks! There's no uninstaller at all!?
285 if (empty($actions))
286 {
287 deltree($boarddir . '/Packages/temp');
288 fatal_lang_error('package_uninstall_cannot', false);
289 }
290
291 // Can't edit the custom themes it's edited if you're unisntalling, they must be removed.
292 $context['themes_locked'] = true;
293
294 // Only let them uninstall themes it was installed into.
295 foreach ($theme_paths as $id => $data)
296 if ($id != 1 && !in_array($id, $old_themes))
297 unset($theme_paths[$id]);
298 }
299 elseif (isset($old_version) && $old_version != $packageInfo['version'])
300 {
301 // Look for an upgrade...
302 $actions = parsePackageInfo($packageInfo['xml'], true, 'upgrade', $old_version);
303
304 // There was no upgrade....
305 if (empty($actions))
306 $context['is_installed'] = true;
307 else
308 {
309 // Otherwise they can only upgrade themes from the first time around.
310 foreach ($theme_paths as $id => $data)
311 if ($id != 1 && !in_array($id, $old_themes))
312 unset($theme_paths[$id]);
313 }
314 }
315 elseif (isset($old_version) && $old_version == $packageInfo['version'])
316 $context['is_installed'] = true;
317
318 if (!isset($old_version) || $context['is_installed'])
319 $actions = parsePackageInfo($packageInfo['xml'], true, 'install');
320
321 $context['actions'] = array();
322 $context['ftp_needed'] = false;
323 $context['has_failure'] = false;
324 $chmod_files = array();
325
326 if (empty($actions))
327 return;
328
329 // This will hold data about anything that can be installed in other themes.
330 $themeFinds = array(
331 'candidates' => array(),
332 'other_themes' => array(),
333 );
334
335 // Now prepare things for the template.
336 foreach ($actions as $action)
337 {
338 // Not failed until proven otherwise.
339 $failed = false;
340
341 if ($action['type'] == 'chmod')
342 {
343 $chmod_files[] = $action['filename'];
344 continue;
345 }
346 elseif ($action['type'] == 'readme')
347 {
348 if (file_exists($boarddir . '/Packages/temp/' . $context['base_path'] . $action['filename']))
349 $context['package_readme'] = htmlspecialchars(trim(file_get_contents($boarddir . '/Packages/temp/' . $context['base_path'] . $action['filename']), "\n\r"));
350 elseif (file_exists($action['filename']))
351 $context['package_readme'] = htmlspecialchars(trim(file_get_contents($action['filename']), "\n\r"));
352
353 if (!empty($action['parse_bbc']))
354 {
355 require_once($sourcedir . '/Subs-Post.php');
356 preparsecode($context['package_readme']);
357 $context['package_readme'] = parse_bbc($context['package_readme']);
358 }
359 else
360 $context['package_readme'] = nl2br($context['package_readme']);
361
362 continue;
363 }
364 // Don't show redirects.
365 elseif ($action['type'] == 'redirect')
366 continue;
367 elseif ($action['type'] == 'error')
368 $context['has_failure'] = true;
369 elseif ($action['type'] == 'modification')
370 {
371 if (!file_exists($boarddir . '/Packages/temp/' . $context['base_path'] . $action['filename']))
372 {
373 $context['has_failure'] = true;
374
375 $context['actions'][] = array(
376 'type' => $txt['execute_modification'],
377 'action' => $smcFunc['htmlspecialchars'](strtr($action['filename'], array($boarddir => '.'))),
378 'description' => $txt['package_action_error'],
379 'failed' => true,
380 );
381 }
382
383 if ($action['boardmod'])
384 $mod_actions = parseBoardMod(@file_get_contents($boarddir . '/Packages/temp/' . $context['base_path'] . $action['filename']), true, $action['reverse'], $theme_paths);
385 else
386 $mod_actions = parseModification(@file_get_contents($boarddir . '/Packages/temp/' . $context['base_path'] . $action['filename']), true, $action['reverse'], $theme_paths);
387
388 if (count($mod_actions) == 1 && isset($mod_actions[0]) && $mod_actions[0]['type'] == 'error' && $mod_actions[0]['filename'] == '-')
389 $mod_actions[0]['filename'] = $action['filename'];
390
391 foreach ($mod_actions as $key => $mod_action)
392 {
393 // Lets get the last section of the file name.
394 if (isset($mod_action['filename']) && substr($mod_action['filename'], -13) != '.template.php')
395 $actual_filename = strtolower(substr(strrchr($mod_action['filename'], '/'), 1) . '||' . $action['filename']);
396 elseif (isset($mod_action['filename']) && preg_match('~([\w]*)/([\w]*)\.template\.php$~', $mod_action['filename'], $matches))
397 $actual_filename = strtolower($matches[1] . '/' . $matches[2] . '.template.php' . '||' . $action['filename']);
398 else
399 $actual_filename = $key;
400
401 if ($mod_action['type'] == 'opened')
402 $failed = false;
403 elseif ($mod_action['type'] == 'failure')
404 {
405 if (empty($mod_action['is_custom']))
406 $context['has_failure'] = true;
407 $failed = true;
408 }
409 elseif ($mod_action['type'] == 'chmod')
410 {
411 $chmod_files[] = $mod_action['filename'];
412 }
413 elseif ($mod_action['type'] == 'saved')
414 {
415 if (!empty($mod_action['is_custom']))
416 {
417 if (!isset($context['theme_actions'][$mod_action['is_custom']]))
418 $context['theme_actions'][$mod_action['is_custom']] = array(
419 'name' => $theme_paths[$mod_action['is_custom']]['name'],
420 'actions' => array(),
421 'has_failure' => $failed,
422 );
423 else
424 $context['theme_actions'][$mod_action['is_custom']]['has_failure'] |= $failed;
425
426 $context['theme_actions'][$mod_action['is_custom']]['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 elseif (!isset($context['actions'][$actual_filename]))
434 {
435 $context['actions'][$actual_filename] = array(
436 'type' => $txt['execute_modification'],
437 'action' => $smcFunc['htmlspecialchars'](strtr($mod_action['filename'], array($boarddir => '.'))),
438 'description' => $failed ? $txt['package_action_failure'] : $txt['package_action_success'],
439 'failed' => $failed,
440 );
441 }
442 else
443 {
444 $context['actions'][$actual_filename]['failed'] |= $failed;
445 $context['actions'][$actual_filename]['description'] = $context['actions'][$actual_filename]['failed'] ? $txt['package_action_failure'] : $txt['package_action_success'];
446 }
447 }
448 elseif ($mod_action['type'] == 'skipping')
449 {
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_skipping']
454 );
455 }
456 elseif ($mod_action['type'] == 'missing' && empty($mod_action['is_custom']))
457 {
458 $context['has_failure'] = true;
459 $context['actions'][$actual_filename] = array(
460 'type' => $txt['execute_modification'],
461 'action' => $smcFunc['htmlspecialchars'](strtr($mod_action['filename'], array($boarddir => '.'))),
462 'description' => $txt['package_action_missing'],
463 'failed' => true,
464 );
465 }
466 elseif ($mod_action['type'] == 'error')
467 $context['actions'][$actual_filename] = array(
468 'type' => $txt['execute_modification'],
469 'action' => $smcFunc['htmlspecialchars'](strtr($mod_action['filename'], array($boarddir => '.'))),
470 'description' => $txt['package_action_error'],
471 'failed' => true,
472 );
473 }
474
475 // We need to loop again just to get the operations down correctly.
476 foreach ($mod_actions as $operation_key => $mod_action)
477 {
478 // Lets get the last section of the file name.
479 if (isset($mod_action['filename']) && substr($mod_action['filename'], -13) != '.template.php')
480 $actual_filename = strtolower(substr(strrchr($mod_action['filename'], '/'), 1) . '||' . $action['filename']);
481 elseif (isset($mod_action['filename']) && preg_match('~([\w]*)/([\w]*)\.template\.php$~', $mod_action['filename'], $matches))
482 $actual_filename = strtolower($matches[1] . '/' . $matches[2] . '.template.php' . '||' . $action['filename']);
483 else
484 $actual_filename = $key;
485
486 // We just need it for actual parse changes.
487 if (!in_array($mod_action['type'], array('error', 'result', 'opened', 'saved', 'end', 'missing', 'skipping', 'chmod')))
488 {
489 if (empty($mod_action['is_custom']))
490 $context['actions'][$actual_filename]['operations'][] = array(
491 'type' => $txt['execute_modification'],
492 'action' => $smcFunc['htmlspecialchars'](strtr($mod_action['filename'], array($boarddir => '.'))),
493 'description' => $mod_action['failed'] ? $txt['package_action_failure'] : $txt['package_action_success'],
494 'position' => $mod_action['position'],
495 'operation_key' => $operation_key,
496 'filename' => $action['filename'],
497 'is_boardmod' => $action['boardmod'],
498 'failed' => $mod_action['failed'],
499 'ignore_failure' => !empty($mod_action['ignore_failure']),
500 );
501
502 // Themes are under the saved type.
503 if (isset($mod_action['is_custom']) && isset($context['theme_actions'][$mod_action['is_custom']]))
504 $context['theme_actions'][$mod_action['is_custom']]['actions'][$actual_filename]['operations'][] = array(
505 'type' => $txt['execute_modification'],
506 'action' => $smcFunc['htmlspecialchars'](strtr($mod_action['filename'], array($boarddir => '.'))),
507 'description' => $mod_action['failed'] ? $txt['package_action_failure'] : $txt['package_action_success'],
508 'position' => $mod_action['position'],
509 'operation_key' => $operation_key,
510 'filename' => $action['filename'],
511 'is_boardmod' => $action['boardmod'],
512 'failed' => $mod_action['failed'],
513 'ignore_failure' => !empty($mod_action['ignore_failure']),
514 );
515 }
516 }
517
518 // Don't add anything else.
519 $thisAction = array();
520 }
521 elseif ($action['type'] == 'code')
522 $thisAction = array(
523 'type' => $txt['execute_code'],
524 'action' => $smcFunc['htmlspecialchars']($action['filename']),
525 );
526 elseif ($action['type'] == 'database')
527 {
528 $thisAction = array(
529 'type' => $txt['execute_database_changes'],
530 'action' => $smcFunc['htmlspecialchars']($action['filename']),
531 );
532 }
533 elseif (in_array($action['type'], array('create-dir', 'create-file')))
534 $thisAction = array(
535 'type' => $txt['package_create'] . ' ' . ($action['type'] == 'create-dir' ? $txt['package_tree'] : $txt['package_file']),
536 'action' => $smcFunc['htmlspecialchars'](strtr($action['destination'], array($boarddir => '.')))
537 );
538 elseif (in_array($action['type'], array('require-dir', 'require-file')))
539 {
540 // Do this one...
541 $thisAction = array(
542 'type' => $txt['package_extract'] . ' ' . ($action['type'] == 'require-dir' ? $txt['package_tree'] : $txt['package_file']),
543 'action' => $smcFunc['htmlspecialchars'](strtr($action['destination'], array($boarddir => '.')))
544 );
545
546 // Could this be theme related?
547 if (!empty($action['unparsed_destination']) && preg_match('~^\$(languagedir|languages_dir|imagesdir|themedir|themes_dir)~i', $action['unparsed_destination'], $matches))
548 {
549 // Is the action already stated?
550 $theme_action = !empty($action['theme_action']) && in_array($action['theme_action'], array('no', 'yes', 'auto')) ? $action['theme_action'] : 'auto';
551 // If it's not auto do we think we have something we can act upon?
552 if ($theme_action != 'auto' && !in_array($matches[1], array('languagedir', 'languages_dir', 'imagesdir', 'themedir')))
553 $theme_action = '';
554 // ... or if it's auto do we even want to do anything?
555 elseif ($theme_action == 'auto' && $matches[1] != 'imagesdir')
556 $theme_action = '';
557
558 // So, we still want to do something?
559 if ($theme_action != '')
560 $themeFinds['candidates'][] = $action;
561 // Otherwise is this is going into another theme record it.
562 elseif ($matches[1] == 'themes_dir')
563 $themeFinds['other_themes'][] = strtolower(strtr(parse_path($action['unparsed_destination']), array('\\' => '/')) . '/' . basename($action['filename']));
564 }
565 }
566 elseif (in_array($action['type'], array('move-dir', 'move-file')))
567 $thisAction = array(
568 'type' => $txt['package_move'] . ' ' . ($action['type'] == 'move-dir' ? $txt['package_tree'] : $txt['package_file']),
569 'action' => $smcFunc['htmlspecialchars'](strtr($action['source'], array($boarddir => '.'))) . ' => ' . $smcFunc['htmlspecialchars'](strtr($action['destination'], array($boarddir => '.')))
570 );
571 elseif (in_array($action['type'], array('remove-dir', 'remove-file')))
572 {
573 $thisAction = array(
574 'type' => $txt['package_delete'] . ' ' . ($action['type'] == 'remove-dir' ? $txt['package_tree'] : $txt['package_file']),
575 'action' => $smcFunc['htmlspecialchars'](strtr($action['filename'], array($boarddir => '.')))
576 );
577
578 // Could this be theme related?
579 if (!empty($action['unparsed_filename']) && preg_match('~^\$(languagedir|languages_dir|imagesdir|themedir|themes_dir)~i', $action['unparsed_filename'], $matches))
580 {
581
582 // Is the action already stated?
583 $theme_action = !empty($action['theme_action']) && in_array($action['theme_action'], array('no', 'yes', 'auto')) ? $action['theme_action'] : 'auto';
584 $action['unparsed_destination'] = $action['unparsed_filename'];
585 // If it's not auto do we think we have something we can act upon?
586 if ($theme_action != 'auto' && !in_array($matches[1], array('languagedir', 'languages_dir', 'imagesdir', 'themedir')))
587 $theme_action = '';
588 // ... or if it's auto do we even want to do anything?
589 elseif ($theme_action == 'auto' && $matches[1] != 'imagesdir')
590 $theme_action = '';
591
592 // So, we still want to do something?
593 if ($theme_action != '')
594 $themeFinds['candidates'][] = $action;
595 // Otherwise is this is going into another theme record it.
596 elseif ($matches[1] == 'themes_dir')
597 $themeFinds['other_themes'][] = strtolower(strtr(parse_path($action['unparsed_filename']), array('\\' => '/')) . '/' . basename($action['filename']));
598 }
599 }
600
601 if (empty($thisAction))
602 continue;
603
604 // !!! None given?
605 $thisAction['description'] = isset($action['description']) ? $action['description'] : '';
606 $context['actions'][] = $thisAction;
607 }
608
609 // Have we got some things which we might want to do "multi-theme"?
610 if (!empty($themeFinds['candidates']))
611 {
612 foreach ($themeFinds['candidates'] as $action_data)
613 {
614 // Get the part of the file we'll be dealing with.
615 preg_match('~^\$(languagedir|languages_dir|imagesdir|themedir)(\\|/)*(.+)*~i', $action_data['unparsed_destination'], $matches);
616
617 if ($matches[1] == 'imagesdir')
618 $path = '/' . basename($settings['default_images_url']);
619 elseif ($matches[1] == 'languagedir' || $matches[1] == 'languages_dir')
620 $path = '/languages';
621 else
622 $path = '';
623
624 if (!empty($matches[3]))
625 $path .= $matches[3];
626
627 if (!$context['uninstalling'])
628 $path .= '/' . basename($action_data['filename']);
629
630 // Loop through each custom theme to note it's candidacy!
631 foreach ($theme_paths as $id => $theme_data)
632 {
633 if (isset($theme_data['theme_dir']) && $id != 1)
634 {
635 $real_path = $theme_data['theme_dir'] . $path;
636 // Confirm that we don't already have this dealt with by another entry.
637 if (!in_array(strtolower(strtr($real_path, array('\\' => '/'))), $themeFinds['other_themes']))
638 {
639 // Check if we will need to chmod this.
640 if (!mktree(dirname($real_path), false))
641 {
642 $temp = dirname($real_path);
643 while (!file_exists($temp) && strlen($temp) > 1)
644 $temp = dirname($temp);
645 $chmod_files[] = $temp;
646 }
647 if ($action_data['type'] == 'require-dir' && !is_writable($real_path) && (file_exists($real_path) || !is_writable(dirname($real_path))))
648 $chmod_files[] = $real_path;
649
650 if (!isset($context['theme_actions'][$id]))
651 $context['theme_actions'][$id] = array(
652 'name' => $theme_data['name'],
653 'actions' => array(),
654 );
655
656 if ($context['uninstalling'])
657 $context['theme_actions'][$id]['actions'][] = array(
658 'type' => $txt['package_delete'] . ' ' . ($action_data['type'] == 'require-dir' ? $txt['package_tree'] : $txt['package_file']),
659 'action' => strtr($real_path, array('\\' => '/', $boarddir => '.')),
660 'description' => '',
661 'value' => base64_encode(serialize(array('type' => $action_data['type'], 'orig' => $action_data['filename'], 'future' => $real_path, 'id' => $id))),
662 'not_mod' => true,
663 );
664 else
665 $context['theme_actions'][$id]['actions'][] = array(
666 'type' => $txt['package_extract'] . ' ' . ($action_data['type'] == 'require-dir' ? $txt['package_tree'] : $txt['package_file']),
667 'action' => strtr($real_path, array('\\' => '/', $boarddir => '.')),
668 'description' => '',
669 'value' => base64_encode(serialize(array('type' => $action_data['type'], 'orig' => $action_data['destination'], 'future' => $real_path, 'id' => $id))),
670 'not_mod' => true,
671 );
672 }
673 }
674 }
675 }
676 }
677
678 // Trash the cache... which will also check permissions for us!
679 package_flush_cache(true);
680
681 if (file_exists($boarddir . '/Packages/temp'))
682 deltree($boarddir . '/Packages/temp');
683
684 if (!empty($chmod_files))
685 {
686 $ftp_status = create_chmod_control($chmod_files);
687 $context['ftp_needed'] = !empty($ftp_status['files']['notwritable']) && !empty($context['package_ftp']);
688 }
689
690 checkSubmitOnce('register');
691 }
692
693 // Apply another type of (avatar, language, etc.) package.
694 function PackageInstall()
695 {
696 global $boarddir, $txt, $context, $boardurl, $scripturl, $sourcedir, $modSettings;
697 global $user_info, $smcFunc;
698
699 // Make sure we don't install this mod twice.
700 checkSubmitOnce('check');
701 checkSession();
702
703 // If there's no file, what are we installing?
704 if (!isset($_REQUEST['package']) || $_REQUEST['package'] == '')
705 redirectexit('action=admin;area=packages');
706 $context['filename'] = $_REQUEST['package'];
707
708 // If this is an uninstall, we'll have an id.
709 $context['install_id'] = isset($_REQUEST['pid']) ? (int) $_REQUEST['pid'] : 0;
710
711 require_once($sourcedir . '/Subs-Package.php');
712
713 // !!! TODO: Perhaps do it in steps, if necessary?
714
715 $context['uninstalling'] = $_REQUEST['sa'] == 'uninstall2';
716
717 // Set up the linktree for other.
718 $context['linktree'][count($context['linktree']) - 1] = array(
719 'url' => $scripturl . '?action=admin;area=packages;sa=browse',
720 'name' => $context['uninstalling'] ? $txt['uninstall'] : $txt['extracting']
721 );
722 $context['page_title'] .= ' - ' . ($context['uninstalling'] ? $txt['uninstall'] : $txt['extracting']);
723
724 $context['sub_template'] = 'extract_package';
725
726 if (!file_exists($boarddir . '/Packages/' . $context['filename']))
727 fatal_lang_error('package_no_file', false);
728
729 // Load up the package FTP information?
730 create_chmod_control(array(), array('destination_url' => $scripturl . '?action=admin;area=packages;sa=' . $_REQUEST['sa'] . ';package=' . $_REQUEST['package']));
731
732 // Make sure temp directory exists and is empty!
733 if (file_exists($boarddir . '/Packages/temp'))
734 deltree($boarddir . '/Packages/temp', false);
735 else
736 mktree($boarddir . '/Packages/temp', 0777);
737
738 // Let the unpacker do the work.
739 if (is_file($boarddir . '/Packages/' . $context['filename']))
740 {
741 $context['extracted_files'] = read_tgz_file($boarddir . '/Packages/' . $context['filename'], $boarddir . '/Packages/temp');
742
743 if (!file_exists($boarddir . '/Packages/temp/package-info.xml'))
744 foreach ($context['extracted_files'] as $file)
745 if (basename($file['filename']) == 'package-info.xml')
746 {
747 $context['base_path'] = dirname($file['filename']) . '/';
748 break;
749 }
750
751 if (!isset($context['base_path']))
752 $context['base_path'] = '';
753 }
754 elseif (is_dir($boarddir . '/Packages/' . $context['filename']))
755 {
756 copytree($boarddir . '/Packages/' . $context['filename'], $boarddir . '/Packages/temp');
757 $context['extracted_files'] = listtree($boarddir . '/Packages/temp');
758 $context['base_path'] = '';
759 }
760 else
761 fatal_lang_error('no_access', false);
762
763 // Are we installing this into any custom themes?
764 $custom_themes = array(1);
765 $known_themes = explode(',', $modSettings['knownThemes']);
766 if (!empty($_POST['custom_theme']))
767 {
768 foreach ($_POST['custom_theme'] as $tid)
769 if (in_array($tid, $known_themes))
770 $custom_themes[] = (int) $tid;
771 }
772
773 // Now load up the paths of the themes that we need to know about.
774 $request = $smcFunc['db_query']('', '
775 SELECT id_theme, variable, value
776 FROM {db_prefix}themes
777 WHERE id_theme IN ({array_int:custom_themes})
778 AND variable IN ({string:name}, {string:theme_dir})',
779 array(
780 'custom_themes' => $custom_themes,
781 'name' => 'name',
782 'theme_dir' => 'theme_dir',
783 )
784 );
785 $theme_paths = array();
786 $themes_installed = array(1);
787 while ($row = $smcFunc['db_fetch_assoc']($request))
788 $theme_paths[$row['id_theme']][$row['variable']] = $row['value'];
789 $smcFunc['db_free_result']($request);
790
791 // Are there any theme copying that we want to take place?
792 $context['theme_copies'] = array(
793 'require-file' => array(),
794 'require-dir' => array(),
795 );
796 if (!empty($_POST['theme_changes']))
797 {
798 foreach ($_POST['theme_changes'] as $change)
799 {
800 if (empty($change))
801 continue;
802 $theme_data = unserialize(base64_decode($change));
803 if (empty($theme_data['type']))
804 continue;
805
806 $themes_installed[] = $theme_data['id'];
807 $context['theme_copies'][$theme_data['type']][$theme_data['orig']][] = $theme_data['future'];
808 }
809 }
810
811 // Get the package info...
812 $packageInfo = getPackageInfo($context['filename']);
813 if (!is_array($packageInfo))
814 fatal_lang_error($packageInfo);
815
816 $packageInfo['filename'] = $context['filename'];
817
818 // Set the type of extraction...
819 $context['extract_type'] = isset($packageInfo['type']) ? $packageInfo['type'] : 'modification';
820
821 // Create a backup file to roll back to! (but if they do this more than once, don't run it a zillion times.)
822 if (!empty($modSettings['package_make_backups']) && (!isset($_SESSION['last_backup_for']) || $_SESSION['last_backup_for'] != $context['filename'] . ($context['uninstalling'] ? '$$' : '$')))
823 {
824 $_SESSION['last_backup_for'] = $context['filename'] . ($context['uninstalling'] ? '$$' : '$');
825 // !!! Internationalize this?
826 package_create_backup(($context['uninstalling'] ? 'backup_' : 'before_') . strtok($context['filename'], '.'));
827 }
828
829 // The mod isn't installed.... unless proven otherwise.
830 $context['is_installed'] = false;
831
832 // Is it actually installed?
833 $request = $smcFunc['db_query']('', '
834 SELECT version, themes_installed, db_changes
835 FROM {db_prefix}log_packages
836 WHERE package_id = {string:current_package}
837 AND install_state != {int:not_installed}
838 ORDER BY time_installed DESC
839 LIMIT 1',
840 array(
841 'not_installed' => 0,
842 'current_package' => $packageInfo['id'],
843 )
844 );
845 while ($row = $smcFunc['db_fetch_assoc']($request))
846 {
847 $old_themes = explode(',', $row['themes_installed']);
848 $old_version = $row['version'];
849 $db_changes = empty($row['db_changes']) ? array() : unserialize($row['db_changes']);
850 }
851 $smcFunc['db_free_result']($request);
852
853 // Wait, it's not installed yet!
854 // !!! TODO: Replace with a better error message!
855 if (!isset($old_version) && $context['uninstalling'])
856 {
857 deltree($boarddir . '/Packages/temp');
858 fatal_error('Hacker?', false);
859 }
860 // Uninstalling?
861 elseif ($context['uninstalling'])
862 {
863 $install_log = parsePackageInfo($packageInfo['xml'], false, 'uninstall');
864
865 // Gadzooks! There's no uninstaller at all!?
866 if (empty($install_log))
867 fatal_lang_error('package_uninstall_cannot', false);
868
869 // They can only uninstall from what it was originally installed into.
870 foreach ($theme_paths as $id => $data)
871 if ($id != 1 && !in_array($id, $old_themes))
872 unset($theme_paths[$id]);
873 }
874 elseif (isset($old_version) && $old_version != $packageInfo['version'])
875 {
876 // Look for an upgrade...
877 $install_log = parsePackageInfo($packageInfo['xml'], false, 'upgrade', $old_version);
878
879 // There was no upgrade....
880 if (empty($install_log))
881 $context['is_installed'] = true;
882 else
883 {
884 // Upgrade previous themes only!
885 foreach ($theme_paths as $id => $data)
886 if ($id != 1 && !in_array($id, $old_themes))
887 unset($theme_paths[$id]);
888 }
889 }
890 elseif (isset($old_version) && $old_version == $packageInfo['version'])
891 $context['is_installed'] = true;
892
893 if (!isset($old_version) || $context['is_installed'])
894 $install_log = parsePackageInfo($packageInfo['xml'], false, 'install');
895
896 $context['install_finished'] = false;
897
898 // !!! TODO: Make a log of any errors that occurred and output them?
899
900 if (!empty($install_log))
901 {
902 $failed_steps = array();
903 $failed_count = 0;
904
905 foreach ($install_log as $action)
906 {
907 $failed_count++;
908
909 if ($action['type'] == 'modification' && !empty($action['filename']))
910 {
911 if ($action['boardmod'])
912 $mod_actions = parseBoardMod(file_get_contents($boarddir . '/Packages/temp/' . $context['base_path'] . $action['filename']), false, $action['reverse'], $theme_paths);
913 else
914 $mod_actions = parseModification(file_get_contents($boarddir . '/Packages/temp/' . $context['base_path'] . $action['filename']), false, $action['reverse'], $theme_paths);
915
916 // Any errors worth noting?
917 foreach ($mod_actions as $key => $action)
918 {
919 if ($action['type'] == 'failure')
920 $failed_steps[] = array(
921 'file' => $action['filename'],
922 'large_step' => $failed_count,
923 'sub_step' => $key,
924 'theme' => 1,
925 );
926
927 // Gather the themes we installed into.
928 if (!empty($action['is_custom']))
929 $themes_installed[] = $action['is_custom'];
930 }
931 }
932 elseif ($action['type'] == 'code' && !empty($action['filename']))
933 {
934 // This is just here as reference for what is available.
935 global $txt, $boarddir, $sourcedir, $modSettings, $context, $settings, $forum_version, $smcFunc;
936
937 // Now include the file and be done with it ;).
938 require($boarddir . '/Packages/temp/' . $context['base_path'] . $action['filename']);
939 }
940 // Only do the database changes on uninstall if requested.
941 elseif ($action['type'] == 'database' && !empty($action['filename']) && (!$context['uninstalling'] || !empty($_POST['do_db_changes'])))
942 {
943 // These can also be there for database changes.
944 global $txt, $boarddir, $sourcedir, $modSettings, $context, $settings, $forum_version, $smcFunc;
945 global $db_package_log;
946
947 // We'll likely want the package specific database functionality!
948 db_extend('packages');
949
950 // Let the file work its magic ;)
951 require($boarddir . '/Packages/temp/' . $context['base_path'] . $action['filename']);
952 }
953 // Handle a redirect...
954 elseif ($action['type'] == 'redirect' && !empty($action['redirect_url']))
955 {
956 $context['redirect_url'] = $action['redirect_url'];
957 $context['redirect_text'] = !empty($action['filename']) && file_exists($boarddir . '/Packages/temp/' . $context['base_path'] . $action['filename']) ? file_get_contents($boarddir . '/Packages/temp/' . $context['base_path'] . $action['filename']) : ($context['uninstalling'] ? $txt['package_uninstall_done'] : $txt['package_installed_done']);
958 $context['redirect_timeout'] = $action['redirect_timeout'];
959
960 // Parse out a couple of common urls.
961 $urls = array(
962 '$boardurl' => $boardurl,
963 '$scripturl' => $scripturl,
964 '$session_var' => $context['session_var'],
965 '$session_id' => $context['session_id'],
966 );
967
968 $context['redirect_url'] = strtr($context['redirect_url'], $urls);
969 }
970 }
971
972 package_flush_cache();
973
974 // First, ensure this change doesn't get removed by putting a stake in the ground (So to speak).
975 package_put_contents($boarddir . '/Packages/installed.list', time());
976
977 // See if this is already installed, and change it's state as required.
978 $request = $smcFunc['db_query']('', '
979 SELECT package_id, install_state, db_changes
980 FROM {db_prefix}log_packages
981 WHERE install_state != {int:not_installed}
982 AND package_id = {string:current_package}
983 ' . ($context['install_id'] ? ' AND id_install = {int:install_id} ' : '') . '
984 ORDER BY time_installed DESC
985 LIMIT 1',
986 array(
987 'not_installed' => 0,
988 'install_id' => $context['install_id'],
989 'current_package' => $packageInfo['id'],
990 )
991 );
992 $is_upgrade = false;
993 while ($row = $smcFunc['db_fetch_assoc']($request))
994 {
995 // Uninstalling?
996 if ($context['uninstalling'])
997 {
998 $smcFunc['db_query']('', '
999 UPDATE {db_prefix}log_packages
1000 SET install_state = {int:not_installed}, member_removed = {string:member_name}, id_member_removed = {int:current_member},
1001 time_removed = {int:current_time}
1002 WHERE package_id = {string:package_id}',
1003 array(
1004 'current_member' => $user_info['id'],
1005 'not_installed' => 0,
1006 'current_time' => time(),
1007 'package_id' => $row['package_id'],
1008 'member_name' => $user_info['name'],
1009 )
1010 );
1011 }
1012 // Otherwise must be an upgrade.
1013 else
1014 {
1015 $is_upgrade = true;
1016 $old_db_changes = empty($row['db_changes']) ? array() : unserialize($row['db_changes']);
1017 }
1018 }
1019
1020 // Assuming we're not uninstalling, add the entry.
1021 if (!$context['uninstalling'])
1022 {
1023 // Any db changes from older version?
1024 if (!empty($old_db_changes))
1025 $db_package_log = empty($db_package_log) ? $old_db_changes : array_merge($old_db_changes, $db_package_log);
1026
1027 // If there are some database changes we might want to remove then filter them out.
1028 if (!empty($db_package_log))
1029 {
1030 // We're really just checking for entries which are create table AND add columns (etc).
1031 $tables = array();
1032 function sort_table_first($a, $b)
1033 {
1034 if ($a[0] == $b[0])
1035 return 0;
1036 return $a[0] == 'remove_table' ? -1 : 1;
1037 }
1038 usort($db_package_log, 'sort_table_first');
1039 foreach ($db_package_log as $k => $log)
1040 {
1041 if ($log[0] == 'remove_table')
1042 $tables[] = $log[1];
1043 elseif (in_array($log[1], $tables))
1044 unset($db_package_log[$k]);
1045 }
1046 $db_changes = serialize($db_package_log);
1047 }
1048 else
1049 $db_changes = '';
1050
1051 // What themes did we actually install?
1052 $themes_installed = array_unique($themes_installed);
1053 $themes_installed = implode(',', $themes_installed);
1054
1055 // What failed steps?
1056 $failed_step_insert = serialize($failed_steps);
1057
1058 $smcFunc['db_insert']('',
1059 '{db_prefix}log_packages',
1060 array(
1061 'filename' => 'string', 'name' => 'string', 'package_id' => 'string', 'version' => 'string',
1062 'id_member_installed' => 'int', 'member_installed' => 'string','time_installed' => 'int',
1063 'install_state' => 'int', 'failed_steps' => 'string', 'themes_installed' => 'string',
1064 'member_removed' => 'int', 'db_changes' => 'string',
1065 ),
1066 array(
1067 $packageInfo['filename'], $packageInfo['name'], $packageInfo['id'], $packageInfo['version'],
1068 $user_info['id'], $user_info['name'], time(),
1069 $is_upgrade ? 2 : 1, $failed_step_insert, $themes_installed,
1070 0, $db_changes,
1071 ),
1072 array('id_install')
1073 );
1074 }
1075 $smcFunc['db_free_result']($request);
1076
1077 $context['install_finished'] = true;
1078 }
1079
1080 // If there's database changes - and they want them removed - let's do it last!
1081 if (!empty($db_changes) && !empty($_POST['do_db_changes']))
1082 {
1083 // We're gonna be needing the package db functions!
1084 db_extend('packages');
1085
1086 foreach ($db_changes as $change)
1087 {
1088 if ($change[0] == 'remove_table' && isset($change[1]))
1089 $smcFunc['db_drop_table']($change[1]);
1090 elseif ($change[0] == 'remove_column' && isset($change[2]))
1091 $smcFunc['db_remove_column']($change[1], $change[2]);
1092 elseif ($change[0] == 'remove_index' && isset($change[2]))
1093 $smcFunc['db_remove_index']($change[1], $change[2]);
1094 }
1095 }
1096
1097 // Clean house... get rid of the evidence ;).
1098 if (file_exists($boarddir . '/Packages/temp'))
1099 deltree($boarddir . '/Packages/temp');
1100
1101 // Log what we just did.
1102 logAction($context['uninstalling'] ? 'uninstall_package' : (!empty($is_upgrade) ? 'upgrade_package' : 'install_package'), array('package' => $smcFunc['htmlspecialchars']($packageInfo['name']), 'version' => $smcFunc['htmlspecialchars']($packageInfo['version'])), 'admin');
1103
1104 // Just in case, let's clear the whole cache to avoid anything going up the swanny.
1105 clean_cache();
1106
1107 // Restore file permissions?
1108 create_chmod_control(array(), array(), true);
1109 }
1110
1111 // List the files in a package.
1112 function PackageList()
1113 {
1114 global $txt, $scripturl, $boarddir, $context, $sourcedir;
1115
1116 require_once($sourcedir . '/Subs-Package.php');
1117
1118 // No package? Show him or her the door.
1119 if (!isset($_REQUEST['package']) || $_REQUEST['package'] == '')
1120 redirectexit('action=admin;area=packages');
1121
1122 $context['linktree'][] = array(
1123 'url' => $scripturl . '?action=admin;area=packages;sa=list;package=' . $_REQUEST['package'],
1124 'name' => $txt['list_file']
1125 );
1126 $context['page_title'] .= ' - ' . $txt['list_file'];
1127 $context['sub_template'] = 'list';
1128
1129 // The filename...
1130 $context['filename'] = $_REQUEST['package'];
1131
1132 // Let the unpacker do the work.
1133 if (is_file($boarddir . '/Packages/' . $context['filename']))
1134 $context['files'] = read_tgz_file($boarddir . '/Packages/' . $context['filename'], null);
1135 elseif (is_dir($boarddir . '/Packages/' . $context['filename']))
1136 $context['files'] = listtree($boarddir . '/Packages/' . $context['filename']);
1137 }
1138
1139 // List the files in a package.
1140 function ExamineFile()
1141 {
1142 global $txt, $scripturl, $boarddir, $context, $sourcedir;
1143
1144 require_once($sourcedir . '/Subs-Package.php');
1145
1146 // No package? Show him or her the door.
1147 if (!isset($_REQUEST['package']) || $_REQUEST['package'] == '')
1148 redirectexit('action=admin;area=packages');
1149
1150 // No file? Show him or her the door.
1151 if (!isset($_REQUEST['file']) || $_REQUEST['file'] == '')
1152 redirectexit('action=admin;area=packages');
1153
1154 $_REQUEST['package'] = preg_replace('~[\.]+~', '.', strtr($_REQUEST['package'], array('/' => '_', '\\' => '_')));
1155 $_REQUEST['file'] = preg_replace('~[\.]+~', '.', $_REQUEST['file']);
1156
1157 if (isset($_REQUEST['raw']))
1158 {
1159 if (is_file($boarddir . '/Packages/' . $_REQUEST['package']))
1160 echo read_tgz_file($boarddir . '/Packages/' . $_REQUEST['package'], $_REQUEST['file'], true);
1161 elseif (is_dir($boarddir . '/Packages/' . $_REQUEST['package']))
1162 echo file_get_contents($boarddir . '/Packages/' . $_REQUEST['package'] . '/' . $_REQUEST['file']);
1163
1164 obExit(false);
1165 }
1166
1167 $context['linktree'][count($context['linktree']) - 1] = array(
1168 'url' => $scripturl . '?action=admin;area=packages;sa=list;package=' . $_REQUEST['package'],
1169 'name' => $txt['package_examine_file']
1170 );
1171 $context['page_title'] .= ' - ' . $txt['package_examine_file'];
1172 $context['sub_template'] = 'examine';
1173
1174 // The filename...
1175 $context['package'] = $_REQUEST['package'];
1176 $context['filename'] = $_REQUEST['file'];
1177
1178 // Let the unpacker do the work.... but make sure we handle images properly.
1179 if (in_array(strtolower(strrchr($_REQUEST['file'], '.')), array('.bmp', '.gif', '.jpeg', '.jpg', '.png')))
1180 $context['filedata'] = '<img src="' . $scripturl . '?action=admin;area=packages;sa=examine;package=' . $_REQUEST['package'] . ';file=' . $_REQUEST['file'] . ';raw" alt="' . $_REQUEST['file'] . '" />';
1181 else
1182 {
1183 if (is_file($boarddir . '/Packages/' . $_REQUEST['package']))
1184 $context['filedata'] = htmlspecialchars(read_tgz_file($boarddir . '/Packages/' . $_REQUEST['package'], $_REQUEST['file'], true));
1185 elseif (is_dir($boarddir . '/Packages/' . $_REQUEST['package']))
1186 $context['filedata'] = htmlspecialchars(file_get_contents($boarddir . '/Packages/' . $_REQUEST['package'] . '/' . $_REQUEST['file']));
1187
1188 if (strtolower(strrchr($_REQUEST['file'], '.')) == '.php')
1189 $context['filedata'] = highlight_php_code($context['filedata']);
1190 }
1191 }
1192
1193 // List the installed packages.
1194 function InstalledList()
1195 {
1196 global $txt, $scripturl, $context;
1197
1198 $context['page_title'] .= ' - ' . $txt['installed_packages'];
1199 $context['sub_template'] = 'view_installed';
1200
1201 // Load the installed mods and send them to the template.
1202 $context['installed_mods'] = loadInstalledPackages();
1203 }
1204
1205 // Empty out the installed list.
1206 function FlushInstall()
1207 {
1208 global $boarddir, $sourcedir, $smcFunc;
1209
1210 // Always check the session.
1211 checkSession('get');
1212
1213 include_once($sourcedir . '/Subs-Package.php');
1214
1215 // Record when we last did this.
1216 package_put_contents($boarddir . '/Packages/installed.list', time());
1217
1218 // Set everything as uninstalled.
1219 $smcFunc['db_query']('', '
1220 UPDATE {db_prefix}log_packages
1221 SET install_state = {int:not_installed}',
1222 array(
1223 'not_installed' => 0,
1224 )
1225 );
1226
1227 redirectexit('action=admin;area=packages;sa=installed');
1228 }
1229
1230 // Delete a package.
1231 function PackageRemove()
1232 {
1233 global $scripturl, $boarddir;
1234
1235 // Check it.
1236 checkSession('get');
1237
1238 // Ack, don't allow deletion of arbitrary files here, could become a security hole somehow!
1239 if (!isset($_GET['package']) || $_GET['package'] == 'index.php' || $_GET['package'] == 'installed.list' || $_GET['package'] == 'backups')
1240 redirectexit('action=admin;area=packages;sa=browse');
1241 $_GET['package'] = preg_replace('~[\.]+~', '.', strtr($_GET['package'], array('/' => '_', '\\' => '_')));
1242
1243 // Can't delete what's not there.
1244 if (file_exists($boarddir . '/Packages/' . $_GET['package']) && (substr($_GET['package'], -4) == '.zip' || substr($_GET['package'], -4) == '.tgz' || substr($_GET['package'], -7) == '.tar.gz' || is_dir($boarddir . '/Packages/' . $_GET['package'])) && $_GET['package'] != 'backups' && substr($_GET['package'], 0, 1) != '.')
1245 {
1246 create_chmod_control(array($boarddir . '/Packages/' . $_GET['package']), array('destination_url' => $scripturl . '?action=admin;area=packages;sa=remove;package=' . $_GET['package'], 'crash_on_error' => true));
1247
1248 if (is_dir($boarddir . '/Packages/' . $_GET['package']))
1249 deltree($boarddir . '/Packages/' . $_GET['package']);
1250 else
1251 {
1252 @chmod($boarddir . '/Packages/' . $_GET['package'], 0777);
1253 unlink($boarddir . '/Packages/' . $_GET['package']);
1254 }
1255 }
1256
1257 redirectexit('action=admin;area=packages;sa=browse');
1258 }
1259
1260 // Browse a list of installed packages.
1261 function PackageBrowse()
1262 {
1263 global $txt, $boarddir, $scripturl, $context, $forum_version;
1264
1265 $context['page_title'] .= ' - ' . $txt['browse_packages'];
1266 $context['sub_template'] = 'browse';
1267
1268 $context['forum_version'] = $forum_version;
1269
1270 $instmods = loadInstalledPackages();
1271
1272 $installed_mods = array();
1273 // Look through the list of installed mods...
1274 foreach ($instmods as $installed_mod)
1275 $installed_mods[$installed_mod['package_id']] = array(
1276 'id' => $installed_mod['id'],
1277 'version' => $installed_mod['version'],
1278 );
1279
1280 $the_version = strtr($forum_version, array('SMF ' => ''));
1281
1282 // Here we have a little code to help those who class themselves as something of gods, version emulation ;)
1283 if (isset($_GET['version_emulate']))
1284 {
1285 if ($_GET['version_emulate'] === 0 && isset($_SESSION['version_emulate']))
1286 unset($_SESSION['version_emulate']);
1287 elseif ($_GET['version_emulate'] !== 0)
1288 $_SESSION['version_emulate'] = strtr($_GET['version_emulate'], array('-' => ' ', '+' => ' ', 'SMF ' => ''));
1289 }
1290 if (!empty($_SESSION['version_emulate']))
1291 {
1292 $context['forum_version'] = 'SMF ' . $_SESSION['version_emulate'];
1293 $the_version = $_SESSION['version_emulate'];
1294 }
1295
1296 // Get a list of all the ids installed, so the latest packages won't include already installed ones.
1297 $context['installed_mods'] = array_keys($installed_mods);
1298
1299 // Empty lists for now.
1300 $context['available_mods'] = array();
1301 $context['available_avatars'] = array();
1302 $context['available_languages'] = array();
1303 $context['available_other'] = array();
1304 $context['available_all'] = array();
1305
1306 // We need the packages directory to be writable for this.
1307 if (!@is_writable($boarddir . '/Packages'))
1308 create_chmod_control(array($boarddir . '/Packages'), array('destination_url' => $scripturl . '?action=admin;area=packages', 'crash_on_error' => true));
1309
1310 if ($dir = @opendir($boarddir . '/Packages'))
1311 {
1312 $dirs = array();
1313 while ($package = readdir($dir))
1314 {
1315 if ($package == '.' || $package == '..' || $package == 'temp' || (!(is_dir($boarddir . '/Packages/' . $package) && file_exists($boarddir . '/Packages/' . $package . '/package-info.xml')) && substr(strtolower($package), -7) != '.tar.gz' && substr(strtolower($package), -4) != '.tgz' && substr(strtolower($package), -4) != '.zip'))
1316 continue;
1317
1318 // Skip directories or files that are named the same.
1319 if (is_dir($boarddir . '/Packages/' . $package))
1320 {
1321 if (in_array($package, $dirs))
1322 continue;
1323 $dirs[] = $package;
1324 }
1325 elseif (substr(strtolower($package), -7) == '.tar.gz')
1326 {
1327 if (in_array(substr($package, 0, -7), $dirs))
1328 continue;
1329 $dirs[] = substr($package, 0, -7);
1330 }
1331 elseif (substr(strtolower($package), -4) == '.zip' || substr(strtolower($package), -4) == '.tgz')
1332 {
1333 if (in_array(substr($package, 0, -4), $dirs))
1334 continue;
1335 $dirs[] = substr($package, 0, -4);
1336 }
1337
1338 $packageInfo = getPackageInfo($package);
1339 if (!is_array($packageInfo))
1340 continue;
1341
1342 $packageInfo['installed_id'] = isset($installed_mods[$packageInfo['id']]) ? $installed_mods[$packageInfo['id']]['id'] : 0;
1343
1344 $packageInfo['is_installed'] = isset($installed_mods[$packageInfo['id']]);
1345 $packageInfo['is_current'] = $packageInfo['is_installed'] && ($installed_mods[$packageInfo['id']]['version'] == $packageInfo['version']);
1346 $packageInfo['is_newer'] = $packageInfo['is_installed'] && ($installed_mods[$packageInfo['id']]['version'] > $packageInfo['version']);
1347
1348 $packageInfo['can_install'] = false;
1349 $packageInfo['can_uninstall'] = false;
1350 $packageInfo['can_upgrade'] = false;
1351
1352 // This package is currently NOT installed. Check if it can be.
1353 if (!$packageInfo['is_installed'] && $packageInfo['xml']->exists('install'))
1354 {
1355 // Check if there's an install for *THIS* version of SMF.
1356 $installs = $packageInfo['xml']->set('install');
1357 foreach ($installs as $install)
1358 {
1359 if (!$install->exists('@for') || matchPackageVersion($the_version, $install->fetch('@for')))
1360 {
1361 // Okay, this one is good to go.
1362 $packageInfo['can_install'] = true;
1363 break;
1364 }
1365 }
1366 }
1367 // An already installed, but old, package. Can we upgrade it?
1368 elseif ($packageInfo['is_installed'] && !$packageInfo['is_current'] && $packageInfo['xml']->exists('upgrade'))
1369 {
1370 $upgrades = $packageInfo['xml']->set('upgrade');
1371
1372 // First go through, and check against the current version of SMF.
1373 foreach ($upgrades as $upgrade)
1374 {
1375 // Even if it is for this SMF, is it for the installed version of the mod?
1376 if (!$upgrade->exists('@for') || matchPackageVersion($the_version, $upgrade->fetch('@for')))
1377 if (!$upgrade->exists('@from') || matchPackageVersion($installed_mods[$packageInfo['id']]['version'], $upgrade->fetch('@from')))
1378 {
1379 $packageInfo['can_upgrade'] = true;
1380 break;
1381 }
1382 }
1383 }
1384 // Note that it has to be the current version to be uninstallable. Shucks.
1385 elseif ($packageInfo['is_installed'] && $packageInfo['is_current'] && $packageInfo['xml']->exists('uninstall'))
1386 {
1387 $uninstalls = $packageInfo['xml']->set('uninstall');
1388
1389 // Can we find any uninstallation methods that work for this SMF version?
1390 foreach ($uninstalls as $uninstall)
1391 if (!$uninstall->exists('@for') || matchPackageVersion($the_version, $uninstall->fetch('@for')))
1392 {
1393 $packageInfo['can_uninstall'] = true;
1394 break;
1395 }
1396 }
1397
1398 // Store a complete list.
1399 $context['available_all'][] = $packageInfo;
1400
1401 // Modification.
1402 if ($packageInfo['type'] == 'modification' || $packageInfo['type'] == 'mod')
1403 $context['available_mods'][] = $packageInfo;
1404 // Avatar package.
1405 elseif ($packageInfo['type'] == 'avatar')
1406 $context['available_avatars'][] = $packageInfo;
1407 // Language package.
1408 elseif ($packageInfo['type'] == 'language')
1409 $context['available_languages'][] = $packageInfo;
1410 // Other stuff.
1411 else
1412 $context['available_other'][] = $packageInfo;
1413 }
1414 closedir($dir);
1415 }
1416 }
1417
1418 function PackageOptions()
1419 {
1420 global $txt, $scripturl, $context, $sourcedir, $modSettings, $smcFunc;
1421
1422 if (isset($_POST['submit']))
1423 {
1424 checkSession('post');
1425
1426 updateSettings(array(
1427 'package_server' => trim($smcFunc['htmlspecialchars']($_POST['pack_server'])),
1428 'package_port' => trim($smcFunc['htmlspecialchars']($_POST['pack_port'])),
1429 'package_username' => trim($smcFunc['htmlspecialchars']($_POST['pack_user'])),
1430 'package_make_backups' => !empty($_POST['package_make_backups'])
1431 ));
1432
1433 redirectexit('action=admin;area=packages;sa=options');
1434 }
1435
1436 if (preg_match('~^/home/([^/]+?)/public_html~', $_SERVER['DOCUMENT_ROOT'], $match))
1437 $default_username = $match[1];
1438 else
1439 $default_username = '';
1440
1441 $context['page_title'] = $txt['package_settings'];
1442 $context['sub_template'] = 'install_options';
1443
1444 $context['package_ftp_server'] = isset($modSettings['package_server']) ? $modSettings['package_server'] : 'localhost';
1445 $context['package_ftp_port'] = isset($modSettings['package_port']) ? $modSettings['package_port'] : '21';
1446 $context['package_ftp_username'] = isset($modSettings['package_username']) ? $modSettings['package_username'] : $default_username;
1447 $context['package_make_backups'] = !empty($modSettings['package_make_backups']);
1448 }
1449
1450 function ViewOperations()
1451 {
1452 global $context, $txt, $boarddir, $sourcedir, $smcFunc, $modSettings;
1453
1454 // Can't be in here buddy.
1455 isAllowedTo('admin_forum');
1456
1457 // We need to know the operation key for the search and replace, mod file looking at, is it a board mod?
1458 if (!isset($_REQUEST['operation_key'], $_REQUEST['filename']) && !is_numeric($_REQUEST['operation_key']))
1459 fatal_lang_error('operation_invalid', 'general');
1460
1461 // Load the required file.
1462 require_once($sourcedir . '/Subs-Package.php');
1463
1464 // Uninstalling the mod?
1465 $reverse = isset($_REQUEST['reverse']) ? true : false;
1466
1467 // Get the base name.
1468 $context['filename'] = preg_replace('~[\.]+~', '.', $_REQUEST['package']);
1469
1470 // We need to extract this again.
1471 if (is_file($boarddir . '/Packages/' . $context['filename']))
1472 {
1473 $context['extracted_files'] = read_tgz_file($boarddir . '/Packages/' . $context['filename'], $boarddir . '/Packages/temp');
1474
1475 if ($context['extracted_files'] && !file_exists($boarddir . '/Packages/temp/package-info.xml'))
1476 foreach ($context['extracted_files'] as $file)
1477 if (basename($file['filename']) == 'package-info.xml')
1478 {
1479 $context['base_path'] = dirname($file['filename']) . '/';
1480 break;
1481 }
1482
1483 if (!isset($context['base_path']))
1484 $context['base_path'] = '';
1485 }
1486 elseif (is_dir($boarddir . '/Packages/' . $context['filename']))
1487 {
1488 copytree($boarddir . '/Packages/' . $context['filename'], $boarddir . '/Packages/temp');
1489 $context['extracted_files'] = listtree($boarddir . '/Packages/temp');
1490 $context['base_path'] = '';
1491 }
1492
1493 // Load up any custom themes we may want to install into...
1494 $request = $smcFunc['db_query']('', '
1495 SELECT id_theme, variable, value
1496 FROM {db_prefix}themes
1497 WHERE (id_theme = {int:default_theme} OR id_theme IN ({array_int:known_theme_list}))
1498 AND variable IN ({string:name}, {string:theme_dir})',
1499 array(
1500 'known_theme_list' => explode(',', $modSettings['knownThemes']),
1501 'default_theme' => 1,
1502 'name' => 'name',
1503 'theme_dir' => 'theme_dir',
1504 )
1505 );
1506 $theme_paths = array();
1507 while ($row = $smcFunc['db_fetch_assoc']($request))
1508 $theme_paths[$row['id_theme']][$row['variable']] = $row['value'];
1509 $smcFunc['db_free_result']($request);
1510
1511 // Boardmod?
1512 if (isset($_REQUEST['boardmod']))
1513 $mod_actions = parseBoardMod(@file_get_contents($boarddir . '/Packages/temp/' . $context['base_path'] . $_REQUEST['filename']), true, $reverse, $theme_paths);
1514 else
1515 $mod_actions = parseModification(@file_get_contents($boarddir . '/Packages/temp/' . $context['base_path'] . $_REQUEST['filename']), true, $reverse, $theme_paths);
1516
1517 // Ok lets get the content of the file.
1518 $context['operations'] = array(
1519 'search' => strtr(htmlspecialchars($mod_actions[$_REQUEST['operation_key']]['search_original']), array('[' => '&#91;', ']' => '&#93;')),
1520 'replace' => strtr(htmlspecialchars($mod_actions[$_REQUEST['operation_key']]['replace_original']), array('[' => '&#91;', ']' => '&#93;')),
1521 'position' => $mod_actions[$_REQUEST['operation_key']]['position'],
1522 );
1523
1524 // Let's do some formatting...
1525 $operation_text = $context['operations']['position'] == 'replace' ? 'operation_replace' : ($context['operations']['position'] == 'before' ? 'operation_after' : 'operation_before');
1526 $context['operations']['search'] = parse_bbc('[code=' . $txt['operation_find'] . ']' . ($context['operations']['position'] == 'end' ? '?&gt;' : $context['operations']['search']) . '[/code]');
1527 $context['operations']['replace'] = parse_bbc('[code=' . $txt[$operation_text] . ']' . $context['operations']['replace'] . '[/code]');
1528
1529 // No layers
1530 $context['template_layers'] = array();
1531 $context['sub_template'] = 'view_operations';
1532 }
1533
1534 // Allow the admin to reset permissions on files.
1535 function PackagePermissions()
1536 {
1537 global $context, $txt, $modSettings, $boarddir, $sourcedir, $cachedir, $smcFunc, $package_ftp;
1538
1539 // Let's try and be good, yes?
1540 checkSession('get');
1541
1542 // If we're restoring permissions this is just a pass through really.
1543 if (isset($_GET['restore']))
1544 {
1545 create_chmod_control(array(), array(), true);
1546 fatal_lang_error('no_access', false);
1547 }
1548
1549 // This is a memory eat.
1550 @ini_set('memory_limit', '128M');
1551 @set_time_limit(600);
1552
1553 // Load up some FTP stuff.
1554 create_chmod_control();
1555
1556 if (empty($package_ftp) && !isset($_POST['skip_ftp']))
1557 {
1558 loadClassFile('Class-Package.php');
1559 $ftp = new ftp_connection(null);
1560 list ($username, $detect_path, $found_path) = $ftp->detect_path($boarddir);
1561
1562 $context['package_ftp'] = array(
1563 'server' => isset($modSettings['package_server']) ? $modSettings['package_server'] : 'localhost',
1564 'port' => isset($modSettings['package_port']) ? $modSettings['package_port'] : '21',
1565 'username' => empty($username) ? (isset($modSettings['package_username']) ? $modSettings['package_username'] : '') : $username,
1566 'path' => $detect_path,
1567 'form_elements_only' => true,
1568 );
1569 }
1570 else
1571 $context['ftp_connected'] = true;
1572
1573 // Define the template.
1574 $context['page_title'] = $txt['package_file_perms'];
1575 $context['sub_template'] = 'file_permissions';
1576
1577 // Define what files we're interested in, as a tree.
1578 $context['file_tree'] = array(
1579 strtr($boarddir, array('\\' => '/')) => array(
1580 'type' => 'dir',
1581 'contents' => array(
1582 'agreement.txt' => array(
1583 'type' => 'file',
1584 'writable_on' => 'standard',
1585 ),
1586 'Settings.php' => array(
1587 'type' => 'file',
1588 'writable_on' => 'restrictive',
1589 ),
1590 'Settings_bak.php' => array(
1591 'type' => 'file',
1592 'writable_on' => 'restrictive',
1593 ),
1594 'attachments' => array(
1595 'type' => 'dir',
1596 'writable_on' => 'restrictive',
1597 ),
1598 'avatars' => array(
1599 'type' => 'dir',
1600 'writable_on' => 'standard',
1601 ),
1602 'cache' => array(
1603 'type' => 'dir',
1604 'writable_on' => 'restrictive',
1605 ),
1606 'custom_avatar_dir' => array(
1607 'type' => 'dir',
1608 'writable_on' => 'restrictive',
1609 ),
1610 'Smileys' => array(
1611 'type' => 'dir_recursive',
1612 'writable_on' => 'standard',
1613 ),
1614 'Sources' => array(
1615 'type' => 'dir',
1616 'list_contents' => true,
1617 'writable_on' => 'standard',
1618 ),
1619 'Themes' => array(
1620 'type' => 'dir_recursive',
1621 'writable_on' => 'standard',
1622 'contents' => array(
1623 'default' => array(
1624 'type' => 'dir_recursive',
1625 'list_contents' => true,
1626 'contents' => array(
1627 'languages' => array(
1628 'type' => 'dir',
1629 'list_contents' => true,
1630 ),
1631 ),
1632 ),
1633 ),
1634 ),
1635 'Packages' => array(
1636 'type' => 'dir',
1637 'writable_on' => 'standard',
1638 'contents' => array(
1639 'temp' => array(
1640 'type' => 'dir',
1641 ),
1642 'backup' => array(
1643 'type' => 'dir',
1644 ),
1645 'installed.list' => array(
1646 'type' => 'file',
1647 'writable_on' => 'standard',
1648 ),
1649 ),
1650 ),
1651 ),
1652 ),
1653 );
1654
1655 // Directories that can move.
1656 if (substr($sourcedir, 0, strlen($boarddir)) != $boarddir)
1657 {
1658 unset($context['file_tree'][strtr($boarddir, array('\\' => '/'))]['contents']['Sources']);
1659 $context['file_tree'][strtr($sourcedir, array('\\' => '/'))] = array(
1660 'type' => 'dir',
1661 'list_contents' => true,
1662 'writable_on' => 'standard',
1663 );
1664 }
1665
1666 // Moved the cache?
1667 if (substr($cachedir, 0, strlen($boarddir)) != $boarddir)
1668 {
1669 unset($context['file_tree'][strtr($boarddir, array('\\' => '/'))]['contents']['cache']);
1670 $context['file_tree'][strtr($cachedir, array('\\' => '/'))] = array(
1671 'type' => 'dir',
1672 'list_contents' => false,
1673 'writable_on' => 'restrictive',
1674 );
1675 }
1676
1677 // Are we using multiple attachment directories?
1678 if (!empty($modSettings['currentAttachmentUploadDir']))
1679 {
1680 unset($context['file_tree'][strtr($boarddir, array('\\' => '/'))]['contents']['attachments']);
1681
1682 if (!is_array($modSettings['attachmentUploadDir']))
1683 $modSettings['attachmentUploadDir'] = unserialize($modSettings['attachmentUploadDir']);
1684
1685 // !!! Should we suggest non-current directories be read only?
1686 foreach ($modSettings['attachmentUploadDir'] as $dir)
1687 $context['file_tree'][strtr($dir, array('\\' => '/'))] = array(
1688 'type' => 'dir',
1689 'writable_on' => 'restrictive',
1690 );
1691
1692 }
1693 elseif (substr($modSettings['attachmentUploadDir'], 0, strlen($boarddir)) != $boarddir)
1694 {
1695 unset($context['file_tree'][strtr($boarddir, array('\\' => '/'))]['contents']['attachments']);
1696 $context['file_tree'][strtr($modSettings['attachmentUploadDir'], array('\\' => '/'))] = array(
1697 'type' => 'dir',
1698 'writable_on' => 'restrictive',
1699 );
1700 }
1701
1702 if (substr($modSettings['smileys_dir'], 0, strlen($boarddir)) != $boarddir)
1703 {
1704 unset($context['file_tree'][strtr($boarddir, array('\\' => '/'))]['contents']['Smileys']);
1705 $context['file_tree'][strtr($modSettings['smileys_dir'], array('\\' => '/'))] = array(
1706 'type' => 'dir_recursive',
1707 'writable_on' => 'standard',
1708 );
1709 }
1710 if (substr($modSettings['avatar_directory'], 0, strlen($boarddir)) != $boarddir)
1711 {
1712 unset($context['file_tree'][strtr($boarddir, array('\\' => '/'))]['contents']['avatars']);
1713 $context['file_tree'][strtr($modSettings['avatar_directory'], array('\\' => '/'))] = array(
1714 'type' => 'dir',
1715 'writable_on' => 'standard',
1716 );
1717 }
1718 if (isset($modSettings['custom_avatar_dir']) && substr($modSettings['custom_avatar_dir'], 0, strlen($boarddir)) != $boarddir)
1719 {
1720 unset($context['file_tree'][strtr($boarddir, array('\\' => '/'))]['contents']['custom_avatar_dir']);
1721 $context['file_tree'][strtr($modSettings['custom_avatar_dir'], array('\\' => '/'))] = array(
1722 'type' => 'dir',
1723 'writable_on' => 'restrictive',
1724 );
1725 }
1726
1727 // Load up any custom themes.
1728 $request = $smcFunc['db_query']('', '
1729 SELECT value
1730 FROM {db_prefix}themes
1731 WHERE id_theme > {int:default_theme_id}
1732 AND id_member = {int:guest_id}
1733 AND variable = {string:theme_dir}
1734 ORDER BY value ASC',
1735 array(
1736 'default_theme_id' => 1,
1737 'guest_id' => 0,
1738 'theme_dir' => 'theme_dir',
1739 )
1740 );
1741 while ($row = $smcFunc['db_fetch_assoc']($request))
1742 {
1743 if (substr(strtolower(strtr($row['value'], array('\\' => '/'))), 0, strlen($boarddir) + 7) == strtolower(strtr($boarddir, array('\\' => '/')) . '/Themes'))
1744 $context['file_tree'][strtr($boarddir, array('\\' => '/'))]['contents']['Themes']['contents'][substr($row['value'], strlen($boarddir) + 8)] = array(
1745 'type' => 'dir_recursive',
1746 'list_contents' => true,
1747 'contents' => array(
1748 'languages' => array(
1749 'type' => 'dir',
1750 'list_contents' => true,
1751 ),
1752 ),
1753 );
1754 else
1755 {
1756 $context['file_tree'][strtr($row['value'], array('\\' => '/'))] = array(
1757 'type' => 'dir_recursive',
1758 'list_contents' => true,
1759 'contents' => array(
1760 'languages' => array(
1761 'type' => 'dir',
1762 'list_contents' => true,
1763 ),
1764 ),
1765 );
1766 }
1767 }
1768 $smcFunc['db_free_result']($request);
1769
1770 // If we're submitting then let's move on to another function to keep things cleaner..
1771 if (isset($_POST['action_changes']))
1772 return PackagePermissionsAction();
1773
1774 $context['look_for'] = array();
1775 // Are we looking for a particular tree - normally an expansion?
1776 if (!empty($_REQUEST['find']))
1777 $context['look_for'][] = base64_decode($_REQUEST['find']);
1778 // Only that tree?
1779 $context['only_find'] = isset($_GET['xml']) && !empty($_REQUEST['onlyfind']) ? $_REQUEST['onlyfind'] : '';
1780 if ($context['only_find'])
1781 $context['look_for'][] = $context['only_find'];
1782
1783 // Have we got a load of back-catalogue trees to expand from a submit etc?
1784 if (!empty($_GET['back_look']))
1785 {
1786 $potententialTrees = unserialize(base64_decode($_GET['back_look']));
1787 foreach ($potententialTrees as $tree)
1788 $context['look_for'][] = $tree;
1789 }
1790 // ... maybe posted?
1791 if (!empty($_POST['back_look']))
1792 $context['only_find'] = array_merge($context['only_find'], $_POST['back_look']);
1793
1794 $context['back_look_data'] = base64_encode(serialize(array_slice($context['look_for'], 0, 15)));
1795
1796 // Are we finding more files than first thought?
1797 $context['file_offset'] = !empty($_REQUEST['fileoffset']) ? (int) $_REQUEST['fileoffset'] : 0;
1798 // Don't list more than this many files in a directory.
1799 $context['file_limit'] = 150;
1800
1801 // How many levels shall we show?
1802 $context['default_level'] = empty($context['only_find']) ? 2 : 25;
1803
1804 // This will be used if we end up catching XML data.
1805 $context['xml_data'] = array(
1806 'roots' => array(
1807 'identifier' => 'root',
1808 'children' => array(
1809 array(
1810 'value' => preg_replace('~[^A-Za-z0-9_\-=:]~', ':-:', $context['only_find']),
1811 ),
1812 ),
1813 ),
1814 'folders' => array(
1815 'identifier' => 'folder',
1816 'children' => array(),
1817 ),
1818 );
1819
1820 foreach ($context['file_tree'] as $path => $data)
1821 {
1822 // Run this directory.
1823 if (file_exists($path) && (empty($context['only_find']) || substr($context['only_find'], 0, strlen($path)) == $path))
1824 {
1825 // Get the first level down only.
1826 fetchPerms__recursive($path, $context['file_tree'][$path], 1);
1827 $context['file_tree'][$path]['perms'] = array(
1828 'chmod' => @is_writable($path),
1829 'perms' => @fileperms($path),
1830 );
1831 }
1832 else
1833 unset($context['file_tree'][$path]);
1834 }
1835
1836 // Is this actually xml?
1837 if (isset($_GET['xml']))
1838 {
1839 loadTemplate('Xml');
1840 $context['sub_template'] = 'generic_xml';
1841 $context['template_layers'] = array();
1842 }
1843 }
1844
1845 function fetchPerms__recursive($path, &$data, $level)
1846 {
1847 global $context;
1848
1849 $isLikelyPath = false;
1850 foreach ($context['look_for'] as $possiblePath)
1851 if (substr($possiblePath, 0, strlen($path)) == $path)
1852 $isLikelyPath = true;
1853
1854 // Is this where we stop?
1855 if (isset($_GET['xml']) && !empty($context['look_for']) && !$isLikelyPath)
1856 return;
1857 elseif ($level > $context['default_level'] && !$isLikelyPath)
1858 return;
1859
1860 // Are we actually interested in saving this data?
1861 $save_data = empty($context['only_find']) || $context['only_find'] == $path;
1862
1863 //!!! Shouldn't happen - but better error message?
1864 if (!is_dir($path))
1865 fatal_lang_error('no_access', false);
1866
1867 // This is where we put stuff we've found for sorting.
1868 $foundData = array(
1869 'files' => array(),
1870 'folders' => array(),
1871 );
1872
1873 $dh = opendir($path);
1874 while ($entry = readdir($dh))
1875 {
1876 // Some kind of file?
1877 if (!is_dir($path . '/' . $entry))
1878 {
1879 // Are we listing PHP files in this directory?
1880 if ($save_data && !empty($data['list_contents']) && substr($entry, -4) == '.php')
1881 $foundData['files'][$entry] = true;
1882 // A file we were looking for.
1883 elseif ($save_data && isset($data['contents'][$entry]))
1884 $foundData['files'][$entry] = true;
1885 }
1886 // It's a directory - we're interested one way or another, probably...
1887 elseif ($entry != '.' && $entry != '..')
1888 {
1889 // Going further?
1890 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'))))
1891 {
1892 if (!isset($data['contents'][$entry]))
1893 $foundData['folders'][$entry] = 'dir_recursive';
1894 else
1895 $foundData['folders'][$entry] = true;
1896
1897 // If this wasn't expected inherit the recusiveness...
1898 if (!isset($data['contents'][$entry]))
1899 // We need to do this as we will be going all recursive.
1900 $data['contents'][$entry] = array(
1901 'type' => 'dir_recursive',
1902 );
1903
1904 // Actually do the recursive stuff...
1905 fetchPerms__recursive($path . '/' . $entry, $data['contents'][$entry], $level + 1);
1906 }
1907 // Maybe it is a folder we are not descending into.
1908 elseif (isset($data['contents'][$entry]))
1909 $foundData['folders'][$entry] = true;
1910 // Otherwise we stop here.
1911 }
1912 }
1913 closedir($dh);
1914
1915 // Nothing to see here?
1916 if (!$save_data)
1917 return;
1918
1919 // Now actually add the data, starting with the folders.
1920 ksort($foundData['folders']);
1921 foreach ($foundData['folders'] as $folder => $type)
1922 {
1923 $additional_data = array(
1924 'perms' => array(
1925 'chmod' => @is_writable($path . '/' . $folder),
1926 'perms' => @fileperms($path . '/' . $folder),
1927 ),
1928 );
1929 if ($type !== true)
1930 $additional_data['type'] = $type;
1931
1932 // If there's an offset ignore any folders in XML mode.
1933 if (isset($_GET['xml']) && $context['file_offset'] == 0)
1934 {
1935 $context['xml_data']['folders']['children'][] = array(
1936 'attributes' => array(
1937 'writable' => $additional_data['perms']['chmod'] ? 1 : 0,
1938 'permissions' => substr(sprintf('%o', $additional_data['perms']['perms']), -4),
1939 'folder' => 1,
1940 'path' => $context['only_find'],
1941 'level' => $level,
1942 'more' => 0,
1943 'offset' => $context['file_offset'],
1944 'my_ident' => preg_replace('~[^A-Za-z0-9_\-=:]~', ':-:', $context['only_find'] . '/' . $folder),
1945 'ident' => preg_replace('~[^A-Za-z0-9_\-=:]~', ':-:', $context['only_find']),
1946 ),
1947 'value' => $folder,
1948 );
1949 }
1950 elseif (!isset($_GET['xml']))
1951 {
1952 if (isset($data['contents'][$folder]))
1953 $data['contents'][$folder] = array_merge($data['contents'][$folder], $additional_data);
1954 else
1955 $data['contents'][$folder] = $additional_data;
1956 }
1957 }
1958
1959 // Now we want to do a similar thing with files.
1960 ksort($foundData['files']);
1961 $counter = -1;
1962 foreach ($foundData['files'] as $file => $dummy)
1963 {
1964 $counter++;
1965
1966 // Have we reached our offset?
1967 if ($context['file_offset'] > $counter)
1968 continue;
1969 // Gone too far?
1970 if ($counter > ($context['file_offset'] + $context['file_limit']))
1971 continue;
1972
1973 $additional_data = array(
1974 'perms' => array(
1975 'chmod' => @is_writable($path . '/' . $file),
1976 'perms' => @fileperms($path . '/' . $file),
1977 ),
1978 );
1979
1980 // XML?
1981 if (isset($_GET['xml']))
1982 {
1983 $context['xml_data']['folders']['children'][] = array(
1984 'attributes' => array(
1985 'writable' => $additional_data['perms']['chmod'] ? 1 : 0,
1986 'permissions' => substr(sprintf('%o', $additional_data['perms']['perms']), -4),
1987 'folder' => 0,
1988 'path' => $context['only_find'],
1989 'level' => $level,
1990 'more' => $counter == ($context['file_offset'] + $context['file_limit']) ? 1 : 0,
1991 'offset' => $context['file_offset'],
1992 'my_ident' => preg_replace('~[^A-Za-z0-9_\-=:]~', ':-:', $context['only_find'] . '/' . $file),
1993 'ident' => preg_replace('~[^A-Za-z0-9_\-=:]~', ':-:', $context['only_find']),
1994 ),
1995 'value' => $file,
1996 );
1997 }
1998 elseif ($counter != ($context['file_offset'] + $context['file_limit']))
1999 {
2000 if (isset($data['contents'][$file]))
2001 $data['contents'][$file] = array_merge($data['contents'][$file], $additional_data);
2002 else
2003 $data['contents'][$file] = $additional_data;
2004 }
2005 }
2006 }
2007
2008 // Actually action the permission changes they want.
2009 function PackagePermissionsAction()
2010 {
2011 global $context, $txt, $time_start, $package_ftp;
2012
2013 umask(0);
2014
2015 $timeout_limit = 5;
2016
2017 $context['method'] = $_POST['method'] == 'individual' ? 'individual' : 'predefined';
2018 $context['sub_template'] = 'action_permissions';
2019 $context['page_title'] = $txt['package_file_perms_applying'];
2020 $context['back_look_data'] = isset($_POST['back_look']) ? $_POST['back_look'] : array();
2021
2022 // Skipping use of FTP?
2023 if (empty($package_ftp))
2024 $context['skip_ftp'] = true;
2025
2026 // 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.
2027 if ($context['method'] == 'individual')
2028 {
2029 // Only these path roots are legal.
2030 $legal_roots = array_keys($context['file_tree']);
2031 $context['custom_value'] = (int) $_POST['custom_value'];
2032
2033 // Continuing?
2034 if (isset($_POST['toProcess']))
2035 $_POST['permStatus'] = unserialize(base64_decode($_POST['toProcess']));
2036
2037 if (isset($_POST['permStatus']))
2038 {
2039 $context['to_process'] = array();
2040 $validate_custom = false;
2041 foreach ($_POST['permStatus'] as $path => $status)
2042 {
2043 // Nothing to see here?
2044 if ($status == 'no_change')
2045 continue;
2046 $legal = false;
2047 foreach ($legal_roots as $root)
2048 if (substr($path, 0, strlen($root)) == $root)
2049 $legal = true;
2050
2051 if (!$legal)
2052 continue;
2053
2054 // Check it exists.
2055 if (!file_exists($path))
2056 continue;
2057
2058 if ($status == 'custom')
2059 $validate_custom = true;
2060
2061 // Now add it.
2062 $context['to_process'][$path] = $status;
2063 }
2064 $context['total_items'] = isset($_POST['totalItems']) ? (int) $_POST['totalItems'] : count($context['to_process']);
2065
2066 // Make sure the chmod status is valid?
2067 if ($validate_custom)
2068 {
2069 if (preg_match('~^[4567][4567][4567]$~', $context['custom_value']) == false)
2070 fatal_error($txt['chmod_value_invalid']);
2071 }
2072
2073 // Nothing to do?
2074 if (empty($context['to_process']))
2075 redirectexit('action=admin;area=packages;sa=perms' . (!empty($context['back_look_data']) ? ';back_look=' . base64_encode(serialize($context['back_look_data'])) : '') . ';' . $context['session_var'] . '=' . $context['session_id']);
2076 }
2077 // Should never get here,
2078 else
2079 fatal_lang_error('no_access', false);
2080
2081 // Setup the custom value.
2082 $custom_value = octdec('0' . $context['custom_value']);
2083
2084 // Start processing items.
2085 foreach ($context['to_process'] as $path => $status)
2086 {
2087 if (in_array($status, array('execute', 'writable', 'read')))
2088 package_chmod($path, $status);
2089 elseif ($status == 'custom' && !empty($custom_value))
2090 {
2091 // Use FTP if we have it.
2092 if (!empty($package_ftp) && !empty($_SESSION['pack_ftp']))
2093 {
2094 $ftp_file = strtr($path, array($_SESSION['pack_ftp']['root'] => ''));
2095 $package_ftp->chmod($ftp_file, $custom_value);
2096 }
2097 else
2098 @chmod($path, $custom_value);
2099 }
2100
2101 // This fish is fried...
2102 unset($context['to_process'][$path]);
2103
2104 // See if we're out of time?
2105 if (time() - array_sum(explode(' ', $time_start)) > $timeout_limit)
2106 return false;
2107 }
2108 }
2109 // If predefined this is a little different.
2110 else
2111 {
2112 $context['predefined_type'] = isset($_POST['predefined']) ? $_POST['predefined'] : 'restricted';
2113
2114 $context['total_items'] = isset($_POST['totalItems']) ? (int) $_POST['totalItems'] : 0;
2115 $context['directory_list'] = isset($_POST['dirList']) ? unserialize(base64_decode($_POST['dirList'])) : array();
2116
2117 $context['file_offset'] = isset($_POST['fileOffset']) ? (int) $_POST['fileOffset'] : 0;
2118
2119 // Haven't counted the items yet?
2120 if (empty($context['total_items']))
2121 {
2122 function count_directories__recursive($dir)
2123 {
2124 global $context;
2125
2126 $count = 0;
2127 $dh = @opendir($dir);
2128 while ($entry = readdir($dh))
2129 {
2130 if ($entry != '.' && $entry != '..' && is_dir($dir . '/' . $entry))
2131 {
2132 $context['directory_list'][$dir . '/' . $entry] = 1;
2133 $count++;
2134 $count += count_directories__recursive($dir . '/' . $entry);
2135 }
2136 }
2137 closedir($dh);
2138
2139 return $count;
2140 }
2141
2142 foreach ($context['file_tree'] as $path => $data)
2143 {
2144 if (is_dir($path))
2145 {
2146 $context['directory_list'][$path] = 1;
2147 $context['total_items'] += count_directories__recursive($path);
2148 $context['total_items']++;
2149 }
2150 }
2151 }
2152
2153 // Have we built up our list of special files?
2154 if (!isset($_POST['specialFiles']) && $context['predefined_type'] != 'free')
2155 {
2156 $context['special_files'] = array();
2157 function build_special_files__recursive($path, &$data)
2158 {
2159 global $context;
2160
2161 if (!empty($data['writable_on']))
2162 if ($context['predefined_type'] == 'standard' || $data['writable_on'] == 'restrictive')
2163 $context['special_files'][$path] = 1;
2164
2165 if (!empty($data['contents']))
2166 foreach ($data['contents'] as $name => $contents)
2167 build_special_files__recursive($path . '/' . $name, $contents);
2168 }
2169
2170 foreach ($context['file_tree'] as $path => $data)
2171 build_special_files__recursive($path, $data);
2172 }
2173 // Free doesn't need special files.
2174 elseif ($context['predefined_type'] == 'free')
2175 $context['special_files'] = array();
2176 else
2177 $context['special_files'] = unserialize(base64_decode($_POST['specialFiles']));
2178
2179 // Now we definitely know where we are, we need to go through again doing the chmod!
2180 foreach ($context['directory_list'] as $path => $dummy)
2181 {
2182 // Do the contents of the directory first.
2183 $dh = @opendir($path);
2184 $file_count = 0;
2185 $dont_chmod = false;
2186 while ($entry = readdir($dh))
2187 {
2188 $file_count++;
2189 // Actually process this file?
2190 if (!$dont_chmod && !is_dir($path . '/' . $entry) && (empty($context['file_offset']) || $context['file_offset'] < $file_count))
2191 {
2192 $status = $context['predefined_type'] == 'free' || isset($context['special_files'][$path . '/' . $entry]) ? 'writable' : 'execute';
2193 package_chmod($path . '/' . $entry, $status);
2194 }
2195
2196 // See if we're out of time?
2197 if (!$dont_chmod && time() - array_sum(explode(' ', $time_start)) > $timeout_limit)
2198 {
2199 $dont_chmod = true;
2200 // Don't do this again.
2201 $context['file_offset'] = $file_count;
2202 }
2203 }
2204 closedir($dh);
2205
2206 // If this is set it means we timed out half way through.
2207 if ($dont_chmod)
2208 {
2209 $context['total_files'] = $file_count;
2210 return false;
2211 }
2212
2213 // Do the actual directory.
2214 $status = $context['predefined_type'] == 'free' || isset($context['special_files'][$path]) ? 'writable' : 'execute';
2215 package_chmod($path, $status);
2216
2217 // We've finished the directory so no file offset, and no record.
2218 $context['file_offset'] = 0;
2219 unset($context['directory_list'][$path]);
2220
2221 // See if we're out of time?
2222 if (time() - array_sum(explode(' ', $time_start)) > $timeout_limit)
2223 return false;
2224 }
2225 }
2226
2227 // If we're here we are done!
2228 redirectexit('action=admin;area=packages;sa=perms' . (!empty($context['back_look_data']) ? ';back_look=' . base64_encode(serialize($context['back_look_data'])) : '') . ';' . $context['session_var'] . '=' . $context['session_id']);
2229 }
2230
2231 // Test an FTP connection.
2232 function PackageFTPTest()
2233 {
2234 global $context, $txt, $package_ftp;
2235
2236 checkSession('get');
2237
2238 // Try to make the FTP connection.
2239 create_chmod_control(array(), array('force_find_error' => true));
2240
2241 // Deal with the template stuff.
2242 loadTemplate('Xml');
2243 $context['sub_template'] = 'generic_xml';
2244 $context['template_layers'] = array();
2245
2246 // Define the return data, this is simple.
2247 $context['xml_data'] = array(
2248 'results' => array(
2249 'identifier' => 'result',
2250 'children' => array(
2251 array(
2252 'attributes' => array(
2253 'success' => !empty($package_ftp) ? 1 : 0,
2254 ),
2255 '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']),
2256 ),
2257 ),
2258 ),
2259 );
2260 }
2261
2262 ?>