PageRenderTime 79ms CodeModel.GetById 23ms RepoModel.GetById 0ms app.codeStats 1ms

/sources/subs/Package.subs.php

https://github.com/Arantor/Elkarte
PHP | 3204 lines | 2305 code | 402 blank | 497 comment | 633 complexity | 514ba34874905b139a4775f9c4463a9f MD5 | raw file
Possible License(s): BSD-3-Clause, LGPL-3.0

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

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

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