diff --git a/administrator/components/com_admin/sql/updates/mysql/3.3.6-2014-09-30.sql b/administrator/components/com_admin/sql/updates/mysql/3.3.6-2014-09-30.sql
new file mode 100644
index 0000000000000..73b7c1ba0ed3f
--- /dev/null
+++ b/administrator/components/com_admin/sql/updates/mysql/3.3.6-2014-09-30.sql
@@ -0,0 +1,5 @@
+INSERT INTO `#__update_sites` (`name`, `type`, `location`, `enabled`) VALUES
+('Joomla! Update Component Update Site', 'extension', 'http://update.joomla.org/core/extensions/com_joomlaupdate.xml', 1);
+
+INSERT INTO `#__update_sites_extensions` (`update_site_id`, `extension_id`) VALUES
+((SELECT `update_site_id` FROM `#__update_sites` WHERE `name` = 'Joomla! Update Component Update Site'), (SELECT `extension_id` FROM `#__extensions` WHERE `name` = 'com_joomlaupdate'));
diff --git a/administrator/components/com_admin/sql/updates/postgresql/3.3.6-2014-09-30.sql b/administrator/components/com_admin/sql/updates/postgresql/3.3.6-2014-09-30.sql
new file mode 100644
index 0000000000000..fae9c20d45522
--- /dev/null
+++ b/administrator/components/com_admin/sql/updates/postgresql/3.3.6-2014-09-30.sql
@@ -0,0 +1,5 @@
+INSERT INTO "#__update_sites" ("name", "type", "location", "enabled") VALUES
+('Joomla! Update Component Update Site', 'extension', 'http://update.joomla.org/core/extensions/com_joomlaupdate.xml', 1);
+
+INSERT INTO "#__update_sites_extensions" ("update_site_id", "extension_id") VALUES
+((SELECT "update_site_id" FROM "#__update_sites" WHERE "name" = 'Joomla! Update Component Update Site'), (SELECT "extension_id" FROM "#__extensions" WHERE "name" = 'com_joomlaupdate'));
diff --git a/administrator/components/com_admin/sql/updates/sqlazure/3.3.6-2014-09-30.sql b/administrator/components/com_admin/sql/updates/sqlazure/3.3.6-2014-09-30.sql
new file mode 100644
index 0000000000000..309ade5b7c50a
--- /dev/null
+++ b/administrator/components/com_admin/sql/updates/sqlazure/3.3.6-2014-09-30.sql
@@ -0,0 +1,5 @@
+INSERT INTO [#__update_sites] ([name], [type], [location], [enabled])
+SELECT 'Joomla! Update Component Update Site', 'extension', 'http://update.joomla.org/core/extensions/com_joomlaupdate.xml', 1;
+
+INSERT INTO [#__update_sites_extensions] ([update_site_id], [extension_id])
+SELECT (SELECT [update_site_id] FROM [#__update_sites] WHERE [name] = 'Joomla! Update Component Update Site'), (SELECT [extension_id] FROM [#__extensions] WHERE [name] = 'com_joomlaupdate');
diff --git a/administrator/components/com_categories/views/categories/tmpl/modal.php b/administrator/components/com_categories/views/categories/tmpl/modal.php
index 8060242933793..c91bf088da6a8 100644
--- a/administrator/components/com_categories/views/categories/tmpl/modal.php
+++ b/administrator/components/com_categories/views/categories/tmpl/modal.php
@@ -144,7 +144,7 @@
- id; ?>
+ id; ?>
|
diff --git a/administrator/components/com_contact/tables/contact.php b/administrator/components/com_contact/tables/contact.php
index cc0c8b7590d78..140b7b09221a2 100644
--- a/administrator/components/com_contact/tables/contact.php
+++ b/administrator/components/com_contact/tables/contact.php
@@ -21,7 +21,7 @@ class ContactTableContact extends JTable
* @var array
* @since 3.3
*/
- protected $jsonEncode = array('params', 'metadata');
+ protected $_jsonEncode = array('params', 'metadata');
/**
* Constructor
diff --git a/administrator/components/com_joomlaupdate/joomlaupdate.xml b/administrator/components/com_joomlaupdate/joomlaupdate.xml
index 003bb1d792616..919cfe5e170fc 100644
--- a/administrator/components/com_joomlaupdate/joomlaupdate.xml
+++ b/administrator/components/com_joomlaupdate/joomlaupdate.xml
@@ -26,5 +26,8 @@
language/en-GB.com_joomlaupdate.sys.ini
+
+ http://update.joomla.org/core/extensions/com_joomlaupdate.xml
+
diff --git a/administrator/components/com_joomlaupdate/restore.php b/administrator/components/com_joomlaupdate/restore.php
index 3d47bf6c96d80..83e74c2878dad 100644
--- a/administrator/components/com_joomlaupdate/restore.php
+++ b/administrator/components/com_joomlaupdate/restore.php
@@ -1,82 +1,134 @@
|'),
- array('*' => '.*', '?' => '.?')) . '$/i', $string
+ array('*' => '.*', '?' => '.?')) . '$/i', $string
);
}
}
// Unicode-safe binary data length function
-if(function_exists('mb_strlen')) {
- function akstringlen($string) { return mb_strlen($string,'8bit'); }
-} else {
- function akstringlen($string) { return strlen($string); }
+if (!function_exists('akstringlen'))
+{
+ if (function_exists('mb_strlen'))
+ {
+ function akstringlen($string)
+ {
+ return mb_strlen($string, '8bit');
+ }
+ }
+ else
+ {
+ function akstringlen($string)
+ {
+ return strlen($string);
+ }
+ }
}
/**
* Gets a query parameter from GET or POST data
+ *
* @param $key
* @param $default
*/
-function getQueryParam( $key, $default = null )
+function getQueryParam($key, $default = null)
{
- $value = null;
+ $value = $default;
- if(array_key_exists($key, $_REQUEST)) {
+ if (array_key_exists($key, $_REQUEST))
+ {
$value = $_REQUEST[$key];
- } elseif(array_key_exists($key, $_POST)) {
- $value = $_POST[$key];
- } elseif(array_key_exists($key, $_GET)) {
- $value = $_GET[$key];
- } else {
- return $default;
}
- if(get_magic_quotes_gpc() && !is_null($value)) $value=stripslashes($value);
+ if (get_magic_quotes_gpc() && !is_null($value))
+ {
+ $value = stripslashes($value);
+ }
return $value;
}
+// Debugging function
+function debugMsg($msg)
+{
+ if (!defined('KSDEBUG'))
+ {
+ return;
+ }
+
+ $fp = fopen('debug.txt', 'at');
+
+ fwrite($fp, $msg . "\n");
+ fclose($fp);
+}
+
+/**
+ * Akeeba Restore
+ * A JSON-powered JPA, JPS and ZIP archive extraction library
+ *
+ * @copyright 2010-2014 Nicholas K. Dionysopoulos / Akeeba Ltd.
+ * @license GNU GPL v2 or - at your option - any later version
+ * @package akeebabackup
+ * @subpackage kickstart
+ */
+
/**
* Akeeba Backup's JSON compatibility layer
*
@@ -900,6 +952,16 @@ function json_decode($value, $assoc = false)
}
}
+/**
+ * Akeeba Restore
+ * A JSON-powered JPA, JPS and ZIP archive extraction library
+ *
+ * @copyright 2010-2014 Nicholas K. Dionysopoulos / Akeeba Ltd.
+ * @license GNU GPL v2 or - at your option - any later version
+ * @package akeebabackup
+ * @subpackage kickstart
+ */
+
/**
* The base class of Akeeba Engine objects. Allows for error and warnings logging
* and propagation. Largely based on the Joomla! 1.5 JObject class.
@@ -1154,438 +1216,823 @@ private function getItemFromArray($array, $i = null)
}
/**
- * File post processor engines base class
+ * Akeeba Restore
+ * A JSON-powered JPA, JPS and ZIP archive extraction library
+ *
+ * @copyright 2010-2014 Nicholas K. Dionysopoulos / Akeeba Ltd.
+ * @license GNU GPL v2 or - at your option - any later version
+ * @package akeebabackup
+ * @subpackage kickstart
*/
-abstract class AKAbstractPostproc extends AKAbstractObject
-{
- /** @var string The current (real) file path we'll have to process */
- protected $filename = null;
-
- /** @var int The requested permissions */
- protected $perms = 0755;
-
- /** @var string The temporary file path we gave to the unarchiver engine */
- protected $tempFilename = null;
-
- /** @var int The UNIX timestamp of the file's desired modification date */
- public $timestamp = 0;
+/**
+ * The superclass of all Akeeba Kickstart parts. The "parts" are intelligent stateful
+ * classes which perform a single procedure and have preparation, running and
+ * finalization phases. The transition between phases is handled automatically by
+ * this superclass' tick() final public method, which should be the ONLY public API
+ * exposed to the rest of the Akeeba Engine.
+ */
+abstract class AKAbstractPart extends AKAbstractObject
+{
/**
- * Processes the current file, e.g. moves it from temp to final location by FTP
+ * Indicates whether this part has finished its initialisation cycle
+ * @var boolean
*/
- abstract public function process();
+ protected $isPrepared = false;
/**
- * The unarchiver tells us the path to the filename it wants to extract and we give it
- * a different path instead.
- * @param string $filename The path to the real file
- * @param int $perms The permissions we need the file to have
- * @return string The path to the temporary file
+ * Indicates whether this part has more work to do (it's in running state)
+ * @var boolean
*/
- abstract public function processFilename($filename, $perms = 0755);
+ protected $isRunning = false;
/**
- * Recursively creates a directory if it doesn't exist
- * @param string $dirName The directory to create
- * @param int $perms The permissions to give to that directory
+ * Indicates whether this part has finished its finalization cycle
+ * @var boolean
*/
- abstract public function createDirRecursive( $dirName, $perms );
-
- abstract public function chmod( $file, $perms );
-
- abstract public function unlink( $file );
-
- abstract public function rmdir( $directory );
-
- abstract public function rename( $from, $to );
-}
-
-/**
- * The base class of unarchiver classes
- */
-abstract class AKAbstractUnarchiver extends AKAbstractPart
-{
- /** @var string Archive filename */
- protected $filename = null;
-
- /** @var array List of the names of all archive parts */
- public $archiveList = array();
-
- /** @var int The total size of all archive parts */
- public $totalSize = array();
-
- /** @var integer Current archive part number */
- protected $currentPartNumber = -1;
-
- /** @var integer The offset inside the current part */
- protected $currentPartOffset = 0;
-
- /** @var bool Should I restore permissions? */
- protected $flagRestorePermissions = false;
-
- /** @var AKAbstractPostproc Post processing class */
- protected $postProcEngine = null;
+ protected $isFinished = false;
- /** @var string Absolute path to prepend to extracted files */
- protected $addPath = '';
+ /**
+ * Indicates whether this part has finished its run cycle
+ * @var boolean
+ */
+ protected $hasRan = false;
- /** @var array Which files to rename */
- public $renameFiles = array();
+ /**
+ * The name of the engine part (a.k.a. Domain), used in return table
+ * generation.
+ * @var string
+ */
+ protected $active_domain = "";
- /** @var array Which directories to rename */
- public $renameDirs = array();
+ /**
+ * The step this engine part is in. Used verbatim in return table and
+ * should be set by the code in the _run() method.
+ * @var string
+ */
+ protected $active_step = "";
- /** @var array Which files to skip */
- public $skipFiles = array();
+ /**
+ * A more detailed description of the step this engine part is in. Used
+ * verbatim in return table and should be set by the code in the _run()
+ * method.
+ * @var string
+ */
+ protected $active_substep = "";
- /** @var integer Chunk size for processing */
- protected $chunkSize = 524288;
+ /**
+ * Any configuration variables, in the form of an array.
+ * @var array
+ */
+ protected $_parametersArray = array();
- /** @var resource File pointer to the current archive part file */
- protected $fp = null;
+ /** @var string The database root key */
+ protected $databaseRoot = array();
- /** @var int Run state when processing the current archive file */
- protected $runState = null;
+ /** @var int Last reported warnings's position in array */
+ private $warnings_pointer = -1;
- /** @var stdClass File header data, as read by the readFileHeader() method */
- protected $fileHeader = null;
+ /** @var array An array of observers */
+ protected $observers = array();
- /** @var int How much of the uncompressed data we've read so far */
- protected $dataReadLength = 0;
+ /**
+ * Runs the preparation for this part. Should set _isPrepared
+ * to true
+ */
+ abstract protected function _prepare();
/**
- * Public constructor
+ * Runs the finalisation process for this part. Should set
+ * _isFinished to true.
*/
- public function __construct()
- {
- parent::__construct();
- }
+ abstract protected function _finalize();
/**
- * Wakeup function, called whenever the class is unserialized
+ * Runs the main functionality loop for this part. Upon calling,
+ * should set the _isRunning to true. When it finished, should set
+ * the _hasRan to true. If an error is encountered, setError should
+ * be used.
*/
- public function __wakeup()
- {
- if($this->currentPartNumber >= 0)
- {
- $this->fp = @fopen($this->archiveList[$this->currentPartNumber], 'rb');
- if( (is_resource($this->fp)) && ($this->currentPartOffset > 0) )
- {
- @fseek($this->fp, $this->currentPartOffset);
- }
- }
- }
+ abstract protected function _run();
/**
- * Sleep function, called whenever the class is serialized
+ * Sets the BREAKFLAG, which instructs this engine part that the current step must break immediately,
+ * in fear of timing out.
*/
- public function shutdown()
+ protected function setBreakFlag()
{
- if(is_resource($this->fp))
- {
- $this->currentPartOffset = @ftell($this->fp);
- @fclose($this->fp);
- }
+ AKFactory::set('volatile.breakflag', true);
}
/**
- * Implements the abstract _prepare() method
+ * Sets the engine part's internal state, in an easy to use manner
+ *
+ * @param string $state One of init, prepared, running, postrun, finished, error
+ * @param string $errorMessage The reported error message, should the state be set to error
*/
- final protected function _prepare()
+ protected function setState($state = 'init', $errorMessage='Invalid setState argument')
{
- parent::__construct();
-
- if( count($this->_parametersArray) > 0 )
+ switch($state)
{
- foreach($this->_parametersArray as $key => $value)
- {
- switch($key)
- {
- case 'filename': // Archive's absolute filename
- $this->filename = $value;
- break;
-
- case 'restore_permissions': // Should I restore permissions?
- $this->flagRestorePermissions = $value;
- break;
+ case 'init':
+ $this->isPrepared = false;
+ $this->isRunning = false;
+ $this->isFinished = false;
+ $this->hasRun = false;
+ break;
- case 'post_proc': // Should I use FTP?
- $this->postProcEngine = AKFactory::getpostProc($value);
- break;
+ case 'prepared':
+ $this->isPrepared = true;
+ $this->isRunning = false;
+ $this->isFinished = false;
+ $this->hasRun = false;
+ break;
- case 'add_path': // Path to prepend
- $this->addPath = $value;
- $this->addPath = str_replace('\\','/',$this->addPath);
- $this->addPath = rtrim($this->addPath,'/');
- if(!empty($this->addPath)) $this->addPath .= '/';
- break;
+ case 'running':
+ $this->isPrepared = true;
+ $this->isRunning = true;
+ $this->isFinished = false;
+ $this->hasRun = false;
+ break;
- case 'rename_files': // Which files to rename (hash array)
- $this->renameFiles = $value;
- break;
-
- case 'rename_dirs': // Which files to rename (hash array)
- $this->renameDirs = $value;
- break;
+ case 'postrun':
+ $this->isPrepared = true;
+ $this->isRunning = false;
+ $this->isFinished = false;
+ $this->hasRun = true;
+ break;
- case 'skip_files': // Which files to skip (indexed array)
- $this->skipFiles = $value;
- break;
- }
- }
+ case 'finished':
+ $this->isPrepared = true;
+ $this->isRunning = false;
+ $this->isFinished = true;
+ $this->hasRun = false;
+ break;
+
+ case 'error':
+ default:
+ $this->setError($errorMessage);
+ break;
}
+ }
- $this->scanArchives();
+ /**
+ * The public interface to an engine part. This method takes care for
+ * calling the correct method in order to perform the initialisation -
+ * run - finalisation cycle of operation and return a proper reponse array.
+ * @return array A Reponse Array
+ */
+ final public function tick()
+ {
+ // Call the right action method, depending on engine part state
+ switch( $this->getState() )
+ {
+ case "init":
+ $this->_prepare();
+ break;
+ case "prepared":
+ $this->_run();
+ break;
+ case "running":
+ $this->_run();
+ break;
+ case "postrun":
+ $this->_finalize();
+ break;
+ }
- $this->readArchiveHeader();
- $errMessage = $this->getError();
- if(!empty($errMessage))
+ // Send a Return Table back to the caller
+ $out = $this->_makeReturnTable();
+ return $out;
+ }
+
+ /**
+ * Returns a copy of the class's status array
+ * @return array
+ */
+ public function getStatusArray()
+ {
+ return $this->_makeReturnTable();
+ }
+
+ /**
+ * Sends any kind of setup information to the engine part. Using this,
+ * we avoid passing parameters to the constructor of the class. These
+ * parameters should be passed as an indexed array and should be taken
+ * into account during the preparation process only. This function will
+ * set the error flag if it's called after the engine part is prepared.
+ *
+ * @param array $parametersArray The parameters to be passed to the
+ * engine part.
+ */
+ final public function setup( $parametersArray )
+ {
+ if( $this->isPrepared )
{
- $this->setState('error', $errMessage);
+ $this->setState('error', "Can't modify configuration after the preparation of " . $this->active_domain);
}
else
{
- $this->runState = AK_STATE_NOFILE;
- $this->setState('prepared');
+ $this->_parametersArray = $parametersArray;
+ if(array_key_exists('root', $parametersArray))
+ {
+ $this->databaseRoot = $parametersArray['root'];
+ }
}
}
- protected function _run()
+ /**
+ * Returns the state of this engine part.
+ *
+ * @return string The state of this engine part. It can be one of
+ * error, init, prepared, running, postrun, finished.
+ */
+ final public function getState()
{
- if($this->getState() == 'postrun') return;
-
- $this->setState('running');
-
- $timer = AKFactory::getTimer();
-
- $status = true;
- while( $status && ($timer->getTimeLeft() > 0) )
+ if( $this->getError() )
{
- switch( $this->runState )
- {
- case AK_STATE_NOFILE:
- $status = $this->readFileHeader();
- if($status)
- {
- // Send start of file notification
- $message = new stdClass;
- $message->type = 'startfile';
- $message->content = new stdClass;
- if( array_key_exists('realfile', get_object_vars($this->fileHeader)) ) {
- $message->content->realfile = $this->fileHeader->realFile;
- } else {
- $message->content->realfile = $this->fileHeader->file;
- }
- $message->content->file = $this->fileHeader->file;
- if( array_key_exists('compressed', get_object_vars($this->fileHeader)) ) {
- $message->content->compressed = $this->fileHeader->compressed;
- } else {
- $message->content->compressed = 0;
- }
- $message->content->uncompressed = $this->fileHeader->uncompressed;
- $this->notify($message);
- }
- break;
-
- case AK_STATE_HEADER:
- case AK_STATE_DATA:
- $status = $this->processFileData();
- break;
+ return "error";
+ }
- case AK_STATE_DATAREAD:
- case AK_STATE_POSTPROC:
- $this->postProcEngine->timestamp = $this->fileHeader->timestamp;
- $status = $this->postProcEngine->process();
- $this->propagateFromObject( $this->postProcEngine );
- $this->runState = AK_STATE_DONE;
- break;
+ if( !($this->isPrepared) )
+ {
+ return "init";
+ }
- case AK_STATE_DONE:
- default:
- if($status)
- {
- // Send end of file notification
- $message = new stdClass;
- $message->type = 'endfile';
- $message->content = new stdClass;
- if( array_key_exists('realfile', get_object_vars($this->fileHeader)) ) {
- $message->content->realfile = $this->fileHeader->realFile;
- } else {
- $message->content->realfile = $this->fileHeader->file;
- }
- $message->content->file = $this->fileHeader->file;
- if( array_key_exists('compressed', get_object_vars($this->fileHeader)) ) {
- $message->content->compressed = $this->fileHeader->compressed;
- } else {
- $message->content->compressed = 0;
- }
- $message->content->uncompressed = $this->fileHeader->uncompressed;
- $this->notify($message);
- }
- $this->runState = AK_STATE_NOFILE;
- continue;
- }
+ if( !($this->isFinished) && !($this->isRunning) && !( $this->hasRun ) && ($this->isPrepared) )
+ {
+ return "prepared";
}
- $error = $this->getError();
- if( !$status && ($this->runState == AK_STATE_NOFILE) && empty( $error ) )
+ if ( !($this->isFinished) && $this->isRunning && !( $this->hasRun ) )
{
- // We just finished
- $this->setState('postrun');
+ return "running";
}
- elseif( !empty($error) )
+
+ if ( !($this->isFinished) && !($this->isRunning) && $this->hasRun )
{
- $this->setState( 'error', $error );
+ return "postrun";
}
- }
- protected function _finalize()
- {
- // Nothing to do
- $this->setState('finished');
+ if ( $this->isFinished )
+ {
+ return "finished";
+ }
}
/**
- * Returns the base extension of the file, e.g. '.jpa'
- * @return string
+ * Constructs a Response Array based on the engine part's state.
+ * @return array The Response Array for the current state
*/
- private function getBaseExtension()
+ final protected function _makeReturnTable()
{
- static $baseextension;
-
- if(empty($baseextension))
+ // Get a list of warnings
+ $warnings = $this->getWarnings();
+ // Report only new warnings if there is no warnings queue size
+ if( $this->_warnings_queue_size == 0 )
{
- $basename = basename($this->filename);
- $lastdot = strrpos($basename,'.');
- $baseextension = substr($basename, $lastdot);
+ if( ($this->warnings_pointer > 0) && ($this->warnings_pointer < (count($warnings)) ) )
+ {
+ $warnings = array_slice($warnings, $this->warnings_pointer + 1);
+ $this->warnings_pointer += count($warnings);
+ }
+ else
+ {
+ $this->warnings_pointer = count($warnings);
+ }
}
- return $baseextension;
+ $out = array(
+ 'HasRun' => (!($this->isFinished)),
+ 'Domain' => $this->active_domain,
+ 'Step' => $this->active_step,
+ 'Substep' => $this->active_substep,
+ 'Error' => $this->getError(),
+ 'Warnings' => $warnings
+ );
+
+ return $out;
}
- /**
- * Scans for archive parts
- */
- private function scanArchives()
+ final protected function setDomain($new_domain)
{
- $privateArchiveList = array();
+ $this->active_domain = $new_domain;
+ }
- // Get the components of the archive filename
- $dirname = dirname($this->filename);
- $base_extension = $this->getBaseExtension();
- $basename = basename($this->filename, $base_extension);
- $this->totalSize = 0;
+ final public function getDomain()
+ {
+ return $this->active_domain;
+ }
- // Scan for multiple parts until we don't find any more of them
- $count = 0;
- $found = true;
- $this->archiveList = array();
- while($found)
- {
- ++$count;
- $extension = substr($base_extension, 0, 2).sprintf('%02d', $count);
- $filename = $dirname.DIRECTORY_SEPARATOR.$basename.$extension;
- $found = file_exists($filename);
- if($found)
- {
- // Add yet another part, with a numeric-appended filename
- $this->archiveList[] = $filename;
+ final protected function setStep($new_step)
+ {
+ $this->active_step = $new_step;
+ }
- $filesize = @filesize($filename);
- $this->totalSize += $filesize;
+ final public function getStep()
+ {
+ return $this->active_step;
+ }
- $privateArchiveList[] = array($filename, $filesize);
- }
- else
- {
- // Add the last part, with the regular extension
- $this->archiveList[] = $this->filename;
+ final protected function setSubstep($new_substep)
+ {
+ $this->active_substep = $new_substep;
+ }
- $filename = $this->filename;
- $filesize = @filesize($filename);
- $this->totalSize += $filesize;
+ final public function getSubstep()
+ {
+ return $this->active_substep;
+ }
- $privateArchiveList[] = array($filename, $filesize);
- }
- }
+ /**
+ * Attaches an observer object
+ * @param AKAbstractPartObserver $obs
+ */
+ function attach(AKAbstractPartObserver $obs) {
+ $this->observers["$obs"] = $obs;
+ }
- $this->currentPartNumber = -1;
- $this->currentPartOffset = 0;
- $this->runState = AK_STATE_NOFILE;
+ /**
+ * Dettaches an observer object
+ * @param AKAbstractPartObserver $obs
+ */
+ function detach(AKAbstractPartObserver $obs) {
+ delete($this->observers["$obs"]);
+ }
- // Send start of file notification
- $message = new stdClass;
- $message->type = 'totalsize';
- $message->content = new stdClass;
- $message->content->totalsize = $this->totalSize;
- $message->content->filelist = $privateArchiveList;
- $this->notify($message);
- }
+ /**
+ * Notifies observers each time something interesting happened to the part
+ * @param mixed $message The event object
+ */
+ protected function notify($message) {
+ foreach ($this->observers as $obs) {
+ $obs->update($this, $message);
+ }
+ }
+}
+
+/**
+ * Akeeba Restore
+ * A JSON-powered JPA, JPS and ZIP archive extraction library
+ *
+ * @copyright 2010-2014 Nicholas K. Dionysopoulos / Akeeba Ltd.
+ * @license GNU GPL v2 or - at your option - any later version
+ * @package akeebabackup
+ * @subpackage kickstart
+ */
+
+/**
+ * The base class of unarchiver classes
+ */
+abstract class AKAbstractUnarchiver extends AKAbstractPart
+{
+ /** @var string Archive filename */
+ protected $filename = null;
+
+ /** @var array List of the names of all archive parts */
+ public $archiveList = array();
+
+ /** @var int The total size of all archive parts */
+ public $totalSize = array();
+
+ /** @var integer Current archive part number */
+ protected $currentPartNumber = -1;
+
+ /** @var integer The offset inside the current part */
+ protected $currentPartOffset = 0;
+
+ /** @var bool Should I restore permissions? */
+ protected $flagRestorePermissions = false;
+
+ /** @var AKAbstractPostproc Post processing class */
+ protected $postProcEngine = null;
+
+ /** @var string Absolute path to prepend to extracted files */
+ protected $addPath = '';
+
+ /** @var array Which files to rename */
+ public $renameFiles = array();
+
+ /** @var array Which directories to rename */
+ public $renameDirs = array();
+
+ /** @var array Which files to skip */
+ public $skipFiles = array();
+
+ /** @var integer Chunk size for processing */
+ protected $chunkSize = 524288;
+
+ /** @var resource File pointer to the current archive part file */
+ protected $fp = null;
+
+ /** @var int Run state when processing the current archive file */
+ protected $runState = null;
+
+ /** @var stdClass File header data, as read by the readFileHeader() method */
+ protected $fileHeader = null;
+
+ /** @var int How much of the uncompressed data we've read so far */
+ protected $dataReadLength = 0;
+
+ /** @var array Unwriteable files in these directories are always ignored and do not cause errors when not extracted */
+ protected $ignoreDirectories = array();
/**
- * Opens the next part file for reading
+ * Public constructor
*/
- protected function nextFile()
+ public function __construct()
{
- ++$this->currentPartNumber;
+ parent::__construct();
+ }
- if( $this->currentPartNumber > (count($this->archiveList) - 1) )
+ /**
+ * Wakeup function, called whenever the class is unserialized
+ */
+ public function __wakeup()
+ {
+ if($this->currentPartNumber >= 0)
{
- $this->setState('postrun');
- return false;
+ $this->fp = @fopen($this->archiveList[$this->currentPartNumber], 'rb');
+ if( (is_resource($this->fp)) && ($this->currentPartOffset > 0) )
+ {
+ @fseek($this->fp, $this->currentPartOffset);
+ }
}
- else
+ }
+
+ /**
+ * Sleep function, called whenever the class is serialized
+ */
+ public function shutdown()
+ {
+ if(is_resource($this->fp))
{
- if( is_resource($this->fp) ) @fclose($this->fp);
- $this->fp = @fopen( $this->archiveList[$this->currentPartNumber], 'rb' );
- fseek($this->fp, 0);
- $this->currentPartOffset = 0;
- return true;
+ $this->currentPartOffset = @ftell($this->fp);
+ @fclose($this->fp);
}
}
/**
- * Returns true if we have reached the end of file
- * @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
- * @return bool True if we have reached End Of File
+ * Implements the abstract _prepare() method
*/
- protected function isEOF($local = false)
+ final protected function _prepare()
{
- $eof = @feof($this->fp);
+ parent::__construct();
- if(!$eof)
+ if( count($this->_parametersArray) > 0 )
{
- // Border case: right at the part's end (eeeek!!!). For the life of me, I don't understand why
- // feof() doesn't report true. It expects the fp to be positioned *beyond* the EOF to report
- // true. Incredible! :(
- $position = @ftell($this->fp);
- $filesize = @filesize( $this->archiveList[$this->currentPartNumber] );
- if( $position >= $filesize ) $eof = true;
+ foreach($this->_parametersArray as $key => $value)
+ {
+ switch($key)
+ {
+ // Archive's absolute filename
+ case 'filename':
+ $this->filename = $value;
+
+ // Sanity check
+ if (!empty($value))
+ {
+ $value = strtolower($value);
+
+ if (strlen($value) > 6)
+ {
+ if (
+ (substr($value, 0, 7) == 'http://')
+ || (substr($value, 0, 8) == 'https://')
+ || (substr($value, 0, 6) == 'ftp://')
+ || (substr($value, 0, 7) == 'ssh2://')
+ || (substr($value, 0, 6) == 'ssl://')
+ )
+ {
+ $this->setState('error', 'Invalid archive location');
+ }
+ }
+ }
+
+
+
+ break;
+
+ // Should I restore permissions?
+ case 'restore_permissions':
+ $this->flagRestorePermissions = $value;
+ break;
+
+ // Should I use FTP?
+ case 'post_proc':
+ $this->postProcEngine = AKFactory::getpostProc($value);
+ break;
+
+ // Path to add in the beginning
+ case 'add_path':
+ $this->addPath = $value;
+ $this->addPath = str_replace('\\','/',$this->addPath);
+ $this->addPath = rtrim($this->addPath,'/');
+ if(!empty($this->addPath)) $this->addPath .= '/';
+ break;
+
+ // Which files to rename (hash array)
+ case 'rename_files':
+ $this->renameFiles = $value;
+ break;
+
+ // Which files to rename (hash array)
+ case 'rename_dirs':
+ $this->renameDirs = $value;
+ break;
+
+ // Which files to skip (indexed array)
+ case 'skip_files':
+ $this->skipFiles = $value;
+ break;
+
+ // Which directories to ignore when we can't write files in them (indexed array)
+ case 'ignoredirectories':
+ $this->ignoreDirectories = $value;
+ break;
+ }
+ }
}
- if($local)
+ $this->scanArchives();
+
+ $this->readArchiveHeader();
+ $errMessage = $this->getError();
+ if(!empty($errMessage))
{
- return $eof;
+ $this->setState('error', $errMessage);
}
else
{
- return $eof && ($this->currentPartNumber >= (count($this->archiveList)-1) );
+ $this->runState = AK_STATE_NOFILE;
+ $this->setState('prepared');
}
}
- /**
- * Tries to make a directory user-writable so that we can write a file to it
- * @param $path string A path to a file
- */
- protected function setCorrectPermissions($path)
+ protected function _run()
{
- static $rootDir = null;
-
- if(is_null($rootDir)) {
- $rootDir = rtrim(AKFactory::get('kickstart.setup.destdir',''),'/\\');
- }
-
- $directory = rtrim(dirname($path),'/\\');
+ if($this->getState() == 'postrun') return;
+
+ $this->setState('running');
+
+ $timer = AKFactory::getTimer();
+
+ $status = true;
+ while( $status && ($timer->getTimeLeft() > 0) )
+ {
+ switch( $this->runState )
+ {
+ case AK_STATE_NOFILE:
+ debugMsg(__CLASS__.'::_run() - Reading file header');
+ $status = $this->readFileHeader();
+ if($status)
+ {
+ debugMsg(__CLASS__.'::_run() - Preparing to extract '.$this->fileHeader->realFile);
+ // Send start of file notification
+ $message = new stdClass;
+ $message->type = 'startfile';
+ $message->content = new stdClass;
+ if( array_key_exists('realfile', get_object_vars($this->fileHeader)) ) {
+ $message->content->realfile = $this->fileHeader->realFile;
+ } else {
+ $message->content->realfile = $this->fileHeader->file;
+ }
+ $message->content->file = $this->fileHeader->file;
+ if( array_key_exists('compressed', get_object_vars($this->fileHeader)) ) {
+ $message->content->compressed = $this->fileHeader->compressed;
+ } else {
+ $message->content->compressed = 0;
+ }
+ $message->content->uncompressed = $this->fileHeader->uncompressed;
+ $this->notify($message);
+ } else {
+ debugMsg(__CLASS__.'::_run() - Could not read file header');
+ }
+ break;
+
+ case AK_STATE_HEADER:
+ case AK_STATE_DATA:
+ debugMsg(__CLASS__.'::_run() - Processing file data');
+ $status = $this->processFileData();
+ break;
+
+ case AK_STATE_DATAREAD:
+ case AK_STATE_POSTPROC:
+ debugMsg(__CLASS__.'::_run() - Calling post-processing class');
+ $this->postProcEngine->timestamp = $this->fileHeader->timestamp;
+ $status = $this->postProcEngine->process();
+ $this->propagateFromObject( $this->postProcEngine );
+ $this->runState = AK_STATE_DONE;
+ break;
+
+ case AK_STATE_DONE:
+ default:
+ if($status)
+ {
+ debugMsg(__CLASS__.'::_run() - Finished extracting file');
+ // Send end of file notification
+ $message = new stdClass;
+ $message->type = 'endfile';
+ $message->content = new stdClass;
+ if( array_key_exists('realfile', get_object_vars($this->fileHeader)) ) {
+ $message->content->realfile = $this->fileHeader->realFile;
+ } else {
+ $message->content->realfile = $this->fileHeader->file;
+ }
+ $message->content->file = $this->fileHeader->file;
+ if( array_key_exists('compressed', get_object_vars($this->fileHeader)) ) {
+ $message->content->compressed = $this->fileHeader->compressed;
+ } else {
+ $message->content->compressed = 0;
+ }
+ $message->content->uncompressed = $this->fileHeader->uncompressed;
+ $this->notify($message);
+ }
+ $this->runState = AK_STATE_NOFILE;
+ continue;
+ }
+ }
+
+ $error = $this->getError();
+ if( !$status && ($this->runState == AK_STATE_NOFILE) && empty( $error ) )
+ {
+ debugMsg(__CLASS__.'::_run() - Just finished');
+ // We just finished
+ $this->setState('postrun');
+ }
+ elseif( !empty($error) )
+ {
+ debugMsg(__CLASS__.'::_run() - Halted with an error:');
+ debugMsg($error);
+ $this->setState( 'error', $error );
+ }
+ }
+
+ protected function _finalize()
+ {
+ // Nothing to do
+ $this->setState('finished');
+ }
+
+ /**
+ * Returns the base extension of the file, e.g. '.jpa'
+ * @return string
+ */
+ private function getBaseExtension()
+ {
+ static $baseextension;
+
+ if(empty($baseextension))
+ {
+ $basename = basename($this->filename);
+ $lastdot = strrpos($basename,'.');
+ $baseextension = substr($basename, $lastdot);
+ }
+
+ return $baseextension;
+ }
+
+ /**
+ * Scans for archive parts
+ */
+ private function scanArchives()
+ {
+ if(defined('KSDEBUG')) {
+ @unlink('debug.txt');
+ }
+ debugMsg('Preparing to scan archives');
+
+ $privateArchiveList = array();
+
+ // Get the components of the archive filename
+ $dirname = dirname($this->filename);
+ $base_extension = $this->getBaseExtension();
+ $basename = basename($this->filename, $base_extension);
+ $this->totalSize = 0;
+
+ // Scan for multiple parts until we don't find any more of them
+ $count = 0;
+ $found = true;
+ $this->archiveList = array();
+ while($found)
+ {
+ ++$count;
+ $extension = substr($base_extension, 0, 2).sprintf('%02d', $count);
+ $filename = $dirname.DIRECTORY_SEPARATOR.$basename.$extension;
+ $found = file_exists($filename);
+ if($found)
+ {
+ debugMsg('- Found archive '.$filename);
+ // Add yet another part, with a numeric-appended filename
+ $this->archiveList[] = $filename;
+
+ $filesize = @filesize($filename);
+ $this->totalSize += $filesize;
+
+ $privateArchiveList[] = array($filename, $filesize);
+ }
+ else
+ {
+ debugMsg('- Found archive '.$this->filename);
+ // Add the last part, with the regular extension
+ $this->archiveList[] = $this->filename;
+
+ $filename = $this->filename;
+ $filesize = @filesize($filename);
+ $this->totalSize += $filesize;
+
+ $privateArchiveList[] = array($filename, $filesize);
+ }
+ }
+ debugMsg('Total archive parts: '.$count);
+
+ $this->currentPartNumber = -1;
+ $this->currentPartOffset = 0;
+ $this->runState = AK_STATE_NOFILE;
+
+ // Send start of file notification
+ $message = new stdClass;
+ $message->type = 'totalsize';
+ $message->content = new stdClass;
+ $message->content->totalsize = $this->totalSize;
+ $message->content->filelist = $privateArchiveList;
+ $this->notify($message);
+ }
+
+ /**
+ * Opens the next part file for reading
+ */
+ protected function nextFile()
+ {
+ debugMsg('Current part is '.$this->currentPartNumber.'; opening the next part');
+ ++$this->currentPartNumber;
+
+ if( $this->currentPartNumber > (count($this->archiveList) - 1) )
+ {
+ $this->setState('postrun');
+ return false;
+ }
+ else
+ {
+ if( is_resource($this->fp) ) @fclose($this->fp);
+ debugMsg('Opening file '.$this->archiveList[$this->currentPartNumber]);
+ $this->fp = @fopen( $this->archiveList[$this->currentPartNumber], 'rb' );
+ if($this->fp === false) {
+ debugMsg('Could not open file - crash imminent');
+ }
+ fseek($this->fp, 0);
+ $this->currentPartOffset = 0;
+ return true;
+ }
+ }
+
+ /**
+ * Returns true if we have reached the end of file
+ * @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
+ * @return bool True if we have reached End Of File
+ */
+ protected function isEOF($local = false)
+ {
+ $eof = @feof($this->fp);
+
+ if(!$eof)
+ {
+ // Border case: right at the part's end (eeeek!!!). For the life of me, I don't understand why
+ // feof() doesn't report true. It expects the fp to be positioned *beyond* the EOF to report
+ // true. Incredible! :(
+ $position = @ftell($this->fp);
+ $filesize = @filesize( $this->archiveList[$this->currentPartNumber] );
+ if($filesize <= 0) {
+ // 2Gb or more files on a 32 bit version of PHP tend to get screwed up. Meh.
+ $eof = false;
+ } elseif( $position >= $filesize ) {
+ $eof = true;
+ }
+ }
+
+ if($local)
+ {
+ return $eof;
+ }
+ else
+ {
+ return $eof && ($this->currentPartNumber >= (count($this->archiveList)-1) );
+ }
+ }
+
+ /**
+ * Tries to make a directory user-writable so that we can write a file to it
+ * @param $path string A path to a file
+ */
+ protected function setCorrectPermissions($path)
+ {
+ static $rootDir = null;
+
+ if(is_null($rootDir)) {
+ $rootDir = rtrim(AKFactory::get('kickstart.setup.destdir',''),'/\\');
+ }
+
+ $directory = rtrim(dirname($path),'/\\');
if($directory != $rootDir) {
// Is this an unwritable directory?
if(!is_writeable($directory)) {
@@ -1626,12 +2073,12 @@ protected function fread($fp, $length = null)
if($length > 0) {
$data = fread($fp, $length);
} else {
- $data = fread($fp);
+ $data = fread($fp, PHP_INT_MAX);
}
}
else
{
- $data = fread($fp);
+ $data = fread($fp, PHP_INT_MAX);
}
if($data === false) $data = '';
@@ -1644,514 +2091,1468 @@ protected function fread($fp, $length = null)
return $data;
}
-}
-/**
- * The superclass of all Akeeba Kickstart parts. The "parts" are intelligent stateful
- * classes which perform a single procedure and have preparation, running and
- * finalization phases. The transition between phases is handled automatically by
- * this superclass' tick() final public method, which should be the ONLY public API
- * exposed to the rest of the Akeeba Engine.
- */
-abstract class AKAbstractPart extends AKAbstractObject
-{
/**
- * Indicates whether this part has finished its initialisation cycle
- * @var boolean
+ * Is this file or directory contained in a directory we've decided to ignore
+ * write errors for? This is useful to let the extraction work despite write
+ * errors in the log, logs and tmp directories which MIGHT be used by the system
+ * on some low quality hosts and Plesk-powered hosts.
+ *
+ * @param string $shortFilename The relative path of the file/directory in the package
+ *
+ * @return boolean True if it belongs in an ignored directory
*/
- protected $isPrepared = false;
-
- /**
- * Indicates whether this part has more work to do (it's in running state)
- * @var boolean
- */
- protected $isRunning = false;
+ public function isIgnoredDirectory($shortFilename)
+ {
+ return false;
+ if (substr($shortFilename, -1) == '/')
+ {
+ $check = substr($shortFilename, 0, -1);
+ }
+ else
+ {
+ $check = dirname($shortFilename);
+ }
- /**
- * Indicates whether this part has finished its finalization cycle
- * @var boolean
- */
- protected $isFinished = false;
+ return in_array($check, $this->ignoreDirectories);
+ }
+}
- /**
- * Indicates whether this part has finished its run cycle
- * @var boolean
- */
- protected $hasRan = false;
+/**
+ * Akeeba Restore
+ * A JSON-powered JPA, JPS and ZIP archive extraction library
+ *
+ * @copyright 2010-2014 Nicholas K. Dionysopoulos / Akeeba Ltd.
+ * @license GNU GPL v2 or - at your option - any later version
+ * @package akeebabackup
+ * @subpackage kickstart
+ */
- /**
- * The name of the engine part (a.k.a. Domain), used in return table
- * generation.
- * @var string
- */
- protected $active_domain = "";
+/**
+ * File post processor engines base class
+ */
+abstract class AKAbstractPostproc extends AKAbstractObject
+{
+ /** @var string The current (real) file path we'll have to process */
+ protected $filename = null;
+
+ /** @var int The requested permissions */
+ protected $perms = 0755;
+
+ /** @var string The temporary file path we gave to the unarchiver engine */
+ protected $tempFilename = null;
+
+ /** @var int The UNIX timestamp of the file's desired modification date */
+ public $timestamp = 0;
/**
- * The step this engine part is in. Used verbatim in return table and
- * should be set by the code in the _run() method.
- * @var string
+ * Processes the current file, e.g. moves it from temp to final location by FTP
*/
- protected $active_step = "";
+ abstract public function process();
/**
- * A more detailed description of the step this engine part is in. Used
- * verbatim in return table and should be set by the code in the _run()
- * method.
- * @var string
+ * The unarchiver tells us the path to the filename it wants to extract and we give it
+ * a different path instead.
+ * @param string $filename The path to the real file
+ * @param int $perms The permissions we need the file to have
+ * @return string The path to the temporary file
*/
- protected $active_substep = "";
+ abstract public function processFilename($filename, $perms = 0755);
/**
- * Any configuration variables, in the form of an array.
- * @var array
+ * Recursively creates a directory if it doesn't exist
+ * @param string $dirName The directory to create
+ * @param int $perms The permissions to give to that directory
*/
- protected $_parametersArray = array();
+ abstract public function createDirRecursive( $dirName, $perms );
+
+ abstract public function chmod( $file, $perms );
+
+ abstract public function unlink( $file );
+
+ abstract public function rmdir( $directory );
+
+ abstract public function rename( $from, $to );
+}
+
+
+/**
+ * Akeeba Restore
+ * A JSON-powered JPA, JPS and ZIP archive extraction library
+ *
+ * @copyright 2010-2014 Nicholas K. Dionysopoulos / Akeeba Ltd.
+ * @license GNU GPL v2 or - at your option - any later version
+ * @package akeebabackup
+ * @subpackage kickstart
+ */
+
+/**
+ * Descendants of this class can be used in the unarchiver's observer methods (attach, detach and notify)
+ * @author Nicholas
+ *
+ */
+abstract class AKAbstractPartObserver
+{
+ abstract public function update($object, $message);
+}
+
+
+/**
+ * Akeeba Restore
+ * A JSON-powered JPA, JPS and ZIP archive extraction library
+ *
+ * @copyright 2010-2014 Nicholas K. Dionysopoulos / Akeeba Ltd.
+ * @license GNU GPL v2 or - at your option - any later version
+ * @package akeebabackup
+ * @subpackage kickstart
+ */
+
+/**
+ * Direct file writer
+ */
+class AKPostprocDirect extends AKAbstractPostproc
+{
+ public function process()
+ {
+ $restorePerms = AKFactory::get('kickstart.setup.restoreperms', false);
+ if($restorePerms)
+ {
+ @chmod($this->filename, $this->perms);
+ }
+ else
+ {
+ if(@is_file($this->filename))
+ {
+ @chmod($this->filename, 0644);
+ }
+ else
+ {
+ @chmod($this->filename, 0755);
+ }
+ }
+ if($this->timestamp > 0)
+ {
+ @touch($this->filename, $this->timestamp);
+ }
+ return true;
+ }
+
+ public function processFilename($filename, $perms = 0755)
+ {
+ $this->perms = $perms;
+ $this->filename = $filename;
+ return $filename;
+ }
+
+ public function createDirRecursive( $dirName, $perms )
+ {
+ if( AKFactory::get('kickstart.setup.dryrun','0') ) return true;
+ if (@mkdir($dirName, 0755, true)) {
+ @chmod($dirName, 0755);
+ return true;
+ }
+
+ $root = AKFactory::get('kickstart.setup.destdir');
+ $root = rtrim(str_replace('\\','/',$root),'/');
+ $dir = rtrim(str_replace('\\','/',$dirName),'/');
+ if(strpos($dir, $root) === 0) {
+ $dir = ltrim(substr($dir, strlen($root)), '/');
+ $root .= '/';
+ } else {
+ $root = '';
+ }
+
+ if(empty($dir)) return true;
+
+ $dirArray = explode('/', $dir);
+ $path = '';
+ foreach( $dirArray as $dir )
+ {
+ $path .= $dir . '/';
+ $ret = is_dir($root.$path) ? true : @mkdir($root.$path);
+ if( !$ret ) {
+ // Is this a file instead of a directory?
+ if(is_file($root.$path) )
+ {
+ @unlink($root.$path);
+ $ret = @mkdir($root.$path);
+ }
+ if( !$ret ) {
+ $this->setError( AKText::sprintf('COULDNT_CREATE_DIR',$path) );
+ return false;
+ }
+ }
+ // Try to set new directory permissions to 0755
+ @chmod($root.$path, $perms);
+ }
+ return true;
+ }
+
+ public function chmod( $file, $perms )
+ {
+ if( AKFactory::get('kickstart.setup.dryrun','0') ) return true;
+
+ return @chmod( $file, $perms );
+ }
+
+ public function unlink( $file )
+ {
+ return @unlink( $file );
+ }
+
+ public function rmdir( $directory )
+ {
+ return @rmdir( $directory );
+ }
+
+ public function rename( $from, $to )
+ {
+ return @rename($from, $to);
+ }
+
+}
+
+/**
+ * Akeeba Restore
+ * A JSON-powered JPA, JPS and ZIP archive extraction library
+ *
+ * @copyright 2010-2014 Nicholas K. Dionysopoulos / Akeeba Ltd.
+ * @license GNU GPL v2 or - at your option - any later version
+ * @package akeebabackup
+ * @subpackage kickstart
+ */
+
+/**
+ * FTP file writer
+ */
+class AKPostprocFTP extends AKAbstractPostproc
+{
+ /** @var bool Should I use FTP over implicit SSL? */
+ public $useSSL = false;
+ /** @var bool use Passive mode? */
+ public $passive = true;
+ /** @var string FTP host name */
+ public $host = '';
+ /** @var int FTP port */
+ public $port = 21;
+ /** @var string FTP user name */
+ public $user = '';
+ /** @var string FTP password */
+ public $pass = '';
+ /** @var string FTP initial directory */
+ public $dir = '';
+ /** @var resource The FTP handle */
+ private $handle = null;
+ /** @var string The temporary directory where the data will be stored */
+ private $tempDir = '';
+
+ public function __construct()
+ {
+ parent::__construct();
+
+ $this->useSSL = AKFactory::get('kickstart.ftp.ssl', false);
+ $this->passive = AKFactory::get('kickstart.ftp.passive', true);
+ $this->host = AKFactory::get('kickstart.ftp.host', '');
+ $this->port = AKFactory::get('kickstart.ftp.port', 21);
+ if(trim($this->port) == '') $this->port = 21;
+ $this->user = AKFactory::get('kickstart.ftp.user', '');
+ $this->pass = AKFactory::get('kickstart.ftp.pass', '');
+ $this->dir = AKFactory::get('kickstart.ftp.dir', '');
+ $this->tempDir = AKFactory::get('kickstart.ftp.tempdir', '');
+
+ $connected = $this->connect();
+
+ if($connected)
+ {
+ if(!empty($this->tempDir))
+ {
+ $tempDir = rtrim($this->tempDir, '/\\').'/';
+ $writable = $this->isDirWritable($tempDir);
+ }
+ else
+ {
+ $tempDir = '';
+ $writable = false;
+ }
+
+ if(!$writable) {
+ // Default temporary directory is the current root
+ $tempDir = KSROOTDIR;
+ if(empty($tempDir))
+ {
+ // Oh, we have no directory reported!
+ $tempDir = '.';
+ }
+ $absoluteDirToHere = $tempDir;
+ $tempDir = rtrim(str_replace('\\','/',$tempDir),'/');
+ if(!empty($tempDir)) $tempDir .= '/';
+ $this->tempDir = $tempDir;
+ // Is this directory writable?
+ $writable = $this->isDirWritable($tempDir);
+ }
+
+ if(!$writable)
+ {
+ // Nope. Let's try creating a temporary directory in the site's root.
+ $tempDir = $absoluteDirToHere.'/kicktemp';
+ $this->createDirRecursive($tempDir, 0777);
+ // Try making it writable...
+ $this->fixPermissions($tempDir);
+ $writable = $this->isDirWritable($tempDir);
+ }
+
+ // Was the new directory writable?
+ if(!$writable)
+ {
+ // Let's see if the user has specified one
+ $userdir = AKFactory::get('kickstart.ftp.tempdir', '');
+ if(!empty($userdir))
+ {
+ // Is it an absolute or a relative directory?
+ $absolute = false;
+ $absolute = $absolute || ( substr($userdir,0,1) == '/' );
+ $absolute = $absolute || ( substr($userdir,1,1) == ':' );
+ $absolute = $absolute || ( substr($userdir,2,1) == ':' );
+ if(!$absolute)
+ {
+ // Make absolute
+ $tempDir = $absoluteDirToHere.$userdir;
+ }
+ else
+ {
+ // it's already absolute
+ $tempDir = $userdir;
+ }
+ // Does the directory exist?
+ if( is_dir($tempDir) )
+ {
+ // Yeah. Is it writable?
+ $writable = $this->isDirWritable($tempDir);
+ }
+ }
+ }
+ $this->tempDir = $tempDir;
+
+ if(!$writable)
+ {
+ // No writable directory found!!!
+ $this->setError(AKText::_('FTP_TEMPDIR_NOT_WRITABLE'));
+ }
+ else
+ {
+ AKFactory::set('kickstart.ftp.tempdir', $tempDir);
+ $this->tempDir = $tempDir;
+ }
+ }
+ }
+
+ function __wakeup()
+ {
+ $this->connect();
+ }
+
+ public function connect()
+ {
+ // Connect to server, using SSL if so required
+ if($this->useSSL) {
+ $this->handle = @ftp_ssl_connect($this->host, $this->port);
+ } else {
+ $this->handle = @ftp_connect($this->host, $this->port);
+ }
+ if($this->handle === false)
+ {
+ $this->setError(AKText::_('WRONG_FTP_HOST'));
+ return false;
+ }
+
+ // Login
+ if(! @ftp_login($this->handle, $this->user, $this->pass))
+ {
+ $this->setError(AKText::_('WRONG_FTP_USER'));
+ @ftp_close($this->handle);
+ return false;
+ }
+
+ // Change to initial directory
+ if(! @ftp_chdir($this->handle, $this->dir))
+ {
+ $this->setError(AKText::_('WRONG_FTP_PATH1'));
+ @ftp_close($this->handle);
+ return false;
+ }
+
+ // Enable passive mode if the user requested it
+ if( $this->passive )
+ {
+ @ftp_pasv($this->handle, true);
+ }
+ else
+ {
+ @ftp_pasv($this->handle, false);
+ }
+
+ // Try to download ourselves
+ $testFilename = defined('KSSELFNAME') ? KSSELFNAME : basename(__FILE__);
+ $tempHandle = fopen('php://temp', 'r+');
+ if (@ftp_fget($this->handle, $tempHandle, $testFilename, FTP_ASCII, 0) === false)
+ {
+ $this->setError(AKText::_('WRONG_FTP_PATH2'));
+ @ftp_close($this->handle);
+ fclose($tempHandle);
+
+ return false;
+ }
+ fclose($tempHandle);
+
+ return true;
+ }
+
+ public function process()
+ {
+ if( is_null($this->tempFilename) )
+ {
+ // If an empty filename is passed, it means that we shouldn't do any post processing, i.e.
+ // the entity was a directory or symlink
+ return true;
+ }
+
+ $remotePath = dirname($this->filename);
+ $removePath = AKFactory::get('kickstart.setup.destdir','');
+ if(!empty($removePath))
+ {
+ $removePath = ltrim($removePath, "/");
+ $remotePath = ltrim($remotePath, "/");
+ $left = substr($remotePath, 0, strlen($removePath));
+ if($left == $removePath)
+ {
+ $remotePath = substr($remotePath, strlen($removePath));
+ }
+ }
+
+ $absoluteFSPath = dirname($this->filename);
+ $relativeFTPPath = trim($remotePath, '/');
+ $absoluteFTPPath = '/'.trim( $this->dir, '/' ).'/'.trim($remotePath, '/');
+ $onlyFilename = basename($this->filename);
+
+ $remoteName = $absoluteFTPPath.'/'.$onlyFilename;
+
+ $ret = @ftp_chdir($this->handle, $absoluteFTPPath);
+ if($ret === false)
+ {
+ $ret = $this->createDirRecursive( $absoluteFSPath, 0755);
+ if($ret === false) {
+ $this->setError(AKText::sprintf('FTP_COULDNT_UPLOAD', $this->filename));
+ return false;
+ }
+ $ret = @ftp_chdir($this->handle, $absoluteFTPPath);
+ if($ret === false) {
+ $this->setError(AKText::sprintf('FTP_COULDNT_UPLOAD', $this->filename));
+ return false;
+ }
+ }
+
+ $ret = @ftp_put($this->handle, $remoteName, $this->tempFilename, FTP_BINARY);
+ if($ret === false)
+ {
+ // If we couldn't create the file, attempt to fix the permissions in the PHP level and retry!
+ $this->fixPermissions($this->filename);
+ $this->unlink($this->filename);
+
+ $fp = @fopen($this->tempFilename, 'rb');
+ if($fp !== false)
+ {
+ $ret = @ftp_fput($this->handle, $remoteName, $fp, FTP_BINARY);
+ @fclose($fp);
+ }
+ else
+ {
+ $ret = false;
+ }
+ }
+ @unlink($this->tempFilename);
+
+ if($ret === false)
+ {
+ $this->setError(AKText::sprintf('FTP_COULDNT_UPLOAD', $this->filename));
+ return false;
+ }
+ $restorePerms = AKFactory::get('kickstart.setup.restoreperms', false);
+ if($restorePerms)
+ {
+ @ftp_chmod($this->_handle, $this->perms, $remoteName);
+ }
+ else
+ {
+ @ftp_chmod($this->_handle, 0644, $remoteName);
+ }
+ return true;
+ }
+
+ public function processFilename($filename, $perms = 0755)
+ {
+ // Catch some error conditions...
+ if($this->getError())
+ {
+ return false;
+ }
+
+ // If a null filename is passed, it means that we shouldn't do any post processing, i.e.
+ // the entity was a directory or symlink
+ if(is_null($filename))
+ {
+ $this->filename = null;
+ $this->tempFilename = null;
+ return null;
+ }
+
+ // Strip absolute filesystem path to website's root
+ $removePath = AKFactory::get('kickstart.setup.destdir','');
+ if(!empty($removePath))
+ {
+ $left = substr($filename, 0, strlen($removePath));
+ if($left == $removePath)
+ {
+ $filename = substr($filename, strlen($removePath));
+ }
+ }
+
+ // Trim slash on the left
+ $filename = ltrim($filename, '/');
+
+ $this->filename = $filename;
+ $this->tempFilename = tempnam($this->tempDir, 'kickstart-');
+ $this->perms = $perms;
+
+ if( empty($this->tempFilename) )
+ {
+ // Oops! Let's try something different
+ $this->tempFilename = $this->tempDir.'/kickstart-'.time().'.dat';
+ }
+
+ return $this->tempFilename;
+ }
+
+ private function isDirWritable($dir)
+ {
+ $fp = @fopen($dir.'/kickstart.dat', 'wb');
+ if($fp === false)
+ {
+ return false;
+ }
+ else
+ {
+ @fclose($fp);
+ unlink($dir.'/kickstart.dat');
+ return true;
+ }
+ }
+
+ public function createDirRecursive( $dirName, $perms )
+ {
+ // Strip absolute filesystem path to website's root
+ $removePath = AKFactory::get('kickstart.setup.destdir','');
+ if(!empty($removePath))
+ {
+ // UNIXize the paths
+ $removePath = str_replace('\\','/',$removePath);
+ $dirName = str_replace('\\','/',$dirName);
+ // Make sure they both end in a slash
+ $removePath = rtrim($removePath,'/\\').'/';
+ $dirName = rtrim($dirName,'/\\').'/';
+ // Process the path removal
+ $left = substr($dirName, 0, strlen($removePath));
+ if($left == $removePath)
+ {
+ $dirName = substr($dirName, strlen($removePath));
+ }
+ }
+ if(empty($dirName)) $dirName = ''; // 'cause the substr() above may return FALSE.
+
+ $check = '/'.trim($this->dir,'/').'/'.trim($dirName, '/');
+ if($this->is_dir($check)) return true;
+
+ $alldirs = explode('/', $dirName);
+ $previousDir = '/'.trim($this->dir);
+ foreach($alldirs as $curdir)
+ {
+ $check = $previousDir.'/'.$curdir;
+ if(!$this->is_dir($check))
+ {
+ // Proactively try to delete a file by the same name
+ @ftp_delete($this->handle, $check);
+
+ if(@ftp_mkdir($this->handle, $check) === false)
+ {
+ // If we couldn't create the directory, attempt to fix the permissions in the PHP level and retry!
+ $this->fixPermissions($removePath.$check);
+ if(@ftp_mkdir($this->handle, $check) === false)
+ {
+ // Can we fall back to pure PHP mode, sire?
+ if(!@mkdir($check))
+ {
+ $this->setError(AKText::sprintf('FTP_CANT_CREATE_DIR', $check));
+ return false;
+ }
+ else
+ {
+ // Since the directory was built by PHP, change its permissions
+ @chmod($check, "0777");
+ return true;
+ }
+ }
+ }
+ @ftp_chmod($this->handle, $perms, $check);
+ }
+ $previousDir = $check;
+ }
+
+ return true;
+ }
+
+ public function close()
+ {
+ @ftp_close($this->handle);
+ }
+
+ /*
+ * Tries to fix directory/file permissions in the PHP level, so that
+ * the FTP operation doesn't fail.
+ * @param $path string The full path to a directory or file
+ */
+ private function fixPermissions( $path )
+ {
+ // Turn off error reporting
+ if(!defined('KSDEBUG')) {
+ $oldErrorReporting = @error_reporting(E_NONE);
+ }
+
+ // Get UNIX style paths
+ $relPath = str_replace('\\','/',$path);
+ $basePath = rtrim(str_replace('\\','/',KSROOTDIR),'/');
+ $basePath = rtrim($basePath,'/');
+ if(!empty($basePath)) $basePath .= '/';
+ // Remove the leading relative root
+ if( substr($relPath,0,strlen($basePath)) == $basePath )
+ $relPath = substr($relPath,strlen($basePath));
+ $dirArray = explode('/', $relPath);
+ $pathBuilt = rtrim($basePath,'/');
+ foreach( $dirArray as $dir )
+ {
+ if(empty($dir)) continue;
+ $oldPath = $pathBuilt;
+ $pathBuilt .= '/'.$dir;
+ if(is_dir($oldPath.$dir))
+ {
+ @chmod($oldPath.$dir, 0777);
+ }
+ else
+ {
+ if(@chmod($oldPath.$dir, 0777) === false)
+ {
+ @unlink($oldPath.$dir);
+ }
+ }
+ }
+
+ // Restore error reporting
+ if(!defined('KSDEBUG')) {
+ @error_reporting($oldErrorReporting);
+ }
+ }
+
+ public function chmod( $file, $perms )
+ {
+ return @ftp_chmod($this->handle, $perms, $file);
+ }
+
+ private function is_dir( $dir )
+ {
+ return @ftp_chdir( $this->handle, $dir );
+ }
+
+ public function unlink( $file )
+ {
+ $removePath = AKFactory::get('kickstart.setup.destdir','');
+ if(!empty($removePath))
+ {
+ $left = substr($file, 0, strlen($removePath));
+ if($left == $removePath)
+ {
+ $file = substr($file, strlen($removePath));
+ }
+ }
+
+ $check = '/'.trim($this->dir,'/').'/'.trim($file, '/');
+
+ return @ftp_delete( $this->handle, $check );
+ }
+
+ public function rmdir( $directory )
+ {
+ $removePath = AKFactory::get('kickstart.setup.destdir','');
+ if(!empty($removePath))
+ {
+ $left = substr($directory, 0, strlen($removePath));
+ if($left == $removePath)
+ {
+ $directory = substr($directory, strlen($removePath));
+ }
+ }
+
+ $check = '/'.trim($this->dir,'/').'/'.trim($directory, '/');
+
+ return @ftp_rmdir( $this->handle, $check );
+ }
+
+ public function rename( $from, $to )
+ {
+ $originalFrom = $from;
+ $originalTo = $to;
+
+ $removePath = AKFactory::get('kickstart.setup.destdir','');
+ if(!empty($removePath))
+ {
+ $left = substr($from, 0, strlen($removePath));
+ if($left == $removePath)
+ {
+ $from = substr($from, strlen($removePath));
+ }
+ }
+ $from = '/'.trim($this->dir,'/').'/'.trim($from, '/');
+
+ if(!empty($removePath))
+ {
+ $left = substr($to, 0, strlen($removePath));
+ if($left == $removePath)
+ {
+ $to = substr($to, strlen($removePath));
+ }
+ }
+ $to = '/'.trim($this->dir,'/').'/'.trim($to, '/');
+
+ $result = @ftp_rename( $this->handle, $from, $to );
+ if($result !== true)
+ {
+ return @rename($from, $to);
+ }
+ else
+ {
+ return true;
+ }
+ }
+
+}
+
+
+/**
+ * Akeeba Restore
+ * A JSON-powered JPA, JPS and ZIP archive extraction library
+ *
+ * @copyright 2010-2014 Nicholas K. Dionysopoulos / Akeeba Ltd.
+ * @license GNU GPL v2 or - at your option - any later version
+ * @package akeebabackup
+ * @subpackage kickstart
+ */
+
+/**
+ * FTP file writer
+ */
+class AKPostprocSFTP extends AKAbstractPostproc
+{
+ /** @var bool Should I use FTP over implicit SSL? */
+ public $useSSL = false;
+ /** @var bool use Passive mode? */
+ public $passive = true;
+ /** @var string FTP host name */
+ public $host = '';
+ /** @var int FTP port */
+ public $port = 21;
+ /** @var string FTP user name */
+ public $user = '';
+ /** @var string FTP password */
+ public $pass = '';
+ /** @var string FTP initial directory */
+ public $dir = '';
+
+ /** @var resource SFTP resource handle */
+ private $handle = null;
+
+ /** @var resource SSH2 connection resource handle */
+ private $_connection = null;
+
+ /** @var string Current remote directory, including the remote directory string */
+ private $_currentdir;
+
+ /** @var string The temporary directory where the data will be stored */
+ private $tempDir = '';
+
+ public function __construct()
+ {
+ parent::__construct();
+
+ $this->host = AKFactory::get('kickstart.ftp.host', '');
+ $this->port = AKFactory::get('kickstart.ftp.port', 22);
+
+ if(trim($this->port) == '') $this->port = 22;
+
+ $this->user = AKFactory::get('kickstart.ftp.user', '');
+ $this->pass = AKFactory::get('kickstart.ftp.pass', '');
+ $this->dir = AKFactory::get('kickstart.ftp.dir', '');
+ $this->tempDir = AKFactory::get('kickstart.ftp.tempdir', '');
+
+ $connected = $this->connect();
+
+ if($connected)
+ {
+ if(!empty($this->tempDir))
+ {
+ $tempDir = rtrim($this->tempDir, '/\\').'/';
+ $writable = $this->isDirWritable($tempDir);
+ }
+ else
+ {
+ $tempDir = '';
+ $writable = false;
+ }
+
+ if(!$writable) {
+ // Default temporary directory is the current root
+ $tempDir = KSROOTDIR;
+ if(empty($tempDir))
+ {
+ // Oh, we have no directory reported!
+ $tempDir = '.';
+ }
+ $absoluteDirToHere = $tempDir;
+ $tempDir = rtrim(str_replace('\\','/',$tempDir),'/');
+ if(!empty($tempDir)) $tempDir .= '/';
+ $this->tempDir = $tempDir;
+ // Is this directory writable?
+ $writable = $this->isDirWritable($tempDir);
+ }
+
+ if(!$writable)
+ {
+ // Nope. Let's try creating a temporary directory in the site's root.
+ $tempDir = $absoluteDirToHere.'/kicktemp';
+ $this->createDirRecursive($tempDir, 0777);
+ // Try making it writable...
+ $this->fixPermissions($tempDir);
+ $writable = $this->isDirWritable($tempDir);
+ }
+
+ // Was the new directory writable?
+ if(!$writable)
+ {
+ // Let's see if the user has specified one
+ $userdir = AKFactory::get('kickstart.ftp.tempdir', '');
+ if(!empty($userdir))
+ {
+ // Is it an absolute or a relative directory?
+ $absolute = false;
+ $absolute = $absolute || ( substr($userdir,0,1) == '/' );
+ $absolute = $absolute || ( substr($userdir,1,1) == ':' );
+ $absolute = $absolute || ( substr($userdir,2,1) == ':' );
+ if(!$absolute)
+ {
+ // Make absolute
+ $tempDir = $absoluteDirToHere.$userdir;
+ }
+ else
+ {
+ // it's already absolute
+ $tempDir = $userdir;
+ }
+ // Does the directory exist?
+ if( is_dir($tempDir) )
+ {
+ // Yeah. Is it writable?
+ $writable = $this->isDirWritable($tempDir);
+ }
+ }
+ }
+ $this->tempDir = $tempDir;
+
+ if(!$writable)
+ {
+ // No writable directory found!!!
+ $this->setError(AKText::_('SFTP_TEMPDIR_NOT_WRITABLE'));
+ }
+ else
+ {
+ AKFactory::set('kickstart.ftp.tempdir', $tempDir);
+ $this->tempDir = $tempDir;
+ }
+ }
+ }
+
+ function __wakeup()
+ {
+ $this->connect();
+ }
+
+ public function connect()
+ {
+ $this->_connection = false;
- /** @var string The database root key */
- protected $databaseRoot = array();
+ if(!function_exists('ssh2_connect'))
+ {
+ $this->setError(AKText::_('SFTP_NO_SSH2'));
+ return false;
+ }
- /** @var int Last reported warnings's position in array */
- private $warnings_pointer = -1;
+ $this->_connection = @ssh2_connect($this->host, $this->port);
- /** @var array An array of observers */
- protected $observers = array();
+ if (!@ssh2_auth_password($this->_connection, $this->user, $this->pass))
+ {
+ $this->setError(AKText::_('SFTP_WRONG_USER'));
- /**
- * Runs the preparation for this part. Should set _isPrepared
- * to true
- */
- abstract protected function _prepare();
+ $this->_connection = false;
- /**
- * Runs the finalisation process for this part. Should set
- * _isFinished to true.
- */
- abstract protected function _finalize();
+ return false;
+ }
- /**
- * Runs the main functionality loop for this part. Upon calling,
- * should set the _isRunning to true. When it finished, should set
- * the _hasRan to true. If an error is encountered, setError should
- * be used.
- */
- abstract protected function _run();
+ $this->handle = @ssh2_sftp($this->_connection);
- /**
- * Sets the BREAKFLAG, which instructs this engine part that the current step must break immediately,
- * in fear of timing out.
- */
- protected function setBreakFlag()
- {
- AKFactory::set('volatile.breakflag', true);
- }
+ // I must have an absolute directory
+ if(!$this->dir)
+ {
+ $this->setError(AKText::_('SFTP_WRONG_STARTING_DIR'));
+ return false;
+ }
- /**
- * Sets the engine part's internal state, in an easy to use manner
- *
- * @param string $state One of init, prepared, running, postrun, finished, error
- * @param string $errorMessage The reported error message, should the state be set to error
- */
- protected function setState($state = 'init', $errorMessage='Invalid setState argument')
- {
- switch($state)
- {
- case 'init':
- $this->isPrepared = false;
- $this->isRunning = false;
- $this->isFinished = false;
- $this->hasRun = false;
- break;
+ // Change to initial directory
+ if(!$this->sftp_chdir('/'))
+ {
+ $this->setError(AKText::_('SFTP_WRONG_STARTING_DIR'));
- case 'prepared':
- $this->isPrepared = true;
- $this->isRunning = false;
- $this->isFinished = false;
- $this->hasRun = false;
- break;
+ unset($this->_connection);
+ unset($this->handle);
- case 'running':
- $this->isPrepared = true;
- $this->isRunning = true;
- $this->isFinished = false;
- $this->hasRun = false;
- break;
+ return false;
+ }
- case 'postrun':
- $this->isPrepared = true;
- $this->isRunning = false;
- $this->isFinished = false;
- $this->hasRun = true;
- break;
+ // Try to download ourselves
+ $testFilename = defined('KSSELFNAME') ? KSSELFNAME : basename(__FILE__);
+ $basePath = '/'.trim($this->dir, '/');
- case 'finished':
- $this->isPrepared = true;
- $this->isRunning = false;
- $this->isFinished = true;
- $this->hasRun = false;
- break;
+ if(@fopen("ssh2.sftp://{$this->handle}$basePath/$testFilename",'r+') === false)
+ {
+ $this->setError(AKText::_('SFTP_WRONG_STARTING_DIR'));
- case 'error':
- default:
- $this->setError($errorMessage);
- break;
- }
+ unset($this->_connection);
+ unset($this->handle);
+
+ return false;
+ }
+
+ return true;
}
- /**
- * The public interface to an engine part. This method takes care for
- * calling the correct method in order to perform the initialisation -
- * run - finalisation cycle of operation and return a proper reponse array.
- * @return array A Reponse Array
- */
- final public function tick()
+ public function process()
{
- // Call the right action method, depending on engine part state
- switch( $this->getState() )
+ if( is_null($this->tempFilename) )
{
- case "init":
- $this->_prepare();
- break;
- case "prepared":
- $this->_run();
- break;
- case "running":
- $this->_run();
- break;
- case "postrun":
- $this->_finalize();
- break;
+ // If an empty filename is passed, it means that we shouldn't do any post processing, i.e.
+ // the entity was a directory or symlink
+ return true;
}
- // Send a Return Table back to the caller
- $out = $this->_makeReturnTable();
- return $out;
- }
+ $remotePath = dirname($this->filename);
+ $absoluteFSPath = dirname($this->filename);
+ $absoluteFTPPath = '/'.trim( $this->dir, '/' ).'/'.trim($remotePath, '/');
+ $onlyFilename = basename($this->filename);
- /**
- * Returns a copy of the class's status array
- * @return array
- */
- public function getStatusArray()
- {
- return $this->_makeReturnTable();
- }
+ $remoteName = $absoluteFTPPath.'/'.$onlyFilename;
- /**
- * Sends any kind of setup information to the engine part. Using this,
- * we avoid passing parameters to the constructor of the class. These
- * parameters should be passed as an indexed array and should be taken
- * into account during the preparation process only. This function will
- * set the error flag if it's called after the engine part is prepared.
- *
- * @param array $parametersArray The parameters to be passed to the
- * engine part.
- */
- final public function setup( $parametersArray )
- {
- if( $this->isPrepared )
- {
- $this->setState('error', "Can't modify configuration after the preparation of " . $this->active_domain);
- }
- else
+ $ret = $this->sftp_chdir($absoluteFTPPath);
+
+ if($ret === false)
{
- $this->_parametersArray = $parametersArray;
- if(array_key_exists('root', $parametersArray))
- {
- $this->databaseRoot = $parametersArray['root'];
+ $ret = $this->createDirRecursive( $absoluteFSPath, 0755);
+
+ if($ret === false)
+ {
+ $this->setError(AKText::sprintf('SFTP_COULDNT_UPLOAD', $this->filename));
+ return false;
}
- }
- }
- /**
- * Returns the state of this engine part.
- *
- * @return string The state of this engine part. It can be one of
- * error, init, prepared, running, postrun, finished.
- */
- final public function getState()
- {
- if( $this->getError() )
- {
- return "error";
- }
+ $ret = $this->sftp_chdir($absoluteFTPPath);
- if( !($this->isPrepared) )
- {
- return "init";
+ if($ret === false)
+ {
+ $this->setError(AKText::sprintf('SFTP_COULDNT_UPLOAD', $this->filename));
+ return false;
+ }
}
- if( !($this->isFinished) && !($this->isRunning) && !( $this->hasRun ) && ($this->isPrepared) )
+ // Create the file
+ $ret = $this->write($this->tempFilename, $remoteName);
+
+ // If I got a -1 it means that I wasn't able to open the file, so I have to stop here
+ if($ret === -1)
+ {
+ $this->setError(AKText::sprintf('SFTP_COULDNT_UPLOAD', $this->filename));
+ return false;
+ }
+
+ if($ret === false)
{
- return "prepared";
+ // If we couldn't create the file, attempt to fix the permissions in the PHP level and retry!
+ $this->fixPermissions($this->filename);
+ $this->unlink($this->filename);
+
+ $ret = $this->write($this->tempFilename, $remoteName);
}
- if ( !($this->isFinished) && $this->isRunning && !( $this->hasRun ) )
+ @unlink($this->tempFilename);
+
+ if($ret === false)
{
- return "running";
+ $this->setError(AKText::sprintf('SFTP_COULDNT_UPLOAD', $this->filename));
+ return false;
}
+ $restorePerms = AKFactory::get('kickstart.setup.restoreperms', false);
- if ( !($this->isFinished) && !($this->isRunning) && $this->hasRun )
+ if($restorePerms)
{
- return "postrun";
+ $this->chmod($remoteName, $this->perms);
}
-
- if ( $this->isFinished )
+ else
{
- return "finished";
+ $this->chmod($remoteName, 0644);
}
+ return true;
}
- /**
- * Constructs a Response Array based on the engine part's state.
- * @return array The Response Array for the current state
- */
- final protected function _makeReturnTable()
+ public function processFilename($filename, $perms = 0755)
{
- // Get a list of warnings
- $warnings = $this->getWarnings();
- // Report only new warnings if there is no warnings queue size
- if( $this->_warnings_queue_size == 0 )
+ // Catch some error conditions...
+ if($this->getError())
{
- if( ($this->warnings_pointer > 0) && ($this->warnings_pointer < (count($warnings)) ) )
- {
- $warnings = array_slice($warnings, $this->warnings_pointer + 1);
- $this->warnings_pointer += count($warnings);
- }
- else
- {
- $this->warnings_pointer = count($warnings);
- }
+ return false;
}
- $out = array(
- 'HasRun' => (!($this->isFinished)),
- 'Domain' => $this->active_domain,
- 'Step' => $this->active_step,
- 'Substep' => $this->active_substep,
- 'Error' => $this->getError(),
- 'Warnings' => $warnings
- );
-
- return $out;
- }
+ // If a null filename is passed, it means that we shouldn't do any post processing, i.e.
+ // the entity was a directory or symlink
+ if(is_null($filename))
+ {
+ $this->filename = null;
+ $this->tempFilename = null;
+ return null;
+ }
- final protected function setDomain($new_domain)
- {
- $this->active_domain = $new_domain;
- }
+ // Strip absolute filesystem path to website's root
+ $removePath = AKFactory::get('kickstart.setup.destdir','');
+ if(!empty($removePath))
+ {
+ $left = substr($filename, 0, strlen($removePath));
+ if($left == $removePath)
+ {
+ $filename = substr($filename, strlen($removePath));
+ }
+ }
- final public function getDomain()
- {
- return $this->active_domain;
- }
+ // Trim slash on the left
+ $filename = ltrim($filename, '/');
- final protected function setStep($new_step)
- {
- $this->active_step = $new_step;
- }
+ $this->filename = $filename;
+ $this->tempFilename = tempnam($this->tempDir, 'kickstart-');
+ $this->perms = $perms;
- final public function getStep()
- {
- return $this->active_step;
- }
+ if( empty($this->tempFilename) )
+ {
+ // Oops! Let's try something different
+ $this->tempFilename = $this->tempDir.'/kickstart-'.time().'.dat';
+ }
- final protected function setSubstep($new_substep)
- {
- $this->active_substep = $new_substep;
+ return $this->tempFilename;
}
- final public function getSubstep()
+ private function isDirWritable($dir)
{
- return $this->active_substep;
+ if(@fopen("ssh2.sftp://{$this->handle}$dir/kickstart.dat",'wb') === false)
+ {
+ return false;
+ }
+ else
+ {
+ @ssh2_sftp_unlink($this->handle, $dir.'/kickstart.dat');
+ return true;
+ }
}
- /**
- * Attaches an observer object
- * @param AKAbstractPartObserver $obs
- */
- function attach(AKAbstractPartObserver $obs) {
- $this->observers["$obs"] = $obs;
- }
+ public function createDirRecursive( $dirName, $perms )
+ {
+ // Strip absolute filesystem path to website's root
+ $removePath = AKFactory::get('kickstart.setup.destdir','');
+ if(!empty($removePath))
+ {
+ // UNIXize the paths
+ $removePath = str_replace('\\','/',$removePath);
+ $dirName = str_replace('\\','/',$dirName);
+ // Make sure they both end in a slash
+ $removePath = rtrim($removePath,'/\\').'/';
+ $dirName = rtrim($dirName,'/\\').'/';
+ // Process the path removal
+ $left = substr($dirName, 0, strlen($removePath));
+ if($left == $removePath)
+ {
+ $dirName = substr($dirName, strlen($removePath));
+ }
+ }
+ if(empty($dirName)) $dirName = ''; // 'cause the substr() above may return FALSE.
- /**
- * Dettaches an observer object
- * @param AKAbstractPartObserver $obs
- */
- function detach(AKAbstractPartObserver $obs) {
- delete($this->observers["$obs"]);
- }
+ $check = '/'.trim($this->dir,'/ ').'/'.trim($dirName, '/');
- /**
- * Notifies observers each time something interesting happened to the part
- * @param mixed $message The event object
- */
- protected function notify($message) {
- foreach ($this->observers as $obs) {
- $obs->update($this, $message);
+ if($this->is_dir($check))
+ {
+ return true;
}
- }
-}
-/**
- * Descendants of this class can be used in the unarchiver's observer methods (attach, detach and notify)
- * @author Nicholas
- *
- */
-abstract class AKAbstractPartObserver
-{
- abstract public function update($object, $message);
-}
+ $alldirs = explode('/', $dirName);
+ $previousDir = '/'.trim($this->dir, '/ ');
-/**
- * Direct file writer
- */
-class AKPostprocDirect extends AKAbstractPostproc
-{
- public function process()
- {
- $restorePerms = AKFactory::get('kickstart.setup.restoreperms', false);
- if($restorePerms)
- {
- @chmod($this->filename, $this->perms);
- }
- else
+ foreach($alldirs as $curdir)
{
- if(@is_file($this->filename))
- {
- @chmod($this->filename, 0644);
- }
- else
+ if(!$curdir)
+ {
+ continue;
+ }
+
+ $check = $previousDir.'/'.$curdir;
+
+ if(!$this->is_dir($check))
{
- @chmod($this->filename, 0755);
+ // Proactively try to delete a file by the same name
+ @ssh2_sftp_unlink($this->handle, $check);
+
+ if(@ssh2_sftp_mkdir($this->handle, $check) === false)
+ {
+ // If we couldn't create the directory, attempt to fix the permissions in the PHP level and retry!
+ $this->fixPermissions($check);
+
+ if(@ssh2_sftp_mkdir($this->handle, $check) === false)
+ {
+ // Can we fall back to pure PHP mode, sire?
+ if(!@mkdir($check))
+ {
+ $this->setError(AKText::sprintf('FTP_CANT_CREATE_DIR', $check));
+ return false;
+ }
+ else
+ {
+ // Since the directory was built by PHP, change its permissions
+ @chmod($check, "0777");
+ return true;
+ }
+ }
+ }
+
+ @ssh2_sftp_chmod($this->handle, $check, $perms);
}
+
+ $previousDir = $check;
}
- if($this->timestamp > 0)
- {
- @touch($this->filename, $this->timestamp);
- }
+
return true;
}
- public function processFilename($filename, $perms = 0755)
+ public function close()
{
- $this->perms = $perms;
- $this->filename = $filename;
- return $filename;
+ unset($this->_connection);
+ unset($this->handle);
}
- public function createDirRecursive( $dirName, $perms )
+ /*
+ * Tries to fix directory/file permissions in the PHP level, so that
+ * the FTP operation doesn't fail.
+ * @param $path string The full path to a directory or file
+ */
+ private function fixPermissions( $path )
{
- if( AKFactory::get('kickstart.setup.dryrun','0') ) return true;
- if (@mkdir($dirName, 0755, true)) {
- @chmod($dirName, 0755);
- return true;
+ // Turn off error reporting
+ if(!defined('KSDEBUG')) {
+ $oldErrorReporting = @error_reporting(E_NONE);
}
- $root = AKFactory::get('kickstart.setup.destdir');
- $root = rtrim(str_replace('\\','/',$root),'/');
- $dir = rtrim(str_replace('\\','/',$dirName),'/');
- if(strpos($dir, $root) === 0) {
- $dir = ltrim(substr($dir, strlen($root)), '/');
- $root .= '/';
- } else {
- $root = '';
- }
-
- if(empty($dir)) return true;
+ // Get UNIX style paths
+ $relPath = str_replace('\\','/',$path);
+ $basePath = rtrim(str_replace('\\','/',KSROOTDIR),'/');
+ $basePath = rtrim($basePath,'/');
+
+ if(!empty($basePath))
+ {
+ $basePath .= '/';
+ }
+
+ // Remove the leading relative root
+ if( substr($relPath,0,strlen($basePath)) == $basePath )
+ {
+ $relPath = substr($relPath,strlen($basePath));
+ }
+
+ $dirArray = explode('/', $relPath);
+ $pathBuilt = rtrim($basePath,'/');
- $dirArray = explode('/', $dir);
- $path = '';
foreach( $dirArray as $dir )
{
- $path .= $dir . '/';
- $ret = is_dir($root.$path) ? true : @mkdir($root.$path);
- if( !$ret ) {
- // Is this a file instead of a directory?
- if(is_file($root.$path) )
+ if(empty($dir))
+ {
+ continue;
+ }
+
+ $oldPath = $pathBuilt;
+ $pathBuilt .= '/'.$dir;
+
+ if(is_dir($oldPath.'/'.$dir))
+ {
+ @chmod($oldPath.'/'.$dir, 0777);
+ }
+ else
+ {
+ if(@chmod($oldPath.'/'.$dir, 0777) === false)
{
- @unlink($root.$path);
- $ret = @mkdir($root.$path);
- }
- if( !$ret ) {
- $this->setError( AKText::sprintf('COULDNT_CREATE_DIR',$path) );
- return false;
+ @unlink($oldPath.$dir);
}
}
- // Try to set new directory permissions to 0755
- @chmod($root.$path, $perms);
}
- return true;
+
+ // Restore error reporting
+ if(!defined('KSDEBUG')) {
+ @error_reporting($oldErrorReporting);
+ }
}
public function chmod( $file, $perms )
{
- if( AKFactory::get('kickstart.setup.dryrun','0') ) return true;
+ return @ssh2_sftp_chmod($this->handle, $file, $perms);
+ }
- return @chmod( $file, $perms );
+ private function is_dir( $dir )
+ {
+ return $this->sftp_chdir($dir);
}
+ private function write($local, $remote)
+ {
+ $fp = @fopen("ssh2.sftp://{$this->handle}$remote",'w');
+ $localfp = @fopen($local,'rb');
+
+ if($fp === false)
+ {
+ return -1;
+ }
+
+ if($localfp === false)
+ {
+ @fclose($fp);
+ return -1;
+ }
+
+ $res = true;
+
+ while(!feof($localfp) && ($res !== false))
+ {
+ $buffer = @fread($localfp, 65567);
+ $res = @fwrite($fp, $buffer);
+ }
+
+ @fclose($fp);
+ @fclose($localfp);
+
+ return $res;
+ }
+
public function unlink( $file )
{
- return @unlink( $file );
+ $check = '/'.trim($this->dir,'/').'/'.trim($file, '/');
+
+ return @ssh2_sftp_unlink($this->handle, $check);
}
public function rmdir( $directory )
{
- return @rmdir( $directory );
+ $check = '/'.trim($this->dir,'/').'/'.trim($directory, '/');
+
+ return @ssh2_sftp_rmdir( $this->handle, $check);
}
public function rename( $from, $to )
{
- return @rename($from, $to);
+ $from = '/'.trim($this->dir,'/').'/'.trim($from, '/');
+ $to = '/'.trim($this->dir,'/').'/'.trim($to, '/');
+
+ $result = @ssh2_sftp_rename($this->handle, $from, $to);
+
+ if($result !== true)
+ {
+ return @rename($from, $to);
+ }
+ else
+ {
+ return true;
+ }
}
+ /**
+ * Changes to the requested directory in the remote server. You give only the
+ * path relative to the initial directory and it does all the rest by itself,
+ * including doing nothing if the remote directory is the one we want.
+ *
+ * @param string $dir The (realtive) remote directory
+ *
+ * @return bool True if successful, false otherwise.
+ */
+ private function sftp_chdir($dir)
+ {
+ // Strip absolute filesystem path to website's root
+ $removePath = AKFactory::get('kickstart.setup.destdir','');
+ if(!empty($removePath))
+ {
+ // UNIXize the paths
+ $removePath = str_replace('\\','/',$removePath);
+ $dir = str_replace('\\','/',$dir);
+
+ // Make sure they both end in a slash
+ $removePath = rtrim($removePath,'/\\').'/';
+ $dir = rtrim($dir,'/\\').'/';
+
+ // Process the path removal
+ $left = substr($dir, 0, strlen($removePath));
+
+ if($left == $removePath)
+ {
+ $dir = substr($dir, strlen($removePath));
+ }
+ }
+
+ if(empty($dir))
+ {
+ // Because the substr() above may return FALSE.
+ $dir = '';
+ }
+
+ // Calculate "real" (absolute) SFTP path
+ $realdir = substr($this->dir, -1) == '/' ? substr($this->dir, 0, strlen($this->dir) - 1) : $this->dir;
+ $realdir .= '/'.$dir;
+ $realdir = substr($realdir, 0, 1) == '/' ? $realdir : '/'.$realdir;
+
+ if($this->_currentdir == $realdir)
+ {
+ // Already there, do nothing
+ return true;
+ }
+
+ $result = @ssh2_sftp_stat($this->handle, $realdir);
+
+ if($result === false)
+ {
+ return false;
+ }
+ else
+ {
+ // Update the private "current remote directory" variable
+ $this->_currentdir = $realdir;
+
+ return true;
+ }
+ }
+
}
+
/**
- * FTP file writer
+ * Akeeba Restore
+ * A JSON-powered JPA, JPS and ZIP archive extraction library
+ *
+ * @copyright 2010-2014 Nicholas K. Dionysopoulos / Akeeba Ltd.
+ * @license GNU GPL v2 or - at your option - any later version
+ * @package akeebabackup
+ * @subpackage kickstart
*/
-class AKPostprocFTP extends AKAbstractPostproc
+
+/**
+ * Hybrid direct / FTP mode file writer
+ */
+class AKPostprocHybrid extends AKAbstractPostproc
{
+
+ /** @var bool Should I use the FTP layer? */
+ public $useFTP = false;
+
/** @var bool Should I use FTP over implicit SSL? */
public $useSSL = false;
+
/** @var bool use Passive mode? */
public $passive = true;
+
/** @var string FTP host name */
public $host = '';
+
/** @var int FTP port */
public $port = 21;
+
/** @var string FTP user name */
public $user = '';
+
/** @var string FTP password */
public $pass = '';
+
/** @var string FTP initial directory */
public $dir = '';
+
/** @var resource The FTP handle */
private $handle = null;
+
/** @var string The temporary directory where the data will be stored */
private $tempDir = '';
+ /** @var null The FTP connection handle */
+ private $_handle = null;
+
+ /**
+ * Public constructor. Tries to connect to the FTP server.
+ */
public function __construct()
{
parent::__construct();
+ $this->useFTP = true;
$this->useSSL = AKFactory::get('kickstart.ftp.ssl', false);
$this->passive = AKFactory::get('kickstart.ftp.passive', true);
$this->host = AKFactory::get('kickstart.ftp.host', '');
$this->port = AKFactory::get('kickstart.ftp.port', 21);
- if(trim($this->port) == '') $this->port = 21;
$this->user = AKFactory::get('kickstart.ftp.user', '');
$this->pass = AKFactory::get('kickstart.ftp.pass', '');
$this->dir = AKFactory::get('kickstart.ftp.dir', '');
$this->tempDir = AKFactory::get('kickstart.ftp.tempdir', '');
+ if (trim($this->port) == '')
+ {
+ $this->port = 21;
+ }
+
+ // If FTP is not configured, skip it altogether
+ if (empty($this->host) || empty($this->user) || empty($this->pass))
+ {
+ $this->useFTP = false;
+ }
+
+ // Try to connect to the FTP server
$connected = $this->connect();
- if($connected)
+ // If the connection fails, skip FTP altogether
+ if (!$connected)
{
- if(!empty($this->tempDir))
+ $this->useFTP = false;
+ }
+
+ if ($connected)
+ {
+ if (!empty($this->tempDir))
{
- $tempDir = rtrim($this->tempDir, '/\\').'/';
+ $tempDir = rtrim($this->tempDir, '/\\') . '/';
$writable = $this->isDirWritable($tempDir);
}
else
@@ -2160,26 +3561,30 @@ public function __construct()
$writable = false;
}
- if(!$writable) {
+ if (!$writable)
+ {
// Default temporary directory is the current root
- $tempDir = function_exists('getcwd') ? getcwd() : dirname(__FILE__);
- if(empty($tempDir))
+ $tempDir = KSROOTDIR;
+ if (empty($tempDir))
{
// Oh, we have no directory reported!
$tempDir = '.';
}
$absoluteDirToHere = $tempDir;
- $tempDir = rtrim(str_replace('\\','/',$tempDir),'/');
- if(!empty($tempDir)) $tempDir .= '/';
+ $tempDir = rtrim(str_replace('\\', '/', $tempDir), '/');
+ if (!empty($tempDir))
+ {
+ $tempDir .= '/';
+ }
$this->tempDir = $tempDir;
// Is this directory writable?
$writable = $this->isDirWritable($tempDir);
}
- if(!$writable)
+ if (!$writable)
{
// Nope. Let's try creating a temporary directory in the site's root.
- $tempDir = $absoluteDirToHere.'/kicktemp';
+ $tempDir = $absoluteDirToHere . '/kicktemp';
$this->createDirRecursive($tempDir, 0777);
// Try making it writable...
$this->fixPermissions($tempDir);
@@ -2187,21 +3592,21 @@ public function __construct()
}
// Was the new directory writable?
- if(!$writable)
+ if (!$writable)
{
// Let's see if the user has specified one
$userdir = AKFactory::get('kickstart.ftp.tempdir', '');
- if(!empty($userdir))
+ if (!empty($userdir))
{
// Is it an absolute or a relative directory?
$absolute = false;
- $absolute = $absolute || ( substr($userdir,0,1) == '/' );
- $absolute = $absolute || ( substr($userdir,1,1) == ':' );
- $absolute = $absolute || ( substr($userdir,2,1) == ':' );
- if(!$absolute)
+ $absolute = $absolute || (substr($userdir, 0, 1) == '/');
+ $absolute = $absolute || (substr($userdir, 1, 1) == ':');
+ $absolute = $absolute || (substr($userdir, 2, 1) == ':');
+ if (!$absolute)
{
// Make absolute
- $tempDir = $absoluteDirToHere.$userdir;
+ $tempDir = $absoluteDirToHere . $userdir;
}
else
{
@@ -2209,7 +3614,7 @@ public function __construct()
$tempDir = $userdir;
}
// Does the directory exist?
- if( is_dir($tempDir) )
+ if (is_dir($tempDir))
{
// Yeah. Is it writable?
$writable = $this->isDirWritable($tempDir);
@@ -2218,7 +3623,7 @@ public function __construct()
}
$this->tempDir = $tempDir;
- if(!$writable)
+ if (!$writable)
{
// No writable directory found!!!
$this->setError(AKText::_('FTP_TEMPDIR_NOT_WRITABLE'));
@@ -2231,43 +3636,73 @@ public function __construct()
}
}
+ /**
+ * Called after unserialisation, tries to reconnect to FTP
+ */
function __wakeup()
{
- $this->connect();
+ if ($this->useFTP)
+ {
+ $this->connect();
+ }
+ }
+
+ function __destruct()
+ {
+ if (!$this->useFTP)
+ {
+ @ftp_close($this->handle);
+ }
}
+ /**
+ * Tries to connect to the FTP server
+ *
+ * @return bool
+ */
public function connect()
{
+ if (!$this->useFTP)
+ {
+ return false;
+ }
+
// Connect to server, using SSL if so required
- if($this->useSSL) {
+ if ($this->useSSL)
+ {
$this->handle = @ftp_ssl_connect($this->host, $this->port);
- } else {
+ }
+ else
+ {
$this->handle = @ftp_connect($this->host, $this->port);
}
- if($this->handle === false)
+ if ($this->handle === false)
{
$this->setError(AKText::_('WRONG_FTP_HOST'));
+
return false;
}
// Login
- if(! @ftp_login($this->handle, $this->user, $this->pass))
+ if (!@ftp_login($this->handle, $this->user, $this->pass))
{
$this->setError(AKText::_('WRONG_FTP_USER'));
@ftp_close($this->handle);
+
return false;
}
// Change to initial directory
- if(! @ftp_chdir($this->handle, $this->dir))
+ if (!@ftp_chdir($this->handle, $this->dir))
{
$this->setError(AKText::_('WRONG_FTP_PATH1'));
@ftp_close($this->handle);
+
return false;
}
// Enable passive mode if the user requested it
- if( $this->passive )
+ if ($this->passive)
{
@ftp_pasv($this->handle, true);
}
@@ -2276,12 +3711,32 @@ public function connect()
@ftp_pasv($this->handle, false);
}
+ // Try to download ourselves
+ $testFilename = defined('KSSELFNAME') ? KSSELFNAME : basename(__FILE__);
+ $tempHandle = fopen('php://temp', 'r+');
+
+ if (@ftp_fget($this->handle, $tempHandle, $testFilename, FTP_ASCII, 0) === false)
+ {
+ $this->setError(AKText::_('WRONG_FTP_PATH2'));
+ @ftp_close($this->handle);
+ fclose($tempHandle);
+
+ return false;
+ }
+
+ fclose($tempHandle);
+
return true;
}
+ /**
+ * Post-process an extracted file, using FTP or direct file writes to move it
+ *
+ * @return bool
+ */
public function process()
{
- if( is_null($this->tempFilename) )
+ if (is_null($this->tempFilename))
{
// If an empty filename is passed, it means that we shouldn't do any post processing, i.e.
// the entity was a directory or symlink
@@ -2289,13 +3744,16 @@ public function process()
}
$remotePath = dirname($this->filename);
- $removePath = AKFactory::get('kickstart.setup.destdir','');
- if(!empty($removePath))
+ $removePath = AKFactory::get('kickstart.setup.destdir', '');
+ $root = rtrim($removePath, '/\\');
+
+ if (!empty($removePath))
{
$removePath = ltrim($removePath, "/");
$remotePath = ltrim($remotePath, "/");
$left = substr($remotePath, 0, strlen($removePath));
- if($left == $removePath)
+
+ if ($left == $removePath)
{
$remotePath = substr($remotePath, strlen($removePath));
}
@@ -2303,86 +3761,124 @@ public function process()
$absoluteFSPath = dirname($this->filename);
$relativeFTPPath = trim($remotePath, '/');
- $absoluteFTPPath = '/'.trim( $this->dir, '/' ).'/'.trim($remotePath, '/');
+ $absoluteFTPPath = '/' . trim($this->dir, '/') . '/' . trim($remotePath, '/');
$onlyFilename = basename($this->filename);
- $remoteName = $absoluteFTPPath.'/'.$onlyFilename;
+ $remoteName = $absoluteFTPPath . '/' . $onlyFilename;
- $ret = @ftp_chdir($this->handle, $absoluteFTPPath);
- if($ret === false)
+ // Does the directory exist?
+ if (!is_dir($root . '/' . $absoluteFSPath))
{
- $ret = $this->createDirRecursive( $absoluteFSPath, 0755);
- if($ret === false) {
- $this->setError(AKText::sprintf('FTP_COULDNT_UPLOAD', $this->filename));
- return false;
+ $ret = $this->createDirRecursive($absoluteFSPath, 0755);
+
+ if (($ret === false) && ($this->useFTP))
+ {
+ $ret = @ftp_chdir($this->handle, $absoluteFTPPath);
}
- $ret = @ftp_chdir($this->handle, $absoluteFTPPath);
- if($ret === false) {
+
+ if ($ret === false)
+ {
$this->setError(AKText::sprintf('FTP_COULDNT_UPLOAD', $this->filename));
+
return false;
}
}
- $ret = @ftp_put($this->handle, $remoteName, $this->tempFilename, FTP_BINARY);
- if($ret === false)
+ if ($this->useFTP)
+ {
+ $ret = @ftp_chdir($this->handle, $absoluteFTPPath);
+ }
+
+ // Try copying directly
+ $ret = @copy($this->tempFilename, $root . '/' . $this->filename);
+
+ if ($ret === false)
{
- // If we couldn't create the file, attempt to fix the permissions in the PHP level and retry!
$this->fixPermissions($this->filename);
$this->unlink($this->filename);
- $fp = @fopen($this->tempFilename);
- if($fp !== false)
- {
- $ret = @ftp_fput($this->handle, $remoteName, $fp, FTP_BINARY);
- @fclose($fp);
- }
- else
+ $ret = @copy($this->tempFilename, $root . '/' . $this->filename);
+ }
+
+ if ($this->useFTP && ($ret === false))
+ {
+ $ret = @ftp_put($this->handle, $remoteName, $this->tempFilename, FTP_BINARY);
+
+ if ($ret === false)
{
- $ret = false;
+ // If we couldn't create the file, attempt to fix the permissions in the PHP level and retry!
+ $this->fixPermissions($this->filename);
+ $this->unlink($this->filename);
+
+ $fp = @fopen($this->tempFilename, 'rb');
+ if ($fp !== false)
+ {
+ $ret = @ftp_fput($this->handle, $remoteName, $fp, FTP_BINARY);
+ @fclose($fp);
+ }
+ else
+ {
+ $ret = false;
+ }
}
}
+
@unlink($this->tempFilename);
- if($ret === false)
+ if ($ret === false)
{
$this->setError(AKText::sprintf('FTP_COULDNT_UPLOAD', $this->filename));
+
return false;
}
+
$restorePerms = AKFactory::get('kickstart.setup.restoreperms', false);
- if($restorePerms)
+ $perms = $restorePerms ? $this->perms : 0644;
+
+ $ret = @chmod($root . '/' . $this->filename, $perms);
+
+ if ($this->useFTP && ($ret === false))
{
@ftp_chmod($this->_handle, $perms, $remoteName);
}
- else
- {
- @ftp_chmod($this->_handle, 0644, $remoteName);
- }
+
return true;
}
+ /**
+ * Create a temporary filename
+ *
+ * @param string $filename The original filename
+ * @param int $perms The file permissions
+ *
+ * @return string
+ */
public function processFilename($filename, $perms = 0755)
{
// Catch some error conditions...
- if($this->getError())
+ if ($this->getError())
{
return false;
}
// If a null filename is passed, it means that we shouldn't do any post processing, i.e.
// the entity was a directory or symlink
- if(is_null($filename))
+ if (is_null($filename))
{
$this->filename = null;
$this->tempFilename = null;
+
return null;
}
// Strip absolute filesystem path to website's root
- $removePath = AKFactory::get('kickstart.setup.destdir','');
- if(!empty($removePath))
+ $removePath = AKFactory::get('kickstart.setup.destdir', '');
+
+ if (!empty($removePath))
{
$left = substr($filename, 0, strlen($removePath));
- if($left == $removePath)
+
+ if ($left == $removePath)
{
$filename = substr($filename, strlen($removePath));
}
@@ -2395,243 +3891,371 @@ public function processFilename($filename, $perms = 0755)
$this->tempFilename = tempnam($this->tempDir, 'kickstart-');
$this->perms = $perms;
- if( empty($this->tempFilename) )
+ if (empty($this->tempFilename))
{
// Oops! Let's try something different
- $this->tempFilename = $this->tempDir.'/kickstart-'.time().'.dat';
+ $this->tempFilename = $this->tempDir . '/kickstart-' . time() . '.dat';
}
return $this->tempFilename;
}
+ /**
+ * Is the directory writeable?
+ *
+ * @param string $dir The directory ti check
+ *
+ * @return bool
+ */
private function isDirWritable($dir)
{
- $fp = @fopen($dir.'/kickstart.dat', 'wb');
- if($fp === false)
+ $fp = @fopen($dir . '/kickstart.dat', 'wb');
+
+ if ($fp === false)
{
return false;
}
- else
- {
- @fclose($fp);
- unlink($dir.'/kickstart.dat');
- return true;
- }
+
+ @fclose($fp);
+ unlink($dir . '/kickstart.dat');
+
+ return true;
}
- public function createDirRecursive( $dirName, $perms )
+ /**
+ * Create a directory, recursively
+ *
+ * @param string $dirName The directory to create
+ * @param int $perms The permissions to give to the directory
+ *
+ * @return bool
+ */
+ public function createDirRecursive($dirName, $perms)
{
// Strip absolute filesystem path to website's root
- $removePath = AKFactory::get('kickstart.setup.destdir','');
- if(!empty($removePath))
+ $removePath = AKFactory::get('kickstart.setup.destdir', '');
+
+ if (!empty($removePath))
{
// UNIXize the paths
- $removePath = str_replace('\\','/',$removePath);
- $dirName = str_replace('\\','/',$dirName);
+ $removePath = str_replace('\\', '/', $removePath);
+ $dirName = str_replace('\\', '/', $dirName);
// Make sure they both end in a slash
- $removePath = rtrim($removePath,'/\\').'/';
- $dirName = rtrim($dirName,'/\\').'/';
+ $removePath = rtrim($removePath, '/\\') . '/';
+ $dirName = rtrim($dirName, '/\\') . '/';
// Process the path removal
$left = substr($dirName, 0, strlen($removePath));
- if($left == $removePath)
+
+ if ($left == $removePath)
{
$dirName = substr($dirName, strlen($removePath));
}
}
- if(empty($dirName)) $dirName = ''; // 'cause the substr() above may return FALSE.
-
- $check = '/'.trim($this->dir,'/').'/'.trim($dirName, '/');
- if($this->is_dir($check)) return true;
+
+ // 'cause the substr() above may return FALSE.
+ if (empty($dirName))
+ {
+ $dirName = '';
+ }
+
+ $check = '/' . trim($this->dir, '/') . '/' . trim($dirName, '/');
+ $checkFS = $removePath . trim($dirName, '/');
+
+ if ($this->is_dir($check))
+ {
+ return true;
+ }
$alldirs = explode('/', $dirName);
- $previousDir = '/'.trim($this->dir);
- foreach($alldirs as $curdir)
+ $previousDir = '/' . trim($this->dir);
+ $previousDirFS = rtrim($removePath, '/\\');
+
+ foreach ($alldirs as $curdir)
{
- $check = $previousDir.'/'.$curdir;
- if(!$this->is_dir($check))
+ $check = $previousDir . '/' . $curdir;
+ $checkFS = $previousDirFS . '/' . $curdir;
+
+ if (!is_dir($checkFS) && !$this->is_dir($check))
{
// Proactively try to delete a file by the same name
- @ftp_delete($this->handle, $check);
+ if (!@unlink($checkFS) && $this->useFTP)
+ {
+ @ftp_delete($this->handle, $check);
+ }
- if(@ftp_mkdir($this->handle, $check) === false)
+ $createdDir = @mkdir($checkFS, 0755);
+
+ if (!$createdDir && $this->useFTP)
+ {
+ $createdDir = @ftp_mkdir($this->handle, $check);
+ }
+
+ if ($createdDir === false)
{
// If we couldn't create the directory, attempt to fix the permissions in the PHP level and retry!
- $this->fixPermissions($removePath.$check);
- if(@ftp_mkdir($this->handle, $check) === false)
+ $this->fixPermissions($checkFS);
+
+ $createdDir = @mkdir($checkFS, 0755);
+ if (!$createdDir && $this->useFTP)
{
- // Can we fall back to pure PHP mode, sire?
- if(!@mkdir($check))
- {
- $this->setError(AKText::sprintf('FTP_CANT_CREATE_DIR',$dir));
- return false;
- }
- else
- {
- // Since the directory was built by PHP, change its permissions
- @chmod($check, "0777");
- return true;
- }
+ $createdDir = @ftp_mkdir($this->handle, $check);
+ }
+
+ if ($createdDir === false)
+ {
+ $this->setError(AKText::sprintf('FTP_CANT_CREATE_DIR', $check));
+
+ return false;
}
}
- @ftp_chmod($this->handle, $perms, $check);
+
+ if (!@chmod($checkFS, $perms) && $this->useFTP)
+ {
+ @ftp_chmod($this->handle, $perms, $check);
+ }
}
+
$previousDir = $check;
+ $previousDirFS = $checkFS;
}
return true;
}
+ /**
+ * Closes the FTP connection
+ */
public function close()
{
- @ftp_close($this->handle);
+ if (!$this->useFTP)
+ {
+ @ftp_close($this->handle);
+ }
}
- /*
+ /**
* Tries to fix directory/file permissions in the PHP level, so that
* the FTP operation doesn't fail.
+ *
* @param $path string The full path to a directory or file
*/
- private function fixPermissions( $path )
+ private function fixPermissions($path)
{
// Turn off error reporting
- if(!defined('KSDEBUG')) {
+ if (!defined('KSDEBUG'))
+ {
$oldErrorReporting = @error_reporting(E_NONE);
}
- // Get UNIX style paths
- $relPath = str_replace('\\','/',$path);
- $basePath = rtrim(str_replace('\\','/',dirname(__FILE__)),'/');
- $basePath = rtrim($basePath,'/');
- if(!empty($basePath)) $basePath .= '/';
+ // Get UNIX style paths
+ $relPath = str_replace('\\', '/', $path);
+ $basePath = rtrim(str_replace('\\', '/', KSROOTDIR), '/');
+ $basePath = rtrim($basePath, '/');
+
+ if (!empty($basePath))
+ {
+ $basePath .= '/';
+ }
+
// Remove the leading relative root
- if( substr($relPath,0,strlen($basePath)) == $basePath )
- $relPath = substr($relPath,strlen($basePath));
+ if (substr($relPath, 0, strlen($basePath)) == $basePath)
+ {
+ $relPath = substr($relPath, strlen($basePath));
+ }
+
$dirArray = explode('/', $relPath);
- $pathBuilt = rtrim($basePath,'/');
- foreach( $dirArray as $dir )
+ $pathBuilt = rtrim($basePath, '/');
+
+ foreach ($dirArray as $dir)
{
- if(empty($dir)) continue;
+ if (empty($dir))
+ {
+ continue;
+ }
+
$oldPath = $pathBuilt;
- $pathBuilt .= '/'.$dir;
- if(is_dir($oldPath.$dir))
+ $pathBuilt .= '/' . $dir;
+
+ if (is_dir($oldPath . $dir))
{
- @chmod($oldPath.$dir, 0777);
+ @chmod($oldPath . $dir, 0777);
}
else
{
- if(@chmod($oldPath.$dir, 0777) === false)
+ if (@chmod($oldPath . $dir, 0777) === false)
{
- @unlink($oldPath.$dir);
+ @unlink($oldPath . $dir);
}
}
}
// Restore error reporting
- if(!defined('KSDEBUG')) {
+ if (!defined('KSDEBUG'))
+ {
@error_reporting($oldErrorReporting);
}
}
- public function chmod( $file, $perms )
+ public function chmod($file, $perms)
{
- return @ftp_chmod($this->handle, $perms, $path);
+ if (AKFactory::get('kickstart.setup.dryrun', '0'))
+ {
+ return true;
+ }
+
+ $ret = @chmod($file, $perms);
+
+ if (!$ret && $this->useFTP)
+ {
+ // Strip absolute filesystem path to website's root
+ $removePath = AKFactory::get('kickstart.setup.destdir', '');
+
+ if (!empty($removePath))
+ {
+ $left = substr($file, 0, strlen($removePath));
+
+ if ($left == $removePath)
+ {
+ $file = substr($file, strlen($removePath));
+ }
+ }
+
+ // Trim slash on the left
+ $file = ltrim($file, '/');
+
+ $ret = @ftp_chmod($this->handle, $perms, $file);
+ }
+
+ return $ret;
}
- private function is_dir( $dir )
+ private function is_dir($dir)
{
- return @ftp_chdir( $this->handle, $dir );
+ if ($this->useFTP)
+ {
+ return @ftp_chdir($this->handle, $dir);
+ }
+
+ return false;
}
- public function unlink( $file )
+ public function unlink($file)
{
- $removePath = AKFactory::get('kickstart.setup.destdir','');
- if(!empty($removePath))
+ $ret = @unlink($file);
+
+ if (!$ret && $this->useFTP)
{
- $left = substr($file, 0, strlen($removePath));
- if($left == $removePath)
+ $removePath = AKFactory::get('kickstart.setup.destdir', '');
+ if (!empty($removePath))
{
- $file = substr($file, strlen($removePath));
+ $left = substr($file, 0, strlen($removePath));
+ if ($left == $removePath)
+ {
+ $file = substr($file, strlen($removePath));
+ }
}
- }
- $check = '/'.trim($this->dir,'/').'/'.trim($file, '/');
+ $check = '/' . trim($this->dir, '/') . '/' . trim($file, '/');
- return @ftp_delete( $this->handle, $check );
+ $ret = @ftp_delete($this->handle, $check);
+ }
+
+ return $ret;
}
- public function rmdir( $directory )
+ public function rmdir($directory)
{
- $removePath = AKFactory::get('kickstart.setup.destdir','');
- if(!empty($removePath))
+ $ret = @rmdir($directory);
+
+ if (!$ret && $this->useFTP)
{
- $left = substr($directory, 0, strlen($removePath));
- if($left == $removePath)
+ $removePath = AKFactory::get('kickstart.setup.destdir', '');
+ if (!empty($removePath))
{
- $directory = substr($directory, strlen($removePath));
+ $left = substr($directory, 0, strlen($removePath));
+ if ($left == $removePath)
+ {
+ $directory = substr($directory, strlen($removePath));
+ }
}
- }
- $check = '/'.trim($this->dir,'/').'/'.trim($directory, '/');
+ $check = '/' . trim($this->dir, '/') . '/' . trim($directory, '/');
- return @ftp_rmdir( $this->handle, $check );
+ $ret = @ftp_rmdir($this->handle, $check);
+ }
+
+ return $ret;
}
- public function rename( $from, $to )
+ public function rename($from, $to)
{
- $originalFrom = $from;
- $originalTo = $to;
+ $ret = @rename($from, $to);
- $removePath = AKFactory::get('kickstart.setup.destdir','');
- if(!empty($removePath))
+ if (!$ret && $this->useFTP)
{
- $left = substr($from, 0, strlen($removePath));
- if($left == $removePath)
+ $originalFrom = $from;
+ $originalTo = $to;
+
+ $removePath = AKFactory::get('kickstart.setup.destdir', '');
+ if (!empty($removePath))
{
- $from = substr($from, strlen($removePath));
+ $left = substr($from, 0, strlen($removePath));
+ if ($left == $removePath)
+ {
+ $from = substr($from, strlen($removePath));
+ }
}
- }
- $from = '/'.trim($this->dir,'/').'/'.trim($from, '/');
+ $from = '/' . trim($this->dir, '/') . '/' . trim($from, '/');
- if(!empty($removePath))
- {
- $left = substr($to, 0, strlen($removePath));
- if($left == $removePath)
+ if (!empty($removePath))
{
- $to = substr($to, strlen($removePath));
+ $left = substr($to, 0, strlen($removePath));
+ if ($left == $removePath)
+ {
+ $to = substr($to, strlen($removePath));
+ }
}
- }
- $to = '/'.trim($this->dir,'/').'/'.trim($to, '/');
+ $to = '/' . trim($this->dir, '/') . '/' . trim($to, '/');
- $result = @ftp_rename( $this->handle, $from, $to );
- if($result !== true)
- {
- return @rename($from, $to);
- }
- else
- {
- return true;
+ $ret = @ftp_rename($this->handle, $from, $to);
}
- }
+ return $ret;
+ }
}
+/**
+ * Akeeba Restore
+ * A JSON-powered JPA, JPS and ZIP archive extraction library
+ *
+ * @copyright 2010-2014 Nicholas K. Dionysopoulos / Akeeba Ltd.
+ * @license GNU GPL v2 or - at your option - any later version
+ * @package akeebabackup
+ * @subpackage kickstart
+ */
+
/**
* JPA archive extraction class
*/
class AKUnarchiverJPA extends AKAbstractUnarchiver
{
- private $archiveHeaderData = array();
+ protected $archiveHeaderData = array();
protected function readArchiveHeader()
{
+ debugMsg('Preparing to read archive header');
// Initialize header data array
$this->archiveHeaderData = new stdClass();
// Open the first part
+ debugMsg('Opening the first part');
$this->nextFile();
// Fail for unreadable files
- if( $this->fp === false ) return false;
+ if( $this->fp === false ) {
+ debugMsg('Could not open the first part');
+ return false;
+ }
// Read the signature
$sig = fread( $this->fp, 3 );
@@ -2639,6 +4263,7 @@ protected function readArchiveHeader()
if ($sig != 'JPA')
{
// Not a JPA file
+ debugMsg('Invalid archive signature');
$this->setError( AKText::_('ERR_NOT_A_JPA_FILE') );
return false;
}
@@ -2675,6 +4300,14 @@ protected function readArchiveHeader()
$this->archiveHeaderData->{$key} = $value;
}
+ debugMsg('Header data:');
+ debugMsg('Length : '.$header_length);
+ debugMsg('Major : '.$header_data['major']);
+ debugMsg('Minor : '.$header_data['minor']);
+ debugMsg('File count : '.$header_data['count']);
+ debugMsg('Uncompressed size : '.$header_data['uncsize']);
+ debugMsg('Compressed size : '.$header_data['csize']);
+
$this->currentPartOffset = @ftell($this->fp);
$this->dataReadLength = 0;
@@ -2690,9 +4323,11 @@ protected function readFileHeader()
{
// If the current part is over, proceed to the next part please
if( $this->isEOF(true) ) {
+ debugMsg('Archive part EOF; moving to next file');
$this->nextFile();
}
+ debugMsg('Reading file signature');
// Get and decode Entity Description Block
$signature = fread($this->fp, 3);
@@ -2708,6 +4343,7 @@ protected function readFileHeader()
$this->nextFile();
if(!$this->isEOF(false))
{
+ debugMsg('Invalid file signature before end of archive encountered');
$this->setError(AKText::sprintf('INVALID_FILE_HEADER', $this->currentPartNumber, $this->currentPartOffset));
return false;
}
@@ -2716,9 +4352,22 @@ protected function readFileHeader()
}
else
{
- // This is not a file block! The archive is corrupt.
- $this->setError(AKText::sprintf('INVALID_FILE_HEADER', $this->currentPartNumber, $this->currentPartOffset));
- return false;
+ $screwed = true;
+ if(AKFactory::get('kickstart.setup.ignoreerrors', false)) {
+ debugMsg('Invalid file block signature; launching heuristic file block signature scanner');
+ $screwed = !$this->heuristicFileHeaderLocator();
+ if(!$screwed) {
+ $signature = 'JPF';
+ } else {
+ debugMsg('Heuristics failed. Brace yourself for the imminent crash.');
+ }
+ }
+ if($screwed) {
+ debugMsg('Invalid file block signature');
+ // This is not a file block! The archive is corrupt.
+ $this->setError(AKText::sprintf('INVALID_FILE_HEADER', $this->currentPartNumber, $this->currentPartOffset));
+ return false;
+ }
}
}
// This a JPA Entity Block. Process the header.
@@ -2744,7 +4393,7 @@ protected function readFileHeader()
$isRenamed = true;
}
}
-
+
// Handle directory renaming
$isDirRenamed = false;
if(is_array($this->renameDirs) && (count($this->renameDirs) > 0)) {
@@ -2847,6 +4496,7 @@ protected function readFileHeader()
// If we have a banned file, let's skip it
if($isBannedFile)
{
+ debugMsg('Skipping file '.$this->fileHeader->file);
// Advance the file pointer, skipping exactly the size of the compressed data
$seekleft = $this->fileHeader->compressed;
while($seekleft > 0)
@@ -2948,6 +4598,10 @@ protected function processFileData()
}
break;
+
+ default:
+ debugMsg('Unknown file type '.$this->fileHeader->type);
+ break;
}
}
@@ -2963,7 +4617,7 @@ private function processTypeFileUncompressed()
// Open the output file
if( !AKFactory::get('kickstart.setup.dryrun','0') )
{
- $ignore = AKFactory::get('kickstart.setup.ignoreerrors', false);
+ $ignore = AKFactory::get('kickstart.setup.ignoreerrors', false) || $this->isIgnoredDirectory($this->fileHeader->file);
if ($this->dataReadLength == 0) {
$outfp = @fopen( $this->fileHeader->realFile, 'wb' );
} else {
@@ -2973,6 +4627,7 @@ private function processTypeFileUncompressed()
// Can we write to the file?
if( ($outfp === false) && (!$ignore) ) {
// An error occured
+ debugMsg('Could not write to output file');
$this->setError( AKText::sprintf('COULDNT_WRITE_FILE', $this->fileHeader->realFile) );
return false;
}
@@ -3012,6 +4667,7 @@ private function processTypeFileUncompressed()
else
{
// Nope. The archive is corrupt
+ debugMsg('Not enough data in file. The archive is truncated or corrupt.');
$this->setError( AKText::_('ERR_CORRUPT_ARCHIVE') );
return false;
}
@@ -3050,9 +4706,10 @@ private function processTypeFileCompressedSimple()
$outfp = @fopen( $this->fileHeader->realFile, 'wb' );
// Can we write to the file?
- $ignore = AKFactory::get('kickstart.setup.ignoreerrors', false);
+ $ignore = AKFactory::get('kickstart.setup.ignoreerrors', false) || $this->isIgnoredDirectory($this->fileHeader->file);
if( ($outfp === false) && (!$ignore) ) {
// An error occured
+ debugMsg('Could not write to output file');
$this->setError( AKText::sprintf('COULDNT_WRITE_FILE', $this->fileHeader->realFile) );
return false;
}
@@ -3082,6 +4739,7 @@ private function processTypeFileCompressedSimple()
}
else
{
+ debugMsg('End of local file before reading all data with no more parts left. The archive is corrupt or truncated.');
$this->setError( AKText::_('ERR_CORRUPT_ARCHIVE') );
return false;
}
@@ -3137,6 +4795,7 @@ private function processTypeLink()
}
else
{
+ debugMsg('End of local file before reading all data with no more parts left. The archive is corrupt or truncated.');
// Nope. The archive is corrupt
$this->setError( AKText::_('ERR_CORRUPT_ARCHIVE') );
return false;
@@ -3179,8 +4838,8 @@ protected function createDirectory()
if(empty($this->fileHeader->realFile)) $this->fileHeader->realFile = $this->fileHeader->file;
$lastSlash = strrpos($this->fileHeader->realFile, '/');
$dirName = substr( $this->fileHeader->realFile, 0, $lastSlash);
- $perms = $this->flagRestorePermissions ? $retArray['permissions'] : 0755;
- $ignore = AKFactory::get('kickstart.setup.ignoreerrors', false);
+ $perms = $this->flagRestorePermissions ? $this->fileHeader->permissions : 0755;
+ $ignore = AKFactory::get('kickstart.setup.ignoreerrors', false) || $this->isIgnoredDirectory($dirName);
if( ($this->postProcEngine->createDirRecursive($dirName, $perms) == false) && (!$ignore) ) {
$this->setError( AKText::sprintf('COULDNT_CREATE_DIR', $dirName) );
return false;
@@ -3190,8 +4849,53 @@ protected function createDirectory()
return true;
}
}
+
+ protected function heuristicFileHeaderLocator()
+ {
+ $ret = false;
+ $fullEOF = false;
+
+ while(!$ret && !$fullEOF) {
+ $this->currentPartOffset = @ftell($this->fp);
+ if($this->isEOF(true)) {
+ $this->nextFile();
+ }
+
+ if($this->isEOF(false)) {
+ $fullEOF = true;
+ continue;
+ }
+
+ // Read 512Kb
+ $chunk = fread($this->fp, 524288);
+ $size_read = mb_strlen($chunk,'8bit');
+ //$pos = strpos($chunk, 'JPF');
+ $pos = mb_strpos($chunk, 'JPF', 0, '8bit');
+ if($pos !== false) {
+ // We found it!
+ $this->currentPartOffset += $pos + 3;
+ @fseek($this->fp, $this->currentPartOffset, SEEK_SET);
+ $ret = true;
+ } else {
+ // Not yet found :(
+ $this->currentPartOffset = @ftell($this->fp);
+ }
+ }
+
+ return $ret;
+ }
}
+/**
+ * Akeeba Restore
+ * A JSON-powered JPA, JPS and ZIP archive extraction library
+ *
+ * @copyright 2010-2014 Nicholas K. Dionysopoulos / Akeeba Ltd.
+ * @license GNU GPL v2 or - at your option - any later version
+ * @package akeebabackup
+ * @subpackage kickstart
+ */
+
/**
* ZIP archive extraction class
*
@@ -3206,21 +4910,31 @@ class AKUnarchiverZIP extends AKUnarchiverJPA
protected function readArchiveHeader()
{
+ debugMsg('Preparing to read archive header');
// Initialize header data array
$this->archiveHeaderData = new stdClass();
// Open the first part
+ debugMsg('Opening the first part');
$this->nextFile();
// Fail for unreadable files
- if( $this->fp === false ) return false;
+ if( $this->fp === false ) {
+ debugMsg('The first part is not readable');
+ return false;
+ }
// Read a possible multipart signature
$sigBinary = fread( $this->fp, 4 );
$headerData = unpack('Vsig', $sigBinary);
// Roll back if it's not a multipart archive
- if( $headerData['sig'] == 0x04034b50 ) fseek($this->fp, -4, SEEK_CUR);
+ if( $headerData['sig'] == 0x04034b50 ) {
+ debugMsg('The archive is not multipart');
+ fseek($this->fp, -4, SEEK_CUR);
+ } else {
+ debugMsg('The archive is multipart');
+ }
$multiPartSigs = array(
0x08074b50, // Multi-part ZIP
@@ -3229,11 +4943,13 @@ protected function readArchiveHeader()
);
if( !in_array($headerData['sig'], $multiPartSigs) )
{
+ debugMsg('Invalid header signature '.dechex($headerData['sig']));
$this->setError(AKText::_('ERR_CORRUPT_ARCHIVE'));
return false;
}
$this->currentPartOffset = @ftell($this->fp);
+ debugMsg('Current part offset after reading header: '.$this->currentPartOffset);
$this->dataReadLength = 0;
@@ -3248,6 +4964,7 @@ protected function readFileHeader()
{
// If the current part is over, proceed to the next part please
if( $this->isEOF(true) ) {
+ debugMsg('Opening next archive part');
$this->nextFile();
}
@@ -3261,23 +4978,17 @@ protected function readFileHeader()
if($junk['sig'] == 0x08074b50) {
// Yes, there was a signature
$junk = @fread($this->fp, 12);
- if(defined('KSDEBUG')) {
- debugMsg('Data descriptor (w/ header) skipped at '.(ftell($this->fp)-12));
- }
+ debugMsg('Data descriptor (w/ header) skipped at '.(ftell($this->fp)-12));
} else {
// No, there was no signature, just read another 8 bytes
$junk = @fread($this->fp, 8);
- if(defined('KSDEBUG')) {
- debugMsg('Data descriptor (w/out header) skipped at '.(ftell($this->fp)-8));
- }
+ debugMsg('Data descriptor (w/out header) skipped at '.(ftell($this->fp)-8));
}
// And check for EOF, too
if( $this->isEOF(true) ) {
- if(defined('KSDEBUG')) {
- debugMsg('EOF before reading header');
- }
-
+ debugMsg('EOF before reading header');
+
$this->nextFile();
}
}
@@ -3289,16 +5000,12 @@ protected function readFileHeader()
// Check signature
if(!( $headerData['sig'] == 0x04034b50 ))
{
- if(defined('KSDEBUG')) {
- debugMsg('Not a file signature at '.(ftell($this->fp)-4));
- }
-
+ debugMsg('Not a file signature at '.(ftell($this->fp)-4));
+
// The signature is not the one used for files. Is this a central directory record (i.e. we're done)?
if($headerData['sig'] == 0x02014b50)
{
- if(defined('KSDEBUG')) {
- debugMsg('EOCD signature at '.(ftell($this->fp)-4));
- }
+ debugMsg('EOCD signature at '.(ftell($this->fp)-4));
// End of ZIP file detected. We'll just skip to the end of file...
while( $this->nextFile() ) {};
@fseek($this->fp, 0, SEEK_END); // Go to EOF
@@ -3306,9 +5013,7 @@ protected function readFileHeader()
}
else
{
- if(defined('KSDEBUG')) {
- debugMsg( 'Invalid signature ' . dechex($headerData['sig']) . ' at '.ftell($this->fp) );
- }
+ debugMsg( 'Invalid signature ' . dechex($headerData['sig']) . ' at '.ftell($this->fp) );
$this->setError(AKText::_('ERR_CORRUPT_ARCHIVE'));
return false;
}
@@ -3323,23 +5028,23 @@ protected function readFileHeader()
// Read the last modified data and time
$lastmodtime = $headerData['lastmodtime'];
$lastmoddate = $headerData['lastmoddate'];
-
+
if($lastmoddate && $lastmodtime)
{
// ----- Extract time
$v_hour = ($lastmodtime & 0xF800) >> 11;
$v_minute = ($lastmodtime & 0x07E0) >> 5;
$v_seconde = ($lastmodtime & 0x001F)*2;
-
+
// ----- Extract date
$v_year = (($lastmoddate & 0xFE00) >> 9) + 1980;
$v_month = ($lastmoddate & 0x01E0) >> 5;
$v_day = $lastmoddate & 0x001F;
-
+
// ----- Get UNIX date format
$this->fileHeader->timestamp = @mktime($v_hour, $v_minute, $v_seconde, $v_month, $v_day, $v_year);
}
-
+
$isBannedFile = false;
$this->fileHeader->compressed = $headerData['compsize'];
@@ -3360,12 +5065,12 @@ protected function readFileHeader()
$isRenamed = true;
}
}
-
+
// Handle directory renaming
$isDirRenamed = false;
if(is_array($this->renameDirs) && (count($this->renameDirs) > 0)) {
- if(array_key_exists(dirname($file), $this->renameDirs)) {
- $file = rtrim($this->renameDirs[dirname($file)],'/').'/'.basename($file);
+ if(array_key_exists(dirname($this->fileHeader->file), $this->renameDirs)) {
+ $file = rtrim($this->renameDirs[dirname($this->fileHeader->file)],'/').'/'.basename($this->fileHeader->file);
$isRenamed = true;
$isDirRenamed = true;
}
@@ -3375,11 +5080,9 @@ protected function readFileHeader()
if($extraFieldLength > 0) {
$extrafield = fread( $this->fp, $extraFieldLength );
}
-
- if(defined('KSDEBUG')) {
- debugMsg( '*'.ftell($this->fp).' IS START OF '.$this->fileHeader->file. ' ('.$this->fileHeader->compressed.' bytes)' );
- }
-
+
+ debugMsg( '*'.ftell($this->fp).' IS START OF '.$this->fileHeader->file. ' ('.$this->fileHeader->compressed.' bytes)' );
+
// Decide filetype -- Check for directories
$this->fileHeader->type = 'file';
@@ -3424,230 +5127,71 @@ protected function readFileHeader()
$curPos = @ftell($this->fp);
$canSeek = $curSize - $curPos;
if($canSeek > $seekleft) $canSeek = $seekleft;
- @fseek( $this->fp, $canSeek, SEEK_CUR );
- $seekleft -= $canSeek;
- if($seekleft) $this->nextFile();
- }
-
- $this->currentPartOffset = @ftell($this->fp);
- $this->runState = AK_STATE_DONE;
- return true;
- }
-
- // Last chance to prepend a path to the filename
- if(!empty($this->addPath) && !$isDirRenamed)
- {
- $this->fileHeader->file = $this->addPath.$this->fileHeader->file;
- }
-
- // Get the translated path name
- if($this->fileHeader->type == 'file')
- {
- $this->fileHeader->realFile = $this->postProcEngine->processFilename( $this->fileHeader->file );
- }
- elseif($this->fileHeader->type == 'dir')
- {
- $this->fileHeader->timestamp = 0;
-
- $dir = $this->fileHeader->file;
-
- $this->postProcEngine->createDirRecursive( $this->fileHeader->file, 0755 );
- $this->postProcEngine->processFilename(null);
- }
- else
- {
- // Symlink; do not post-process
- $this->fileHeader->timestamp = 0;
- $this->postProcEngine->processFilename(null);
- }
-
- $this->createDirectory();
-
- // Header is read
- $this->runState = AK_STATE_HEADER;
-
- return true;
- }
-
-}
-
-/**
- * Timer class
- */
-class AKCoreTimer extends AKAbstractObject
-{
- /** @var int Maximum execution time allowance per step */
- private $max_exec_time = null;
-
- /** @var int Timestamp of execution start */
- private $start_time = null;
-
- /**
- * Public constructor, creates the timer object and calculates the execution time limits
- * @return AECoreTimer
- */
- public function __construct()
- {
- parent::__construct();
-
- // Initialize start time
- $this->start_time = $this->microtime_float();
-
- // Get configured max time per step and bias
- $config_max_exec_time = AKFactory::get('kickstart.tuning.max_exec_time', 14);
- $bias = AKFactory::get('kickstart.tuning.run_time_bias', 75)/100;
-
- // Get PHP's maximum execution time (our upper limit)
- if(@function_exists('ini_get'))
- {
- $php_max_exec_time = @ini_get("maximum_execution_time");
- if ( (!is_numeric($php_max_exec_time)) || ($php_max_exec_time == 0) ) {
- // If we have no time limit, set a hard limit of about 10 seconds
- // (safe for Apache and IIS timeouts, verbose enough for users)
- $php_max_exec_time = 14;
+ @fseek( $this->fp, $canSeek, SEEK_CUR );
+ $seekleft -= $canSeek;
+ if($seekleft) $this->nextFile();
}
+
+ $this->currentPartOffset = @ftell($this->fp);
+ $this->runState = AK_STATE_DONE;
+ return true;
}
- else
+
+ // Last chance to prepend a path to the filename
+ if(!empty($this->addPath) && !$isDirRenamed)
{
- // If ini_get is not available, use a rough default
- $php_max_exec_time = 14;
+ $this->fileHeader->file = $this->addPath.$this->fileHeader->file;
}
- // Apply an arbitrary correction to counter CMS load time
- $php_max_exec_time--;
-
- // Apply bias
- $php_max_exec_time = $php_max_exec_time * $bias;
- $config_max_exec_time = $config_max_exec_time * $bias;
-
- // Use the most appropriate time limit value
- if( $config_max_exec_time > $php_max_exec_time )
+ // Get the translated path name
+ if($this->fileHeader->type == 'file')
{
- $this->max_exec_time = $php_max_exec_time;
+ $this->fileHeader->realFile = $this->postProcEngine->processFilename( $this->fileHeader->file );
}
- else
+ elseif($this->fileHeader->type == 'dir')
{
- $this->max_exec_time = $config_max_exec_time;
- }
- }
-
- /**
- * Wake-up function to reset internal timer when we get unserialized
- */
- public function __wakeup()
- {
- // Re-initialize start time on wake-up
- $this->start_time = $this->microtime_float();
- }
-
- /**
- * Gets the number of seconds left, before we hit the "must break" threshold
- * @return float
- */
- public function getTimeLeft()
- {
- return $this->max_exec_time - $this->getRunningTime();
- }
-
- /**
- * Gets the time elapsed since object creation/unserialization, effectively how
- * long Akeeba Engine has been processing data
- * @return float
- */
- public function getRunningTime()
- {
- return $this->microtime_float() - $this->start_time;
- }
+ $this->fileHeader->timestamp = 0;
- /**
- * Returns the current timestampt in decimal seconds
- */
- private function microtime_float()
- {
- list($usec, $sec) = explode(" ", microtime());
- return ((float)$usec + (float)$sec);
- }
+ $dir = $this->fileHeader->file;
- /**
- * Enforce the minimum execution time
- */
- public function enforce_min_exec_time()
- {
- // Try to get a sane value for PHP's maximum_execution_time INI parameter
- if(@function_exists('ini_get'))
- {
- $php_max_exec = @ini_get("maximum_execution_time");
+ $this->postProcEngine->createDirRecursive( $this->fileHeader->file, 0755 );
+ $this->postProcEngine->processFilename(null);
}
else
{
- $php_max_exec = 10;
- }
- if ( ($php_max_exec == "") || ($php_max_exec == 0) ) {
- $php_max_exec = 10;
+ // Symlink; do not post-process
+ $this->fileHeader->timestamp = 0;
+ $this->postProcEngine->processFilename(null);
}
- // Decrease $php_max_exec time by 500 msec we need (approx.) to tear down
- // the application, as well as another 500msec added for rounding
- // error purposes. Also make sure this is never gonna be less than 0.
- $php_max_exec = max($php_max_exec * 1000 - 1000, 0);
-
- // Get the "minimum execution time per step" Akeeba Backup configuration variable
- $minexectime = AKFactory::get('kickstart.tuning.min_exec_time',0);
- if(!is_numeric($minexectime)) $minexectime = 0;
- // Make sure we are not over PHP's time limit!
- if($minexectime > $php_max_exec) $minexectime = $php_max_exec;
+ $this->createDirectory();
- // Get current running time
- $elapsed_time = $this->getRunningTime() * 1000;
+ // Header is read
+ $this->runState = AK_STATE_HEADER;
- // Only run a sleep delay if we haven't reached the minexectime execution time
- if( ($minexectime > $elapsed_time) && ($elapsed_time > 0) )
- {
- $sleep_msec = $minexectime - $elapsed_time;
- if(function_exists('usleep'))
- {
- usleep(1000 * $sleep_msec);
- }
- elseif(function_exists('time_nanosleep'))
- {
- $sleep_sec = floor($sleep_msec / 1000);
- $sleep_nsec = 1000000 * ($sleep_msec - ($sleep_sec * 1000));
- time_nanosleep($sleep_sec, $sleep_nsec);
- }
- elseif(function_exists('time_sleep_until'))
- {
- $until_timestamp = time() + $sleep_msec / 1000;
- time_sleep_until($until_timestamp);
- }
- elseif(function_exists('sleep'))
- {
- $sleep_sec = ceil($sleep_msec/1000);
- sleep( $sleep_sec );
- }
- }
- elseif( $elapsed_time > 0 )
- {
- // No sleep required, even if user configured us to be able to do so.
- }
+ return true;
}
- /**
- * Reset the timer. It should only be used in CLI mode!
- */
- public function resetTime()
- {
- $this->start_time = $this->microtime_float();
- }
}
+/**
+ * Akeeba Restore
+ * A JSON-powered JPA, JPS and ZIP archive extraction library
+ *
+ * @copyright 2010-2014 Nicholas K. Dionysopoulos / Akeeba Ltd.
+ * @license GNU GPL v2 or - at your option - any later version
+ * @package akeebabackup
+ * @subpackage kickstart
+ */
+
/**
* JPS archive extraction class
*/
class AKUnarchiverJPS extends AKUnarchiverJPA
{
- private $archiveHeaderData = array();
+ protected $archiveHeaderData = array();
- private $password = '';
+ protected $password = '';
public function __construct()
{
@@ -3788,7 +5332,7 @@ protected function readFileHeader()
$isRenamed = true;
}
}
-
+
// Handle directory renaming
$isDirRenamed = false;
if(is_array($this->renameDirs) && (count($this->renameDirs) > 0)) {
@@ -3870,7 +5414,7 @@ protected function readFileHeader()
else
{
// Skip forward by the amount of compressed data
- $miniHead = unpack('Vencsize/Vdecsize');
+ $miniHead = unpack('Vencsize/Vdecsize', $binMiniHead);
@fseek($this->fp, $miniHead['encsize'], SEEK_CUR);
}
}
@@ -3978,7 +5522,7 @@ private function processTypeFileUncompressed()
// Open the output file
if( !AKFactory::get('kickstart.setup.dryrun','0') )
{
- $ignore = AKFactory::get('kickstart.setup.ignoreerrors', false);
+ $ignore = AKFactory::get('kickstart.setup.ignoreerrors', false) || $this->isIgnoredDirectory($this->fileHeader->file);
if ($this->dataReadLength == 0) {
$outfp = @fopen( $this->fileHeader->realFile, 'wb' );
} else {
@@ -4028,7 +5572,7 @@ private function processTypeFileCompressedSimple()
$outfp = @fopen( $this->fileHeader->realFile, 'wb' );
// Can we write to the file?
- $ignore = AKFactory::get('kickstart.setup.ignoreerrors', false);
+ $ignore = AKFactory::get('kickstart.setup.ignoreerrors', false) || $this->isIgnoredDirectory($this->fileHeader->file);
if( ($outfp === false) && (!$ignore) ) {
// An error occured
$this->setError( AKText::sprintf('COULDNT_WRITE_FILE', $this->fileHeader->realFile) );
@@ -4153,6 +5697,8 @@ private function processTypeFileCompressedSimple()
$this->runState = AK_STATE_DATAREAD;
$this->dataReadLength = 0;
}
+
+ return true;
}
/**
@@ -4227,66 +5773,258 @@ private function processTypeLink()
}
}
- // Decrypt the data
- $data = AKEncryptionAES::AESDecryptCBC($data, $this->password, 128);
-
- // Is the length of the decrypted data less than expected?
- $data_length = akstringlen($data);
- if($data_length < $miniHeader['decsize']) {
- $this->setError(AKText::_('ERR_INVALID_JPS_PASSWORD'));
- return false;
- }
+ // Decrypt the data
+ $data = AKEncryptionAES::AESDecryptCBC($data, $this->password, 128);
+
+ // Is the length of the decrypted data less than expected?
+ $data_length = akstringlen($data);
+ if($data_length < $miniHeader['decsize']) {
+ $this->setError(AKText::_('ERR_INVALID_JPS_PASSWORD'));
+ return false;
+ }
+
+ // Trim the data
+ $data = substr($data,0,$miniHeader['decsize']);
+
+ // Try to remove an existing file or directory by the same name
+ if(file_exists($this->fileHeader->file)) { @unlink($this->fileHeader->file); @rmdir($this->fileHeader->file); }
+ // Remove any trailing slash
+ if(substr($this->fileHeader->file, -1) == '/') $this->fileHeader->file = substr($this->fileHeader->file, 0, -1);
+ // Create the symlink - only possible within PHP context. There's no support built in the FTP protocol, so no postproc use is possible here :(
+
+ if( !AKFactory::get('kickstart.setup.dryrun','0') )
+ {
+ @symlink($data, $this->fileHeader->file);
+ }
+
+ $this->runState = AK_STATE_DATAREAD;
+
+ return true; // No matter if the link was created!
+ }
+
+ /**
+ * Process the file data of a directory entry
+ * @return bool
+ */
+ private function processTypeDir()
+ {
+ // Directory entries in the JPA do not have file data, therefore we're done processing the entry
+ $this->runState = AK_STATE_DATAREAD;
+ return true;
+ }
+
+ /**
+ * Creates the directory this file points to
+ */
+ protected function createDirectory()
+ {
+ if( AKFactory::get('kickstart.setup.dryrun','0') ) return true;
+
+ // Do we need to create a directory?
+ $lastSlash = strrpos($this->fileHeader->realFile, '/');
+ $dirName = substr( $this->fileHeader->realFile, 0, $lastSlash);
+ $perms = $this->flagRestorePermissions ? $retArray['permissions'] : 0755;
+ $ignore = AKFactory::get('kickstart.setup.ignoreerrors', false) || $this->isIgnoredDirectory($dirName);
+ if( ($this->postProcEngine->createDirRecursive($dirName, $perms) == false) && (!$ignore) ) {
+ $this->setError( AKText::sprintf('COULDNT_CREATE_DIR', $dirName) );
+ return false;
+ }
+ else
+ {
+ return true;
+ }
+ }
+}
+
+/**
+ * Akeeba Restore
+ * A JSON-powered JPA, JPS and ZIP archive extraction library
+ *
+ * @copyright 2010-2014 Nicholas K. Dionysopoulos / Akeeba Ltd.
+ * @license GNU GPL v2 or - at your option - any later version
+ * @package akeebabackup
+ * @subpackage kickstart
+ */
+
+/**
+ * Timer class
+ */
+class AKCoreTimer extends AKAbstractObject
+{
+ /** @var int Maximum execution time allowance per step */
+ private $max_exec_time = null;
+
+ /** @var int Timestamp of execution start */
+ private $start_time = null;
+
+ /**
+ * Public constructor, creates the timer object and calculates the execution time limits
+ * @return AECoreTimer
+ */
+ public function __construct()
+ {
+ parent::__construct();
+
+ // Initialize start time
+ $this->start_time = $this->microtime_float();
+
+ // Get configured max time per step and bias
+ $config_max_exec_time = AKFactory::get('kickstart.tuning.max_exec_time', 14);
+ $bias = AKFactory::get('kickstart.tuning.run_time_bias', 75)/100;
+
+ // Get PHP's maximum execution time (our upper limit)
+ if(@function_exists('ini_get'))
+ {
+ $php_max_exec_time = @ini_get("maximum_execution_time");
+ if ( (!is_numeric($php_max_exec_time)) || ($php_max_exec_time == 0) ) {
+ // If we have no time limit, set a hard limit of about 10 seconds
+ // (safe for Apache and IIS timeouts, verbose enough for users)
+ $php_max_exec_time = 14;
+ }
+ }
+ else
+ {
+ // If ini_get is not available, use a rough default
+ $php_max_exec_time = 14;
+ }
+
+ // Apply an arbitrary correction to counter CMS load time
+ $php_max_exec_time--;
- // Trim the data
- $data = substr($data,0,$miniHeader['decsize']);
+ // Apply bias
+ $php_max_exec_time = $php_max_exec_time * $bias;
+ $config_max_exec_time = $config_max_exec_time * $bias;
- // Try to remove an existing file or directory by the same name
- if(file_exists($this->fileHeader->realFile)) { @unlink($this->fileHeader->realFile); @rmdir($this->fileHeader->realFile); }
- // Remove any trailing slash
- if(substr($this->fileHeader->realFile, -1) == '/') $this->fileHeader->realFile = substr($this->fileHeader->realFile, 0, -1);
- // Create the symlink - only possible within PHP context. There's no support built in the FTP protocol, so no postproc use is possible here :(
- if( !AKFactory::get('kickstart.setup.dryrun','0') )
- @symlink($data, $this->fileHeader->realFile);
+ // Use the most appropriate time limit value
+ if( $config_max_exec_time > $php_max_exec_time )
+ {
+ $this->max_exec_time = $php_max_exec_time;
+ }
+ else
+ {
+ $this->max_exec_time = $config_max_exec_time;
+ }
+ }
- $this->runState = AK_STATE_DATAREAD;
+ /**
+ * Wake-up function to reset internal timer when we get unserialized
+ */
+ public function __wakeup()
+ {
+ // Re-initialize start time on wake-up
+ $this->start_time = $this->microtime_float();
+ }
- return true; // No matter if the link was created!
+ /**
+ * Gets the number of seconds left, before we hit the "must break" threshold
+ * @return float
+ */
+ public function getTimeLeft()
+ {
+ return $this->max_exec_time - $this->getRunningTime();
}
/**
- * Process the file data of a directory entry
- * @return bool
+ * Gets the time elapsed since object creation/unserialization, effectively how
+ * long Akeeba Engine has been processing data
+ * @return float
*/
- private function processTypeDir()
+ public function getRunningTime()
{
- // Directory entries in the JPA do not have file data, therefore we're done processing the entry
- $this->runState = AK_STATE_DATAREAD;
- return true;
+ return $this->microtime_float() - $this->start_time;
}
/**
- * Creates the directory this file points to
+ * Returns the current timestampt in decimal seconds
*/
- protected function createDirectory()
+ private function microtime_float()
{
- if( AKFactory::get('kickstart.setup.dryrun','0') ) return true;
+ list($usec, $sec) = explode(" ", microtime());
+ return ((float)$usec + (float)$sec);
+ }
- // Do we need to create a directory?
- $lastSlash = strrpos($this->fileHeader->realFile, '/');
- $dirName = substr( $this->fileHeader->realFile, 0, $lastSlash);
- $perms = $this->flagRestorePermissions ? $retArray['permissions'] : 0755;
- $ignore = AKFactory::get('kickstart.setup.ignoreerrors', false);
- if( ($this->postProcEngine->createDirRecursive($dirName, $perms) == false) && (!$ignore) ) {
- $this->setError( AKText::sprintf('COULDNT_CREATE_DIR', $dirName) );
- return false;
+ /**
+ * Enforce the minimum execution time
+ */
+ public function enforce_min_exec_time()
+ {
+ // Try to get a sane value for PHP's maximum_execution_time INI parameter
+ if(@function_exists('ini_get'))
+ {
+ $php_max_exec = @ini_get("maximum_execution_time");
}
else
{
- return true;
+ $php_max_exec = 10;
+ }
+ if ( ($php_max_exec == "") || ($php_max_exec == 0) ) {
+ $php_max_exec = 10;
+ }
+ // Decrease $php_max_exec time by 500 msec we need (approx.) to tear down
+ // the application, as well as another 500msec added for rounding
+ // error purposes. Also make sure this is never gonna be less than 0.
+ $php_max_exec = max($php_max_exec * 1000 - 1000, 0);
+
+ // Get the "minimum execution time per step" Akeeba Backup configuration variable
+ $minexectime = AKFactory::get('kickstart.tuning.min_exec_time',0);
+ if(!is_numeric($minexectime)) $minexectime = 0;
+
+ // Make sure we are not over PHP's time limit!
+ if($minexectime > $php_max_exec) $minexectime = $php_max_exec;
+
+ // Get current running time
+ $elapsed_time = $this->getRunningTime() * 1000;
+
+ // Only run a sleep delay if we haven't reached the minexectime execution time
+ if( ($minexectime > $elapsed_time) && ($elapsed_time > 0) )
+ {
+ $sleep_msec = $minexectime - $elapsed_time;
+ if(function_exists('usleep'))
+ {
+ usleep(1000 * $sleep_msec);
+ }
+ elseif(function_exists('time_nanosleep'))
+ {
+ $sleep_sec = floor($sleep_msec / 1000);
+ $sleep_nsec = 1000000 * ($sleep_msec - ($sleep_sec * 1000));
+ time_nanosleep($sleep_sec, $sleep_nsec);
+ }
+ elseif(function_exists('time_sleep_until'))
+ {
+ $until_timestamp = time() + $sleep_msec / 1000;
+ time_sleep_until($until_timestamp);
+ }
+ elseif(function_exists('sleep'))
+ {
+ $sleep_sec = ceil($sleep_msec/1000);
+ sleep( $sleep_sec );
+ }
+ }
+ elseif( $elapsed_time > 0 )
+ {
+ // No sleep required, even if user configured us to be able to do so.
}
}
+
+ /**
+ * Reset the timer. It should only be used in CLI mode!
+ */
+ public function resetTime()
+ {
+ $this->start_time = $this->microtime_float();
+ }
}
+/**
+ * Akeeba Restore
+ * A JSON-powered JPA, JPS and ZIP archive extraction library
+ *
+ * @copyright 2010-2014 Nicholas K. Dionysopoulos / Akeeba Ltd.
+ * @license GNU GPL v2 or - at your option - any later version
+ * @package akeebabackup
+ * @subpackage kickstart
+ */
+
/**
* A filesystem scanner which uses opendir()
*/
@@ -4361,6 +6099,16 @@ public function &getFolders($folder, $pattern = '*')
}
}
+/**
+ * Akeeba Restore
+ * A JSON-powered JPA, JPS and ZIP archive extraction library
+ *
+ * @copyright 2010-2014 Nicholas K. Dionysopoulos / Akeeba Ltd.
+ * @license GNU GPL v2 or - at your option - any later version
+ * @package akeebabackup
+ * @subpackage kickstart
+ */
+
/**
* A simple INI-based i18n engine
*/
@@ -4383,6 +6131,7 @@ class AKText extends AKAbstractObject
'WRONG_FTP_PATH1' => 'Wrong FTP initial directory - the directory doesn\'t exist',
'FTP_CANT_CREATE_DIR' => 'Could not create directory %s',
'FTP_TEMPDIR_NOT_WRITABLE' => 'Could not find or create a writable temporary directory',
+ 'SFTP_TEMPDIR_NOT_WRITABLE' => 'Could not find or create a writable temporary directory',
'FTP_COULDNT_UPLOAD' => 'Could not upload %s',
'THINGS_HEADER' => 'Things you should know about Akeeba Kickstart',
'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.',
@@ -4399,23 +6148,45 @@ class AKText extends AKAbstractObject
'ARCHIVE_FILE' => 'Archive file:',
'SELECT_EXTRACTION' => 'Select an extraction method',
'WRITE_TO_FILES' => 'Write to files:',
+ 'WRITE_HYBRID' => 'Hybrid (use FTP only if needed)',
'WRITE_DIRECTLY' => 'Directly',
- 'WRITE_FTP' => 'Use FTP',
- 'FTP_HOST' => 'FTP host name:',
- 'FTP_PORT' => 'FTP port:',
+ 'WRITE_FTP' => 'Use FTP for all files',
+ 'WRITE_SFTP' => 'Use SFTP for all files',
+ 'FTP_HOST' => '(S)FTP host name:',
+ 'FTP_PORT' => '(S)FTP port:',
'FTP_FTPS' => 'Use FTP over SSL (FTPS)',
'FTP_PASSIVE' => 'Use FTP Passive Mode',
- 'FTP_USER' => 'FTP user name:',
- 'FTP_PASS' => 'FTP password:',
- 'FTP_DIR' => 'FTP directory:',
+ 'FTP_USER' => '(S)FTP user name:',
+ 'FTP_PASS' => '(S)FTP password:',
+ 'FTP_DIR' => '(S)FTP directory:',
'FTP_TEMPDIR' => 'Temporary directory:',
'FTP_CONNECTION_OK' => 'FTP Connection Established',
+ 'SFTP_CONNECTION_OK' => 'SFTP Connection Established',
'FTP_CONNECTION_FAILURE' => 'The FTP Connection Failed',
+ 'SFTP_CONNECTION_FAILURE' => 'The SFTP Connection Failed',
'FTP_TEMPDIR_WRITABLE' => 'The temporary directory is writable.',
'FTP_TEMPDIR_UNWRITABLE' => 'The temporary directory is not writable. Please check the permissions.',
+ 'FTPBROWSER_ERROR_HOSTNAME' => "Invalid FTP host or port",
+ 'FTPBROWSER_ERROR_USERPASS' => "Invalid FTP username or password",
+ 'FTPBROWSER_ERROR_NOACCESS' => "Directory doesn't exist or you don't have enough permissions to access it",
+ 'FTPBROWSER_ERROR_UNSUPPORTED' => "Sorry, your FTP server doesn't support our FTP directory browser.",
+ 'FTPBROWSER_LBL_GOPARENT' => "<up one level>",
+ 'FTPBROWSER_LBL_INSTRUCTIONS' => 'Click on a directory to navigate into it. Click on OK to select that directory, Cancel to abort the procedure.',
+ 'FTPBROWSER_LBL_ERROR' => 'An error occurred',
+ 'SFTP_NO_SSH2' => 'Your web server does not have the SSH2 PHP module, therefore can not connect to SFTP servers.',
+ 'SFTP_NO_FTP_SUPPORT' => 'Your SSH server does not allow SFTP connections',
+ 'SFTP_WRONG_USER' => 'Wrong SFTP username or password',
+ 'SFTP_WRONG_STARTING_DIR' => 'You must supply a valid absolute path',
+ 'SFTPBROWSER_ERROR_NOACCESS' => "Directory doesn't exist or you don't have enough permissions to access it",
+ 'SFTP_COULDNT_UPLOAD' => 'Could not upload %s',
+ 'SFTP_CANT_CREATE_DIR' => 'Could not create directory %s',
+ 'UI-ROOT' => '<root>',
+ 'CONFIG_UI_FTPBROWSER_TITLE' => 'FTP Directory Browser',
+ 'FTP_BROWSE' => 'Browse',
'BTN_CHECK' => 'Check',
'BTN_RESET' => 'Reset',
'BTN_TESTFTPCON' => 'Test FTP connection',
+ 'BTN_TESTSFTPCON' => 'Test SFTP connection',
'BTN_GOTOSTART' => 'Start over',
'FINE_TUNE' => 'Fine tune',
'MIN_EXEC_TIME' => 'Minimum execution time:',
@@ -4446,7 +6217,12 @@ class AKText extends AKAbstractObject
'UPDATE_HEADER' => 'An updated version of Akeeba Kickstart (unknown) is available!',
'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.',
'UPDATE_DLNOW' => 'Download now',
- 'UPDATE_MOREINFO' => 'More information'
+ 'UPDATE_MOREINFO' => 'More information',
+ 'IGNORE_MOST_ERRORS' => 'Ignore most errors',
+ 'WRONG_FTP_PATH2' => 'Wrong FTP initial directory - the directory doesn\'t correspond to your site\'s web root',
+ 'ARCHIVE_DIRECTORY' => 'Archive directory:',
+ 'RELOAD_ARCHIVES' => 'Reload',
+ 'CONFIG_UI_SFTPBROWSER_TITLE' => 'SFTP Directory Browser',
);
/**
@@ -4481,7 +6257,7 @@ public function __construct()
/**
* Singleton pattern for Language
- * @return Language The global Language instance
+ * @return AKText The global AKText instance
*/
public static function &getInstance()
{
@@ -4594,24 +6370,32 @@ public function getBrowserLanguage()
$this->language = null;
$basename=basename(__FILE__, '.php') . '.ini';
-
+
// 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.
- $fs = new AKUtilsLister();
- $iniFiles = $fs->getFiles( dirname(__FILE__), '*.'.$basename );
- if(empty($iniFiles) && ($basename != 'kickstart.ini')) {
- $basename = 'kickstart.ini';
- $iniFiles = $fs->getFiles( dirname(__FILE__), '*.'.$basename );
+ if (class_exists('AKUtilsLister'))
+ {
+ $fs = new AKUtilsLister();
+ $iniFiles = $fs->getFiles(KSROOTDIR, '*.'.$basename );
+ if(empty($iniFiles) && ($basename != 'kickstart.ini')) {
+ $basename = 'kickstart.ini';
+ $iniFiles = $fs->getFiles(KSROOTDIR, '*.'.$basename );
+ }
+ }
+ else
+ {
+ $iniFiles = null;
}
+
if (is_array($iniFiles)) {
foreach($user_languages as $languageStruct)
{
if(is_null($this->language))
{
// Get files matching the main lang part
- $iniFiles = $fs->getFiles( dirname(__FILE__), $languageStruct[1].'-??.'.$basename );
+ $iniFiles = $fs->getFiles(KSROOTDIR, $languageStruct[1].'-??.'.$basename );
if (count($iniFiles) > 0) {
$filename = $iniFiles[0];
- $filename = substr($filename, strlen(dirname(__FILE__))+1);
+ $filename = substr($filename, strlen(KSROOTDIR)+1);
$this->language = substr($filename, 0, 5);
} else {
$this->language = null;
@@ -4619,7 +6403,7 @@ public function getBrowserLanguage()
}
}
}
-
+
if(is_null($this->language)) {
// Try to find a full language match
foreach($user_languages as $languageStruct)
@@ -4641,15 +6425,22 @@ public function getBrowserLanguage()
}
}
}
-
+
// Now, scan for full language based on the partial match
-
+
}
private function loadTranslation( $lang = null )
{
- $dirname = function_exists('getcwd') ? getcwd() : dirname(__FILE__);
- $basename=basename(__FILE__, '.php') . '.ini';
+ if (defined('KSLANGDIR'))
+ {
+ $dirname = KSLANGDIR;
+ }
+ else
+ {
+ $dirname = KSROOTDIR;
+ }
+ $basename = basename(__FILE__, '.php') . '.ini';
if( empty($lang) ) $lang = $this->language;
$translationFilename = $dirname.DIRECTORY_SEPARATOR.$lang.'.'.$basename;
@@ -4668,6 +6459,14 @@ private function loadTranslation( $lang = null )
}
}
+ public function addDefaultLanguageStrings($stringList = array())
+ {
+ if(!is_array($stringList)) return;
+ if(empty($stringList)) return;
+
+ $this->strings = array_merge($stringList, $this->strings);
+ }
+
/**
* A PHP based INI file parser.
*
@@ -4765,6 +6564,16 @@ public static function parse_ini_file($file, $process_sections = false, $raw_dat
}
}
+/**
+ * Akeeba Restore
+ * A JSON-powered JPA, JPS and ZIP archive extraction library
+ *
+ * @copyright 2010-2014 Nicholas K. Dionysopoulos / Akeeba Ltd.
+ * @license GNU GPL v2 or - at your option - any later version
+ * @package akeebabackup
+ * @subpackage kickstart
+ */
+
/**
* The Akeeba Kickstart Factory class
* This class is reponssible for instanciating all Akeeba Kicsktart classes
@@ -4952,20 +6761,29 @@ public static function &getUnarchiver( $configOverride = null )
$destdir = self::get('kickstart.setup.destdir', null);
if(empty($destdir))
{
- $destdir = function_exists('getcwd') ? getcwd() : dirname(__FILE__);
+ $destdir = KSROOTDIR;
}
$object = self::getClassInstance($class_name);
if( $object->getState() == 'init')
{
+ $sourcePath = self::get('kickstart.setup.sourcepath', '');
+ $sourceFile = self::get('kickstart.setup.sourcefile', '');
+
+ if (!empty($sourcePath))
+ {
+ $sourceFile = rtrim($sourcePath, '/\\') . '/' . $sourceFile;
+ }
+
// Initialize the object
$config = array(
- 'filename' => self::get('kickstart.setup.sourcefile', ''),
+ 'filename' => $sourceFile,
'restore_permissions' => self::get('kickstart.setup.restoreperms', 0),
'post_proc' => self::get('kickstart.procengine', 'direct'),
- 'add_path' => $destdir,
- 'rename_files' => array( '.htaccess' => 'htaccess.bak', 'php.ini' => 'php.ini.bak' ),
- 'skip_files' => array( basename(__FILE__), 'kickstart.php', 'abiautomation.ini', 'htaccess.bak', 'php.ini.bak' )
+ 'add_path' => self::get('kickstart.setup.targetpath', $destdir),
+ 'rename_files' => array('.htaccess' => 'htaccess.bak', 'php.ini' => 'php.ini.bak', 'web.config' => 'web.config.bak'),
+ 'skip_files' => array(basename(__FILE__), 'kickstart.php', 'abiautomation.ini', 'htaccess.bak', 'php.ini.bak', 'cacert.pem'),
+ 'ignoredirectories' => array('tmp', 'log', 'logs'),
);
if(!defined('KICKSTART'))
@@ -5001,19 +6819,25 @@ public static function &getTimer()
}
/**
- * AES implementation in PHP (c) Chris Veness 2005-2011
- * (http://www.movable-type.co.uk/scripts/aes-php.html)
- * I offer these formulæ & scripts for free use and adaptation as my contribution to the
- * open-source info-sphere from which I have received so much. You are welcome to re-use these
- * scripts [under a simple attribution license or a GPL licence, without any warranty express or implied]
- * provided solely that you retain my copyright notice and a link to this page.
+ * Akeeba Restore
+ * A JSON-powered JPA, JPS and ZIP archive extraction library
+ *
+ * @copyright 2010-2014 Nicholas K. Dionysopoulos / Akeeba Ltd.
+ * @license GNU GPL v2 or - at your option - any later version
+ * @package akeebabackup
+ * @subpackage kickstart
+ */
+
+/**
+ * AES implementation in PHP (c) Chris Veness 2005-2013.
+ * Right to use and adapt is granted for under a simple creative commons attribution
* licence. No warranty of any form is offered.
*
* Modified for Akeeba Backup by Nicholas K. Dionysopoulos
*/
class AKEncryptionAES
{
- // Sbox is pre-computed multiplicative inverse in GF(2^8) used in SubBytes and KeyExpansion [�5.1.1]
+ // Sbox is pre-computed multiplicative inverse in GF(2^8) used in SubBytes and KeyExpansion [�5.1.1]
protected static $Sbox =
array(0x63,0x7c,0x77,0x7b,0xf2,0x6b,0x6f,0xc5,0x30,0x01,0x67,0x2b,0xfe,0xd7,0xab,0x76,
0xca,0x82,0xc9,0x7d,0xfa,0x59,0x47,0xf0,0xad,0xd4,0xa2,0xaf,0x9c,0xa4,0x72,0xc0,
@@ -5032,7 +6856,7 @@ class AKEncryptionAES
0xe1,0xf8,0x98,0x11,0x69,0xd9,0x8e,0x94,0x9b,0x1e,0x87,0xe9,0xce,0x55,0x28,0xdf,
0x8c,0xa1,0x89,0x0d,0xbf,0xe6,0x42,0x68,0x41,0x99,0x2d,0x0f,0xb0,0x54,0xbb,0x16);
- // Rcon is Round Constant used for the Key Expansion [1st col is 2^(r-1) in GF(2^8)] [�5.2]
+ // Rcon is Round Constant used for the Key Expansion [1st col is 2^(r-1) in GF(2^8)] [�5.2]
protected static $Rcon = array(
array(0x00, 0x00, 0x00, 0x00),
array(0x01, 0x00, 0x00, 0x00),
@@ -5056,11 +6880,11 @@ class AKEncryptionAES
* generated from the cipher key by KeyExpansion()
* @return ciphertext as byte-array (16 bytes)
*/
- protected static function Cipher($input, $w) { // main Cipher function [�5.1]
+ protected static function Cipher($input, $w) { // main Cipher function [�5.1]
$Nb = 4; // block size (in words): no of columns in state (fixed at 4 for AES)
$Nr = count($w)/$Nb - 1; // no of rounds: 10/12/14 for 128/192/256-bit keys
- $state = array(); // initialise 4xNb byte-array 'state' with input [�3.4]
+ $state = array(); // initialise 4xNb byte-array 'state' with input [�3.4]
for ($i=0; $i<4*$Nb; $i++) $state[$i%4][floor($i/4)] = $input[$i];
$state = self::AddRoundKey($state, $w, 0, $Nb);
@@ -5076,26 +6900,26 @@ protected static function Cipher($input, $w) { // main Cipher function [ï¿
$state = self::ShiftRows($state, $Nb);
$state = self::AddRoundKey($state, $w, $Nr, $Nb);
- $output = array(4*$Nb); // convert state to 1-d array before returning [�3.4]
+ $output = array(4*$Nb); // convert state to 1-d array before returning [�3.4]
for ($i=0; $i<4*$Nb; $i++) $output[$i] = $state[$i%4][floor($i/4)];
return $output;
}
- protected static function AddRoundKey($state, $w, $rnd, $Nb) { // xor Round Key into state S [�5.1.4]
+ protected static function AddRoundKey($state, $w, $rnd, $Nb) { // xor Round Key into state S [�5.1.4]
for ($r=0; $r<4; $r++) {
for ($c=0; $c<$Nb; $c++) $state[$r][$c] ^= $w[$rnd*4+$c][$r];
}
return $state;
}
- protected static function SubBytes($s, $Nb) { // apply SBox to state S [�5.1.1]
+ protected static function SubBytes($s, $Nb) { // apply SBox to state S [�5.1.1]
for ($r=0; $r<4; $r++) {
for ($c=0; $c<$Nb; $c++) $s[$r][$c] = self::$Sbox[$s[$r][$c]];
}
return $s;
}
- protected static function ShiftRows($s, $Nb) { // shift row r of state S left by r bytes [�5.1.2]
+ protected static function ShiftRows($s, $Nb) { // shift row r of state S left by r bytes [�5.1.2]
$t = array(4);
for ($r=1; $r<4; $r++) {
for ($c=0; $c<4; $c++) $t[$c] = $s[$r][($c+$r)%$Nb]; // shift into temp copy
@@ -5104,15 +6928,15 @@ protected static function ShiftRows($s, $Nb) { // shift row r of state S left
return $s; // see fp.gladman.plus.com/cryptography_technology/rijndael/aes.spec.311.pdf
}
- protected static function MixColumns($s, $Nb) { // combine bytes of each col of state S [�5.1.3]
+ protected static function MixColumns($s, $Nb) { // combine bytes of each col of state S [�5.1.3]
for ($c=0; $c<4; $c++) {
$a = array(4); // 'a' is a copy of the current column from 's'
- $b = array(4); // 'b' is a�{02} in GF(2^8)
+ $b = array(4); // 'b' is a�{02} in GF(2^8)
for ($i=0; $i<4; $i++) {
$a[$i] = $s[$i][$c];
$b[$i] = $s[$i][$c]&0x80 ? $s[$i][$c]<<1 ^ 0x011b : $s[$i][$c]<<1;
}
- // a[n] ^ b[n] is a�{03} in GF(2^8)
+ // a[n] ^ b[n] is a�{03} in GF(2^8)
$s[0][$c] = $b[0] ^ $a[1] ^ $b[1] ^ $a[2] ^ $a[3]; // 2*a0 + 3*a1 + a2 + a3
$s[1][$c] = $a[0] ^ $b[1] ^ $a[2] ^ $b[2] ^ $a[3]; // a0 * 2*a1 + 3*a2 + a3
$s[2][$c] = $a[0] ^ $a[1] ^ $b[2] ^ $a[3] ^ $b[3]; // a0 + a1 + 2*a2 + 3*a3
@@ -5128,7 +6952,7 @@ protected static function MixColumns($s, $Nb) { // combine bytes of each col o
* @param key cipher key byte-array (16 bytes)
* @return key schedule as 2D byte-array (Nr+1 x Nb bytes)
*/
- protected static function KeyExpansion($key) { // generate Key Schedule from Cipher Key [�5.2]
+ protected static function KeyExpansion($key) { // generate Key Schedule from Cipher Key [�5.2]
$Nb = 4; // block size (in words): no of columns in state (fixed at 4 for AES)
$Nk = count($key)/4; // key length (in words): 4/6/8 for 128/192/256-bit keys
$Nr = $Nk + 6; // no of rounds: 10/12/14 for 128/192/256-bit keys
@@ -5209,7 +7033,7 @@ public static function AESEncryptCtr($plaintext, $password, $nBits) {
$key = self::Cipher($pwBytes, self::KeyExpansion($pwBytes));
$key = array_merge($key, array_slice($key, 0, $nBytes-16)); // expand key to 16/24/32 bytes long
- // initialise counter block (NIST SP800-38A �B.2): millisecond time-stamp for nonce in
+ // initialise counter block (NIST SP800-38A �B.2): millisecond time-stamp for nonce in
// 1st 8 bytes, block counter in 2nd 8 bytes
$counterBlock = array();
$nonce = floor(microtime(true)*1000); // timestamp: milliseconds since 1-Jan-1970
@@ -5387,8 +7211,19 @@ public static function AESDecryptCBC($ciphertext, $password, $nBits = 128)
}
/**
- * The Master Setup will read the configuration parameters from restoration.php, abiautomation.ini, or
+ * Akeeba Restore
+ * A JSON-powered JPA, JPS and ZIP archive extraction library
+ *
+ * @copyright 2010-2014 Nicholas K. Dionysopoulos / Akeeba Ltd.
+ * @license GNU GPL v2 or - at your option - any later version
+ * @package akeebabackup
+ * @subpackage kickstart
+ */
+
+/**
+ * The Master Setup will read the configuration parameters from restoration.php or
* the JSON-encoded "configuration" input variable and return the status.
+ *
* @return bool True if the master configuration was applied to the Factory object
*/
function masterSetup()
@@ -5400,26 +7235,29 @@ function masterSetup()
$ini_data = null;
// In restore.php mode, require restoration.php or fail
- if(!defined('KICKSTART'))
+ if (!defined('KICKSTART'))
{
// This is the standalone mode, used by Akeeba Backup Professional. It looks for a restoration.php
// file to perform its magic. If the file is not there, we will abort.
$setupFile = 'restoration.php';
- if( !file_exists($setupFile) )
+ if (!file_exists($setupFile))
{
- // Uh oh... Somebody tried to pooh on our back yard. Lock the gates! Don't let the traitor inside!
AKFactory::set('kickstart.enabled', false);
+
return false;
}
// Load restoration.php. It creates a global variable named $restoration_setup
require_once $setupFile;
+
$ini_data = $restoration_setup;
- if(empty($ini_data))
+
+ if (empty($ini_data))
{
// No parameters fetched. Darn, how am I supposed to work like that?!
AKFactory::set('kickstart.enabled', false);
+
return false;
}
@@ -5429,17 +7267,21 @@ function masterSetup()
{
// Maybe we have $restoration_setup defined in the head of kickstart.php
global $restoration_setup;
- if(!empty($restoration_setup) && !is_array($restoration_setup)) {
+
+ if (!empty($restoration_setup) && !is_array($restoration_setup))
+ {
$ini_data = AKText::parse_ini_file($restoration_setup, false, true);
- } elseif(is_array($restoration_setup)) {
+ }
+ elseif (is_array($restoration_setup))
+ {
$ini_data = $restoration_setup;
}
}
// Import any data from $restoration_setup
- if(!empty($ini_data))
+ if (!empty($ini_data))
{
- foreach($ini_data as $key => $value)
+ foreach ($ini_data as $key => $value)
{
AKFactory::set($key, $value);
}
@@ -5456,89 +7298,126 @@ function masterSetup()
// Detect a JSON string in the request variable and store it.
$json = getQueryParam('json', null);
- // Remove everything from the request array
- if(!empty($_REQUEST))
+ // Remove everything from the request, post and get arrays
+ if (!empty($_REQUEST))
{
- foreach($_REQUEST as $key => $value)
+ foreach ($_REQUEST as $key => $value)
{
unset($_REQUEST[$key]);
}
}
+
+ if (!empty($_POST))
+ {
+ foreach ($_POST as $key => $value)
+ {
+ unset($_POST[$key]);
+ }
+ }
+
+ if (!empty($_GET))
+ {
+ foreach ($_GET as $key => $value)
+ {
+ unset($_GET[$key]);
+ }
+ }
+
// Decrypt a possibly encrypted JSON string
- if(!empty($json))
+ $password = AKFactory::get('kickstart.security.password', null);
+
+ if (!empty($json))
{
- $password = AKFactory::get('kickstart.security.password', null);
- if(!empty($password))
+ if (!empty($password))
{
$json = AKEncryptionAES::AESDecryptCtr($json, $password, 128);
+
+ if (empty($json))
+ {
+ die('###{"status":false,"message":"Invalid login"}###');
+ }
}
// Get the raw data
- $raw = json_decode( $json, true );
+ $raw = json_decode($json, true);
+
+ if (!empty($password) && (empty($raw)))
+ {
+ die('###{"status":false,"message":"Invalid login"}###');
+ }
+
// Pass all JSON data to the request array
- if(!empty($raw))
+ if (!empty($raw))
{
- foreach($raw as $key => $value)
+ foreach ($raw as $key => $value)
{
$_REQUEST[$key] = $value;
}
}
}
+ elseif (!empty($password))
+ {
+ die('###{"status":false,"message":"Invalid login"}###');
+ }
// ------------------------------------------------------------
// 3. Try the "factory" variable
// ------------------------------------------------------------
// A "factory" variable will override all other settings.
$serialized = getQueryParam('factory', null);
- if( !is_null($serialized) )
+
+ if (!is_null($serialized))
{
// Get the serialized factory
AKFactory::unserialize($serialized);
AKFactory::set('kickstart.enabled', true);
+
return true;
}
// ------------------------------------------------------------
- // 4. Try abiautomation.ini and the configuration variable for Kickstart
+ // 4. Try the configuration variable for Kickstart
// ------------------------------------------------------------
- if(defined('KICKSTART'))
+ if (defined('KICKSTART'))
{
- // We are in Kickstart mode. abiautomation.ini has precedence.
- $setupFile = 'abiautomation.ini';
- if( file_exists($setupFile) )
+ $configuration = getQueryParam('configuration');
+
+ if (!is_null($configuration))
{
- // abiautomation.ini was found
- $ini_data = AKText::parse_ini_file('restoration.ini', false);
+ // Let's decode the configuration from JSON to array
+ $ini_data = json_decode($configuration, true);
}
else
{
- // abiautomation.ini was not found. Let's try input parameters.
- $configuration = getQueryParam('configuration');
- if( !is_null($configuration) )
- {
- // Let's decode the configuration from JSON to array
- $ini_data = json_decode($configuration, true);
- }
- else
- {
- // Neither exists. Enable Kickstart's interface anyway.
- $ini_data = array('kickstart.enabled'=>true);
- }
+ // Neither exists. Enable Kickstart's interface anyway.
+ $ini_data = array('kickstart.enabled' => true);
}
// Import any INI data we might have from other sources
- if(!empty($ini_data))
+ if (!empty($ini_data))
{
- foreach($ini_data as $key => $value)
+ foreach ($ini_data as $key => $value)
{
AKFactory::set($key, $value);
}
+
AKFactory::set('kickstart.enabled', true);
+
return true;
}
}
}
+/**
+ * Akeeba Restore
+ * A JSON-powered JPA, JPS and ZIP archive extraction library
+ *
+ * @copyright 2010-2014 Nicholas K. Dionysopoulos / Akeeba Ltd.
+ * @license GNU GPL v2 or - at your option - any later version
+ * @package akeebabackup
+ * @subpackage kickstart
+ */
+
// Mini-controller for restore.php
if(!defined('KICKSTART'))
{
@@ -5646,11 +7525,33 @@ public function __toString()
$postproc->rename( $root.'/htaccess.bak', $root.'/.htaccess' );
}
+ // Rename htaccess.bak to .htaccess
+ if(file_exists($root.'/web.config.bak'))
+ {
+ if( file_exists($root.'/web.config') )
+ {
+ $postproc->unlink($root.'/web.config');
+ }
+ $postproc->rename( $root.'/web.config.bak', $root.'/web.config' );
+ }
+
// Remove restoration.php
- $basepath = dirname(__FILE__);
+ $basepath = KSROOTDIR;
$basepath = rtrim( str_replace('\\','/',$basepath), '/' );
if(!empty($basepath)) $basepath .= '/';
$postproc->unlink( $basepath.'restoration.php' );
+
+ // Import a custom finalisation file
+ if (file_exists(dirname(__FILE__) . '/restore_finalisation.php'))
+ {
+ include_once dirname(__FILE__) . '/restore_finalisation.php';
+ }
+
+ // Run a custom finalisation script
+ if (function_exists('finalizeRestore'))
+ {
+ finalizeRestore($root, $basepath);
+ }
break;
default:
@@ -5743,4 +7644,3 @@ function recursive_remove_directory($directory)
return TRUE;
}
}
-?>
\ No newline at end of file
diff --git a/administrator/components/com_media/models/manager.php b/administrator/components/com_media/models/manager.php
index 679f54f21687e..b99f2b2bd9b15 100644
--- a/administrator/components/com_media/models/manager.php
+++ b/administrator/components/com_media/models/manager.php
@@ -90,7 +90,7 @@ function getFolderList($base = null)
// so both string and integer are supported.
if ($asset == 0)
{
- $asset = $input->get('asset', 0, 'string');
+ $asset = $input->get('asset', 0, 'cmd');
}
$author = $input->get('author', 0, 'integer');
diff --git a/administrator/components/com_modules/views/modules/tmpl/default.php b/administrator/components/com_modules/views/modules/tmpl/default.php
index 251cb6da206d5..cb58c064c9ed6 100644
--- a/administrator/components/com_modules/views/modules/tmpl/default.php
+++ b/administrator/components/com_modules/views/modules/tmpl/default.php
@@ -159,7 +159,7 @@
- published, $i, $canChange, 'cb'); ?>
+ published, $i, 'modules.', $canChange, 'cb', $item->publish_up, $item->publish_down); ?>
" />
-
\ No newline at end of file
+
diff --git a/administrator/components/com_newsfeeds/tables/newsfeed.php b/administrator/components/com_newsfeeds/tables/newsfeed.php
index e6c5515581732..bede2c2139501 100644
--- a/administrator/components/com_newsfeeds/tables/newsfeed.php
+++ b/administrator/components/com_newsfeeds/tables/newsfeed.php
@@ -21,7 +21,7 @@ class NewsfeedsTableNewsfeed extends JTable
* @var array
* @since 3.3
*/
- protected $jsonEncode = array('params', 'metadata', 'images');
+ protected $_jsonEncode = array('params', 'metadata', 'images');
/**
* Constructor
diff --git a/administrator/components/com_plugins/views/plugins/tmpl/default.php b/administrator/components/com_plugins/views/plugins/tmpl/default.php
index eef9d0f6d8dd9..cadba65af4ac4 100644
--- a/administrator/components/com_plugins/views/plugins/tmpl/default.php
+++ b/administrator/components/com_plugins/views/plugins/tmpl/default.php
@@ -47,7 +47,7 @@
|