protected function _prepare() { Factory::getLog()->log(LogLevel::DEBUG, __CLASS__ . " :: Processing parameters"); // Get the DB connection parameters if (is_array($this->_parametersArray)) { $driver = array_key_exists('driver', $this->_parametersArray) ? $this->_parametersArray['driver'] : 'mysql'; $host = array_key_exists('host', $this->_parametersArray) ? $this->_parametersArray['host'] : ''; $port = array_key_exists('port', $this->_parametersArray) ? $this->_parametersArray['port'] : ''; $username = array_key_exists('username', $this->_parametersArray) ? $this->_parametersArray['username'] : ''; $username = array_key_exists('user', $this->_parametersArray) ? $this->_parametersArray['user'] : $username; $password = array_key_exists('password', $this->_parametersArray) ? $this->_parametersArray['password'] : ''; $database = array_key_exists('database', $this->_parametersArray) ? $this->_parametersArray['database'] : ''; $prefix = array_key_exists('prefix', $this->_parametersArray) ? $this->_parametersArray['prefix'] : ''; } if ($driver == 'mysql' && !function_exists('mysql_connect')) { $driver = 'mysqli'; } $options = array('driver' => $driver, 'host' => $host . ($port != '' ? ':' . $port : ''), 'user' => $username, 'password' => $password, 'database' => $database, 'prefix' => is_null($prefix) ? '' : $prefix); $db = Factory::getDatabase($options); $driverType = $db->getDriverType(); if ($driverType == 'mssql') { $driverType = 'sqlsrv'; } $className = '\\Akeeba\\Engine\\Dump\\Reverse\\' . ucfirst($driverType); Factory::getLog()->log(LogLevel::DEBUG, __CLASS__ . " :: Instanciating new reverse engineering database dump engine {$className}"); if (!class_exists($className, true)) { $this->setState('error', 'Akeeba Engine does not have a reverse engineering dump engine for ' . $driverType . ' databases'); } else { $this->_engine = new $className(); $this->_engine->setup($this->_parametersArray); $this->_engine->callStage('_prepare'); $this->setState($this->_engine->getState(), $this->_engine->getError()); $this->propagateFromObject($this->_engine); } }
protected function _prepare() { Factory::getLog()->log(LogLevel::DEBUG, __CLASS__ . " :: Processing parameters"); $options = null; // Get the DB connection parameters if (is_array($this->_parametersArray)) { $driver = array_key_exists('driver', $this->_parametersArray) ? $this->_parametersArray['driver'] : 'mysql'; $host = array_key_exists('host', $this->_parametersArray) ? $this->_parametersArray['host'] : ''; $port = array_key_exists('port', $this->_parametersArray) ? $this->_parametersArray['port'] : ''; $username = array_key_exists('username', $this->_parametersArray) ? $this->_parametersArray['username'] : ''; $username = array_key_exists('user', $this->_parametersArray) ? $this->_parametersArray['user'] : $username; $password = array_key_exists('password', $this->_parametersArray) ? $this->_parametersArray['password'] : ''; $database = array_key_exists('database', $this->_parametersArray) ? $this->_parametersArray['database'] : ''; $prefix = array_key_exists('prefix', $this->_parametersArray) ? $this->_parametersArray['prefix'] : ''; $options = array('driver' => $driver, 'host' => $host . ($port != '' ? ':' . $port : ''), 'user' => $username, 'password' => $password, 'database' => $database, 'prefix' => is_null($prefix) ? '' : $prefix); } $db = Factory::getDatabase($options); $driverType = $db->getDriverType(); $className = '\\Akeeba\\Engine\\Dump\\Native\\' . ucfirst($driverType); // Check if we have a native dump driver if (!class_exists($className, true)) { Factory::getLog()->log(LogLevel::DEBUG, __CLASS__ . " :: Native database dump engine {$className} not found; trying Reverse Engineering instead"); // Native driver nor found, I will try falling back to reverse engineering $className = '\\Akeeba\\Engine\\Dump\\Reverse\\' . ucfirst($driverType); } if (!class_exists($className, true)) { $this->setState('error', 'Akeeba Engine does not have a native dump engine for ' . $driverType . ' databases'); } else { Factory::getLog()->log(LogLevel::DEBUG, __CLASS__ . " :: Instanciating new native database dump engine {$className}"); $this->_engine = new $className(); $this->_engine->setup($this->_parametersArray); $this->_engine->callStage('_prepare'); $this->setState($this->_engine->getState(), $this->_engine->getError()); } }
/** * Performs one more step of dumping database data * * @return void */ protected function stepDatabaseDump() { // We do not create any dump file, we will simply include the whole database file inside the backup Factory::getLog()->log(LogLevel::INFO, "SQLite database detected, no SQL dump files will be created."); $this->setState('postrun'); $this->setStep(''); $this->setSubstep(''); }
/** * Execute the JSON API task * * @param array $parameters The parameters to this task * * @return mixed * * @throws \RuntimeException In case of an error */ public function execute(array $parameters = array()) { // Get the passed configuration values $defConfig = array('tag' => 'remote'); $defConfig = array_merge($defConfig, $parameters); $tag = (int) $defConfig['tag']; $filename = Factory::getLog()->getLogFilename($tag); return file_get_contents($filename); }
/** * Updates the multipart status of the current backup attempt's statistics record * * @param int $multipart The new multipart status */ public function updateMultipart($multipart) { if ($this->multipart_lock) { return; } Factory::getLog()->log(LogLevel::DEBUG, 'Updating multipart status to ' . $multipart); // Cache this change and commit to db only after the backup is done, or failed $registry = Factory::getConfiguration(); $registry->set('volatile.statistics.multipart', $multipart); }
public function echoRawLog() { $tag = $this->getState('tag', ''); echo "WARNING: Do not copy and paste lines from this file!\r\n"; echo "You are supposed to ZIP and attach it in your support forum post.\r\n"; echo "If you fail to do so, your support request will receive minimal priority.\r\n"; echo "\r\n"; echo "--- START OF RAW LOG --\r\n"; @readfile(Factory::getLog()->getLogFilename($tag)); // The at sign is necessary to skip showing PHP errors if the file doesn't exist or isn't readable for some reason echo "--- END OF RAW LOG ---\r\n"; }
/** * Output the raw text log file to the standard output * * @return void */ public function echoRawLog() { $tag = $this->getState('tag', ''); echo "WARNING: Do not copy and paste lines from this file!\r\n"; echo "You are supposed to ZIP and attach it in your support forum post.\r\n"; echo "If you fail to do so, we will be unable to provide efficient support.\r\n"; echo "\r\n"; echo "--- START OF RAW LOG --\r\n"; // The at sign (silence operator) is necessary to prevent PHP showing a warning if the file doesn't exist or // isn't readable for any reason. @readfile(Factory::getLog()->getLogFilename($tag)); echo "--- END OF RAW LOG ---\r\n"; }
/** * Very efficient CRC32 calculation for PHP 5.1.2 and greater, requiring * the 'hash' PECL extension * * @param string $filename Absolute filepath * * @return integer The CRC32 */ protected function crc32_file_php512($filename) { // Detection of buggy PHP hosts static $mustInvert = null; if (is_null($mustInvert)) { $test_crc = @hash('crc32b', 'test', false); $mustInvert = strtolower($test_crc) == '0c7e7fd8'; // Normally, it's D87F7E0C :) if ($mustInvert) { Factory::getLog()->log(LogLevel::WARNING, 'Your server has a buggy PHP version which produces inverted CRC32 values. Attempting a workaround. ZIP files may appear as corrupt.'); } } $res = @hash_file('crc32b', $filename, false); if ($mustInvert) { // Workaround for buggy PHP versions (I think before 5.1.8) which produce inverted CRC32 sums $res2 = substr($res, 6, 2) . substr($res, 4, 2) . substr($res, 2, 2) . substr($res, 0, 2); $res = $res2; } $res = hexdec($res); return $res; }
/** * The main page of the log viewer. It allows you to select a profile to display. When you do it displays the IFRAME * with the actual log content and the button to download the raw log file. * * @return void */ public function onBeforeMain() { // Get a list of log names /** @var Log $model */ $model = $this->getModel(); $this->logs = $model->getLogList(); $tag = $model->getState('tag'); if (empty($tag)) { $tag = null; } $this->tag = $tag; // Let's check if the file is too big to display if ($this->tag) { $file = Factory::getLog()->getLogFilename($this->tag); if (@file_exists($file)) { $this->logSize = filesize($file); $this->logTooBig = $this->logSize >= self::bigLogSize; } } if ($this->logTooBig) { $src = 'index.php?option=com_akeeba&view=Log&task=download&attachment=0&tag=' . urlencode($this->tag); $js = <<<JS ;// Prevent broken 3PD Javascript from causing errors akeeba.jQuery(document).ready(function (\$){ \$('#showlog').click(function(){ \$('<iframe width="99%" src="{$src}" height="400px"/>').appendTo('.iframe-holder'); \$(this).hide(); }) }); JS; $this->addJavascriptInline($js); } $this->getProfileIdAndName(); JHtml::_('formbehavior.chosen'); }
/** * Creates a new part for the spanned archive * * @param bool $finalPart Is this the final archive part? * * @return bool True on success */ protected function _createNewPart($finalPart = false) { // Close any open file pointers if (is_resource($this->fp)) { $this->_fclose($this->fp); } if (is_resource($this->cdfp)) { $this->_fclose($this->cdfp); } // Remove the just finished part from the list of resumable offsets $this->_removeFromOffsetsList($this->_dataFileName); // Set the file pointers to null $this->fp = null; $this->cdfp = null; // Push the previous part if we have to post-process it immediately $configuration = Factory::getConfiguration(); if ($configuration->get('engine.postproc.common.after_part', 0)) { // The first part needs its header overwritten during archive // finalization. Skip it from immediate processing. if ($this->_currentFragment != 1) { $this->finishedPart[] = $this->_dataFileName; } } $this->_totalFragments++; $this->_currentFragment = $this->_totalFragments; if ($finalPart) { $this->_dataFileName = $this->_dataFileNameBase . '.jpa'; } else { $this->_dataFileName = $this->_dataFileNameBase . '.j' . sprintf('%02d', $this->_currentFragment); } Factory::getLog()->log(LogLevel::INFO, 'Creating new JPA part #' . $this->_currentFragment . ', file ' . $this->_dataFileName); $statistics = Factory::getStatistics(); $statistics->updateMultipart($this->_totalFragments); // Try to remove any existing file @unlink($this->_dataFileName); // Touch the new file $result = @touch($this->_dataFileName); if (function_exists('chmod')) { chmod($this->_dataFileName, 0666); } // Try to write 6 bytes to it if ($result) { $result = @file_put_contents($this->_dataFileName, 'AKEEBA') == 6; } if ($result) { @unlink($this->_dataFileName); $result = @touch($this->_dataFileName); if (function_exists('chmod')) { chmod($this->_dataFileName, 0666); } } return $result; }
/** * Implements the _run() abstract method */ function _run() { if ($this->getState() == 'postrun') { Factory::getLog()->log(LogLevel::DEBUG, __CLASS__ . " :: Already finished"); $this->setStep(''); $this->setSubstep(''); } else { $this->setState('running'); } // Try to step the archiver $archive = Factory::getArchiverEngine(); $ret = $archive->transformJPA($this->xformIndex, $this->offset); // Error propagation $this->propagateFromObject($archive); if ($ret !== false && $archive->getError() == '') { $this->offset = $ret['offset']; $this->xformIndex = $ret['index']; $this->setStep($ret['filename']); } // Check for completion if ($ret['done']) { Factory::getLog()->log(LogLevel::DEBUG, __CLASS__ . ":: archive is initialized"); $this->setState('finished'); } // Calculate percentage $this->runningSize += $ret['chunkProcessed']; if ($ret['filesize'] > 0) { $this->progress = $this->runningSize / $ret['filesize']; } }
/** * Forcibly refresh the Google Drive tokens * * @return void */ protected function forceRefreshTokens() { // Retrieve engine configuration data $config = Factory::getConfiguration(); $pingResult = $this->googleDrive->ping(true); Factory::getLog()->log(LogLevel::DEBUG, __CLASS__ . '::' . __METHOD__ . " - Google Drive tokens were forcibly refreshed"); $config->set('engine.postproc.googledrive.access_token', $pingResult['access_token'], false); $profile_id = Platform::getInstance()->get_active_profile(); Platform::getInstance()->save_configuration($profile_id); }
/** * Enforce the minimum execution time * * @param bool $log Should I log what I'm doing? Default is true. * @param bool $serverSideSleep Should I sleep on the server side? If false we return the amount of time to wait in msec * * @return int Wait time to reach min_execution_time in msec */ public function enforce_min_exec_time($log = true, $serverSideSleep = true) { // 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 { $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 $configuration = Factory::getConfiguration(); $minexectime = $configuration->get('akeeba.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; $clientSideSleep = 0; // 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 (!$serverSideSleep) { Factory::getLog()->log(LogLevel::DEBUG, "Asking client to sleep for {$sleep_msec} msec"); $clientSideSleep = $sleep_msec; } elseif (function_exists('usleep')) { if ($log) { Factory::getLog()->log(LogLevel::DEBUG, "Sleeping for {$sleep_msec} msec, using usleep()"); } usleep(1000 * $sleep_msec); } elseif (function_exists('time_nanosleep')) { if ($log) { Factory::getLog()->log(LogLevel::DEBUG, "Sleeping for {$sleep_msec} msec, using 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')) { if ($log) { Factory::getLog()->log(LogLevel::DEBUG, "Sleeping for {$sleep_msec} msec, using time_sleep_until()"); } $until_timestamp = time() + $sleep_msec / 1000; time_sleep_until($until_timestamp); } elseif (function_exists('sleep')) { $sleep_sec = ceil($sleep_msec / 1000); if ($log) { Factory::getLog()->log(LogLevel::DEBUG, "Sleeping for {$sleep_sec} seconds, using sleep()"); } sleep($sleep_sec); } } elseif ($elapsed_time > 0) { // No sleep required, even if user configured us to be able to do so. if ($log) { Factory::getLog()->log(LogLevel::DEBUG, "No need to sleep; execution time: {$elapsed_time} msec; min. exec. time: {$minexectime} msec"); } } return $clientSideSleep; }
/** * Resets the engine state, wiping out any pending backups and/or stale * temporary data. * * @param array $config Configuration parameters for the reset operation */ public static function resetState($config = array()) { $default_config = array('global' => true, 'log' => false, 'maxrun' => 180); $config = (object) array_merge($default_config, $config); // Pause logging if so desired if (!$config->log) { Factory::getLog()->pause(); } $originTag = null; if (!$config->global) { // If we're not resetting globally, get a list of running backups per tag $originTag = Platform::getInstance()->get_backup_origin(); } // Cache the factory before proceeding $factory = self::serialize(); $runningList = Platform::getInstance()->get_running_backups($originTag); // Origins we have to clean $origins = array(Platform::getInstance()->get_backup_origin()); // 1. Detect failed backups if (is_array($runningList) && !empty($runningList)) { // The current timestamp $now = time(); // Mark running backups as failed foreach ($runningList as $running) { if (empty($originTag)) { // Check the timestamp of the log file to decide if it's stuck, // but only if a tag is not set $tstamp = Factory::getLog()->getLastTimestamp($running['origin']); if (!is_null($tstamp)) { // We can only check the timestamp if it's returned. If not, we assume the backup is stale $difference = abs($now - $tstamp); // Backups less than maxrun seconds old are not considered stale (default: 3 minutes) if ($difference < $config->maxrun) { continue; } } } $filenames = Factory::getStatistics()->get_all_filenames($running, false); $totalSize = 0; // Process if there are files to delete... if (!is_null($filenames)) { // Delete the failed backup's archive, if exists foreach ($filenames as $failedArchive) { if (file_exists($failedArchive)) { $totalSize += (int) @filesize($failedArchive); Platform::getInstance()->unlink($failedArchive); } } } // Mark the backup failed if (!$running['total_size']) { $running['total_size'] = $totalSize; } $running['status'] = 'fail'; $running['multipart'] = 0; $dummy = null; Platform::getInstance()->set_or_update_statistics($running['id'], $running, $dummy); $backupId = isset($running['backupid']) ? '.' . $running['backupid'] : ''; $origins[] = $running['origin'] . $backupId; } } if (!empty($origins)) { $origins = array_unique($origins); foreach ($origins as $originTag) { self::loadState($originTag); // Remove temporary files Factory::getTempFiles()->deleteTempFiles(); // Delete any stale temporary data self::getFactoryStorage()->reset($originTag); } } // Reload the factory self::unserialize($factory); unset($factory); // Unpause logging if it was previously paused if (!$config->log) { Factory::getLog()->unpause(); } }
/** * @param \Akeeba\Engine\Archiver\Base $archiver * @param \Akeeba\Engine\Configuration $configuration * * @return bool */ protected function postProcessDonePartFile(\Akeeba\Engine\Archiver\Base $archiver, \Akeeba\Engine\Configuration $configuration) { $filename = array_shift($archiver->finishedPart); Factory::getLog()->log(LogLevel::INFO, 'Preparing to post process ' . basename($filename)); // Add this part's size to the volatile storage $volatileTotalSize = $configuration->get('volatile.engine.archiver.totalsize', 0); $volatileTotalSize += (int) @filesize($filename); $configuration->set('volatile.engine.archiver.totalsize', $volatileTotalSize); $post_proc = Factory::getPostprocEngine(); $result = $post_proc->processPart($filename); $this->propagateFromObject($post_proc); if ($result === false) { $this->setWarning('Failed to process file ' . basename($filename)); } else { Factory::getLog()->log(LogLevel::INFO, 'Successfully processed file ' . basename($filename)); } // Should we delete the file afterwards? if ($configuration->get('engine.postproc.common.delete_after', false) && $post_proc->allow_deletes && $result !== false) { Factory::getLog()->log(LogLevel::DEBUG, 'Deleting already processed file ' . basename($filename)); Platform::getInstance()->unlink($filename); } if ($post_proc->break_after && $result !== false) { $configuration->set('volatile.breakflag', true); return true; } // This is required to let the backup continue even after a post-proc failure $this->resetErrors(); $this->setState('running'); return false; }
/** * Deletes all temporary files * * @return void */ public function deleteTempFiles() { $configuration = Factory::getConfiguration(); $serialised = $configuration->get('volatile.tempfiles', false); $tempFiles = array(); if ($serialised !== false) { $tempFiles = @unserialize($serialised); } if (!is_array($tempFiles)) { $tempFiles = array(); } $fileName = null; if (!empty($tempFiles)) { foreach ($tempFiles as $fileName) { Factory::getLog()->log(LogLevel::DEBUG, "-- Removing temporary file {$fileName}"); $file = $configuration->get('akeeba.basic.output_directory') . '/' . $fileName; $platform = strtoupper(PHP_OS); // TODO Like above, this shouldn't even work on Windows. I wonder why it's necessary. if (substr($platform, 0, 6) == 'CYGWIN' || substr($platform, 0, 3) == 'WIN') { // On Windows we have to chwon() the file first to make it owned by Nobody @chown($file, 600); } $ret = @$this->nullifyAndDelete($file); } } $tempFiles = array(); $configuration->set('volatile.tempfiles', serialize($tempFiles)); }
/** * Process all table dependencies * * @return void */ protected function process_dependencies() { if (count($this->table_name_map) > 0) { foreach ($this->table_name_map as $table_name => $table_abstract) { $this->push_table($table_name); } } Factory::getLog()->log(LogLevel::DEBUG, __CLASS__ . " :: Processed dependencies"); }
/** * Create a Central Directory temporary file * * @return void * * @throws ErrorException */ private function createCentralDirectoryTempFile() { $configuration = Factory::getConfiguration(); $this->centralDirectoryFilename = tempnam($configuration->get('akeeba.basic.output_directory'), 'akzcd'); $this->centralDirectoryFilename = basename($this->centralDirectoryFilename); $pos = strrpos($this->centralDirectoryFilename, '/'); if ($pos !== false) { $this->centralDirectoryFilename = substr($this->centralDirectoryFilename, $pos + 1); } $pos = strrpos($this->centralDirectoryFilename, '\\'); if ($pos !== false) { $this->centralDirectoryFilename = substr($this->centralDirectoryFilename, $pos + 1); } $this->centralDirectoryFilename = Factory::getTempFiles()->registerTempFile($this->centralDirectoryFilename); Factory::getLog()->log(LogLevel::DEBUG, __CLASS__ . " :: CntDir Tempfile = " . $this->centralDirectoryFilename); // Create temporary file if (!@touch($this->centralDirectoryFilename)) { throw new ErrorException("Could not open temporary file for ZIP archiver. Please check your temporary directory's permissions!"); } if (function_exists('chmod')) { chmod($this->centralDirectoryFilename, 0666); } }
protected function createDatabasesINI() { // caching databases.ini contents Factory::getLog()->log(LogLevel::DEBUG, __CLASS__ . "AkeebaCUBEDomainDBBackup :: Creating databases.ini data"); // Create a new string $this->databases_ini = ''; $blankOutPass = Factory::getConfiguration()->get('engine.dump.common.blankoutpass', 0); // Loop through databases list foreach ($this->dumpedDatabases as $definition) { $section = basename($definition['dumpFile']); $dboInstance = Factory::getDatabase($definition); $type = $dboInstance->name; $tech = $dboInstance->getDriverType(); if ($blankOutPass) { $this->databases_ini .= <<<ENDDEF [{$section}] dbtype = "{$type}" dbtech = "{$tech}" dbname = "{$definition['database']}" sqlfile = "{$definition['dumpFile']}" dbhost = "{$definition['host']}" dbuser = "" dbpass = "" prefix = "{$definition['prefix']}" parts = "{$definition['parts']}" ENDDEF; } else { // We have to escape the password $escapedPassword = addcslashes($definition['password'], "\"\\\n\r"); $this->databases_ini .= <<<ENDDEF [{$section}] dbtype = "{$type}" dbtech = "{$tech}" dbname = "{$definition['database']}" sqlfile = "{$definition['dumpFile']}" dbhost = "{$definition['host']}" dbuser = "******" dbpass = "******" prefix = "{$definition['prefix']}" parts = "{$definition['parts']}" ENDDEF; } } }
private function _apiGetLog($config) { $defConfig = array('tag' => 'remote'); $config = array_merge($defConfig, $config); $tag = $config['tag']; $filename = Factory::getLog()->getLogFilename($tag); $buffer = file_get_contents($filename); switch ($this->encapsulation) { case self::ENCAPSULATION_RAW: return base64_encode($buffer); break; case self::ENCAPSULATION_AESCTR128: $this->encapsulation = self::ENCAPSULATION_AESCBC128; return $buffer; break; case self::ENCAPSULATION_AESCTR256: $this->encapsulation = self::ENCAPSULATION_AESCBC256; return $buffer; break; default: // On encrypted comms the encryption will take care of transport encoding return $buffer; break; } }
/** * Invalidates older records sharing the same $archivename * * @param string $archivename */ public function remove_duplicate_backup_records($archivename) { Factory::getLog()->log(LogLevel::DEBUG, "Removing any old records with {$archivename} filename"); $db = Factory::getDatabase($this->get_platform_database_options()); $query = $db->getQuery(true)->select($db->qn('id'))->from($db->qn($this->tableNameStats))->where($db->qn('archivename') . ' = ' . $db->q($archivename))->order($db->qn('id') . ' DESC'); $db->setQuery($query); $array = $db->loadColumn(); Factory::getLog()->log(LogLevel::DEBUG, count($array) . " records found"); // No records?! Quit. if (empty($array)) { return; } // Only one record. Quit. if (count($array) == 1) { return; } // Shift the first (latest) element off the array $currentID = array_shift($array); // Invalidate older records $this->invalidate_backup_records($array); }
protected function _finalize() { // Open the log $logTag = $this->getLogTag(); Factory::getLog()->open($logTag); if (!static::$registeredErrorHandler) { static::$registeredErrorHandler = true; set_error_handler('\\Akeeba\\Engine\\Core\\akeebaBackupErrorHandler'); } // Kill the cached array $this->array_cache = null; // Remove the memory file $tempVarsTag = $this->tag . (empty($this->backup_id) ? '' : '.' . $this->backup_id); Factory::getFactoryStorage()->reset($tempVarsTag); // All done. Factory::getLog()->log(LogLevel::DEBUG, "Kettenrad :: Just finished"); $this->setState('finished'); // Send a push message to mark the end of backup $pushSubjectKey = $this->warnings_issued ? 'COM_AKEEBA_PUSH_ENDBACKUP_WARNINGS_SUBJECT' : 'COM_AKEEBA_PUSH_ENDBACKUP_SUCCESS_SUBJECT'; $pushBodyKey = $this->warnings_issued ? 'COM_AKEEBA_PUSH_ENDBACKUP_WARNINGS_BODY' : 'COM_AKEEBA_PUSH_ENDBACKUP_SUCCESS_BODY'; $platform = Platform::getInstance(); $timeStamp = date($platform->translate('DATE_FORMAT_LC2')); $pushSubject = sprintf($platform->translate($pushSubjectKey), $platform->get_site_name(), $platform->get_host()); $pushDetails = sprintf($platform->translate($pushBodyKey), $platform->get_site_name(), $platform->get_host(), $timeStamp); Factory::getPush()->message($pushSubject, $pushDetails); //restore_error_handler(); }
/** * Returns the relative and absolute path to the archive */ protected function getArchiveName() { $registry = Factory::getConfiguration(); // Import volatile scripting keys to the registry Factory::getEngineParamsProvider()->importScriptingToRegistry(); // Determine the extension $force_extension = Factory::getEngineParamsProvider()->getScriptingParameter('core.forceextension', null); if (is_null($force_extension)) { $archiver = Factory::getArchiverEngine(); $extension = $archiver->getExtension(); } else { $extension = $force_extension; } // Get the template name $templateName = $registry->get('akeeba.basic.archive_name'); Factory::getLog()->log(LogLevel::DEBUG, "Archive template name: {$templateName}"); // Parse all tags $fsUtils = Factory::getFilesystemTools(); $templateName = $fsUtils->replace_archive_name_variables($templateName); Factory::getLog()->log(LogLevel::DEBUG, "Expanded template name: {$templateName}"); $ds = DIRECTORY_SEPARATOR; $relative_path = $templateName . $extension; $absolute_path = $fsUtils->TranslateWinPath($registry->get('akeeba.basic.output_directory') . $ds . $relative_path); return array($relative_path, $absolute_path); }
/** * Called by Akeeba Engine when it needs to "add a file / folder to the archive". Since we're just doing file * scanning in this class we ignore directories and only perform scanning on files meeting our criteria (they * have the right extension). This is the brains of the scanner. * * @param boolean $isVirtual If true, the next parameter contains file data instead of a file name * @param string $sourceNameOrData Absolute file name to read data from or the file data itself is $isVirtual is * true * @param string $targetName The (relative) file name under which to store the file in the archive * * @return boolean True on success, false otherwise */ protected function _addFile($isVirtual, &$sourceNameOrData, $targetName) { if ($isVirtual) { return true; } $extensions = explode('|', \Akeeba\Engine\Factory::getConfiguration()->get('akeeba.basic.file_extensions', 'php|phps|php3|inc')); $ignore = true; foreach ($extensions as $extension) { if ('.' . $extension == substr($targetName, -(strlen($extension) + 1))) { $ignore = false; break; } } if ($ignore) { Factory::getLog()->log(LogLevel::DEBUG, "Skipped {$targetName}"); unset($extensions); return true; } unset($extensions); Factory::getLog()->log(LogLevel::DEBUG, "Scanning {$targetName} (extension: {$extension})"); // Count one more file scanned $multipart = \Akeeba\Engine\Factory::getConfiguration()->get('volatile.statistics.multipart', 0); $multipart++; \Akeeba\Engine\Factory::getConfiguration()->set('volatile.statistics.multipart', $multipart); $filedata = (object) array('path' => $targetName, 'filedate' => @filemtime($sourceNameOrData), 'filesize' => @filesize($sourceNameOrData), 'data' => '', 'checksum' => md5_file($sourceNameOrData), 'sourcePath' => $sourceNameOrData); if ($this->generateDiff) { $filedata->data = gzdeflate(@file_get_contents($sourceNameOrData), 9); } $db = \JFactory::getDbo(); if (class_exists('ReflectionClass') && count($db->getLog()) > 100) { // I need to reset the query log, otherwise it consumes all available memory and crashes our code. $mirror = new \ReflectionClass($db); if ($mirror->hasProperty('log')) { $property = $mirror->getProperty('log'); $property->setAccessible(true); $property->setValue($db, array()); } } $sql = $db->getQuery(true)->select('*')->from($db->qn('#__admintools_filescache'))->where($db->qn('path') . ' = ' . $db->q($targetName)); $db->setQuery($sql, 0, 1); $oldRecord = $db->loadObject(); if (!is_null($oldRecord)) { // Check for changes $fileModified = false; if ($oldRecord->filedate != $filedata->filedate) { $fileModified = true; } if ($oldRecord->filesize != $filedata->filesize) { $fileModified = true; } if ($oldRecord->checksum != $filedata->checksum) { $fileModified = true; } if ($fileModified) { // ### MODIFIED FILE ### $this->_logFileChange($filedata, $oldRecord); unset($oldRecord); // Replace the old record $sql = $db->getQuery(true)->delete($db->qn('#__admintools_filescache'))->where($db->qn('path') . ' = ' . $db->q($targetName)); $db->setQuery($sql); $db->execute(); unset($filedata->sourcePath); $db->insertObject('#__admintools_filescache', $filedata); } else { unset($oldRecord); // Existing file. Get the last log record. $sql = $db->getQuery(true)->select('*')->from($db->qn('#__admintools_scanalerts'))->where($db->qn('path') . ' = ' . $db->q($targetName))->order($db->qn('scan_id') . ' DESC'); $db->setQuery($sql, 0, 1); $lastRecord = $db->loadObject(); // If the file is not "acknowledged", we have to // check its threat score. if (is_object($lastRecord)) { if ($lastRecord->acknowledged) { unset($lastRecord); return true; } } unset($lastRecord); // Not acknowledged. Proceed. $text = @file_get_contents($sourceNameOrData); $threatScore = $this->_getThreatScore($text); if ($threatScore == 0) { return true; } // ### SUSPICIOUS EXISTING FILE ### // Still here? It's a possible threat! Log it as a modified file. $alertRecord = array('path' => $targetName, 'scan_id' => \Akeeba\Engine\Factory::getStatistics()->getId(), 'diff' => "###SUSPICIOUS FILE###\n", 'threat_score' => $threatScore, 'acknowledged' => 0); if ($this->generateDiff) { $alertRecord['diff'] = <<<ENDFILEDATA ###SUSPICIOUS FILE### >> Admin Tools detected that this file contains potentially suspicious code. >> This DOES NOT necessarily mean that it is a hacking script. There is always >> the possibility of a false alarm. The contents of the file are included >> below this line so that you can review them. {$text} ENDFILEDATA; } unset($text); $alertRecord = (object) $alertRecord; $db->insertObject('#__admintools_scanalerts', $alertRecord); unset($alertRecord); } } else { // ### NEW FILE ### $this->_logFileChange($filedata); // Add a new file record unset($filedata->sourcePath); $db->insertObject('#__admintools_filescache', $filedata); unset($filedata); } return true; }
/** * Returns the inclusion filters for a specific object type * * @param string $object The inclusion object (dir|db) * * @return array */ public function &getInclusions($object) { $inclusions = array(); if (!empty($this->filters)) { /** * @var string $filter_name * @var FilterBase $filter */ foreach ($this->filters as $filter_name => $filter) { if (!is_object($filter)) { Factory::getLog()->log(LogLevel::ERROR, "Object for filter {$filter_name} not found. The engine will now crash."); } $new_inclusions = $filter->getInclusions($object); if (!empty($new_inclusions)) { $inclusions = array_merge($inclusions, $new_inclusions); } } } return $inclusions; }
/** * Transforms a JPA archive (containing an installer) to the native archive format * of the class. It actually extracts the source JPA in memory and instructs the * class to include each extracted file. * * @codeCoverageIgnore * * @param integer $index The index in the source JPA archive's list currently in use * @param integer $offset The source JPA archive's offset to use * * @return boolean False if an error occurred, true otherwise */ public function transformJPA($index, $offset) { static $totalSize = 0; // Do we have to open the file? if (!$this->_xform_fp) { // Get the source path $registry = Factory::getConfiguration(); $embedded_installer = $registry->get('akeeba.advanced.embedded_installer'); // Fetch the name of the installer image $installerDescriptors = Factory::getEngineParamsProvider()->getInstallerList(); $xform_source = Platform::getInstance()->get_installer_images_path() . '/foobar.jpa'; // We need this as a "safe fallback" // Try to find a sane default if we are not given a valid embedded installer if (!array_key_exists($embedded_installer, $installerDescriptors)) { $embedded_installer = 'angie'; if (!array_key_exists($embedded_installer, $installerDescriptors)) { $allInstallers = array_keys($installerDescriptors); foreach ($allInstallers as $anInstaller) { if ($anInstaller == 'none') { continue; } $embedded_installer = $anInstaller; break; } } } if (array_key_exists($embedded_installer, $installerDescriptors)) { $packages = $installerDescriptors[$embedded_installer]['package']; if (empty($packages)) { // No installer package specified. Pretend we are done! $retArray = array("filename" => '', "data" => '', "index" => 0, "offset" => 0, "skip" => false, "done" => true, "filesize" => 0); return $retArray; } $packages = explode(',', $packages); $totalSize = 0; $pathPrefix = Platform::getInstance()->get_installer_images_path() . '/'; foreach ($packages as $package) { $filePath = $pathPrefix . $package; $totalSize += (int) @filesize($filePath); } if (count($packages) < $index) { $this->setError(__CLASS__ . ":: Installer package index {$index} not found for embedded installer {$embedded_installer}"); return false; } $package = $packages[$index]; // A package is specified, use it! $xform_source = $pathPrefix . $package; } // 2.3: Try to use sane default if the indicated installer doesn't exist if (!file_exists($xform_source) && basename($xform_source) != 'angie.jpa') { $this->setError(__CLASS__ . ":: Installer package {$xform_source} of embedded installer {$embedded_installer} not found. Please go to the configuration page, select an Embedded Installer, save the configuration and try backing up again."); return false; } // Try opening the file if (file_exists($xform_source)) { $this->_xform_fp = @fopen($xform_source, 'r'); if ($this->_xform_fp === false) { $this->setError(__CLASS__ . ":: Can't seed archive with installer package " . $xform_source); return false; } } else { $this->setError(__CLASS__ . ":: Installer package " . $xform_source . " does not exist!"); return false; } } $headerDataLength = 0; if (!$offset) { // First run detected! Factory::getLog()->log(LogLevel::DEBUG, 'Initializing with JPA package ' . $xform_source); // Skip over the header and check no problem exists $offset = $this->_xformReadHeader(); if ($offset === false) { $this->setError('JPA package file was not read'); return false; // Oops! The package file doesn't exist or is corrupt } $headerDataLength = $offset; } $ret = $this->_xformExtract($offset); $ret['index'] = $index; if (is_array($ret)) { $ret['chunkProcessed'] = $headerDataLength + $ret['offset'] - $offset; $offset = $ret['offset']; if (!$ret['skip'] && !$ret['done']) { Factory::getLog()->log(LogLevel::DEBUG, ' Adding ' . $ret['filename'] . '; Next offset:' . $offset); $this->addVirtualFile($ret['filename'], '', $ret['data']); if ($this->getError()) { return false; } } elseif ($ret['done']) { $registry = Factory::getConfiguration(); $embedded_installer = $registry->get('akeeba.advanced.embedded_installer'); $installerDescriptors = Factory::getEngineParamsProvider()->getInstallerList(); $packages = $installerDescriptors[$embedded_installer]['package']; $packages = explode(',', $packages); Factory::getLog()->log(LogLevel::DEBUG, ' Done with package ' . $packages[$index]); if (count($packages) > $index + 1) { $ret['done'] = false; $ret['index'] = $index + 1; $ret['offset'] = 0; $this->_xform_fp = null; } else { Factory::getLog()->log(LogLevel::DEBUG, ' Done with installer seeding.'); } } else { $reason = ' Skipping ' . $ret['filename']; Factory::getLog()->log(LogLevel::DEBUG, $reason); } } else { $this->setError('JPA extraction returned FALSE. The installer image is corrupt.'); return false; } if ($ret['done']) { // We are finished! Close the file fclose($this->_xform_fp); Factory::getLog()->log(LogLevel::DEBUG, 'Initializing with JPA package has finished'); } $ret['filesize'] = $totalSize; return $ret; }
protected function _finalize() { // Open the log $logTag = $this->getLogTag(); Factory::getLog()->open($logTag); if (!static::$registeredErrorHandler) { static::$registeredErrorHandler = true; set_error_handler('\\Akeeba\\Engine\\Core\\akeebaBackupErrorHandler'); } // Kill the cached array $this->array_cache = null; // Remove the memory file $tempVarsTag = $this->tag . (empty($this->backup_id) ? '' : '.' . $this->backup_id); Factory::getFactoryStorage()->reset($tempVarsTag); // All done. Factory::getLog()->log(LogLevel::DEBUG, "Kettenrad :: Just finished"); $this->setState('finished'); //restore_error_handler(); }
/** * Applies the size and count quotas * * @return bool True on success */ protected function get_remote_quotas() { // Get all records with a remote filename $allRecords = Platform::getInstance()->get_valid_remote_records(); // Bail out if no records found if (empty($allRecords)) { return array(); } // Try to find the files to be deleted due to quota settings $statistics = Factory::getStatistics(); $latestBackupId = $statistics->getId(); // Filter out the current record $temp = array(); foreach ($allRecords as $item) { if ($item['id'] == $latestBackupId) { continue; } $item['files'] = $this->get_remote_files($item['remote_filename'], $item['multipart']); $temp[] = $item; } $allRecords = $temp; // Bail out if only the current backup was included in the list if (count($allRecords) == 0) { return array(); } // Get quota values $registry = Factory::getConfiguration(); $countQuota = $registry->get('akeeba.quota.count_quota'); $sizeQuota = $registry->get('akeeba.quota.size_quota'); $useCountQuotas = $registry->get('akeeba.quota.enable_count_quota'); $useSizeQuotas = $registry->get('akeeba.quota.enable_size_quota'); $useDayQuotas = $registry->get('akeeba.quota.maxage.enable'); $daysQuota = $registry->get('akeeba.quota.maxage.maxdays'); $preserveDay = $registry->get('akeeba.quota.maxage.keepday'); $leftover = array(); $ret = array(); $killids = array(); if ($useDayQuotas) { $killDatetime = new \DateTime(); $killDatetime->modify('-' . $daysQuota . ($daysQuota == 1 ? ' day' : ' days')); $killTS = $killDatetime->format('U'); foreach ($allRecords as $def) { $backupstart = new \DateTime($def['backupstart']); $backupTS = $backupstart->format('U'); $backupDay = $backupstart->format('d'); // Is this on a preserve day? if ($preserveDay > 0) { if ($preserveDay == $backupDay) { $leftover[] = $def; continue; } } // Otherwise, check the timestamp if ($backupTS < $killTS) { $ret[] = $def['files']; $killids[] = $def['id']; } else { $leftover[] = $def; } } } // Do we need to apply count quotas? if ($useCountQuotas && $countQuota >= 1 && !$useDayQuotas) { $countQuota--; // Are there more files than the quota limit? if (!(count($allRecords) > $countQuota)) { // No, effectively skip the quota checking $leftover = $allRecords; } else { Factory::getLog()->log(LogLevel::DEBUG, "Processing remote count quotas"); // Yes, apply the quota setting. $totalRecords = count($allRecords); for ($count = 0; $count <= $totalRecords; $count++) { $def = array_pop($allRecords); if (count($leftover) >= $countQuota) { $ret[] = $def['files']; $killids[] = $def['id']; } else { $leftover[] = $def; } } unset($allRecords); } } else { // No count quotas are applied $leftover = $allRecords; } // Do we need to apply size quotas? if ($useSizeQuotas && $sizeQuota > 0 && count($leftover) > 0 && !$useDayQuotas) { Factory::getLog()->log(LogLevel::DEBUG, "Processing remote size quotas"); // OK, let's start counting bytes! $runningSize = 0; while (count($leftover) > 0) { // Each time, remove the last element of the backup array and calculate // running size. If it's over the limit, add the archive to the $ret array. $def = array_pop($leftover); $runningSize += $def['total_size']; if ($runningSize >= $sizeQuota) { $ret[] = $def['files']; $killids[] = $def['id']; } } } // Convert the $ret 2-dimensional array to single dimensional $quotaFiles = array(); foreach ($ret as $temp) { if (!is_array($temp) || empty($temp)) { continue; } foreach ($temp as $filename) { $quotaFiles[] = $filename; } } // Update the statistics record with the removed remote files if (!empty($killids)) { foreach ($killids as $id) { if (empty($id)) { continue; } $data = array('remote_filename' => ''); Platform::getInstance()->set_or_update_statistics($id, $data, $this); } } return $quotaFiles; }
/** * @param \Akeeba\Engine\Archiver\Base $archiver * @param \Akeeba\Engine\Configuration $configuration * * @return bool */ protected function postProcessDonePartFile(\Akeeba\Engine\Archiver\Base $archiver, \Akeeba\Engine\Configuration $configuration) { $filename = array_shift($archiver->finishedPart); Factory::getLog()->log(LogLevel::INFO, 'Preparing to post process ' . basename($filename)); $timer = Factory::getTimer(); $startTime = $timer->getRunningTime(); $post_proc = Factory::getPostprocEngine(); $result = $post_proc->processPart($filename); $this->propagateFromObject($post_proc); if ($result === false) { Factory::getLog()->log(LogLevel::WARNING, 'Failed to process file ' . $filename); Factory::getLog()->log(LogLevel::WARNING, 'Error received from the post-processing engine:'); Factory::getLog()->log(LogLevel::WARNING, implode("\n", array_merge($this->getWarnings(), $this->getErrors()))); $this->setWarning('Failed to process file ' . basename($filename)); } elseif ($result === true) { // Add this part's size to the volatile storage $volatileTotalSize = $configuration->get('volatile.engine.archiver.totalsize', 0); $volatileTotalSize += (int) @filesize($filename); $configuration->set('volatile.engine.archiver.totalsize', $volatileTotalSize); Factory::getLog()->log(LogLevel::INFO, 'Successfully processed file ' . basename($filename)); } else { // More work required Factory::getLog()->log(LogLevel::INFO, 'More post-processing steps required for file ' . $filename); $configuration->set('volatile.postproc.filename', $filename); // Let's push back the file into the archiver stack array_unshift($archiver->finishedPart, $filename); // Do we need to break the step? $endTime = $timer->getRunningTime(); $stepTime = $endTime - $startTime; $timeLeft = $timer->getTimeLeft(); if ($timeLeft < $stepTime) { // We predict that running yet another step would cause a timeout $configuration->set('volatile.breakflag', true); } else { // We have enough time to run yet another step $configuration->set('volatile.breakflag', false); } } // Should we delete the file afterwards? if ($configuration->get('engine.postproc.common.delete_after', false) && $post_proc->allow_deletes && $result === true) { Factory::getLog()->log(LogLevel::DEBUG, 'Deleting already processed file ' . basename($filename)); Platform::getInstance()->unlink($filename); } else { Factory::getLog()->log(LogLevel::DEBUG, 'Not removing processed file ' . $filename); } if ($post_proc->break_after && $result === true) { $configuration->set('volatile.breakflag', true); return true; } // This is required to let the backup continue even after a post-proc failure $this->resetErrors(); $this->setState('running'); return false; }
/** * Sends a push message, containing a URL/URI, to all connected devices. The URL will be rendered as something * clickable on most devices. * * @param string $url The URL/URI * @param string $subject The subject of the message, shown in the lock screen. Keep it short. * @param string $details Long(er) description of what the message is about. Plain text (no HTML). * * @return void */ public function link($url, $subject, $details = null) { if (!$this->enabled) { return; } try { $this->connector->pushLink('', $subject, $url, $details); } catch (\Exception $e) { Factory::getLog()->warning('Push messages suspended. Error received when trying to send push message with a link:' . $e->getMessage()); $this->enabled = false; } }