protected function _prepare() { AEUtilLogger::WriteLog(_AE_LOG_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'] : ''; } $options = array('driver' => $driver, 'host' => $host . ($port != '' ? ':' . $port : ''), 'user' => $username, 'password' => $password, 'database' => $database, 'prefix' => is_null($prefix) ? '' : $prefix); $db = AEFactory::getDatabase($options); $driverType = $db->getDriverType(); $className = 'AEDumpNative' . ucfirst($driverType); // Check if we have a native dump driver if (!class_exists($className, true)) { AEUtilLogger::WriteLog(_AE_LOG_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 = 'AEDumpReverse' . ucfirst($driverType); } if (!class_exists($className, true)) { $this->setState('error', 'Akeeba Engine does not have a native dump engine for ' . $driverType . ' databases'); } else { AEUtilLogger::WriteLog(_AE_LOG_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()); } }
/** * Implements the _run() abstract method */ function _run() { if ($this->getState() == 'postrun') { AEUtilLogger::WriteLog(_AE_LOG_DEBUG, __CLASS__ . " :: Already finished"); $this->setStep(''); $this->setSubstep(''); } else { $this->setState('running'); } // Try to step the archiver $archive = AEFactory::getArchiverEngine(); $ret = $archive->transformJPA($this->offset); // Error propagation $this->propagateFromObject($archive); if ($ret !== false && $archive->getError() == '') { $this->offset = $ret['offset']; $this->setStep($ret['filename']); } // Check for completion if ($ret['done']) { AEUtilLogger::WriteLog(_AE_LOG_DEBUG, __CLASS__ . ":: archive is initialized"); $this->setState('finished'); } // Calculate percentage $total_size = $ret['filesize']; if ($total_size > 0) { $this->progress = $this->offset / $total_size; } }
public function processPart($absolute_filename) { // Retrieve engine configuration data $config = AEFactory::getConfiguration(); $address = trim($config->get('engine.postproc.email.address', '')); $subject = $config->get('engine.postproc.email.subject', '0'); // Sanity checks if (empty($address)) { $this->setError('You have not set up a recipient\'s email address for the backup files'); return false; } // Send the file $basename = basename($absolute_filename); AEUtilLogger::WriteLog(_AE_LOG_INFO, "Preparing to email {$basename} to {$address}"); if (empty($subject)) { $subject = JText::_('AKEEBA_DEFAULT_EMAIL_SUBJECT'); } $body = "Emailing {$basename}"; AEUtilLogger::WriteLog(_AE_LOG_DEBUG, "Subject: {$subject}"); AEUtilLogger::WriteLog(_AE_LOG_DEBUG, "Body: {$body}"); $result = AEPlatform::getInstance()->send_email($address, $subject, $body, $absolute_filename); // Return the result if ($result !== true) { // An error occured $this->setError($result); // Notify that we failed return false; } else { // Return success AEUtilLogger::WriteLog(_AE_LOG_INFO, "Email sent successfully"); return true; } }
/** * 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; } AEUtilLogger::WriteLog(_AE_LOG_DEBUG, 'Updating multipart status to ' . $multipart); // Cache this change and commit to db only after the backup is done, or failed $registry = AEFactory::getConfiguration(); $registry->set('volatile.statistics.multipart', $multipart); }
/** * Resets the Kettenrad state, wipping out any pending backups and/or stale * temporary data. * * @param array $config Configuration parameters for the reset operation */ public static function reset($config = array()) { $default_config = array('global' => true, 'log' => false, 'maxrun' => 0); $config = (object) array_merge($default_config, $config); // Pause logging if so desired if (!$config->log) { AEUtilLogger::WriteLog(false, ''); } $tag = null; if (!$config->global) { // If we're not resetting globally, get a list of running backups per tag $tag = AEPlatform::getInstance()->get_backup_origin(); } // Cache the factory before proceeding $factory = AEFactory::serialize(); $runningList = AEPlatform::getInstance()->get_running_backups($tag); // Origins we have to clean $origins = array(AEPlatform::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($tag)) { // Check the timestamp of the log file to decide if it's stuck, // but only if a tag is not set $tstamp = @filemtime(AEUtilLogger::logName($running['origin'])); if ($tstamp !== false) { // 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 3 minutes old are not considered stale if ($difference < $config->maxrun) { continue; } } } $filenames = AEUtilStatistics::get_all_filenames($running, false); // Process if there are files to delete... if (!is_null($filenames)) { // Delete the failed backup's archive, if exists foreach ($filenames as $failedArchive) { AEPlatform::getInstance()->unlink($failedArchive); } } // Mark the backup failed $running['status'] = 'fail'; $running['multipart'] = 0; $dummy = null; AEPlatform::getInstance()->set_or_update_statistics($running['id'], $running, $dummy); $origins[] = $running['origin']; } } if (!empty($origins)) { $origins = array_unique($origins); foreach ($origins as $tag) { AECoreKettenrad::load($tag); // Remove temporary files AEUtilTempfiles::deleteTempFiles(); // Delete any stale temporary data AEUtilTempvars::reset($tag); } } // Reload the factory AEFactory::unserialize($factory); unset($factory); // Unpause logging if it was previously paused if (!$config->log) { AEUtilLogger::WriteLog(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 */ public final function tick($nesting = 0) { $configuration = AEFactory::getConfiguration(); $timer = AEFactory::getTimer(); // Call the right action method, depending on engine part state switch ($this->getState()) { case "init": $this->_prepare(); $breakFlag = $configuration->set('volatile.breakflag', false); break; case "prepared": $this->_run(); break; case "running": $this->_run(); break; case "postrun": $this->_finalize(); $breakFlag = $configuration->set('volatile.breakflag', false); break; } // If there is still time, we are not finished and there is no break flag set, re-run the tick() // method. $breakFlag = $configuration->get('volatile.breakflag', false); if (!in_array($this->getState(), array('finished', 'error')) && $timer->getTimeLeft() > 0 && !$breakFlag && $nesting < 20 && $this->nest_logging) { // Nesting is only applied if $this->nest_logging == true (currently only Kettenrad has this) $nesting++; if ($this->nest_logging) { AEUtilLogger::WriteLog(_AE_LOG_DEBUG, "*** Batching successive steps (nesting level {$nesting})"); } $out = $this->tick($nesting); } else { // Return the output array $out = $this->_makeReturnTable(); // Things to do for nest-logged parts (currently, only Kettenrad is) if ($this->nest_logging) { if ($breakFlag) { AEUtilLogger::WriteLog(_AE_LOG_DEBUG, "*** Engine steps batching: Break flag detected."); } // Reset the break flag $configuration->set('volatile.breakflag', false); // Log that we're breaking the step AEUtilLogger::WriteLog(_AE_LOG_DEBUG, "*** Batching of engine steps finished. I will now return control to the caller."); // Enforce minimum execution time $timer = AEFactory::getTimer(); $timer->enforce_min_exec_time(true); } } // Send a Return Table back to the caller return $out; }
function AkeebaTimeoutTrap() { if (connection_status() >= 2) { AEUtilLogger::WriteLog(_AE_LOG_ERROR, 'Akeeba Engine has timed out'); } }
public function processPart($absolute_filename) { // Retrieve engine configuration data $config = AEFactory::getConfiguration(); $accesskey = trim($config->get('engine.postproc.s3.accesskey', '')); $secret = trim($config->get('engine.postproc.s3.secretkey', '')); $usessl = $config->get('engine.postproc.s3.usessl', 0) == 0 ? false : true; $bucket = $config->get('engine.postproc.s3.bucket', ''); $legacy = $config->get('engine.postproc.s3.legacy', 0); $directory = $config->get('volatile.postproc.directory', null); $lowercase = $config->get('engine.postproc.s3.lowercase', 1); $rrs = $config->get('engine.postproc.s3.rrs', 1); $endpoint = $config->get('engine.postproc.s3.customendpoint', ''); if (empty($directory)) { $directory = $config->get('engine.postproc.s3.directory', 0); } if (!empty($directory)) { $directory = str_replace('\\', '/', $directory); $directory = rtrim($directory, '/'); } $endpoint = trim($endpoint); if (!empty($endpoint)) { $protoPos = strpos($endpoint, ':\\'); if ($protoPos !== false) { $endpoint = substr($endpoint, $protoPos + 3); } $slashPos = strpos($endpoint, '/'); if ($slashPos !== false) { $endpoint = substr($endpoint, $slashPos + 1); } } // Sanity checks if (empty($accesskey)) { $this->setError('You have not set up your Amazon S3 Access Key'); return false; } if (empty($secret)) { $this->setError('You have not set up your Amazon S3 Secret Key'); return false; } if (empty($bucket)) { $this->setError('You have not set up your Amazon S3 Bucket'); return false; } else { // Remove any slashes from the bucket $bucket = str_replace('/', '', $bucket); if ($lowercase) { $bucket = strtolower($bucket); } } // Create an S3 instance with the required credentials $s3 = AEUtilAmazons3::getInstance($accesskey, $secret, $usessl); if (!empty($endpoint)) { $s3->defaultHost = $endpoint; } // Do not use multipart uploads when in an immediate post-processing step, // i.e. we are uploading a part right after its creation $immediateEnabled = $config->get('engine.postproc.common.after_part', 0); if ($immediateEnabled) { $noMultipart = true; } else { $noMultipart = false; } // Disable multipart uploads if the user requested it if ($legacy) { $noMultipart = true; } // Are we already processing a multipart upload? if (!empty($this->cache)) { // Continue processing an existing file and return $filename = $this->cache->filename; $absolute_filename = $this->cache->absolute_filename; $partNumber = $this->cache->partnumber; $uploadID = $this->cache->uploadid; $etags = $this->cache->etags; $partNumber++; AEUtilLogger::WriteLog(_AE_LOG_DEBUG, "S3 -- Uploading part {$partNumber} of {$uploadID}"); // DEBUG /** $flatetags = implode(', ',$etags); AEUtilLogger::WriteLog(_AE_LOG_DEBUG,"S3 -- Absolute/relative filenames: $absolute_filename ### $filename"); AEUtilLogger::WriteLog(_AE_LOG_DEBUG,"S3 -- Etags: ".$flatetags); AEUtilLogger::WriteLog(_AE_LOG_DEBUG,"S3 -- Serialized cache: ".serialize($this->cache)); /**/ $fileInfo = AEUtilAmazons3::inputFile($absolute_filename, false); $fileInfo['UploadID'] = $uploadID; $fileInfo['etags'] = $etags; $fileInfo['PartNumber'] = $partNumber; $input = $fileInfo; // So that it doesn't get overwritten $etag = AEUtilAmazons3::uploadMultipart($input, $bucket, $filename); if ($etag === 0) { // Done uploading, finalize $this->cache = null; AEUtilLogger::WriteLog(_AE_LOG_DEBUG, "S3 -- Finalizing multipart upload of " . basename($absolute_filename) . " (part #{$partNumber})"); $result = AEUtilAmazonS3::finalizeMultipart($fileInfo, $bucket, $filename); $this->propagateFromObject($s3); if ($result === false) { // Finalization failed return false; } else { // Finalization successful return true; } } elseif ($etag === false) { // Upload failed $this->propagateFromObject($s3); return false; } else { // Successfully uploaded part $this->propagateFromObject($s3); $fileInfo['etags'][] = $etag; // Update stored values $this->cache->partnumber = $partNumber; $this->cache->etags = $fileInfo['etags']; $this->cache->uploadid = $fileInfo['UploadID']; // Return -1 so that we get called again return -1; } } // If we are here, we'll have to start uploading the file. Let's prepare ourselves for that. // Fix the directory name, if required if (!empty($directory)) { $directory = trim($directory); $directory = ltrim(AEUtilFilesystem::TranslateWinPath($directory), '/'); } else { $directory = ''; } // Parse tags $directory = AEUtilFilesystem::replace_archive_name_variables($directory); $config->set('volatile.postproc.directory', $directory); // Calculate relative remote filename $filename = basename($absolute_filename); if (!empty($directory) && $directory != '/') { $filename = $directory . '/' . $filename; } // Store the absolute remote path in the class property $this->remote_path = $filename; // Do we have to upload in one go or do a multipart upload instead? $filesize = @filesize($absolute_filename); if ($filesize > 5242880 && !$noMultipart) { AEUtilLogger::WriteLog(_AE_LOG_DEBUG, "S3 -- Starting multipart upload of " . basename($absolute_filename)); // Start multipart processing for files over 5Mb $fileInfo = AEUtilAmazons3::inputFile($absolute_filename, false); $input = $fileInfo; // Required to avoid the original array being tampered with $uploadID = AEUtilAmazonS3::startMultipart($input, $bucket, $filename, AEUtilAmazons3::ACL_BUCKET_OWNER_FULL_CONTROL, array(), array('x-amz-storage-class' => $rrs ? 'REDUCED_REDUNDANCY' : 'STANDARD')); // Necessary warnings propagation, no matter the outcome $this->propagateFromObject($s3); if ($uploadID === false) { // We couldn't start the upload. Bail out at once! return false; } else { // Save the information we need for multipart uploading $fileInfo['UploadID'] = $uploadID; $fileInfo['etags'] = array(); $cache = array('absolute_filename' => $absolute_filename, 'filename' => $filename, 'partnumber' => 0, 'uploadid' => $uploadID, 'etags' => array()); $this->cache = (object) $cache; // Return -1 so that we get called again, even on the same step return -1; } } else { AEUtilLogger::WriteLog(_AE_LOG_DEBUG, "S3 -- Legacy (single part) upload of " . basename($absolute_filename)); // Legacy single part uploads $result = $s3->putObject(AEUtilAmazons3::inputFile($absolute_filename, false), $bucket, $filename, AEUtilAmazons3::ACL_BUCKET_OWNER_FULL_CONTROL, array(), array('x-amz-storage-class' => $rrs ? 'REDUCED_REDUNDANCY' : 'STANDARD')); // Return the result $this->propagateFromObject($s3); return $result; } }
/** * 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 */ private 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) { AEUtilLogger::WriteLog(_AE_LOG_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; }
public function apply_srp_quotas($parent) { $parent->relayStep('Applying quotas'); $parent->relaySubstep(''); // If no quota settings are enabled, quit $registry =& AEFactory::getConfiguration(); $srpQuotas = $registry->get('akeeba.quota.srp_size_quota'); if($srpQuotas <= 0) { AEUtilLogger::WriteLog(_AE_LOG_DEBUG, "No restore point quotas were defined; old restore point files will be kept intact" ); return true; // No quota limits were requested } // Get valid-looking backup ID's $validIDs =& AEPlatform::get_valid_backup_records(true, array('restorepoint')); $statistics =& AEFactory::getStatistics(); $latestBackupId = $statistics->getId(); // Create a list of valid files $allFiles = array(); if(count($validIDs)) { foreach($validIDs as $id) { $stat = AEPlatform::get_statistics($id); // Multipart processing $filenames = AEUtilStatistics::get_all_filenames($stat, true); if(!is_null($filenames)) { // Only process existing files $filesize = 0; foreach($filenames as $filename) { $filesize += @filesize($filename); } $allFiles[] = array('id' => $id, 'filenames' => $filenames, 'size' => $filesize); } } } unset($validIDs); // If there are no files, exit early if(count($allFiles) == 0) { AEUtilLogger::WriteLog(_AE_LOG_DEBUG, "There were no old restore points to apply quotas on" ); return true; } // Init arrays $killids = array(); $ret = array(); $leftover = array(); // Do we need to apply size quotas? AEUtilLogger::WriteLog(_AE_LOG_DEBUG, "Processing restore point size quotas" ); // OK, let's start counting bytes! $runningSize = 0; while(count($allFiles) > 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 return array. $def = array_pop($allFiles); $runningSize += $def['size']; if($runningSize >= $srpQuotas) { if($latestBackupId == $def['id']) { $runningSize -= $def['size']; } else { $ret[] = $def['filenames']; $killids[] = $def['filenames']; } } } // Convert the $ret 2-dimensional array to single dimensional $quotaFiles = array(); foreach($ret as $temp) { foreach($temp as $filename) { $quotaFiles[] = $filename; } } // Update the statistics record with the removed remote files if(!empty($killids)) foreach($killids as $id) { $data = array('filesexist' => '0'); AEPlatform::set_or_update_statistics($id, $data, $parent); } // Apply quotas if(count($quotaFiles) > 0) { AEUtilLogger::WriteLog(_AE_LOG_DEBUG, "Applying quotas" ); jimport('joomla.filesystem.file'); foreach($quotaFiles as $file) { if(!@AEPlatform::unlink($file)) { $parent->setWarning("Failed to remove old system restore point file ".$file ); } } } return true; }
/** * Enforce the minimum execution time * * @param bool $log Should I log what I'm doing? Default is true. */ public function enforce_min_exec_time($log = 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 = AEFactory::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; // 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')) { if ($log) { AEUtilLogger::WriteLog(_AE_LOG_DEBUG, "Sleeping for {$sleep_msec} msec, using usleep()"); } usleep(1000 * $sleep_msec); } elseif (function_exists('time_nanosleep')) { if ($log) { AEUtilLogger::WriteLog(_AE_LOG_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) { AEUtilLogger::WriteLog(_AE_LOG_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) { AEUtilLogger::WriteLog(_AE_LOG_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) { AEUtilLogger::WriteLog(_AE_LOG_DEBUG, "No need to sleep; execution time: {$elapsed_time} msec; min. exec. time: {$minexectime} msec"); } } }
/** * Public constructor, loads filter data and filter classes */ public final function __construct() { static $initializing = false; parent::__construct(); // Call parent's constructor // Load filter data from platform's database AEUtilLogger::WriteLog(_AE_LOG_DEBUG, 'Fetching filter data from database'); $this->filter_registry =& AEPlatform::getInstance()->load_filters(); // Load platform, plugin and core filters $this->filters = array(); $locations = array(AEFactory::getAkeebaRoot() . DIRECTORY_SEPARATOR . 'platform' . DIRECTORY_SEPARATOR . AKEEBAPLATFORM . DIRECTORY_SEPARATOR . 'filters', AEFactory::getAkeebaRoot() . DIRECTORY_SEPARATOR . 'plugins' . DIRECTORY_SEPARATOR . 'filters', AEFactory::getAkeebaRoot() . DIRECTORY_SEPARATOR . 'filters'); AEUtilLogger::WriteLog(_AE_LOG_DEBUG, 'Loading filters'); foreach ($locations as $folder) { $is_platform = $folder == AEFactory::getAkeebaRoot() . DIRECTORY_SEPARATOR . 'platform' . DIRECTORY_SEPARATOR . AKEEBAPLATFORM . DIRECTORY_SEPARATOR . 'filters'; $files = AEUtilScanner::getFiles($folder); if ($files === false) { continue; } // Skip inexistent folders if (empty($files)) { continue; } // Skip no-match folders // Loop all files foreach ($files as $file) { if (substr($file, -4) != '.php') { continue; } // Skip non-PHP files if (in_array(substr($file, 0, 1), array('.', '_'))) { continue; } // Skip filter files starting with dot or dash $filter_name = ($is_platform ? 'Platform' : '') . ucfirst(basename($file, '.php')); // Extract filter base name if (array_key_exists($filter_name, $this->filters)) { continue; } // Skip already loaded filters AEUtilLogger::WriteLog(_AE_LOG_DEBUG, '-- Loading filter ' . $filter_name); $this->filters[$filter_name] =& AEFactory::getFilterObject($filter_name); // Add the filter } } // Load platform, plugin and core stacked filters $locations = array(AEFactory::getAkeebaRoot() . DIRECTORY_SEPARATOR . 'platform' . DIRECTORY_SEPARATOR . AKEEBAPLATFORM . DIRECTORY_SEPARATOR . 'filters' . DIRECTORY_SEPARATOR . 'stack', AEFactory::getAkeebaRoot() . DIRECTORY_SEPARATOR . 'plugins' . DIRECTORY_SEPARATOR . 'filters' . DIRECTORY_SEPARATOR . 'stack', AEFactory::getAkeebaRoot() . DIRECTORY_SEPARATOR . 'filters' . DIRECTORY_SEPARATOR . 'stack'); $config =& AEFactory::getConfiguration(); AEUtilLogger::WriteLog(_AE_LOG_DEBUG, 'Loading optional filters'); foreach ($locations as $folder) { $is_platform = $folder == AEFactory::getAkeebaRoot() . DIRECTORY_SEPARATOR . 'platform' . DIRECTORY_SEPARATOR . AKEEBAPLATFORM . DIRECTORY_SEPARATOR . 'filters' . DIRECTORY_SEPARATOR . 'stack'; $files = AEUtilScanner::getFiles($folder); if ($files === false) { continue; } // Skip inexistent folders if (empty($files)) { continue; } // Skip no-match folders // Loop all files foreach ($files as $file) { if (substr($file, -4) != '.php') { continue; } // Skip non-PHP files $bare_name = strtolower(basename($file, '.php')); $filter_name = 'Stack' . ($is_platform ? 'Platform' : '') . ucfirst(basename($file, '.php')); // Extract filter base name if (array_key_exists($filter_name, $this->filters)) { continue; } // Skip already loaded filters if (!file_exists(substr($file, 0, -4) . '.ini')) { continue; } // Make sure the INI file also exists $key = "core.filters.{$bare_name}.enabled"; if ($config->get($key, 0)) { AEUtilLogger::WriteLog(_AE_LOG_DEBUG, '-- Loading optional filter ' . $filter_name); $this->filters[$filter_name] =& AEFactory::getFilterObject($filter_name); // Add the filter } } } }
/** * Process all table dependencies * * @return null */ 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); } } AEUtilLogger::WriteLog(_AE_LOG_DEBUG, __CLASS__ . " :: Processed dependencies"); }
/** * 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. * * @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 final function transformJPA($index, $offset) { static $totalSize = 0; // Do we have to open the file? if (!$this->_xform_fp) { // Get the source path $registry = AEFactory::getConfiguration(); $embedded_installer = $registry->get('akeeba.advanced.embedded_installer'); // Fetch the name of the installer image $installerDescriptors = AEUtilInihelper::getInstallerList(); $xform_source = AEPlatform::getInstance()->get_installer_images_path() . DIRECTORY_SEPARATOR . 'foobar.jpa'; // We need this as a "safe fallback" 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; } else { $packages = explode(',', $packages); $totalSize = 0; $pathPrefix = AEPlatform::getInstance()->get_installer_images_path() . DIRECTORY_SEPARATOR; 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') { if ($index == 0) { $this->setWarning(__CLASS__ . ":: Selected embedded installer not found, using ANGIE instead"); $xform_source = dirname($xform_source) . '/angie.jpa'; } else { $this->setError(__CLASS__ . ":: Installer package {$xform_source} not found."); 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! AEUtilLogger::WriteLog(_AE_LOG_DEBUG, 'Initializing with JPA package ' . $xform_source); $offset = 0; // 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']) { AEUtilLogger::WriteLog(_AE_LOG_DEBUG, ' Adding ' . $ret['filename'] . '; Next offset:' . $offset); $this->addVirtualFile($ret['filename'], '', $ret['data']); if ($this->getError()) { return false; } } elseif ($ret['done']) { $registry = AEFactory::getConfiguration(); $embedded_installer = $registry->get('akeeba.advanced.embedded_installer'); $installerDescriptors = AEUtilInihelper::getInstallerList(); $packages = $installerDescriptors[$embedded_installer]['package']; $packages = explode(',', $packages); AEUtilLogger::WriteLog(_AE_LOG_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 { AEUtilLogger::WriteLog(_AE_LOG_DEBUG, ' Done with installer seeding.'); } } else { $reason = ' Skipping ' . $ret['filename']; AEUtilLogger::WriteLog(_AE_LOG_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); AEUtilLogger::WriteLog(_AE_LOG_DEBUG, 'Initializing with JPA package has finished'); } $ret['filesize'] = $totalSize; return $ret; }
/** * The most basic file transaction: add a single entry (file or directory) to * the archive. * * @param bool $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 True on success, false otherwise */ protected function _addFile($isVirtual, &$sourceNameOrData, $targetName) { if (!is_object($this->zip)) { return false; } if (!$isVirtual) { AEUtilLogger::WriteLog(_AE_LOG_DEBUG, "AEArchiverZipnative :: Adding {$sourceNameOrData}"); if (is_dir($sourceNameOrData)) { $result = $this->zip->addEmptyDir($targetName); } else { $this->runningSum += filesize($sourceNameOrData); $result = $this->zip->addFile($sourceNameOrData, $targetName); } } else { AEUtilLogger::WriteLog(_AE_LOG_DEBUG, ' Virtual add:' . $targetName . ' (' . strlen($sourceNameOrData) . ')'); $this->runningSum += strlen($sourceNameOrData); if (empty($sourceNameOrData)) { $result = $this->zip->addEmptyDir($targetName); } else { $result = $this->zip->addFromString($targetName, $sourceNameOrData); } } $this->zip->close(); $this->__bootstrap_code(); return true; }
/** * Applies the size and count quotas * * @return bool */ private function get_remote_quotas() { // Get all records with a remote filename $allRecords = AEPlatform::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 = AEFactory::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 = AEFactory::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 { AEUtilLogger::WriteLog(_AE_LOG_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) { AEUtilLogger::WriteLog(_AE_LOG_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' => ''); AEPlatform::getInstance()->set_or_update_statistics($id, $data, $this); } } return $quotaFiles; }
public function processPart($absolute_filename, $upload_as = null) { // Retrieve engine configuration data $config = AEFactory::getConfiguration(); $accesskey = trim($config->get('engine.postproc.googlestorage.accesskey', '')); $secret = trim($config->get('engine.postproc.googlestorage.secretkey', '')); $usessl = $config->get('engine.postproc.googlestorage.usessl', 0) == 0 ? false : true; $bucket = $config->get('engine.postproc.googlestorage.bucket', ''); $directory = $config->get('volatile.postproc.directory', null); $lowercase = $config->get('engine.postproc.googlestorage.lowercase', 1); if (empty($directory)) { $directory = $config->get('engine.postproc.googlestorage.directory', 0); } // Sanity checks if (empty($accesskey)) { $this->setError('You have not set up your Google Storage Access Key'); return false; } if (empty($secret)) { $this->setError('You have not set up your Google Storage Secret Key'); return false; } if (empty($bucket)) { $this->setError('You have not set up your Google Storage Bucket'); return false; } else { // Remove any slashes from the bucket $bucket = str_replace('/', '', $bucket); if ($lowercase) { $bucket = strtolower($bucket); } } // Create an S3 instance with the required credentials $s3 = AEUtilAmazons3::getInstance($accesskey, $secret, $usessl); $s3->defaultHost = 'commondatastorage.googleapis.com'; // If we are here, we'll have to start uploading the file. Let's prepare ourselves for that. // Fix the directory name, if required if (!empty($directory)) { $directory = trim($directory); $directory = ltrim(AEUtilFilesystem::TranslateWinPath($directory), '/'); } else { $directory = ''; } // Parse tags $directory = AEUtilFilesystem::replace_archive_name_variables($directory); $config->set('volatile.postproc.directory', $directory); // Calculate relative remote filename $filename = empty($upload_as) ? basename($absolute_filename) : $upload_as; if (!empty($directory) && $directory != '/') { $filename = $directory . '/' . $filename; } // Store the absolute remote path in the class property $this->remote_path = $filename; // Do we have to upload in one go or do a multipart upload instead? $filesize = @filesize($absolute_filename); AEUtilLogger::WriteLog(_AE_LOG_DEBUG, "Google Storage -- Uploading " . basename($absolute_filename)); // Legacy single part uploads $result = $s3->putObject(AEUtilAmazons3::inputFile($absolute_filename, false), $bucket, $filename, AEUtilAmazons3::ACL_BUCKET_OWNER_FULL_CONTROL, array(), array()); // Return the result $this->propagateFromObject($s3); return $result; }
/** * Uploads a file to the remote server * @param $sourceName string The absolute path to the source local file * @param $targetName string The relative path to the targer remote file * @return bool True if successful */ private function _upload($sourceName, $targetName) { // Try to change into the remote directory, possibly creating it if it doesn't exist $dir = dirname($targetName); if (!$this->_sftp_chdir($dir)) { return false; } // Upload $realdir = substr($this->_initdir, -1) == '/' ? substr($this->_initdir, 0, strlen($this->_initdir) - 1) : $this->_initdir; $realdir .= '/' . $dir; $realdir = substr($realdir, 0, 1) == '/' ? $realdir : '/' . $realdir; $realname = $realdir . '/' . basename($targetName); $fp = @fopen("ssh2.sftp://{$this->_sftphandle}{$realname}", 'w'); if ($fp === false) { AEUtilLogger::WriteLog(_AE_LOG_WARNING, "Could not open remote file {$realname} for writing"); return false; } $localfp = @fopen($sourceName, 'rb'); if ($localfp === false) { AEUtilLogger::WriteLog(_AE_LOG_WARNING, "Could not open local file {$sourceName} for reading"); @fclose($fp); return false; } $res = true; while (!feof($localfp) && $res !== false) { $buffer = @fread($localfp, 65567); $res = @fwrite($fp, $buffer); } @fclose($fp); @fclose($localfp); if ($res === false) { // If the file was unreadable, just skip it... if (is_readable($sourceName)) { $this->setError('Uploading ' . $targetName . ' has failed.'); return false; } else { $this->setWarning('Uploading ' . $targetName . ' has failed because the file is unreadable.'); return true; } } }
/** * Reverse engineers the View definitions of this database * * @param AEAbstractDriver $dbi Database connection to INFORMATION_SCHEMA */ protected function reverse_engineer_views(&$dbi) { // @TODO - THIS IS NOT PORTED TO SQL SERVER YET! $schema_name = $this->database; $sql = 'SELECT * FROM `views` WHERE `table_schema` = ' . $dbi->quote($schema_name); $dbi->setQuery($sql); $all_views = $dbi->loadObjectList(); $registry = AEFactory::getConfiguration(); $root = $registry->get('volatile.database.root', '[SITEDB]'); // If we have filters, make sure the tables pass the filtering $filters = AEFactory::getFilters(); // First pass: populate the table_name_map if (!empty($all_views)) { foreach ($all_views as $table_object) { // Extract the table name $table_name = $table_object->TABLE_NAME; // Filter and convert if (substr($table_name, 0, 3) == '#__') { AEUtilLogger::WriteLog(_AE_LOG_WARNING, __CLASS__ . " :: Table {$table_name} has a prefix of #__. This would cause restoration errors; table skipped."); continue; } $table_abstract = $this->getAbstract($table_name); if (substr($table_abstract, 0, 4) != 'bak_') { // Apply exclusion filters if (!$filters->isFiltered($table_abstract, $root, 'dbobject', 'all')) { AEUtilLogger::WriteLog(_AE_LOG_INFO, __CLASS__ . " :: Adding {$table_name} (internal name {$table_abstract})"); $this->table_name_map[$table_name] = $table_abstract; } else { AEUtilLogger::WriteLog(_AE_LOG_INFO, __CLASS__ . " :: Skipping {$table_name} (internal name {$table_abstract})"); continue; } } else { AEUtilLogger::WriteLog(_AE_LOG_INFO, __CLASS__ . " :: Backup table {$table_name} automatically skipped."); continue; } } } // Second pass: get the create commands if (!empty($all_views)) { foreach ($all_views as $table_object) { // Extract the table name $table_name = $table_object->TABLE_NAME; if (!in_array($table_name, $this->table_name_map)) { // Skip any views which have been filtered out continue; } $table_abstract = $this->getAbstract($table_name); // Still here? The view is added. We now have to store its // create command, dependency info and so on $new_entry = array('type' => 'view', 'dump_records' => false); $dependencies = array(); $table_sql = 'CREATE OR REPLACE VIEW `' . $table_name . '` AS ' . $table_object->VIEW_DEFINITION; $old_table_sql = $table_sql; foreach ($this->table_name_map as $ref_normal => $ref_abstract) { if ($pos = strpos($table_sql, "`{$ref_normal}`")) { // Add a reference hit $this->dependencies[$ref_normal][] = $table_name; // Add the dependency to this table's metadata $dependencies[] = $ref_normal; // Do the replacement $table_sql = str_replace("`{$ref_normal}`", "`{$ref_abstract}`", $table_sql); } } // On DB only backup we don't want any replacing to take place, do we? if (!AEUtilScripting::getScriptingParameter('db.abstractnames', 1)) { $table_sql = $old_table_sql; } // Replace newlines with spaces $table_sql = str_replace("\n", " ", $table_sql) . ";\n"; $table_sql = str_replace("\r", " ", $table_sql); $table_sql = str_replace("\t", " ", $table_sql); $new_entry['create'] = $table_sql; $new_entry['dependencies'] = $dependencies; $this->tables_data[$table_name] = $new_entry; } } }
/** * Creates a new archive part * @param bool $finalPart Set to true if it is the final part (therefore has the .jps extension) */ private function _createNewPart($finalPart = false) { // Push the previous part if we have to post-process it immediately $configuration = AEFactory::getConfiguration(); if ($configuration->get('engine.postproc.common.after_part', 0)) { $this->finishedPart[] = $this->_dataFileName; } $this->_totalFragments++; $this->_currentFragment = $this->_totalFragments; if ($finalPart) { $this->_dataFileName = $this->_dataFileNameBase . '.jps'; } else { $this->_dataFileName = $this->_dataFileNameBase . '.j' . sprintf('%02d', $this->_currentFragment); } AEUtilLogger::WriteLog(_AE_LOG_INFO, 'Creating new JPS part #' . $this->_currentFragment . ', file ' . $this->_dataFileName); // Inform that we have chenged the multipart number $statistics = AEFactory::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); } return $result; }
/** * Try to pack some files in the $file_list, restraining ourselves not to reach the max * number of files or max fragment size while doing so. If this process is over and we are * left without any more files, reset $done_scanning to false in order to instruct the class * to scan for more files. * * @return bool True if there were files packed, false otherwise (empty filelist) */ private function pack_files() { // Get a reference to the archiver and the timer classes $archiver = AEFactory::getArchiverEngine(); $timer = AEFactory::getTimer(); $configuration = AEFactory::getConfiguration(); // If post-processing after part creation is enabled, make sure we do post-process each part before moving on if ($configuration->get('engine.postproc.common.after_part', 0)) { if (!empty($archiver->finishedPart)) { $filename = array_shift($archiver->finishedPart); AEUtilLogger::WriteLog(_AE_LOG_INFO, 'Preparing to post process ' . basename($filename)); $post_proc = AEFactory::getPostprocEngine(); $result = $post_proc->processPart($filename); $this->propagateFromObject($post_proc); if ($result === false) { $this->setWarning('Failed to process file ' . basename($filename)); } else { AEUtilLogger::WriteLog(_AE_LOG_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) { AEUtilLogger::WriteLog(_AE_LOG_DEBUG, 'Deleting already processed file ' . basename($filename)); AEPlatform::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'); } } // If the archiver has work to do, make sure it finished up before continuing if ($configuration->get('volatile.engine.archiver.processingfile', false)) { AEUtilLogger::WriteLog(_AE_LOG_DEBUG, "Continuing file packing from previous step"); $result = $archiver->addFile('', '', ''); $this->propagateFromObject($archiver); if ($this->getError()) { return false; } // If that was the last step, mark a file done if (!$configuration->get('volatile.engine.archiver.processingfile', false)) { $this->progressMarkFileDone(); } } // Did it finish, or does it have more work to do? if ($configuration->get('volatile.engine.archiver.processingfile', false)) { // More work to do. Let's just tell our parent that we finished up successfully. return true; } // Normal file backup loop; we keep on processing the file list, packing files as we go. if (count($this->file_list) == 0) { // No files left to pack -- This should never happen! We catch this condition at the end of this method! $this->done_scanning = false; $this->progressMarkFolderDone(); return false; } else { AEUtilLogger::WriteLog(_AE_LOG_DEBUG, "Packing files"); $packedSize = 0; $numberOfFiles = 0; list($usec, $sec) = explode(" ", microtime()); $opStartTime = (double) $usec + (double) $sec; while (count($this->file_list) > 0) { $file = @array_shift($this->file_list); $size = 0; if (file_exists($file)) { $size = @filesize($file); } // Anticipatory file size algorithm if ($numberOfFiles > 0 && $size > AELargeFileThreshold) { if (!AEFactory::getConfiguration()->get('akeeba.tuning.nobreak.beforelargefile', 0)) { // If the file is bigger than the big file threshold, break the step // to avoid potential timeouts $this->setBreakFlag(); AEUtilLogger::WriteLog(_AE_LOG_INFO, "Breaking step _before_ large file: " . $file . " - size: " . $size); // Push the file back to the list. array_unshift($this->file_list, $file); // Mark that we are not done packing files $this->done_scanning = true; return true; } } // Proactive potential timeout detection // Rough estimation of packing speed in bytes per second list($usec, $sec) = explode(" ", microtime()); $opEndTime = (double) $usec + (double) $sec; if ($opEndTime - $opStartTime == 0) { $_packSpeed = 0; } else { $_packSpeed = $packedSize / ($opEndTime - $opStartTime); } // Estimate required time to pack next file. If it's the first file of this operation, // do not impose any limitations. $_reqTime = $_packSpeed - 0.01 <= 0 ? 0 : $size / $_packSpeed; // Do we have enough time? if ($timer->getTimeLeft() < $_reqTime) { if (!AEFactory::getConfiguration()->get('akeeba.tuning.nobreak.proactive', 0)) { array_unshift($this->file_list, $file); AEUtilLogger::WriteLog(_AE_LOG_INFO, "Proactive step break - file: " . $file . " - size: " . $size . " - req. time " . sprintf('%2.2f', $_reqTime)); $this->setBreakFlag(); $this->done_scanning = true; return true; } } $packedSize += $size; $numberOfFiles++; $ret = $archiver->addFile($file, $this->remove_path_prefix, $this->path_prefix); // If no more processing steps are required, mark a done file if (!$configuration->get('volatile.engine.archiver.processingfile', false)) { $this->progressMarkFileDone(); } // Error propagation $this->propagateFromObject($archiver); if ($this->getError()) { return false; } // If this was the first file of the fragment and it exceeded the fragment's capacity, // break the step. Continuing with more operations after packing such a big file is // increasing the risk to hit a timeout. if ($packedSize > AELargeFileThreshold && $numberOfFiles == 1) { if (!AEFactory::getConfiguration()->get('akeeba.tuning.nobreak.afterlargefile', 0)) { AEUtilLogger::WriteLog(_AE_LOG_INFO, "Breaking step *after* large file: " . $file . " - size: " . $size); $this->setBreakFlag(); return true; } } // If we have to continue processing the file, break the file packing loop forcibly if ($configuration->get('volatile.engine.archiver.processingfile', false)) { return true; } } $this->done_scanning = count($this->file_list) > 0; if (!$this->done_scanning) { $this->progressMarkFolderDone(); } return true; } }
/** * 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. * * @param integer $offset The source JPA archive's offset to use * * @return boolean False if an error occurred, true otherwise */ public final function transformJPA($offset) { // Do we have to open the file? if (!$this->_xform_fp) { // Get the source path $registry = AEFactory::getConfiguration(); $embedded_installer = $registry->get('akeeba.advanced.embedded_installer'); // Fetch the name of the installer image $installerDescriptors = AEUtilInihelper::getInstallerList(); $xform_source = AEPlatform::getInstance()->get_installer_images_path() . DIRECTORY_SEPARATOR . 'foobar.jpa'; // We need this as a "safe fallback" if (array_key_exists($embedded_installer, $installerDescriptors)) { $package = $installerDescriptors[$embedded_installer]['package']; if (empty($package)) { // No installer package specified. Pretend we are done! $retArray = array("filename" => '', "data" => '', "offset" => 0, "skip" => false, "done" => true, "filesize" => 0); return $retArray; } else { // A package is specified, use it! $xform_source = AEPlatform::getInstance()->get_installer_images_path() . DIRECTORY_SEPARATOR . $package; } } // 2.3: Try to use sane default if the indicated installer doesn't exist if (!file_exists($xform_source) && basename($xform_source) != 'abi.jpa') { $this->setWarning(__CLASS__ . ":: Selected embedded installer not found, using ABI instead"); $xform_source = dirname($xform_source) . '/abi.jpa'; } // 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; } } if (!$offset) { // First run detected! AEUtilLogger::WriteLog(_AE_LOG_DEBUG, 'Initializing with JPA package '); $offset = 0; // 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 } } $ret = $this->_xformExtract($offset); if (is_array($ret)) { $offset = $ret['offset']; if (!$ret['skip'] && !$ret['done']) { AEUtilLogger::WriteLog(_AE_LOG_DEBUG, ' Adding ' . $ret['filename'] . '; Next offset:' . $offset); $this->addVirtualFile($ret['filename'], '', $ret['data']); if ($this->getError()) { return false; } } else { $reason = $ret['done'] ? 'Done' : ' Skipping ' . $ret['filename']; AEUtilLogger::WriteLog(_AE_LOG_DEBUG, $reason); } } else { $this->setError('JPA extraction returned FALSE'); return false; } if ($ret['done']) { // We are finished! Close the file fclose($this->_xform_fp); AEUtilLogger::WriteLog(_AE_LOG_DEBUG, 'Initializing with JPA package has finished'); } $ret['filesize'] = isset($xform_source) ? @filesize($xform_source) : 0; return $ret; }
/** * Joomla!-specific function to get an instance of the mailer class * @return JMail */ public function &getMailer() { $mailer = JFactory::getMailer(); if (!is_object($mailer)) { AEUtilLogger::WriteLog(_AE_LOG_WARNING, "Fetching Joomla!'s mailer was impossible; imminent crash!"); } else { $emailMethod = $mailer->Mailer; AEUtilLogger::WriteLog(_AE_LOG_DEBUG, "-- Joomla!'s mailer is using {$emailMethod} mail method."); } return $mailer; }
/** * Deletes all temporary files */ static function deleteTempFiles() { $configuration = AEFactory::getConfiguration(); $tempFiles = $configuration->get('volatile.tempfiles', false); if ($tempFiles === false) { $tempFiles = array(); } else { $tempFiles = @unserialize($tempFiles); } $fileName = null; if (!empty($tempFiles)) { foreach ($tempFiles as $fileName) { AEUtilLogger::WriteLog(_AE_LOG_DEBUG, "-- Removing temporary file {$fileName}"); $file = $configuration->get('akeeba.basic.output_directory') . '/' . $fileName; $platform = strtoupper(PHP_OS); 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 = @self::nullifyAndDelete($file); } } $tempFiles = array(); $configuration->set('volatile.tempfiles', serialize($tempFiles)); }
private function createDatabasesINI() { // caching databases.ini contents AEUtilLogger::WriteLog(_AE_LOG_DEBUG, __CLASS__ . "AkeebaCUBEDomainDBBackup :: Creating databases.ini data"); // Create a new string $databasesINI = ''; // Loop through databases list foreach ($this->dumpedDatabases as $definition) { $section = basename($definition['dumpFile']); $this->databases_ini .= <<<ENDDEF [{$section}] dbname = "{$definition['database']}" sqlfile = "{$definition['dumpFile']}" dbhost = "{$definition['host']}" dbuser = "******" dbpass = "******" prefix = "{$definition['prefix']}" parts = "{$definition['parts']}" ENDDEF; } }
/** * Invalidates older records sharing the same $archivename * @param string $archivename */ public function remove_duplicate_backup_records($archivename) { AEUtilLogger::WriteLog(_AE_LOG_DEBUG, "Removing any old records with {$archivename} filename"); $db = AEFactory::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); if (version_compare(JVERSION, '3.0', 'ge')) { $array = $db->loadColumn(); } else { $array = $db->loadResultArray(); } AEUtilLogger::WriteLog(_AE_LOG_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); }
/** * Implements the _run() abstract method */ protected function _run() { if ($this->getState() == 'postrun') { AEUtilLogger::WriteLog(_AE_LOG_DEBUG, __CLASS__ . " :: Already finished"); $this->setStep(''); $this->setSubstep(''); return; } else { $this->setState('running'); } // Load the version defines AEPlatform::getInstance()->load_version_defines(); $registry = AEFactory::getConfiguration(); // Write log file's header AEUtilLogger::WriteLog(_AE_LOG_INFO, "--------------------------------------------------------------------------------"); AEUtilLogger::WriteLog(_AE_LOG_INFO, "Akeeba Backup " . AKEEBA_VERSION . ' (' . AKEEBA_DATE . ')'); AEUtilLogger::WriteLog(_AE_LOG_INFO, "Got backup?"); AEUtilLogger::WriteLog(_AE_LOG_INFO, "--------------------------------------------------------------------------------"); // PHP configuration variables are tried to be logged only for debug and info log levels if ($registry->get('akeeba.basic.log_level') >= _AE_LOG_INFO) { AEUtilLogger::WriteLog(_AE_LOG_INFO, "--- System Information ---"); AEUtilLogger::WriteLog(_AE_LOG_INFO, "PHP Version :" . PHP_VERSION); AEUtilLogger::WriteLog(_AE_LOG_INFO, "PHP OS :" . PHP_OS); AEUtilLogger::WriteLog(_AE_LOG_INFO, "PHP SAPI :" . PHP_SAPI); if (function_exists('php_uname')) { AEUtilLogger::WriteLog(_AE_LOG_INFO, "OS Version :" . php_uname('s')); } $db = AEFactory::getDatabase(); AEUtilLogger::WriteLog(_AE_LOG_INFO, "DB Version :" . $db->getVersion()); if (isset($_SERVER['SERVER_SOFTWARE'])) { $server = $_SERVER['SERVER_SOFTWARE']; } else { if ($sf = getenv('SERVER_SOFTWARE')) { $server = $sf; } else { $server = 'n/a'; } } AEUtilLogger::WriteLog(_AE_LOG_INFO, "Web Server :" . $server); $platform = 'Unknown platform'; $version = '(unknown version)'; $platformData = AEPlatform::getInstance()->getPlatformVersion(); AEUtilLogger::WriteLog(_AE_LOG_INFO, $platformData['name'] . " version :" . $platformData['version']); if (isset($_SERVER['HTTP_USER_AGENT'])) { AEUtilLogger::WriteLog(_AE_LOG_INFO, "User agent :" . phpversion() <= "4.2.1" ? getenv("HTTP_USER_AGENT") : $_SERVER['HTTP_USER_AGENT']); } AEUtilLogger::WriteLog(_AE_LOG_INFO, "Safe mode :" . ini_get("safe_mode")); AEUtilLogger::WriteLog(_AE_LOG_INFO, "Display errors :" . ini_get("display_errors")); AEUtilLogger::WriteLog(_AE_LOG_INFO, "Error reporting :" . self::error2string()); AEUtilLogger::WriteLog(_AE_LOG_INFO, "Error display :" . self::errordisplay()); AEUtilLogger::WriteLog(_AE_LOG_INFO, "Disabled functions :" . ini_get("disable_functions")); AEUtilLogger::WriteLog(_AE_LOG_INFO, "open_basedir restr.:" . ini_get('open_basedir')); AEUtilLogger::WriteLog(_AE_LOG_INFO, "Max. exec. time :" . ini_get("max_execution_time")); AEUtilLogger::WriteLog(_AE_LOG_INFO, "Memory limit :" . ini_get("memory_limit")); if (function_exists("memory_get_usage")) { AEUtilLogger::WriteLog(_AE_LOG_INFO, "Current mem. usage :" . memory_get_usage()); } if (function_exists("gzcompress")) { AEUtilLogger::WriteLog(_AE_LOG_INFO, "GZIP Compression : available (good)"); } else { AEUtilLogger::WriteLog(_AE_LOG_INFO, "GZIP Compression : n/a (no compression)"); } AEPlatform::getInstance()->log_platform_special_directories(); AEUtilLogger::WriteLog(_AE_LOG_INFO, "Output directory :" . $registry->get('akeeba.basic.output_directory')); AEUtilLogger::WriteLog(_AE_LOG_INFO, "--------------------------------------------------------------------------------"); } // Quirks reporting $quirks = AEUtilQuirks::get_quirks(true); if (!empty($quirks)) { AEUtilLogger::WriteLog(_AE_LOG_INFO, "Akeeba Backup has detected the following potential problems:"); foreach ($quirks as $q) { AEUtilLogger::WriteLog(_AE_LOG_INFO, '- ' . $q['code'] . ' ' . $q['description'] . ' (' . $q['severity'] . ')'); } AEUtilLogger::WriteLog(_AE_LOG_INFO, "You probably do not have to worry about them, but you should be aware of them."); AEUtilLogger::WriteLog(_AE_LOG_INFO, "--------------------------------------------------------------------------------"); } if (!version_compare(PHP_VERSION, '5.3.0', 'ge')) { AEUtilLogger::WriteLog(_AE_LOG_WARNING, "You are using an outdated version of PHP. Akeeba Engine may not work properly. Please upgrade to PHP 5.3 or later."); } // Report profile ID $profile_id = AEPlatform::getInstance()->get_active_profile(); AEUtilLogger::WriteLog(_AE_LOG_INFO, "Loaded profile #{$profile_id}"); // Get archive name AEUtilFilesystem::get_archive_name($relativeArchiveName, $absoluteArchiveName); // ==== Stats initialisation === $origin = AEPlatform::getInstance()->get_backup_origin(); // Get backup origin $profile_id = AEPlatform::getInstance()->get_active_profile(); // Get active profile $registry = AEFactory::getConfiguration(); $backupType = $registry->get('akeeba.basic.backup_type'); AEUtilLogger::WriteLog(_AE_LOG_DEBUG, "Backup type is now set to '" . $backupType . "'"); // Substitute "variables" in the archive name $description = AEUtilFilesystem::replace_archive_name_variables($this->description); $comment = AEUtilFilesystem::replace_archive_name_variables($this->comment); if ($registry->get('volatile.writer.store_on_server', true)) { // Archive files are stored on our server $stat_relativeArchiveName = $relativeArchiveName; $stat_absoluteArchiveName = $absoluteArchiveName; } else { // Archive files are not stored on our server (FTP backup, cloud backup, sent by email, etc) $stat_relativeArchiveName = ''; $stat_absoluteArchiveName = ''; } $kettenrad = AEFactory::getKettenrad(); $temp = array('description' => $description, 'comment' => $comment, 'backupstart' => AEPlatform::getInstance()->get_timestamp_database(), 'status' => 'run', 'origin' => $origin, 'type' => $backupType, 'profile_id' => $profile_id, 'archivename' => $stat_relativeArchiveName, 'absolute_path' => $stat_absoluteArchiveName, 'multipart' => 0, 'filesexist' => 1, 'tag' => $kettenrad->getTag()); // Save the entry $statistics = AEFactory::getStatistics(); $statistics->setStatistics($temp); if ($statistics->getError()) { $this->setError($statistics->getError()); return; } $statistics->release_multipart_lock(); // Initialize the archive. if (AEUtilScripting::getScriptingParameter('core.createarchive', true)) { AEUtilLogger::WriteLog(_AE_LOG_DEBUG, "Expanded archive file name: " . $absoluteArchiveName); AEUtilLogger::WriteLog(_AE_LOG_DEBUG, "Initializing archiver engine"); $archiver = AEFactory::getArchiverEngine(); $archiver->initialize($absoluteArchiveName); $archiver->setComment($comment); // Add the comment to the archive itself. $archiver->propagateToObject($this); if ($this->getError()) { return; } } $this->setState('postrun'); }
public function closeFile() { AEUtilLogger::WriteLog(_AE_LOG_DEBUG, "Closing SQL dump file."); if (is_resource($this->fp)) { @fclose($this->fp); $this->fp = null; } }
/** * Performs a chunk upload * * @param string $file Local path to the file that will be uploaded * @param string $path Path on DropBox * @param int $size Size of the chunks (Mb) * @param string $filename Name of the file created on DropBox * @param bool $overwrite Should I overwrite the file? * * @throws AEUtilDropboxException * @return object|string */ public function putChunkedFile($file, $path, $size = 1, $filename = '', $overwrite = true) { $chunkSize = 1024 * 1024 * $size; $this->chunked = true; AEUtilLogger::WriteLog(_AE_LOG_DEBUG, __CLASS__ . '::' . __METHOD__ . " - Starting chunked transfer with chunk size of {$size} Mb ({$chunkSize} bytes)"); $handle = fopen($file, 'rb+'); $read = 0; $retry = 0; while (!feof($handle)) { AEUtilLogger::WriteLog(_AE_LOG_DEBUG, __CLASS__ . '::' . __METHOD__ . " *** New chunk iteration, offset {$read}"); // Retry only 3 times, after that abort the upload if ($retry > 3) { throw new AEUtilDropboxException('Could not upload chunk to DropBox. Tried 3 times, aborting'); } AEUtilLogger::WriteLog(_AE_LOG_DEBUG, __CLASS__ . '::' . __METHOD__ . " - Reading {$chunkSize} bytes"); $chunk = $this->readFully($handle, $chunkSize); $params = array('upload_id' => $this->upload_id, 'offset' => $read); $read += $chunkSize; // Prevent exception throwing, so I can handle the offset error AEUtilLogger::WriteLog(_AE_LOG_DEBUG, __CLASS__ . '::' . __METHOD__ . " - Uploading with upload_id = {$this->upload_id}"); $response = $this->fetch('PUT', self::CONTENT_URL, 'chunked_upload', $params, $chunk, false); // Wrong offset, let's rollback to the correct one if ($response['code'] == 400) { $read = $response['body']->offset; AEUtilLogger::WriteLog(_AE_LOG_DEBUG, __CLASS__ . '::' . __METHOD__ . " - Got HTTP 400. Rewinding to offset {$read}. Retry count: {$retry}"); fseek($handle, $read); $retry += 1; usleep(200); continue; } // If I'm here, let's reset the counter $retry = 0; if (!$this->upload_id) { AEUtilLogger::WriteLog(_AE_LOG_DEBUG, __CLASS__ . '::' . __METHOD__ . " - Got new upload_id = {$this->upload_id}"); $this->upload_id = $response['body']->upload_id; } } AEUtilLogger::WriteLog(_AE_LOG_DEBUG, __CLASS__ . '::' . __METHOD__ . " *** Done uploading {$size}M chunks"); // This will have to change, now it's just to have a filename if (!$filename) { $filename = basename($file); } AEUtilLogger::WriteLog(_AE_LOG_DEBUG, __CLASS__ . '::' . __METHOD__ . " - Committing chunked upload. Overwrite is set to " . (int) $overwrite); $call = 'commit_chunked_upload/' . $this->root . '/' . $this->encodePath($path . '/' . $filename); $params = array('overwrite' => (int) $overwrite, 'upload_id' => $this->upload_id); $response = $this->fetch('POST', self::CONTENT_URL, $call, $params); return $response; }
private function _createNewPart($finalPart = false) { // Push the previous part if we have to post-process it immediately $configuration =& AEFactory::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); } AEUtilLogger::WriteLog(_AE_LOG_INFO, 'Creating new JPA part #' . $this->_currentFragment . ', file ' . $this->_dataFileName); $statistics =& AEFactory::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; }