PageRenderTime 74ms CodeModel.GetById 28ms RepoModel.GetById 0ms app.codeStats 1ms

/forum/Sources/Subs-Package.php

https://github.com/leftnode/nooges.com
PHP | 2950 lines | 2167 code | 379 blank | 404 comment | 615 complexity | 82c4eea80a692588747286a46af49446 MD5 | raw file

Large files files are truncated, but you can click here to view the full file

  1. <?php
  2. /**********************************************************************************
  3. * Subs-Package.php *
  4. ***********************************************************************************
  5. * SMF: Simple Machines Forum *
  6. * Open-Source Project Inspired by Zef Hemel (zef@zefhemel.com) *
  7. * =============================================================================== *
  8. * Software Version: SMF 2.0 RC2 *
  9. * Software by: Simple Machines (http://www.simplemachines.org) *
  10. * Copyright 2006-2009 by: Simple Machines LLC (http://www.simplemachines.org) *
  11. * 2001-2006 by: Lewis Media (http://www.lewismedia.com) *
  12. * Support, News, Updates at: http://www.simplemachines.org *
  13. ***********************************************************************************
  14. * This program is free software; you may redistribute it and/or modify it under *
  15. * the terms of the provided license as published by Simple Machines LLC. *
  16. * *
  17. * This program is distributed in the hope that it is and will be useful, but *
  18. * WITHOUT ANY WARRANTIES; without even any implied warranty of MERCHANTABILITY *
  19. * or FITNESS FOR A PARTICULAR PURPOSE. *
  20. * *
  21. * See the "license.txt" file for details of the Simple Machines license. *
  22. * The latest version can always be found at http://www.simplemachines.org. *
  23. **********************************************************************************/
  24. if (!defined('SMF'))
  25. die('Hacking attempt...');
  26. /* This file's central purpose of existence is that of making the package
  27. manager work nicely. It contains functions for handling tar.gz and zip
  28. files, as well as a simple xml parser to handle the xml package stuff.
  29. Not to mention a few functions to make file handling easier.
  30. array read_tgz_file(string filename, string destination,
  31. bool single_file = false, bool overwrite = false, array files_to_extract = null)
  32. - reads a .tar.gz file, filename, in and extracts file(s) from it.
  33. - essentially just a shortcut for read_tgz_data().
  34. array read_tgz_data(string data, string destination,
  35. bool single_file = false, bool overwrite = false, array files_to_extract = null)
  36. - extracts a file or files from the .tar.gz contained in data.
  37. - detects if the file is really a .zip file, and if so returns the
  38. result of read_zip_data
  39. - if destination is null, returns a list of files in the archive.
  40. - if single_file is true, returns the contents of the file specified
  41. by destination, if it exists, or false.
  42. - if single_file is true, destination can start with * and / to
  43. signify that the file may come from any directory.
  44. - destination should not begin with a / if single_file is true.
  45. - overwrites existing files with newer modification times if and
  46. only if overwrite is true.
  47. - creates the destination directory if it doesn't exist, and is
  48. is specified.
  49. - requires zlib support be built into PHP.
  50. - returns an array of the files extracted.
  51. - if files_to_extract is not equal to null only extracts file within this array.
  52. array read_zip_data(string data, string destination,
  53. bool single_file = false, bool overwrite = false, array files_to_extract = null)
  54. - extracts a file or files from the .zip contained in data.
  55. - if destination is null, returns a list of files in the archive.
  56. - if single_file is true, returns the contents of the file specified
  57. by destination, if it exists, or false.
  58. - if single_file is true, destination can start with * and / to
  59. signify that the file may come from any directory.
  60. - destination should not begin with a / if single_file is true.
  61. - overwrites existing files with newer modification times if and
  62. only if overwrite is true.
  63. - creates the destination directory if it doesn't exist, and is
  64. is specified.
  65. - requires zlib support be built into PHP.
  66. - returns an array of the files extracted.
  67. - if files_to_extract is not equal to null only extracts file within this array.
  68. bool url_exists(string url)
  69. - checks to see if url is valid, and returns a 200 status code.
  70. - will return false if the file is "moved permanently" or similar.
  71. - returns true if the remote url exists.
  72. array loadInstalledPackages()
  73. - loads and returns an array of installed packages.
  74. - gets this information from Packages/installed.list.
  75. - returns the array of data.
  76. array getPackageInfo(string filename)
  77. - loads a package's information and returns a representative array.
  78. - expects the file to be a package in Packages/.
  79. - returns a error string if the package-info is invalid.
  80. - returns a basic array of id, version, filename, and similar
  81. information.
  82. - in the array returned, an xmlArray is available in 'xml'.
  83. void packageRequireFTP(string destination_url, array files = none, bool return = false)
  84. // !!!
  85. array parsePackageInfo(xmlArray &package, bool testing_only = true,
  86. string method = 'install', string previous_version = '')
  87. - parses the actions in package-info.xml files from packages.
  88. - package should be an xmlArray with package-info as its base.
  89. - testing_only should be true if the package should not actually be
  90. applied.
  91. - method is upgrade, install, or uninstall. Its default is install.
  92. - previous_version should be set to the previous installed version
  93. of this package, if any.
  94. - does not handle failure terribly well; testing first is always
  95. better.
  96. - returns an array of those changes made.
  97. bool matchPackageVersion(string version, string versions)
  98. - checks if version matches any of the versions in versions.
  99. - supports comma separated version numbers, with or without
  100. whitespace.
  101. - supports lower and upper bounds. (1.0-1.2)
  102. - returns true if the version matched.
  103. string parse_path(string path)
  104. - parses special identifiers out of the specified path.
  105. - returns the parsed path.
  106. void deltree(string path, bool delete_directory = true)
  107. - deletes a directory, and all the files and direcories inside it.
  108. - requires access to delete these files.
  109. bool mktree(string path, int mode)
  110. - creates the specified tree structure with the mode specified.
  111. - creates every directory in path until it finds one that already
  112. exists.
  113. - returns true if successful, false otherwise.
  114. void copytree(string source, string destination)
  115. - copies one directory structure over to another.
  116. - requires the destination to be writable.
  117. void listtree(string path, string sub_path = none)
  118. // !!!
  119. array parseModification(string file, bool testing = true, bool undo = false, array theme_paths = array())
  120. - parses a xml-style modification file (file).
  121. - testing tells it the modifications shouldn't actually be saved.
  122. - undo specifies that the modifications the file requests should be
  123. undone; this doesn't work with everything (regular expressions.)
  124. - returns an array of those changes made.
  125. array parseBoardMod(string file, bool testing = true, bool undo = false, array theme_paths = array())
  126. - parses a boardmod-style modification file (file).
  127. - testing tells it the modifications shouldn't actually be saved.
  128. - undo specifies that the modifications the file requests should be
  129. undone.
  130. - returns an array of those changes made.
  131. // !!!
  132. int package_put_contents(string filename, string data)
  133. - writes data to a file, almost exactly like the file_put_contents()
  134. function.
  135. - uses FTP to create/chmod the file when necessary and available.
  136. - uses text mode for text mode file extensions.
  137. - returns the number of bytes written.
  138. void package_chmod(string filename)
  139. // !!!
  140. string package_crypt(string password)
  141. // !!!
  142. string fetch_web_data(string url, string post_data = '',
  143. bool keep_alive = false)
  144. // !!!
  145. Creating your own package server:
  146. ---------------------------------------------------------------------------
  147. // !!!
  148. Creating your own package:
  149. ---------------------------------------------------------------------------
  150. // !!!
  151. */
  152. // Get the data from the file and extract it.
  153. function read_tgz_file($gzfilename, $destination, $single_file = false, $overwrite = false, $files_to_extract = null)
  154. {
  155. if (substr($gzfilename, 0, 7) == 'http://')
  156. {
  157. $data = fetch_web_data($gzfilename);
  158. if ($data === false)
  159. return false;
  160. }
  161. else
  162. {
  163. $data = @file_get_contents($gzfilename);
  164. if ($data === false)
  165. return false;
  166. }
  167. return read_tgz_data($data, $destination, $single_file, $overwrite, $files_to_extract);
  168. }
  169. // Extract tar.gz data. If destination is null, return a listing.
  170. function read_tgz_data($data, $destination, $single_file = false, $overwrite = false, $files_to_extract = null)
  171. {
  172. // This function sorta needs gzinflate!
  173. if (!function_exists('gzinflate'))
  174. fatal_lang_error('package_no_zlib', 'critical');
  175. umask(0);
  176. if (!$single_file && $destination !== null && !file_exists($destination))
  177. mktree($destination, 0777);
  178. // No signature?
  179. if (strlen($data) < 2)
  180. return false;
  181. $id = unpack('H2a/H2b', substr($data, 0, 2));
  182. if (strtolower($id['a'] . $id['b']) != '1f8b')
  183. {
  184. // Okay, this ain't no tar.gz, but maybe it's a zip file.
  185. if (substr($data, 0, 2) == 'PK')
  186. return read_zip_data($data, $destination, $single_file, $overwrite, $files_to_extract);
  187. else
  188. return false;
  189. }
  190. $flags = unpack('Ct/Cf', substr($data, 2, 2));
  191. // Not deflate!
  192. if ($flags['t'] != 8)
  193. return false;
  194. $flags = $flags['f'];
  195. $offset = 10;
  196. $octdec = array('mode', 'uid', 'gid', 'size', 'mtime', 'checksum', 'type');
  197. // "Read" the filename and comment. // !!! Might be mussed.
  198. if ($flags & 12)
  199. {
  200. while ($flags & 8 && $data{$offset++} != "\0")
  201. continue;
  202. while ($flags & 4 && $data{$offset++} != "\0")
  203. continue;
  204. }
  205. $crc = unpack('Vcrc32/Visize', substr($data, strlen($data) - 8, 8));
  206. $data = @gzinflate(substr($data, $offset, strlen($data) - 8 - $offset));
  207. // smf_crc32 and crc32 may not return the same results, so we accept either.
  208. if ($crc['crc32'] != smf_crc32($data) && $crc['crc32'] != crc32($data))
  209. return false;
  210. $blocks = strlen($data) / 512 - 1;
  211. $offset = 0;
  212. $return = array();
  213. while ($offset < $blocks)
  214. {
  215. $header = substr($data, $offset << 9, 512);
  216. $current = unpack('a100filename/a8mode/a8uid/a8gid/a12size/a12mtime/a8checksum/a1type/a100linkname/a6magic/a2version/a32uname/a32gname/a8devmajor/a8devminor/a155path', $header);
  217. foreach ($current as $k => $v)
  218. {
  219. if (in_array($k, $octdec))
  220. $current[$k] = octdec(trim($v));
  221. else
  222. $current[$k] = trim($v);
  223. }
  224. $checksum = 256;
  225. for ($i = 0; $i < 148; $i++)
  226. $checksum += ord($header{$i});
  227. for ($i = 156; $i < 512; $i++)
  228. $checksum += ord($header{$i});
  229. if ($current['checksum'] != $checksum)
  230. return $return;
  231. $size = ceil($current['size'] / 512);
  232. $current['data'] = substr($data, ++$offset << 9, $current['size']);
  233. $offset += $size;
  234. // Not a directory and doesn't exist already...
  235. if (substr($current['filename'], -1, 1) != '/' && !file_exists($destination . '/' . $current['filename']))
  236. $write_this = true;
  237. // File exists... check if it is newer.
  238. elseif (substr($current['filename'], -1, 1) != '/')
  239. $write_this = $overwrite || filemtime($destination . '/' . $current['filename']) < $current['mtime'];
  240. // Folder... create.
  241. elseif ($destination !== null && !$single_file)
  242. {
  243. // Protect from accidental parent directory writing...
  244. $current['filename'] = strtr($current['filename'], array('../' => '', '/..' => ''));
  245. if (!file_exists($destination . '/' . $current['filename']))
  246. mktree($destination . '/' . $current['filename'], 0777);
  247. $write_this = false;
  248. }
  249. else
  250. $write_this = false;
  251. if ($write_this && $destination !== null)
  252. {
  253. if (strpos($current['filename'], '/') !== false && !$single_file)
  254. mktree($destination . '/' . dirname($current['filename']), 0777);
  255. // Is this the file we're looking for?
  256. if ($single_file && ($destination == $current['filename'] || $destination == '*/' . basename($current['filename'])))
  257. return $current['data'];
  258. // If we're looking for another file, keep going.
  259. elseif ($single_file)
  260. continue;
  261. // Looking for restricted files?
  262. elseif ($files_to_extract !== null && !in_array($current['filename'], $files_to_extract))
  263. continue;
  264. package_put_contents($destination . '/' . $current['filename'], $current['data']);
  265. }
  266. if (substr($current['filename'], -1, 1) != '/')
  267. $return[] = array(
  268. 'filename' => $current['filename'],
  269. 'md5' => md5($current['data']),
  270. 'preview' => substr($current['data'], 0, 100),
  271. 'size' => $current['size'],
  272. 'skipped' => false
  273. );
  274. }
  275. if ($destination !== null && !$single_file)
  276. package_flush_cache();
  277. if ($single_file)
  278. return false;
  279. else
  280. return $return;
  281. }
  282. // Extract zip data. If destination is null, return a listing.
  283. function read_zip_data($data, $destination, $single_file = false, $overwrite = false, $files_to_extract = null)
  284. {
  285. umask(0);
  286. if ($destination !== null && !file_exists($destination) && !$single_file)
  287. mktree($destination, 0777);
  288. // Look for the PK header...
  289. if (substr($data, 0, 2) != 'PK')
  290. return false;
  291. // Find the central whosamawhatsit at the end; if there's a comment it's a pain.
  292. if (substr($data, -22, 4) == 'PK' . chr(5) . chr(6))
  293. $p = -22;
  294. else
  295. {
  296. // Have to find where the comment begins, ugh.
  297. for ($p = -22; $p > -strlen($data); $p--)
  298. {
  299. if (substr($data, $p, 4) == 'PK' . chr(5) . chr(6))
  300. break;
  301. }
  302. }
  303. $return = array();
  304. // Get the basic zip file info.
  305. $zip_info = unpack('vfiles/Vsize/Voffset', substr($data, $p + 10, 10));
  306. $p = $zip_info['offset'];
  307. for ($i = 0; $i < $zip_info['files']; $i++)
  308. {
  309. // Make sure this is a file entry...
  310. if (substr($data, $p, 4) != 'PK' . chr(1) . chr(2))
  311. return false;
  312. // Get all the important file information.
  313. $file_info = unpack('Vcrc/Vcompressed_size/Vsize/vfilename_len/vextra_len/vcomment_len/vdisk/vinternal/Vexternal/Voffset', substr($data, $p + 16, 30));
  314. $file_info['filename'] = substr($data, $p + 46, $file_info['filename_len']);
  315. // Skip all the information we don't care about anyway.
  316. $p += 46 + $file_info['filename_len'] + $file_info['extra_len'] + $file_info['comment_len'];
  317. // If this is a file, and it doesn't exist.... happy days!
  318. if (substr($file_info['filename'], -1, 1) != '/' && !file_exists($destination . '/' . $file_info['filename']))
  319. $write_this = true;
  320. // If the file exists, we may not want to overwrite it.
  321. elseif (substr($file_info['filename'], -1, 1) != '/')
  322. $write_this = $overwrite;
  323. // This is a directory, so we're gonna want to create it. (probably...)
  324. elseif ($destination !== null && !$single_file)
  325. {
  326. // Just a little accident prevention, don't mind me.
  327. $file_info['filename'] = strtr($file_info['filename'], array('../' => '', '/..' => ''));
  328. if (!file_exists($destination . '/' . $file_info['filename']))
  329. mktree($destination . '/' . $file_info['filename'], 0777);
  330. $write_this = false;
  331. }
  332. else
  333. $write_this = false;
  334. // Check that the data is there and does exist.
  335. if (substr($data, $file_info['offset'], 4) != 'PK' . chr(3) . chr(4))
  336. return false;
  337. // Get the actual compressed data.
  338. $file_info['data'] = substr($data, $file_info['offset'] + 30 + $file_info['filename_len'] + $file_info['extra_len'], $file_info['compressed_size']);
  339. // Only inflate it if we need to ;).
  340. if ($file_info['compressed_size'] != $file_info['size'])
  341. $file_info['data'] = @gzinflate($file_info['data']);
  342. // Okay! We can write this file, looks good from here...
  343. if ($write_this && $destination !== null)
  344. {
  345. if (strpos($file_info['filename'], '/') !== false && !$single_file)
  346. mktree($destination . '/' . dirname($file_info['filename']), 0777);
  347. // If we're looking for a specific file, and this is it... ka-bam, baby.
  348. if ($single_file && ($destination == $file_info['filename'] || $destination == '*/' . basename($file_info['filename'])))
  349. return $file_info['data'];
  350. // Oh? Another file. Fine. You don't like this file, do you? I know how it is. Yeah... just go away. No, don't apologize. I know this file's just not *good enough* for you.
  351. elseif ($single_file)
  352. continue;
  353. // Don't really want this?
  354. elseif ($files_to_extract !== null && !in_array($file_info['filename'], $files_to_extract))
  355. continue;
  356. package_put_contents($destination . '/' . $file_info['filename'], $file_info['data']);
  357. }
  358. if (substr($file_info['filename'], -1, 1) != '/')
  359. $return[] = array(
  360. 'filename' => $file_info['filename'],
  361. 'md5' => md5($file_info['data']),
  362. 'preview' => substr($file_info['data'], 0, 100),
  363. 'size' => $file_info['size'],
  364. 'skipped' => false
  365. );
  366. }
  367. if ($destination !== null && !$single_file)
  368. package_flush_cache();
  369. if ($single_file)
  370. return false;
  371. else
  372. return $return;
  373. }
  374. // Checks the existence of a remote file since file_exists() does not do remote.
  375. function url_exists($url)
  376. {
  377. $a_url = parse_url($url);
  378. if (!isset($a_url['scheme']))
  379. return false;
  380. // Attempt to connect...
  381. $temp = '';
  382. $fid = fsockopen($a_url['host'], !isset($a_url['port']) ? 80 : $a_url['port'], $temp, $temp, 8);
  383. if (!$fid)
  384. return false;
  385. fputs($fid, 'HEAD ' . $a_url['path'] . ' HTTP/1.0' . "\r\n" . 'Host: ' . $a_url['host'] . "\r\n\r\n");
  386. $head = fread($fid, 1024);
  387. fclose($fid);
  388. return preg_match('~^HTTP/.+\s+(20[01]|30[127])~i', $head) == 1;
  389. }
  390. // Load the installed packages.
  391. function loadInstalledPackages()
  392. {
  393. global $boarddir, $smcFunc;
  394. // First, check that the database is valid, installed.list is still king.
  395. $install_file = implode('', file($boarddir . '/Packages/installed.list'));
  396. if (trim($install_file) == '')
  397. {
  398. $smcFunc['db_query']('', '
  399. UPDATE {db_prefix}log_packages
  400. SET install_state = {int:not_installed}',
  401. array(
  402. 'not_installed' => 0,
  403. )
  404. );
  405. // Don't have anything left, so send an empty array.
  406. return array();
  407. }
  408. // Load the packages from the database - note this is ordered by install time to ensure latest package uninstalled first.
  409. $request = $smcFunc['db_query']('', '
  410. SELECT id_install, package_id, filename, name, version
  411. FROM {db_prefix}log_packages
  412. WHERE install_state != {int:not_installed}
  413. ORDER BY time_installed DESC',
  414. array(
  415. 'not_installed' => 0,
  416. )
  417. );
  418. $installed = array();
  419. $found = array();
  420. while ($row = $smcFunc['db_fetch_assoc']($request))
  421. {
  422. // Already found this? If so don't add it twice!
  423. if (in_array($row['package_id'], $found))
  424. continue;
  425. $found[] = $row['package_id'];
  426. $installed[] = array(
  427. 'id' => $row['id_install'],
  428. 'name' => $row['name'],
  429. 'filename' => $row['filename'],
  430. 'package_id' => $row['package_id'],
  431. 'version' => $row['version'],
  432. );
  433. }
  434. $smcFunc['db_free_result']($request);
  435. return $installed;
  436. }
  437. function getPackageInfo($gzfilename)
  438. {
  439. global $boarddir;
  440. // Extract package-info.xml from downloaded file. (*/ is used because it could be in any directory.)
  441. if (strpos($gzfilename, 'http://') !== false)
  442. $packageInfo = read_tgz_data(fetch_web_data($gzfilename, '', true), '*/package-info.xml', true);
  443. else
  444. {
  445. if (!file_exists($boarddir . '/Packages/' . $gzfilename))
  446. return 'package_get_error_not_found';
  447. if (is_file($boarddir . '/Packages/' . $gzfilename))
  448. $packageInfo = read_tgz_file($boarddir . '/Packages/' . $gzfilename, '*/package-info.xml', true);
  449. elseif (file_exists($boarddir . '/Packages/' . $gzfilename . '/package-info.xml'))
  450. $packageInfo = file_get_contents($boarddir . '/Packages/' . $gzfilename . '/package-info.xml');
  451. else
  452. return 'package_get_error_missing_xml';
  453. }
  454. // Nothing?
  455. if (empty($packageInfo))
  456. return 'package_get_error_is_zero';
  457. // Parse package-info.xml into an xmlArray.
  458. loadClassFile('Class-Package.php');
  459. $packageInfo = new xmlArray($packageInfo);
  460. // !!! Error message of some sort?
  461. if (!$packageInfo->exists('package-info[0]'))
  462. return 'package_get_error_packageinfo_corrupt';
  463. $packageInfo = $packageInfo->path('package-info[0]');
  464. $package = $packageInfo->to_array();
  465. $package['xml'] = $packageInfo;
  466. $package['filename'] = $gzfilename;
  467. if (!isset($package['type']))
  468. $package['type'] = 'modification';
  469. return $package;
  470. }
  471. // Create a chmod control for chmoding files.
  472. function create_chmod_control($chmodFiles = array(), $chmodOptions = array(), $restore_write_status = false)
  473. {
  474. global $context, $modSettings, $package_ftp, $boarddir, $txt, $sourcedir, $scripturl;
  475. // If we're restoring the status of existing files prepare the data.
  476. if ($restore_write_status && isset($_SESSION['pack_ftp']) && !empty($_SESSION['pack_ftp']['original_perms']))
  477. {
  478. function list_restoreFiles($dummy1, $dummy2, $dummy3, $do_change)
  479. {
  480. global $txt;
  481. $restore_files = array();
  482. foreach ($_SESSION['pack_ftp']['original_perms'] as $file => $perms)
  483. {
  484. // Check the file still exists, and the permissions were indeed different than now.
  485. $file_permissions = @fileperms($file);
  486. if (!file_exists($file) || $file_permissions == $perms)
  487. {
  488. unset($_SESSION['pack_ftp']['original_perms'][$file]);
  489. continue;
  490. }
  491. // Are we wanting to change the permission?
  492. if ($do_change && isset($_POST['restore_files']) && in_array($file, $_POST['restore_files']))
  493. {
  494. // Use FTP if we have it.
  495. if (!empty($package_ftp))
  496. {
  497. $ftp_file = strtr($file, array($_SESSION['pack_ftp']['root'] => ''));
  498. $package_ftp->chmod($ftp_file, $perms);
  499. }
  500. else
  501. @chmod($file, $perms);
  502. $new_permissions = @fileperms($file);
  503. $result = $new_permissions == $perms ? 'success' : 'failure';
  504. unset($_SESSION['pack_ftp']['original_perms'][$file]);
  505. }
  506. elseif ($do_change)
  507. {
  508. $new_permissions = '';
  509. $result = 'skipped';
  510. unset($_SESSION['pack_ftp']['original_perms'][$file]);
  511. }
  512. // Record the results!
  513. $restore_files[] = array(
  514. 'path' => $file,
  515. 'old_perms_raw' => $perms,
  516. 'old_perms' => substr(sprintf('%o', $perms), -4),
  517. 'cur_perms' => substr(sprintf('%o', $file_permissions), -4),
  518. 'new_perms' => isset($new_permissions) ? substr(sprintf('%o', $new_permissions), -4) : '',
  519. 'result' => isset($result) ? $result : '',
  520. 'writable_message' => '<span style="color: ' . (@is_writable($file) ? 'green' : 'red') . '">' . (@is_writable($file) ? $txt['package_file_perms_writable'] : $txt['package_file_perms_not_writable']) . '</span>',
  521. );
  522. }
  523. return $restore_files;
  524. }
  525. $listOptions = array(
  526. 'id' => 'restore_file_permissions',
  527. 'title' => $txt['package_restore_permissions'],
  528. 'get_items' => array(
  529. 'function' => 'list_restoreFiles',
  530. 'params' => array(
  531. !empty($_POST['restore_perms']),
  532. ),
  533. ),
  534. 'columns' => array(
  535. 'path' => array(
  536. 'header' => array(
  537. 'value' => $txt['package_restore_permissions_filename'],
  538. ),
  539. 'data' => array(
  540. 'db' => 'path',
  541. 'class' => 'smalltext',
  542. ),
  543. ),
  544. 'old_perms' => array(
  545. 'header' => array(
  546. 'value' => $txt['package_restore_permissions_orig_status'],
  547. ),
  548. 'data' => array(
  549. 'db' => 'old_perms',
  550. 'class' => 'smalltext',
  551. ),
  552. ),
  553. 'cur_perms' => array(
  554. 'header' => array(
  555. 'value' => $txt['package_restore_permissions_cur_status'],
  556. ),
  557. 'data' => array(
  558. 'function' => create_function('$rowData', '
  559. global $txt;
  560. $formatTxt = $rowData[\'result\'] == \'\' || $rowData[\'result\'] == \'skipped\' ? $txt[\'package_restore_permissions_pre_change\'] : $txt[\'package_restore_permissions_post_change\'];
  561. return sprintf($formatTxt, $rowData[\'cur_perms\'], $rowData[\'new_perms\'], $rowData[\'writable_message\']);
  562. '),
  563. 'class' => 'smalltext',
  564. ),
  565. ),
  566. 'check' => array(
  567. 'header' => array(
  568. 'value' => '<input type="checkbox" onclick="invertAll(this, this.form);" class="input_check" />',
  569. ),
  570. 'data' => array(
  571. 'sprintf' => array(
  572. 'format' => '<input type="checkbox" name="restore_files[]" value="%1$s" class="input_check" />',
  573. 'params' => array(
  574. 'path' => false,
  575. ),
  576. ),
  577. 'style' => 'text-align: center',
  578. ),
  579. ),
  580. 'result' => array(
  581. 'header' => array(
  582. 'value' => $txt['package_restore_permissions_result'],
  583. ),
  584. 'data' => array(
  585. 'function' => create_function('$rowData', '
  586. global $txt;
  587. return $txt[\'package_restore_permissions_action_\' . $rowData[\'result\']];
  588. '),
  589. 'class' => 'smalltext',
  590. ),
  591. ),
  592. ),
  593. 'form' => array(
  594. 'href' => !empty($chmodOptions['destination_url']) ? $chmodOptions['destination_url'] : $scripturl . '?action=admin;area=packages;sa=perms;restore;' . $context['session_var'] . '=' . $context['session_id'],
  595. ),
  596. 'additional_rows' => array(
  597. array(
  598. 'position' => 'below_table_data',
  599. 'value' => '<input type="submit" name="restore_perms" value="' . $txt['package_restore_permissions_restore'] . '" class="button_submit" />',
  600. 'class' => 'titlebg',
  601. 'style' => 'text-align: right;',
  602. ),
  603. array(
  604. 'position' => 'after_title',
  605. 'value' => '<span class="smalltext">' . $txt['package_restore_permissions_desc'] . '</span>',
  606. 'class' => 'windowbg2',
  607. ),
  608. ),
  609. );
  610. // Work out what columns and the like to show.
  611. if (!empty($_POST['restore_perms']))
  612. {
  613. $listOptions['additional_rows'][1]['value'] = sprintf($txt['package_restore_permissions_action_done'], $scripturl . '?action=admin;area=packages;sa=perms;' . $context['session_var'] . '=' . $context['session_id']);
  614. unset($listOptions['columns']['check'], $listOptions['form'], $listOptions['additional_rows'][0]);
  615. $context['sub_template'] = 'show_list';
  616. $context['default_list'] = 'restore_file_permissions';
  617. }
  618. else
  619. {
  620. unset($listOptions['columns']['result']);
  621. }
  622. // Create the list for display.
  623. require_once($sourcedir . '/Subs-List.php');
  624. createList($listOptions);
  625. // If we just restored permissions then whereever we are, we are now done and dusted.
  626. if (!empty($_POST['restore_perms']))
  627. obExit();
  628. }
  629. // Otherwise, it's entirely irrelevant?
  630. elseif ($restore_write_status)
  631. return true;
  632. // This is where we report what we got up to.
  633. $return_data = array(
  634. 'files' => array(
  635. 'writable' => array(),
  636. 'notwritable' => array(),
  637. ),
  638. );
  639. // If we have some FTP information already, then let's assume it was required and try to get ourselves connected.
  640. if (!empty($_SESSION['pack_ftp']['connected']))
  641. {
  642. // Load the file containing the ftp_connection class.
  643. loadClassFile('Class-Package.php');
  644. $package_ftp = new ftp_connection($_SESSION['pack_ftp']['server'], $_SESSION['pack_ftp']['port'], $_SESSION['pack_ftp']['username'], package_crypt($_SESSION['pack_ftp']['password']));
  645. }
  646. // Just got a submission did we?
  647. if (empty($package_ftp) && isset($_POST['ftp_username']))
  648. {
  649. loadClassFile('Class-Package.php');
  650. $ftp = new ftp_connection($_POST['ftp_server'], $_POST['ftp_port'], $_POST['ftp_username'], $_POST['ftp_password']);
  651. // We're connected, jolly good!
  652. if ($ftp->error === false)
  653. {
  654. // Common mistake, so let's try to remedy it...
  655. if (!$ftp->chdir($_POST['ftp_path']))
  656. {
  657. $ftp_error = $ftp->last_message;
  658. $ftp->chdir(preg_replace('~^/home[2]?/[^/]+?~', '', $_POST['ftp_path']));
  659. }
  660. if (!in_array($_POST['ftp_path'], array('', '/')))
  661. {
  662. $ftp_root = strtr($boarddir, array($_POST['ftp_path'] => ''));
  663. if (substr($ftp_root, -1) == '/' && ($_POST['ftp_path'] == '' || substr($_POST['ftp_path'], 0, 1) == '/'))
  664. $ftp_root = substr($ftp_root, 0, -1);
  665. }
  666. else
  667. $ftp_root = $boarddir;
  668. $_SESSION['pack_ftp'] = array(
  669. 'server' => $_POST['ftp_server'],
  670. 'port' => $_POST['ftp_port'],
  671. 'username' => $_POST['ftp_username'],
  672. 'password' => package_crypt($_POST['ftp_password']),
  673. 'path' => $_POST['ftp_path'],
  674. 'root' => $ftp_root,
  675. 'connected' => true,
  676. );
  677. if (!isset($modSettings['package_path']) || $modSettings['package_path'] != $_POST['ftp_path'])
  678. updateSettings(array('package_path' => $_POST['ftp_path']));
  679. // This is now the primary connection.
  680. $package_ftp = $ftp;
  681. }
  682. }
  683. // Now try to simply make the files writable, with whatever we might have.
  684. if (!empty($chmodFiles))
  685. {
  686. foreach ($chmodFiles as $k => $file)
  687. {
  688. // Sometimes this can somehow happen maybe?
  689. if (empty($file))
  690. unset($chmodFiles[$k]);
  691. // Already writable?
  692. elseif (@is_writable($file))
  693. $return_data['files']['writable'][] = $file;
  694. else
  695. {
  696. // Now try to change that.
  697. $return_data['files'][package_chmod($file, 'writable', true) ? 'writable' : 'notwritable'][] = $file;
  698. }
  699. }
  700. }
  701. // Have we still got nasty files which ain't writable? Dear me we need more FTP good sir.
  702. if (empty($package_ftp) && (!empty($return_data['files']['notwritable']) || !empty($chmodOptions['force_find_error'])))
  703. {
  704. if (!isset($ftp) || $ftp->error !== false)
  705. {
  706. if (!isset($ftp))
  707. {
  708. loadClassFile('Class-Package.php');
  709. $ftp = new ftp_connection(null);
  710. }
  711. elseif ($ftp->error !== false && !isset($ftp_error))
  712. $ftp_error = $ftp->last_message === null ? '' : $ftp->last_message;
  713. list ($username, $detect_path, $found_path) = $ftp->detect_path($boarddir);
  714. if ($found_path)
  715. $_POST['ftp_path'] = $detect_path;
  716. elseif (!isset($_POST['ftp_path']))
  717. $_POST['ftp_path'] = isset($modSettings['package_path']) ? $modSettings['package_path'] : $detect_path;
  718. if (!isset($_POST['ftp_username']))
  719. $_POST['ftp_username'] = $username;
  720. }
  721. $context['package_ftp'] = array(
  722. 'server' => isset($_POST['ftp_server']) ? $_POST['ftp_server'] : (isset($modSettings['package_server']) ? $modSettings['package_server'] : 'localhost'),
  723. 'port' => isset($_POST['ftp_port']) ? $_POST['ftp_port'] : (isset($modSettings['package_port']) ? $modSettings['package_port'] : '21'),
  724. 'username' => isset($_POST['ftp_username']) ? $_POST['ftp_username'] : (isset($modSettings['package_username']) ? $modSettings['package_username'] : ''),
  725. 'path' => $_POST['ftp_path'],
  726. 'error' => empty($ftp_error) ? null : $ftp_error,
  727. 'destination' => !empty($chmodOptions['destination_url']) ? $chmodOptions['destination_url'] : '',
  728. );
  729. // Which files failed?
  730. if (!isset($context['notwritable_files']))
  731. $context['notwritable_files'] = array();
  732. $context['notwritable_files'] = array_merge($context['notwritable_files'], $return_data['files']['notwritable']);
  733. // Sent here to die?
  734. if (!empty($chmodOptions['crash_on_error']))
  735. {
  736. $context['page_title'] = $txt['package_ftp_necessary'];
  737. $context['sub_template'] = 'ftp_required';
  738. obExit();
  739. }
  740. }
  741. return $return_data;
  742. }
  743. function packageRequireFTP($destination_url, $files = null, $return = false)
  744. {
  745. global $context, $modSettings, $package_ftp, $boarddir, $txt;
  746. // Try to make them writable the manual way.
  747. if ($files !== null)
  748. {
  749. foreach ($files as $k => $file)
  750. {
  751. // If this file doesn't exist, then we actually want to look at the directory, no?
  752. if (!file_exists($file))
  753. $file = dirname($file);
  754. // This looks odd, but it's an attempt to work around PHP suExec.
  755. if (!@is_writable($file))
  756. @chmod($file, 0755);
  757. if (!@is_writable($file))
  758. @chmod($file, 0777);
  759. if (!@is_writable(dirname($file)))
  760. @chmod($file, 0755);
  761. if (!@is_writable(dirname($file)))
  762. @chmod($file, 0777);
  763. $fp = is_dir($file) ? @opendir($file) : @fopen($file, 'rb');
  764. if (@is_writable($file) && $fp)
  765. {
  766. unset($files[$k]);
  767. if (!is_dir($file))
  768. fclose($fp);
  769. else
  770. closedir($fp);
  771. }
  772. }
  773. // No FTP required!
  774. if (empty($files))
  775. return array();
  776. }
  777. // They've opted to not use FTP, and try anyway.
  778. if (isset($_SESSION['pack_ftp']) && $_SESSION['pack_ftp'] == false)
  779. {
  780. if ($files === null)
  781. return array();
  782. foreach ($files as $k => $file)
  783. {
  784. // This looks odd, but it's an attempt to work around PHP suExec.
  785. if (!file_exists($file))
  786. {
  787. mktree(dirname($file), 0755);
  788. @touch($file);
  789. @chmod($file, 0755);
  790. }
  791. if (!@is_writable($file))
  792. @chmod($file, 0777);
  793. if (!@is_writable(dirname($file)))
  794. @chmod(dirname($file), 0777);
  795. if (@is_writable($file))
  796. unset($files[$k]);
  797. }
  798. return $files;
  799. }
  800. elseif (isset($_SESSION['pack_ftp']))
  801. {
  802. // Load the file containing the ftp_connection class.
  803. loadClassFile('Class-Package.php');
  804. $package_ftp = new ftp_connection($_SESSION['pack_ftp']['server'], $_SESSION['pack_ftp']['port'], $_SESSION['pack_ftp']['username'], package_crypt($_SESSION['pack_ftp']['password']));
  805. if ($files === null)
  806. return array();
  807. foreach ($files as $k => $file)
  808. {
  809. $ftp_file = strtr($file, array($_SESSION['pack_ftp']['root'] => ''));
  810. // This looks odd, but it's an attempt to work around PHP suExec.
  811. if (!file_exists($file))
  812. {
  813. mktree(dirname($file), 0755);
  814. $package_ftp->create_file($ftp_file);
  815. $package_ftp->chmod($ftp_file, 0755);
  816. }
  817. if (!@is_writable($file))
  818. $package_ftp->chmod($ftp_file, 0777);
  819. if (!@is_writable(dirname($file)))
  820. $package_ftp->chmod(dirname($ftp_file), 0777);
  821. if (@is_writable($file))
  822. unset($files[$k]);
  823. }
  824. return $files;
  825. }
  826. if (isset($_POST['ftp_none']))
  827. {
  828. $_SESSION['pack_ftp'] = false;
  829. $files = packageRequireFTP($destination_url, $files, $return);
  830. return $files;
  831. }
  832. elseif (isset($_POST['ftp_username']))
  833. {
  834. loadClassFile('Class-Package.php');
  835. $ftp = new ftp_connection($_POST['ftp_server'], $_POST['ftp_port'], $_POST['ftp_username'], $_POST['ftp_password']);
  836. if ($ftp->error === false)
  837. {
  838. // Common mistake, so let's try to remedy it...
  839. if (!$ftp->chdir($_POST['ftp_path']))
  840. {
  841. $ftp_error = $ftp->last_message;
  842. $ftp->chdir(preg_replace('~^/home[2]?/[^/]+?~', '', $_POST['ftp_path']));
  843. }
  844. }
  845. }
  846. if (!isset($ftp) || $ftp->error !== false)
  847. {
  848. if (!isset($ftp))
  849. {
  850. loadClassFile('Class-Package.php');
  851. $ftp = new ftp_connection(null);
  852. }
  853. elseif ($ftp->error !== false && !isset($ftp_error))
  854. $ftp_error = $ftp->last_message === null ? '' : $ftp->last_message;
  855. list ($username, $detect_path, $found_path) = $ftp->detect_path($boarddir);
  856. if ($found_path)
  857. $_POST['ftp_path'] = $detect_path;
  858. elseif (!isset($_POST['ftp_path']))
  859. $_POST['ftp_path'] = isset($modSettings['package_path']) ? $modSettings['package_path'] : $detect_path;
  860. if (!isset($_POST['ftp_username']))
  861. $_POST['ftp_username'] = $username;
  862. $context['package_ftp'] = array(
  863. 'server' => isset($_POST['ftp_server']) ? $_POST['ftp_server'] : (isset($modSettings['package_server']) ? $modSettings['package_server'] : 'localhost'),
  864. 'port' => isset($_POST['ftp_port']) ? $_POST['ftp_port'] : (isset($modSettings['package_port']) ? $modSettings['package_port'] : '21'),
  865. 'username' => isset($_POST['ftp_username']) ? $_POST['ftp_username'] : (isset($modSettings['package_username']) ? $modSettings['package_username'] : ''),
  866. 'path' => $_POST['ftp_path'],
  867. 'error' => empty($ftp_error) ? null : $ftp_error,
  868. 'destination' => $destination_url,
  869. );
  870. // If we're returning dump out here.
  871. if ($return)
  872. return $files;
  873. $context['page_title'] = $txt['package_ftp_necessary'];
  874. $context['sub_template'] = 'ftp_required';
  875. obExit();
  876. }
  877. else
  878. {
  879. if (!in_array($_POST['ftp_path'], array('', '/')))
  880. {
  881. $ftp_root = strtr($boarddir, array($_POST['ftp_path'] => ''));
  882. if (substr($ftp_root, -1) == '/' && ($_POST['ftp_path'] == '' || substr($_POST['ftp_path'], 0, 1) == '/'))
  883. $ftp_root = substr($ftp_root, 0, -1);
  884. }
  885. else
  886. $ftp_root = $boarddir;
  887. $_SESSION['pack_ftp'] = array(
  888. 'server' => $_POST['ftp_server'],
  889. 'port' => $_POST['ftp_port'],
  890. 'username' => $_POST['ftp_username'],
  891. 'password' => package_crypt($_POST['ftp_password']),
  892. 'path' => $_POST['ftp_path'],
  893. 'root' => $ftp_root,
  894. );
  895. if (!isset($modSettings['package_path']) || $modSettings['package_path'] != $_POST['ftp_path'])
  896. updateSettings(array('package_path' => $_POST['ftp_path']));
  897. $files = packageRequireFTP($destination_url, $files, $return);
  898. }
  899. return $files;
  900. }
  901. // Parses a package-info.xml file - method can be 'install', 'upgrade', or 'uninstall'.
  902. function parsePackageInfo(&$packageXML, $testing_only = true, $method = 'install', $previous_version = '')
  903. {
  904. global $boarddir, $forum_version, $context, $temp_path, $language;
  905. // Mayday! That action doesn't exist!!
  906. if (empty($packageXML) || !$packageXML->exists($method))
  907. return array();
  908. // We haven't found the package script yet...
  909. $script = false;
  910. $the_version = strtr($forum_version, array('SMF ' => ''));
  911. // Emulation support...
  912. if (!empty($_SESSION['version_emulate']))
  913. $the_version = $_SESSION['version_emulate'];
  914. // Get all the versions of this method and find the right one.
  915. $these_methods = $packageXML->set($method);
  916. foreach ($these_methods as $this_method)
  917. {
  918. // They specified certain versions this part is for.
  919. if ($this_method->exists('@for'))
  920. {
  921. // Don't keep going if this won't work for this version of SMF.
  922. if (!matchPackageVersion($the_version, $this_method->fetch('@for')))
  923. continue;
  924. }
  925. // Upgrades may go from a certain old version of the mod.
  926. if ($method == 'upgrade' && $this_method->exists('@from'))
  927. {
  928. // Well, this is for the wrong old version...
  929. if (!matchPackageVersion($previous_version, $this_method->fetch('@from')))
  930. continue;
  931. }
  932. // We've found it!
  933. $script = $this_method;
  934. break;
  935. }
  936. // Bad news, a matching script wasn't found!
  937. if ($script === false)
  938. return array();
  939. // Find all the actions in this method - in theory, these should only be allowed actions. (* means all.)
  940. $actions = $script->set('*');
  941. $return = array();
  942. $temp_auto = 0;
  943. $temp_path = $boarddir . '/Packages/temp/' . (isset($context['base_path']) ? $context['base_path'] : '');
  944. $context['readmes'] = array();
  945. // This is the testing phase... nothing shall be done yet.
  946. foreach ($actions as $action)
  947. {
  948. $actionType = $action->name();
  949. if ($actionType == 'readme' || $actionType == 'code' || $actionType == 'database' || $actionType == 'modification' || $actionType == 'redirect')
  950. {
  951. // Allow for translated readme files.
  952. if ($actionType == 'readme')
  953. {
  954. if ($action->exists('@lang'))
  955. {
  956. // Auto-select a readme language based on either request variable or current language.
  957. if ((isset($_REQUEST['readme']) && $action->fetch('@lang') == $_REQUEST['readme']) || (!isset($_REQUEST['readme']) && $action->fetch('@lang') == $language))
  958. {
  959. // In case the user put the readme blocks in the wrong order.
  960. if (isset($context['readmes']['selected']) && $context['readmes']['selected'] == 'default')
  961. $context['readmes'][] = 'default';
  962. $context['readmes']['selected'] = htmlspecialchars($action->fetch('@lang'));
  963. }
  964. else
  965. {
  966. // We don't want this readme now, but we'll allow the user to select to read it.
  967. $context['readmes'][] = htmlspecialchars($action->fetch('@lang'));
  968. continue;
  969. }
  970. }
  971. // Fallback readme. Without lang parameter.
  972. else
  973. {
  974. // Already selected a readme.
  975. if (isset($context['readmes']['selected']))
  976. {
  977. $context['readmes'][] = 'default';
  978. continue;
  979. }
  980. else
  981. $context['readmes']['selected'] = 'default';
  982. }
  983. }
  984. // !!! TODO: Make sure the file actually exists? Might not work when testing?
  985. if ($action->exists('@type') && $action->fetch('@type') == 'inline')
  986. {
  987. $filename = $temp_path . '$auto_' . $temp_auto++ . ($actionType == 'readme' || $actionType == 'redirect' ? '.txt' : ($actionType == 'code' || $actionType == 'database' ? '.php' : '.mod'));
  988. package_put_contents($filename, $action->fetch('.'));
  989. $filename = strtr($filename, array($temp_path => ''));
  990. }
  991. else
  992. $filename = $action->fetch('.');
  993. $return[] = array(
  994. 'type' => $actionType,
  995. 'filename' => $filename,
  996. 'description' => '',
  997. 'reverse' => $action->exists('@reverse') && $action->fetch('@reverse') == 'true',
  998. 'boardmod' => $action->exists('@format') && $action->fetch('@format') == 'boardmod',
  999. 'redirect_url' => $action->exists('@url') ? $action->fetch('@url') : '',
  1000. 'redirect_timeout' => $action->exists('@timeout') ? (int) $action->fetch('@timeout') : '',
  1001. 'parse_bbc' => $action->exists('@parsebbc') && $action->fetch('@parsebbc') == 'true',
  1002. 'language' => ($actionType == 'readme' && $action->exists('@lang') && $action->fetch('@lang') == $language) ? $language : '',
  1003. );
  1004. continue;
  1005. }
  1006. elseif ($actionType == 'error')
  1007. {
  1008. $return[] = array(
  1009. 'type' => 'error',
  1010. );
  1011. }
  1012. $this_action = &$return[];
  1013. $this_action = array(
  1014. 'type' => $actionType,
  1015. 'filename' => $action->fetch('@name'),
  1016. 'description' => $action->fetch('.')
  1017. );
  1018. // If there is a destination, make sure it makes sense.
  1019. if (substr($actionType, 0, 6) != 'remove')
  1020. {
  1021. $this_action['unparsed_destination'] = $action->fetch('@destination');
  1022. $this_action['destination'] = parse_path($action->fetch('@destination')) . '/' . basename($this_action['filename']);
  1023. }
  1024. else
  1025. {
  1026. $this_action['unparsed_filename'] = $this_action['filename'];
  1027. $this_action['filename'] = parse_path($this_action['filename']);
  1028. }
  1029. // If we're moving or requiring (copying) a file.
  1030. if (substr($actionType, 0, 4) == 'move' || substr($actionType, 0, 7) == 'require')
  1031. {
  1032. if ($action->exists('@from'))
  1033. $this_action['source'] = parse_path($action->fetch('@from'));
  1034. else
  1035. $this_action['source'] = $temp_path . $this_action['filename'];
  1036. }
  1037. // Check if these things can be done. (chmod's etc.)
  1038. if ($actionType == 'create-dir')
  1039. {
  1040. if (!mktree($this_action['destination'], false))
  1041. {
  1042. $temp = $this_action['destination'];
  1043. while (!file_exists($temp) && strlen($temp) > 1)
  1044. $temp = dirname($temp);
  1045. $return[] = array(
  1046. 'type' => 'chmod',
  1047. 'filename' => $temp
  1048. );
  1049. }
  1050. }
  1051. elseif ($actionType == 'create-file')
  1052. {
  1053. if (!mktree(dirname($this_action['destination']), false))
  1054. {
  1055. $temp = dirname($this_action['destination']);
  1056. while (!file_exists($temp) && strlen($temp) > 1)
  1057. $temp = dirname($temp);
  1058. $return[] = array(
  1059. 'type' => 'chmod',
  1060. 'filename' => $temp
  1061. );
  1062. }
  1063. if (!is_writable($this_action['destination']) && (file_exists($this_action['destination']) || !is_writable(dirname($this_action['destination']))))
  1064. $return[] = array(
  1065. 'type' => 'chmod',
  1066. 'filename' => $this_action['destination']
  1067. );
  1068. }
  1069. elseif ($actionType == 'require-dir')
  1070. {
  1071. if (!mktree($this_action['destination'], false))
  1072. {
  1073. $temp = $this_action['destination'];
  1074. while (!file_exists($temp) && strlen($temp) > 1)
  1075. $temp = dirname($temp);
  1076. $return[] = array(
  1077. 'type' => 'chmod',
  1078. 'filename' => $temp
  1079. );
  1080. }
  1081. }
  1082. elseif ($actionType == 'require-file')
  1083. {
  1084. if ($action->exists('@theme'))
  1085. $this_action['theme_action'] = $action->fetch('@theme');
  1086. if (!mktree(dirname($this_action['destination']), false))
  1087. {
  1088. $temp = dirname($this_action['destination']);
  1089. while (!file_exists($temp) && strlen($temp) > 1)
  1090. $temp = dirname($temp);
  1091. $return[] = array(
  1092. 'type' => 'chmod',
  1093. 'filename' => $temp
  1094. );
  1095. }
  1096. if (!is_writable($this_action['destination']) && (file_exists($this_action['destination']) || !is_writable(dirname($this_action['destination']))))
  1097. $return[] = array(
  1098. 'type' => 'chmod',
  1099. 'filename' => $this_action['destination']
  1100. );
  1101. }
  1102. elseif ($actionType == 'move-dir' || $actionType == 'move-file')
  1103. {
  1104. if (!mktree(dirname($this_action['destination']), false))
  1105. {
  1106. $temp = dirname($this_action['destination']);
  1107. while (!file_exists($temp) && strlen($temp) > 1)
  1108. $temp = dirname($temp);
  1109. $return[] = array(
  1110. 'type' => 'chmod',
  1111. 'filename' => $temp
  1112. );
  1113. }
  1114. if (!is_writable($this_action['destination']) && (file_exists($this_action['destination']) || !is_writable(dirname($this_action['destination']))))
  1115. $return[] = array(
  1116. 'type' => 'chmod',
  1117. 'filename' => $this_action['destination']
  1118. );
  1119. }
  1120. elseif ($actionType == 'remove-dir')
  1121. {
  1122. if (!is_writable($this_action['filename']) && file_exists($this_action['destination']))
  1123. $return[] = array(
  1124. 'type' => 'chmod',
  1125. 'filename' => $this_action['filename']
  1126. );
  1127. }
  1128. elseif ($actionType == 'remove-file')
  1129. {
  1130. if (!is_writable($this_action['filename']) && file_exists($this_action['filename']))
  1131. $return[] = array(
  1132. 'type' => 'chmod',
  1133. 'filename' => $this_action['filename']
  1134. );
  1135. }
  1136. }
  1137. // Only testing - just return a list of things to be done.
  1138. if ($testing_only)
  1139. return $return;
  1140. umask(0);
  1141. $failure = false;
  1142. $not_done = array(array('type' => '!'));
  1143. foreach ($return as $action)
  1144. {
  1145. if ($action['type'] == 'modification' || $action['type'] == 'code' || $action['type'] == 'database' || $action['type'] == 'redirect')
  1146. $not_done[] = $action;
  1147. if ($action['type'] == 'create-dir')
  1148. {
  1149. if (!mktree($action['destination'], 0755) || !is_writable($action['destination']))
  1150. $failure |= !mktree($action['destination'], 0777);
  1151. }
  1152. elseif ($action['type'] == 'create-file')
  1153. {
  1154. if (!mktree(dirname($action['destination']), 0755) || !is_writable(dirname($action['destination'])))
  1155. $failure |= !mktree(dirname($action['destination']), 0777);
  1156. // Create an empty file.
  1157. package_put_contents($action['destination'], package_get_contents($action['source']), $testing_only);
  1158. if (!file_exists($action['destination']))
  1159. $failure = true;
  1160. }
  1161. elseif ($action['type'] == 'require-dir')
  1162. {
  1163. copytree($action['source'], $action['destination']);
  1164. // Any other theme folders?
  1165. if (!empty($context['theme_copies']) && !empty($context['theme_copies'][$action['type']][$action['destination']]))
  1166. foreach ($context['theme_copies'][$action['type']][$action['destination']] as $theme_destination)
  1167. copytree($action['source'], $theme_destination);
  1168. }
  1169. elseif ($action['type'] == 'require-file')
  1170. {
  1171. if (!mktree(dirname($action['destination']), 0755) || !is_writable(dirname($action['destination'])))
  1172. $failure |= !mktree(dirname($action['destination']), 0777);
  1173. package_put_contents($action['destination'], package_get_contents($action['source']), $testing_only);
  1174. $failure |= !copy($action['source'], $action['destination']);
  1175. // Any other theme files?
  1176. if (!empty($context['theme_copies']) && !empty($context['theme_copies'][$action['type']][$action['destination']]))
  1177. foreach ($context['theme_copies'][$action['type']][$action['destination']] as $theme_destination)
  1178. {
  1179. if (!mktree(dirname($theme_destination), 0755) || !is_writable(dirname($theme_destination)))
  1180. $failure |= !mktree(dirname($theme_destination), 0777);
  1181. package_put_contents($theme_destination, package_get_contents($action['source']), $testing_only);
  1182. $failure |= !copy($action['source'], $theme_destination);
  1183. }
  1184. }
  1185. elseif ($action['type'] == 'move-file')
  1186. {
  1187. if (!mktree(dirname($action['destination']), 0755) || !is_writable(dirname($action['destination'])))
  1188. $failure |= !mktree(dirname($action['destination']), 0777);
  1189. $failure |= !rename($action['source'], $action['destination']);
  1190. }
  1191. elseif ($action['type'] == 'move-dir')
  1192. {
  1193. if (!mktree($action['destination'], 0755) || !is_writable($action['destination']))
  1194. $failure |= !mktree($action['destination'], 0777);
  1195. $failure |= !rename($action['source'], $action['destination']);
  1196. }
  1197. elseif ($action['type'] == 'remove-dir')
  1198. {
  1199. deltree($action['filename']);
  1200. // Any other theme folders?
  1201. if (!empty($context['theme_copies']) && !empty($context['theme_copies'][$action['type']][$action['filename']]))
  1202. foreach ($context['theme_copies'][$action['type']][$action['filename']] as $theme_destination)
  1203. deltree($theme_destination);
  1204. }
  1205. elseif ($action['type'] == 'remove-file')
  1206. {
  1207. // Make sure the file exists before deleting it.
  1208. if (file_exists($action['filename']))
  1209. {
  1210. package_chmod($action['filename']);
  1211. $failure |= !unlink($action['filename']);
  1212. }
  1213. // The file that was supposed to be deleted couldn't be found.
  1214. else
  1215. $failure = true;
  1216. // Any other theme folders?
  1217. if (!empty($context['theme_copies']) && !empty($context['theme_copies'][$action['type']][$action['filename']]))
  1218. foreach ($context['theme_copies'][$action['type']][$action['filename']] as $theme_destination)
  1219. if (file_exists($theme_destination))
  1220. $failure |= !unlink($theme_destination);
  1221. else
  1222. $failure = true;
  1223. }
  1224. }
  1225. return $not_done;
  1226. }
  1227. // This is such a pain I created a function for it :P.
  1228. function matchPackageVersion($version, $versions)
  1229. {
  1230. $version = strtolower($version);
  1231. $for = explode(',', strtolower($versions));
  1232. // Trim them all!
  1233. for ($i = 0, $n = count($for); $i < $n; $i++)
  1234. $for[$i] = trim($for[$i]);
  1235. // The version is explicitly defined... too easy.
  1236. if (in_array($version, $for) || in_array('all', $for))
  1237. return true;
  1238. foreach ($for as $list)
  1239. {
  1240. if (substr($list, -1) == '*' && strpos($list, '-') === false)
  1241. {
  1242. // "Nothing" is the lowest alphanumeric character, z the highest.
  1243. $list = substr($list, 0, -1) . '-' . substr($list, 0, -1) . 'z';
  1244. }
  1245. // Look for a version specification like "1.0-1.2".
  1246. elseif (strpos($list, '-') === false)
  1247. continue;
  1248. list ($lower, $upper) = explode('-', $list);
  1249. $lower = explode('.', $lower);
  1250. $upper = explode('.', $upper);
  1251. $version = explode('.', $version);
  1252. foreach ($upper as $key => $high)
  1253. {
  1254. // Let's check that this is at or below the upper... o…

Large files files are truncated, but you can click here to view the full file