PageRenderTime 1993ms CodeModel.GetById 12ms RepoModel.GetById 14ms app.codeStats 4ms

/kickstart.php

https://github.com/finchiefin/olympian
PHP | 9572 lines | 7194 code | 838 blank | 1540 comment | 847 complexity | a2c20af0fdfc8fd5f81a5213274f83a1 MD5 | raw file
Possible License(s): GPL-2.0, Apache-2.0, LGPL-2.1, BSD-3-Clause, JSON
  1. <?php
  2. /**
  3. * Akeeba Kickstart
  4. * A JSON-powered archive extraction tool
  5. *
  6. * @copyright 2010-2013 Nicholas K. Dionysopoulos / AkeebaBackup.com
  7. * @license GNU GPL v2 or - at your option - any later version
  8. * @package akeebabackup
  9. * @subpackage kickstart
  10. */
  11. /*
  12. Akeeba Kickstart - The server-side archive extraction wizard
  13. Copyright (C) 2008-2013 Nicholas K. Dionysopoulos / AkeebaBackup.com
  14. This program is free software: you can redistribute it and/or modify
  15. it under the terms of the GNU General Public License as published by
  16. the Free Software Foundation, either version 3 of the License, or
  17. (at your option) any later version.
  18. This program is distributed in the hope that it will be useful,
  19. but WITHOUT ANY WARRANTY; without even the implied warranty of
  20. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  21. GNU General Public License for more details.
  22. You should have received a copy of the GNU General Public License
  23. along with this program. If not, see <http://www.gnu.org/licenses/>.
  24. */
  25. define('KICKSTART',1);
  26. define('VERSION', '3.8.0');
  27. define('KICKSTARTPRO', '0');
  28. // Uncomment the following line to enable Kickstart's debug mode
  29. //define('KSDEBUG', 1);
  30. if(!defined('KSROOTDIR'))
  31. {
  32. define('KSROOTDIR', dirname(__FILE__));
  33. }
  34. if(defined('KSDEBUG')) {
  35. ini_set('error_log', KSROOTDIR.'/kickstart_error_log' );
  36. if(file_exists(KSROOTDIR.'/kickstart_error_log')) {
  37. @unlink(KSROOTDIR.'/kickstart_error_log');
  38. }
  39. error_reporting(E_ALL | E_STRICT);
  40. } else {
  41. @error_reporting(E_NONE);
  42. }
  43. // ==========================================================================================
  44. // IIS missing REQUEST_URI workaround
  45. // ==========================================================================================
  46. /*
  47. * Based REQUEST_URI for IIS Servers 1.0 by NeoSmart Technologies
  48. * The proper method to solve IIS problems is to take a look at this:
  49. * http://neosmart.net/dl.php?id=7
  50. */
  51. //This file should be located in the same directory as php.exe or php5isapi.dll
  52. if (!isset($_SERVER['REQUEST_URI']))
  53. {
  54. if (isset($_SERVER['HTTP_REQUEST_URI']))
  55. {
  56. $_SERVER['REQUEST_URI'] = $_SERVER['HTTP_REQUEST_URI'];
  57. //Good to go!
  58. }
  59. else
  60. {
  61. //Someone didn't follow the instructions!
  62. if(isset($_SERVER['SCRIPT_NAME']))
  63. $_SERVER['HTTP_REQUEST_URI'] = $_SERVER['SCRIPT_NAME'];
  64. else
  65. $_SERVER['HTTP_REQUEST_URI'] = $_SERVER['PHP_SELF'];
  66. if($_SERVER['QUERY_STRING']){
  67. $_SERVER['HTTP_REQUEST_URI'] .= '?' . $_SERVER['QUERY_STRING'];
  68. }
  69. //WARNING: This is a workaround!
  70. //For guaranteed compatibility, HTTP_REQUEST_URI *MUST* be defined!
  71. //See product documentation for instructions!
  72. $_SERVER['REQUEST_URI'] = $_SERVER['HTTP_REQUEST_URI'];
  73. }
  74. }
  75. // Define the cacert.pem location, if it exists
  76. $cacertpem = KSROOTDIR . '/cacert.pem';
  77. if(is_file($cacertpem)) {
  78. if(is_readable($cacertpem)) {
  79. define('AKEEBA_CACERT_PEM', $cacertpem);
  80. }
  81. }
  82. unset($cacertpem);
  83. // Loads other PHP files containing extra Kickstart features
  84. $dh = @opendir(KSROOTDIR);
  85. if($dh === false) return;
  86. while($filename = readdir($dh)) {
  87. if(!is_file($filename)) continue;
  88. if(substr($filename, 0, 10) != 'kickstart.') continue;
  89. if(substr($filename, -4) != '.php') continue;
  90. include_once $filename;
  91. }
  92. /**
  93. * Akeeba Restore
  94. * A JSON-powered JPA, JPS and ZIP archive extraction library
  95. *
  96. * @copyright 2010-2013 Nicholas K. Dionysopoulos / Akeeba Ltd.
  97. * @license GNU GPL v2 or - at your option - any later version
  98. * @package akeebabackup
  99. * @subpackage kickstart
  100. */
  101. define('_AKEEBA_RESTORATION', 1);
  102. defined('DS') or define('DS', DIRECTORY_SEPARATOR);
  103. // Unarchiver run states
  104. define('AK_STATE_NOFILE', 0); // File header not read yet
  105. define('AK_STATE_HEADER', 1); // File header read; ready to process data
  106. define('AK_STATE_DATA', 2); // Processing file data
  107. define('AK_STATE_DATAREAD', 3); // Finished processing file data; ready to post-process
  108. define('AK_STATE_POSTPROC', 4); // Post-processing
  109. define('AK_STATE_DONE', 5); // Done with post-processing
  110. /* Windows system detection */
  111. if(!defined('_AKEEBA_IS_WINDOWS'))
  112. {
  113. if (function_exists('php_uname'))
  114. define('_AKEEBA_IS_WINDOWS', stristr(php_uname(), 'windows'));
  115. else
  116. define('_AKEEBA_IS_WINDOWS', DIRECTORY_SEPARATOR == '\\');
  117. }
  118. // Get the file's root
  119. if(!defined('KSROOTDIR'))
  120. {
  121. define('KSROOTDIR', dirname(__FILE__));
  122. }
  123. if(!defined('KSLANGDIR'))
  124. {
  125. define('KSLANGDIR', KSROOTDIR);
  126. }
  127. // Make sure the locale is correct for basename() to work
  128. if(function_exists('setlocale'))
  129. {
  130. @setlocale(LC_ALL, 'en_US.UTF8');
  131. }
  132. // fnmatch not available on non-POSIX systems
  133. // Thanks to soywiz@php.net for this usefull alternative function [http://gr2.php.net/fnmatch]
  134. if (!function_exists('fnmatch')) {
  135. function fnmatch($pattern, $string) {
  136. return @preg_match(
  137. '/^' . strtr(addcslashes($pattern, '/\\.+^$(){}=!<>|'),
  138. array('*' => '.*', '?' => '.?')) . '$/i', $string
  139. );
  140. }
  141. }
  142. // Unicode-safe binary data length function
  143. if (!function_exists('akstringlen'))
  144. {
  145. if(function_exists('mb_strlen')) {
  146. function akstringlen($string) { return mb_strlen($string,'8bit'); }
  147. } else {
  148. function akstringlen($string) { return strlen($string); }
  149. }
  150. }
  151. /**
  152. * Gets a query parameter from GET or POST data
  153. * @param $key
  154. * @param $default
  155. */
  156. function getQueryParam( $key, $default = null )
  157. {
  158. $value = null;
  159. if(array_key_exists($key, $_REQUEST)) {
  160. $value = $_REQUEST[$key];
  161. } elseif(array_key_exists($key, $_POST)) {
  162. $value = $_POST[$key];
  163. } elseif(array_key_exists($key, $_GET)) {
  164. $value = $_GET[$key];
  165. } else {
  166. return $default;
  167. }
  168. if(get_magic_quotes_gpc() && !is_null($value)) $value=stripslashes($value);
  169. return $value;
  170. }
  171. // Debugging function
  172. function debugMsg($msg)
  173. {
  174. if(!defined('KSDEBUG')) return;
  175. $fp = fopen('debug.txt','at');
  176. fwrite($fp, $msg."\n");
  177. fclose($fp);
  178. }
  179. /**
  180. * Akeeba Restore
  181. * A JSON-powered JPA, JPS and ZIP archive extraction library
  182. *
  183. * @copyright 2010-2013 Nicholas K. Dionysopoulos / Akeeba Ltd.
  184. * @license GNU GPL v2 or - at your option - any later version
  185. * @package akeebabackup
  186. * @subpackage kickstart
  187. */
  188. /**
  189. * Akeeba Backup's JSON compatibility layer
  190. *
  191. * On systems where json_encode and json_decode are not available, Akeeba
  192. * Backup will attempt to use PEAR's Services_JSON library to emulate them.
  193. * A copy of this library is included in this file and will be used if and
  194. * only if it isn't already loaded, e.g. due to PEAR's auto-loading, or a
  195. * 3PD extension loading it for its own purposes.
  196. */
  197. /**
  198. * Converts to and from JSON format.
  199. *
  200. * JSON (JavaScript Object Notation) is a lightweight data-interchange
  201. * format. It is easy for humans to read and write. It is easy for machines
  202. * to parse and generate. It is based on a subset of the JavaScript
  203. * Programming Language, Standard ECMA-262 3rd Edition - December 1999.
  204. * This feature can also be found in Python. JSON is a text format that is
  205. * completely language independent but uses conventions that are familiar
  206. * to programmers of the C-family of languages, including C, C++, C#, Java,
  207. * JavaScript, Perl, TCL, and many others. These properties make JSON an
  208. * ideal data-interchange language.
  209. *
  210. * This package provides a simple encoder and decoder for JSON notation. It
  211. * is intended for use with client-side Javascript applications that make
  212. * use of HTTPRequest to perform server communication functions - data can
  213. * be encoded into JSON notation for use in a client-side javascript, or
  214. * decoded from incoming Javascript requests. JSON format is native to
  215. * Javascript, and can be directly eval()'ed with no further parsing
  216. * overhead
  217. *
  218. * All strings should be in ASCII or UTF-8 format!
  219. *
  220. * LICENSE: Redistribution and use in source and binary forms, with or
  221. * without modification, are permitted provided that the following
  222. * conditions are met: Redistributions of source code must retain the
  223. * above copyright notice, this list of conditions and the following
  224. * disclaimer. Redistributions in binary form must reproduce the above
  225. * copyright notice, this list of conditions and the following disclaimer
  226. * in the documentation and/or other materials provided with the
  227. * distribution.
  228. *
  229. * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
  230. * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
  231. * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
  232. * NO EVENT SHALL CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
  233. * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
  234. * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
  235. * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
  236. * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
  237. * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
  238. * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
  239. * DAMAGE.
  240. *
  241. * @category
  242. * @package Services_JSON
  243. * @author Michal Migurski <mike-json@teczno.com>
  244. * @author Matt Knapp <mdknapp[at]gmail[dot]com>
  245. * @author Brett Stimmerman <brettstimmerman[at]gmail[dot]com>
  246. * @copyright 2005 Michal Migurski
  247. * @version CVS: $Id: restore.php 612 2011-05-19 08:26:26Z nikosdion $
  248. * @license http://www.opensource.org/licenses/bsd-license.php
  249. * @link http://pear.php.net/pepr/pepr-proposal-show.php?id=198
  250. */
  251. if(!defined('JSON_FORCE_OBJECT'))
  252. {
  253. define('JSON_FORCE_OBJECT', 1);
  254. }
  255. if(!defined('SERVICES_JSON_SLICE'))
  256. {
  257. /**
  258. * Marker constant for Services_JSON::decode(), used to flag stack state
  259. */
  260. define('SERVICES_JSON_SLICE', 1);
  261. /**
  262. * Marker constant for Services_JSON::decode(), used to flag stack state
  263. */
  264. define('SERVICES_JSON_IN_STR', 2);
  265. /**
  266. * Marker constant for Services_JSON::decode(), used to flag stack state
  267. */
  268. define('SERVICES_JSON_IN_ARR', 3);
  269. /**
  270. * Marker constant for Services_JSON::decode(), used to flag stack state
  271. */
  272. define('SERVICES_JSON_IN_OBJ', 4);
  273. /**
  274. * Marker constant for Services_JSON::decode(), used to flag stack state
  275. */
  276. define('SERVICES_JSON_IN_CMT', 5);
  277. /**
  278. * Behavior switch for Services_JSON::decode()
  279. */
  280. define('SERVICES_JSON_LOOSE_TYPE', 16);
  281. /**
  282. * Behavior switch for Services_JSON::decode()
  283. */
  284. define('SERVICES_JSON_SUPPRESS_ERRORS', 32);
  285. }
  286. /**
  287. * Converts to and from JSON format.
  288. *
  289. * Brief example of use:
  290. *
  291. * <code>
  292. * // create a new instance of Services_JSON
  293. * $json = new Services_JSON();
  294. *
  295. * // convert a complexe value to JSON notation, and send it to the browser
  296. * $value = array('foo', 'bar', array(1, 2, 'baz'), array(3, array(4)));
  297. * $output = $json->encode($value);
  298. *
  299. * print($output);
  300. * // prints: ["foo","bar",[1,2,"baz"],[3,[4]]]
  301. *
  302. * // accept incoming POST data, assumed to be in JSON notation
  303. * $input = file_get_contents('php://input', 1000000);
  304. * $value = $json->decode($input);
  305. * </code>
  306. */
  307. if(!class_exists('Akeeba_Services_JSON'))
  308. {
  309. class Akeeba_Services_JSON
  310. {
  311. /**
  312. * constructs a new JSON instance
  313. *
  314. * @param int $use object behavior flags; combine with boolean-OR
  315. *
  316. * possible values:
  317. * - SERVICES_JSON_LOOSE_TYPE: loose typing.
  318. * "{...}" syntax creates associative arrays
  319. * instead of objects in decode().
  320. * - SERVICES_JSON_SUPPRESS_ERRORS: error suppression.
  321. * Values which can't be encoded (e.g. resources)
  322. * appear as NULL instead of throwing errors.
  323. * By default, a deeply-nested resource will
  324. * bubble up with an error, so all return values
  325. * from encode() should be checked with isError()
  326. */
  327. function Akeeba_Services_JSON($use = 0)
  328. {
  329. $this->use = $use;
  330. }
  331. /**
  332. * convert a string from one UTF-16 char to one UTF-8 char
  333. *
  334. * Normally should be handled by mb_convert_encoding, but
  335. * provides a slower PHP-only method for installations
  336. * that lack the multibye string extension.
  337. *
  338. * @param string $utf16 UTF-16 character
  339. * @return string UTF-8 character
  340. * @access private
  341. */
  342. function utf162utf8($utf16)
  343. {
  344. // oh please oh please oh please oh please oh please
  345. if(function_exists('mb_convert_encoding')) {
  346. return mb_convert_encoding($utf16, 'UTF-8', 'UTF-16');
  347. }
  348. $bytes = (ord($utf16{0}) << 8) | ord($utf16{1});
  349. switch(true) {
  350. case ((0x7F & $bytes) == $bytes):
  351. // this case should never be reached, because we are in ASCII range
  352. // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
  353. return chr(0x7F & $bytes);
  354. case (0x07FF & $bytes) == $bytes:
  355. // return a 2-byte UTF-8 character
  356. // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
  357. return chr(0xC0 | (($bytes >> 6) & 0x1F))
  358. . chr(0x80 | ($bytes & 0x3F));
  359. case (0xFFFF & $bytes) == $bytes:
  360. // return a 3-byte UTF-8 character
  361. // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
  362. return chr(0xE0 | (($bytes >> 12) & 0x0F))
  363. . chr(0x80 | (($bytes >> 6) & 0x3F))
  364. . chr(0x80 | ($bytes & 0x3F));
  365. }
  366. // ignoring UTF-32 for now, sorry
  367. return '';
  368. }
  369. /**
  370. * convert a string from one UTF-8 char to one UTF-16 char
  371. *
  372. * Normally should be handled by mb_convert_encoding, but
  373. * provides a slower PHP-only method for installations
  374. * that lack the multibye string extension.
  375. *
  376. * @param string $utf8 UTF-8 character
  377. * @return string UTF-16 character
  378. * @access private
  379. */
  380. function utf82utf16($utf8)
  381. {
  382. // oh please oh please oh please oh please oh please
  383. if(function_exists('mb_convert_encoding')) {
  384. return mb_convert_encoding($utf8, 'UTF-16', 'UTF-8');
  385. }
  386. switch(strlen($utf8)) {
  387. case 1:
  388. // this case should never be reached, because we are in ASCII range
  389. // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
  390. return $utf8;
  391. case 2:
  392. // return a UTF-16 character from a 2-byte UTF-8 char
  393. // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
  394. return chr(0x07 & (ord($utf8{0}) >> 2))
  395. . chr((0xC0 & (ord($utf8{0}) << 6))
  396. | (0x3F & ord($utf8{1})));
  397. case 3:
  398. // return a UTF-16 character from a 3-byte UTF-8 char
  399. // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
  400. return chr((0xF0 & (ord($utf8{0}) << 4))
  401. | (0x0F & (ord($utf8{1}) >> 2)))
  402. . chr((0xC0 & (ord($utf8{1}) << 6))
  403. | (0x7F & ord($utf8{2})));
  404. }
  405. // ignoring UTF-32 for now, sorry
  406. return '';
  407. }
  408. /**
  409. * encodes an arbitrary variable into JSON format
  410. *
  411. * @param mixed $var any number, boolean, string, array, or object to be encoded.
  412. * see argument 1 to Services_JSON() above for array-parsing behavior.
  413. * if var is a strng, note that encode() always expects it
  414. * to be in ASCII or UTF-8 format!
  415. *
  416. * @return mixed JSON string representation of input var or an error if a problem occurs
  417. * @access public
  418. */
  419. function encode($var)
  420. {
  421. switch (gettype($var)) {
  422. case 'boolean':
  423. return $var ? 'true' : 'false';
  424. case 'NULL':
  425. return 'null';
  426. case 'integer':
  427. return (int) $var;
  428. case 'double':
  429. case 'float':
  430. return (float) $var;
  431. case 'string':
  432. // STRINGS ARE EXPECTED TO BE IN ASCII OR UTF-8 FORMAT
  433. $ascii = '';
  434. $strlen_var = strlen($var);
  435. /*
  436. * Iterate over every character in the string,
  437. * escaping with a slash or encoding to UTF-8 where necessary
  438. */
  439. for ($c = 0; $c < $strlen_var; ++$c) {
  440. $ord_var_c = ord($var{$c});
  441. switch (true) {
  442. case $ord_var_c == 0x08:
  443. $ascii .= '\b';
  444. break;
  445. case $ord_var_c == 0x09:
  446. $ascii .= '\t';
  447. break;
  448. case $ord_var_c == 0x0A:
  449. $ascii .= '\n';
  450. break;
  451. case $ord_var_c == 0x0C:
  452. $ascii .= '\f';
  453. break;
  454. case $ord_var_c == 0x0D:
  455. $ascii .= '\r';
  456. break;
  457. case $ord_var_c == 0x22:
  458. case $ord_var_c == 0x2F:
  459. case $ord_var_c == 0x5C:
  460. // double quote, slash, slosh
  461. $ascii .= '\\'.$var{$c};
  462. break;
  463. case (($ord_var_c >= 0x20) && ($ord_var_c <= 0x7F)):
  464. // characters U-00000000 - U-0000007F (same as ASCII)
  465. $ascii .= $var{$c};
  466. break;
  467. case (($ord_var_c & 0xE0) == 0xC0):
  468. // characters U-00000080 - U-000007FF, mask 110XXXXX
  469. // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
  470. $char = pack('C*', $ord_var_c, ord($var{$c + 1}));
  471. $c += 1;
  472. $utf16 = $this->utf82utf16($char);
  473. $ascii .= sprintf('\u%04s', bin2hex($utf16));
  474. break;
  475. case (($ord_var_c & 0xF0) == 0xE0):
  476. // characters U-00000800 - U-0000FFFF, mask 1110XXXX
  477. // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
  478. $char = pack('C*', $ord_var_c,
  479. ord($var{$c + 1}),
  480. ord($var{$c + 2}));
  481. $c += 2;
  482. $utf16 = $this->utf82utf16($char);
  483. $ascii .= sprintf('\u%04s', bin2hex($utf16));
  484. break;
  485. case (($ord_var_c & 0xF8) == 0xF0):
  486. // characters U-00010000 - U-001FFFFF, mask 11110XXX
  487. // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
  488. $char = pack('C*', $ord_var_c,
  489. ord($var{$c + 1}),
  490. ord($var{$c + 2}),
  491. ord($var{$c + 3}));
  492. $c += 3;
  493. $utf16 = $this->utf82utf16($char);
  494. $ascii .= sprintf('\u%04s', bin2hex($utf16));
  495. break;
  496. case (($ord_var_c & 0xFC) == 0xF8):
  497. // characters U-00200000 - U-03FFFFFF, mask 111110XX
  498. // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
  499. $char = pack('C*', $ord_var_c,
  500. ord($var{$c + 1}),
  501. ord($var{$c + 2}),
  502. ord($var{$c + 3}),
  503. ord($var{$c + 4}));
  504. $c += 4;
  505. $utf16 = $this->utf82utf16($char);
  506. $ascii .= sprintf('\u%04s', bin2hex($utf16));
  507. break;
  508. case (($ord_var_c & 0xFE) == 0xFC):
  509. // characters U-04000000 - U-7FFFFFFF, mask 1111110X
  510. // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
  511. $char = pack('C*', $ord_var_c,
  512. ord($var{$c + 1}),
  513. ord($var{$c + 2}),
  514. ord($var{$c + 3}),
  515. ord($var{$c + 4}),
  516. ord($var{$c + 5}));
  517. $c += 5;
  518. $utf16 = $this->utf82utf16($char);
  519. $ascii .= sprintf('\u%04s', bin2hex($utf16));
  520. break;
  521. }
  522. }
  523. return '"'.$ascii.'"';
  524. case 'array':
  525. /*
  526. * As per JSON spec if any array key is not an integer
  527. * we must treat the the whole array as an object. We
  528. * also try to catch a sparsely populated associative
  529. * array with numeric keys here because some JS engines
  530. * will create an array with empty indexes up to
  531. * max_index which can cause memory issues and because
  532. * the keys, which may be relevant, will be remapped
  533. * otherwise.
  534. *
  535. * As per the ECMA and JSON specification an object may
  536. * have any string as a property. Unfortunately due to
  537. * a hole in the ECMA specification if the key is a
  538. * ECMA reserved word or starts with a digit the
  539. * parameter is only accessible using ECMAScript's
  540. * bracket notation.
  541. */
  542. // treat as a JSON object
  543. if (is_array($var) && count($var) && (array_keys($var) !== range(0, sizeof($var) - 1))) {
  544. $properties = array_map(array($this, 'name_value'),
  545. array_keys($var),
  546. array_values($var));
  547. foreach($properties as $property) {
  548. if(Akeeba_Services_JSON::isError($property)) {
  549. return $property;
  550. }
  551. }
  552. return '{' . join(',', $properties) . '}';
  553. }
  554. // treat it like a regular array
  555. $elements = array_map(array($this, 'encode'), $var);
  556. foreach($elements as $element) {
  557. if(Akeeba_Services_JSON::isError($element)) {
  558. return $element;
  559. }
  560. }
  561. return '[' . join(',', $elements) . ']';
  562. case 'object':
  563. $vars = get_object_vars($var);
  564. $properties = array_map(array($this, 'name_value'),
  565. array_keys($vars),
  566. array_values($vars));
  567. foreach($properties as $property) {
  568. if(Akeeba_Services_JSON::isError($property)) {
  569. return $property;
  570. }
  571. }
  572. return '{' . join(',', $properties) . '}';
  573. default:
  574. return ($this->use & SERVICES_JSON_SUPPRESS_ERRORS)
  575. ? 'null'
  576. : new Akeeba_Services_JSON_Error(gettype($var)." can not be encoded as JSON string");
  577. }
  578. }
  579. /**
  580. * array-walking function for use in generating JSON-formatted name-value pairs
  581. *
  582. * @param string $name name of key to use
  583. * @param mixed $value reference to an array element to be encoded
  584. *
  585. * @return string JSON-formatted name-value pair, like '"name":value'
  586. * @access private
  587. */
  588. function name_value($name, $value)
  589. {
  590. $encoded_value = $this->encode($value);
  591. if(Akeeba_Services_JSON::isError($encoded_value)) {
  592. return $encoded_value;
  593. }
  594. return $this->encode(strval($name)) . ':' . $encoded_value;
  595. }
  596. /**
  597. * reduce a string by removing leading and trailing comments and whitespace
  598. *
  599. * @param $str string string value to strip of comments and whitespace
  600. *
  601. * @return string string value stripped of comments and whitespace
  602. * @access private
  603. */
  604. function reduce_string($str)
  605. {
  606. $str = preg_replace(array(
  607. // eliminate single line comments in '// ...' form
  608. '#^\s*//(.+)$#m',
  609. // eliminate multi-line comments in '/* ... */' form, at start of string
  610. '#^\s*/\*(.+)\*/#Us',
  611. // eliminate multi-line comments in '/* ... */' form, at end of string
  612. '#/\*(.+)\*/\s*$#Us'
  613. ), '', $str);
  614. // eliminate extraneous space
  615. return trim($str);
  616. }
  617. /**
  618. * decodes a JSON string into appropriate variable
  619. *
  620. * @param string $str JSON-formatted string
  621. *
  622. * @return mixed number, boolean, string, array, or object
  623. * corresponding to given JSON input string.
  624. * See argument 1 to Akeeba_Services_JSON() above for object-output behavior.
  625. * Note that decode() always returns strings
  626. * in ASCII or UTF-8 format!
  627. * @access public
  628. */
  629. function decode($str)
  630. {
  631. $str = $this->reduce_string($str);
  632. switch (strtolower($str)) {
  633. case 'true':
  634. return true;
  635. case 'false':
  636. return false;
  637. case 'null':
  638. return null;
  639. default:
  640. $m = array();
  641. if (is_numeric($str)) {
  642. // Lookie-loo, it's a number
  643. // This would work on its own, but I'm trying to be
  644. // good about returning integers where appropriate:
  645. // return (float)$str;
  646. // Return float or int, as appropriate
  647. return ((float)$str == (integer)$str)
  648. ? (integer)$str
  649. : (float)$str;
  650. } elseif (preg_match('/^("|\').*(\1)$/s', $str, $m) && $m[1] == $m[2]) {
  651. // STRINGS RETURNED IN UTF-8 FORMAT
  652. $delim = substr($str, 0, 1);
  653. $chrs = substr($str, 1, -1);
  654. $utf8 = '';
  655. $strlen_chrs = strlen($chrs);
  656. for ($c = 0; $c < $strlen_chrs; ++$c) {
  657. $substr_chrs_c_2 = substr($chrs, $c, 2);
  658. $ord_chrs_c = ord($chrs{$c});
  659. switch (true) {
  660. case $substr_chrs_c_2 == '\b':
  661. $utf8 .= chr(0x08);
  662. ++$c;
  663. break;
  664. case $substr_chrs_c_2 == '\t':
  665. $utf8 .= chr(0x09);
  666. ++$c;
  667. break;
  668. case $substr_chrs_c_2 == '\n':
  669. $utf8 .= chr(0x0A);
  670. ++$c;
  671. break;
  672. case $substr_chrs_c_2 == '\f':
  673. $utf8 .= chr(0x0C);
  674. ++$c;
  675. break;
  676. case $substr_chrs_c_2 == '\r':
  677. $utf8 .= chr(0x0D);
  678. ++$c;
  679. break;
  680. case $substr_chrs_c_2 == '\\"':
  681. case $substr_chrs_c_2 == '\\\'':
  682. case $substr_chrs_c_2 == '\\\\':
  683. case $substr_chrs_c_2 == '\\/':
  684. if (($delim == '"' && $substr_chrs_c_2 != '\\\'') ||
  685. ($delim == "'" && $substr_chrs_c_2 != '\\"')) {
  686. $utf8 .= $chrs{++$c};
  687. }
  688. break;
  689. case preg_match('/\\\u[0-9A-F]{4}/i', substr($chrs, $c, 6)):
  690. // single, escaped unicode character
  691. $utf16 = chr(hexdec(substr($chrs, ($c + 2), 2)))
  692. . chr(hexdec(substr($chrs, ($c + 4), 2)));
  693. $utf8 .= $this->utf162utf8($utf16);
  694. $c += 5;
  695. break;
  696. case ($ord_chrs_c >= 0x20) && ($ord_chrs_c <= 0x7F):
  697. $utf8 .= $chrs{$c};
  698. break;
  699. case ($ord_chrs_c & 0xE0) == 0xC0:
  700. // characters U-00000080 - U-000007FF, mask 110XXXXX
  701. //see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
  702. $utf8 .= substr($chrs, $c, 2);
  703. ++$c;
  704. break;
  705. case ($ord_chrs_c & 0xF0) == 0xE0:
  706. // characters U-00000800 - U-0000FFFF, mask 1110XXXX
  707. // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
  708. $utf8 .= substr($chrs, $c, 3);
  709. $c += 2;
  710. break;
  711. case ($ord_chrs_c & 0xF8) == 0xF0:
  712. // characters U-00010000 - U-001FFFFF, mask 11110XXX
  713. // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
  714. $utf8 .= substr($chrs, $c, 4);
  715. $c += 3;
  716. break;
  717. case ($ord_chrs_c & 0xFC) == 0xF8:
  718. // characters U-00200000 - U-03FFFFFF, mask 111110XX
  719. // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
  720. $utf8 .= substr($chrs, $c, 5);
  721. $c += 4;
  722. break;
  723. case ($ord_chrs_c & 0xFE) == 0xFC:
  724. // characters U-04000000 - U-7FFFFFFF, mask 1111110X
  725. // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
  726. $utf8 .= substr($chrs, $c, 6);
  727. $c += 5;
  728. break;
  729. }
  730. }
  731. return $utf8;
  732. } elseif (preg_match('/^\[.*\]$/s', $str) || preg_match('/^\{.*\}$/s', $str)) {
  733. // array, or object notation
  734. if ($str{0} == '[') {
  735. $stk = array(SERVICES_JSON_IN_ARR);
  736. $arr = array();
  737. } else {
  738. if ($this->use & SERVICES_JSON_LOOSE_TYPE) {
  739. $stk = array(SERVICES_JSON_IN_OBJ);
  740. $obj = array();
  741. } else {
  742. $stk = array(SERVICES_JSON_IN_OBJ);
  743. $obj = new stdClass();
  744. }
  745. }
  746. array_push($stk, array('what' => SERVICES_JSON_SLICE,
  747. 'where' => 0,
  748. 'delim' => false));
  749. $chrs = substr($str, 1, -1);
  750. $chrs = $this->reduce_string($chrs);
  751. if ($chrs == '') {
  752. if (reset($stk) == SERVICES_JSON_IN_ARR) {
  753. return $arr;
  754. } else {
  755. return $obj;
  756. }
  757. }
  758. //print("\nparsing {$chrs}\n");
  759. $strlen_chrs = strlen($chrs);
  760. for ($c = 0; $c <= $strlen_chrs; ++$c) {
  761. $top = end($stk);
  762. $substr_chrs_c_2 = substr($chrs, $c, 2);
  763. if (($c == $strlen_chrs) || (($chrs{$c} == ',') && ($top['what'] == SERVICES_JSON_SLICE))) {
  764. // found a comma that is not inside a string, array, etc.,
  765. // OR we've reached the end of the character list
  766. $slice = substr($chrs, $top['where'], ($c - $top['where']));
  767. array_push($stk, array('what' => SERVICES_JSON_SLICE, 'where' => ($c + 1), 'delim' => false));
  768. //print("Found split at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");
  769. if (reset($stk) == SERVICES_JSON_IN_ARR) {
  770. // we are in an array, so just push an element onto the stack
  771. array_push($arr, $this->decode($slice));
  772. } elseif (reset($stk) == SERVICES_JSON_IN_OBJ) {
  773. // we are in an object, so figure
  774. // out the property name and set an
  775. // element in an associative array,
  776. // for now
  777. $parts = array();
  778. if (preg_match('/^\s*(["\'].*[^\\\]["\'])\s*:\s*(\S.*),?$/Uis', $slice, $parts)) {
  779. // "name":value pair
  780. $key = $this->decode($parts[1]);
  781. $val = $this->decode($parts[2]);
  782. if ($this->use & SERVICES_JSON_LOOSE_TYPE) {
  783. $obj[$key] = $val;
  784. } else {
  785. $obj->$key = $val;
  786. }
  787. } elseif (preg_match('/^\s*(\w+)\s*:\s*(\S.*),?$/Uis', $slice, $parts)) {
  788. // name:value pair, where name is unquoted
  789. $key = $parts[1];
  790. $val = $this->decode($parts[2]);
  791. if ($this->use & SERVICES_JSON_LOOSE_TYPE) {
  792. $obj[$key] = $val;
  793. } else {
  794. $obj->$key = $val;
  795. }
  796. }
  797. }
  798. } elseif ((($chrs{$c} == '"') || ($chrs{$c} == "'")) && ($top['what'] != SERVICES_JSON_IN_STR)) {
  799. // found a quote, and we are not inside a string
  800. array_push($stk, array('what' => SERVICES_JSON_IN_STR, 'where' => $c, 'delim' => $chrs{$c}));
  801. //print("Found start of string at {$c}\n");
  802. } elseif (($chrs{$c} == $top['delim']) &&
  803. ($top['what'] == SERVICES_JSON_IN_STR) &&
  804. ((strlen(substr($chrs, 0, $c)) - strlen(rtrim(substr($chrs, 0, $c), '\\'))) % 2 != 1)) {
  805. // found a quote, we're in a string, and it's not escaped
  806. // we know that it's not escaped becase there is _not_ an
  807. // odd number of backslashes at the end of the string so far
  808. array_pop($stk);
  809. //print("Found end of string at {$c}: ".substr($chrs, $top['where'], (1 + 1 + $c - $top['where']))."\n");
  810. } elseif (($chrs{$c} == '[') &&
  811. in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) {
  812. // found a left-bracket, and we are in an array, object, or slice
  813. array_push($stk, array('what' => SERVICES_JSON_IN_ARR, 'where' => $c, 'delim' => false));
  814. //print("Found start of array at {$c}\n");
  815. } elseif (($chrs{$c} == ']') && ($top['what'] == SERVICES_JSON_IN_ARR)) {
  816. // found a right-bracket, and we're in an array
  817. array_pop($stk);
  818. //print("Found end of array at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");
  819. } elseif (($chrs{$c} == '{') &&
  820. in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) {
  821. // found a left-brace, and we are in an array, object, or slice
  822. array_push($stk, array('what' => SERVICES_JSON_IN_OBJ, 'where' => $c, 'delim' => false));
  823. //print("Found start of object at {$c}\n");
  824. } elseif (($chrs{$c} == '}') && ($top['what'] == SERVICES_JSON_IN_OBJ)) {
  825. // found a right-brace, and we're in an object
  826. array_pop($stk);
  827. //print("Found end of object at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");
  828. } elseif (($substr_chrs_c_2 == '/*') &&
  829. in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) {
  830. // found a comment start, and we are in an array, object, or slice
  831. array_push($stk, array('what' => SERVICES_JSON_IN_CMT, 'where' => $c, 'delim' => false));
  832. $c++;
  833. //print("Found start of comment at {$c}\n");
  834. } elseif (($substr_chrs_c_2 == '*/') && ($top['what'] == SERVICES_JSON_IN_CMT)) {
  835. // found a comment end, and we're in one now
  836. array_pop($stk);
  837. $c++;
  838. for ($i = $top['where']; $i <= $c; ++$i)
  839. $chrs = substr_replace($chrs, ' ', $i, 1);
  840. //print("Found end of comment at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");
  841. }
  842. }
  843. if (reset($stk) == SERVICES_JSON_IN_ARR) {
  844. return $arr;
  845. } elseif (reset($stk) == SERVICES_JSON_IN_OBJ) {
  846. return $obj;
  847. }
  848. }
  849. }
  850. }
  851. function isError($data, $code = null)
  852. {
  853. if (class_exists('pear')) {
  854. return PEAR::isError($data, $code);
  855. } elseif (is_object($data) && (get_class($data) == 'services_json_error' ||
  856. is_subclass_of($data, 'services_json_error'))) {
  857. return true;
  858. }
  859. return false;
  860. }
  861. }
  862. class Akeeba_Services_JSON_Error
  863. {
  864. function Akeeba_Services_JSON_Error($message = 'unknown error', $code = null,
  865. $mode = null, $options = null, $userinfo = null)
  866. {
  867. }
  868. }
  869. }
  870. if(!function_exists('json_encode'))
  871. {
  872. function json_encode($value, $options = 0) {
  873. $flags = SERVICES_JSON_LOOSE_TYPE;
  874. if( $options & JSON_FORCE_OBJECT ) $flags = 0;
  875. $encoder = new Akeeba_Services_JSON($flags);
  876. return $encoder->encode($value);
  877. }
  878. }
  879. if(!function_exists('json_decode'))
  880. {
  881. function json_decode($value, $assoc = false)
  882. {
  883. $flags = 0;
  884. if($assoc) $flags = SERVICES_JSON_LOOSE_TYPE;
  885. $decoder = new Akeeba_Services_JSON($flags);
  886. return $decoder->decode($value);
  887. }
  888. }
  889. /**
  890. * Akeeba Restore
  891. * A JSON-powered JPA, JPS and ZIP archive extraction library
  892. *
  893. * @copyright 2010-2013 Nicholas K. Dionysopoulos / Akeeba Ltd.
  894. * @license GNU GPL v2 or - at your option - any later version
  895. * @package akeebabackup
  896. * @subpackage kickstart
  897. */
  898. /**
  899. * The base class of Akeeba Engine objects. Allows for error and warnings logging
  900. * and propagation. Largely based on the Joomla! 1.5 JObject class.
  901. */
  902. abstract class AKAbstractObject
  903. {
  904. /** @var array An array of errors */
  905. private $_errors = array();
  906. /** @var array The queue size of the $_errors array. Set to 0 for infinite size. */
  907. protected $_errors_queue_size = 0;
  908. /** @var array An array of warnings */
  909. private $_warnings = array();
  910. /** @var array The queue size of the $_warnings array. Set to 0 for infinite size. */
  911. protected $_warnings_queue_size = 0;
  912. /**
  913. * Public constructor, makes sure we are instanciated only by the factory class
  914. */
  915. public function __construct()
  916. {
  917. /*
  918. // Assisted Singleton pattern
  919. if(function_exists('debug_backtrace'))
  920. {
  921. $caller=debug_backtrace();
  922. if(
  923. ($caller[1]['class'] != 'AKFactory') &&
  924. ($caller[2]['class'] != 'AKFactory') &&
  925. ($caller[3]['class'] != 'AKFactory') &&
  926. ($caller[4]['class'] != 'AKFactory')
  927. ) {
  928. var_dump(debug_backtrace());
  929. trigger_error("You can't create direct descendants of ".__CLASS__, E_USER_ERROR);
  930. }
  931. }
  932. */
  933. }
  934. /**
  935. * Get the most recent error message
  936. * @param integer $i Optional error index
  937. * @return string Error message
  938. */
  939. public function getError($i = null)
  940. {
  941. return $this->getItemFromArray($this->_errors, $i);
  942. }
  943. /**
  944. * Return all errors, if any
  945. * @return array Array of error messages
  946. */
  947. public function getErrors()
  948. {
  949. return $this->_errors;
  950. }
  951. /**
  952. * Add an error message
  953. * @param string $error Error message
  954. */
  955. public function setError($error)
  956. {
  957. if($this->_errors_queue_size > 0)
  958. {
  959. if(count($this->_errors) >= $this->_errors_queue_size)
  960. {
  961. array_shift($this->_errors);
  962. }
  963. }
  964. array_push($this->_errors, $error);
  965. }
  966. /**
  967. * Resets all error messages
  968. */
  969. public function resetErrors()
  970. {
  971. $this->_errors = array();
  972. }
  973. /**
  974. * Get the most recent warning message
  975. * @param integer $i Optional warning index
  976. * @return string Error message
  977. */
  978. public function getWarning($i = null)
  979. {
  980. return $this->getItemFromArray($this->_warnings, $i);
  981. }
  982. /**
  983. * Return all warnings, if any
  984. * @return array Array of error messages
  985. */
  986. public function getWarnings()
  987. {
  988. return $this->_warnings;
  989. }
  990. /**
  991. * Add an error message
  992. * @param string $error Error message
  993. */
  994. public function setWarning($warning)
  995. {
  996. if($this->_warnings_queue_size > 0)
  997. {
  998. if(count($this->_warnings) >= $this->_warnings_queue_size)
  999. {
  1000. array_shift($this->_warnings);
  1001. }
  1002. }
  1003. array_push($this->_warnings, $warning);
  1004. }
  1005. /**
  1006. * Resets all warning messages
  1007. */
  1008. public function resetWarnings()
  1009. {
  1010. $this->_warnings = array();
  1011. }
  1012. /**
  1013. * Propagates errors and warnings to a foreign object. The foreign object SHOULD
  1014. * implement the setError() and/or setWarning() methods but DOESN'T HAVE TO be of
  1015. * AKAbstractObject type. For example, this can even be used to propagate to a
  1016. * JObject instance in Joomla!. Propagated items will be removed from ourself.
  1017. * @param object $object The object to propagate errors and warnings to.
  1018. */
  1019. public function propagateToObject(&$object)
  1020. {
  1021. // Skip non-objects
  1022. if(!is_object($object)) return;
  1023. if( method_exists($object,'setError') )
  1024. {
  1025. if(!empty($this->_errors))
  1026. {
  1027. foreach($this->_errors as $error)
  1028. {
  1029. $object->setError($error);
  1030. }
  1031. $this->_errors = array();
  1032. }
  1033. }
  1034. if( method_exists($object,'setWarning') )
  1035. {
  1036. if(!empty($this->_warnings))
  1037. {
  1038. foreach($this->_warnings as $warning)
  1039. {
  1040. $object->setWarning($warning);
  1041. }
  1042. $this->_warnings = array();
  1043. }
  1044. }
  1045. }
  1046. /**
  1047. * Propagates errors and warnings from a foreign object. Each propagated list is
  1048. * then cleared on the foreign object, as long as it implements resetErrors() and/or
  1049. * resetWarnings() methods.
  1050. * @param object $object The object to propagate errors and warnings from
  1051. */
  1052. public function propagateFromObject(&$object)
  1053. {
  1054. if( method_exists($object,'getErrors') )
  1055. {
  1056. $errors = $object->getErrors();
  1057. if(!empty($errors))
  1058. {
  1059. foreach($errors as $error)
  1060. {
  1061. $this->setError($error);
  1062. }
  1063. }
  1064. if(method_exists($object,'resetErrors'))
  1065. {
  1066. $object->resetErrors();
  1067. }
  1068. }
  1069. if( method_exists($object,'getWarnings') )
  1070. {
  1071. $warnings = $object->getWarnings();
  1072. if(!empty($warnings))
  1073. {
  1074. foreach($warnings as $warning)
  1075. {
  1076. $this->setWarning($warning);
  1077. }
  1078. }
  1079. if(method_exists($object,'resetWarnings'))
  1080. {
  1081. $object->resetWarnings();
  1082. }
  1083. }
  1084. }
  1085. /**
  1086. * Sets the size of the error queue (acts like a LIFO buffer)
  1087. * @param int $newSize The new queue size. Set to 0 for infinite length.
  1088. */
  1089. protected function setErrorsQueueSize($newSize = 0)
  1090. {
  1091. $this->_errors_queue_size = (int)$newSize;
  1092. }
  1093. /**
  1094. * Sets the size of the warnings queue (acts like a LIFO buffer)
  1095. * @param int $newSize The new queue size. Set to 0 for infinite length.
  1096. */
  1097. protected function setWarningsQueueSize($newSize = 0)
  1098. {
  1099. $this->_warnings_queue_size = (int)$newSize;
  1100. }
  1101. /**
  1102. * Returns the last item of a LIFO string message queue, or a specific item
  1103. * if so specified.
  1104. * @param array $array An array of strings, holding messages
  1105. * @param int $i Optional message index
  1106. * @return mixed The message string, or false if the key doesn't exist
  1107. */
  1108. private function getItemFromArray($array, $i = null)
  1109. {
  1110. // Find the item
  1111. if ( $i === null) {
  1112. // Default, return the last item
  1113. $item = end($array);
  1114. }
  1115. else
  1116. if ( ! array_key_exists($i, $array) ) {
  1117. // If $i has been specified but does not exist, return false
  1118. return false;
  1119. }
  1120. else
  1121. {
  1122. $item = $array[$i];
  1123. }
  1124. return $item;
  1125. }
  1126. }
  1127. /**
  1128. * Akeeba Restore
  1129. * A JSON-powered JPA, JPS and ZIP archive extraction library
  1130. *
  1131. * @copyright 2010-2013 Nicholas K. Dionysopoulos / Akeeba Ltd.
  1132. * @license GNU GPL v2 or - at your option - any later version
  1133. * @package akeebabackup
  1134. * @subpackage kickstart
  1135. */
  1136. /**
  1137. * The superclass of all Akeeba Kickstart parts. The "parts" are intelligent stateful
  1138. * classes which perform a single procedure and have preparation, running and
  1139. * finalization phases. The transition between phases is handled automatically by
  1140. * this superclass' tick() final public method, which should be the ONLY public API
  1141. * exposed to the rest of the Akeeba Engine.
  1142. */
  1143. abstract class AKAbstractPart extends AKAbstractObject
  1144. {
  1145. /**
  1146. * Indicates whether this part has finished its initialisation cycle
  1147. * @var boolean
  1148. */
  1149. protected $isPrepared = false;
  1150. /**
  1151. * Indicates whether this part has more work to do (it's in running state)
  1152. * @var boolean
  1153. */
  1154. protected $isRunning = false;
  1155. /**
  1156. * Indicates whether this part has finished its finalization cycle
  1157. * @var boolean
  1158. */
  1159. protected $isFinished = false;
  1160. /**
  1161. * Indicates whether this part has finished its run cycle
  1162. * @var boolean
  1163. */
  1164. protected $hasRan = false;
  1165. /**
  1166. * The name of the engine part (a.k.a. Domain), used in return table
  1167. * generation.
  1168. * @var string
  1169. */
  1170. protected $active_domain = "";
  1171. /**
  1172. * The step this engine part is in. Used verbatim in return table and
  1173. * should be set by the code in the _run() method.
  1174. * @var string
  1175. */
  1176. protected $active_step = "";
  1177. /**
  1178. * A more detailed description of the step this engine part is in. Used
  1179. * verbatim in return table and should be set by the code in the _run()
  1180. * method.
  1181. * @var string
  1182. */
  1183. protected $active_substep = "";
  1184. /**
  1185. * Any configuration variables, in the form of an array.
  1186. * @var array
  1187. */
  1188. protected $_parametersArray = array();
  1189. /** @var string The database root key */
  1190. protected $databaseRoot = array();
  1191. /** @var int Last reported warnings's position in array */
  1192. private $warnings_pointer = -1;
  1193. /** @var array An array of observers */
  1194. protected $observers = array();
  1195. /**
  1196. * Runs the preparation for this part. Should set _isPrepared
  1197. * to true
  1198. */
  1199. abstract protected function _prepare();
  1200. /**
  1201. * Runs the finalisation process for this part. Should set
  1202. * _isFinished to true.
  1203. */
  1204. abstract protected function _finalize();
  1205. /**
  1206. * Runs the main functionality loop for this part. Upon calling,
  1207. * should set the _isRunning to true. When it finished, should set
  1208. * the _hasRan to true. If an error is encountered, setError should
  1209. * be used.
  1210. */
  1211. abstract protected function _run();
  1212. /**
  1213. * Sets the BREAKFLAG, which instructs this engine part that the current step must break immediately,
  1214. * in fear of timing out.
  1215. */
  1216. protected function setBreakFlag()
  1217. {
  1218. AKFactory::set('volatile.breakflag', true);
  1219. }
  1220. /**
  1221. * Sets the engine part's internal state, in an easy to use manner
  1222. *
  1223. * @param string $state One of init, prepared, running, postrun, finished, error
  1224. * @param string $errorMessage The reported error message, should the state be set to error
  1225. */
  1226. protected function setState($state = 'init', $errorMessage='Invalid setState argument')
  1227. {
  1228. switch($state)
  1229. {
  1230. case 'init':
  1231. $this->isPrepared = false;
  1232. $this->isRunning = false;
  1233. $this->isFinished = false;
  1234. $this->hasRun = false;
  1235. break;
  1236. case 'prepared':
  1237. $this->isPrepared = true;
  1238. $this->isRunning = false;
  1239. $this->isFinished = false;
  1240. $this->hasRun = false;
  1241. break;
  1242. case 'running':
  1243. $this->isPrepared = true;
  1244. $this->isRunning = true;
  1245. $this->isFinished = false;
  1246. $this->hasRun = false;
  1247. break;
  1248. case 'postrun':
  1249. $this->isPrepared = true;
  1250. $this->isRunning = false;
  1251. $this->isFinished = false;
  1252. $this->hasRun = true;
  1253. break;
  1254. case 'finished':
  1255. $this->isPrepared = true;
  1256. $this->isRunning = false;
  1257. $this->isFinished = true;
  1258. $this->hasRun = false;
  1259. break;
  1260. case 'error':
  1261. default:
  1262. $this->setError($errorMessage);
  1263. break;
  1264. }
  1265. }
  1266. /**
  1267. * The public interface to an engine part. This method takes care for
  1268. * calling the correct method in order to perform the initialisation -
  1269. * run - finalisation cycle of operation and return a proper reponse array.
  1270. * @return array A Reponse Array
  1271. */
  1272. final public function tick()
  1273. {
  1274. // Call the right action method, depending on engine part state
  1275. switch( $this->getState() )
  1276. {
  1277. case "init":
  1278. $this->_prepare();
  1279. break;
  1280. case "prepared":
  1281. $this->_run();
  1282. break;
  1283. case "running":
  1284. $this->_run();
  1285. break;
  1286. case "postrun":
  1287. $this->_finalize();
  1288. break;
  1289. }
  1290. // Send a Return Table back to the caller
  1291. $out = $this->_makeReturnTable();
  1292. return $out;
  1293. }
  1294. /**
  1295. * Returns a copy of the class's status array
  1296. * @return array
  1297. */
  1298. public function getStatusArray()
  1299. {
  1300. return $this->_makeReturnTable();
  1301. }
  1302. /**
  1303. * Sends any kind of setup information to the engine part. Using this,
  1304. * we avoid passing parameters to the constructor of the class. These
  1305. * parameters should be passed as an indexed array and should be taken
  1306. * into account during the preparation process only. This function will
  1307. * set the error flag if it's called after the engine part is prepared.
  1308. *
  1309. * @param array $parametersArray The parameters to be passed to the
  1310. * engine part.
  1311. */
  1312. final public function setup( $parametersArray )
  1313. {
  1314. if( $this->isPrepared )
  1315. {
  1316. $this->setState('error', "Can't modify configuration after the preparation of " . $this->active_domain);
  1317. }
  1318. else
  1319. {
  1320. $this->_parametersArray = $parametersArray;
  1321. if(array_key_exists('root', $parametersArray))
  1322. {
  1323. $this->databaseRoot = $parametersArray['root'];
  1324. }
  1325. }
  1326. }
  1327. /**
  1328. * Returns the state of this engine part.
  1329. *
  1330. * @return string The state of this engine part. It can be one of
  1331. * error, init, prepared, running, postrun, finished.
  1332. */
  1333. final public function getState()
  1334. {
  1335. if( $this->getError() )
  1336. {
  1337. return "error";
  1338. }
  1339. if( !($this->isPrepared) )
  1340. {
  1341. return "init";
  1342. }
  1343. if( !($this->isFinished) && !($this->isRunning) && !( $this->hasRun ) && ($this->isPrepared) )
  1344. {
  1345. return "prepared";
  1346. }
  1347. if ( !($this->isFinished) && $this->isRunning && !( $this->hasRun ) )
  1348. {
  1349. return "running";
  1350. }
  1351. if ( !($this->isFinished) && !($this->isRunning) && $this->hasRun )
  1352. {
  1353. return "postrun";
  1354. }
  1355. if ( $this->isFinished )
  1356. {
  1357. return "finished";
  1358. }
  1359. }
  1360. /**
  1361. * Constructs a Response Array based on the engine part's state.
  1362. * @return array The Response Array for the current state
  1363. */
  1364. final protected function _makeReturnTable()
  1365. {
  1366. // Get a list of warnings
  1367. $warnings = $this->getWarnings();
  1368. // Report only new warnings if there is no warnings queue size
  1369. if( $this->_warnings_queue_size == 0 )
  1370. {
  1371. if( ($this->warnings_pointer > 0) && ($this->warnings_pointer < (count($warnings)) ) )
  1372. {
  1373. $warnings = array_slice($warnings, $this->warnings_pointer + 1);
  1374. $this->warnings_pointer += count($warnings);
  1375. }
  1376. else
  1377. {
  1378. $this->warnings_pointer = count($warnings);
  1379. }
  1380. }
  1381. $out = array(
  1382. 'HasRun' => (!($this->isFinished)),
  1383. 'Domain' => $this->active_domain,
  1384. 'Step' => $this->active_step,
  1385. 'Substep' => $this->active_substep,
  1386. 'Error' => $this->getError(),
  1387. 'Warnings' => $warnings
  1388. );
  1389. return $out;
  1390. }
  1391. final protected function setDomain($new_domain)
  1392. {
  1393. $this->active_domain = $new_domain;
  1394. }
  1395. final public function getDomain()
  1396. {
  1397. return $this->active_domain;
  1398. }
  1399. final protected function setStep($new_step)
  1400. {
  1401. $this->active_step = $new_step;
  1402. }
  1403. final public function getStep()
  1404. {
  1405. return $this->active_step;
  1406. }
  1407. final protected function setSubstep($new_substep)
  1408. {
  1409. $this->active_substep = $new_substep;
  1410. }
  1411. final public function getSubstep()
  1412. {
  1413. return $this->active_substep;
  1414. }
  1415. /**
  1416. * Attaches an observer object
  1417. * @param AKAbstractPartObserver $obs
  1418. */
  1419. function attach(AKAbstractPartObserver $obs) {
  1420. $this->observers["$obs"] = $obs;
  1421. }
  1422. /**
  1423. * Dettaches an observer object
  1424. * @param AKAbstractPartObserver $obs
  1425. */
  1426. function detach(AKAbstractPartObserver $obs) {
  1427. delete($this->observers["$obs"]);
  1428. }
  1429. /**
  1430. * Notifies observers each time something interesting happened to the part
  1431. * @param mixed $message The event object
  1432. */
  1433. protected function notify($message) {
  1434. foreach ($this->observers as $obs) {
  1435. $obs->update($this, $message);
  1436. }
  1437. }
  1438. }
  1439. /**
  1440. * Akeeba Restore
  1441. * A JSON-powered JPA, JPS and ZIP archive extraction library
  1442. *
  1443. * @copyright 2010-2013 Nicholas K. Dionysopoulos / Akeeba Ltd.
  1444. * @license GNU GPL v2 or - at your option - any later version
  1445. * @package akeebabackup
  1446. * @subpackage kickstart
  1447. */
  1448. /**
  1449. * The base class of unarchiver classes
  1450. */
  1451. abstract class AKAbstractUnarchiver extends AKAbstractPart
  1452. {
  1453. /** @var string Archive filename */
  1454. protected $filename = null;
  1455. /** @var array List of the names of all archive parts */
  1456. public $archiveList = array();
  1457. /** @var int The total size of all archive parts */
  1458. public $totalSize = array();
  1459. /** @var integer Current archive part number */
  1460. protected $currentPartNumber = -1;
  1461. /** @var integer The offset inside the current part */
  1462. protected $currentPartOffset = 0;
  1463. /** @var bool Should I restore permissions? */
  1464. protected $flagRestorePermissions = false;
  1465. /** @var AKAbstractPostproc Post processing class */
  1466. protected $postProcEngine = null;
  1467. /** @var string Absolute path to prepend to extracted files */
  1468. protected $addPath = '';
  1469. /** @var array Which files to rename */
  1470. public $renameFiles = array();
  1471. /** @var array Which directories to rename */
  1472. public $renameDirs = array();
  1473. /** @var array Which files to skip */
  1474. public $skipFiles = array();
  1475. /** @var integer Chunk size for processing */
  1476. protected $chunkSize = 524288;
  1477. /** @var resource File pointer to the current archive part file */
  1478. protected $fp = null;
  1479. /** @var int Run state when processing the current archive file */
  1480. protected $runState = null;
  1481. /** @var stdClass File header data, as read by the readFileHeader() method */
  1482. protected $fileHeader = null;
  1483. /** @var int How much of the uncompressed data we've read so far */
  1484. protected $dataReadLength = 0;
  1485. /** @var array Unwriteable files in these directories are always ignored and do not cause errors when not extracted */
  1486. protected $ignoreDirectories = array();
  1487. /**
  1488. * Public constructor
  1489. */
  1490. public function __construct()
  1491. {
  1492. parent::__construct();
  1493. }
  1494. /**
  1495. * Wakeup function, called whenever the class is unserialized
  1496. */
  1497. public function __wakeup()
  1498. {
  1499. if($this->currentPartNumber >= 0)
  1500. {
  1501. $this->fp = @fopen($this->archiveList[$this->currentPartNumber], 'rb');
  1502. if( (is_resource($this->fp)) && ($this->currentPartOffset > 0) )
  1503. {
  1504. @fseek($this->fp, $this->currentPartOffset);
  1505. }
  1506. }
  1507. }
  1508. /**
  1509. * Sleep function, called whenever the class is serialized
  1510. */
  1511. public function shutdown()
  1512. {
  1513. if(is_resource($this->fp))
  1514. {
  1515. $this->currentPartOffset = @ftell($this->fp);
  1516. @fclose($this->fp);
  1517. }
  1518. }
  1519. /**
  1520. * Implements the abstract _prepare() method
  1521. */
  1522. final protected function _prepare()
  1523. {
  1524. parent::__construct();
  1525. if( count($this->_parametersArray) > 0 )
  1526. {
  1527. foreach($this->_parametersArray as $key => $value)
  1528. {
  1529. switch($key)
  1530. {
  1531. // Archive's absolute filename
  1532. case 'filename':
  1533. $this->filename = $value;
  1534. break;
  1535. // Should I restore permissions?
  1536. case 'restore_permissions':
  1537. $this->flagRestorePermissions = $value;
  1538. break;
  1539. // Should I use FTP?
  1540. case 'post_proc':
  1541. $this->postProcEngine = AKFactory::getpostProc($value);
  1542. break;
  1543. // Path to add in the beginning
  1544. case 'add_path':
  1545. $this->addPath = $value;
  1546. $this->addPath = str_replace('\\','/',$this->addPath);
  1547. $this->addPath = rtrim($this->addPath,'/');
  1548. if(!empty($this->addPath)) $this->addPath .= '/';
  1549. break;
  1550. // Which files to rename (hash array)
  1551. case 'rename_files':
  1552. $this->renameFiles = $value;
  1553. break;
  1554. // Which files to rename (hash array)
  1555. case 'rename_dirs':
  1556. $this->renameDirs = $value;
  1557. break;
  1558. // Which files to skip (indexed array)
  1559. case 'skip_files':
  1560. $this->skipFiles = $value;
  1561. break;
  1562. // Which directories to ignore when we can't write files in them (indexed array)
  1563. case 'ignoredirectories':
  1564. $this->ignoreDirectories = $value;
  1565. break;
  1566. }
  1567. }
  1568. }
  1569. $this->scanArchives();
  1570. $this->readArchiveHeader();
  1571. $errMessage = $this->getError();
  1572. if(!empty($errMessage))
  1573. {
  1574. $this->setState('error', $errMessage);
  1575. }
  1576. else
  1577. {
  1578. $this->runState = AK_STATE_NOFILE;
  1579. $this->setState('prepared');
  1580. }
  1581. }
  1582. protected function _run()
  1583. {
  1584. if($this->getState() == 'postrun') return;
  1585. $this->setState('running');
  1586. $timer = AKFactory::getTimer();
  1587. $status = true;
  1588. while( $status && ($timer->getTimeLeft() > 0) )
  1589. {
  1590. switch( $this->runState )
  1591. {
  1592. case AK_STATE_NOFILE:
  1593. debugMsg(__CLASS__.'::_run() - Reading file header');
  1594. $status = $this->readFileHeader();
  1595. if($status)
  1596. {
  1597. debugMsg(__CLASS__.'::_run() - Preparing to extract '.$this->fileHeader->realFile);
  1598. // Send start of file notification
  1599. $message = new stdClass;
  1600. $message->type = 'startfile';
  1601. $message->content = new stdClass;
  1602. if( array_key_exists('realfile', get_object_vars($this->fileHeader)) ) {
  1603. $message->content->realfile = $this->fileHeader->realFile;
  1604. } else {
  1605. $message->content->realfile = $this->fileHeader->file;
  1606. }
  1607. $message->content->file = $this->fileHeader->file;
  1608. if( array_key_exists('compressed', get_object_vars($this->fileHeader)) ) {
  1609. $message->content->compressed = $this->fileHeader->compressed;
  1610. } else {
  1611. $message->content->compressed = 0;
  1612. }
  1613. $message->content->uncompressed = $this->fileHeader->uncompressed;
  1614. $this->notify($message);
  1615. } else {
  1616. debugMsg(__CLASS__.'::_run() - Could not read file header');
  1617. }
  1618. break;
  1619. case AK_STATE_HEADER:
  1620. case AK_STATE_DATA:
  1621. debugMsg(__CLASS__.'::_run() - Processing file data');
  1622. $status = $this->processFileData();
  1623. break;
  1624. case AK_STATE_DATAREAD:
  1625. case AK_STATE_POSTPROC:
  1626. debugMsg(__CLASS__.'::_run() - Calling post-processing class');
  1627. $this->postProcEngine->timestamp = $this->fileHeader->timestamp;
  1628. $status = $this->postProcEngine->process();
  1629. $this->propagateFromObject( $this->postProcEngine );
  1630. $this->runState = AK_STATE_DONE;
  1631. break;
  1632. case AK_STATE_DONE:
  1633. default:
  1634. if($status)
  1635. {
  1636. debugMsg(__CLASS__.'::_run() - Finished extracting file');
  1637. // Send end of file notification
  1638. $message = new stdClass;
  1639. $message->type = 'endfile';
  1640. $message->content = new stdClass;
  1641. if( array_key_exists('realfile', get_object_vars($this->fileHeader)) ) {
  1642. $message->content->realfile = $this->fileHeader->realFile;
  1643. } else {
  1644. $message->content->realfile = $this->fileHeader->file;
  1645. }
  1646. $message->content->file = $this->fileHeader->file;
  1647. if( array_key_exists('compressed', get_object_vars($this->fileHeader)) ) {
  1648. $message->content->compressed = $this->fileHeader->compressed;
  1649. } else {
  1650. $message->content->compressed = 0;
  1651. }
  1652. $message->content->uncompressed = $this->fileHeader->uncompressed;
  1653. $this->notify($message);
  1654. }
  1655. $this->runState = AK_STATE_NOFILE;
  1656. continue;
  1657. }
  1658. }
  1659. $error = $this->getError();
  1660. if( !$status && ($this->runState == AK_STATE_NOFILE) && empty( $error ) )
  1661. {
  1662. debugMsg(__CLASS__.'::_run() - Just finished');
  1663. // We just finished
  1664. $this->setState('postrun');
  1665. }
  1666. elseif( !empty($error) )
  1667. {
  1668. debugMsg(__CLASS__.'::_run() - Halted with an error:');
  1669. debugMsg($error);
  1670. $this->setState( 'error', $error );
  1671. }
  1672. }
  1673. protected function _finalize()
  1674. {
  1675. // Nothing to do
  1676. $this->setState('finished');
  1677. }
  1678. /**
  1679. * Returns the base extension of the file, e.g. '.jpa'
  1680. * @return string
  1681. */
  1682. private function getBaseExtension()
  1683. {
  1684. static $baseextension;
  1685. if(empty($baseextension))
  1686. {
  1687. $basename = basename($this->filename);
  1688. $lastdot = strrpos($basename,'.');
  1689. $baseextension = substr($basename, $lastdot);
  1690. }
  1691. return $baseextension;
  1692. }
  1693. /**
  1694. * Scans for archive parts
  1695. */
  1696. private function scanArchives()
  1697. {
  1698. if(defined('KSDEBUG')) {
  1699. @unlink('debug.txt');
  1700. }
  1701. debugMsg('Preparing to scan archives');
  1702. $privateArchiveList = array();
  1703. // Get the components of the archive filename
  1704. $dirname = dirname($this->filename);
  1705. $base_extension = $this->getBaseExtension();
  1706. $basename = basename($this->filename, $base_extension);
  1707. $this->totalSize = 0;
  1708. // Scan for multiple parts until we don't find any more of them
  1709. $count = 0;
  1710. $found = true;
  1711. $this->archiveList = array();
  1712. while($found)
  1713. {
  1714. ++$count;
  1715. $extension = substr($base_extension, 0, 2).sprintf('%02d', $count);
  1716. $filename = $dirname.DIRECTORY_SEPARATOR.$basename.$extension;
  1717. $found = file_exists($filename);
  1718. if($found)
  1719. {
  1720. debugMsg('- Found archive '.$filename);
  1721. // Add yet another part, with a numeric-appended filename
  1722. $this->archiveList[] = $filename;
  1723. $filesize = @filesize($filename);
  1724. $this->totalSize += $filesize;
  1725. $privateArchiveList[] = array($filename, $filesize);
  1726. }
  1727. else
  1728. {
  1729. debugMsg('- Found archive '.$this->filename);
  1730. // Add the last part, with the regular extension
  1731. $this->archiveList[] = $this->filename;
  1732. $filename = $this->filename;
  1733. $filesize = @filesize($filename);
  1734. $this->totalSize += $filesize;
  1735. $privateArchiveList[] = array($filename, $filesize);
  1736. }
  1737. }
  1738. debugMsg('Total archive parts: '.$count);
  1739. $this->currentPartNumber = -1;
  1740. $this->currentPartOffset = 0;
  1741. $this->runState = AK_STATE_NOFILE;
  1742. // Send start of file notification
  1743. $message = new stdClass;
  1744. $message->type = 'totalsize';
  1745. $message->content = new stdClass;
  1746. $message->content->totalsize = $this->totalSize;
  1747. $message->content->filelist = $privateArchiveList;
  1748. $this->notify($message);
  1749. }
  1750. /**
  1751. * Opens the next part file for reading
  1752. */
  1753. protected function nextFile()
  1754. {
  1755. debugMsg('Current part is '.$this->currentPartNumber.'; opening the next part');
  1756. ++$this->currentPartNumber;
  1757. if( $this->currentPartNumber > (count($this->archiveList) - 1) )
  1758. {
  1759. $this->setState('postrun');
  1760. return false;
  1761. }
  1762. else
  1763. {
  1764. if( is_resource($this->fp) ) @fclose($this->fp);
  1765. debugMsg('Opening file '.$this->archiveList[$this->currentPartNumber]);
  1766. $this->fp = @fopen( $this->archiveList[$this->currentPartNumber], 'rb' );
  1767. if($this->fp === false) {
  1768. debugMsg('Could not open file - crash imminent');
  1769. }
  1770. fseek($this->fp, 0);
  1771. $this->currentPartOffset = 0;
  1772. return true;
  1773. }
  1774. }
  1775. /**
  1776. * Returns true if we have reached the end of file
  1777. * @param $local bool True to return EOF of the local file, false (default) to return if we have reached the end of the archive set
  1778. * @return bool True if we have reached End Of File
  1779. */
  1780. protected function isEOF($local = false)
  1781. {
  1782. $eof = @feof($this->fp);
  1783. if(!$eof)
  1784. {
  1785. // Border case: right at the part's end (eeeek!!!). For the life of me, I don't understand why
  1786. // feof() doesn't report true. It expects the fp to be positioned *beyond* the EOF to report
  1787. // true. Incredible! :(
  1788. $position = @ftell($this->fp);
  1789. $filesize = @filesize( $this->archiveList[$this->currentPartNumber] );
  1790. if($filesize <= 0) {
  1791. // 2Gb or more files on a 32 bit version of PHP tend to get screwed up. Meh.
  1792. $eof = false;
  1793. } elseif( $position >= $filesize ) {
  1794. $eof = true;
  1795. }
  1796. }
  1797. if($local)
  1798. {
  1799. return $eof;
  1800. }
  1801. else
  1802. {
  1803. return $eof && ($this->currentPartNumber >= (count($this->archiveList)-1) );
  1804. }
  1805. }
  1806. /**
  1807. * Tries to make a directory user-writable so that we can write a file to it
  1808. * @param $path string A path to a file
  1809. */
  1810. protected function setCorrectPermissions($path)
  1811. {
  1812. static $rootDir = null;
  1813. if(is_null($rootDir)) {
  1814. $rootDir = rtrim(AKFactory::get('kickstart.setup.destdir',''),'/\\');
  1815. }
  1816. $directory = rtrim(dirname($path),'/\\');
  1817. if($directory != $rootDir) {
  1818. // Is this an unwritable directory?
  1819. if(!is_writeable($directory)) {
  1820. $this->postProcEngine->chmod( $directory, 0755 );
  1821. }
  1822. }
  1823. $this->postProcEngine->chmod( $path, 0644 );
  1824. }
  1825. /**
  1826. * Concrete classes are supposed to use this method in order to read the archive's header and
  1827. * prepare themselves to the point of being ready to extract the first file.
  1828. */
  1829. protected abstract function readArchiveHeader();
  1830. /**
  1831. * Concrete classes must use this method to read the file header
  1832. * @return bool True if reading the file was successful, false if an error occured or we reached end of archive
  1833. */
  1834. protected abstract function readFileHeader();
  1835. /**
  1836. * Concrete classes must use this method to process file data. It must set $runState to AK_STATE_DATAREAD when
  1837. * it's finished processing the file data.
  1838. * @return bool True if processing the file data was successful, false if an error occured
  1839. */
  1840. protected abstract function processFileData();
  1841. /**
  1842. * Reads data from the archive and notifies the observer with the 'reading' message
  1843. * @param $fp
  1844. * @param $length
  1845. */
  1846. protected function fread($fp, $length = null)
  1847. {
  1848. if(is_numeric($length))
  1849. {
  1850. if($length > 0) {
  1851. $data = fread($fp, $length);
  1852. } else {
  1853. $data = fread($fp, PHP_INT_MAX);
  1854. }
  1855. }
  1856. else
  1857. {
  1858. $data = fread($fp, PHP_INT_MAX);
  1859. }
  1860. if($data === false) $data = '';
  1861. // Send start of file notification
  1862. $message = new stdClass;
  1863. $message->type = 'reading';
  1864. $message->content = new stdClass;
  1865. $message->content->length = strlen($data);
  1866. $this->notify($message);
  1867. return $data;
  1868. }
  1869. /**
  1870. * Is this file or directory contained in a directory we've decided to ignore
  1871. * write errors for? This is useful to let the extraction work despite write
  1872. * errors in the log, logs and tmp directories which MIGHT be used by the system
  1873. * on some low quality hosts and Plesk-powered hosts.
  1874. *
  1875. * @param string $shortFilename The relative path of the file/directory in the package
  1876. *
  1877. * @return boolean True if it belongs in an ignored directory
  1878. */
  1879. public function isIgnoredDirectory($shortFilename)
  1880. {
  1881. return false;
  1882. if (substr($shortFilename, -1) == '/')
  1883. {
  1884. $check = substr($shortFilename, 0, -1);
  1885. }
  1886. else
  1887. {
  1888. $check = dirname($shortFilename);
  1889. }
  1890. return in_array($check, $this->ignoreDirectories);
  1891. }
  1892. }
  1893. /**
  1894. * Akeeba Restore
  1895. * A JSON-powered JPA, JPS and ZIP archive extraction library
  1896. *
  1897. * @copyright 2010-2013 Nicholas K. Dionysopoulos / Akeeba Ltd.
  1898. * @license GNU GPL v2 or - at your option - any later version
  1899. * @package akeebabackup
  1900. * @subpackage kickstart
  1901. */
  1902. /**
  1903. * File post processor engines base class
  1904. */
  1905. abstract class AKAbstractPostproc extends AKAbstractObject
  1906. {
  1907. /** @var string The current (real) file path we'll have to process */
  1908. protected $filename = null;
  1909. /** @var int The requested permissions */
  1910. protected $perms = 0755;
  1911. /** @var string The temporary file path we gave to the unarchiver engine */
  1912. protected $tempFilename = null;
  1913. /** @var int The UNIX timestamp of the file's desired modification date */
  1914. public $timestamp = 0;
  1915. /**
  1916. * Processes the current file, e.g. moves it from temp to final location by FTP
  1917. */
  1918. abstract public function process();
  1919. /**
  1920. * The unarchiver tells us the path to the filename it wants to extract and we give it
  1921. * a different path instead.
  1922. * @param string $filename The path to the real file
  1923. * @param int $perms The permissions we need the file to have
  1924. * @return string The path to the temporary file
  1925. */
  1926. abstract public function processFilename($filename, $perms = 0755);
  1927. /**
  1928. * Recursively creates a directory if it doesn't exist
  1929. * @param string $dirName The directory to create
  1930. * @param int $perms The permissions to give to that directory
  1931. */
  1932. abstract public function createDirRecursive( $dirName, $perms );
  1933. abstract public function chmod( $file, $perms );
  1934. abstract public function unlink( $file );
  1935. abstract public function rmdir( $directory );
  1936. abstract public function rename( $from, $to );
  1937. }
  1938. /**
  1939. * Akeeba Restore
  1940. * A JSON-powered JPA, JPS and ZIP archive extraction library
  1941. *
  1942. * @copyright 2010-2013 Nicholas K. Dionysopoulos / Akeeba Ltd.
  1943. * @license GNU GPL v2 or - at your option - any later version
  1944. * @package akeebabackup
  1945. * @subpackage kickstart
  1946. */
  1947. /**
  1948. * Descendants of this class can be used in the unarchiver's observer methods (attach, detach and notify)
  1949. * @author Nicholas
  1950. *
  1951. */
  1952. abstract class AKAbstractPartObserver
  1953. {
  1954. abstract public function update($object, $message);
  1955. }
  1956. /**
  1957. * Akeeba Restore
  1958. * A JSON-powered JPA, JPS and ZIP archive extraction library
  1959. *
  1960. * @copyright 2010-2013 Nicholas K. Dionysopoulos / Akeeba Ltd.
  1961. * @license GNU GPL v2 or - at your option - any later version
  1962. * @package akeebabackup
  1963. * @subpackage kickstart
  1964. */
  1965. /**
  1966. * Direct file writer
  1967. */
  1968. class AKPostprocDirect extends AKAbstractPostproc
  1969. {
  1970. public function process()
  1971. {
  1972. $restorePerms = AKFactory::get('kickstart.setup.restoreperms', false);
  1973. if($restorePerms)
  1974. {
  1975. @chmod($this->filename, $this->perms);
  1976. }
  1977. else
  1978. {
  1979. if(@is_file($this->filename))
  1980. {
  1981. @chmod($this->filename, 0644);
  1982. }
  1983. else
  1984. {
  1985. @chmod($this->filename, 0755);
  1986. }
  1987. }
  1988. if($this->timestamp > 0)
  1989. {
  1990. @touch($this->filename, $this->timestamp);
  1991. }
  1992. return true;
  1993. }
  1994. public function processFilename($filename, $perms = 0755)
  1995. {
  1996. $this->perms = $perms;
  1997. $this->filename = $filename;
  1998. return $filename;
  1999. }
  2000. public function createDirRecursive( $dirName, $perms )
  2001. {
  2002. if( AKFactory::get('kickstart.setup.dryrun','0') ) return true;
  2003. if (@mkdir($dirName, 0755, true)) {
  2004. @chmod($dirName, 0755);
  2005. return true;
  2006. }
  2007. $root = AKFactory::get('kickstart.setup.destdir');
  2008. $root = rtrim(str_replace('\\','/',$root),'/');
  2009. $dir = rtrim(str_replace('\\','/',$dirName),'/');
  2010. if(strpos($dir, $root) === 0) {
  2011. $dir = ltrim(substr($dir, strlen($root)), '/');
  2012. $root .= '/';
  2013. } else {
  2014. $root = '';
  2015. }
  2016. if(empty($dir)) return true;
  2017. $dirArray = explode('/', $dir);
  2018. $path = '';
  2019. foreach( $dirArray as $dir )
  2020. {
  2021. $path .= $dir . '/';
  2022. $ret = is_dir($root.$path) ? true : @mkdir($root.$path);
  2023. if( !$ret ) {
  2024. // Is this a file instead of a directory?
  2025. if(is_file($root.$path) )
  2026. {
  2027. @unlink($root.$path);
  2028. $ret = @mkdir($root.$path);
  2029. }
  2030. if( !$ret ) {
  2031. $this->setError( AKText::sprintf('COULDNT_CREATE_DIR',$path) );
  2032. return false;
  2033. }
  2034. }
  2035. // Try to set new directory permissions to 0755
  2036. @chmod($root.$path, $perms);
  2037. }
  2038. return true;
  2039. }
  2040. public function chmod( $file, $perms )
  2041. {
  2042. if( AKFactory::get('kickstart.setup.dryrun','0') ) return true;
  2043. return @chmod( $file, $perms );
  2044. }
  2045. public function unlink( $file )
  2046. {
  2047. return @unlink( $file );
  2048. }
  2049. public function rmdir( $directory )
  2050. {
  2051. return @rmdir( $directory );
  2052. }
  2053. public function rename( $from, $to )
  2054. {
  2055. return @rename($from, $to);
  2056. }
  2057. }
  2058. /**
  2059. * Akeeba Restore
  2060. * A JSON-powered JPA, JPS and ZIP archive extraction library
  2061. *
  2062. * @copyright 2010-2013 Nicholas K. Dionysopoulos / Akeeba Ltd.
  2063. * @license GNU GPL v2 or - at your option - any later version
  2064. * @package akeebabackup
  2065. * @subpackage kickstart
  2066. */
  2067. /**
  2068. * FTP file writer
  2069. */
  2070. class AKPostprocFTP extends AKAbstractPostproc
  2071. {
  2072. /** @var bool Should I use FTP over implicit SSL? */
  2073. public $useSSL = false;
  2074. /** @var bool use Passive mode? */
  2075. public $passive = true;
  2076. /** @var string FTP host name */
  2077. public $host = '';
  2078. /** @var int FTP port */
  2079. public $port = 21;
  2080. /** @var string FTP user name */
  2081. public $user = '';
  2082. /** @var string FTP password */
  2083. public $pass = '';
  2084. /** @var string FTP initial directory */
  2085. public $dir = '';
  2086. /** @var resource The FTP handle */
  2087. private $handle = null;
  2088. /** @var string The temporary directory where the data will be stored */
  2089. private $tempDir = '';
  2090. public function __construct()
  2091. {
  2092. parent::__construct();
  2093. $this->useSSL = AKFactory::get('kickstart.ftp.ssl', false);
  2094. $this->passive = AKFactory::get('kickstart.ftp.passive', true);
  2095. $this->host = AKFactory::get('kickstart.ftp.host', '');
  2096. $this->port = AKFactory::get('kickstart.ftp.port', 21);
  2097. if(trim($this->port) == '') $this->port = 21;
  2098. $this->user = AKFactory::get('kickstart.ftp.user', '');
  2099. $this->pass = AKFactory::get('kickstart.ftp.pass', '');
  2100. $this->dir = AKFactory::get('kickstart.ftp.dir', '');
  2101. $this->tempDir = AKFactory::get('kickstart.ftp.tempdir', '');
  2102. $connected = $this->connect();
  2103. if($connected)
  2104. {
  2105. if(!empty($this->tempDir))
  2106. {
  2107. $tempDir = rtrim($this->tempDir, '/\\').'/';
  2108. $writable = $this->isDirWritable($tempDir);
  2109. }
  2110. else
  2111. {
  2112. $tempDir = '';
  2113. $writable = false;
  2114. }
  2115. if(!$writable) {
  2116. // Default temporary directory is the current root
  2117. $tempDir = KSROOTDIR;
  2118. if(empty($tempDir))
  2119. {
  2120. // Oh, we have no directory reported!
  2121. $tempDir = '.';
  2122. }
  2123. $absoluteDirToHere = $tempDir;
  2124. $tempDir = rtrim(str_replace('\\','/',$tempDir),'/');
  2125. if(!empty($tempDir)) $tempDir .= '/';
  2126. $this->tempDir = $tempDir;
  2127. // Is this directory writable?
  2128. $writable = $this->isDirWritable($tempDir);
  2129. }
  2130. if(!$writable)
  2131. {
  2132. // Nope. Let's try creating a temporary directory in the site's root.
  2133. $tempDir = $absoluteDirToHere.'/kicktemp';
  2134. $this->createDirRecursive($tempDir, 0777);
  2135. // Try making it writable...
  2136. $this->fixPermissions($tempDir);
  2137. $writable = $this->isDirWritable($tempDir);
  2138. }
  2139. // Was the new directory writable?
  2140. if(!$writable)
  2141. {
  2142. // Let's see if the user has specified one
  2143. $userdir = AKFactory::get('kickstart.ftp.tempdir', '');
  2144. if(!empty($userdir))
  2145. {
  2146. // Is it an absolute or a relative directory?
  2147. $absolute = false;
  2148. $absolute = $absolute || ( substr($userdir,0,1) == '/' );
  2149. $absolute = $absolute || ( substr($userdir,1,1) == ':' );
  2150. $absolute = $absolute || ( substr($userdir,2,1) == ':' );
  2151. if(!$absolute)
  2152. {
  2153. // Make absolute
  2154. $tempDir = $absoluteDirToHere.$userdir;
  2155. }
  2156. else
  2157. {
  2158. // it's already absolute
  2159. $tempDir = $userdir;
  2160. }
  2161. // Does the directory exist?
  2162. if( is_dir($tempDir) )
  2163. {
  2164. // Yeah. Is it writable?
  2165. $writable = $this->isDirWritable($tempDir);
  2166. }
  2167. }
  2168. }
  2169. $this->tempDir = $tempDir;
  2170. if(!$writable)
  2171. {
  2172. // No writable directory found!!!
  2173. $this->setError(AKText::_('FTP_TEMPDIR_NOT_WRITABLE'));
  2174. }
  2175. else
  2176. {
  2177. AKFactory::set('kickstart.ftp.tempdir', $tempDir);
  2178. $this->tempDir = $tempDir;
  2179. }
  2180. }
  2181. }
  2182. function __wakeup()
  2183. {
  2184. $this->connect();
  2185. }
  2186. public function connect()
  2187. {
  2188. // Connect to server, using SSL if so required
  2189. if($this->useSSL) {
  2190. $this->handle = @ftp_ssl_connect($this->host, $this->port);
  2191. } else {
  2192. $this->handle = @ftp_connect($this->host, $this->port);
  2193. }
  2194. if($this->handle === false)
  2195. {
  2196. $this->setError(AKText::_('WRONG_FTP_HOST'));
  2197. return false;
  2198. }
  2199. // Login
  2200. if(! @ftp_login($this->handle, $this->user, $this->pass))
  2201. {
  2202. $this->setError(AKText::_('WRONG_FTP_USER'));
  2203. @ftp_close($this->handle);
  2204. return false;
  2205. }
  2206. // Change to initial directory
  2207. if(! @ftp_chdir($this->handle, $this->dir))
  2208. {
  2209. $this->setError(AKText::_('WRONG_FTP_PATH1'));
  2210. @ftp_close($this->handle);
  2211. return false;
  2212. }
  2213. // Enable passive mode if the user requested it
  2214. if( $this->passive )
  2215. {
  2216. @ftp_pasv($this->handle, true);
  2217. }
  2218. else
  2219. {
  2220. @ftp_pasv($this->handle, false);
  2221. }
  2222. // Try to download ourselves
  2223. $testFilename = defined('KSSELFNAME') ? KSSELFNAME : basename(__FILE__);
  2224. $tempHandle = fopen('php://temp', 'r+');
  2225. if (@ftp_fget($this->handle, $tempHandle, $testFilename, FTP_ASCII, 0) === false)
  2226. {
  2227. $this->setError(AKText::_('WRONG_FTP_PATH2'));
  2228. @ftp_close($this->handle);
  2229. fclose($tempHandle);
  2230. return false;
  2231. }
  2232. fclose($tempHandle);
  2233. return true;
  2234. }
  2235. public function process()
  2236. {
  2237. if( is_null($this->tempFilename) )
  2238. {
  2239. // If an empty filename is passed, it means that we shouldn't do any post processing, i.e.
  2240. // the entity was a directory or symlink
  2241. return true;
  2242. }
  2243. $remotePath = dirname($this->filename);
  2244. $removePath = AKFactory::get('kickstart.setup.destdir','');
  2245. if(!empty($removePath))
  2246. {
  2247. $removePath = ltrim($removePath, "/");
  2248. $remotePath = ltrim($remotePath, "/");
  2249. $left = substr($remotePath, 0, strlen($removePath));
  2250. if($left == $removePath)
  2251. {
  2252. $remotePath = substr($remotePath, strlen($removePath));
  2253. }
  2254. }
  2255. $absoluteFSPath = dirname($this->filename);
  2256. $relativeFTPPath = trim($remotePath, '/');
  2257. $absoluteFTPPath = '/'.trim( $this->dir, '/' ).'/'.trim($remotePath, '/');
  2258. $onlyFilename = basename($this->filename);
  2259. $remoteName = $absoluteFTPPath.'/'.$onlyFilename;
  2260. $ret = @ftp_chdir($this->handle, $absoluteFTPPath);
  2261. if($ret === false)
  2262. {
  2263. $ret = $this->createDirRecursive( $absoluteFSPath, 0755);
  2264. if($ret === false) {
  2265. $this->setError(AKText::sprintf('FTP_COULDNT_UPLOAD', $this->filename));
  2266. return false;
  2267. }
  2268. $ret = @ftp_chdir($this->handle, $absoluteFTPPath);
  2269. if($ret === false) {
  2270. $this->setError(AKText::sprintf('FTP_COULDNT_UPLOAD', $this->filename));
  2271. return false;
  2272. }
  2273. }
  2274. $ret = @ftp_put($this->handle, $remoteName, $this->tempFilename, FTP_BINARY);
  2275. if($ret === false)
  2276. {
  2277. // If we couldn't create the file, attempt to fix the permissions in the PHP level and retry!
  2278. $this->fixPermissions($this->filename);
  2279. $this->unlink($this->filename);
  2280. $fp = @fopen($this->tempFilename, 'rb');
  2281. if($fp !== false)
  2282. {
  2283. $ret = @ftp_fput($this->handle, $remoteName, $fp, FTP_BINARY);
  2284. @fclose($fp);
  2285. }
  2286. else
  2287. {
  2288. $ret = false;
  2289. }
  2290. }
  2291. @unlink($this->tempFilename);
  2292. if($ret === false)
  2293. {
  2294. $this->setError(AKText::sprintf('FTP_COULDNT_UPLOAD', $this->filename));
  2295. return false;
  2296. }
  2297. $restorePerms = AKFactory::get('kickstart.setup.restoreperms', false);
  2298. if($restorePerms)
  2299. {
  2300. @ftp_chmod($this->_handle, $perms, $remoteName);
  2301. }
  2302. else
  2303. {
  2304. @ftp_chmod($this->_handle, 0644, $remoteName);
  2305. }
  2306. return true;
  2307. }
  2308. public function processFilename($filename, $perms = 0755)
  2309. {
  2310. // Catch some error conditions...
  2311. if($this->getError())
  2312. {
  2313. return false;
  2314. }
  2315. // If a null filename is passed, it means that we shouldn't do any post processing, i.e.
  2316. // the entity was a directory or symlink
  2317. if(is_null($filename))
  2318. {
  2319. $this->filename = null;
  2320. $this->tempFilename = null;
  2321. return null;
  2322. }
  2323. // Strip absolute filesystem path to website's root
  2324. $removePath = AKFactory::get('kickstart.setup.destdir','');
  2325. if(!empty($removePath))
  2326. {
  2327. $left = substr($filename, 0, strlen($removePath));
  2328. if($left == $removePath)
  2329. {
  2330. $filename = substr($filename, strlen($removePath));
  2331. }
  2332. }
  2333. // Trim slash on the left
  2334. $filename = ltrim($filename, '/');
  2335. $this->filename = $filename;
  2336. $this->tempFilename = tempnam($this->tempDir, 'kickstart-');
  2337. $this->perms = $perms;
  2338. if( empty($this->tempFilename) )
  2339. {
  2340. // Oops! Let's try something different
  2341. $this->tempFilename = $this->tempDir.'/kickstart-'.time().'.dat';
  2342. }
  2343. return $this->tempFilename;
  2344. }
  2345. private function isDirWritable($dir)
  2346. {
  2347. $fp = @fopen($dir.'/kickstart.dat', 'wb');
  2348. if($fp === false)
  2349. {
  2350. return false;
  2351. }
  2352. else
  2353. {
  2354. @fclose($fp);
  2355. unlink($dir.'/kickstart.dat');
  2356. return true;
  2357. }
  2358. }
  2359. public function createDirRecursive( $dirName, $perms )
  2360. {
  2361. // Strip absolute filesystem path to website's root
  2362. $removePath = AKFactory::get('kickstart.setup.destdir','');
  2363. if(!empty($removePath))
  2364. {
  2365. // UNIXize the paths
  2366. $removePath = str_replace('\\','/',$removePath);
  2367. $dirName = str_replace('\\','/',$dirName);
  2368. // Make sure they both end in a slash
  2369. $removePath = rtrim($removePath,'/\\').'/';
  2370. $dirName = rtrim($dirName,'/\\').'/';
  2371. // Process the path removal
  2372. $left = substr($dirName, 0, strlen($removePath));
  2373. if($left == $removePath)
  2374. {
  2375. $dirName = substr($dirName, strlen($removePath));
  2376. }
  2377. }
  2378. if(empty($dirName)) $dirName = ''; // 'cause the substr() above may return FALSE.
  2379. $check = '/'.trim($this->dir,'/').'/'.trim($dirName, '/');
  2380. if($this->is_dir($check)) return true;
  2381. $alldirs = explode('/', $dirName);
  2382. $previousDir = '/'.trim($this->dir);
  2383. foreach($alldirs as $curdir)
  2384. {
  2385. $check = $previousDir.'/'.$curdir;
  2386. if(!$this->is_dir($check))
  2387. {
  2388. // Proactively try to delete a file by the same name
  2389. @ftp_delete($this->handle, $check);
  2390. if(@ftp_mkdir($this->handle, $check) === false)
  2391. {
  2392. // If we couldn't create the directory, attempt to fix the permissions in the PHP level and retry!
  2393. $this->fixPermissions($removePath.$check);
  2394. if(@ftp_mkdir($this->handle, $check) === false)
  2395. {
  2396. // Can we fall back to pure PHP mode, sire?
  2397. if(!@mkdir($check))
  2398. {
  2399. $this->setError(AKText::sprintf('FTP_CANT_CREATE_DIR', $check));
  2400. return false;
  2401. }
  2402. else
  2403. {
  2404. // Since the directory was built by PHP, change its permissions
  2405. @chmod($check, "0777");
  2406. return true;
  2407. }
  2408. }
  2409. }
  2410. @ftp_chmod($this->handle, $perms, $check);
  2411. }
  2412. $previousDir = $check;
  2413. }
  2414. return true;
  2415. }
  2416. public function close()
  2417. {
  2418. @ftp_close($this->handle);
  2419. }
  2420. /*
  2421. * Tries to fix directory/file permissions in the PHP level, so that
  2422. * the FTP operation doesn't fail.
  2423. * @param $path string The full path to a directory or file
  2424. */
  2425. private function fixPermissions( $path )
  2426. {
  2427. // Turn off error reporting
  2428. if(!defined('KSDEBUG')) {
  2429. $oldErrorReporting = @error_reporting(E_NONE);
  2430. }
  2431. // Get UNIX style paths
  2432. $relPath = str_replace('\\','/',$path);
  2433. $basePath = rtrim(str_replace('\\','/',KSROOTDIR),'/');
  2434. $basePath = rtrim($basePath,'/');
  2435. if(!empty($basePath)) $basePath .= '/';
  2436. // Remove the leading relative root
  2437. if( substr($relPath,0,strlen($basePath)) == $basePath )
  2438. $relPath = substr($relPath,strlen($basePath));
  2439. $dirArray = explode('/', $relPath);
  2440. $pathBuilt = rtrim($basePath,'/');
  2441. foreach( $dirArray as $dir )
  2442. {
  2443. if(empty($dir)) continue;
  2444. $oldPath = $pathBuilt;
  2445. $pathBuilt .= '/'.$dir;
  2446. if(is_dir($oldPath.$dir))
  2447. {
  2448. @chmod($oldPath.$dir, 0777);
  2449. }
  2450. else
  2451. {
  2452. if(@chmod($oldPath.$dir, 0777) === false)
  2453. {
  2454. @unlink($oldPath.$dir);
  2455. }
  2456. }
  2457. }
  2458. // Restore error reporting
  2459. if(!defined('KSDEBUG')) {
  2460. @error_reporting($oldErrorReporting);
  2461. }
  2462. }
  2463. public function chmod( $file, $perms )
  2464. {
  2465. return @ftp_chmod($this->handle, $perms, $file);
  2466. }
  2467. private function is_dir( $dir )
  2468. {
  2469. return @ftp_chdir( $this->handle, $dir );
  2470. }
  2471. public function unlink( $file )
  2472. {
  2473. $removePath = AKFactory::get('kickstart.setup.destdir','');
  2474. if(!empty($removePath))
  2475. {
  2476. $left = substr($file, 0, strlen($removePath));
  2477. if($left == $removePath)
  2478. {
  2479. $file = substr($file, strlen($removePath));
  2480. }
  2481. }
  2482. $check = '/'.trim($this->dir,'/').'/'.trim($file, '/');
  2483. return @ftp_delete( $this->handle, $check );
  2484. }
  2485. public function rmdir( $directory )
  2486. {
  2487. $removePath = AKFactory::get('kickstart.setup.destdir','');
  2488. if(!empty($removePath))
  2489. {
  2490. $left = substr($directory, 0, strlen($removePath));
  2491. if($left == $removePath)
  2492. {
  2493. $directory = substr($directory, strlen($removePath));
  2494. }
  2495. }
  2496. $check = '/'.trim($this->dir,'/').'/'.trim($directory, '/');
  2497. return @ftp_rmdir( $this->handle, $check );
  2498. }
  2499. public function rename( $from, $to )
  2500. {
  2501. $originalFrom = $from;
  2502. $originalTo = $to;
  2503. $removePath = AKFactory::get('kickstart.setup.destdir','');
  2504. if(!empty($removePath))
  2505. {
  2506. $left = substr($from, 0, strlen($removePath));
  2507. if($left == $removePath)
  2508. {
  2509. $from = substr($from, strlen($removePath));
  2510. }
  2511. }
  2512. $from = '/'.trim($this->dir,'/').'/'.trim($from, '/');
  2513. if(!empty($removePath))
  2514. {
  2515. $left = substr($to, 0, strlen($removePath));
  2516. if($left == $removePath)
  2517. {
  2518. $to = substr($to, strlen($removePath));
  2519. }
  2520. }
  2521. $to = '/'.trim($this->dir,'/').'/'.trim($to, '/');
  2522. $result = @ftp_rename( $this->handle, $from, $to );
  2523. if($result !== true)
  2524. {
  2525. return @rename($from, $to);
  2526. }
  2527. else
  2528. {
  2529. return true;
  2530. }
  2531. }
  2532. }
  2533. /**
  2534. * Akeeba Restore
  2535. * A JSON-powered JPA, JPS and ZIP archive extraction library
  2536. *
  2537. * @copyright 2010-2013 Nicholas K. Dionysopoulos / Akeeba Ltd.
  2538. * @license GNU GPL v2 or - at your option - any later version
  2539. * @package akeebabackup
  2540. * @subpackage kickstart
  2541. */
  2542. /**
  2543. * Hybrid direct / FTP mode file writer
  2544. */
  2545. class AKPostprocHybrid extends AKAbstractPostproc
  2546. {
  2547. /** @var bool Should I use the FTP layer? */
  2548. public $useFTP = false;
  2549. /** @var bool Should I use FTP over implicit SSL? */
  2550. public $useSSL = false;
  2551. /** @var bool use Passive mode? */
  2552. public $passive = true;
  2553. /** @var string FTP host name */
  2554. public $host = '';
  2555. /** @var int FTP port */
  2556. public $port = 21;
  2557. /** @var string FTP user name */
  2558. public $user = '';
  2559. /** @var string FTP password */
  2560. public $pass = '';
  2561. /** @var string FTP initial directory */
  2562. public $dir = '';
  2563. /** @var resource The FTP handle */
  2564. private $handle = null;
  2565. /** @var string The temporary directory where the data will be stored */
  2566. private $tempDir = '';
  2567. public function __construct()
  2568. {
  2569. parent::__construct();
  2570. $this->useFTP = AKFactory::get('kickstart.ftp.enable', false);
  2571. $this->useSSL = AKFactory::get('kickstart.ftp.ssl', false);
  2572. $this->passive = AKFactory::get('kickstart.ftp.passive', true);
  2573. $this->host = AKFactory::get('kickstart.ftp.host', '');
  2574. $this->port = AKFactory::get('kickstart.ftp.port', 21);
  2575. if(trim($this->port) == '') $this->port = 21;
  2576. $this->user = AKFactory::get('kickstart.ftp.user', '');
  2577. $this->pass = AKFactory::get('kickstart.ftp.pass', '');
  2578. $this->dir = AKFactory::get('kickstart.ftp.dir', '');
  2579. $this->tempDir = AKFactory::get('kickstart.ftp.tempdir', '');
  2580. $connected = $this->connect();
  2581. if($connected)
  2582. {
  2583. if(!empty($this->tempDir))
  2584. {
  2585. $tempDir = rtrim($this->tempDir, '/\\').'/';
  2586. $writable = $this->isDirWritable($tempDir);
  2587. }
  2588. else
  2589. {
  2590. $tempDir = '';
  2591. $writable = false;
  2592. }
  2593. if(!$writable) {
  2594. // Default temporary directory is the current root
  2595. $tempDir = KSROOTDIR;
  2596. if(empty($tempDir))
  2597. {
  2598. // Oh, we have no directory reported!
  2599. $tempDir = '.';
  2600. }
  2601. $absoluteDirToHere = $tempDir;
  2602. $tempDir = rtrim(str_replace('\\','/',$tempDir),'/');
  2603. if(!empty($tempDir)) $tempDir .= '/';
  2604. $this->tempDir = $tempDir;
  2605. // Is this directory writable?
  2606. $writable = $this->isDirWritable($tempDir);
  2607. }
  2608. if(!$writable)
  2609. {
  2610. // Nope. Let's try creating a temporary directory in the site's root.
  2611. $tempDir = $absoluteDirToHere.'/kicktemp';
  2612. $this->createDirRecursive($tempDir, 0777);
  2613. // Try making it writable...
  2614. $this->fixPermissions($tempDir);
  2615. $writable = $this->isDirWritable($tempDir);
  2616. }
  2617. // Was the new directory writable?
  2618. if(!$writable)
  2619. {
  2620. // Let's see if the user has specified one
  2621. $userdir = AKFactory::get('kickstart.ftp.tempdir', '');
  2622. if(!empty($userdir))
  2623. {
  2624. // Is it an absolute or a relative directory?
  2625. $absolute = false;
  2626. $absolute = $absolute || ( substr($userdir,0,1) == '/' );
  2627. $absolute = $absolute || ( substr($userdir,1,1) == ':' );
  2628. $absolute = $absolute || ( substr($userdir,2,1) == ':' );
  2629. if(!$absolute)
  2630. {
  2631. // Make absolute
  2632. $tempDir = $absoluteDirToHere.$userdir;
  2633. }
  2634. else
  2635. {
  2636. // it's already absolute
  2637. $tempDir = $userdir;
  2638. }
  2639. // Does the directory exist?
  2640. if( is_dir($tempDir) )
  2641. {
  2642. // Yeah. Is it writable?
  2643. $writable = $this->isDirWritable($tempDir);
  2644. }
  2645. }
  2646. }
  2647. $this->tempDir = $tempDir;
  2648. if(!$writable)
  2649. {
  2650. // No writable directory found!!!
  2651. $this->setError(AKText::_('FTP_TEMPDIR_NOT_WRITABLE'));
  2652. }
  2653. else
  2654. {
  2655. AKFactory::set('kickstart.ftp.tempdir', $tempDir);
  2656. $this->tempDir = $tempDir;
  2657. }
  2658. }
  2659. }
  2660. function __wakeup()
  2661. {
  2662. if ($this->useFTP)
  2663. {
  2664. $this->connect();
  2665. }
  2666. }
  2667. public function connect()
  2668. {
  2669. if (!$this->useFTP)
  2670. {
  2671. return true;
  2672. }
  2673. // Connect to server, using SSL if so required
  2674. if($this->useSSL) {
  2675. $this->handle = @ftp_ssl_connect($this->host, $this->port);
  2676. } else {
  2677. $this->handle = @ftp_connect($this->host, $this->port);
  2678. }
  2679. if($this->handle === false)
  2680. {
  2681. $this->setError(AKText::_('WRONG_FTP_HOST'));
  2682. return false;
  2683. }
  2684. // Login
  2685. if(! @ftp_login($this->handle, $this->user, $this->pass))
  2686. {
  2687. $this->setError(AKText::_('WRONG_FTP_USER'));
  2688. @ftp_close($this->handle);
  2689. return false;
  2690. }
  2691. // Change to initial directory
  2692. if(! @ftp_chdir($this->handle, $this->dir))
  2693. {
  2694. $this->setError(AKText::_('WRONG_FTP_PATH1'));
  2695. @ftp_close($this->handle);
  2696. return false;
  2697. }
  2698. // Enable passive mode if the user requested it
  2699. if( $this->passive )
  2700. {
  2701. @ftp_pasv($this->handle, true);
  2702. }
  2703. else
  2704. {
  2705. @ftp_pasv($this->handle, false);
  2706. }
  2707. // Try to download ourselves
  2708. $testFilename = defined('KSSELFNAME') ? KSSELFNAME : basename(__FILE__);
  2709. $tempHandle = fopen('php://temp', 'r+');
  2710. if (@ftp_fget($this->handle, $tempHandle, $testFilename, FTP_ASCII, 0) === false)
  2711. {
  2712. $this->setError(AKText::_('WRONG_FTP_PATH2'));
  2713. @ftp_close($this->handle);
  2714. fclose($tempHandle);
  2715. return false;
  2716. }
  2717. fclose($tempHandle);
  2718. return true;
  2719. }
  2720. public function process()
  2721. {
  2722. if( is_null($this->tempFilename) )
  2723. {
  2724. // If an empty filename is passed, it means that we shouldn't do any post processing, i.e.
  2725. // the entity was a directory or symlink
  2726. return true;
  2727. }
  2728. $remotePath = dirname($this->filename);
  2729. $removePath = AKFactory::get('kickstart.setup.destdir','');
  2730. $root = rtrim($removePath, '/\\');
  2731. if(!empty($removePath))
  2732. {
  2733. $removePath = ltrim($removePath, "/");
  2734. $remotePath = ltrim($remotePath, "/");
  2735. $left = substr($remotePath, 0, strlen($removePath));
  2736. if($left == $removePath)
  2737. {
  2738. $remotePath = substr($remotePath, strlen($removePath));
  2739. }
  2740. }
  2741. $absoluteFSPath = dirname($this->filename);
  2742. $relativeFTPPath = trim($remotePath, '/');
  2743. $absoluteFTPPath = '/'.trim( $this->dir, '/' ).'/'.trim($remotePath, '/');
  2744. $onlyFilename = basename($this->filename);
  2745. $remoteName = $absoluteFTPPath.'/'.$onlyFilename;
  2746. // Does the directory exist?
  2747. if (!is_dir($root . '/' . $absoluteFSPath))
  2748. {
  2749. $ret = $this->createDirRecursive( $absoluteFSPath, 0755);
  2750. if(($ret === false) && ($this->useFTP))
  2751. {
  2752. $ret = @ftp_chdir($this->handle, $absoluteFTPPath);
  2753. }
  2754. if($ret === false)
  2755. {
  2756. $this->setError(AKText::sprintf('FTP_COULDNT_UPLOAD', $this->filename));
  2757. return false;
  2758. }
  2759. }
  2760. if ($this->useFTP)
  2761. {
  2762. $ret = @ftp_chdir($this->handle, $absoluteFTPPath);
  2763. }
  2764. // Try copying directly
  2765. $ret = @copy($this->tempFilename, $root . '/' . $this->filename);
  2766. if ($ret === false)
  2767. {
  2768. $this->fixPermissions($this->filename);
  2769. $this->unlink($this->filename);
  2770. $ret = @copy($this->tempFilename, $root . '/' . $this->filename);
  2771. }
  2772. if ($this->useFTP && ($ret === false))
  2773. {
  2774. $ret = @ftp_put($this->handle, $remoteName, $this->tempFilename, FTP_BINARY);
  2775. if($ret === false)
  2776. {
  2777. // If we couldn't create the file, attempt to fix the permissions in the PHP level and retry!
  2778. $this->fixPermissions($this->filename);
  2779. $this->unlink($this->filename);
  2780. $fp = @fopen($this->tempFilename, 'rb');
  2781. if($fp !== false)
  2782. {
  2783. $ret = @ftp_fput($this->handle, $remoteName, $fp, FTP_BINARY);
  2784. @fclose($fp);
  2785. }
  2786. else
  2787. {
  2788. $ret = false;
  2789. }
  2790. }
  2791. }
  2792. @unlink($this->tempFilename);
  2793. if($ret === false)
  2794. {
  2795. $this->setError(AKText::sprintf('FTP_COULDNT_UPLOAD', $this->filename));
  2796. return false;
  2797. }
  2798. $restorePerms = AKFactory::get('kickstart.setup.restoreperms', false);
  2799. $perms = $restorePerms ? $perms : 0644;
  2800. $ret = @chmod($root . '/' . $this->filename, $perms);
  2801. if ($this->useFTP && ($ret === false))
  2802. {
  2803. @ftp_chmod($this->_handle, $perms, $remoteName);
  2804. }
  2805. return true;
  2806. }
  2807. public function processFilename($filename, $perms = 0755)
  2808. {
  2809. // Catch some error conditions...
  2810. if($this->getError())
  2811. {
  2812. return false;
  2813. }
  2814. // If a null filename is passed, it means that we shouldn't do any post processing, i.e.
  2815. // the entity was a directory or symlink
  2816. if(is_null($filename))
  2817. {
  2818. $this->filename = null;
  2819. $this->tempFilename = null;
  2820. return null;
  2821. }
  2822. // Strip absolute filesystem path to website's root
  2823. $removePath = AKFactory::get('kickstart.setup.destdir','');
  2824. if(!empty($removePath))
  2825. {
  2826. $left = substr($filename, 0, strlen($removePath));
  2827. if($left == $removePath)
  2828. {
  2829. $filename = substr($filename, strlen($removePath));
  2830. }
  2831. }
  2832. // Trim slash on the left
  2833. $filename = ltrim($filename, '/');
  2834. $this->filename = $filename;
  2835. $this->tempFilename = tempnam($this->tempDir, 'kickstart-');
  2836. $this->perms = $perms;
  2837. if( empty($this->tempFilename) )
  2838. {
  2839. // Oops! Let's try something different
  2840. $this->tempFilename = $this->tempDir.'/kickstart-'.time().'.dat';
  2841. }
  2842. return $this->tempFilename;
  2843. }
  2844. private function isDirWritable($dir)
  2845. {
  2846. $fp = @fopen($dir.'/kickstart.dat', 'wb');
  2847. if($fp === false)
  2848. {
  2849. return false;
  2850. }
  2851. else
  2852. {
  2853. @fclose($fp);
  2854. unlink($dir.'/kickstart.dat');
  2855. return true;
  2856. }
  2857. }
  2858. public function createDirRecursive( $dirName, $perms )
  2859. {
  2860. // Strip absolute filesystem path to website's root
  2861. $removePath = AKFactory::get('kickstart.setup.destdir','');
  2862. if(!empty($removePath))
  2863. {
  2864. // UNIXize the paths
  2865. $removePath = str_replace('\\','/',$removePath);
  2866. $dirName = str_replace('\\','/',$dirName);
  2867. // Make sure they both end in a slash
  2868. $removePath = rtrim($removePath,'/\\').'/';
  2869. $dirName = rtrim($dirName,'/\\').'/';
  2870. // Process the path removal
  2871. $left = substr($dirName, 0, strlen($removePath));
  2872. if($left == $removePath)
  2873. {
  2874. $dirName = substr($dirName, strlen($removePath));
  2875. }
  2876. }
  2877. if(empty($dirName)) $dirName = ''; // 'cause the substr() above may return FALSE.
  2878. $check = '/'.trim($this->dir,'/').'/'.trim($dirName, '/');
  2879. $checkFS = $removePath . trim($dirName, '/');
  2880. if($this->is_dir($check)) return true;
  2881. $alldirs = explode('/', $dirName);
  2882. $previousDir = '/'.trim($this->dir);
  2883. $previousDirFS = rtrim($removePath, '/\\');
  2884. foreach($alldirs as $curdir)
  2885. {
  2886. $check = $previousDir.'/'.$curdir;
  2887. $checkFS = $previousDirFS . '/' . $curdir;
  2888. if(!is_dir($checkFS) && !$this->is_dir($check))
  2889. {
  2890. // Proactively try to delete a file by the same name
  2891. if (!@unlink($checkFS) && $this->useFTP)
  2892. {
  2893. @ftp_delete($this->handle, $check);
  2894. }
  2895. $createdDir = @mkdir($checkFS, 0755);
  2896. if (!$createdDir && $this->useFTP)
  2897. {
  2898. $createdDir = @ftp_mkdir($this->handle, $check);
  2899. }
  2900. if($createdDir === false)
  2901. {
  2902. // If we couldn't create the directory, attempt to fix the permissions in the PHP level and retry!
  2903. $this->fixPermissions($checkFS);
  2904. $createdDir = @mkdir($checkFS, 0755);
  2905. if (!$createdDir && $this->useFTP)
  2906. {
  2907. $createdDir = @ftp_mkdir($this->handle, $check);
  2908. }
  2909. if($createdDir === false)
  2910. {
  2911. $this->setError(AKText::sprintf('FTP_CANT_CREATE_DIR', $check));
  2912. return false;
  2913. }
  2914. }
  2915. if (!@chmod($checkFS, $perms) && $this->useFTP)
  2916. {
  2917. @ftp_chmod($this->handle, $perms, $check);
  2918. }
  2919. }
  2920. $previousDir = $check;
  2921. $previousDirFS = $checkFS;
  2922. }
  2923. return true;
  2924. }
  2925. public function close()
  2926. {
  2927. if (!$this->useFTP)
  2928. {
  2929. @ftp_close($this->handle);
  2930. }
  2931. }
  2932. /*
  2933. * Tries to fix directory/file permissions in the PHP level, so that
  2934. * the FTP operation doesn't fail.
  2935. * @param $path string The full path to a directory or file
  2936. */
  2937. private function fixPermissions( $path )
  2938. {
  2939. // Turn off error reporting
  2940. if(!defined('KSDEBUG')) {
  2941. $oldErrorReporting = @error_reporting(E_NONE);
  2942. }
  2943. // Get UNIX style paths
  2944. $relPath = str_replace('\\','/',$path);
  2945. $basePath = rtrim(str_replace('\\','/',KSROOTDIR),'/');
  2946. $basePath = rtrim($basePath,'/');
  2947. if(!empty($basePath)) $basePath .= '/';
  2948. // Remove the leading relative root
  2949. if( substr($relPath,0,strlen($basePath)) == $basePath )
  2950. $relPath = substr($relPath,strlen($basePath));
  2951. $dirArray = explode('/', $relPath);
  2952. $pathBuilt = rtrim($basePath,'/');
  2953. foreach( $dirArray as $dir )
  2954. {
  2955. if(empty($dir)) continue;
  2956. $oldPath = $pathBuilt;
  2957. $pathBuilt .= '/'.$dir;
  2958. if(is_dir($oldPath.$dir))
  2959. {
  2960. @chmod($oldPath.$dir, 0777);
  2961. }
  2962. else
  2963. {
  2964. if(@chmod($oldPath.$dir, 0777) === false)
  2965. {
  2966. @unlink($oldPath.$dir);
  2967. }
  2968. }
  2969. }
  2970. // Restore error reporting
  2971. if(!defined('KSDEBUG')) {
  2972. @error_reporting($oldErrorReporting);
  2973. }
  2974. }
  2975. public function chmod( $file, $perms )
  2976. {
  2977. if( AKFactory::get('kickstart.setup.dryrun','0') ) return true;
  2978. $ret = @chmod($file, $perms);
  2979. if (!$ret && $this->useFTP)
  2980. {
  2981. // Strip absolute filesystem path to website's root
  2982. $removePath = AKFactory::get('kickstart.setup.destdir','');
  2983. if(!empty($removePath))
  2984. {
  2985. $left = substr($file, 0, strlen($removePath));
  2986. if($left == $removePath)
  2987. {
  2988. $file = substr($file, strlen($removePath));
  2989. }
  2990. }
  2991. // Trim slash on the left
  2992. $file = ltrim($file, '/');
  2993. $ret = @ftp_chmod($this->handle, $perms, $file);
  2994. }
  2995. return $ret;
  2996. }
  2997. private function is_dir( $dir )
  2998. {
  2999. return @ftp_chdir( $this->handle, $dir );
  3000. }
  3001. public function unlink( $file )
  3002. {
  3003. $ret = @unlink($file);
  3004. if (!$ret && $this->useFTP)
  3005. {
  3006. $removePath = AKFactory::get('kickstart.setup.destdir','');
  3007. if(!empty($removePath))
  3008. {
  3009. $left = substr($file, 0, strlen($removePath));
  3010. if($left == $removePath)
  3011. {
  3012. $file = substr($file, strlen($removePath));
  3013. }
  3014. }
  3015. $check = '/'.trim($this->dir,'/').'/'.trim($file, '/');
  3016. $ret = @ftp_delete( $this->handle, $check );
  3017. }
  3018. return $ret;
  3019. }
  3020. public function rmdir( $directory )
  3021. {
  3022. $ret = @rmdir($directory);
  3023. if (!$ret && $this->useFTP)
  3024. {
  3025. $removePath = AKFactory::get('kickstart.setup.destdir','');
  3026. if(!empty($removePath))
  3027. {
  3028. $left = substr($directory, 0, strlen($removePath));
  3029. if($left == $removePath)
  3030. {
  3031. $directory = substr($directory, strlen($removePath));
  3032. }
  3033. }
  3034. $check = '/'.trim($this->dir,'/').'/'.trim($directory, '/');
  3035. $ret = @ftp_rmdir( $this->handle, $check );
  3036. }
  3037. return $ret;
  3038. }
  3039. public function rename( $from, $to )
  3040. {
  3041. $ret = @rename($from, $to);
  3042. if (!$ret && $this->useFTP)
  3043. {
  3044. $originalFrom = $from;
  3045. $originalTo = $to;
  3046. $removePath = AKFactory::get('kickstart.setup.destdir','');
  3047. if(!empty($removePath))
  3048. {
  3049. $left = substr($from, 0, strlen($removePath));
  3050. if($left == $removePath)
  3051. {
  3052. $from = substr($from, strlen($removePath));
  3053. }
  3054. }
  3055. $from = '/'.trim($this->dir,'/').'/'.trim($from, '/');
  3056. if(!empty($removePath))
  3057. {
  3058. $left = substr($to, 0, strlen($removePath));
  3059. if($left == $removePath)
  3060. {
  3061. $to = substr($to, strlen($removePath));
  3062. }
  3063. }
  3064. $to = '/'.trim($this->dir,'/').'/'.trim($to, '/');
  3065. $ret = @ftp_rename( $this->handle, $from, $to );
  3066. }
  3067. return $ret;
  3068. }
  3069. }
  3070. /**
  3071. * Akeeba Restore
  3072. * A JSON-powered JPA, JPS and ZIP archive extraction library
  3073. *
  3074. * @copyright 2010-2013 Nicholas K. Dionysopoulos / Akeeba Ltd.
  3075. * @license GNU GPL v2 or - at your option - any later version
  3076. * @package akeebabackup
  3077. * @subpackage kickstart
  3078. */
  3079. /**
  3080. * JPA archive extraction class
  3081. */
  3082. class AKUnarchiverJPA extends AKAbstractUnarchiver
  3083. {
  3084. private $archiveHeaderData = array();
  3085. protected function readArchiveHeader()
  3086. {
  3087. debugMsg('Preparing to read archive header');
  3088. // Initialize header data array
  3089. $this->archiveHeaderData = new stdClass();
  3090. // Open the first part
  3091. debugMsg('Opening the first part');
  3092. $this->nextFile();
  3093. // Fail for unreadable files
  3094. if( $this->fp === false ) {
  3095. debugMsg('Could not open the first part');
  3096. return false;
  3097. }
  3098. // Read the signature
  3099. $sig = fread( $this->fp, 3 );
  3100. if ($sig != 'JPA')
  3101. {
  3102. // Not a JPA file
  3103. debugMsg('Invalid archive signature');
  3104. $this->setError( AKText::_('ERR_NOT_A_JPA_FILE') );
  3105. return false;
  3106. }
  3107. // Read and parse header length
  3108. $header_length_array = unpack( 'v', fread( $this->fp, 2 ) );
  3109. $header_length = $header_length_array[1];
  3110. // Read and parse the known portion of header data (14 bytes)
  3111. $bin_data = fread($this->fp, 14);
  3112. $header_data = unpack('Cmajor/Cminor/Vcount/Vuncsize/Vcsize', $bin_data);
  3113. // Load any remaining header data (forward compatibility)
  3114. $rest_length = $header_length - 19;
  3115. if( $rest_length > 0 )
  3116. $junk = fread($this->fp, $rest_length);
  3117. else
  3118. $junk = '';
  3119. // Temporary array with all the data we read
  3120. $temp = array(
  3121. 'signature' => $sig,
  3122. 'length' => $header_length,
  3123. 'major' => $header_data['major'],
  3124. 'minor' => $header_data['minor'],
  3125. 'filecount' => $header_data['count'],
  3126. 'uncompressedsize' => $header_data['uncsize'],
  3127. 'compressedsize' => $header_data['csize'],
  3128. 'unknowndata' => $junk
  3129. );
  3130. // Array-to-object conversion
  3131. foreach($temp as $key => $value)
  3132. {
  3133. $this->archiveHeaderData->{$key} = $value;
  3134. }
  3135. debugMsg('Header data:');
  3136. debugMsg('Length : '.$header_length);
  3137. debugMsg('Major : '.$header_data['major']);
  3138. debugMsg('Minor : '.$header_data['minor']);
  3139. debugMsg('File count : '.$header_data['count']);
  3140. debugMsg('Uncompressed size : '.$header_data['uncsize']);
  3141. debugMsg('Compressed size : '.$header_data['csize']);
  3142. $this->currentPartOffset = @ftell($this->fp);
  3143. $this->dataReadLength = 0;
  3144. return true;
  3145. }
  3146. /**
  3147. * Concrete classes must use this method to read the file header
  3148. * @return bool True if reading the file was successful, false if an error occured or we reached end of archive
  3149. */
  3150. protected function readFileHeader()
  3151. {
  3152. // If the current part is over, proceed to the next part please
  3153. if( $this->isEOF(true) ) {
  3154. debugMsg('Archive part EOF; moving to next file');
  3155. $this->nextFile();
  3156. }
  3157. debugMsg('Reading file signature');
  3158. // Get and decode Entity Description Block
  3159. $signature = fread($this->fp, 3);
  3160. $this->fileHeader = new stdClass();
  3161. $this->fileHeader->timestamp = 0;
  3162. // Check signature
  3163. if( $signature != 'JPF' )
  3164. {
  3165. if($this->isEOF(true))
  3166. {
  3167. // This file is finished; make sure it's the last one
  3168. $this->nextFile();
  3169. if(!$this->isEOF(false))
  3170. {
  3171. debugMsg('Invalid file signature before end of archive encountered');
  3172. $this->setError(AKText::sprintf('INVALID_FILE_HEADER', $this->currentPartNumber, $this->currentPartOffset));
  3173. return false;
  3174. }
  3175. // We're just finished
  3176. return false;
  3177. }
  3178. else
  3179. {
  3180. $screwed = true;
  3181. if(AKFactory::get('kickstart.setup.ignoreerrors', false)) {
  3182. debugMsg('Invalid file block signature; launching heuristic file block signature scanner');
  3183. $screwed = !$this->heuristicFileHeaderLocator();
  3184. if(!$screwed) {
  3185. $signature = 'JPF';
  3186. } else {
  3187. debugMsg('Heuristics failed. Brace yourself for the imminent crash.');
  3188. }
  3189. }
  3190. if($screwed) {
  3191. debugMsg('Invalid file block signature');
  3192. // This is not a file block! The archive is corrupt.
  3193. $this->setError(AKText::sprintf('INVALID_FILE_HEADER', $this->currentPartNumber, $this->currentPartOffset));
  3194. return false;
  3195. }
  3196. }
  3197. }
  3198. // This a JPA Entity Block. Process the header.
  3199. $isBannedFile = false;
  3200. // Read length of EDB and of the Entity Path Data
  3201. $length_array = unpack('vblocksize/vpathsize', fread($this->fp, 4));
  3202. // Read the path data
  3203. if($length_array['pathsize'] > 0) {
  3204. $file = fread( $this->fp, $length_array['pathsize'] );
  3205. } else {
  3206. $file = '';
  3207. }
  3208. // Handle file renaming
  3209. $isRenamed = false;
  3210. if(is_array($this->renameFiles) && (count($this->renameFiles) > 0) )
  3211. {
  3212. if(array_key_exists($file, $this->renameFiles))
  3213. {
  3214. $file = $this->renameFiles[$file];
  3215. $isRenamed = true;
  3216. }
  3217. }
  3218. // Handle directory renaming
  3219. $isDirRenamed = false;
  3220. if(is_array($this->renameDirs) && (count($this->renameDirs) > 0)) {
  3221. if(array_key_exists(dirname($file), $this->renameDirs)) {
  3222. $file = rtrim($this->renameDirs[dirname($file)],'/').'/'.basename($file);
  3223. $isRenamed = true;
  3224. $isDirRenamed = true;
  3225. }
  3226. }
  3227. // Read and parse the known data portion
  3228. $bin_data = fread( $this->fp, 14 );
  3229. $header_data = unpack('Ctype/Ccompression/Vcompsize/Vuncompsize/Vperms', $bin_data);
  3230. // Read any unknown data
  3231. $restBytes = $length_array['blocksize'] - (21 + $length_array['pathsize']);
  3232. if( $restBytes > 0 )
  3233. {
  3234. // Start reading the extra fields
  3235. while($restBytes >= 4)
  3236. {
  3237. $extra_header_data = fread($this->fp, 4);
  3238. $extra_header = unpack('vsignature/vlength', $extra_header_data);
  3239. $restBytes -= 4;
  3240. $extra_header['length'] -= 4;
  3241. switch($extra_header['signature'])
  3242. {
  3243. case 256:
  3244. // File modified timestamp
  3245. if($extra_header['length'] > 0)
  3246. {
  3247. $bindata = fread($this->fp, $extra_header['length']);
  3248. $restBytes -= $extra_header['length'];
  3249. $timestamps = unpack('Vmodified', substr($bindata,0,4));
  3250. $filectime = $timestamps['modified'];
  3251. $this->fileHeader->timestamp = $filectime;
  3252. }
  3253. break;
  3254. default:
  3255. // Unknown field
  3256. if($extra_header['length']>0) {
  3257. $junk = fread($this->fp, $extra_header['length']);
  3258. $restBytes -= $extra_header['length'];
  3259. }
  3260. break;
  3261. }
  3262. }
  3263. if($restBytes > 0) $junk = fread($this->fp, $restBytes);
  3264. }
  3265. $compressionType = $header_data['compression'];
  3266. // Populate the return array
  3267. $this->fileHeader->file = $file;
  3268. $this->fileHeader->compressed = $header_data['compsize'];
  3269. $this->fileHeader->uncompressed = $header_data['uncompsize'];
  3270. switch($header_data['type'])
  3271. {
  3272. case 0:
  3273. $this->fileHeader->type = 'dir';
  3274. break;
  3275. case 1:
  3276. $this->fileHeader->type = 'file';
  3277. break;
  3278. case 2:
  3279. $this->fileHeader->type = 'link';
  3280. break;
  3281. }
  3282. switch( $compressionType )
  3283. {
  3284. case 0:
  3285. $this->fileHeader->compression = 'none';
  3286. break;
  3287. case 1:
  3288. $this->fileHeader->compression = 'gzip';
  3289. break;
  3290. case 2:
  3291. $this->fileHeader->compression = 'bzip2';
  3292. break;
  3293. }
  3294. $this->fileHeader->permissions = $header_data['perms'];
  3295. // Find hard-coded banned files
  3296. if( (basename($this->fileHeader->file) == ".") || (basename($this->fileHeader->file) == "..") )
  3297. {
  3298. $isBannedFile = true;
  3299. }
  3300. // Also try to find banned files passed in class configuration
  3301. if((count($this->skipFiles) > 0) && (!$isRenamed) )
  3302. {
  3303. if(in_array($this->fileHeader->file, $this->skipFiles))
  3304. {
  3305. $isBannedFile = true;
  3306. }
  3307. }
  3308. // If we have a banned file, let's skip it
  3309. if($isBannedFile)
  3310. {
  3311. debugMsg('Skipping file '.$this->fileHeader->file);
  3312. // Advance the file pointer, skipping exactly the size of the compressed data
  3313. $seekleft = $this->fileHeader->compressed;
  3314. while($seekleft > 0)
  3315. {
  3316. // Ensure that we can seek past archive part boundaries
  3317. $curSize = @filesize($this->archiveList[$this->currentPartNumber]);
  3318. $curPos = @ftell($this->fp);
  3319. $canSeek = $curSize - $curPos;
  3320. if($canSeek > $seekleft) $canSeek = $seekleft;
  3321. @fseek( $this->fp, $canSeek, SEEK_CUR );
  3322. $seekleft -= $canSeek;
  3323. if($seekleft) $this->nextFile();
  3324. }
  3325. $this->currentPartOffset = @ftell($this->fp);
  3326. $this->runState = AK_STATE_DONE;
  3327. return true;
  3328. }
  3329. // Last chance to prepend a path to the filename
  3330. if(!empty($this->addPath) && !$isDirRenamed)
  3331. {
  3332. $this->fileHeader->file = $this->addPath.$this->fileHeader->file;
  3333. }
  3334. // Get the translated path name
  3335. $restorePerms = AKFactory::get('kickstart.setup.restoreperms', false);
  3336. if($this->fileHeader->type == 'file')
  3337. {
  3338. // Regular file; ask the postproc engine to process its filename
  3339. if($restorePerms)
  3340. {
  3341. $this->fileHeader->realFile = $this->postProcEngine->processFilename( $this->fileHeader->file, $this->fileHeader->permissions );
  3342. }
  3343. else
  3344. {
  3345. $this->fileHeader->realFile = $this->postProcEngine->processFilename( $this->fileHeader->file );
  3346. }
  3347. }
  3348. elseif($this->fileHeader->type == 'dir')
  3349. {
  3350. $dir = $this->fileHeader->file;
  3351. // Directory; just create it
  3352. if($restorePerms)
  3353. {
  3354. $this->postProcEngine->createDirRecursive( $this->fileHeader->file, $this->fileHeader->permissions );
  3355. }
  3356. else
  3357. {
  3358. $this->postProcEngine->createDirRecursive( $this->fileHeader->file, 0755 );
  3359. }
  3360. $this->postProcEngine->processFilename(null);
  3361. }
  3362. else
  3363. {
  3364. // Symlink; do not post-process
  3365. $this->postProcEngine->processFilename(null);
  3366. }
  3367. $this->createDirectory();
  3368. // Header is read
  3369. $this->runState = AK_STATE_HEADER;
  3370. $this->dataReadLength = 0;
  3371. return true;
  3372. }
  3373. /**
  3374. * Concrete classes must use this method to process file data. It must set $runState to AK_STATE_DATAREAD when
  3375. * it's finished processing the file data.
  3376. * @return bool True if processing the file data was successful, false if an error occured
  3377. */
  3378. protected function processFileData()
  3379. {
  3380. switch( $this->fileHeader->type )
  3381. {
  3382. case 'dir':
  3383. return $this->processTypeDir();
  3384. break;
  3385. case 'link':
  3386. return $this->processTypeLink();
  3387. break;
  3388. case 'file':
  3389. switch($this->fileHeader->compression)
  3390. {
  3391. case 'none':
  3392. return $this->processTypeFileUncompressed();
  3393. break;
  3394. case 'gzip':
  3395. case 'bzip2':
  3396. return $this->processTypeFileCompressedSimple();
  3397. break;
  3398. }
  3399. break;
  3400. default:
  3401. debugMsg('Unknown file type '.$this->fileHeader->type);
  3402. break;
  3403. }
  3404. }
  3405. private function processTypeFileUncompressed()
  3406. {
  3407. // Uncompressed files are being processed in small chunks, to avoid timeouts
  3408. if( ($this->dataReadLength == 0) && !AKFactory::get('kickstart.setup.dryrun','0') )
  3409. {
  3410. // Before processing file data, ensure permissions are adequate
  3411. $this->setCorrectPermissions( $this->fileHeader->file );
  3412. }
  3413. // Open the output file
  3414. if( !AKFactory::get('kickstart.setup.dryrun','0') )
  3415. {
  3416. $ignore = AKFactory::get('kickstart.setup.ignoreerrors', false) || $this->isIgnoredDirectory($this->fileHeader->file);
  3417. if ($this->dataReadLength == 0) {
  3418. $outfp = @fopen( $this->fileHeader->realFile, 'wb' );
  3419. } else {
  3420. $outfp = @fopen( $this->fileHeader->realFile, 'ab' );
  3421. }
  3422. // Can we write to the file?
  3423. if( ($outfp === false) && (!$ignore) ) {
  3424. // An error occured
  3425. debugMsg('Could not write to output file');
  3426. $this->setError( AKText::sprintf('COULDNT_WRITE_FILE', $this->fileHeader->realFile) );
  3427. return false;
  3428. }
  3429. }
  3430. // Does the file have any data, at all?
  3431. if( $this->fileHeader->compressed == 0 )
  3432. {
  3433. // No file data!
  3434. if( !AKFactory::get('kickstart.setup.dryrun','0') && is_resource($outfp) ) @fclose($outfp);
  3435. $this->runState = AK_STATE_DATAREAD;
  3436. return true;
  3437. }
  3438. // Reference to the global timer
  3439. $timer = AKFactory::getTimer();
  3440. $toReadBytes = 0;
  3441. $leftBytes = $this->fileHeader->compressed - $this->dataReadLength;
  3442. // Loop while there's data to read and enough time to do it
  3443. while( ($leftBytes > 0) && ($timer->getTimeLeft() > 0) )
  3444. {
  3445. $toReadBytes = ($leftBytes > $this->chunkSize) ? $this->chunkSize : $leftBytes;
  3446. $data = $this->fread( $this->fp, $toReadBytes );
  3447. $reallyReadBytes = akstringlen($data);
  3448. $leftBytes -= $reallyReadBytes;
  3449. $this->dataReadLength += $reallyReadBytes;
  3450. if($reallyReadBytes < $toReadBytes)
  3451. {
  3452. // We read less than requested! Why? Did we hit local EOF?
  3453. if( $this->isEOF(true) && !$this->isEOF(false) )
  3454. {
  3455. // Yeap. Let's go to the next file
  3456. $this->nextFile();
  3457. }
  3458. else
  3459. {
  3460. // Nope. The archive is corrupt
  3461. debugMsg('Not enough data in file. The archive is truncated or corrupt.');
  3462. $this->setError( AKText::_('ERR_CORRUPT_ARCHIVE') );
  3463. return false;
  3464. }
  3465. }
  3466. if( !AKFactory::get('kickstart.setup.dryrun','0') )
  3467. if(is_resource($outfp)) @fwrite( $outfp, $data );
  3468. }
  3469. // Close the file pointer
  3470. if( !AKFactory::get('kickstart.setup.dryrun','0') )
  3471. if(is_resource($outfp)) @fclose($outfp);
  3472. // Was this a pre-timeout bail out?
  3473. if( $leftBytes > 0 )
  3474. {
  3475. $this->runState = AK_STATE_DATA;
  3476. }
  3477. else
  3478. {
  3479. // Oh! We just finished!
  3480. $this->runState = AK_STATE_DATAREAD;
  3481. $this->dataReadLength = 0;
  3482. }
  3483. return true;
  3484. }
  3485. private function processTypeFileCompressedSimple()
  3486. {
  3487. if( !AKFactory::get('kickstart.setup.dryrun','0') )
  3488. {
  3489. // Before processing file data, ensure permissions are adequate
  3490. $this->setCorrectPermissions( $this->fileHeader->file );
  3491. // Open the output file
  3492. $outfp = @fopen( $this->fileHeader->realFile, 'wb' );
  3493. // Can we write to the file?
  3494. $ignore = AKFactory::get('kickstart.setup.ignoreerrors', false) || $this->isIgnoredDirectory($this->fileHeader->file);
  3495. if( ($outfp === false) && (!$ignore) ) {
  3496. // An error occured
  3497. debugMsg('Could not write to output file');
  3498. $this->setError( AKText::sprintf('COULDNT_WRITE_FILE', $this->fileHeader->realFile) );
  3499. return false;
  3500. }
  3501. }
  3502. // Does the file have any data, at all?
  3503. if( $this->fileHeader->compressed == 0 )
  3504. {
  3505. // No file data!
  3506. if( !AKFactory::get('kickstart.setup.dryrun','0') )
  3507. if(is_resource($outfp)) @fclose($outfp);
  3508. $this->runState = AK_STATE_DATAREAD;
  3509. return true;
  3510. }
  3511. // Simple compressed files are processed as a whole; we can't do chunk processing
  3512. $zipData = $this->fread( $this->fp, $this->fileHeader->compressed );
  3513. while( akstringlen($zipData) < $this->fileHeader->compressed )
  3514. {
  3515. // End of local file before reading all data, but have more archive parts?
  3516. if($this->isEOF(true) && !$this->isEOF(false))
  3517. {
  3518. // Yeap. Read from the next file
  3519. $this->nextFile();
  3520. $bytes_left = $this->fileHeader->compressed - akstringlen($zipData);
  3521. $zipData .= $this->fread( $this->fp, $bytes_left );
  3522. }
  3523. else
  3524. {
  3525. debugMsg('End of local file before reading all data with no more parts left. The archive is corrupt or truncated.');
  3526. $this->setError( AKText::_('ERR_CORRUPT_ARCHIVE') );
  3527. return false;
  3528. }
  3529. }
  3530. if($this->fileHeader->compression == 'gzip')
  3531. {
  3532. $unzipData = gzinflate( $zipData );
  3533. }
  3534. elseif($this->fileHeader->compression == 'bzip2')
  3535. {
  3536. $unzipData = bzdecompress( $zipData );
  3537. }
  3538. unset($zipData);
  3539. // Write to the file.
  3540. if( !AKFactory::get('kickstart.setup.dryrun','0') && is_resource($outfp) )
  3541. {
  3542. @fwrite( $outfp, $unzipData, $this->fileHeader->uncompressed );
  3543. @fclose( $outfp );
  3544. }
  3545. unset($unzipData);
  3546. $this->runState = AK_STATE_DATAREAD;
  3547. return true;
  3548. }
  3549. /**
  3550. * Process the file data of a link entry
  3551. * @return bool
  3552. */
  3553. private function processTypeLink()
  3554. {
  3555. $readBytes = 0;
  3556. $toReadBytes = 0;
  3557. $leftBytes = $this->fileHeader->compressed;
  3558. $data = '';
  3559. while( $leftBytes > 0)
  3560. {
  3561. $toReadBytes = ($leftBytes > $this->chunkSize) ? $this->chunkSize : $leftBytes;
  3562. $mydata = $this->fread( $this->fp, $toReadBytes );
  3563. $reallyReadBytes = akstringlen($mydata);
  3564. $data .= $mydata;
  3565. $leftBytes -= $reallyReadBytes;
  3566. if($reallyReadBytes < $toReadBytes)
  3567. {
  3568. // We read less than requested! Why? Did we hit local EOF?
  3569. if( $this->isEOF(true) && !$this->isEOF(false) )
  3570. {
  3571. // Yeap. Let's go to the next file
  3572. $this->nextFile();
  3573. }
  3574. else
  3575. {
  3576. debugMsg('End of local file before reading all data with no more parts left. The archive is corrupt or truncated.');
  3577. // Nope. The archive is corrupt
  3578. $this->setError( AKText::_('ERR_CORRUPT_ARCHIVE') );
  3579. return false;
  3580. }
  3581. }
  3582. }
  3583. // Try to remove an existing file or directory by the same name
  3584. if(file_exists($this->fileHeader->realFile)) { @unlink($this->fileHeader->realFile); @rmdir($this->fileHeader->realFile); }
  3585. // Remove any trailing slash
  3586. if(substr($this->fileHeader->realFile, -1) == '/') $this->fileHeader->realFile = substr($this->fileHeader->realFile, 0, -1);
  3587. // Create the symlink - only possible within PHP context. There's no support built in the FTP protocol, so no postproc use is possible here :(
  3588. if( !AKFactory::get('kickstart.setup.dryrun','0') )
  3589. @symlink($data, $this->fileHeader->realFile);
  3590. $this->runState = AK_STATE_DATAREAD;
  3591. return true; // No matter if the link was created!
  3592. }
  3593. /**
  3594. * Process the file data of a directory entry
  3595. * @return bool
  3596. */
  3597. private function processTypeDir()
  3598. {
  3599. // Directory entries in the JPA do not have file data, therefore we're done processing the entry
  3600. $this->runState = AK_STATE_DATAREAD;
  3601. return true;
  3602. }
  3603. /**
  3604. * Creates the directory this file points to
  3605. */
  3606. protected function createDirectory()
  3607. {
  3608. if( AKFactory::get('kickstart.setup.dryrun','0') ) return true;
  3609. // Do we need to create a directory?
  3610. if(empty($this->fileHeader->realFile)) $this->fileHeader->realFile = $this->fileHeader->file;
  3611. $lastSlash = strrpos($this->fileHeader->realFile, '/');
  3612. $dirName = substr( $this->fileHeader->realFile, 0, $lastSlash);
  3613. $perms = $this->flagRestorePermissions ? $retArray['permissions'] : 0755;
  3614. $ignore = AKFactory::get('kickstart.setup.ignoreerrors', false) || $this->isIgnoredDirectory($dirName);
  3615. if( ($this->postProcEngine->createDirRecursive($dirName, $perms) == false) && (!$ignore) ) {
  3616. $this->setError( AKText::sprintf('COULDNT_CREATE_DIR', $dirName) );
  3617. return false;
  3618. }
  3619. else
  3620. {
  3621. return true;
  3622. }
  3623. }
  3624. protected function heuristicFileHeaderLocator()
  3625. {
  3626. $ret = false;
  3627. $fullEOF = false;
  3628. while(!$ret && !$fullEOF) {
  3629. $this->currentPartOffset = @ftell($this->fp);
  3630. if($this->isEOF(true)) {
  3631. $this->nextFile();
  3632. }
  3633. if($this->isEOF(false)) {
  3634. $fullEOF = true;
  3635. continue;
  3636. }
  3637. // Read 512Kb
  3638. $chunk = fread($this->fp, 524288);
  3639. $size_read = mb_strlen($string,'8bit');
  3640. //$pos = strpos($chunk, 'JPF');
  3641. $pos = mb_strpos($chunk, 'JPF', 0, '8bit');
  3642. if($pos !== false) {
  3643. // We found it!
  3644. $this->currentPartOffset += $pos + 3;
  3645. @fseek($this->fp, $this->currentPartOffset, SEEK_SET);
  3646. $ret = true;
  3647. } else {
  3648. // Not yet found :(
  3649. $this->currentPartOffset = @ftell($this->fp);
  3650. }
  3651. }
  3652. return $ret;
  3653. }
  3654. }
  3655. /**
  3656. * Akeeba Restore
  3657. * A JSON-powered JPA, JPS and ZIP archive extraction library
  3658. *
  3659. * @copyright 2010-2013 Nicholas K. Dionysopoulos / Akeeba Ltd.
  3660. * @license GNU GPL v2 or - at your option - any later version
  3661. * @package akeebabackup
  3662. * @subpackage kickstart
  3663. */
  3664. /**
  3665. * ZIP archive extraction class
  3666. *
  3667. * Since the file data portion of ZIP and JPA are similarly structured (it's empty for dirs,
  3668. * linked node name for symlinks, dumped binary data for no compressions and dumped gzipped
  3669. * binary data for gzip compression) we just have to subclass AKUnarchiverJPA and change the
  3670. * header reading bits. Reusable code ;)
  3671. */
  3672. class AKUnarchiverZIP extends AKUnarchiverJPA
  3673. {
  3674. var $expectDataDescriptor = false;
  3675. protected function readArchiveHeader()
  3676. {
  3677. debugMsg('Preparing to read archive header');
  3678. // Initialize header data array
  3679. $this->archiveHeaderData = new stdClass();
  3680. // Open the first part
  3681. debugMsg('Opening the first part');
  3682. $this->nextFile();
  3683. // Fail for unreadable files
  3684. if( $this->fp === false ) {
  3685. debugMsg('The first part is not readable');
  3686. return false;
  3687. }
  3688. // Read a possible multipart signature
  3689. $sigBinary = fread( $this->fp, 4 );
  3690. $headerData = unpack('Vsig', $sigBinary);
  3691. // Roll back if it's not a multipart archive
  3692. if( $headerData['sig'] == 0x04034b50 ) {
  3693. debugMsg('The archive is not multipart');
  3694. fseek($this->fp, -4, SEEK_CUR);
  3695. } else {
  3696. debugMsg('The archive is multipart');
  3697. }
  3698. $multiPartSigs = array(
  3699. 0x08074b50, // Multi-part ZIP
  3700. 0x30304b50, // Multi-part ZIP (alternate)
  3701. 0x04034b50 // Single file
  3702. );
  3703. if( !in_array($headerData['sig'], $multiPartSigs) )
  3704. {
  3705. debugMsg('Invalid header signature '.dechex($headerData['sig']));
  3706. $this->setError(AKText::_('ERR_CORRUPT_ARCHIVE'));
  3707. return false;
  3708. }
  3709. $this->currentPartOffset = @ftell($this->fp);
  3710. debugMsg('Current part offset after reading header: '.$this->currentPartOffset);
  3711. $this->dataReadLength = 0;
  3712. return true;
  3713. }
  3714. /**
  3715. * Concrete classes must use this method to read the file header
  3716. * @return bool True if reading the file was successful, false if an error occured or we reached end of archive
  3717. */
  3718. protected function readFileHeader()
  3719. {
  3720. // If the current part is over, proceed to the next part please
  3721. if( $this->isEOF(true) ) {
  3722. debugMsg('Opening next archive part');
  3723. $this->nextFile();
  3724. }
  3725. if($this->expectDataDescriptor)
  3726. {
  3727. // The last file had bit 3 of the general purpose bit flag set. This means that we have a
  3728. // 12 byte data descriptor we need to skip. To make things worse, there might also be a 4
  3729. // byte optional data descriptor header (0x08074b50).
  3730. $junk = @fread($this->fp, 4);
  3731. $junk = unpack('Vsig', $junk);
  3732. if($junk['sig'] == 0x08074b50) {
  3733. // Yes, there was a signature
  3734. $junk = @fread($this->fp, 12);
  3735. debugMsg('Data descriptor (w/ header) skipped at '.(ftell($this->fp)-12));
  3736. } else {
  3737. // No, there was no signature, just read another 8 bytes
  3738. $junk = @fread($this->fp, 8);
  3739. debugMsg('Data descriptor (w/out header) skipped at '.(ftell($this->fp)-8));
  3740. }
  3741. // And check for EOF, too
  3742. if( $this->isEOF(true) ) {
  3743. debugMsg('EOF before reading header');
  3744. $this->nextFile();
  3745. }
  3746. }
  3747. // Get and decode Local File Header
  3748. $headerBinary = fread($this->fp, 30);
  3749. $headerData = unpack('Vsig/C2ver/vbitflag/vcompmethod/vlastmodtime/vlastmoddate/Vcrc/Vcompsize/Vuncomp/vfnamelen/veflen', $headerBinary);
  3750. // Check signature
  3751. if(!( $headerData['sig'] == 0x04034b50 ))
  3752. {
  3753. debugMsg('Not a file signature at '.(ftell($this->fp)-4));
  3754. // The signature is not the one used for files. Is this a central directory record (i.e. we're done)?
  3755. if($headerData['sig'] == 0x02014b50)
  3756. {
  3757. debugMsg('EOCD signature at '.(ftell($this->fp)-4));
  3758. // End of ZIP file detected. We'll just skip to the end of file...
  3759. while( $this->nextFile() ) {};
  3760. @fseek($this->fp, 0, SEEK_END); // Go to EOF
  3761. return false;
  3762. }
  3763. else
  3764. {
  3765. debugMsg( 'Invalid signature ' . dechex($headerData['sig']) . ' at '.ftell($this->fp) );
  3766. $this->setError(AKText::_('ERR_CORRUPT_ARCHIVE'));
  3767. return false;
  3768. }
  3769. }
  3770. // If bit 3 of the bitflag is set, expectDataDescriptor is true
  3771. $this->expectDataDescriptor = ($headerData['bitflag'] & 4) == 4;
  3772. $this->fileHeader = new stdClass();
  3773. $this->fileHeader->timestamp = 0;
  3774. // Read the last modified data and time
  3775. $lastmodtime = $headerData['lastmodtime'];
  3776. $lastmoddate = $headerData['lastmoddate'];
  3777. if($lastmoddate && $lastmodtime)
  3778. {
  3779. // ----- Extract time
  3780. $v_hour = ($lastmodtime & 0xF800) >> 11;
  3781. $v_minute = ($lastmodtime & 0x07E0) >> 5;
  3782. $v_seconde = ($lastmodtime & 0x001F)*2;
  3783. // ----- Extract date
  3784. $v_year = (($lastmoddate & 0xFE00) >> 9) + 1980;
  3785. $v_month = ($lastmoddate & 0x01E0) >> 5;
  3786. $v_day = $lastmoddate & 0x001F;
  3787. // ----- Get UNIX date format
  3788. $this->fileHeader->timestamp = @mktime($v_hour, $v_minute, $v_seconde, $v_month, $v_day, $v_year);
  3789. }
  3790. $isBannedFile = false;
  3791. $this->fileHeader->compressed = $headerData['compsize'];
  3792. $this->fileHeader->uncompressed = $headerData['uncomp'];
  3793. $nameFieldLength = $headerData['fnamelen'];
  3794. $extraFieldLength = $headerData['eflen'];
  3795. // Read filename field
  3796. $this->fileHeader->file = fread( $this->fp, $nameFieldLength );
  3797. // Handle file renaming
  3798. $isRenamed = false;
  3799. if(is_array($this->renameFiles) && (count($this->renameFiles) > 0) )
  3800. {
  3801. if(array_key_exists($this->fileHeader->file, $this->renameFiles))
  3802. {
  3803. $this->fileHeader->file = $this->renameFiles[$this->fileHeader->file];
  3804. $isRenamed = true;
  3805. }
  3806. }
  3807. // Handle directory renaming
  3808. $isDirRenamed = false;
  3809. if(is_array($this->renameDirs) && (count($this->renameDirs) > 0)) {
  3810. if(array_key_exists(dirname($this->fileHeader->file), $this->renameDirs)) {
  3811. $file = rtrim($this->renameDirs[dirname($this->fileHeader->file)],'/').'/'.basename($this->fileHeader->file);
  3812. $isRenamed = true;
  3813. $isDirRenamed = true;
  3814. }
  3815. }
  3816. // Read extra field if present
  3817. if($extraFieldLength > 0) {
  3818. $extrafield = fread( $this->fp, $extraFieldLength );
  3819. }
  3820. debugMsg( '*'.ftell($this->fp).' IS START OF '.$this->fileHeader->file. ' ('.$this->fileHeader->compressed.' bytes)' );
  3821. // Decide filetype -- Check for directories
  3822. $this->fileHeader->type = 'file';
  3823. if( strrpos($this->fileHeader->file, '/') == strlen($this->fileHeader->file) - 1 ) $this->fileHeader->type = 'dir';
  3824. // Decide filetype -- Check for symbolic links
  3825. if( ($headerData['ver1'] == 10) && ($headerData['ver2'] == 3) )$this->fileHeader->type = 'link';
  3826. switch( $headerData['compmethod'] )
  3827. {
  3828. case 0:
  3829. $this->fileHeader->compression = 'none';
  3830. break;
  3831. case 8:
  3832. $this->fileHeader->compression = 'gzip';
  3833. break;
  3834. }
  3835. // Find hard-coded banned files
  3836. if( (basename($this->fileHeader->file) == ".") || (basename($this->fileHeader->file) == "..") )
  3837. {
  3838. $isBannedFile = true;
  3839. }
  3840. // Also try to find banned files passed in class configuration
  3841. if((count($this->skipFiles) > 0) && (!$isRenamed))
  3842. {
  3843. if(in_array($this->fileHeader->file, $this->skipFiles))
  3844. {
  3845. $isBannedFile = true;
  3846. }
  3847. }
  3848. // If we have a banned file, let's skip it
  3849. if($isBannedFile)
  3850. {
  3851. // Advance the file pointer, skipping exactly the size of the compressed data
  3852. $seekleft = $this->fileHeader->compressed;
  3853. while($seekleft > 0)
  3854. {
  3855. // Ensure that we can seek past archive part boundaries
  3856. $curSize = @filesize($this->archiveList[$this->currentPartNumber]);
  3857. $curPos = @ftell($this->fp);
  3858. $canSeek = $curSize - $curPos;
  3859. if($canSeek > $seekleft) $canSeek = $seekleft;
  3860. @fseek( $this->fp, $canSeek, SEEK_CUR );
  3861. $seekleft -= $canSeek;
  3862. if($seekleft) $this->nextFile();
  3863. }
  3864. $this->currentPartOffset = @ftell($this->fp);
  3865. $this->runState = AK_STATE_DONE;
  3866. return true;
  3867. }
  3868. // Last chance to prepend a path to the filename
  3869. if(!empty($this->addPath) && !$isDirRenamed)
  3870. {
  3871. $this->fileHeader->file = $this->addPath.$this->fileHeader->file;
  3872. }
  3873. // Get the translated path name
  3874. if($this->fileHeader->type == 'file')
  3875. {
  3876. $this->fileHeader->realFile = $this->postProcEngine->processFilename( $this->fileHeader->file );
  3877. }
  3878. elseif($this->fileHeader->type == 'dir')
  3879. {
  3880. $this->fileHeader->timestamp = 0;
  3881. $dir = $this->fileHeader->file;
  3882. $this->postProcEngine->createDirRecursive( $this->fileHeader->file, 0755 );
  3883. $this->postProcEngine->processFilename(null);
  3884. }
  3885. else
  3886. {
  3887. // Symlink; do not post-process
  3888. $this->fileHeader->timestamp = 0;
  3889. $this->postProcEngine->processFilename(null);
  3890. }
  3891. $this->createDirectory();
  3892. // Header is read
  3893. $this->runState = AK_STATE_HEADER;
  3894. return true;
  3895. }
  3896. }
  3897. /**
  3898. * Akeeba Restore
  3899. * A JSON-powered JPA, JPS and ZIP archive extraction library
  3900. *
  3901. * @copyright 2010-2013 Nicholas K. Dionysopoulos / Akeeba Ltd.
  3902. * @license GNU GPL v2 or - at your option - any later version
  3903. * @package akeebabackup
  3904. * @subpackage kickstart
  3905. */
  3906. /**
  3907. * JPS archive extraction class
  3908. */
  3909. class AKUnarchiverJPS extends AKUnarchiverJPA
  3910. {
  3911. private $archiveHeaderData = array();
  3912. private $password = '';
  3913. public function __construct()
  3914. {
  3915. parent::__construct();
  3916. $this->password = AKFactory::get('kickstart.jps.password','');
  3917. }
  3918. protected function readArchiveHeader()
  3919. {
  3920. // Initialize header data array
  3921. $this->archiveHeaderData = new stdClass();
  3922. // Open the first part
  3923. $this->nextFile();
  3924. // Fail for unreadable files
  3925. if( $this->fp === false ) return false;
  3926. // Read the signature
  3927. $sig = fread( $this->fp, 3 );
  3928. if ($sig != 'JPS')
  3929. {
  3930. // Not a JPA file
  3931. $this->setError( AKText::_('ERR_NOT_A_JPS_FILE') );
  3932. return false;
  3933. }
  3934. // Read and parse the known portion of header data (5 bytes)
  3935. $bin_data = fread($this->fp, 5);
  3936. $header_data = unpack('Cmajor/Cminor/cspanned/vextra', $bin_data);
  3937. // Load any remaining header data (forward compatibility)
  3938. $rest_length = $header_data['extra'];
  3939. if( $rest_length > 0 )
  3940. $junk = fread($this->fp, $rest_length);
  3941. else
  3942. $junk = '';
  3943. // Temporary array with all the data we read
  3944. $temp = array(
  3945. 'signature' => $sig,
  3946. 'major' => $header_data['major'],
  3947. 'minor' => $header_data['minor'],
  3948. 'spanned' => $header_data['spanned']
  3949. );
  3950. // Array-to-object conversion
  3951. foreach($temp as $key => $value)
  3952. {
  3953. $this->archiveHeaderData->{$key} = $value;
  3954. }
  3955. $this->currentPartOffset = @ftell($this->fp);
  3956. $this->dataReadLength = 0;
  3957. return true;
  3958. }
  3959. /**
  3960. * Concrete classes must use this method to read the file header
  3961. * @return bool True if reading the file was successful, false if an error occured or we reached end of archive
  3962. */
  3963. protected function readFileHeader()
  3964. {
  3965. // If the current part is over, proceed to the next part please
  3966. if( $this->isEOF(true) ) {
  3967. $this->nextFile();
  3968. }
  3969. // Get and decode Entity Description Block
  3970. $signature = fread($this->fp, 3);
  3971. // Check for end-of-archive siganture
  3972. if($signature == 'JPE')
  3973. {
  3974. $this->setState('postrun');
  3975. return true;
  3976. }
  3977. $this->fileHeader = new stdClass();
  3978. $this->fileHeader->timestamp = 0;
  3979. // Check signature
  3980. if( $signature != 'JPF' )
  3981. {
  3982. if($this->isEOF(true))
  3983. {
  3984. // This file is finished; make sure it's the last one
  3985. $this->nextFile();
  3986. if(!$this->isEOF(false))
  3987. {
  3988. $this->setError(AKText::sprintf('INVALID_FILE_HEADER', $this->currentPartNumber, $this->currentPartOffset));
  3989. return false;
  3990. }
  3991. // We're just finished
  3992. return false;
  3993. }
  3994. else
  3995. {
  3996. fseek($this->fp, -6, SEEK_CUR);
  3997. $signature = fread($this->fp, 3);
  3998. if($signature == 'JPE')
  3999. {
  4000. return false;
  4001. }
  4002. $this->setError(AKText::sprintf('INVALID_FILE_HEADER', $this->currentPartNumber, $this->currentPartOffset));
  4003. return false;
  4004. }
  4005. }
  4006. // This a JPA Entity Block. Process the header.
  4007. $isBannedFile = false;
  4008. // Read and decrypt the header
  4009. $edbhData = fread($this->fp, 4);
  4010. $edbh = unpack('vencsize/vdecsize', $edbhData);
  4011. $bin_data = fread($this->fp, $edbh['encsize']);
  4012. // Decrypt and truncate
  4013. $bin_data = AKEncryptionAES::AESDecryptCBC($bin_data, $this->password, 128);
  4014. $bin_data = substr($bin_data,0,$edbh['decsize']);
  4015. // Read length of EDB and of the Entity Path Data
  4016. $length_array = unpack('vpathsize', substr($bin_data,0,2) );
  4017. // Read the path data
  4018. $file = substr($bin_data,2,$length_array['pathsize']);
  4019. // Handle file renaming
  4020. $isRenamed = false;
  4021. if(is_array($this->renameFiles) && (count($this->renameFiles) > 0) )
  4022. {
  4023. if(array_key_exists($file, $this->renameFiles))
  4024. {
  4025. $file = $this->renameFiles[$file];
  4026. $isRenamed = true;
  4027. }
  4028. }
  4029. // Handle directory renaming
  4030. $isDirRenamed = false;
  4031. if(is_array($this->renameDirs) && (count($this->renameDirs) > 0)) {
  4032. if(array_key_exists(dirname($file), $this->renameDirs)) {
  4033. $file = rtrim($this->renameDirs[dirname($file)],'/').'/'.basename($file);
  4034. $isRenamed = true;
  4035. $isDirRenamed = true;
  4036. }
  4037. }
  4038. // Read and parse the known data portion
  4039. $bin_data = substr($bin_data, 2 + $length_array['pathsize']);
  4040. $header_data = unpack('Ctype/Ccompression/Vuncompsize/Vperms/Vfilectime', $bin_data);
  4041. $this->fileHeader->timestamp = $header_data['filectime'];
  4042. $compressionType = $header_data['compression'];
  4043. // Populate the return array
  4044. $this->fileHeader->file = $file;
  4045. $this->fileHeader->uncompressed = $header_data['uncompsize'];
  4046. switch($header_data['type'])
  4047. {
  4048. case 0:
  4049. $this->fileHeader->type = 'dir';
  4050. break;
  4051. case 1:
  4052. $this->fileHeader->type = 'file';
  4053. break;
  4054. case 2:
  4055. $this->fileHeader->type = 'link';
  4056. break;
  4057. }
  4058. switch( $compressionType )
  4059. {
  4060. case 0:
  4061. $this->fileHeader->compression = 'none';
  4062. break;
  4063. case 1:
  4064. $this->fileHeader->compression = 'gzip';
  4065. break;
  4066. case 2:
  4067. $this->fileHeader->compression = 'bzip2';
  4068. break;
  4069. }
  4070. $this->fileHeader->permissions = $header_data['perms'];
  4071. // Find hard-coded banned files
  4072. if( (basename($this->fileHeader->file) == ".") || (basename($this->fileHeader->file) == "..") )
  4073. {
  4074. $isBannedFile = true;
  4075. }
  4076. // Also try to find banned files passed in class configuration
  4077. if((count($this->skipFiles) > 0) && (!$isRenamed) )
  4078. {
  4079. if(in_array($this->fileHeader->file, $this->skipFiles))
  4080. {
  4081. $isBannedFile = true;
  4082. }
  4083. }
  4084. // If we have a banned file, let's skip it
  4085. if($isBannedFile)
  4086. {
  4087. $done = false;
  4088. while(!$done)
  4089. {
  4090. // Read the Data Chunk Block header
  4091. $binMiniHead = fread($this->fp, 8);
  4092. if( in_array( substr($binMiniHead,0,3), array('JPF','JPE') ) )
  4093. {
  4094. // Not a Data Chunk Block header, I am done skipping the file
  4095. @fseek($this->fp,-8,SEEK_CUR); // Roll back the file pointer
  4096. $done = true; // Mark as done
  4097. continue; // Exit loop
  4098. }
  4099. else
  4100. {
  4101. // Skip forward by the amount of compressed data
  4102. $miniHead = unpack('Vencsize/Vdecsize', $binMiniHead);
  4103. @fseek($this->fp, $miniHead['encsize'], SEEK_CUR);
  4104. }
  4105. }
  4106. $this->currentPartOffset = @ftell($this->fp);
  4107. $this->runState = AK_STATE_DONE;
  4108. return true;
  4109. }
  4110. // Last chance to prepend a path to the filename
  4111. if(!empty($this->addPath) && !$isDirRenamed)
  4112. {
  4113. $this->fileHeader->file = $this->addPath.$this->fileHeader->file;
  4114. }
  4115. // Get the translated path name
  4116. $restorePerms = AKFactory::get('kickstart.setup.restoreperms', false);
  4117. if($this->fileHeader->type == 'file')
  4118. {
  4119. // Regular file; ask the postproc engine to process its filename
  4120. if($restorePerms)
  4121. {
  4122. $this->fileHeader->realFile = $this->postProcEngine->processFilename( $this->fileHeader->file, $this->fileHeader->permissions );
  4123. }
  4124. else
  4125. {
  4126. $this->fileHeader->realFile = $this->postProcEngine->processFilename( $this->fileHeader->file );
  4127. }
  4128. }
  4129. elseif($this->fileHeader->type == 'dir')
  4130. {
  4131. $dir = $this->fileHeader->file;
  4132. $this->fileHeader->realFile = $dir;
  4133. // Directory; just create it
  4134. if($restorePerms)
  4135. {
  4136. $this->postProcEngine->createDirRecursive( $this->fileHeader->file, $this->fileHeader->permissions );
  4137. }
  4138. else
  4139. {
  4140. $this->postProcEngine->createDirRecursive( $this->fileHeader->file, 0755 );
  4141. }
  4142. $this->postProcEngine->processFilename(null);
  4143. }
  4144. else
  4145. {
  4146. // Symlink; do not post-process
  4147. $this->postProcEngine->processFilename(null);
  4148. }
  4149. $this->createDirectory();
  4150. // Header is read
  4151. $this->runState = AK_STATE_HEADER;
  4152. $this->dataReadLength = 0;
  4153. return true;
  4154. }
  4155. /**
  4156. * Concrete classes must use this method to process file data. It must set $runState to AK_STATE_DATAREAD when
  4157. * it's finished processing the file data.
  4158. * @return bool True if processing the file data was successful, false if an error occured
  4159. */
  4160. protected function processFileData()
  4161. {
  4162. switch( $this->fileHeader->type )
  4163. {
  4164. case 'dir':
  4165. return $this->processTypeDir();
  4166. break;
  4167. case 'link':
  4168. return $this->processTypeLink();
  4169. break;
  4170. case 'file':
  4171. switch($this->fileHeader->compression)
  4172. {
  4173. case 'none':
  4174. return $this->processTypeFileUncompressed();
  4175. break;
  4176. case 'gzip':
  4177. case 'bzip2':
  4178. return $this->processTypeFileCompressedSimple();
  4179. break;
  4180. }
  4181. break;
  4182. }
  4183. }
  4184. private function processTypeFileUncompressed()
  4185. {
  4186. // Uncompressed files are being processed in small chunks, to avoid timeouts
  4187. if( ($this->dataReadLength == 0) && !AKFactory::get('kickstart.setup.dryrun','0') )
  4188. {
  4189. // Before processing file data, ensure permissions are adequate
  4190. $this->setCorrectPermissions( $this->fileHeader->file );
  4191. }
  4192. // Open the output file
  4193. if( !AKFactory::get('kickstart.setup.dryrun','0') )
  4194. {
  4195. $ignore = AKFactory::get('kickstart.setup.ignoreerrors', false) || $this->isIgnoredDirectory($this->fileHeader->file);
  4196. if ($this->dataReadLength == 0) {
  4197. $outfp = @fopen( $this->fileHeader->realFile, 'wb' );
  4198. } else {
  4199. $outfp = @fopen( $this->fileHeader->realFile, 'ab' );
  4200. }
  4201. // Can we write to the file?
  4202. if( ($outfp === false) && (!$ignore) ) {
  4203. // An error occured
  4204. $this->setError( AKText::sprintf('COULDNT_WRITE_FILE', $this->fileHeader->realFile) );
  4205. return false;
  4206. }
  4207. }
  4208. // Does the file have any data, at all?
  4209. if( $this->fileHeader->uncompressed == 0 )
  4210. {
  4211. // No file data!
  4212. if( !AKFactory::get('kickstart.setup.dryrun','0') && is_resource($outfp) ) @fclose($outfp);
  4213. $this->runState = AK_STATE_DATAREAD;
  4214. return true;
  4215. }
  4216. else
  4217. {
  4218. $this->setError('An uncompressed file was detected; this is not supported by this archive extraction utility');
  4219. return false;
  4220. }
  4221. return true;
  4222. }
  4223. private function processTypeFileCompressedSimple()
  4224. {
  4225. $timer = AKFactory::getTimer();
  4226. // Files are being processed in small chunks, to avoid timeouts
  4227. if( ($this->dataReadLength == 0) && !AKFactory::get('kickstart.setup.dryrun','0') )
  4228. {
  4229. // Before processing file data, ensure permissions are adequate
  4230. $this->setCorrectPermissions( $this->fileHeader->file );
  4231. }
  4232. // Open the output file
  4233. if( !AKFactory::get('kickstart.setup.dryrun','0') )
  4234. {
  4235. // Open the output file
  4236. $outfp = @fopen( $this->fileHeader->realFile, 'wb' );
  4237. // Can we write to the file?
  4238. $ignore = AKFactory::get('kickstart.setup.ignoreerrors', false) || $this->isIgnoredDirectory($this->fileHeader->file);
  4239. if( ($outfp === false) && (!$ignore) ) {
  4240. // An error occured
  4241. $this->setError( AKText::sprintf('COULDNT_WRITE_FILE', $this->fileHeader->realFile) );
  4242. return false;
  4243. }
  4244. }
  4245. // Does the file have any data, at all?
  4246. if( $this->fileHeader->uncompressed == 0 )
  4247. {
  4248. // No file data!
  4249. if( !AKFactory::get('kickstart.setup.dryrun','0') )
  4250. if(is_resource($outfp)) @fclose($outfp);
  4251. $this->runState = AK_STATE_DATAREAD;
  4252. return true;
  4253. }
  4254. $leftBytes = $this->fileHeader->uncompressed - $this->dataReadLength;
  4255. // Loop while there's data to write and enough time to do it
  4256. while( ($leftBytes > 0) && ($timer->getTimeLeft() > 0) )
  4257. {
  4258. // Read the mini header
  4259. $binMiniHeader = fread($this->fp, 8);
  4260. $reallyReadBytes = akstringlen($binMiniHeader);
  4261. if($reallyReadBytes < 8)
  4262. {
  4263. // We read less than requested! Why? Did we hit local EOF?
  4264. if( $this->isEOF(true) && !$this->isEOF(false) )
  4265. {
  4266. // Yeap. Let's go to the next file
  4267. $this->nextFile();
  4268. // Retry reading the header
  4269. $binMiniHeader = fread($this->fp, 8);
  4270. $reallyReadBytes = akstringlen($binMiniHeader);
  4271. // Still not enough data? If so, the archive is corrupt or missing parts.
  4272. if($reallyReadBytes < 8) {
  4273. $this->setError( AKText::_('ERR_CORRUPT_ARCHIVE') );
  4274. return false;
  4275. }
  4276. }
  4277. else
  4278. {
  4279. // Nope. The archive is corrupt
  4280. $this->setError( AKText::_('ERR_CORRUPT_ARCHIVE') );
  4281. return false;
  4282. }
  4283. }
  4284. // Read the encrypted data
  4285. $miniHeader = unpack('Vencsize/Vdecsize', $binMiniHeader);
  4286. $toReadBytes = $miniHeader['encsize'];
  4287. $data = $this->fread( $this->fp, $toReadBytes );
  4288. $reallyReadBytes = akstringlen($data);
  4289. if($reallyReadBytes < $toReadBytes)
  4290. {
  4291. // We read less than requested! Why? Did we hit local EOF?
  4292. if( $this->isEOF(true) && !$this->isEOF(false) )
  4293. {
  4294. // Yeap. Let's go to the next file
  4295. $this->nextFile();
  4296. // Read the rest of the data
  4297. $toReadBytes -= $reallyReadBytes;
  4298. $restData = $this->fread( $this->fp, $toReadBytes );
  4299. $reallyReadBytes = akstringlen($restData);
  4300. if($reallyReadBytes < $toReadBytes) {
  4301. $this->setError( AKText::_('ERR_CORRUPT_ARCHIVE') );
  4302. return false;
  4303. }
  4304. if(akstringlen($data) == 0) {
  4305. $data = $restData;
  4306. } else {
  4307. $data .= $restData;
  4308. }
  4309. }
  4310. else
  4311. {
  4312. // Nope. The archive is corrupt
  4313. $this->setError( AKText::_('ERR_CORRUPT_ARCHIVE') );
  4314. return false;
  4315. }
  4316. }
  4317. // Decrypt the data
  4318. $data = AKEncryptionAES::AESDecryptCBC($data, $this->password, 128);
  4319. // Is the length of the decrypted data less than expected?
  4320. $data_length = akstringlen($data);
  4321. if($data_length < $miniHeader['decsize']) {
  4322. $this->setError(AKText::_('ERR_INVALID_JPS_PASSWORD'));
  4323. return false;
  4324. }
  4325. // Trim the data
  4326. $data = substr($data,0,$miniHeader['decsize']);
  4327. // Decompress
  4328. $data = gzinflate($data);
  4329. $unc_len = akstringlen($data);
  4330. // Write the decrypted data
  4331. if( !AKFactory::get('kickstart.setup.dryrun','0') )
  4332. if(is_resource($outfp)) @fwrite( $outfp, $data, akstringlen($data) );
  4333. // Update the read length
  4334. $this->dataReadLength += $unc_len;
  4335. $leftBytes = $this->fileHeader->uncompressed - $this->dataReadLength;
  4336. }
  4337. // Close the file pointer
  4338. if( !AKFactory::get('kickstart.setup.dryrun','0') )
  4339. if(is_resource($outfp)) @fclose($outfp);
  4340. // Was this a pre-timeout bail out?
  4341. if( $leftBytes > 0 )
  4342. {
  4343. $this->runState = AK_STATE_DATA;
  4344. }
  4345. else
  4346. {
  4347. // Oh! We just finished!
  4348. $this->runState = AK_STATE_DATAREAD;
  4349. $this->dataReadLength = 0;
  4350. }
  4351. return true;
  4352. }
  4353. /**
  4354. * Process the file data of a link entry
  4355. * @return bool
  4356. */
  4357. private function processTypeLink()
  4358. {
  4359. // Does the file have any data, at all?
  4360. if( $this->fileHeader->uncompressed == 0 )
  4361. {
  4362. // No file data!
  4363. $this->runState = AK_STATE_DATAREAD;
  4364. return true;
  4365. }
  4366. // Read the mini header
  4367. $binMiniHeader = fread($this->fp, 8);
  4368. $reallyReadBytes = akstringlen($binMiniHeader);
  4369. if($reallyReadBytes < 8)
  4370. {
  4371. // We read less than requested! Why? Did we hit local EOF?
  4372. if( $this->isEOF(true) && !$this->isEOF(false) )
  4373. {
  4374. // Yeap. Let's go to the next file
  4375. $this->nextFile();
  4376. // Retry reading the header
  4377. $binMiniHeader = fread($this->fp, 8);
  4378. $reallyReadBytes = akstringlen($binMiniHeader);
  4379. // Still not enough data? If so, the archive is corrupt or missing parts.
  4380. if($reallyReadBytes < 8) {
  4381. $this->setError( AKText::_('ERR_CORRUPT_ARCHIVE') );
  4382. return false;
  4383. }
  4384. }
  4385. else
  4386. {
  4387. // Nope. The archive is corrupt
  4388. $this->setError( AKText::_('ERR_CORRUPT_ARCHIVE') );
  4389. return false;
  4390. }
  4391. }
  4392. // Read the encrypted data
  4393. $miniHeader = unpack('Vencsize/Vdecsize', $binMiniHeader);
  4394. $toReadBytes = $miniHeader['encsize'];
  4395. $data = $this->fread( $this->fp, $toReadBytes );
  4396. $reallyReadBytes = akstringlen($data);
  4397. if($reallyReadBytes < $toReadBytes)
  4398. {
  4399. // We read less than requested! Why? Did we hit local EOF?
  4400. if( $this->isEOF(true) && !$this->isEOF(false) )
  4401. {
  4402. // Yeap. Let's go to the next file
  4403. $this->nextFile();
  4404. // Read the rest of the data
  4405. $toReadBytes -= $reallyReadBytes;
  4406. $restData = $this->fread( $this->fp, $toReadBytes );
  4407. $reallyReadBytes = akstringlen($data);
  4408. if($reallyReadBytes < $toReadBytes) {
  4409. $this->setError( AKText::_('ERR_CORRUPT_ARCHIVE') );
  4410. return false;
  4411. }
  4412. $data .= $restData;
  4413. }
  4414. else
  4415. {
  4416. // Nope. The archive is corrupt
  4417. $this->setError( AKText::_('ERR_CORRUPT_ARCHIVE') );
  4418. return false;
  4419. }
  4420. }
  4421. // Decrypt the data
  4422. $data = AKEncryptionAES::AESDecryptCBC($data, $this->password, 128);
  4423. // Is the length of the decrypted data less than expected?
  4424. $data_length = akstringlen($data);
  4425. if($data_length < $miniHeader['decsize']) {
  4426. $this->setError(AKText::_('ERR_INVALID_JPS_PASSWORD'));
  4427. return false;
  4428. }
  4429. // Trim the data
  4430. $data = substr($data,0,$miniHeader['decsize']);
  4431. // Try to remove an existing file or directory by the same name
  4432. if(file_exists($this->fileHeader->file)) { @unlink($this->fileHeader->file); @rmdir($this->fileHeader->file); }
  4433. // Remove any trailing slash
  4434. if(substr($this->fileHeader->file, -1) == '/') $this->fileHeader->file = substr($this->fileHeader->file, 0, -1);
  4435. // Create the symlink - only possible within PHP context. There's no support built in the FTP protocol, so no postproc use is possible here :(
  4436. if( !AKFactory::get('kickstart.setup.dryrun','0') )
  4437. {
  4438. @symlink($data, $this->fileHeader->file);
  4439. }
  4440. $this->runState = AK_STATE_DATAREAD;
  4441. return true; // No matter if the link was created!
  4442. }
  4443. /**
  4444. * Process the file data of a directory entry
  4445. * @return bool
  4446. */
  4447. private function processTypeDir()
  4448. {
  4449. // Directory entries in the JPA do not have file data, therefore we're done processing the entry
  4450. $this->runState = AK_STATE_DATAREAD;
  4451. return true;
  4452. }
  4453. /**
  4454. * Creates the directory this file points to
  4455. */
  4456. protected function createDirectory()
  4457. {
  4458. if( AKFactory::get('kickstart.setup.dryrun','0') ) return true;
  4459. // Do we need to create a directory?
  4460. $lastSlash = strrpos($this->fileHeader->realFile, '/');
  4461. $dirName = substr( $this->fileHeader->realFile, 0, $lastSlash);
  4462. $perms = $this->flagRestorePermissions ? $retArray['permissions'] : 0755;
  4463. $ignore = AKFactory::get('kickstart.setup.ignoreerrors', false) || $this->isIgnoredDirectory($dirName);
  4464. if( ($this->postProcEngine->createDirRecursive($dirName, $perms) == false) && (!$ignore) ) {
  4465. $this->setError( AKText::sprintf('COULDNT_CREATE_DIR', $dirName) );
  4466. return false;
  4467. }
  4468. else
  4469. {
  4470. return true;
  4471. }
  4472. }
  4473. }
  4474. /**
  4475. * Akeeba Restore
  4476. * A JSON-powered JPA, JPS and ZIP archive extraction library
  4477. *
  4478. * @copyright 2010-2013 Nicholas K. Dionysopoulos / Akeeba Ltd.
  4479. * @license GNU GPL v2 or - at your option - any later version
  4480. * @package akeebabackup
  4481. * @subpackage kickstart
  4482. */
  4483. /**
  4484. * Timer class
  4485. */
  4486. class AKCoreTimer extends AKAbstractObject
  4487. {
  4488. /** @var int Maximum execution time allowance per step */
  4489. private $max_exec_time = null;
  4490. /** @var int Timestamp of execution start */
  4491. private $start_time = null;
  4492. /**
  4493. * Public constructor, creates the timer object and calculates the execution time limits
  4494. * @return AECoreTimer
  4495. */
  4496. public function __construct()
  4497. {
  4498. parent::__construct();
  4499. // Initialize start time
  4500. $this->start_time = $this->microtime_float();
  4501. // Get configured max time per step and bias
  4502. $config_max_exec_time = AKFactory::get('kickstart.tuning.max_exec_time', 14);
  4503. $bias = AKFactory::get('kickstart.tuning.run_time_bias', 75)/100;
  4504. // Get PHP's maximum execution time (our upper limit)
  4505. if(@function_exists('ini_get'))
  4506. {
  4507. $php_max_exec_time = @ini_get("maximum_execution_time");
  4508. if ( (!is_numeric($php_max_exec_time)) || ($php_max_exec_time == 0) ) {
  4509. // If we have no time limit, set a hard limit of about 10 seconds
  4510. // (safe for Apache and IIS timeouts, verbose enough for users)
  4511. $php_max_exec_time = 14;
  4512. }
  4513. }
  4514. else
  4515. {
  4516. // If ini_get is not available, use a rough default
  4517. $php_max_exec_time = 14;
  4518. }
  4519. // Apply an arbitrary correction to counter CMS load time
  4520. $php_max_exec_time--;
  4521. // Apply bias
  4522. $php_max_exec_time = $php_max_exec_time * $bias;
  4523. $config_max_exec_time = $config_max_exec_time * $bias;
  4524. // Use the most appropriate time limit value
  4525. if( $config_max_exec_time > $php_max_exec_time )
  4526. {
  4527. $this->max_exec_time = $php_max_exec_time;
  4528. }
  4529. else
  4530. {
  4531. $this->max_exec_time = $config_max_exec_time;
  4532. }
  4533. }
  4534. /**
  4535. * Wake-up function to reset internal timer when we get unserialized
  4536. */
  4537. public function __wakeup()
  4538. {
  4539. // Re-initialize start time on wake-up
  4540. $this->start_time = $this->microtime_float();
  4541. }
  4542. /**
  4543. * Gets the number of seconds left, before we hit the "must break" threshold
  4544. * @return float
  4545. */
  4546. public function getTimeLeft()
  4547. {
  4548. return $this->max_exec_time - $this->getRunningTime();
  4549. }
  4550. /**
  4551. * Gets the time elapsed since object creation/unserialization, effectively how
  4552. * long Akeeba Engine has been processing data
  4553. * @return float
  4554. */
  4555. public function getRunningTime()
  4556. {
  4557. return $this->microtime_float() - $this->start_time;
  4558. }
  4559. /**
  4560. * Returns the current timestampt in decimal seconds
  4561. */
  4562. private function microtime_float()
  4563. {
  4564. list($usec, $sec) = explode(" ", microtime());
  4565. return ((float)$usec + (float)$sec);
  4566. }
  4567. /**
  4568. * Enforce the minimum execution time
  4569. */
  4570. public function enforce_min_exec_time()
  4571. {
  4572. // Try to get a sane value for PHP's maximum_execution_time INI parameter
  4573. if(@function_exists('ini_get'))
  4574. {
  4575. $php_max_exec = @ini_get("maximum_execution_time");
  4576. }
  4577. else
  4578. {
  4579. $php_max_exec = 10;
  4580. }
  4581. if ( ($php_max_exec == "") || ($php_max_exec == 0) ) {
  4582. $php_max_exec = 10;
  4583. }
  4584. // Decrease $php_max_exec time by 500 msec we need (approx.) to tear down
  4585. // the application, as well as another 500msec added for rounding
  4586. // error purposes. Also make sure this is never gonna be less than 0.
  4587. $php_max_exec = max($php_max_exec * 1000 - 1000, 0);
  4588. // Get the "minimum execution time per step" Akeeba Backup configuration variable
  4589. $minexectime = AKFactory::get('kickstart.tuning.min_exec_time',0);
  4590. if(!is_numeric($minexectime)) $minexectime = 0;
  4591. // Make sure we are not over PHP's time limit!
  4592. if($minexectime > $php_max_exec) $minexectime = $php_max_exec;
  4593. // Get current running time
  4594. $elapsed_time = $this->getRunningTime() * 1000;
  4595. // Only run a sleep delay if we haven't reached the minexectime execution time
  4596. if( ($minexectime > $elapsed_time) && ($elapsed_time > 0) )
  4597. {
  4598. $sleep_msec = $minexectime - $elapsed_time;
  4599. if(function_exists('usleep'))
  4600. {
  4601. usleep(1000 * $sleep_msec);
  4602. }
  4603. elseif(function_exists('time_nanosleep'))
  4604. {
  4605. $sleep_sec = floor($sleep_msec / 1000);
  4606. $sleep_nsec = 1000000 * ($sleep_msec - ($sleep_sec * 1000));
  4607. time_nanosleep($sleep_sec, $sleep_nsec);
  4608. }
  4609. elseif(function_exists('time_sleep_until'))
  4610. {
  4611. $until_timestamp = time() + $sleep_msec / 1000;
  4612. time_sleep_until($until_timestamp);
  4613. }
  4614. elseif(function_exists('sleep'))
  4615. {
  4616. $sleep_sec = ceil($sleep_msec/1000);
  4617. sleep( $sleep_sec );
  4618. }
  4619. }
  4620. elseif( $elapsed_time > 0 )
  4621. {
  4622. // No sleep required, even if user configured us to be able to do so.
  4623. }
  4624. }
  4625. /**
  4626. * Reset the timer. It should only be used in CLI mode!
  4627. */
  4628. public function resetTime()
  4629. {
  4630. $this->start_time = $this->microtime_float();
  4631. }
  4632. }
  4633. /**
  4634. * Akeeba Restore
  4635. * A JSON-powered JPA, JPS and ZIP archive extraction library
  4636. *
  4637. * @copyright 2010-2013 Nicholas K. Dionysopoulos / Akeeba Ltd.
  4638. * @license GNU GPL v2 or - at your option - any later version
  4639. * @package akeebabackup
  4640. * @subpackage kickstart
  4641. */
  4642. /**
  4643. * A filesystem scanner which uses opendir()
  4644. */
  4645. class AKUtilsLister extends AKAbstractObject
  4646. {
  4647. public function &getFiles($folder, $pattern = '*')
  4648. {
  4649. // Initialize variables
  4650. $arr = array();
  4651. $false = false;
  4652. if(!is_dir($folder)) return $false;
  4653. $handle = @opendir($folder);
  4654. // If directory is not accessible, just return FALSE
  4655. if ($handle === FALSE) {
  4656. $this->setWarning( 'Unreadable directory '.$folder);
  4657. return $false;
  4658. }
  4659. while (($file = @readdir($handle)) !== false)
  4660. {
  4661. if( !fnmatch($pattern, $file) ) continue;
  4662. if (($file != '.') && ($file != '..'))
  4663. {
  4664. $ds = ($folder == '') || ($folder == '/') || (@substr($folder, -1) == '/') || (@substr($folder, -1) == DIRECTORY_SEPARATOR) ? '' : DIRECTORY_SEPARATOR;
  4665. $dir = $folder . $ds . $file;
  4666. $isDir = is_dir($dir);
  4667. if (!$isDir) {
  4668. $arr[] = $dir;
  4669. }
  4670. }
  4671. }
  4672. @closedir($handle);
  4673. return $arr;
  4674. }
  4675. public function &getFolders($folder, $pattern = '*')
  4676. {
  4677. // Initialize variables
  4678. $arr = array();
  4679. $false = false;
  4680. if(!is_dir($folder)) return $false;
  4681. $handle = @opendir($folder);
  4682. // If directory is not accessible, just return FALSE
  4683. if ($handle === FALSE) {
  4684. $this->setWarning( 'Unreadable directory '.$folder);
  4685. return $false;
  4686. }
  4687. while (($file = @readdir($handle)) !== false)
  4688. {
  4689. if( !fnmatch($pattern, $file) ) continue;
  4690. if (($file != '.') && ($file != '..'))
  4691. {
  4692. $ds = ($folder == '') || ($folder == '/') || (@substr($folder, -1) == '/') || (@substr($folder, -1) == DIRECTORY_SEPARATOR) ? '' : DIRECTORY_SEPARATOR;
  4693. $dir = $folder . $ds . $file;
  4694. $isDir = is_dir($dir);
  4695. if ($isDir) {
  4696. $arr[] = $dir;
  4697. }
  4698. }
  4699. }
  4700. @closedir($handle);
  4701. return $arr;
  4702. }
  4703. }
  4704. /**
  4705. * Akeeba Restore
  4706. * A JSON-powered JPA, JPS and ZIP archive extraction library
  4707. *
  4708. * @copyright 2010-2013 Nicholas K. Dionysopoulos / Akeeba Ltd.
  4709. * @license GNU GPL v2 or - at your option - any later version
  4710. * @package akeebabackup
  4711. * @subpackage kickstart
  4712. */
  4713. /**
  4714. * A simple INI-based i18n engine
  4715. */
  4716. class AKText extends AKAbstractObject
  4717. {
  4718. /**
  4719. * The default (en_GB) translation used when no other translation is available
  4720. * @var array
  4721. */
  4722. private $default_translation = array(
  4723. 'AUTOMODEON' => 'Auto-mode enabled',
  4724. 'ERR_NOT_A_JPA_FILE' => 'The file is not a JPA archive',
  4725. 'ERR_CORRUPT_ARCHIVE' => 'The archive file is corrupt, truncated or archive parts are missing',
  4726. 'ERR_INVALID_LOGIN' => 'Invalid login',
  4727. 'COULDNT_CREATE_DIR' => 'Could not create %s folder',
  4728. 'COULDNT_WRITE_FILE' => 'Could not open %s for writing.',
  4729. 'WRONG_FTP_HOST' => 'Wrong FTP host or port',
  4730. 'WRONG_FTP_USER' => 'Wrong FTP username or password',
  4731. 'WRONG_FTP_PATH1' => 'Wrong FTP initial directory - the directory doesn\'t exist',
  4732. 'FTP_CANT_CREATE_DIR' => 'Could not create directory %s',
  4733. 'FTP_TEMPDIR_NOT_WRITABLE' => 'Could not find or create a writable temporary directory',
  4734. 'FTP_COULDNT_UPLOAD' => 'Could not upload %s',
  4735. 'THINGS_HEADER' => 'Things you should know about Akeeba Kickstart',
  4736. 'THINGS_01' => 'Kickstart is not an installer. It is an archive extraction tool. The actual installer was put inside the archive file at backup time.',
  4737. 'THINGS_02' => 'Kickstart is not the only way to extract the backup archive. You can use Akeeba eXtract Wizard and upload the extracted files using FTP instead.',
  4738. 'THINGS_03' => 'Kickstart is bound by your server\'s configuration. As such, it may not work at all.',
  4739. 'THINGS_04' => 'You should download and upload your archive files using FTP in Binary transfer mode. Any other method could lead to a corrupt backup archive and restoration failure.',
  4740. 'THINGS_05' => 'Post-restoration site load errors are usually caused by .htaccess or php.ini directives. You should understand that blank pages, 404 and 500 errors can usually be worked around by editing the aforementioned files. It is not our job to mess with your configuration files, because this could be dangerous for your site.',
  4741. 'THINGS_06' => 'Kickstart overwrites files without a warning. If you are not sure that you are OK with that do not continue.',
  4742. 'THINGS_07' => 'Trying to restore to the temporary URL of a cPanel host (e.g. http://1.2.3.4/~username) will lead to restoration failure and your site will appear to be not working. This is normal and it\'s just how your server and CMS software work.',
  4743. 'THINGS_08' => 'You are supposed to read the documentation before using this software. Most issues can be avoided, or easily worked around, by understanding how this software works.',
  4744. 'THINGS_09' => 'This text does not imply that there is a problem detected. It is standard text displayed every time you launch Kickstart.',
  4745. 'CLOSE_LIGHTBOX' => 'Click here or press ESC to close this message',
  4746. 'SELECT_ARCHIVE' => 'Select a backup archive',
  4747. 'ARCHIVE_FILE' => 'Archive file:',
  4748. 'SELECT_EXTRACTION' => 'Select an extraction method',
  4749. 'WRITE_TO_FILES' => 'Write to files:',
  4750. 'WRITE_HYBRID' => 'Hybrid (use FTP only if needed)',
  4751. 'WRITE_DIRECTLY' => 'Directly',
  4752. 'WRITE_FTP' => 'Use FTP for all files',
  4753. 'FTP_HOST' => 'FTP host name:',
  4754. 'FTP_PORT' => 'FTP port:',
  4755. 'FTP_FTPS' => 'Use FTP over SSL (FTPS)',
  4756. 'FTP_PASSIVE' => 'Use FTP Passive Mode',
  4757. 'FTP_USER' => 'FTP user name:',
  4758. 'FTP_PASS' => 'FTP password:',
  4759. 'FTP_DIR' => 'FTP directory:',
  4760. 'FTP_TEMPDIR' => 'Temporary directory:',
  4761. 'FTP_CONNECTION_OK' => 'FTP Connection Established',
  4762. 'FTP_CONNECTION_FAILURE' => 'The FTP Connection Failed',
  4763. 'FTP_TEMPDIR_WRITABLE' => 'The temporary directory is writable.',
  4764. 'FTP_TEMPDIR_UNWRITABLE' => 'The temporary directory is not writable. Please check the permissions.',
  4765. 'BTN_CHECK' => 'Check',
  4766. 'BTN_RESET' => 'Reset',
  4767. 'BTN_TESTFTPCON' => 'Test FTP connection',
  4768. 'BTN_GOTOSTART' => 'Start over',
  4769. 'FINE_TUNE' => 'Fine tune',
  4770. 'MIN_EXEC_TIME' => 'Minimum execution time:',
  4771. 'MAX_EXEC_TIME' => 'Maximum execution time:',
  4772. 'SECONDS_PER_STEP' => 'seconds per step',
  4773. 'EXTRACT_FILES' => 'Extract files',
  4774. 'BTN_START' => 'Start',
  4775. 'EXTRACTING' => 'Extracting',
  4776. 'DO_NOT_CLOSE_EXTRACT' => 'Do not close this window while the extraction is in progress',
  4777. 'RESTACLEANUP' => 'Restoration and Clean Up',
  4778. 'BTN_RUNINSTALLER' => 'Run the Installer',
  4779. 'BTN_CLEANUP' => 'Clean Up',
  4780. 'BTN_SITEFE' => 'Visit your site\'s front-end',
  4781. 'BTN_SITEBE' => 'Visit your site\'s back-end',
  4782. 'WARNINGS' => 'Extraction Warnings',
  4783. 'ERROR_OCCURED' => 'An error occured',
  4784. 'STEALTH_MODE' => 'Stealth mode',
  4785. 'STEALTH_URL' => 'HTML file to show to web visitors',
  4786. 'ERR_NOT_A_JPS_FILE' => 'The file is not a JPA archive',
  4787. 'ERR_INVALID_JPS_PASSWORD' => 'The password you gave is wrong or the archive is corrupt',
  4788. 'JPS_PASSWORD' => 'Archive Password (for JPS files)',
  4789. 'INVALID_FILE_HEADER' => 'Invalid header in archive file, part %s, offset %s',
  4790. 'NEEDSOMEHELPKS' => 'Want some help to use this tool? Read this first:',
  4791. 'QUICKSTART' => 'Quick Start Guide',
  4792. 'CANTGETITTOWORK' => 'Can\'t get it to work? Click me!',
  4793. 'NOARCHIVESCLICKHERE' => 'No archives detected. Click here for troubleshooting instructions.',
  4794. 'POSTRESTORATIONTROUBLESHOOTING' => 'Something not working after the restoration? Click here for troubleshooting instructions.',
  4795. 'UPDATE_HEADER' => 'An updated version of Akeeba Kickstart (<span id="update-version">unknown</span>) is available!',
  4796. 'UPDATE_NOTICE' => 'You are advised to always use the latest version of Akeeba Kickstart available. Older versions may be subject to bugs and will not be supported.',
  4797. 'UPDATE_DLNOW' => 'Download now',
  4798. 'UPDATE_MOREINFO' => 'More information',
  4799. 'IGNORE_MOST_ERRORS' => 'Ignore most errors'
  4800. );
  4801. /**
  4802. * The array holding the translation keys
  4803. * @var array
  4804. */
  4805. private $strings;
  4806. /**
  4807. * The currently detected language (ISO code)
  4808. * @var string
  4809. */
  4810. private $language;
  4811. /*
  4812. * Initializes the translation engine
  4813. * @return AKText
  4814. */
  4815. public function __construct()
  4816. {
  4817. // Start with the default translation
  4818. $this->strings = $this->default_translation;
  4819. // Try loading the translation file in English, if it exists
  4820. $this->loadTranslation('en-GB');
  4821. // Try loading the translation file in the browser's preferred language, if it exists
  4822. $this->getBrowserLanguage();
  4823. if(!is_null($this->language))
  4824. {
  4825. $this->loadTranslation();
  4826. }
  4827. }
  4828. /**
  4829. * Singleton pattern for Language
  4830. * @return Language The global Language instance
  4831. */
  4832. public static function &getInstance()
  4833. {
  4834. static $instance;
  4835. if(!is_object($instance))
  4836. {
  4837. $instance = new AKText();
  4838. }
  4839. return $instance;
  4840. }
  4841. public static function _($string)
  4842. {
  4843. $text = self::getInstance();
  4844. $key = strtoupper($string);
  4845. $key = substr($key, 0, 1) == '_' ? substr($key, 1) : $key;
  4846. if (isset ($text->strings[$key]))
  4847. {
  4848. $string = $text->strings[$key];
  4849. }
  4850. else
  4851. {
  4852. if (defined($string))
  4853. {
  4854. $string = constant($string);
  4855. }
  4856. }
  4857. return $string;
  4858. }
  4859. public static function sprintf($key)
  4860. {
  4861. $text = self::getInstance();
  4862. $args = func_get_args();
  4863. if (count($args) > 0) {
  4864. $args[0] = $text->_($args[0]);
  4865. return @call_user_func_array('sprintf', $args);
  4866. }
  4867. return '';
  4868. }
  4869. public function dumpLanguage()
  4870. {
  4871. $out = '';
  4872. foreach($this->strings as $key => $value)
  4873. {
  4874. $out .= "$key=$value\n";
  4875. }
  4876. return $out;
  4877. }
  4878. public function asJavascript()
  4879. {
  4880. $out = '';
  4881. foreach($this->strings as $key => $value)
  4882. {
  4883. $key = addcslashes($key, '\\\'"');
  4884. $value = addcslashes($value, '\\\'"');
  4885. if(!empty($out)) $out .= ",\n";
  4886. $out .= "'$key':\t'$value'";
  4887. }
  4888. return $out;
  4889. }
  4890. public function resetTranslation()
  4891. {
  4892. $this->strings = $this->default_translation;
  4893. }
  4894. public function getBrowserLanguage()
  4895. {
  4896. // Detection code from Full Operating system language detection, by Harald Hope
  4897. // Retrieved from http://techpatterns.com/downloads/php_language_detection.php
  4898. $user_languages = array();
  4899. //check to see if language is set
  4900. if ( isset( $_SERVER["HTTP_ACCEPT_LANGUAGE"] ) )
  4901. {
  4902. $languages = strtolower( $_SERVER["HTTP_ACCEPT_LANGUAGE"] );
  4903. // $languages = ' fr-ch;q=0.3, da, en-us;q=0.8, en;q=0.5, fr;q=0.3';
  4904. // need to remove spaces from strings to avoid error
  4905. $languages = str_replace( ' ', '', $languages );
  4906. $languages = explode( ",", $languages );
  4907. foreach ( $languages as $language_list )
  4908. {
  4909. // pull out the language, place languages into array of full and primary
  4910. // string structure:
  4911. $temp_array = array();
  4912. // slice out the part before ; on first step, the part before - on second, place into array
  4913. $temp_array[0] = substr( $language_list, 0, strcspn( $language_list, ';' ) );//full language
  4914. $temp_array[1] = substr( $language_list, 0, 2 );// cut out primary language
  4915. if( (strlen($temp_array[0]) == 5) && ( (substr($temp_array[0],2,1) == '-') || (substr($temp_array[0],2,1) == '_') ) )
  4916. {
  4917. $langLocation = strtoupper(substr($temp_array[0],3,2));
  4918. $temp_array[0] = $temp_array[1].'-'.$langLocation;
  4919. }
  4920. //place this array into main $user_languages language array
  4921. $user_languages[] = $temp_array;
  4922. }
  4923. }
  4924. else// if no languages found
  4925. {
  4926. $user_languages[0] = array( '','' ); //return blank array.
  4927. }
  4928. $this->language = null;
  4929. $basename=basename(__FILE__, '.php') . '.ini';
  4930. // Try to match main language part of the filename, irrespective of the location, e.g. de_DE will do if de_CH doesn't exist.
  4931. if (class_exists('AKUtilsLister'))
  4932. {
  4933. $fs = new AKUtilsLister();
  4934. $iniFiles = $fs->getFiles(KSROOTDIR, '*.'.$basename );
  4935. if(empty($iniFiles) && ($basename != 'kickstart.ini')) {
  4936. $basename = 'kickstart.ini';
  4937. $iniFiles = $fs->getFiles(KSROOTDIR, '*.'.$basename );
  4938. }
  4939. }
  4940. else
  4941. {
  4942. $iniFiles = null;
  4943. }
  4944. if (is_array($iniFiles)) {
  4945. foreach($user_languages as $languageStruct)
  4946. {
  4947. if(is_null($this->language))
  4948. {
  4949. // Get files matching the main lang part
  4950. $iniFiles = $fs->getFiles(KSROOTDIR, $languageStruct[1].'-??.'.$basename );
  4951. if (count($iniFiles) > 0) {
  4952. $filename = $iniFiles[0];
  4953. $filename = substr($filename, strlen(KSROOTDIR)+1);
  4954. $this->language = substr($filename, 0, 5);
  4955. } else {
  4956. $this->language = null;
  4957. }
  4958. }
  4959. }
  4960. }
  4961. if(is_null($this->language)) {
  4962. // Try to find a full language match
  4963. foreach($user_languages as $languageStruct)
  4964. {
  4965. if (@file_exists($languageStruct[0].'.'.$basename) && is_null($this->language)) {
  4966. $this->language = $languageStruct[0];
  4967. } else {
  4968. }
  4969. }
  4970. } else {
  4971. // Do we have an exact match?
  4972. foreach($user_languages as $languageStruct)
  4973. {
  4974. if(substr($this->language,0,strlen($languageStruct[1])) == $languageStruct[1]) {
  4975. if(file_exists($languageStruct[0].'.'.$basename)) {
  4976. $this->language = $languageStruct[0];
  4977. }
  4978. }
  4979. }
  4980. }
  4981. // Now, scan for full language based on the partial match
  4982. }
  4983. private function loadTranslation( $lang = null )
  4984. {
  4985. if (defined('KSLANGDIR'))
  4986. {
  4987. $dirname = KSLANGDIR;
  4988. }
  4989. else
  4990. {
  4991. $dirname = KSROOTDIR;
  4992. }
  4993. $basename = basename(__FILE__, '.php') . '.ini';
  4994. if( empty($lang) ) $lang = $this->language;
  4995. $translationFilename = $dirname.DIRECTORY_SEPARATOR.$lang.'.'.$basename;
  4996. if(!@file_exists($translationFilename) && ($basename != 'kickstart.ini')) {
  4997. $basename = 'kickstart.ini';
  4998. $translationFilename = $dirname.DIRECTORY_SEPARATOR.$lang.'.'.$basename;
  4999. }
  5000. if(!@file_exists($translationFilename)) return;
  5001. $temp = self::parse_ini_file($translationFilename, false);
  5002. if(!is_array($this->strings)) $this->strings = array();
  5003. if(empty($temp)) {
  5004. $this->strings = array_merge($this->default_translation, $this->strings);
  5005. } else {
  5006. $this->strings = array_merge($this->strings, $temp);
  5007. }
  5008. }
  5009. public function addDefaultLanguageStrings($stringList = array())
  5010. {
  5011. if(!is_array($stringList)) return;
  5012. if(empty($stringList)) return;
  5013. $this->strings = array_merge($stringList, $this->strings);
  5014. }
  5015. /**
  5016. * A PHP based INI file parser.
  5017. *
  5018. * Thanks to asohn ~at~ aircanopy ~dot~ net for posting this handy function on
  5019. * the parse_ini_file page on http://gr.php.net/parse_ini_file
  5020. *
  5021. * @param string $file Filename to process
  5022. * @param bool $process_sections True to also process INI sections
  5023. * @return array An associative array of sections, keys and values
  5024. * @access private
  5025. */
  5026. public static function parse_ini_file($file, $process_sections = false, $raw_data = false)
  5027. {
  5028. $process_sections = ($process_sections !== true) ? false : true;
  5029. if(!$raw_data)
  5030. {
  5031. $ini = @file($file);
  5032. }
  5033. else
  5034. {
  5035. $ini = $file;
  5036. }
  5037. if (count($ini) == 0) {return array();}
  5038. $sections = array();
  5039. $values = array();
  5040. $result = array();
  5041. $globals = array();
  5042. $i = 0;
  5043. if(!empty($ini)) foreach ($ini as $line) {
  5044. $line = trim($line);
  5045. $line = str_replace("\t", " ", $line);
  5046. // Comments
  5047. if (!preg_match('/^[a-zA-Z0-9[]/', $line)) {continue;}
  5048. // Sections
  5049. if ($line{0} == '[') {
  5050. $tmp = explode(']', $line);
  5051. $sections[] = trim(substr($tmp[0], 1));
  5052. $i++;
  5053. continue;
  5054. }
  5055. // Key-value pair
  5056. list($key, $value) = explode('=', $line, 2);
  5057. $key = trim($key);
  5058. $value = trim($value);
  5059. if (strstr($value, ";")) {
  5060. $tmp = explode(';', $value);
  5061. if (count($tmp) == 2) {
  5062. if ((($value{0} != '"') && ($value{0} != "'")) ||
  5063. preg_match('/^".*"\s*;/', $value) || preg_match('/^".*;[^"]*$/', $value) ||
  5064. preg_match("/^'.*'\s*;/", $value) || preg_match("/^'.*;[^']*$/", $value) ){
  5065. $value = $tmp[0];
  5066. }
  5067. } else {
  5068. if ($value{0} == '"') {
  5069. $value = preg_replace('/^"(.*)".*/', '$1', $value);
  5070. } elseif ($value{0} == "'") {
  5071. $value = preg_replace("/^'(.*)'.*/", '$1', $value);
  5072. } else {
  5073. $value = $tmp[0];
  5074. }
  5075. }
  5076. }
  5077. $value = trim($value);
  5078. $value = trim($value, "'\"");
  5079. if ($i == 0) {
  5080. if (substr($line, -1, 2) == '[]') {
  5081. $globals[$key][] = $value;
  5082. } else {
  5083. $globals[$key] = $value;
  5084. }
  5085. } else {
  5086. if (substr($line, -1, 2) == '[]') {
  5087. $values[$i-1][$key][] = $value;
  5088. } else {
  5089. $values[$i-1][$key] = $value;
  5090. }
  5091. }
  5092. }
  5093. for($j = 0; $j < $i; $j++) {
  5094. if ($process_sections === true) {
  5095. $result[$sections[$j]] = $values[$j];
  5096. } else {
  5097. $result[] = $values[$j];
  5098. }
  5099. }
  5100. return $result + $globals;
  5101. }
  5102. }
  5103. /**
  5104. * Akeeba Restore
  5105. * A JSON-powered JPA, JPS and ZIP archive extraction library
  5106. *
  5107. * @copyright 2010-2013 Nicholas K. Dionysopoulos / Akeeba Ltd.
  5108. * @license GNU GPL v2 or - at your option - any later version
  5109. * @package akeebabackup
  5110. * @subpackage kickstart
  5111. */
  5112. /**
  5113. * The Akeeba Kickstart Factory class
  5114. * This class is reponssible for instanciating all Akeeba Kicsktart classes
  5115. */
  5116. class AKFactory {
  5117. /** @var array A list of instanciated objects */
  5118. private $objectlist = array();
  5119. /** @var array Simple hash data storage */
  5120. private $varlist = array();
  5121. /** Private constructor makes sure we can't directly instanciate the class */
  5122. private function __construct() {}
  5123. /**
  5124. * Gets a single, internally used instance of the Factory
  5125. * @param string $serialized_data [optional] Serialized data to spawn the instance from
  5126. * @return AKFactory A reference to the unique Factory object instance
  5127. */
  5128. protected static function &getInstance( $serialized_data = null ) {
  5129. static $myInstance;
  5130. if(!is_object($myInstance) || !is_null($serialized_data))
  5131. if(!is_null($serialized_data))
  5132. {
  5133. $myInstance = unserialize($serialized_data);
  5134. }
  5135. else
  5136. {
  5137. $myInstance = new self();
  5138. }
  5139. return $myInstance;
  5140. }
  5141. /**
  5142. * Internal function which instanciates a class named $class_name.
  5143. * The autoloader
  5144. * @param object $class_name
  5145. * @return
  5146. */
  5147. protected static function &getClassInstance($class_name) {
  5148. $self = self::getInstance();
  5149. if(!isset($self->objectlist[$class_name]))
  5150. {
  5151. $self->objectlist[$class_name] = new $class_name;
  5152. }
  5153. return $self->objectlist[$class_name];
  5154. }
  5155. // ========================================================================
  5156. // Public factory interface
  5157. // ========================================================================
  5158. /**
  5159. * Gets a serialized snapshot of the Factory for safekeeping (hibernate)
  5160. * @return string The serialized snapshot of the Factory
  5161. */
  5162. public static function serialize() {
  5163. $engine = self::getUnarchiver();
  5164. $engine->shutdown();
  5165. $serialized = serialize(self::getInstance());
  5166. if(function_exists('base64_encode') && function_exists('base64_decode'))
  5167. {
  5168. $serialized = base64_encode($serialized);
  5169. }
  5170. return $serialized;
  5171. }
  5172. /**
  5173. * Regenerates the full Factory state from a serialized snapshot (resume)
  5174. * @param string $serialized_data The serialized snapshot to resume from
  5175. */
  5176. public static function unserialize($serialized_data) {
  5177. if(function_exists('base64_encode') && function_exists('base64_decode'))
  5178. {
  5179. $serialized_data = base64_decode($serialized_data);
  5180. }
  5181. self::getInstance($serialized_data);
  5182. }
  5183. /**
  5184. * Reset the internal factory state, freeing all previously created objects
  5185. */
  5186. public static function nuke()
  5187. {
  5188. $self = self::getInstance();
  5189. foreach($self->objectlist as $key => $object)
  5190. {
  5191. $self->objectlist[$key] = null;
  5192. }
  5193. $self->objectlist = array();
  5194. }
  5195. // ========================================================================
  5196. // Public hash data storage interface
  5197. // ========================================================================
  5198. public static function set($key, $value)
  5199. {
  5200. $self = self::getInstance();
  5201. $self->varlist[$key] = $value;
  5202. }
  5203. public static function get($key, $default = null)
  5204. {
  5205. $self = self::getInstance();
  5206. if( array_key_exists($key, $self->varlist) )
  5207. {
  5208. return $self->varlist[$key];
  5209. }
  5210. else
  5211. {
  5212. return $default;
  5213. }
  5214. }
  5215. // ========================================================================
  5216. // Akeeba Kickstart classes
  5217. // ========================================================================
  5218. /**
  5219. * Gets the post processing engine
  5220. * @param string $proc_engine
  5221. */
  5222. public static function &getPostProc($proc_engine = null)
  5223. {
  5224. static $class_name;
  5225. if( empty($class_name) )
  5226. {
  5227. if(empty($proc_engine))
  5228. {
  5229. $proc_engine = self::get('kickstart.procengine','direct');
  5230. }
  5231. $class_name = 'AKPostproc'.ucfirst($proc_engine);
  5232. }
  5233. return self::getClassInstance($class_name);
  5234. }
  5235. /**
  5236. * Gets the unarchiver engine
  5237. */
  5238. public static function &getUnarchiver( $configOverride = null )
  5239. {
  5240. static $class_name;
  5241. if(!empty($configOverride))
  5242. {
  5243. if($configOverride['reset']) {
  5244. $class_name = null;
  5245. }
  5246. }
  5247. if( empty($class_name) )
  5248. {
  5249. $filetype = self::get('kickstart.setup.filetype', null);
  5250. if(empty($filetype))
  5251. {
  5252. $filename = self::get('kickstart.setup.sourcefile', null);
  5253. $basename = basename($filename);
  5254. $baseextension = strtoupper(substr($basename,-3));
  5255. switch($baseextension)
  5256. {
  5257. case 'JPA':
  5258. $filetype = 'JPA';
  5259. break;
  5260. case 'JPS':
  5261. $filetype = 'JPS';
  5262. break;
  5263. case 'ZIP':
  5264. $filetype = 'ZIP';
  5265. break;
  5266. default:
  5267. die('Invalid archive type or extension in file '.$filename);
  5268. break;
  5269. }
  5270. }
  5271. $class_name = 'AKUnarchiver'.ucfirst($filetype);
  5272. }
  5273. $destdir = self::get('kickstart.setup.destdir', null);
  5274. if(empty($destdir))
  5275. {
  5276. $destdir = KSROOTDIR;
  5277. }
  5278. $object = self::getClassInstance($class_name);
  5279. if( $object->getState() == 'init')
  5280. {
  5281. // Initialize the object
  5282. $config = array(
  5283. 'filename' => self::get('kickstart.setup.sourcefile', ''),
  5284. 'restore_permissions' => self::get('kickstart.setup.restoreperms', 0),
  5285. 'post_proc' => self::get('kickstart.procengine', 'direct'),
  5286. 'add_path' => self::get('kickstart.setup.targetpath', $destdir),
  5287. 'rename_files' => array('.htaccess' => 'htaccess.bak', 'php.ini' => 'php.ini.bak', 'web.config' => 'web.config.bak'),
  5288. 'skip_files' => array(basename(__FILE__), 'kickstart.php', 'abiautomation.ini', 'htaccess.bak', 'php.ini.bak', 'cacert.pem'),
  5289. 'ignoredirectories' => array('tmp', 'log', 'logs'),
  5290. );
  5291. if(!defined('KICKSTART'))
  5292. {
  5293. // In restore.php mode we have to exclude some more files
  5294. $config['skip_files'][] = 'administrator/components/com_akeeba/restore.php';
  5295. $config['skip_files'][] = 'administrator/components/com_akeeba/restoration.php';
  5296. }
  5297. if(!empty($configOverride))
  5298. {
  5299. foreach($configOverride as $key => $value)
  5300. {
  5301. $config[$key] = $value;
  5302. }
  5303. }
  5304. $object->setup($config);
  5305. }
  5306. return $object;
  5307. }
  5308. /**
  5309. * Get the a reference to the Akeeba Engine's timer
  5310. * @return AKCoreTimer
  5311. */
  5312. public static function &getTimer()
  5313. {
  5314. return self::getClassInstance('AKCoreTimer');
  5315. }
  5316. }
  5317. /**
  5318. * Akeeba Restore
  5319. * A JSON-powered JPA, JPS and ZIP archive extraction library
  5320. *
  5321. * @copyright 2010-2013 Nicholas K. Dionysopoulos / Akeeba Ltd.
  5322. * @license GNU GPL v2 or - at your option - any later version
  5323. * @package akeebabackup
  5324. * @subpackage kickstart
  5325. */
  5326. /**
  5327. * AES implementation in PHP (c) Chris Veness 2005-2013.
  5328. * Right to use and adapt is granted for under a simple creative commons attribution
  5329. * licence. No warranty of any form is offered.
  5330. *
  5331. * Modified for Akeeba Backup by Nicholas K. Dionysopoulos
  5332. */
  5333. class AKEncryptionAES
  5334. {
  5335. // Sbox is pre-computed multiplicative inverse in GF(2^8) used in SubBytes and KeyExpansion [�5.1.1]
  5336. protected static $Sbox =
  5337. array(0x63,0x7c,0x77,0x7b,0xf2,0x6b,0x6f,0xc5,0x30,0x01,0x67,0x2b,0xfe,0xd7,0xab,0x76,
  5338. 0xca,0x82,0xc9,0x7d,0xfa,0x59,0x47,0xf0,0xad,0xd4,0xa2,0xaf,0x9c,0xa4,0x72,0xc0,
  5339. 0xb7,0xfd,0x93,0x26,0x36,0x3f,0xf7,0xcc,0x34,0xa5,0xe5,0xf1,0x71,0xd8,0x31,0x15,
  5340. 0x04,0xc7,0x23,0xc3,0x18,0x96,0x05,0x9a,0x07,0x12,0x80,0xe2,0xeb,0x27,0xb2,0x75,
  5341. 0x09,0x83,0x2c,0x1a,0x1b,0x6e,0x5a,0xa0,0x52,0x3b,0xd6,0xb3,0x29,0xe3,0x2f,0x84,
  5342. 0x53,0xd1,0x00,0xed,0x20,0xfc,0xb1,0x5b,0x6a,0xcb,0xbe,0x39,0x4a,0x4c,0x58,0xcf,
  5343. 0xd0,0xef,0xaa,0xfb,0x43,0x4d,0x33,0x85,0x45,0xf9,0x02,0x7f,0x50,0x3c,0x9f,0xa8,
  5344. 0x51,0xa3,0x40,0x8f,0x92,0x9d,0x38,0xf5,0xbc,0xb6,0xda,0x21,0x10,0xff,0xf3,0xd2,
  5345. 0xcd,0x0c,0x13,0xec,0x5f,0x97,0x44,0x17,0xc4,0xa7,0x7e,0x3d,0x64,0x5d,0x19,0x73,
  5346. 0x60,0x81,0x4f,0xdc,0x22,0x2a,0x90,0x88,0x46,0xee,0xb8,0x14,0xde,0x5e,0x0b,0xdb,
  5347. 0xe0,0x32,0x3a,0x0a,0x49,0x06,0x24,0x5c,0xc2,0xd3,0xac,0x62,0x91,0x95,0xe4,0x79,
  5348. 0xe7,0xc8,0x37,0x6d,0x8d,0xd5,0x4e,0xa9,0x6c,0x56,0xf4,0xea,0x65,0x7a,0xae,0x08,
  5349. 0xba,0x78,0x25,0x2e,0x1c,0xa6,0xb4,0xc6,0xe8,0xdd,0x74,0x1f,0x4b,0xbd,0x8b,0x8a,
  5350. 0x70,0x3e,0xb5,0x66,0x48,0x03,0xf6,0x0e,0x61,0x35,0x57,0xb9,0x86,0xc1,0x1d,0x9e,
  5351. 0xe1,0xf8,0x98,0x11,0x69,0xd9,0x8e,0x94,0x9b,0x1e,0x87,0xe9,0xce,0x55,0x28,0xdf,
  5352. 0x8c,0xa1,0x89,0x0d,0xbf,0xe6,0x42,0x68,0x41,0x99,0x2d,0x0f,0xb0,0x54,0xbb,0x16);
  5353. // Rcon is Round Constant used for the Key Expansion [1st col is 2^(r-1) in GF(2^8)] [�5.2]
  5354. protected static $Rcon = array(
  5355. array(0x00, 0x00, 0x00, 0x00),
  5356. array(0x01, 0x00, 0x00, 0x00),
  5357. array(0x02, 0x00, 0x00, 0x00),
  5358. array(0x04, 0x00, 0x00, 0x00),
  5359. array(0x08, 0x00, 0x00, 0x00),
  5360. array(0x10, 0x00, 0x00, 0x00),
  5361. array(0x20, 0x00, 0x00, 0x00),
  5362. array(0x40, 0x00, 0x00, 0x00),
  5363. array(0x80, 0x00, 0x00, 0x00),
  5364. array(0x1b, 0x00, 0x00, 0x00),
  5365. array(0x36, 0x00, 0x00, 0x00) );
  5366. protected static $passwords = array();
  5367. /**
  5368. * AES Cipher function: encrypt 'input' with Rijndael algorithm
  5369. *
  5370. * @param input message as byte-array (16 bytes)
  5371. * @param w key schedule as 2D byte-array (Nr+1 x Nb bytes) -
  5372. * generated from the cipher key by KeyExpansion()
  5373. * @return ciphertext as byte-array (16 bytes)
  5374. */
  5375. protected static function Cipher($input, $w) { // main Cipher function [�5.1]
  5376. $Nb = 4; // block size (in words): no of columns in state (fixed at 4 for AES)
  5377. $Nr = count($w)/$Nb - 1; // no of rounds: 10/12/14 for 128/192/256-bit keys
  5378. $state = array(); // initialise 4xNb byte-array 'state' with input [�3.4]
  5379. for ($i=0; $i<4*$Nb; $i++) $state[$i%4][floor($i/4)] = $input[$i];
  5380. $state = self::AddRoundKey($state, $w, 0, $Nb);
  5381. for ($round=1; $round<$Nr; $round++) { // apply Nr rounds
  5382. $state = self::SubBytes($state, $Nb);
  5383. $state = self::ShiftRows($state, $Nb);
  5384. $state = self::MixColumns($state, $Nb);
  5385. $state = self::AddRoundKey($state, $w, $round, $Nb);
  5386. }
  5387. $state = self::SubBytes($state, $Nb);
  5388. $state = self::ShiftRows($state, $Nb);
  5389. $state = self::AddRoundKey($state, $w, $Nr, $Nb);
  5390. $output = array(4*$Nb); // convert state to 1-d array before returning [�3.4]
  5391. for ($i=0; $i<4*$Nb; $i++) $output[$i] = $state[$i%4][floor($i/4)];
  5392. return $output;
  5393. }
  5394. protected static function AddRoundKey($state, $w, $rnd, $Nb) { // xor Round Key into state S [�5.1.4]
  5395. for ($r=0; $r<4; $r++) {
  5396. for ($c=0; $c<$Nb; $c++) $state[$r][$c] ^= $w[$rnd*4+$c][$r];
  5397. }
  5398. return $state;
  5399. }
  5400. protected static function SubBytes($s, $Nb) { // apply SBox to state S [�5.1.1]
  5401. for ($r=0; $r<4; $r++) {
  5402. for ($c=0; $c<$Nb; $c++) $s[$r][$c] = self::$Sbox[$s[$r][$c]];
  5403. }
  5404. return $s;
  5405. }
  5406. protected static function ShiftRows($s, $Nb) { // shift row r of state S left by r bytes [�5.1.2]
  5407. $t = array(4);
  5408. for ($r=1; $r<4; $r++) {
  5409. for ($c=0; $c<4; $c++) $t[$c] = $s[$r][($c+$r)%$Nb]; // shift into temp copy
  5410. for ($c=0; $c<4; $c++) $s[$r][$c] = $t[$c]; // and copy back
  5411. } // note that this will work for Nb=4,5,6, but not 7,8 (always 4 for AES):
  5412. return $s; // see fp.gladman.plus.com/cryptography_technology/rijndael/aes.spec.311.pdf
  5413. }
  5414. protected static function MixColumns($s, $Nb) { // combine bytes of each col of state S [�5.1.3]
  5415. for ($c=0; $c<4; $c++) {
  5416. $a = array(4); // 'a' is a copy of the current column from 's'
  5417. $b = array(4); // 'b' is a�{02} in GF(2^8)
  5418. for ($i=0; $i<4; $i++) {
  5419. $a[$i] = $s[$i][$c];
  5420. $b[$i] = $s[$i][$c]&0x80 ? $s[$i][$c]<<1 ^ 0x011b : $s[$i][$c]<<1;
  5421. }
  5422. // a[n] ^ b[n] is a�{03} in GF(2^8)
  5423. $s[0][$c] = $b[0] ^ $a[1] ^ $b[1] ^ $a[2] ^ $a[3]; // 2*a0 + 3*a1 + a2 + a3
  5424. $s[1][$c] = $a[0] ^ $b[1] ^ $a[2] ^ $b[2] ^ $a[3]; // a0 * 2*a1 + 3*a2 + a3
  5425. $s[2][$c] = $a[0] ^ $a[1] ^ $b[2] ^ $a[3] ^ $b[3]; // a0 + a1 + 2*a2 + 3*a3
  5426. $s[3][$c] = $a[0] ^ $b[0] ^ $a[1] ^ $a[2] ^ $b[3]; // 3*a0 + a1 + a2 + 2*a3
  5427. }
  5428. return $s;
  5429. }
  5430. /**
  5431. * Key expansion for Rijndael Cipher(): performs key expansion on cipher key
  5432. * to generate a key schedule
  5433. *
  5434. * @param key cipher key byte-array (16 bytes)
  5435. * @return key schedule as 2D byte-array (Nr+1 x Nb bytes)
  5436. */
  5437. protected static function KeyExpansion($key) { // generate Key Schedule from Cipher Key [�5.2]
  5438. $Nb = 4; // block size (in words): no of columns in state (fixed at 4 for AES)
  5439. $Nk = count($key)/4; // key length (in words): 4/6/8 for 128/192/256-bit keys
  5440. $Nr = $Nk + 6; // no of rounds: 10/12/14 for 128/192/256-bit keys
  5441. $w = array();
  5442. $temp = array();
  5443. for ($i=0; $i<$Nk; $i++) {
  5444. $r = array($key[4*$i], $key[4*$i+1], $key[4*$i+2], $key[4*$i+3]);
  5445. $w[$i] = $r;
  5446. }
  5447. for ($i=$Nk; $i<($Nb*($Nr+1)); $i++) {
  5448. $w[$i] = array();
  5449. for ($t=0; $t<4; $t++) $temp[$t] = $w[$i-1][$t];
  5450. if ($i % $Nk == 0) {
  5451. $temp = self::SubWord(self::RotWord($temp));
  5452. for ($t=0; $t<4; $t++) $temp[$t] ^= self::$Rcon[$i/$Nk][$t];
  5453. } else if ($Nk > 6 && $i%$Nk == 4) {
  5454. $temp = self::SubWord($temp);
  5455. }
  5456. for ($t=0; $t<4; $t++) $w[$i][$t] = $w[$i-$Nk][$t] ^ $temp[$t];
  5457. }
  5458. return $w;
  5459. }
  5460. protected static function SubWord($w) { // apply SBox to 4-byte word w
  5461. for ($i=0; $i<4; $i++) $w[$i] = self::$Sbox[$w[$i]];
  5462. return $w;
  5463. }
  5464. protected static function RotWord($w) { // rotate 4-byte word w left by one byte
  5465. $tmp = $w[0];
  5466. for ($i=0; $i<3; $i++) $w[$i] = $w[$i+1];
  5467. $w[3] = $tmp;
  5468. return $w;
  5469. }
  5470. /*
  5471. * Unsigned right shift function, since PHP has neither >>> operator nor unsigned ints
  5472. *
  5473. * @param a number to be shifted (32-bit integer)
  5474. * @param b number of bits to shift a to the right (0..31)
  5475. * @return a right-shifted and zero-filled by b bits
  5476. */
  5477. protected static function urs($a, $b) {
  5478. $a &= 0xffffffff; $b &= 0x1f; // (bounds check)
  5479. if ($a&0x80000000 && $b>0) { // if left-most bit set
  5480. $a = ($a>>1) & 0x7fffffff; // right-shift one bit & clear left-most bit
  5481. $a = $a >> ($b-1); // remaining right-shifts
  5482. } else { // otherwise
  5483. $a = ($a>>$b); // use normal right-shift
  5484. }
  5485. return $a;
  5486. }
  5487. /**
  5488. * Encrypt a text using AES encryption in Counter mode of operation
  5489. * - see http://csrc.nist.gov/publications/nistpubs/800-38a/sp800-38a.pdf
  5490. *
  5491. * Unicode multi-byte character safe
  5492. *
  5493. * @param plaintext source text to be encrypted
  5494. * @param password the password to use to generate a key
  5495. * @param nBits number of bits to be used in the key (128, 192, or 256)
  5496. * @return encrypted text
  5497. */
  5498. public static function AESEncryptCtr($plaintext, $password, $nBits) {
  5499. $blockSize = 16; // block size fixed at 16 bytes / 128 bits (Nb=4) for AES
  5500. if (!($nBits==128 || $nBits==192 || $nBits==256)) return ''; // standard allows 128/192/256 bit keys
  5501. // note PHP (5) gives us plaintext and password in UTF8 encoding!
  5502. // use AES itself to encrypt password to get cipher key (using plain password as source for
  5503. // key expansion) - gives us well encrypted key
  5504. $nBytes = $nBits/8; // no bytes in key
  5505. $pwBytes = array();
  5506. for ($i=0; $i<$nBytes; $i++) $pwBytes[$i] = ord(substr($password,$i,1)) & 0xff;
  5507. $key = self::Cipher($pwBytes, self::KeyExpansion($pwBytes));
  5508. $key = array_merge($key, array_slice($key, 0, $nBytes-16)); // expand key to 16/24/32 bytes long
  5509. // initialise counter block (NIST SP800-38A �B.2): millisecond time-stamp for nonce in
  5510. // 1st 8 bytes, block counter in 2nd 8 bytes
  5511. $counterBlock = array();
  5512. $nonce = floor(microtime(true)*1000); // timestamp: milliseconds since 1-Jan-1970
  5513. $nonceSec = floor($nonce/1000);
  5514. $nonceMs = $nonce%1000;
  5515. // encode nonce with seconds in 1st 4 bytes, and (repeated) ms part filling 2nd 4 bytes
  5516. for ($i=0; $i<4; $i++) $counterBlock[$i] = self::urs($nonceSec, $i*8) & 0xff;
  5517. for ($i=0; $i<4; $i++) $counterBlock[$i+4] = $nonceMs & 0xff;
  5518. // and convert it to a string to go on the front of the ciphertext
  5519. $ctrTxt = '';
  5520. for ($i=0; $i<8; $i++) $ctrTxt .= chr($counterBlock[$i]);
  5521. // generate key schedule - an expansion of the key into distinct Key Rounds for each round
  5522. $keySchedule = self::KeyExpansion($key);
  5523. $blockCount = ceil(strlen($plaintext)/$blockSize);
  5524. $ciphertxt = array(); // ciphertext as array of strings
  5525. for ($b=0; $b<$blockCount; $b++) {
  5526. // set counter (block #) in last 8 bytes of counter block (leaving nonce in 1st 8 bytes)
  5527. // done in two stages for 32-bit ops: using two words allows us to go past 2^32 blocks (68GB)
  5528. for ($c=0; $c<4; $c++) $counterBlock[15-$c] = self::urs($b, $c*8) & 0xff;
  5529. for ($c=0; $c<4; $c++) $counterBlock[15-$c-4] = self::urs($b/0x100000000, $c*8);
  5530. $cipherCntr = self::Cipher($counterBlock, $keySchedule); // -- encrypt counter block --
  5531. // block size is reduced on final block
  5532. $blockLength = $b<$blockCount-1 ? $blockSize : (strlen($plaintext)-1)%$blockSize+1;
  5533. $cipherByte = array();
  5534. for ($i=0; $i<$blockLength; $i++) { // -- xor plaintext with ciphered counter byte-by-byte --
  5535. $cipherByte[$i] = $cipherCntr[$i] ^ ord(substr($plaintext, $b*$blockSize+$i, 1));
  5536. $cipherByte[$i] = chr($cipherByte[$i]);
  5537. }
  5538. $ciphertxt[$b] = implode('', $cipherByte); // escape troublesome characters in ciphertext
  5539. }
  5540. // implode is more efficient than repeated string concatenation
  5541. $ciphertext = $ctrTxt . implode('', $ciphertxt);
  5542. $ciphertext = base64_encode($ciphertext);
  5543. return $ciphertext;
  5544. }
  5545. /**
  5546. * Decrypt a text encrypted by AES in counter mode of operation
  5547. *
  5548. * @param ciphertext source text to be decrypted
  5549. * @param password the password to use to generate a key
  5550. * @param nBits number of bits to be used in the key (128, 192, or 256)
  5551. * @return decrypted text
  5552. */
  5553. public static function AESDecryptCtr($ciphertext, $password, $nBits) {
  5554. $blockSize = 16; // block size fixed at 16 bytes / 128 bits (Nb=4) for AES
  5555. if (!($nBits==128 || $nBits==192 || $nBits==256)) return ''; // standard allows 128/192/256 bit keys
  5556. $ciphertext = base64_decode($ciphertext);
  5557. // use AES to encrypt password (mirroring encrypt routine)
  5558. $nBytes = $nBits/8; // no bytes in key
  5559. $pwBytes = array();
  5560. for ($i=0; $i<$nBytes; $i++) $pwBytes[$i] = ord(substr($password,$i,1)) & 0xff;
  5561. $key = self::Cipher($pwBytes, self::KeyExpansion($pwBytes));
  5562. $key = array_merge($key, array_slice($key, 0, $nBytes-16)); // expand key to 16/24/32 bytes long
  5563. // recover nonce from 1st element of ciphertext
  5564. $counterBlock = array();
  5565. $ctrTxt = substr($ciphertext, 0, 8);
  5566. for ($i=0; $i<8; $i++) $counterBlock[$i] = ord(substr($ctrTxt,$i,1));
  5567. // generate key schedule
  5568. $keySchedule = self::KeyExpansion($key);
  5569. // separate ciphertext into blocks (skipping past initial 8 bytes)
  5570. $nBlocks = ceil((strlen($ciphertext)-8) / $blockSize);
  5571. $ct = array();
  5572. for ($b=0; $b<$nBlocks; $b++) $ct[$b] = substr($ciphertext, 8+$b*$blockSize, 16);
  5573. $ciphertext = $ct; // ciphertext is now array of block-length strings
  5574. // plaintext will get generated block-by-block into array of block-length strings
  5575. $plaintxt = array();
  5576. for ($b=0; $b<$nBlocks; $b++) {
  5577. // set counter (block #) in last 8 bytes of counter block (leaving nonce in 1st 8 bytes)
  5578. for ($c=0; $c<4; $c++) $counterBlock[15-$c] = self::urs($b, $c*8) & 0xff;
  5579. for ($c=0; $c<4; $c++) $counterBlock[15-$c-4] = self::urs(($b+1)/0x100000000-1, $c*8) & 0xff;
  5580. $cipherCntr = self::Cipher($counterBlock, $keySchedule); // encrypt counter block
  5581. $plaintxtByte = array();
  5582. for ($i=0; $i<strlen($ciphertext[$b]); $i++) {
  5583. // -- xor plaintext with ciphered counter byte-by-byte --
  5584. $plaintxtByte[$i] = $cipherCntr[$i] ^ ord(substr($ciphertext[$b],$i,1));
  5585. $plaintxtByte[$i] = chr($plaintxtByte[$i]);
  5586. }
  5587. $plaintxt[$b] = implode('', $plaintxtByte);
  5588. }
  5589. // join array of blocks into single plaintext string
  5590. $plaintext = implode('',$plaintxt);
  5591. return $plaintext;
  5592. }
  5593. /**
  5594. * AES decryption in CBC mode. This is the standard mode (the CTR methods
  5595. * actually use Rijndael-128 in CTR mode, which - technically - isn't AES).
  5596. *
  5597. * Supports AES-128, AES-192 and AES-256. It supposes that the last 4 bytes
  5598. * contained a little-endian unsigned long integer representing the unpadded
  5599. * data length.
  5600. *
  5601. * @since 3.0.1
  5602. * @author Nicholas K. Dionysopoulos
  5603. *
  5604. * @param string $ciphertext The data to encrypt
  5605. * @param string $password Encryption password
  5606. * @param int $nBits Encryption key size. Can be 128, 192 or 256
  5607. * @return string The plaintext
  5608. */
  5609. public static function AESDecryptCBC($ciphertext, $password, $nBits = 128)
  5610. {
  5611. if (!($nBits==128 || $nBits==192 || $nBits==256)) return false; // standard allows 128/192/256 bit keys
  5612. if(!function_exists('mcrypt_module_open')) return false;
  5613. // Try to fetch cached key/iv or create them if they do not exist
  5614. $lookupKey = $password.'-'.$nBits;
  5615. if(array_key_exists($lookupKey, self::$passwords))
  5616. {
  5617. $key = self::$passwords[$lookupKey]['key'];
  5618. $iv = self::$passwords[$lookupKey]['iv'];
  5619. }
  5620. else
  5621. {
  5622. // use AES itself to encrypt password to get cipher key (using plain password as source for
  5623. // key expansion) - gives us well encrypted key
  5624. $nBytes = $nBits/8; // no bytes in key
  5625. $pwBytes = array();
  5626. for ($i=0; $i<$nBytes; $i++) $pwBytes[$i] = ord(substr($password,$i,1)) & 0xff;
  5627. $key = self::Cipher($pwBytes, self::KeyExpansion($pwBytes));
  5628. $key = array_merge($key, array_slice($key, 0, $nBytes-16)); // expand key to 16/24/32 bytes long
  5629. $newKey = '';
  5630. foreach($key as $int) { $newKey .= chr($int); }
  5631. $key = $newKey;
  5632. // Create an Initialization Vector (IV) based on the password, using the same technique as for the key
  5633. $nBytes = 16; // AES uses a 128 -bit (16 byte) block size, hence the IV size is always 16 bytes
  5634. $pwBytes = array();
  5635. for ($i=0; $i<$nBytes; $i++) $pwBytes[$i] = ord(substr($password,$i,1)) & 0xff;
  5636. $iv = self::Cipher($pwBytes, self::KeyExpansion($pwBytes));
  5637. $newIV = '';
  5638. foreach($iv as $int) { $newIV .= chr($int); }
  5639. $iv = $newIV;
  5640. self::$passwords[$lookupKey]['key'] = $key;
  5641. self::$passwords[$lookupKey]['iv'] = $iv;
  5642. }
  5643. // Read the data size
  5644. $data_size = unpack('V', substr($ciphertext,-4) );
  5645. // Decrypt
  5646. $td = mcrypt_module_open(MCRYPT_RIJNDAEL_128, '', MCRYPT_MODE_CBC, '');
  5647. mcrypt_generic_init($td, $key, $iv);
  5648. $plaintext = mdecrypt_generic($td, substr($ciphertext,0,-4));
  5649. mcrypt_generic_deinit($td);
  5650. // Trim padding, if necessary
  5651. if(strlen($plaintext) > $data_size)
  5652. {
  5653. $plaintext = substr($plaintext, 0, $data_size);
  5654. }
  5655. return $plaintext;
  5656. }
  5657. }
  5658. /**
  5659. * Akeeba Restore
  5660. * A JSON-powered JPA, JPS and ZIP archive extraction library
  5661. *
  5662. * @copyright 2010-2013 Nicholas K. Dionysopoulos / Akeeba Ltd.
  5663. * @license GNU GPL v2 or - at your option - any later version
  5664. * @package akeebabackup
  5665. * @subpackage kickstart
  5666. */
  5667. /**
  5668. * The Master Setup will read the configuration parameters from restoration.php, abiautomation.ini, or
  5669. * the JSON-encoded "configuration" input variable and return the status.
  5670. * @return bool True if the master configuration was applied to the Factory object
  5671. */
  5672. function masterSetup()
  5673. {
  5674. // ------------------------------------------------------------
  5675. // 1. Import basic setup parameters
  5676. // ------------------------------------------------------------
  5677. $ini_data = null;
  5678. // In restore.php mode, require restoration.php or fail
  5679. if(!defined('KICKSTART'))
  5680. {
  5681. // This is the standalone mode, used by Akeeba Backup Professional. It looks for a restoration.php
  5682. // file to perform its magic. If the file is not there, we will abort.
  5683. $setupFile = 'restoration.php';
  5684. if( !file_exists($setupFile) )
  5685. {
  5686. // Uh oh... Somebody tried to pooh on our back yard. Lock the gates! Don't let the traitor inside!
  5687. AKFactory::set('kickstart.enabled', false);
  5688. return false;
  5689. }
  5690. // Load restoration.php. It creates a global variable named $restoration_setup
  5691. require_once $setupFile;
  5692. $ini_data = $restoration_setup;
  5693. if(empty($ini_data))
  5694. {
  5695. // No parameters fetched. Darn, how am I supposed to work like that?!
  5696. AKFactory::set('kickstart.enabled', false);
  5697. return false;
  5698. }
  5699. AKFactory::set('kickstart.enabled', true);
  5700. }
  5701. else
  5702. {
  5703. // Maybe we have $restoration_setup defined in the head of kickstart.php
  5704. global $restoration_setup;
  5705. if(!empty($restoration_setup) && !is_array($restoration_setup)) {
  5706. $ini_data = AKText::parse_ini_file($restoration_setup, false, true);
  5707. } elseif(is_array($restoration_setup)) {
  5708. $ini_data = $restoration_setup;
  5709. }
  5710. }
  5711. // Import any data from $restoration_setup
  5712. if(!empty($ini_data))
  5713. {
  5714. foreach($ini_data as $key => $value)
  5715. {
  5716. AKFactory::set($key, $value);
  5717. }
  5718. AKFactory::set('kickstart.enabled', true);
  5719. }
  5720. // Reinitialize $ini_data
  5721. $ini_data = null;
  5722. // ------------------------------------------------------------
  5723. // 2. Explode JSON parameters into $_REQUEST scope
  5724. // ------------------------------------------------------------
  5725. // Detect a JSON string in the request variable and store it.
  5726. $json = getQueryParam('json', null);
  5727. // Remove everything from the request array
  5728. if(!empty($_REQUEST))
  5729. {
  5730. foreach($_REQUEST as $key => $value)
  5731. {
  5732. unset($_REQUEST[$key]);
  5733. }
  5734. }
  5735. // Decrypt a possibly encrypted JSON string
  5736. if(!empty($json))
  5737. {
  5738. $password = AKFactory::get('kickstart.security.password', null);
  5739. if(!empty($password))
  5740. {
  5741. $json = AKEncryptionAES::AESDecryptCtr($json, $password, 128);
  5742. }
  5743. // Get the raw data
  5744. $raw = json_decode( $json, true );
  5745. // Pass all JSON data to the request array
  5746. if(!empty($raw))
  5747. {
  5748. foreach($raw as $key => $value)
  5749. {
  5750. $_REQUEST[$key] = $value;
  5751. }
  5752. }
  5753. }
  5754. // ------------------------------------------------------------
  5755. // 3. Try the "factory" variable
  5756. // ------------------------------------------------------------
  5757. // A "factory" variable will override all other settings.
  5758. $serialized = getQueryParam('factory', null);
  5759. if( !is_null($serialized) )
  5760. {
  5761. // Get the serialized factory
  5762. AKFactory::unserialize($serialized);
  5763. AKFactory::set('kickstart.enabled', true);
  5764. return true;
  5765. }
  5766. // ------------------------------------------------------------
  5767. // 4. Try abiautomation.ini and the configuration variable for Kickstart
  5768. // ------------------------------------------------------------
  5769. if(defined('KICKSTART'))
  5770. {
  5771. // We are in Kickstart mode. abiautomation.ini has precedence.
  5772. $setupFile = 'abiautomation.ini';
  5773. if( file_exists($setupFile) )
  5774. {
  5775. // abiautomation.ini was found
  5776. $ini_data = AKText::parse_ini_file('restoration.ini', false);
  5777. }
  5778. else
  5779. {
  5780. // abiautomation.ini was not found. Let's try input parameters.
  5781. $configuration = getQueryParam('configuration');
  5782. if( !is_null($configuration) )
  5783. {
  5784. // Let's decode the configuration from JSON to array
  5785. $ini_data = json_decode($configuration, true);
  5786. }
  5787. else
  5788. {
  5789. // Neither exists. Enable Kickstart's interface anyway.
  5790. $ini_data = array('kickstart.enabled'=>true);
  5791. }
  5792. }
  5793. // Import any INI data we might have from other sources
  5794. if(!empty($ini_data))
  5795. {
  5796. foreach($ini_data as $key => $value)
  5797. {
  5798. AKFactory::set($key, $value);
  5799. }
  5800. AKFactory::set('kickstart.enabled', true);
  5801. return true;
  5802. }
  5803. }
  5804. }
  5805. /**
  5806. * Akeeba Restore
  5807. * A JSON-powered JPA, JPS and ZIP archive extraction library
  5808. *
  5809. * @copyright 2010-2013 Nicholas K. Dionysopoulos / Akeeba Ltd.
  5810. * @license GNU GPL v2 or - at your option - any later version
  5811. * @package akeebabackup
  5812. * @subpackage kickstart
  5813. */
  5814. // Mini-controller for restore.php
  5815. if(!defined('KICKSTART'))
  5816. {
  5817. // The observer class, used to report number of files and bytes processed
  5818. class RestorationObserver extends AKAbstractPartObserver
  5819. {
  5820. public $compressedTotal = 0;
  5821. public $uncompressedTotal = 0;
  5822. public $filesProcessed = 0;
  5823. public function update($object, $message)
  5824. {
  5825. if(!is_object($message)) return;
  5826. if( !array_key_exists('type', get_object_vars($message)) ) return;
  5827. if( $message->type == 'startfile' )
  5828. {
  5829. $this->filesProcessed++;
  5830. $this->compressedTotal += $message->content->compressed;
  5831. $this->uncompressedTotal += $message->content->uncompressed;
  5832. }
  5833. }
  5834. public function __toString()
  5835. {
  5836. return __CLASS__;
  5837. }
  5838. }
  5839. // Import configuration
  5840. masterSetup();
  5841. $retArray = array(
  5842. 'status' => true,
  5843. 'message' => null
  5844. );
  5845. $enabled = AKFactory::get('kickstart.enabled', false);
  5846. if($enabled)
  5847. {
  5848. $task = getQueryParam('task');
  5849. switch($task)
  5850. {
  5851. case 'ping':
  5852. // ping task - realy does nothing!
  5853. $timer = AKFactory::getTimer();
  5854. $timer->enforce_min_exec_time();
  5855. break;
  5856. case 'startRestore':
  5857. AKFactory::nuke(); // Reset the factory
  5858. // Let the control flow to the next step (the rest of the code is common!!)
  5859. case 'stepRestore':
  5860. $engine = AKFactory::getUnarchiver(); // Get the engine
  5861. $observer = new RestorationObserver(); // Create a new observer
  5862. $engine->attach($observer); // Attach the observer
  5863. $engine->tick();
  5864. $ret = $engine->getStatusArray();
  5865. if( $ret['Error'] != '' )
  5866. {
  5867. $retArray['status'] = false;
  5868. $retArray['done'] = true;
  5869. $retArray['message'] = $ret['Error'];
  5870. }
  5871. elseif( !$ret['HasRun'] )
  5872. {
  5873. $retArray['files'] = $observer->filesProcessed;
  5874. $retArray['bytesIn'] = $observer->compressedTotal;
  5875. $retArray['bytesOut'] = $observer->uncompressedTotal;
  5876. $retArray['status'] = true;
  5877. $retArray['done'] = true;
  5878. }
  5879. else
  5880. {
  5881. $retArray['files'] = $observer->filesProcessed;
  5882. $retArray['bytesIn'] = $observer->compressedTotal;
  5883. $retArray['bytesOut'] = $observer->uncompressedTotal;
  5884. $retArray['status'] = true;
  5885. $retArray['done'] = false;
  5886. $retArray['factory'] = AKFactory::serialize();
  5887. }
  5888. break;
  5889. case 'finalizeRestore':
  5890. $root = AKFactory::get('kickstart.setup.destdir');
  5891. // Remove the installation directory
  5892. recursive_remove_directory( $root.'/installation' );
  5893. $postproc = AKFactory::getPostProc();
  5894. // Rename htaccess.bak to .htaccess
  5895. if(file_exists($root.'/htaccess.bak'))
  5896. {
  5897. if( file_exists($root.'/.htaccess') )
  5898. {
  5899. $postproc->unlink($root.'/.htaccess');
  5900. }
  5901. $postproc->rename( $root.'/htaccess.bak', $root.'/.htaccess' );
  5902. }
  5903. // Rename htaccess.bak to .htaccess
  5904. if(file_exists($root.'/web.config.bak'))
  5905. {
  5906. if( file_exists($root.'/web.config') )
  5907. {
  5908. $postproc->unlink($root.'/web.config');
  5909. }
  5910. $postproc->rename( $root.'/web.config.bak', $root.'/web.config' );
  5911. }
  5912. // Remove restoration.php
  5913. $basepath = KSROOTDIR;
  5914. $basepath = rtrim( str_replace('\\','/',$basepath), '/' );
  5915. if(!empty($basepath)) $basepath .= '/';
  5916. $postproc->unlink( $basepath.'restoration.php' );
  5917. break;
  5918. default:
  5919. // Invalid task!
  5920. $enabled = false;
  5921. break;
  5922. }
  5923. }
  5924. // Maybe we weren't authorized or the task was invalid?
  5925. if(!$enabled)
  5926. {
  5927. // Maybe the user failed to enter any information
  5928. $retArray['status'] = false;
  5929. $retArray['message'] = AKText::_('ERR_INVALID_LOGIN');
  5930. }
  5931. // JSON encode the message
  5932. $json = json_encode($retArray);
  5933. // Do I have to encrypt?
  5934. $password = AKFactory::get('kickstart.security.password', null);
  5935. if(!empty($password))
  5936. {
  5937. $json = AKEncryptionAES::AESEncryptCtr($json, $password, 128);
  5938. }
  5939. // Return the message
  5940. echo "###$json###";
  5941. }
  5942. // ------------ lixlpixel recursive PHP functions -------------
  5943. // recursive_remove_directory( directory to delete, empty )
  5944. // expects path to directory and optional TRUE / FALSE to empty
  5945. // of course PHP has to have the rights to delete the directory
  5946. // you specify and all files and folders inside the directory
  5947. // ------------------------------------------------------------
  5948. function recursive_remove_directory($directory)
  5949. {
  5950. // if the path has a slash at the end we remove it here
  5951. if(substr($directory,-1) == '/')
  5952. {
  5953. $directory = substr($directory,0,-1);
  5954. }
  5955. // if the path is not valid or is not a directory ...
  5956. if(!file_exists($directory) || !is_dir($directory))
  5957. {
  5958. // ... we return false and exit the function
  5959. return FALSE;
  5960. // ... if the path is not readable
  5961. }elseif(!is_readable($directory))
  5962. {
  5963. // ... we return false and exit the function
  5964. return FALSE;
  5965. // ... else if the path is readable
  5966. }else{
  5967. // we open the directory
  5968. $handle = opendir($directory);
  5969. $postproc = AKFactory::getPostProc();
  5970. // and scan through the items inside
  5971. while (FALSE !== ($item = readdir($handle)))
  5972. {
  5973. // if the filepointer is not the current directory
  5974. // or the parent directory
  5975. if($item != '.' && $item != '..')
  5976. {
  5977. // we build the new path to delete
  5978. $path = $directory.'/'.$item;
  5979. // if the new path is a directory
  5980. if(is_dir($path))
  5981. {
  5982. // we call this function with the new path
  5983. recursive_remove_directory($path);
  5984. // if the new path is a file
  5985. }else{
  5986. // we remove the file
  5987. $postproc->unlink($path);
  5988. }
  5989. }
  5990. }
  5991. // close the directory
  5992. closedir($handle);
  5993. // try to delete the now empty directory
  5994. if(!$postproc->rmdir($directory))
  5995. {
  5996. // return false if not possible
  5997. return FALSE;
  5998. }
  5999. // return success
  6000. return TRUE;
  6001. }
  6002. }
  6003. /**
  6004. * Akeeba Kickstart
  6005. * A JSON-powered archive extraction tool
  6006. *
  6007. * @copyright 2010-2013 Nicholas K. Dionysopoulos / AkeebaBackup.com
  6008. * @license GNU GPL v2 or - at your option - any later version
  6009. * @package akeebabackup
  6010. * @subpackage kickstart
  6011. */
  6012. class AKAutomation
  6013. {
  6014. /**
  6015. * @var bool Is there automation information available?
  6016. */
  6017. private $_hasAutomation = false;
  6018. /**
  6019. * @var array The abiautomation.ini contents, in array format
  6020. */
  6021. private $_automation = array();
  6022. /**
  6023. * Singleton implementation
  6024. * @return ABIAutomation
  6025. */
  6026. public static function &getInstance()
  6027. {
  6028. static $instance;
  6029. if(empty($instance))
  6030. {
  6031. $instance = new AKAutomation();
  6032. }
  6033. return $instance;
  6034. }
  6035. /**
  6036. * Loads and parses the automation INI file
  6037. * @return AKAutomation
  6038. */
  6039. public function __construct()
  6040. {
  6041. // Initialize
  6042. $this->_hasAutomation = false;
  6043. $this->_automation = array();
  6044. $filenames = array('abiautomation.ini', 'kickstart.ini', 'jpi4automation');
  6045. foreach($filenames as $filename)
  6046. {
  6047. // Try to load the abiautomation.ini file
  6048. if(@file_exists($filename))
  6049. {
  6050. $this->_automation = $this->_parse_ini_file($filename, true);
  6051. if(!isset($this->_automation['kickstart']))
  6052. {
  6053. $this->_automation = array();
  6054. }
  6055. else
  6056. {
  6057. $this->_hasAutomation = true;
  6058. break;
  6059. }
  6060. }
  6061. }
  6062. }
  6063. /**
  6064. * Do we have automation?
  6065. * @return bool True if abiautomation.ini exists and has a abi section
  6066. */
  6067. public function hasAutomation()
  6068. {
  6069. return $this->_hasAutomation;
  6070. }
  6071. /**
  6072. * Returns an automation section. If the section doesn't exist, it returns an empty array.
  6073. * @param string $section [optional] The name of the section to load, defaults to 'kickstart'
  6074. * @return array
  6075. */
  6076. public function getSection($section = 'kickstart')
  6077. {
  6078. if(!$this->_hasAutomation)
  6079. {
  6080. return array();
  6081. }
  6082. else
  6083. {
  6084. if(isset($this->_automation[$section]))
  6085. {
  6086. return $this->_automation[$section];
  6087. } else {
  6088. return array();
  6089. }
  6090. }
  6091. }
  6092. private function _parse_ini_file($file, $process_sections = false, $rawdata = false)
  6093. {
  6094. $process_sections = ($process_sections !== true) ? false : true;
  6095. if(!$rawdata)
  6096. {
  6097. $ini = file($file);
  6098. }
  6099. else
  6100. {
  6101. $file = str_replace("\r","",$file);
  6102. $ini = explode("\n", $file);
  6103. }
  6104. if (count($ini) == 0) {return array();}
  6105. $sections = array();
  6106. $values = array();
  6107. $result = array();
  6108. $globals = array();
  6109. $i = 0;
  6110. foreach ($ini as $line) {
  6111. $line = trim($line);
  6112. $line = str_replace("\t", " ", $line);
  6113. // Comments
  6114. if (!preg_match('/^[a-zA-Z0-9[]/', $line)) {continue;}
  6115. // Sections
  6116. if ($line{0} == '[') {
  6117. $tmp = explode(']', $line);
  6118. $sections[] = trim(substr($tmp[0], 1));
  6119. $i++;
  6120. continue;
  6121. }
  6122. // Key-value pair
  6123. list($key, $value) = explode('=', $line, 2);
  6124. $key = trim($key);
  6125. $value = trim($value);
  6126. if (strstr($value, ";")) {
  6127. $tmp = explode(';', $value);
  6128. if (count($tmp) == 2) {
  6129. if ((($value{0} != '"') && ($value{0} != "'")) ||
  6130. preg_match('/^".*"\s*;/', $value) || preg_match('/^".*;[^"]*$/', $value) ||
  6131. preg_match("/^'.*'\s*;/", $value) || preg_match("/^'.*;[^']*$/", $value) ){
  6132. $value = $tmp[0];
  6133. }
  6134. } else {
  6135. if ($value{0} == '"') {
  6136. $value = preg_replace('/^"(.*)".*/', '$1', $value);
  6137. } elseif ($value{0} == "'") {
  6138. $value = preg_replace("/^'(.*)'.*/", '$1', $value);
  6139. } else {
  6140. $value = $tmp[0];
  6141. }
  6142. }
  6143. }
  6144. $value = trim($value);
  6145. $value = trim($value, "'\"");
  6146. if ($i == 0) {
  6147. if (substr($line, -1, 2) == '[]') {
  6148. $globals[$key][] = $value;
  6149. } else {
  6150. $globals[$key] = $value;
  6151. }
  6152. } else {
  6153. if (substr($line, -1, 2) == '[]') {
  6154. $values[$i-1][$key][] = $value;
  6155. } else {
  6156. $values[$i-1][$key] = $value;
  6157. }
  6158. }
  6159. }
  6160. for($j = 0; $j < $i; $j++) {
  6161. if ($process_sections === true) {
  6162. $result[$sections[$j]] = $values[$j];
  6163. } else {
  6164. $result[] = $values[$j];
  6165. }
  6166. }
  6167. return $result + $globals;
  6168. }
  6169. }
  6170. function autoVar($key, $default = '')
  6171. {
  6172. $automation = AKAutomation::getInstance();
  6173. $vars = $automation->getSection('kickstart');
  6174. if(array_key_exists($key, $vars))
  6175. {
  6176. return "'".addcslashes($vars[$key], "'\"\\")."'";
  6177. }
  6178. else
  6179. {
  6180. return "'".addcslashes($default, "'\"\\")."'";
  6181. }
  6182. }
  6183. /**
  6184. * Akeeba Kickstart
  6185. * A JSON-powered archive extraction tool
  6186. *
  6187. * @copyright 2010-2013 Nicholas K. Dionysopoulos / AkeebaBackup.com
  6188. * @license GNU GPL v2 or - at your option - any later version
  6189. * @package akeebabackup
  6190. * @subpackage kickstart
  6191. */
  6192. class AKKickstartUtils
  6193. {
  6194. /**
  6195. * Gets the directory the file is in
  6196. * @return string
  6197. */
  6198. public static function getPath()
  6199. {
  6200. $path = KSROOTDIR;
  6201. $path = rtrim(str_replace('\\','/',$path),'/');
  6202. if(!empty($path)) $path .= '/';
  6203. return $path;
  6204. }
  6205. /**
  6206. * Scans the current directory for archive files (JPA, JPS and ZIP formet)
  6207. * @return array
  6208. */
  6209. public static function findArchives()
  6210. {
  6211. $ret = array();
  6212. $path = self::getPath();
  6213. if(empty($path)) $path = '.';
  6214. $dh = @opendir($path);
  6215. if($dh === false) return $ret;
  6216. while( false !== $file = @readdir($dh) )
  6217. {
  6218. $dotpos = strrpos($file,'.');
  6219. if($dotpos === false) continue;
  6220. if($dotpos == strlen($file)) continue;
  6221. $extension = strtolower( substr($file,$dotpos+1) );
  6222. if(in_array($extension,array('jpa','zip','jps')))
  6223. {
  6224. $ret[] = $file;
  6225. }
  6226. }
  6227. closedir($dh);
  6228. if(!empty($ret)) return $ret;
  6229. // On some hosts using opendir doesn't work. Let's try Dir instead
  6230. $d = dir($path);
  6231. while(false != ($file = $d->read()))
  6232. {
  6233. $dotpos = strrpos($file,'.');
  6234. if($dotpos === false) continue;
  6235. if($dotpos == strlen($file)) continue;
  6236. $extension = strtolower( substr($file,$dotpos+1) );
  6237. if(in_array($extension,array('jpa','zip','jps')))
  6238. {
  6239. $ret[] = $file;
  6240. }
  6241. }
  6242. return $ret;
  6243. }
  6244. /**
  6245. * Scans the current directory for archive files and returns them as <OPTION> tags
  6246. * @return string
  6247. */
  6248. public static function getArchivesAsOptions()
  6249. {
  6250. $ret = '';
  6251. $archives = self::findArchives();
  6252. if(empty($archives)) return $ret;
  6253. foreach($archives as $file)
  6254. {
  6255. //$file = htmlentities($file);
  6256. $ret .= '<option value="'.$file.'">'.$file.'</option>'."\n";
  6257. }
  6258. return $ret;
  6259. }
  6260. }
  6261. /**
  6262. * Akeeba Kickstart
  6263. * A JSON-powered archive extraction tool
  6264. *
  6265. * @copyright 2010-2013 Nicholas K. Dionysopoulos / AkeebaBackup.com
  6266. * @license GNU GPL v2 or - at your option - any later version
  6267. * @package akeebabackup
  6268. * @subpackage kickstart
  6269. */
  6270. class ExtractionObserver extends AKAbstractPartObserver
  6271. {
  6272. public $compressedTotal = 0;
  6273. public $uncompressedTotal = 0;
  6274. public $filesProcessed = 0;
  6275. public $totalSize = null;
  6276. public $fileList = null;
  6277. public $lastFile = '';
  6278. public function update($object, $message)
  6279. {
  6280. if(!is_object($message)) return;
  6281. if( !array_key_exists('type', get_object_vars($message)) ) return;
  6282. if( $message->type == 'startfile' )
  6283. {
  6284. $this->lastFile = $message->content->file;
  6285. $this->filesProcessed++;
  6286. $this->compressedTotal += $message->content->compressed;
  6287. $this->uncompressedTotal += $message->content->uncompressed;
  6288. }
  6289. elseif( $message->type == 'totalsize' )
  6290. {
  6291. $this->totalSize = $message->content->totalsize;
  6292. $this->fileList = $message->content->filelist;
  6293. }
  6294. }
  6295. public function __toString()
  6296. {
  6297. return __CLASS__;
  6298. }
  6299. }
  6300. /**
  6301. * Akeeba Kickstart
  6302. * A JSON-powered archive extraction tool
  6303. *
  6304. * @copyright 2010-2013 Nicholas K. Dionysopoulos / AkeebaBackup.com
  6305. * @license GNU GPL v2 or - at your option - any later version
  6306. * @package akeebabackup
  6307. * @subpackage kickstart
  6308. */
  6309. function callExtraFeature($method = null, array $params = array())
  6310. {
  6311. static $extraFeatureObjects = null;
  6312. if(!is_array($extraFeatureObjects)) {
  6313. $extraFeatureObjects = array();
  6314. $allClasses = get_declared_classes();
  6315. foreach($allClasses as $class) {
  6316. if(substr($class, 0, 9) == 'AKFeature') {
  6317. $extraFeatureObjects[] = new $class;
  6318. }
  6319. }
  6320. }
  6321. if(is_null($method)) {
  6322. return;
  6323. }
  6324. if(empty($extraFeatureObjects)) {
  6325. return;
  6326. }
  6327. $result = null;
  6328. foreach($extraFeatureObjects as $o) {
  6329. if(!method_exists($o, $method)) {
  6330. continue;
  6331. }
  6332. $result = call_user_func(array($o, $method), $params);
  6333. }
  6334. return $result;
  6335. }
  6336. /**
  6337. * Akeeba Kickstart
  6338. * A JSON-powered archive extraction tool
  6339. *
  6340. * @copyright 2010-2013 Nicholas K. Dionysopoulos / AkeebaBackup.com
  6341. * @license GNU GPL v2 or - at your option - any later version
  6342. * @package akeebabackup
  6343. * @subpackage kickstart
  6344. */
  6345. function echoCSS() {
  6346. echo <<<ENDCSS
  6347. html {
  6348. background: #e9e9e9;
  6349. font-size: 62.5%;
  6350. }
  6351. body {
  6352. font-size: 14px;
  6353. font-size: 1.4rem;
  6354. font-family: "Helvetica Neue",Helvetica,Arial,sans-serif;
  6355. font-family: sans-serif;
  6356. text-rendering: optimizelegibility;
  6357. background: transparent;
  6358. color:#555;
  6359. width:90%;
  6360. max-width:980px;
  6361. margin: 0 auto;
  6362. }
  6363. #page-container {
  6364. position:relative;
  6365. margin:5% 0;
  6366. background: #f9f9f9;
  6367. border: 1px solid #777;
  6368. border: 1px solid rgba(0,0,0,.2);
  6369. -webkit-box-shadow: 0px 0px 10px rgba(0,0,0,.1);
  6370. -moz-box-shadow: 0px 0px 10px rgba(0,0,0,.1);
  6371. box-shadow: 0px 0px 10px rgba(0,0,0,.1);
  6372. }
  6373. #header {
  6374. color: #555;
  6375. text-shadow: 0 1px #fff;
  6376. background: #f2f5f6;
  6377. background: -moz-linear-gradient(top, #f2f5f6 0%, #e3eaed 37%, #c8d7dc 100%);
  6378. background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#f2f5f6), color-stop(37%,#e3eaed), color-stop(100%,#c8d7dc));
  6379. background: -webkit-linear-gradient(top, #f2f5f6 0%,#e3eaed 37%,#c8d7dc 100%);
  6380. background: -o-linear-gradient(top, #f2f5f6 0%,#e3eaed 37%,#c8d7dc 100%);
  6381. background: -ms-linear-gradient(top, #f2f5f6 0%,#e3eaed 37%,#c8d7dc 100%);
  6382. background: linear-gradient(top, #f2f5f6 0%,#e3eaed 37%,#c8d7dc 100%);
  6383. -moz-background-clip: padding;
  6384. -webkit-background-clip: padding-box;
  6385. background-clip: padding-box;
  6386. margin-bottom: 0.7em;
  6387. border-bottom: 1px solid #ddd;
  6388. border-bottom: 1px solid rgba(0,0,0,.2);
  6389. padding:.25em;
  6390. font-size: 32px;
  6391. font-size: 3.2rem;
  6392. line-height: 1.2;
  6393. text-align: center;
  6394. }
  6395. #footer {
  6396. font-size: 8pt;
  6397. color: #233b53;
  6398. text-align: center;
  6399. border-top: 1px solid #ddd;
  6400. border-top: 1px solid rgba(0,0,0,.05);
  6401. padding: 1em 2em;
  6402. background: #deefff;
  6403. background: -moz-linear-gradient(top, #deefff 0%, #98bede 100%);
  6404. background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#deefff), color-stop(100%,#98bede));
  6405. background: -webkit-linear-gradient(top, #deefff 0%,#98bede 100%);
  6406. background: -o-linear-gradient(top, #deefff 0%,#98bede 100%);
  6407. background: -ms-linear-gradient(top, #deefff 0%,#98bede 100%);
  6408. background: linear-gradient(top, #deefff 0%,#98bede 100%);
  6409. clear: both;
  6410. }
  6411. #error, .error {
  6412. display: none;
  6413. border: solid #cc0000;
  6414. border-width: 2px 0;
  6415. background: rgb(255,255,136);
  6416. background: -moz-linear-gradient(top, rgba(255,255,136,1) 0%, rgba(255,255,136,1) 100%);
  6417. background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,rgba(255,255,136,1)), color-stop(100%,rgba(255,255,136,1)));
  6418. background: -webkit-linear-gradient(top, rgba(255,255,136,1) 0%,rgba(255,255,136,1) 100%);
  6419. background: -o-linear-gradient(top, rgba(255,255,136,1) 0%,rgba(255,255,136,1) 100%);
  6420. background: -ms-linear-gradient(top, rgba(255,255,136,1) 0%,rgba(255,255,136,1) 100%);
  6421. background: linear-gradient(top, rgba(255,255,136,1) 0%,rgba(255,255,136,1) 100%);
  6422. color: #990000;
  6423. padding:2em 2em 1em;
  6424. margin-bottom: 1.15em;
  6425. text-align:center;
  6426. text-transform: uppercase;
  6427. }
  6428. #errorMessage, .errorMessage {
  6429. text-transform: none;
  6430. }
  6431. #error h3, .error h3 {
  6432. margin: 0;
  6433. padding: 0;
  6434. font-size: 12pt;
  6435. }
  6436. .clr {
  6437. clear: both;
  6438. }
  6439. .circle {
  6440. display: block;
  6441. float: left;
  6442. -moz-border-radius: 2em;
  6443. -webkit-border-radius: 2em;
  6444. border: 2px solid #e5e5e5;
  6445. font-weight: bold;
  6446. font-size: 18px;
  6447. font-size: 1.8rem;
  6448. line-height:1.5em;
  6449. color: #fff;
  6450. height: 1.5em;
  6451. width: 1.5em;
  6452. margin: 0.75em;
  6453. text-align: center;
  6454. background: rgb(35,83,138);
  6455. background: -moz-linear-gradient(top, rgba(35,83,138,1) 0%, rgba(167,207,223,1) 100%);
  6456. background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,rgba(35,83,138,1)), color-stop(100%,rgba(167,207,223,1)));
  6457. background: -webkit-linear-gradient(top, rgba(35,83,138,1) 0%,rgba(167,207,223,1) 100%);
  6458. background: -o-linear-gradient(top, rgba(35,83,138,1) 0%,rgba(167,207,223,1) 100%);
  6459. background: -ms-linear-gradient(top, rgba(35,83,138,1) 0%,rgba(167,207,223,1) 100%);
  6460. background: linear-gradient(top, rgba(35,83,138,1) 0%,rgba(167,207,223,1) 100%);
  6461. -webkit-box-shadow: 0px 0px 2px rgba(0, 0, 0, 0.8) inset, 0px -1px 2px rgba(255,255,255,.9) inset, 0px 0px 1px rgba(0,0,0,.7); 0 -1px 1px rgba(0,0,0,.4);
  6462. -moz-box-shadow: 0px 0px 2px rgba(0, 0, 0, 0.8) inset, 0px -1px 2px rgba(255,255,255,.9) inset, 0px 0px 1px rgba(0,0,0,.7); 0 -1px 1px rgba(0,0,0,.4);
  6463. box-shadow: 0px 0px 2px rgba(0, 0, 0, 0.8) inset, 0px -1px 2px rgba(255,255,255,.9) inset, 0px 0px 1px rgba(0,0,0,.7); 0 -1px 1px rgba(0,0,0,.4);
  6464. }
  6465. .area-container {
  6466. margin: 1em 4em;
  6467. }
  6468. #page2a .area-container {
  6469. margin: 1em 0;
  6470. }
  6471. #runInstaller,
  6472. #runCleanup,
  6473. #gotoSite,
  6474. #gotoAdministrator,
  6475. #gotoPostRestorationRroubleshooting {
  6476. margin: 0 2em 1.3em;
  6477. }
  6478. h2 {
  6479. font-size: 24px;
  6480. font-size: 2.4rem;
  6481. font-weight: normal;
  6482. line-height: 1.3;
  6483. border: solid #ddd;
  6484. text-shadow: 0px 1px #fff;
  6485. border-top: 1px solid rgba(0,0,0,.05);
  6486. border-bottom: 1px solid rgba(0,0,0,.2);
  6487. border-left:none;
  6488. border-right:none;
  6489. padding: 0.5em 0;
  6490. background: #f2f5f6;
  6491. background: -moz-linear-gradient(top, #f2f5f6 0%, #e3eaed 37%, #c8d7dc 100%);
  6492. background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#f2f5f6), color-stop(37%,#e3eaed), color-stop(100%,#c8d7dc));
  6493. background: -webkit-linear-gradient(top, #f2f5f6 0%,#e3eaed 37%,#c8d7dc 100%);
  6494. background: -o-linear-gradient(top, #f2f5f6 0%,#e3eaed 37%,#c8d7dc 100%);
  6495. background: -ms-linear-gradient(top, #f2f5f6 0%,#e3eaed 37%,#c8d7dc 100%);
  6496. background: linear-gradient(top, #f2f5f6 0%,#e3eaed 37%,#c8d7dc 100%);
  6497. }
  6498. #preextraction h2 {
  6499. margin-top:0;
  6500. border-top:0;
  6501. text-align:center;
  6502. }
  6503. input,
  6504. select,
  6505. textarea {
  6506. font-size : 100%;
  6507. margin : 0;
  6508. vertical-align : baseline;
  6509. *vertical-align: middle;
  6510. }
  6511. button,
  6512. input {
  6513. line-height : normal;
  6514. font-weight:normal;
  6515. *overflow: visible;
  6516. }
  6517. input,
  6518. select,
  6519. textarea {
  6520. background:#fff;
  6521. color:#777;
  6522. font-size: 16px;
  6523. font-size: 1.6rem;
  6524. border:1px solid #d5d5d5;
  6525. -webkit-border-radius: .25em;
  6526. -moz-border-radius: .25em;
  6527. border-radius: .25em;
  6528. -webkit-box-sizing: border-box;
  6529. -moz-box-sizing: border-box;
  6530. box-sizing: border-box;
  6531. width:50%;
  6532. padding:0 0 0 .5em;
  6533. }
  6534. input[type="checkbox"] {
  6535. width:auto;
  6536. }
  6537. .field {
  6538. height:1.5em;
  6539. }
  6540. label {
  6541. display:inline-block;
  6542. width:30%;
  6543. font-size: 85%;
  6544. font-weight: normal;
  6545. text-transform: uppercase;
  6546. cursor : pointer;
  6547. color: #777;
  6548. margin:.5em 0;
  6549. }
  6550. input:focus, input:hover {
  6551. background-color: #fffbb3;
  6552. }
  6553. .button {
  6554. display: inline-block;
  6555. margin: 1em .25em;
  6556. text-transform: uppercase;
  6557. padding: 1em 2em;
  6558. background: #2cb12c;
  6559. color:#fff;
  6560. text-shadow: 0 1px 3px rgba(0, 0, 0, 0.2), 0 1px rgba(0, 0, 0, 0.4);
  6561. -webkit-box-shadow: 0 1px 3px rgba(255, 255, 255, 0.5) inset, -1px 2px 2px rgba(0, 0, 0, 0.2);
  6562. -moz-box-shadow: 0 1px 3px rgba(255, 255, 255, 0.5) inset, -1px 2px 2px rgba(0, 0, 0, 0.2);
  6563. box-shadow: 0 1px 3px rgba(255, 255, 255, 0.5) inset, -1px 2px 2px rgba(0, 0, 0, 0.2);
  6564. background: -moz-linear-gradient(top, #2cb12c 0%, #259625 100%);
  6565. background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#2cb12c), color-stop(100%,#259625));
  6566. background: -webkit-linear-gradient(top, #2cb12c 0%,#259625 100%);
  6567. background: -o-linear-gradient(top, #2cb12c 0%,#259625 100%);
  6568. background: -ms-linear-gradient(top, #2cb12c 0%,#259625 100%);
  6569. background: linear-gradient(top, #2cb12c 0%,#259625 100%);
  6570. border: solid #ddd;
  6571. border: 1px solid rgba(0,0,0,.1);
  6572. cursor: pointer;
  6573. -webkit-border-radius: .25em;
  6574. -moz-border-radius: .25em;
  6575. border-radius: .25em;
  6576. -webkit-transition: 0.3s linear all;
  6577. -moz-transition: 0.3s linear all;
  6578. -ms-transition: 0.3s linear all;
  6579. -o-transition: 0.3s linear all;
  6580. transition: 0.3s linear all;
  6581. }
  6582. #checkFTPTempDir.button,
  6583. #resetFTPTempDir.button,
  6584. #testFTP.button,
  6585. #notWorking.button {
  6586. padding: .5em 1em;
  6587. text-transform: none;
  6588. }
  6589. .button:hover {
  6590. background: #259625;
  6591. background: -moz-linear-gradient(top, #259625 0%, #2cb12c 100%);
  6592. background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#259625), color-stop(100%,#2cb12c));
  6593. background: -webkit-linear-gradient(top, #259625 0%,#2cb12c 100%);
  6594. background: -o-linear-gradient(top, #259625 0%,#2cb12c 100%);
  6595. background: -ms-linear-gradient(top, #259625 0%,#2cb12c 100%);
  6596. background: linear-gradient(top, #259625 0%,#2cb12c 100%);
  6597. }
  6598. .button:active {
  6599. background: #3c3;
  6600. color: #444;
  6601. text-shadow: 0 1px #fff;
  6602. border: solid #ccc;
  6603. border: 1px solid rgba(0,0,0,.3);
  6604. -webkit-box-shadow: 0 1px 3px rgba(0,0,0, 0.5) inset;
  6605. -moz-box-shadow: 0 1px 3px rgba(0,0,0, 0.5) inset;
  6606. box-shadow: 0 1px 3px rgba(0,0,0, 0.5) inset;
  6607. }
  6608. #notWorking.button, .bluebutton {
  6609. text-decoration: none;
  6610. background: #7abcff;
  6611. background: -moz-linear-gradient(top, #7abcff 0%, #60abf8 44%, #4096ee 100%);
  6612. background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#7abcff), color-stop(44%,#60abf8), color-stop(100%,#4096ee));
  6613. background: -webkit-linear-gradient(top, #7abcff 0%,#60abf8 44%,#4096ee 100%);
  6614. background: -o-linear-gradient(top, #7abcff 0%,#60abf8 44%,#4096ee 100%);
  6615. background: -ms-linear-gradient(top, #7abcff 0%,#60abf8 44%,#4096ee 100%);
  6616. background: linear-gradient(top, #7abcff 0%,#60abf8 44%,#4096ee 100%);
  6617. }
  6618. #notWorking.button:hover, .bluebutton:hover {
  6619. background: #4096ee;
  6620. background: -moz-linear-gradient(top, #4096ee 0%, #60abf8 56%, #7abcff 100%);
  6621. background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#4096ee), color-stop(56%,#60abf8), color-stop(100%,#7abcff));
  6622. background: -webkit-linear-gradient(top, #4096ee 0%,#60abf8 56%,#7abcff 100%);
  6623. background: -o-linear-gradient(top, #4096ee 0%,#60abf8 56%,#7abcff 100%);
  6624. background: -ms-linear-gradient(top, #4096ee 0%,#60abf8 56%,#7abcff 100%);
  6625. background: linear-gradient(top, #4096ee 0%,#60abf8 56%,#7abcff 100%);
  6626. }
  6627. #notWorking.button:active, .bluebutton:active {
  6628. background: #7abcff;
  6629. }
  6630. .loprofile {
  6631. padding: 0.5em 1em;
  6632. font-size: 80%;
  6633. }
  6634. .black_overlay{
  6635. display: none;
  6636. position: absolute;
  6637. top: 0%;
  6638. left: 0%;
  6639. width: 100%;
  6640. height: 100%;
  6641. background-color: black;
  6642. z-index:1001;
  6643. -moz-opacity: 0.8;
  6644. opacity:.80;
  6645. filter: alpha(opacity=80);
  6646. }
  6647. .white_content {
  6648. display: none;
  6649. position: absolute;
  6650. padding: 0 0 1em;
  6651. background: #fff;
  6652. border: 1px solid #ddd;
  6653. border: 1px solid rgba(0,0,0,.3);
  6654. z-index:1002;
  6655. overflow: hidden;
  6656. }
  6657. .white_content a{
  6658. margin-left:4em;
  6659. }
  6660. ol {
  6661. margin:0 2em;
  6662. padding:0 2em 1em;
  6663. }
  6664. li {
  6665. margin : 0 0 .5em;
  6666. }
  6667. #genericerror {
  6668. background-color: #f0f000 !important;
  6669. border: 4px solid #fcc !important;
  6670. }
  6671. #genericerrorInner {
  6672. font-size: 110%;
  6673. color: #33000;
  6674. }
  6675. #warn-not-close, .warn-not-close {
  6676. padding: 0.2em 0.5em;
  6677. text-align: center;
  6678. background: #fcfc00;
  6679. font-size: smaller;
  6680. font-weight: bold;
  6681. }
  6682. #progressbar, .progressbar {
  6683. display: block;
  6684. width: 80%;
  6685. height: 32px;
  6686. border: 1px solid #ccc;
  6687. margin: 1em 10% 0.2em;
  6688. -moz-border-radius: .25em;
  6689. -webkit-border-radius: .25em;
  6690. border-radius: .25em;
  6691. }
  6692. #progressbar-inner, .progressbar-inner {
  6693. display: block;
  6694. width: 100%;
  6695. height: 100%;
  6696. background: #4096ee;
  6697. background: -moz-linear-gradient(left, #4096ee 0%, #60abf8 56%, #7abcff 100%);
  6698. background: -webkit-gradient(linear, left top, right top, color-stop(0%,#4096ee), color-stop(56%,#60abf8), color-stop(100%,#7abcff));
  6699. background: -webkit-linear-gradient(left, #4096ee 0%,#60abf8 56%,#7abcff 100%);
  6700. background: -o-linear-gradient(left, #4096ee 0%,#60abf8 56%,#7abcff 100%);
  6701. background: -ms-linear-gradient(left, #4096ee 0%,#60abf8 56%,#7abcff 100%);
  6702. background: linear-gradient(left, #4096ee 0%,#60abf8 56%,#7abcff 100%);
  6703. }
  6704. #currentFile {
  6705. font-family: Consolas, "Courier New", Courier, monospace;
  6706. font-size: 9pt;
  6707. height: 10pt;
  6708. overflow: hidden;
  6709. text-overflow: ellipsis;
  6710. background: #ccc;
  6711. margin: 0 10% 1em;
  6712. padding:.125em;
  6713. }
  6714. #extractionComplete {
  6715. }
  6716. #warningsContainer {
  6717. border-bottom: 2px solid brown;
  6718. border-left: 2px solid brown;
  6719. border-right: 2px solid brown;
  6720. padding: 5px 0;
  6721. background: #ffffcc;
  6722. -webkit-border-bottom-right-radius: 5px;
  6723. -webkit-border-bottom-left-radius: 5px;
  6724. -moz-border-radius-bottomleft: 5px;
  6725. -moz-border-radius-bottomright: 5px;
  6726. }
  6727. #warningsHeader h2 {
  6728. color: black;
  6729. text-shadow: 2px 2px 5px #999999;
  6730. border-top: 2px solid brown;
  6731. border-left: 2px solid brown;
  6732. border-right: 2px solid brown;
  6733. border-bottom: thin solid brown;
  6734. -webkit-border-top-right-radius: 5px;
  6735. -webkit-border-top-left-radius: 5px;
  6736. -moz-border-radius-topleft: 5px;
  6737. -moz-border-radius-topright: 5px;
  6738. background: yellow;
  6739. font-size: large;
  6740. padding: 2px 5px;
  6741. margin: 0px;
  6742. }
  6743. #warnings {
  6744. height: 200px;
  6745. overflow-y: scroll;
  6746. }
  6747. #warnings div {
  6748. background: #eeeeee;
  6749. font-size: small;
  6750. padding: 2px 4px;
  6751. border-bottom: thin solid #333333;
  6752. }
  6753. #automode {
  6754. display: inline-block;
  6755. padding: 6pt 12pt;
  6756. background-color: #cc0000;
  6757. border: thick solid yellow;
  6758. color: white;
  6759. font-weight: bold;
  6760. font-size: 125%;
  6761. position: absolute;
  6762. float: right;
  6763. top: 1em;
  6764. right: 1em;
  6765. }
  6766. .helpme,
  6767. #warn-not-close {
  6768. background: rgb(255,255,136);
  6769. background: -moz-linear-gradient(top, rgba(255,255,136,1) 0%, rgba(255,255,136,1) 100%);
  6770. background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,rgba(255,255,136,1)), color-stop(100%,rgba(255,255,136,1)));
  6771. background: -webkit-linear-gradient(top, rgba(255,255,136,1) 0%,rgba(255,255,136,1) 100%);
  6772. background: -o-linear-gradient(top, rgba(255,255,136,1) 0%,rgba(255,255,136,1) 100%);
  6773. background: -ms-linear-gradient(top, rgba(255,255,136,1) 0%,rgba(255,255,136,1) 100%);
  6774. background: linear-gradient(top, rgba(255,255,136,1) 0%,rgba(255,255,136,1) 100%);
  6775. padding: 0.75em 0.5em;
  6776. border: solid #febf01;
  6777. border-width: 1px 0;
  6778. text-align: center;
  6779. }
  6780. #update-notification {
  6781. margin: 1em;
  6782. padding: 0.5em;
  6783. background-color: #FF9;
  6784. color: #F33;
  6785. text-align: center;
  6786. border-radius: 20px;
  6787. border: medium solid red;
  6788. box-shadow: 5px 5px 5px black;
  6789. }
  6790. .update-notify {
  6791. font-size: 20pt;
  6792. font-weight: bold;
  6793. }
  6794. .update-links {
  6795. color: #333;
  6796. font-size: 14pt;
  6797. }
  6798. #update-dlnow {
  6799. text-decoration: none;
  6800. color: #333;
  6801. border: thin solid #333;
  6802. padding: 0.5em;
  6803. border-radius: 5px;
  6804. background-color: #f0f0f0;
  6805. text-shadow: 1px 1px 1px #999;
  6806. }
  6807. #update-dlnow:hover {
  6808. background-color: #fff;
  6809. }
  6810. #update-whatsnew {
  6811. font-size: 11pt;
  6812. color: blue;
  6813. text-decoration: underline;
  6814. }
  6815. .update-whyupdate {
  6816. color: #333;
  6817. font-size: 9pt;
  6818. }
  6819. ENDCSS;
  6820. callExtraFeature('onExtraHeadCSS');
  6821. }
  6822. function echoTranslationStrings()
  6823. {
  6824. callExtraFeature('onLoadTranslations');
  6825. $translation = AKText::getInstance();
  6826. echo $translation->asJavascript();
  6827. }
  6828. function echoPage()
  6829. {
  6830. $edition = KICKSTARTPRO ? 'Professional' : 'Core';
  6831. $automation = AKAutomation::getInstance();
  6832. ?>
  6833. <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
  6834. <html>
  6835. <head>
  6836. <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
  6837. <title>Akeeba Kickstart <?php echo $edition?> <?php echo VERSION?></title>
  6838. <style type="text/css" media="all" rel="stylesheet">
  6839. <?php echoCSS();?>
  6840. </style>
  6841. <?php if(@file_exists('jquery.min.js')):?>
  6842. <script type="text/javascript" src="jquery.min.js"></script>
  6843. <?php else: ?>
  6844. <script type="text/javascript" src="//ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
  6845. <?php endif; ?>
  6846. <?php if(@file_exists('json2.min.js')):?>
  6847. <script type="text/javascript" src="json2.min.js"></script>
  6848. <?php else: ?>
  6849. <script type="text/javascript" src="//yandex.st/json2/2011-10-19/json2.min.js"></script>
  6850. <?php endif; ?>
  6851. <script type="text/javascript" language="javascript">
  6852. var akeeba_automation = <?php echo $automation->hasAutomation() ? 'true' : 'false' ?>;
  6853. var akeeba_debug = <?php echo defined('KSDEBUG') ? 'true' : 'false' ?>;
  6854. /**
  6855. * Returns the version of Internet Explorer or a -1
  6856. * (indicating the use of another browser).
  6857. *
  6858. * @return integer MSIE version or -1
  6859. */
  6860. function getInternetExplorerVersion()
  6861. {
  6862. var rv = -1; // Return value assumes failure.
  6863. if (navigator.appName == 'Microsoft Internet Explorer')
  6864. {
  6865. var ua = navigator.userAgent;
  6866. var re = new RegExp("MSIE ([0-9]{1,}[\.0-9]{0,})");
  6867. if (re.exec(ua) != null)
  6868. {
  6869. rv = parseFloat( RegExp.$1 );
  6870. }
  6871. }
  6872. return rv;
  6873. }
  6874. $(document).ready(function(){
  6875. // Hide 2nd Page
  6876. $('#page2').css('display','none');
  6877. // Translate the GUI
  6878. translateGUI();
  6879. // Hook interaction handlers
  6880. $(document).keyup( closeLightbox );
  6881. $('#kickstart\\.procengine').change( onChangeProcengine );
  6882. $('#checkFTPTempDir').click( oncheckFTPTempDirClick );
  6883. $('#resetFTPTempDir').click( onresetFTPTempDir );
  6884. $('#testFTP').click( onTestFTPClick );
  6885. $('#gobutton').click( onStartExtraction );
  6886. $('#runInstaller').click( onRunInstallerClick );
  6887. $('#runCleanup').click( onRunCleanupClick );
  6888. $('#gotoSite').click(function(event){window.open('index.php','finalstepsite'); window.close();});
  6889. $('#gotoAdministrator').click(function(event){window.open('administrator/index.php','finalstepadmin'); window.close();});
  6890. $('#gotoStart').click( onGotoStartClick );
  6891. // Reset the progress bar
  6892. setProgressBar(0);
  6893. // Do we have automation?
  6894. if(akeeba_automation) {
  6895. $('#automode').css('display','block');
  6896. $('#gobutton').click();
  6897. } else {
  6898. // Show warning
  6899. var msieVersion = getInternetExplorerVersion();
  6900. if((msieVersion != -1) && (msieVersion <= 8.99))
  6901. {
  6902. $('#ie7Warning').css('display','block');
  6903. }
  6904. if(!akeeba_debug) {
  6905. $('#preextraction').css('display','block');
  6906. $('#fade').css('display','block');
  6907. }
  6908. }
  6909. });
  6910. var translation = {
  6911. <?php echoTranslationStrings(); ?>
  6912. }
  6913. var akeeba_ajax_url = '<?php echo defined('KSSELFNAME') ? KSSELFNAME : basename(__FILE__); ?>';
  6914. var akeeba_error_callback = onGenericError;
  6915. var akeeba_restoration_stat_inbytes = 0;
  6916. var akeeba_restoration_stat_outbytes = 0;
  6917. var akeeba_restoration_stat_files = 0;
  6918. var akeeba_restoration_stat_total = 0;
  6919. var akeeba_factory = null;
  6920. function translateGUI()
  6921. {
  6922. $('*').each(function(i,e){
  6923. transKey = $(e).text();
  6924. if(array_key_exists(transKey, translation))
  6925. {
  6926. $(e).text( translation[transKey] );
  6927. }
  6928. });
  6929. }
  6930. function trans(key)
  6931. {
  6932. if(array_key_exists(key, translation)) {
  6933. return translation[key];
  6934. } else {
  6935. return key;
  6936. }
  6937. }
  6938. function array_key_exists ( key, search ) {
  6939. if (!search || (search.constructor !== Array && search.constructor !== Object)){
  6940. return false;
  6941. }
  6942. return key in search;
  6943. }
  6944. function empty (mixed_var) {
  6945. var key;
  6946. if (mixed_var === "" ||
  6947. mixed_var === 0 ||
  6948. mixed_var === "0" ||
  6949. mixed_var === null ||
  6950. mixed_var === false ||
  6951. typeof mixed_var === 'undefined'
  6952. ){
  6953. return true;
  6954. }
  6955. if (typeof mixed_var == 'object') {
  6956. for (key in mixed_var) {
  6957. return false;
  6958. }
  6959. return true;
  6960. }
  6961. return false;
  6962. }
  6963. function is_array (mixed_var) {
  6964. var key = '';
  6965. var getFuncName = function (fn) {
  6966. var name = (/\W*function\s+([\w\$]+)\s*\(/).exec(fn);
  6967. if (!name) {
  6968. return '(Anonymous)';
  6969. }
  6970. return name[1];
  6971. };
  6972. if (!mixed_var) {
  6973. return false;
  6974. }
  6975. // BEGIN REDUNDANT
  6976. this.php_js = this.php_js || {};
  6977. this.php_js.ini = this.php_js.ini || {};
  6978. // END REDUNDANT
  6979. if (typeof mixed_var === 'object') {
  6980. if (this.php_js.ini['phpjs.objectsAsArrays'] && // Strict checking for being a JavaScript array (only check this way if call ini_set('phpjs.objectsAsArrays', 0) to disallow objects as arrays)
  6981. (
  6982. (this.php_js.ini['phpjs.objectsAsArrays'].local_value.toLowerCase &&
  6983. this.php_js.ini['phpjs.objectsAsArrays'].local_value.toLowerCase() === 'off') ||
  6984. parseInt(this.php_js.ini['phpjs.objectsAsArrays'].local_value, 10) === 0)
  6985. ) {
  6986. return mixed_var.hasOwnProperty('length') && // Not non-enumerable because of being on parent class
  6987. !mixed_var.propertyIsEnumerable('length') && // Since is own property, if not enumerable, it must be a built-in function
  6988. getFuncName(mixed_var.constructor) !== 'String'; // exclude String()
  6989. }
  6990. if (mixed_var.hasOwnProperty) {
  6991. for (key in mixed_var) {
  6992. // Checks whether the object has the specified property
  6993. // if not, we figure it's not an object in the sense of a php-associative-array.
  6994. if (false === mixed_var.hasOwnProperty(key)) {
  6995. return false;
  6996. }
  6997. }
  6998. }
  6999. // Read discussion at: http://kevin.vanzonneveld.net/techblog/article/javascript_equivalent_for_phps_is_array/
  7000. return true;
  7001. }
  7002. return false;
  7003. }
  7004. /**
  7005. * Performs an AJAX request and returns the parsed JSON output.
  7006. * The global akeeba_ajax_url is used as the AJAX proxy URL.
  7007. * If there is no errorCallback, the global akeeba_error_callback is used.
  7008. * @param data An object with the query data, e.g. a serialized form
  7009. * @param successCallback A function accepting a single object parameter, called on success
  7010. * @param errorCallback A function accepting a single string parameter, called on failure
  7011. */
  7012. function doAjax(data, successCallback, errorCallback)
  7013. {
  7014. var structure =
  7015. {
  7016. type: "POST",
  7017. url: akeeba_ajax_url,
  7018. cache: false,
  7019. data: data,
  7020. timeout: 600000,
  7021. success: function(msg) {
  7022. // Initialize
  7023. var junk = null;
  7024. var message = "";
  7025. // Get rid of junk before the data
  7026. var valid_pos = msg.indexOf('###');
  7027. if( valid_pos == -1 ) {
  7028. // Valid data not found in the response
  7029. msg = 'Invalid AJAX data received:<br/>' + msg;
  7030. if(errorCallback == null)
  7031. {
  7032. if(akeeba_error_callback != null)
  7033. {
  7034. akeeba_error_callback(msg);
  7035. }
  7036. }
  7037. else
  7038. {
  7039. errorCallback(msg);
  7040. }
  7041. return;
  7042. } else if( valid_pos != 0 ) {
  7043. // Data is prefixed with junk
  7044. junk = msg.substr(0, valid_pos);
  7045. message = msg.substr(valid_pos);
  7046. }
  7047. else
  7048. {
  7049. message = msg;
  7050. }
  7051. message = message.substr(3); // Remove triple hash in the beginning
  7052. // Get of rid of junk after the data
  7053. var valid_pos = message.lastIndexOf('###');
  7054. message = message.substr(0, valid_pos); // Remove triple hash in the end
  7055. try {
  7056. var data = eval('('+message+')');
  7057. } catch(err) {
  7058. var msg = err.message + "\n<br/>\n<pre>\n" + message + "\n</pre>";
  7059. if(errorCallback == null)
  7060. {
  7061. if(akeeba_error_callback != null)
  7062. {
  7063. akeeba_error_callback(msg);
  7064. }
  7065. }
  7066. else
  7067. {
  7068. errorCallback(msg);
  7069. }
  7070. return;
  7071. }
  7072. // Call the callback function
  7073. successCallback(data);
  7074. },
  7075. error: function(Request, textStatus, errorThrown) {
  7076. var message = '<strong>AJAX Loading Error</strong><br/>HTTP Status: '+Request.status+' ('+Request.statusText+')<br/>';
  7077. message = message + 'Internal status: '+textStatus+'<br/>';
  7078. message = message + 'XHR ReadyState: ' + Response.readyState + '<br/>';
  7079. message = message + 'Raw server response:<br/>'+Request.responseText;
  7080. if(errorCallback == null)
  7081. {
  7082. if(akeeba_error_callback != null)
  7083. {
  7084. akeeba_error_callback(message);
  7085. }
  7086. }
  7087. else
  7088. {
  7089. errorCallback(message);
  7090. }
  7091. }
  7092. };
  7093. $.ajax( structure );
  7094. }
  7095. function onChangeProcengine(event)
  7096. {
  7097. if( $('#kickstart\\.procengine').val() == 'direct' )
  7098. {
  7099. $('#ftp-options').hide('fast');
  7100. } else {
  7101. $('#ftp-options').show('fast');
  7102. }
  7103. }
  7104. function closeLightbox(event)
  7105. {
  7106. var closeMe = false;
  7107. if( (event == null) || (event == undefined) ) {
  7108. closeMe = true;
  7109. } else if(event.keyCode == '27') {
  7110. closeMe = true;
  7111. }
  7112. if(closeMe)
  7113. {
  7114. document.getElementById('preextraction').style.display='none';
  7115. document.getElementById('genericerror').style.display='none';
  7116. document.getElementById('fade').style.display='none';
  7117. $(document).unbind('keyup', closeLightbox);
  7118. }
  7119. }
  7120. function onGenericError(msg)
  7121. {
  7122. $('#genericerrorInner').html(msg);
  7123. $('#genericerror').css('display','block');
  7124. $('#fade').css('display','block');
  7125. $(document).keyup(closeLightbox);
  7126. }
  7127. function setProgressBar(percent)
  7128. {
  7129. var newValue = 0;
  7130. if(percent <= 1) {
  7131. newValue = 100 * percent;
  7132. } else {
  7133. newValue = percent;
  7134. }
  7135. $('#progressbar-inner').css('width',percent+'%');
  7136. }
  7137. function oncheckFTPTempDirClick(event)
  7138. {
  7139. var data = {
  7140. 'task' : 'checkTempdir',
  7141. 'json': JSON.stringify({
  7142. 'kickstart.ftp.tempdir': $('#kickstart\\.ftp\\.tempdir').val()
  7143. })
  7144. };
  7145. doAjax(data, function(ret){
  7146. var key = ret.status ? 'FTP_TEMPDIR_WRITABLE' : 'FTP_TEMPDIR_UNWRITABLE';
  7147. alert( trans(key) );
  7148. });
  7149. }
  7150. function onTestFTPClick(event)
  7151. {
  7152. var data = {
  7153. 'task' : 'checkFTP',
  7154. 'json': JSON.stringify({
  7155. 'kickstart.ftp.host': $('#kickstart\\.ftp\\.host').val(),
  7156. 'kickstart.ftp.port': $('#kickstart\\.ftp\\.port').val(),
  7157. 'kickstart.ftp.ssl': $('#kickstart\\.ftp\\.ssl').is(':checked'),
  7158. 'kickstart.ftp.passive': $('#kickstart\\.ftp\\.passive').is(':checked'),
  7159. 'kickstart.ftp.user': $('#kickstart\\.ftp\\.user').val(),
  7160. 'kickstart.ftp.pass': $('#kickstart\\.ftp\\.pass').val(),
  7161. 'kickstart.ftp.dir': $('#kickstart\\.ftp\\.dir').val(),
  7162. 'kickstart.ftp.tempdir': $('#kickstart\\.ftp\\.tempdir').val()
  7163. })
  7164. };
  7165. doAjax(data, function(ret){
  7166. var key = ret.status ? 'FTP_CONNECTION_OK' : 'FTP_CONNECTION_FAILURE';
  7167. alert( trans(key) + "\n\n" + (ret.status ? '' : ret.message) );
  7168. });
  7169. }
  7170. function onStartExtraction()
  7171. {
  7172. $('#page1').hide('fast');
  7173. $('#page2').show('fast');
  7174. $('#currentFile').text( '' );
  7175. akeeba_error_callback = errorHandler;
  7176. var data = {
  7177. 'task' : 'startExtracting',
  7178. 'json': JSON.stringify({
  7179. <?php if(!$automation->hasAutomation()): ?>
  7180. 'kickstart.setup.sourcefile': $('#kickstart\\.setup\\.sourcefile').val(),
  7181. 'kickstart.jps.password': $('#kickstart\\.jps\\.password').val(),
  7182. 'kickstart.tuning.min_exec_time': $('#kickstart\\.tuning\\.min_exec_time').val(),
  7183. 'kickstart.tuning.max_exec_time': $('#kickstart\\.tuning\\.max_exec_time').val(),
  7184. 'kickstart.stealth.enable': $('#kickstart\\.stealth\\.enable').is(':checked'),
  7185. 'kickstart.stealth.url': $('#kickstart\\.stealth\\.url').val(),
  7186. 'kickstart.tuning.run_time_bias': 75,
  7187. 'kickstart.setup.restoreperms': 0,
  7188. 'kickstart.setup.dryrun': 0,
  7189. 'kickstart.setup.ignoreerrors': $('#kickstart\\.setup\\.ignoreerrors').is(':checked'),
  7190. 'kickstart.enabled': 1,
  7191. 'kickstart.security.password': '',
  7192. 'kickstart.procengine': $('#kickstart\\.procengine').val(),
  7193. 'kickstart.ftp.host': $('#kickstart\\.ftp\\.host').val(),
  7194. 'kickstart.ftp.port': $('#kickstart\\.ftp\\.port').val(),
  7195. 'kickstart.ftp.ssl': $('#kickstart\\.ftp\\.ssl').is(':checked'),
  7196. 'kickstart.ftp.passive': $('#kickstart\\.ftp\\.passive').is(':checked'),
  7197. 'kickstart.ftp.user': $('#kickstart\\.ftp\\.user').val(),
  7198. 'kickstart.ftp.pass': $('#kickstart\\.ftp\\.pass').val(),
  7199. 'kickstart.ftp.dir': $('#kickstart\\.ftp\\.dir').val(),
  7200. 'kickstart.ftp.tempdir': $('#kickstart\\.ftp\\.tempdir').val()
  7201. <?php else: ?>
  7202. 'kickstart.setup.sourcefile': <?php echo autoVar('kickstart.setup.sourcefile') ?>,
  7203. 'kickstart.jps.password': <?php echo autoVar('kickstart.jps.password') ?>,
  7204. 'kickstart.tuning.min_exec_time': <?php echo autoVar('kickstart.tuning.min_exec_time', 1) ?>,
  7205. 'kickstart.tuning.max_exec_time': <?php echo autoVar('kickstart.tuning.max_exec_time', 5) ?>,
  7206. 'kickstart.stealth.enable': false,
  7207. 'kickstart.tuning.run_time_bias': 75,
  7208. 'kickstart.setup.restoreperms': 0,
  7209. 'kickstart.setup.dryrun': 0,
  7210. 'kickstart.setup.ignoreerrors': <?php echo autoVar('kickstart.setup.ignoreerrors', 0) ?>,
  7211. 'kickstart.enabled': 1,
  7212. 'kickstart.security.password': '',
  7213. 'kickstart.procengine': <?php echo autoVar('kickstart.procengine', 'direct') ?>,
  7214. 'kickstart.ftp.host': <?php echo autoVar('kickstart.ftp.host','localhost') ?>,
  7215. 'kickstart.ftp.port': <?php echo autoVar('kickstart.ftp.port',22) ?>,
  7216. 'kickstart.ftp.ssl': <?php echo autoVar('kickstart.ftp.ssl',0) ?>,
  7217. 'kickstart.ftp.passive': <?php echo autoVar('kickstart.ftp.passive',1) ?>,
  7218. 'kickstart.ftp.user': <?php echo autoVar('kickstart.ftp.user') ?>,
  7219. 'kickstart.ftp.pass': <?php echo autoVar('kickstart.ftp.pass') ?>,
  7220. 'kickstart.ftp.dir': <?php echo autoVar('kickstart.ftp.dir','/') ?>,
  7221. 'kickstart.ftp.tempdir': <?php echo autoVar('kickstart.ftp.tempdir', AKKickstartUtils::getPath().'kicktemp') ?>
  7222. <?php endif; ?>
  7223. })
  7224. };
  7225. doAjax(data, function(ret){
  7226. processRestorationStep(ret);
  7227. });
  7228. }
  7229. function processRestorationStep(data)
  7230. {
  7231. // Look for errors
  7232. if(!data.status)
  7233. {
  7234. errorHandler(data.message);
  7235. return;
  7236. }
  7237. // Propagate warnings to the GUI
  7238. if( !empty(data.Warnings) )
  7239. {
  7240. $.each(data.Warnings, function(i, item){
  7241. $('#warnings').append(
  7242. $(document.createElement('div'))
  7243. .html(item)
  7244. );
  7245. $('#warningsBox').show('fast');
  7246. });
  7247. }
  7248. // Parse total size, if exists
  7249. if(array_key_exists('totalsize', data))
  7250. {
  7251. if(is_array(data.filelist))
  7252. {
  7253. akeeba_restoration_stat_total = 0;
  7254. $.each(data.filelist,function(i, item)
  7255. {
  7256. akeeba_restoration_stat_total += item[1];
  7257. });
  7258. }
  7259. akeeba_restoration_stat_outbytes = 0;
  7260. akeeba_restoration_stat_inbytes = 0;
  7261. akeeba_restoration_stat_files = 0;
  7262. }
  7263. // Update GUI
  7264. akeeba_restoration_stat_inbytes += data.bytesIn;
  7265. akeeba_restoration_stat_outbytes += data.bytesOut;
  7266. akeeba_restoration_stat_files += data.files;
  7267. var percentage = 0;
  7268. if( akeeba_restoration_stat_total > 0 )
  7269. {
  7270. percentage = 100 * akeeba_restoration_stat_inbytes / akeeba_restoration_stat_total;
  7271. if(percentage < 0) {
  7272. percentage = 0;
  7273. } else if(percentage > 100) {
  7274. percentage = 100;
  7275. }
  7276. }
  7277. if(data.done) percentage = 100;
  7278. setProgressBar(percentage);
  7279. $('#currentFile').text( data.lastfile );
  7280. if(!empty(data.factory)) akeeba_factory = data.factory;
  7281. post = {
  7282. 'task' : 'continueExtracting',
  7283. 'json' : JSON.stringify({factory: akeeba_factory})
  7284. };
  7285. if(!data.done)
  7286. {
  7287. doAjax(post, function(ret){
  7288. processRestorationStep(ret);
  7289. });
  7290. }
  7291. else
  7292. {
  7293. $('#page2a').hide('fast');
  7294. $('#extractionComplete').show('fast');
  7295. $('#runInstaller').css('display','inline-block');
  7296. if(akeeba_automation) $('#runInstaller').click();
  7297. }
  7298. }
  7299. function onGotoStartClick(event)
  7300. {
  7301. $('#page2').hide('fast');
  7302. $('#error').hide('fast');
  7303. $('#page1').show('fast');
  7304. }
  7305. function onRunInstallerClick(event)
  7306. {
  7307. var windowReference = window.open('installation/index.php','installer');
  7308. if(!windowReference.opener) {
  7309. windowReference.opener = this.window;
  7310. }
  7311. $('#runCleanup').css('display','inline-block');
  7312. $('#runInstaller').hide('fast');
  7313. }
  7314. function onRunCleanupClick(event)
  7315. {
  7316. post = {
  7317. 'task' : 'cleanUp',
  7318. // Passing the factory preserves the renamed files array
  7319. 'json' : JSON.stringify({factory: akeeba_factory})
  7320. };
  7321. doAjax(post, function(ret){
  7322. $('#runCleanup').hide('fast');
  7323. $('#gotoSite').css('display','inline-block');
  7324. $('#gotoAdministrator').css('display','inline-block');
  7325. $('#gotoPostRestorationRroubleshooting').css('display','block');
  7326. });
  7327. }
  7328. function errorHandler(msg)
  7329. {
  7330. $('#errorMessage').html(msg);
  7331. $('#error').show('fast');
  7332. }
  7333. function onresetFTPTempDir(event)
  7334. {
  7335. $('#kickstart\\.ftp\\.tempdir').val('<?php echo addcslashes(AKKickstartUtils::getPath(),'\\\'"') ?>');
  7336. }
  7337. /**
  7338. * Akeeba Kickstart Update Check
  7339. */
  7340. var akeeba_update = {version: '0'};
  7341. var akeeba_version = '3.8.0';
  7342. function version_compare (v1, v2, operator) {
  7343. // BEGIN REDUNDANT
  7344. this.php_js = this.php_js || {};
  7345. this.php_js.ENV = this.php_js.ENV || {};
  7346. // END REDUNDANT
  7347. // Important: compare must be initialized at 0.
  7348. var i = 0,
  7349. x = 0,
  7350. compare = 0,
  7351. // vm maps textual PHP versions to negatives so they're less than 0.
  7352. // PHP currently defines these as CASE-SENSITIVE. It is important to
  7353. // leave these as negatives so that they can come before numerical versions
  7354. // and as if no letters were there to begin with.
  7355. // (1alpha is < 1 and < 1.1 but > 1dev1)
  7356. // If a non-numerical value can't be mapped to this table, it receives
  7357. // -7 as its value.
  7358. vm = {
  7359. 'dev': -6,
  7360. 'alpha': -5,
  7361. 'a': -5,
  7362. 'beta': -4,
  7363. 'b': -4,
  7364. 'RC': -3,
  7365. 'rc': -3,
  7366. '#': -2,
  7367. 'p': -1,
  7368. 'pl': -1
  7369. },
  7370. // This function will be called to prepare each version argument.
  7371. // It replaces every _, -, and + with a dot.
  7372. // It surrounds any nonsequence of numbers/dots with dots.
  7373. // It replaces sequences of dots with a single dot.
  7374. // version_compare('4..0', '4.0') == 0
  7375. // Important: A string of 0 length needs to be converted into a value
  7376. // even less than an unexisting value in vm (-7), hence [-8].
  7377. // It's also important to not strip spaces because of this.
  7378. // version_compare('', ' ') == 1
  7379. prepVersion = function (v) {
  7380. v = ('' + v).replace(/[_\-+]/g, '.');
  7381. v = v.replace(/([^.\d]+)/g, '.$1.').replace(/\.{2,}/g, '.');
  7382. return (!v.length ? [-8] : v.split('.'));
  7383. },
  7384. // This converts a version component to a number.
  7385. // Empty component becomes 0.
  7386. // Non-numerical component becomes a negative number.
  7387. // Numerical component becomes itself as an integer.
  7388. numVersion = function (v) {
  7389. return !v ? 0 : (isNaN(v) ? vm[v] || -7 : parseInt(v, 10));
  7390. };
  7391. v1 = prepVersion(v1);
  7392. v2 = prepVersion(v2);
  7393. x = Math.max(v1.length, v2.length);
  7394. for (i = 0; i < x; i++) {
  7395. if (v1[i] == v2[i]) {
  7396. continue;
  7397. }
  7398. v1[i] = numVersion(v1[i]);
  7399. v2[i] = numVersion(v2[i]);
  7400. if (v1[i] < v2[i]) {
  7401. compare = -1;
  7402. break;
  7403. } else if (v1[i] > v2[i]) {
  7404. compare = 1;
  7405. break;
  7406. }
  7407. }
  7408. if (!operator) {
  7409. return compare;
  7410. }
  7411. // Important: operator is CASE-SENSITIVE.
  7412. // "No operator" seems to be treated as "<."
  7413. // Any other values seem to make the function return null.
  7414. switch (operator) {
  7415. case '>':
  7416. case 'gt':
  7417. return (compare > 0);
  7418. case '>=':
  7419. case 'ge':
  7420. return (compare >= 0);
  7421. case '<=':
  7422. case 'le':
  7423. return (compare <= 0);
  7424. case '==':
  7425. case '=':
  7426. case 'eq':
  7427. return (compare === 0);
  7428. case '<>':
  7429. case '!=':
  7430. case 'ne':
  7431. return (compare !== 0);
  7432. case '':
  7433. case '<':
  7434. case 'lt':
  7435. return (compare < 0);
  7436. default:
  7437. return null;
  7438. }
  7439. }
  7440. function checkUpdates()
  7441. {
  7442. var structure =
  7443. {
  7444. type: "GET",
  7445. url: 'http://query.yahooapis.com/v1/public/yql',
  7446. data: {
  7447. <?php if(KICKSTARTPRO): ?>
  7448. q: 'SELECT * FROM xml WHERE url="http://nocdn.akeebabackup.com/updates/kickstart.xml"',
  7449. <?php else: ?>
  7450. q: 'SELECT * FROM xml WHERE url="http://nocdn.akeebabackup.com/updates/kickstartpro.xml"',
  7451. <?php endif; ?>
  7452. format: 'json',
  7453. callback: 'updatesCallback'
  7454. },
  7455. cache: true,
  7456. crossDomain: true,
  7457. jsonp: 'updatesCallback',
  7458. timeout: 15000
  7459. };
  7460. $.ajax( structure );
  7461. }
  7462. function updatesCallback(msg)
  7463. {
  7464. $.each(msg.query.results.updates.update, function(i, el){
  7465. var myUpdate = {
  7466. 'version' : el.version,
  7467. 'infourl' : el.infourl['content'],
  7468. 'dlurl' : el.downloads.downloadurl.content
  7469. }
  7470. if(version_compare(myUpdate.version, akeeba_update.version, 'ge')) {
  7471. akeeba_update = myUpdate;
  7472. }
  7473. });
  7474. if(version_compare(akeeba_update.version, akeeba_version, 'gt')) {
  7475. notifyAboutUpdates();
  7476. }
  7477. }
  7478. function notifyAboutUpdates()
  7479. {
  7480. $('#update-version').text(akeeba_update.version);
  7481. $('#update-dlnow').attr('href', akeeba_update.dlurl);
  7482. $('#update-whatsnew').attr('href', akeeba_update.infourl);
  7483. $('#update-notification').show('slow');
  7484. }
  7485. <?php callExtraFeature('onExtraHeadJavascript'); ?>
  7486. </script>
  7487. </head>
  7488. <body>
  7489. <div id="automode" style="display:none;">
  7490. AUTOMODEON
  7491. </div>
  7492. <div id="fade" class="black_overlay"></div>
  7493. <div id="page-container">
  7494. <div id="preextraction" class="white_content">
  7495. <div id="ie7Warning" style="display:none;">
  7496. <h2>Deprecated Internet Explorer version</h2>
  7497. <p>
  7498. This script is not guaranteed to work properly on Internet Explorer 8
  7499. or previous version, or on Internet Explorer 9 and higher running
  7500. in compatibility mode.
  7501. </p>
  7502. <p>
  7503. Please use Internet Explorer 9 or later in native mode (the
  7504. &quot;broken page&quot; icon next to the address bar should not be
  7505. enabled). Alternatively, you may use the latest versions of Firefox,
  7506. Safari, Google Chrome or Opera.
  7507. </p>
  7508. </div>
  7509. <h2>THINGS_HEADER</h2>
  7510. <ol>
  7511. <li>THINGS_01</li>
  7512. <li>THINGS_02</li>
  7513. <li>THINGS_03</li>
  7514. <li>THINGS_04</li>
  7515. <li>THINGS_05</li>
  7516. <li>THINGS_06</li>
  7517. <li>THINGS_07</li>
  7518. <li>THINGS_08</li>
  7519. <li>THINGS_09</li>
  7520. </ol>
  7521. <a href="javascript:void(0)" onclick="closeLightbox();">CLOSE_LIGHTBOX</a>
  7522. </div>
  7523. <div id="genericerror" class="white_content">
  7524. <pre id="genericerrorInner"></pre>
  7525. </div>
  7526. <div id="header">
  7527. <div class="title">Akeeba Kickstart <?php echo $edition?> 3.8.0</div>
  7528. </div>
  7529. <div id="update-notification" style="display: none">
  7530. <p class="update-notify">UPDATE_HEADER</p>
  7531. <p class="update-whyupdate">UPDATE_NOTICE</p>
  7532. <p class="update-links">
  7533. <a href="#" id="update-dlnow">UPDATE_DLNOW</a>
  7534. <a href="#" id="update-whatsnew" target="_blank">UPDATE_MOREINFO</a>
  7535. </p>
  7536. </div>
  7537. <div id="page1">
  7538. <?php callExtraFeature('onPage1'); ?>
  7539. <div id="page1-content">
  7540. <div class="helpme">
  7541. <span>NEEDSOMEHELPKS</span> <a href="https://www.akeebabackup.com/documentation/quick-start-guide/using-kickstart.html" target="_blank">QUICKSTART</a>
  7542. </div>
  7543. <div class="step1">
  7544. <div class="circle">1</div>
  7545. <h2>SELECT_ARCHIVE</h2>
  7546. <div class="area-container">
  7547. <?php callExtraFeature('onPage1Step1'); ?>
  7548. <div class="clr"></div>
  7549. <label for="kickstart.setup.sourcefile">ARCHIVE_FILE</label>
  7550. <span class="field">
  7551. <?php $filelist = AKKickstartUtils::getArchivesAsOptions();
  7552. if(!empty($filelist)):?>
  7553. <select id="kickstart.setup.sourcefile">
  7554. <?php echo $filelist; ?>
  7555. </select>
  7556. <?php else:?>
  7557. <a href="https://www.akeebabackup.com/documentation/troubleshooter/ksnoarchives.html" target="_blank">NOARCHIVESCLICKHERE</a>
  7558. <?php endif;?>
  7559. </span>
  7560. <br />
  7561. <label for="kickstart.jps.password">JPS_PASSWORD</label>
  7562. <span class="field"><input type="password" id="kickstart.jps.password" value="" /></span>
  7563. </div>
  7564. </div>
  7565. <div class="clr"></div>
  7566. <div class="step2">
  7567. <div class="circle">2</div>
  7568. <h2>SELECT_EXTRACTION</h2>
  7569. <div class="area-container">
  7570. <label for="kickstart.procengine">WRITE_TO_FILES</label>
  7571. <span class="field">
  7572. <select id="kickstart.procengine">
  7573. <option value="hybrid">WRITE_HYBRID</option>
  7574. <option value="direct">WRITE_DIRECTLY</option>
  7575. <option value="ftp">WRITE_FTP</option>
  7576. </select>
  7577. </span><br/>
  7578. <label for="kickstart.setup.ignoreerrors">IGNORE_MOST_ERRORS</label>
  7579. <span class="field"><input type="checkbox" id="kickstart.setup.ignoreerrors" /></span>
  7580. <div id="ftp-options">
  7581. <label for="kickstart.ftp.host">FTP_HOST</label>
  7582. <span class="field"><input type="text" id="kickstart.ftp.host" value="localhost" /></span><br />
  7583. <label for="kickstart.ftp.port">FTP_PORT</label>
  7584. <span class="field"><input type="text" id="kickstart.ftp.port" value="21" /></span><br />
  7585. <label for="kickstart.ftp.ssl">FTP_FTPS</label>
  7586. <span class="field"><input type="checkbox" id="kickstart.ftp.ssl" /></span><br />
  7587. <label for="kickstart.ftp.passive">FTP_PASSIVE</label>
  7588. <span class="field"><input type="checkbox" id="kickstart.ftp.passive" checked="checked" /></span><br />
  7589. <label for="kickstart.ftp.user">FTP_USER</label>
  7590. <span class="field"><input type="text" id="kickstart.ftp.user" value="" /></span><br />
  7591. <label for="kickstart.ftp.pass">FTP_PASS</label>
  7592. <span class="field"><input type="password" id="kickstart.ftp.pass" value="" /></span><br />
  7593. <label for="kickstart.ftp.dir">FTP_DIR</label>
  7594. <span class="field"><input type="text" id="kickstart.ftp.dir" value="" /></span><br />
  7595. <label for="kickstart.ftp.tempdir">FTP_TEMPDIR</label>
  7596. <span class="field">
  7597. <input type="text" id="kickstart.ftp.tempdir" value="<?php echo htmlentities(AKKickstartUtils::getPath()) ?>" />
  7598. <span class="button" id="checkFTPTempDir">BTN_CHECK</span>
  7599. <span class="button" id="resetFTPTempDir">BTN_RESET</span>
  7600. </span><br />
  7601. <label></label>
  7602. <span class="button" id="testFTP">BTN_TESTFTPCON</span>
  7603. <a id="notWorking" class="button" href="https://www.akeebabackup.com/documentation/troubleshooter/kscantextract.html" target="_blank">CANTGETITTOWORK</a>
  7604. <br />
  7605. </div>
  7606. </div>
  7607. </div>
  7608. <div class="clr"></div>
  7609. <div class="step3">
  7610. <div class="circle">3</div>
  7611. <h2>FINE_TUNE</h2>
  7612. <div class="area-container">
  7613. <label for="kickstart.tuning.min_exec_time">MIN_EXEC_TIME</label>
  7614. <span class="field"><input type="text" id="kickstart.tuning.min_exec_time" value="1" /></span> <span>SECONDS_PER_STEP</span><br />
  7615. <label for="kickstart.tuning.max_exec_time">MAX_EXEC_TIME</label>
  7616. <span class="field"><input type="text" id="kickstart.tuning.max_exec_time" value="5" /></span> <span>SECONDS_PER_STEP</span><br />
  7617. <label for="kickstart.stealth.enable">STEALTH_MODE</label>
  7618. <span class="field"><input type="checkbox" id="kickstart.stealth.enable" /></span><br />
  7619. <label for="kickstart.stealth.url">STEALTH_URL</label>
  7620. <span class="field"><input type="text" id="kickstart.stealth.url" value="" /></span><br />
  7621. </div>
  7622. </div>
  7623. <div class="clr"></div>
  7624. <div class="step4">
  7625. <div class="circle">4</div>
  7626. <h2>EXTRACT_FILES</h2>
  7627. <div class="area-container">
  7628. <span></span>
  7629. <span id="gobutton" class="button">BTN_START</span>
  7630. </div>
  7631. </div>
  7632. <div class="clr"></div>
  7633. </div>
  7634. </div>
  7635. <div id="page2">
  7636. <div id="page2a">
  7637. <div class="circle">5</div>
  7638. <h2>EXTRACTING</h2>
  7639. <div class="area-container">
  7640. <div id="warn-not-close">DO_NOT_CLOSE_EXTRACT</div>
  7641. <div id="progressbar">
  7642. <div id="progressbar-inner">&nbsp;</div>
  7643. </div>
  7644. <div id="currentFile"></div>
  7645. </div>
  7646. </div>
  7647. <div id="extractionComplete" style="display: none">
  7648. <div class="circle">6</div>
  7649. <h2>RESTACLEANUP</h2>
  7650. <div id="runInstaller" class="button">BTN_RUNINSTALLER</div>
  7651. <div id="runCleanup" class="button" style="display:none">BTN_CLEANUP</div>
  7652. <div id="gotoSite" class="button" style="display:none">BTN_SITEFE</div>
  7653. <div id="gotoAdministrator" class="button" style="display:none">BTN_SITEBE</div>
  7654. <div id="gotoPostRestorationRroubleshooting" style="display:none">
  7655. <a href="https://www.akeebabackup.com/documentation/troubleshooter/post-restoration.html" target="_blank">POSTRESTORATIONTROUBLESHOOTING</a>
  7656. </div>
  7657. </div>
  7658. <div id="warningsBox" style="display: none;">
  7659. <div id="warningsHeader">
  7660. <h2>WARNINGS</h2>
  7661. </div>
  7662. <div id="warningsContainer">
  7663. <div id="warnings"></div>
  7664. </div>
  7665. </div>
  7666. <div id="error" style="display: none;">
  7667. <h3>ERROR_OCCURED</h3>
  7668. <p id="errorMessage"></p>
  7669. <div id="gotoStart" class="button">BTN_GOTOSTART</div>
  7670. <div>
  7671. <a href="https://www.akeebabackup.com/documentation/troubleshooter/kscantextract.html" target="_blank">CANTGETITTOWORK</a>
  7672. </div>
  7673. </div>
  7674. </div>
  7675. <div id="footer">
  7676. <div class="copyright">Copyright &copy; 2008&ndash;2013 <a href="http://www.akeebabackup.com">Nicholas K.
  7677. Dionysopoulos / Akeeba Backup</a>. All legal rights reserved.<br />
  7678. This program is free software: you can redistribute it and/or modify it under the terms of
  7679. the <a href="http://www.gnu.org/gpl-3.htmlhttp://www.gnu.org/copyleft/gpl.html">GNU General
  7680. Public License</a> as published by the Free Software Foundation, either version 3 of the License,
  7681. or (at your option) any later version.<br />
  7682. Design credits: <a href="http://internet-inspired.com/">Internet Inspired</a>, slightly modified by AkeebaBackup.com
  7683. </div>
  7684. </div>
  7685. </div>
  7686. </body>
  7687. </html>
  7688. <?php
  7689. }
  7690. function createStealthURL()
  7691. {
  7692. $filename = AKFactory::get('kickstart.stealth.url', '');
  7693. // We need an HTML file!
  7694. if(empty($filename)) return;
  7695. // Make sure it ends in .html or .htm
  7696. $filename = basename($filename);
  7697. if( (strtolower(substr($filename,-5)) != '.html') && (strtolower(substr($filename,-4)) != '.htm') ) return;
  7698. $filename_quoted = str_replace('.','\\.',$filename);
  7699. $rewrite_base = trim(dirname(AKFactory::get('kickstart.stealth.url', '')),'/');
  7700. // Get the IP
  7701. $userIP = $_SERVER['REMOTE_ADDR'];
  7702. $userIP = str_replace('.', '\.', $userIP);
  7703. // Get the .htaccess contents
  7704. $stealthHtaccess = <<<ENDHTACCESS
  7705. RewriteEngine On
  7706. RewriteBase /$rewrite_base
  7707. RewriteCond %{REMOTE_HOST} !$userIP
  7708. RewriteCond %{REQUEST_URI} !$filename_quoted
  7709. RewriteCond %{REQUEST_URI} !(\.png|\.jpg|\.gif|\.jpeg|\.bmp|\.swf|\.css|\.js)$
  7710. RewriteRule (.*) $filename [R=307,L]
  7711. ENDHTACCESS;
  7712. // Write the new .htaccess, removing the old one first
  7713. $postproc = AKFactory::getpostProc();
  7714. $postproc->unlink('.htaccess');
  7715. $tempfile = $postproc->processFilename('.htaccess');
  7716. @file_put_contents($tempfile, $stealthHtaccess);
  7717. $postproc->process();
  7718. }
  7719. // Register additional feature classes
  7720. callExtraFeature();
  7721. $retArray = array(
  7722. 'status' => true,
  7723. 'message' => null
  7724. );
  7725. $task = getQueryParam('task', 'display');
  7726. $json = getQueryParam('json');
  7727. $ajax = true;
  7728. switch($task)
  7729. {
  7730. case 'checkTempdir':
  7731. $retArray['status'] = false;
  7732. if(!empty($json))
  7733. {
  7734. $data = json_decode($json, true);
  7735. $dir = @$data['kickstart.ftp.tempdir'];
  7736. if(!empty($dir))
  7737. {
  7738. $retArray['status'] = is_writable($dir);
  7739. }
  7740. }
  7741. break;
  7742. case 'checkFTP':
  7743. $retArray['status'] = false;
  7744. if(!empty($json))
  7745. {
  7746. $data = json_decode($json, true);
  7747. foreach($data as $key => $value)
  7748. {
  7749. AKFactory::set($key, $value);
  7750. }
  7751. $ftp = new AKPostprocFTP();
  7752. $retArray['message'] = $ftp->getError();
  7753. $retArray['status'] = empty($retArray['message']);
  7754. }
  7755. break;
  7756. case 'startExtracting':
  7757. case 'continueExtracting':
  7758. // Look for configuration values
  7759. $retArray['status'] = false;
  7760. if(!empty($json))
  7761. {
  7762. if($task == 'startExtracting') AKFactory::nuke();
  7763. $oldJSON = $json;
  7764. $json = json_decode($json, true);
  7765. if(is_null($json)) {
  7766. $json = stripslashes($oldJSON);
  7767. $json = json_decode($json, true);
  7768. }
  7769. if(!empty($json)) foreach($json as $key => $value)
  7770. {
  7771. if( substr($key,0,9) == 'kickstart' ) {
  7772. AKFactory::set($key, $value);
  7773. }
  7774. }
  7775. // A "factory" variable will override all other settings.
  7776. if( array_key_exists('factory', $json) )
  7777. {
  7778. // Get the serialized factory
  7779. $serialized = $json['factory'];
  7780. AKFactory::unserialize($serialized);
  7781. AKFactory::set('kickstart.enabled', true);
  7782. }
  7783. // Make sure that the destination directory is always set (req'd by both FTP and Direct Writes modes)
  7784. $removePath = AKFactory::get('kickstart.setup.destdir','');
  7785. if(empty($removePath)) AKFactory::set('kickstart.setup.destdir', AKKickstartUtils::getPath());
  7786. if($task=='startExtracting')
  7787. {
  7788. // If the Stealth Mode is enabled, create the .htaccess file
  7789. if( AKFactory::get('kickstart.stealth.enable', false) )
  7790. {
  7791. createStealthURL();
  7792. }
  7793. }
  7794. $engine = AKFactory::getUnarchiver(); // Get the engine
  7795. $observer = new ExtractionObserver(); // Create a new observer
  7796. $engine->attach($observer); // Attach the observer
  7797. $engine->tick();
  7798. $ret = $engine->getStatusArray();
  7799. if( $ret['Error'] != '' )
  7800. {
  7801. $retArray['status'] = false;
  7802. $retArray['done'] = true;
  7803. $retArray['message'] = $ret['Error'];
  7804. }
  7805. elseif( !$ret['HasRun'] )
  7806. {
  7807. $retArray['files'] = $observer->filesProcessed;
  7808. $retArray['bytesIn'] = $observer->compressedTotal;
  7809. $retArray['bytesOut'] = $observer->uncompressedTotal;
  7810. $retArray['status'] = true;
  7811. $retArray['done'] = true;
  7812. }
  7813. else
  7814. {
  7815. $retArray['files'] = $observer->filesProcessed;
  7816. $retArray['bytesIn'] = $observer->compressedTotal;
  7817. $retArray['bytesOut'] = $observer->uncompressedTotal;
  7818. $retArray['status'] = true;
  7819. $retArray['done'] = false;
  7820. $retArray['factory'] = AKFactory::serialize();
  7821. }
  7822. if(!is_null($observer->totalSize))
  7823. {
  7824. $retArray['totalsize'] = $observer->totalSize;
  7825. $retArray['filelist'] = $observer->fileList;
  7826. }
  7827. $retArray['Warnings'] = $ret['Warnings'];
  7828. $retArray['lastfile'] = $observer->lastFile;
  7829. }
  7830. break;
  7831. case 'cleanUp':
  7832. if(!empty($json))
  7833. {
  7834. $json = json_decode($json, true);
  7835. if( array_key_exists('factory', $json) )
  7836. {
  7837. // Get the serialized factory
  7838. $serialized = $json['factory'];
  7839. AKFactory::unserialize($serialized);
  7840. AKFactory::set('kickstart.enabled', true);
  7841. }
  7842. }
  7843. $unarchiver = AKFactory::getUnarchiver(); // Get the engine
  7844. $engine = AKFactory::getPostProc();
  7845. // 1. Remove installation
  7846. recursive_remove_directory('installation');
  7847. // 2. Run the renames, backwards
  7848. $renames = $unarchiver->renameFiles;
  7849. if(!empty($renames)) foreach( $renames as $original => $renamed ) {
  7850. $engine->rename( $renamed, $original );
  7851. }
  7852. // 3. Delete the archive
  7853. foreach( $unarchiver->archiveList as $archive )
  7854. {
  7855. $engine->unlink( $archive );
  7856. }
  7857. // 4. Suicide
  7858. $engine->unlink( basename(__FILE__) );
  7859. // 5. Delete translations
  7860. $dh = opendir(AKKickstartUtils::getPath());
  7861. if($dh !== false)
  7862. {
  7863. $basename = basename(__FILE__, '.php');
  7864. while( false !== $file = @readdir($dh) )
  7865. {
  7866. if( strstr($file, $basename.'.ini') )
  7867. {
  7868. $engine->unlink($file);
  7869. }
  7870. }
  7871. }
  7872. // 6. Delete abiautomation.ini
  7873. $engine->unlink('abiautomation.ini');
  7874. // 7. Delete cacert.pem
  7875. $engine->unlink('cacert.pem');
  7876. // 8. Delete jquery.min.js and json2.min.js
  7877. $engine->unlink('jquery.min.js');
  7878. $engine->unlink('json2.min.js');
  7879. break;
  7880. case 'display':
  7881. $ajax = false;
  7882. $automation = AKAutomation::getInstance();
  7883. echoPage();
  7884. break;
  7885. default:
  7886. $ajax = true;
  7887. if(!empty($json)) {
  7888. $params = json_decode($json, true);
  7889. } else {
  7890. $params = array();
  7891. }
  7892. $retArray = callExtraFeature($task, $params);
  7893. break;
  7894. }
  7895. if($ajax)
  7896. {
  7897. // JSON encode the message
  7898. $json = json_encode($retArray);
  7899. // Do I have to encrypt?
  7900. $password = AKFactory::get('kickstart.security.password', null);
  7901. if(!empty($password))
  7902. {
  7903. $json = AKEncryptionAES::AESEncryptCtr($json, $password, 128);
  7904. }
  7905. // Return the message
  7906. echo "###$json###";
  7907. }
  7908. /**
  7909. * Akeeba Kickstart
  7910. * A JSON-powered archive extraction tool
  7911. *
  7912. * @copyright 2010-2013 Nicholas K. Dionysopoulos / AkeebaBackup.com
  7913. * @license GNU GPL v2 or - at your option - any later version
  7914. * @package akeebabackup
  7915. * @subpackage kickstart
  7916. */
  7917. /**
  7918. * Akeeba Kickstart Import from URL add-on feature
  7919. */
  7920. class AKFeatureURLImport
  7921. {
  7922. private $params = array();
  7923. private static $downloadPageURL = 'http://www.joomla.org/download.html';
  7924. private function getParam($key, $default = null)
  7925. {
  7926. if(array_key_exists($key, $this->params)) {
  7927. return $this->params[$key];
  7928. } else {
  7929. return $default;
  7930. }
  7931. }
  7932. /**
  7933. * Echoes extra CSS to the head of the page
  7934. */
  7935. public function onExtraHeadCSS()
  7936. {
  7937. }
  7938. /**
  7939. * Echoes extra Javascript to the head of the page
  7940. */
  7941. public function onExtraHeadJavascript()
  7942. {
  7943. ?>
  7944. var akeeba_url_filename = null;
  7945. $(document).ready(function(){
  7946. $('#ak-url-showgui').click(function(e){
  7947. $('#ak-url-gui').show('fast');
  7948. $('#ak-url-progress').hide('fast');
  7949. $('#ak-url-complete').hide('fast');
  7950. $('#ak-url-error').hide('fast');
  7951. $('#page1-content').hide('fast');
  7952. });
  7953. $('#ak-url-hidegui').click(function(e){
  7954. $('#ak-url-gui').hide('fast');
  7955. $('#ak-url-progress').hide('fast');
  7956. $('#ak-url-complete').hide('fast');
  7957. $('#ak-url-error').hide('fast');
  7958. $('#page1-content').show('fast');
  7959. });
  7960. $('#ak-url-reload').click(function(e){
  7961. window.location.reload();
  7962. });
  7963. $('#ak-url-gotoStart').click(function(e){
  7964. $('#ak-url-gui').show('fast');
  7965. $('#ak-url-progress').hide('fast');
  7966. $('#ak-url-complete').hide('fast');
  7967. $('#ak-url-error').hide('fast');
  7968. });
  7969. });
  7970. function onAKURLImport()
  7971. {
  7972. akeeba_url_filename = $('#url\\.filename').val();
  7973. ak_urlimport_start();
  7974. }
  7975. function AKURLsetProgressBar(percent)
  7976. {
  7977. var newValue = 0;
  7978. if(percent <= 1) {
  7979. newValue = 100 * percent;
  7980. } else {
  7981. newValue = percent;
  7982. }
  7983. $('#ak-url-progressbar-inner').css('width',percent+'%');
  7984. }
  7985. function ak_urlimport_start()
  7986. {
  7987. akeeba_error_callback = AKURLerrorHandler;
  7988. $('#ak-url-gui').hide('fast');
  7989. $('#ak-url-progress').show('fast');
  7990. $('#ak-url-complete').hide('fast');
  7991. $('#ak-url-error').hide('fast');
  7992. AKURLsetProgressBar(0);
  7993. $('#ak-url-progresstext').html('');
  7994. var data = {
  7995. 'task' : 'urlimport',
  7996. 'json' : JSON.stringify({
  7997. 'file' : akeeba_url_filename,
  7998. 'frag' : "-1",
  7999. 'totalSize' : "-1"
  8000. })
  8001. };
  8002. doAjax(data, function(ret){
  8003. ak_urlimport_step(ret);
  8004. });
  8005. }
  8006. function ak_urlimport_step(data)
  8007. {
  8008. // Look for errors
  8009. if(!data.status)
  8010. {
  8011. AKURLerrorHandler(data.error);
  8012. return;
  8013. }
  8014. var totalSize = 0;
  8015. var doneSize = 0;
  8016. var percent = 0;
  8017. var frag = -1;
  8018. // get running stats
  8019. if(array_key_exists('totalSize', data)) {
  8020. totalSize = data.totalSize;
  8021. }
  8022. if(array_key_exists('doneSize', data)) {
  8023. doneSize = data.doneSize;
  8024. }
  8025. if(array_key_exists('percent', data)) {
  8026. percent = data.percent;
  8027. }
  8028. if(array_key_exists('frag', data)) {
  8029. frag = data.frag;
  8030. }
  8031. // Update GUI
  8032. AKURLsetProgressBar(percent);
  8033. $('#ak-url-progresstext').text( percent+'% ('+doneSize+' / '+totalSize+' bytes)' );
  8034. post = {
  8035. 'task' : 'urlimport',
  8036. 'json' : JSON.stringify({
  8037. 'file' : akeeba_url_filename,
  8038. 'frag' : frag,
  8039. 'totalSize' : totalSize,
  8040. 'doneSize' : doneSize
  8041. })
  8042. };
  8043. if(percent < 100) {
  8044. // More work to do
  8045. doAjax(post, function(ret){
  8046. ak_urlimport_step(ret);
  8047. });
  8048. } else {
  8049. // Done!
  8050. $('#ak-url-gui').hide('fast');
  8051. $('#ak-url-progress').hide('fast');
  8052. $('#ak-url-complete').show('fast');
  8053. $('#ak-url-error').hide('fast');
  8054. }
  8055. }
  8056. function onAKURLJoomla()
  8057. {
  8058. akeeba_error_callback = AKURLerrorHandler;
  8059. var data = {
  8060. 'task' : 'getjurl',
  8061. };
  8062. doAjax(data, function(ret)
  8063. {
  8064. ak_urlimport_gotjurl(ret);
  8065. });
  8066. }
  8067. function ak_urlimport_gotjurl(data)
  8068. {
  8069. var url = '';
  8070. if(array_key_exists('url', data)) {
  8071. url = data.url;
  8072. }
  8073. $('#url\\.filename').val(url);
  8074. }
  8075. function AKURLerrorHandler(msg)
  8076. {
  8077. $('#ak-url-gui').hide('fast');
  8078. $('#ak-url-progress').hide('fast');
  8079. $('#ak-url-complete').hide('fast');
  8080. $('#ak-url-error').show('fast');
  8081. $('#ak-url-errorMessage').html(msg);
  8082. }
  8083. <?php
  8084. }
  8085. /**
  8086. * Echoes extra HTML on page 1 of Kickstart
  8087. */
  8088. public function onPage1()
  8089. {
  8090. ?>
  8091. <div id="ak-url-gui" style="display: none">
  8092. <div class="step1">
  8093. <div class="circle">1</div>
  8094. <h2>AKURL_TITLE_STEP1</h2>
  8095. <div class="area-container">
  8096. <label for="url.filename">AKURL_FILENAME</label>
  8097. <span class="field"><input type="text" style="width: 45%" id="url.filename" value="" /></span>
  8098. <a id="ak-url-joomla" class="button bluebutton loprofile" onclick="onAKURLJoomla()">AKURL_JOOMLA</a>
  8099. <div class="clr"></div>
  8100. <a id="ak-url-connect" class="button" onclick="onAKURLImport()">AKURL_IMPORT</a>
  8101. <a id="ak-url-hidegui" class="button bluebutton">AKURL_CANCEL</a>
  8102. </div>
  8103. </div>
  8104. <div class="clr"></div>
  8105. </div>
  8106. <div id="ak-url-progress" style="display: none">
  8107. <div class="circle">2</div>
  8108. <h2>AKURL_TITLE_STEP2</h2>
  8109. <div class="area-container">
  8110. <div id="ak-url-importing">
  8111. <div class="warn-not-close">AKURL_DO_NOT_CLOSE</div>
  8112. <div id="ak-url-progressbar" class="progressbar">
  8113. <div id="ak-url-progressbar-inner" class="progressbar-inner">&nbsp;</div>
  8114. </div>
  8115. <div id="ak-url-progresstext"></div>
  8116. </div>
  8117. </div>
  8118. </div>
  8119. <div id="ak-url-complete" style="display: none">
  8120. <div class="circle">3</div>
  8121. <h2>AKURL_TITLE_STEP3</h2>
  8122. <div class="area-container">
  8123. <div id="ak-url-reload" class="button">AKURL_BTN_RELOAD</div>
  8124. </div>
  8125. </div>
  8126. <div id="ak-url-error" class="error" style="display: none;">
  8127. <h3>ERROR_OCCURED</h3>
  8128. <p id="ak-url-errorMessage" class="errorMessage"></p>
  8129. <div id="ak-url-gotoStart" class="button">BTN_GOTOSTART</div>
  8130. </div>
  8131. <?php
  8132. }
  8133. /**
  8134. * Outputs HTML to be shown before Step 1's archive selection pane
  8135. */
  8136. public function onPage1Step1()
  8137. {
  8138. ?>
  8139. <a id="ak-url-showgui" class="button bluebutton loprofile">AKURL_IMPORT</a>
  8140. <?php
  8141. }
  8142. public function urlimport($params)
  8143. {
  8144. $this->params = $params;
  8145. // Fetch data
  8146. $filename = $this->getParam('file');
  8147. $frag = $this->getParam('frag', -1);
  8148. $totalSize = $this->getParam('totalSize', -1);
  8149. $doneSize = $this->getParam('doneSize', -1);
  8150. debugMsg('Importing from URL');
  8151. debugMsg(' file : ' . $filename);
  8152. debugMsg(' frag : ' . $frag);
  8153. debugMsg(' totalSize : ' . $totalSize);
  8154. debugMsg(' doneSize : ' . $doneSize);
  8155. // Init retArray
  8156. $retArray = array(
  8157. "status" => true,
  8158. "error" => '',
  8159. "frag" => $frag,
  8160. "totalSize" => $totalSize,
  8161. "doneSize" => $doneSize,
  8162. "percent" => 0,
  8163. );
  8164. try {
  8165. AKFactory::set('kickstart.tuning.max_exec_time', '5');
  8166. AKFactory::set('kickstart.tuning.run_time_bias', '75');
  8167. $timer = new AKCoreTimer();
  8168. $start = $timer->getRunningTime(); // Mark the start of this download
  8169. $break = false; // Don't break the step
  8170. while( ($timer->getTimeLeft() > 0) && !$break )
  8171. {
  8172. // Figure out where on Earth to put that file
  8173. $local_file = KSROOTDIR . '/' . basename($filename);
  8174. debugMsg("- Importing from $filename");
  8175. // Do we have to initialize the file?
  8176. if($frag == -1)
  8177. {
  8178. debugMsg("-- First frag, killing local file");
  8179. // Currently downloaded size
  8180. $doneSize = 0;
  8181. // Delete and touch the output file
  8182. @unlink($local_file);
  8183. $fp = @fopen($local_file, 'wb');
  8184. if($fp !== false)
  8185. {
  8186. @fclose($fp);
  8187. }
  8188. // Init
  8189. $frag = 0;
  8190. // @todo Get the file size
  8191. }
  8192. // Calculate from and length
  8193. $length = 1048576;
  8194. $from = $frag * $length;
  8195. $to = $length + $from - 1;
  8196. //if($from == 0) $from = 1;
  8197. // Try to download the first frag
  8198. $temp_file = $local_file . '.tmp';
  8199. @unlink($temp_file);
  8200. $required_time = 1.0;
  8201. debugMsg("-- Importing frag $frag, byte position from/to: $from / $to");
  8202. try
  8203. {
  8204. $ch = curl_init();
  8205. curl_setopt($ch, CURLOPT_URL, $filename);
  8206. curl_setopt($ch, CURLOPT_RANGE, "$from-$to");
  8207. curl_setopt($ch, CURLOPT_BINARYTRANSFER, 1);
  8208. curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
  8209. @curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
  8210. $result = curl_exec($ch);
  8211. $errno = curl_errno($ch);
  8212. $errmsg = curl_error($ch);
  8213. $http_status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
  8214. if ($result === false)
  8215. {
  8216. $error = "cURL error $errno: $errmsg";
  8217. }
  8218. elseif ($http_status > 299)
  8219. {
  8220. $result = false;
  8221. $error = "HTTP status $http_status";
  8222. }
  8223. else
  8224. {
  8225. $result = file_put_contents($temp_file, $result);
  8226. if ($result === false)
  8227. {
  8228. $error = "Could not open temporary file $temp_file for writing";
  8229. }
  8230. }
  8231. curl_close($ch);
  8232. }
  8233. catch (Exception $e)
  8234. {
  8235. $error = $e->getMessage();
  8236. }
  8237. if(!$result) {
  8238. @unlink($temp_file);
  8239. // Failed download
  8240. if ($frag == 0)
  8241. {
  8242. // Failure to download first frag = failure to download. Period.
  8243. $retArray['status'] = false;
  8244. $retArray['error'] = $error;
  8245. debugMsg("-- Download FAILED");
  8246. return $retArray;
  8247. }
  8248. else
  8249. {
  8250. // Since this is a staggered download, consider this normal and finish
  8251. $frag = -1;
  8252. debugMsg("-- Import complete");
  8253. $doneSize = $totalSize;
  8254. $break = true;
  8255. continue;
  8256. }
  8257. }
  8258. // Add the currently downloaded frag to the total size of downloaded files
  8259. if($result)
  8260. {
  8261. clearstatcache();
  8262. $filesize = (int)@filesize($temp_file);
  8263. debugMsg("-- Successful download of $filesize bytes");
  8264. $doneSize += $filesize;
  8265. // Append the file
  8266. $fp = @fopen($local_file,'ab');
  8267. if($fp === false)
  8268. {
  8269. debugMsg("-- Can't open local file for writing");
  8270. // Can't open the file for writing
  8271. @unlink($temp_file);
  8272. $retArray['status'] = false;
  8273. $retArray['error'] = 'Can\'t write to the local file';
  8274. return false;
  8275. }
  8276. $tf = fopen($temp_file,'rb');
  8277. while(!feof($tf))
  8278. {
  8279. $data = fread($tf, 262144);
  8280. fwrite($fp, $data);
  8281. }
  8282. fclose($tf);
  8283. fclose($fp);
  8284. @unlink($temp_file);
  8285. debugMsg("-- Temporary file merged and removed");
  8286. $frag++;
  8287. debugMsg("-- Proceeding to next fragment, frag $frag");
  8288. }
  8289. // Advance the frag pointer and mark the end
  8290. $end = $timer->getRunningTime();
  8291. // Do we predict that we have enough time?
  8292. $required_time = max(1.1 * ($end - $start), $required_time);
  8293. if( $required_time > (10-$end+$start) )
  8294. {
  8295. $break = true;
  8296. }
  8297. $start = $end;
  8298. }
  8299. if ($frag == -1)
  8300. {
  8301. $percent = 100;
  8302. }
  8303. elseif($doneSize <= 0)
  8304. {
  8305. $percent = 0;
  8306. }
  8307. else
  8308. {
  8309. if ($totalSize > 0)
  8310. {
  8311. $percent = 100 * ($doneSize / $totalSize);
  8312. }
  8313. else
  8314. {
  8315. $percent = 0;
  8316. }
  8317. }
  8318. // Update $retArray
  8319. $retArray = array(
  8320. "status" => true,
  8321. "error" => '',
  8322. "frag" => $frag,
  8323. "totalSize" => $totalSize,
  8324. "doneSize" => $doneSize,
  8325. "percent" => $percent,
  8326. );
  8327. }
  8328. catch(Exception $e)
  8329. {
  8330. debugMsg("EXCEPTION RAISED:");
  8331. debugMsg($e->getMessage());
  8332. $retArray['status'] = false;
  8333. $retArray['error'] = $e->getMessage();
  8334. }
  8335. return $retArray;
  8336. }
  8337. public function getjurl($params)
  8338. {
  8339. return array(
  8340. "url" => $this->getLatestJoomlaURL(),
  8341. );
  8342. }
  8343. public function onLoadTranslations()
  8344. {
  8345. $translation = AKText::getInstance();
  8346. $translation->addDefaultLanguageStrings(array(
  8347. 'AKURL_IMPORT' => "Import from URL",
  8348. 'AKURL_TITLE_STEP1' => "Specify the URL",
  8349. 'AKURL_FILENAME' => "URL to import",
  8350. 'AKURL_JOOMLA' => "Latest Joomla! release",
  8351. 'AKURL_CANCEL' => "Cancel import",
  8352. 'AKURL_TITLE_STEP2' => "Importing...",
  8353. 'AKURL_DO_NOT_CLOSE' => "Please do not close this window while your backup archives are being imported",
  8354. 'AKURL_TITLE_STEP3' => "Import is complete",
  8355. 'AKURL_BTN_RELOAD' => "Reload Kickstart",
  8356. ));
  8357. }
  8358. private function getLatestJoomlaURL()
  8359. {
  8360. $ch = curl_init();
  8361. curl_setopt($ch, CURLOPT_URL, self::$downloadPageURL);
  8362. curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
  8363. @curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
  8364. $pageHTML = curl_exec($ch);
  8365. $http_status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
  8366. if (($pageHTML === false) || ($http_status >= 300))
  8367. {
  8368. return '';
  8369. }
  8370. $uniqueHTMLid = '<a id="latest"';
  8371. $posStart = strpos($pageHTML, $uniqueHTMLid);
  8372. if ($posStart === false)
  8373. {
  8374. return '';
  8375. }
  8376. $posStart += strlen($uniqueHTMLid);
  8377. $posEnd = strpos($pageHTML, '>', $posStart);
  8378. if ($posEnd === false)
  8379. {
  8380. return '';
  8381. }
  8382. $innerAttributes = substr($pageHTML, $posStart, $posEnd - $posStart);
  8383. $posStart = strpos($innerAttributes, '"');
  8384. $posEnd = strpos($innerAttributes, '"', $posStart + 1);
  8385. $url = substr($innerAttributes, $posStart + 1, $posEnd - $posStart - 1);
  8386. return $url;
  8387. }
  8388. }