git-svn-id: https://192.168.0.254/svn/Proyectos.FundacionLQDVI_WebCongresos/trunk@2 94ccb1af-fd9d-d947-8d90-7f70ea60afc8
607 lines
17 KiB
PHP
607 lines
17 KiB
PHP
<?php
|
|
/*
|
|
* Akeeba Backup Lazy Scheduling
|
|
* Copyright (C) 2010 Nicholas K. Dionysopoulos / AkeebaBackup.com
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*
|
|
*
|
|
* Thank you notice:
|
|
* Many thanks to Jean-Sebastien Gervais of LazyBackup.net for proving that
|
|
* backup triggered by visitor activity is possible, essentially inspiring the
|
|
* functionality of this plugin.
|
|
*/
|
|
|
|
// Protect from unauthorized access
|
|
defined('_JEXEC') or die('Restricted Access');
|
|
|
|
class plgSystemAklazy extends JPlugin
|
|
{
|
|
/** @var string A nonce (token) to validate requests */
|
|
private $nonce = null;
|
|
|
|
private $locked = 0;
|
|
|
|
private $tstamp = 0;
|
|
|
|
/** @var bool Did the last backup crash? */
|
|
private $isCrashed = false;
|
|
|
|
private $debugInfo = '';
|
|
|
|
public function __construct(& $subject, $config = array())
|
|
{
|
|
// Use the parent constructor to create the plugin object
|
|
parent::__construct($subject, $config);
|
|
|
|
// Check if we have to disable ourself
|
|
$akreset = JRequest::getCmd('akreset','');
|
|
$defaultpw = $this->params->get('resetpw','');
|
|
if( ($akreset == $defaultpw) && !empty($defaultpw) )
|
|
{
|
|
// Disable the plugin
|
|
$db = JFactory::getDBO();
|
|
|
|
if( version_compare( JVERSION, '1.6.0', 'ge' ) ) {
|
|
$sql = 'UPDATE `#__extensions` SET `enabled` = 0 WHERE `type` = \'plugin\' AND `element` = \'aklazy\'';
|
|
} else {
|
|
$sql = 'UPDATE #__plugins SET `published` = 0 WHERE `element` = \'aklazy\'';
|
|
}
|
|
$db->setQuery($sql);
|
|
$db->query();
|
|
|
|
// Load the configuration
|
|
$profile = (int)$this->params->get('profile',1);
|
|
if($profile <= 0) $profile = 1;
|
|
$session =& JFactory::getSession();
|
|
$session->set('profile', $profile, 'akeeba');
|
|
AEPlatform::load_configuration($profile);
|
|
|
|
// Remove the log files
|
|
$logfile = AEUtilLogger::logName(null);
|
|
@unlink($logfile);
|
|
AEUtilLogger::ResetLog('lazy');
|
|
|
|
// Clear lock
|
|
$this->unsetLock();
|
|
$this->unsetNonce();
|
|
|
|
// Reset pending backups
|
|
AECoreKettenrad::reset();
|
|
|
|
// Redirect
|
|
$app = JFactory::getApplication();
|
|
$app->redirect('index.php');
|
|
|
|
return;
|
|
}
|
|
|
|
// Hijack the application to do the backup steps if aklazy and nonce
|
|
// params are defined in the URL query
|
|
$aklazy = JRequest::getCmd('aklazy',null);
|
|
$nonce = JRequest::getCmd('nonce',null);
|
|
|
|
// Load the settings
|
|
$profile = (int)$this->params->get('profile',1);
|
|
if($profile <= 0) $profile = 1;
|
|
AEPlatform::load_configuration($profile);
|
|
$config = AEFactory::getConfiguration();
|
|
$this->locked = $config->get('lazy.lock.status', 0);
|
|
$this->tstamp = $config->get('lazy.lock.stamp', 0);
|
|
$this->nonce = $config->get('lazy.nonce', null);
|
|
|
|
// When aklazy is 'check', it returns a backup URL, or dies if there's
|
|
// no need to start/step a backup.
|
|
if( ($aklazy == 'check') )
|
|
{
|
|
// Do a backup necessity check and return a URL or nothing at all
|
|
$state = $this->getBackupState();
|
|
if($state != 'none')
|
|
{
|
|
$url = JURI::base().'index.php?aklazy='.$state.'&nonce='.$this->nonce;
|
|
}
|
|
else
|
|
{
|
|
$url = '';
|
|
}
|
|
@ob_end_clean(); // Just in case...
|
|
echo('###'.$url.'###');
|
|
die();
|
|
}
|
|
|
|
if( (in_array($aklazy, array('start','step','ajaxstart','ajaxstep'))) && !empty($nonce) )
|
|
{
|
|
// Make sure we're not locked
|
|
if($this->isLocked()) return;
|
|
|
|
// Get the saved nonce and compare it to the one in the URL
|
|
$this->getNonce();
|
|
if(empty($this->nonce)) return;
|
|
if($this->nonce != $nonce) return;
|
|
|
|
// Lock the backup process
|
|
if($this->isLocked()) return;
|
|
$this->setLock();
|
|
|
|
// Update the nonce
|
|
$this->setNonce();
|
|
|
|
// Try to convince PHP to not abort the request when the user is disconnected
|
|
if(function_exists('ignore_user_abort')) {
|
|
ignore_user_abort(true);
|
|
}
|
|
|
|
// Define the basic constants for the Akeeba Engine
|
|
if(!defined('AKEEBA_BACKUP_ORIGIN')) {
|
|
define('AKEEBA_BACKUP_ORIGIN','lazy'); // Set the backup origin
|
|
}
|
|
if(!defined('AKEEBAENGINE')) {
|
|
define('AKEEBAENGINE', 1); // Required for accessing Akeeba Engine's factory class
|
|
define('AKEEBAPLATFORM', 'joomla15'); // So that platform-specific stuff can get done!
|
|
}
|
|
// Load the Akeeba Engine factory
|
|
require_once JPATH_ADMINISTRATOR.DS.'components'.DS.'com_akeeba'.DS.'akeeba'.DS.'factory.php';
|
|
|
|
// Load the language files
|
|
$jlang =& JFactory::getLanguage();
|
|
$jlang->load('com_akeeba', JPATH_SITE, 'en-GB', true);
|
|
$jlang->load('com_akeeba', JPATH_SITE, $jlang->getDefault(), true);
|
|
$jlang->load('com_akeeba', JPATH_SITE, null, true);
|
|
$jlang->load('com_akeeba', JPATH_ADMINISTRATOR, 'en-GB', true);
|
|
$jlang->load('com_akeeba', JPATH_ADMINISTRATOR, $jlang->getDefault(), true);
|
|
$jlang->load('com_akeeba', JPATH_ADMINISTRATOR, null, true);
|
|
|
|
// Set the profile and load the configuration
|
|
$profile = (int)$this->params->get('profile',1);
|
|
if($profile <= 0) $profile = 1;
|
|
$session =& JFactory::getSession();
|
|
$session->set('profile', $profile, 'akeeba');
|
|
AEPlatform::load_configuration($profile);
|
|
|
|
$isDone = false;
|
|
register_shutdown_function('AkeebaBackupLazyShutdown');
|
|
if(in_array($aklazy,array('start','ajaxstart')))
|
|
{
|
|
// Start a new backup
|
|
AECoreKettenrad::reset();
|
|
$kettenrad =& AECoreKettenrad::load(AKEEBA_BACKUP_ORIGIN);
|
|
$user =& JFactory::getUser();
|
|
$userTZ = $user->getParam('timezone',0);
|
|
$dateNow = new JDate();
|
|
$dateNow->setOffset($userTZ);
|
|
if( version_compare( JVERSION, '1.6.0', 'ge' ) ) {
|
|
$description = JText::_('BACKUP_DEFAULT_DESCRIPTION').' '.$dateNow->format(JText::_('DATE_FORMAT_LC2'), true);
|
|
} else {
|
|
$description = JText::_('BACKUP_DEFAULT_DESCRIPTION').' '.$dateNow->toFormat(JText::_('DATE_FORMAT_LC2'));
|
|
}
|
|
$options = array(
|
|
'description' => $description,
|
|
'comment' => ''
|
|
);
|
|
$kettenrad->setup($options);
|
|
$array = $kettenrad->tick();
|
|
}
|
|
else
|
|
{
|
|
// Run a backup step
|
|
$kettenrad =& AECoreKettenrad::load(AKEEBA_BACKUP_ORIGIN);
|
|
$array = $kettenrad->tick();
|
|
}
|
|
AECoreKettenrad::save(AKEEBA_BACKUP_ORIGIN);
|
|
|
|
// Parse the return array
|
|
if($array['Error'] != '')
|
|
{
|
|
// An error occured. Reset the engine and unset the nonce.
|
|
$this->unsetNonce();
|
|
AECoreKettenrad::reset();
|
|
$isDone = true;
|
|
}
|
|
elseif($array['HasRun'] == false)
|
|
{
|
|
// All done. Clean up and unset the nonce.
|
|
$this->unsetNonce();
|
|
AEFactory::nuke();
|
|
AEUtilTempvars::reset();
|
|
$isDone = true;
|
|
}
|
|
|
|
// Unlock the process
|
|
$this->unsetLock();
|
|
|
|
// Do we need to forward to the new step?
|
|
if(in_array($aklazy, array('start','step')))
|
|
{
|
|
// IFRAME handling
|
|
if(!$isDone)
|
|
{
|
|
$url = JURI::base().'index.php?aklazy=step&nonce='.$this->nonce;
|
|
die("<html><head><meta http-equiv=\"refresh\" content=\"0;$url\" /></head><body></body></html>");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if(!$isDone)
|
|
{
|
|
die('###'.$this->nonce.'###');
|
|
}
|
|
}
|
|
|
|
// Stop processing
|
|
die();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Checks if it's necessary to add background backup code to the page
|
|
*/
|
|
function onAfterRender()
|
|
{
|
|
$caching = $this->getCachingState();
|
|
|
|
$debug = '';
|
|
$html = '';
|
|
|
|
if($caching)
|
|
{
|
|
// If caching is enabled, use Javascript code
|
|
$html = $this->getJavascript();
|
|
}
|
|
else
|
|
{
|
|
// If caching is disabled, create a hidden backup iFrame if we need
|
|
// to start or continue a backup job
|
|
$action = $this->getBackupState();
|
|
if(JDEBUG) {
|
|
$debug = '<pre style="background:white; color: black; border: thick solid red; margin: 2em;">'.$this->debugInfo.'</pre>';
|
|
}
|
|
if($action != 'none') {
|
|
$url = JURI::base().'index.php?aklazy='.$action.'&nonce='.$this->nonce;
|
|
$html = '<iframe src="'.$url.'" style="display:none;" width="0" height="0"> </iframe>';
|
|
}
|
|
}
|
|
|
|
if(empty($html) && empty($debug)) return;
|
|
|
|
// Add the extra HTML to the page
|
|
$buffer = JResponse::getBody();
|
|
$pos = strpos($buffer, "</body>");
|
|
if($pos > 0)
|
|
{
|
|
$buffer = substr($buffer, 0, $pos).$debug.$html.substr($buffer, $pos);
|
|
JResponse::setBody($buffer);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns the backup state ('none','start', or 'step')
|
|
*/
|
|
private function getBackupState()
|
|
{
|
|
$this->debugInfo = '<h6>Akeeba Backup Lazy Mode</h6><hr/>';
|
|
// Make sure we're not locked
|
|
if($this->isLocked()) {
|
|
$this->debugInfo .= 'Backup locked';
|
|
// If the backup has crashed, clean up
|
|
if($this->isCrashed)
|
|
{
|
|
$this->debugInfo .= 'Crashed backup detected';
|
|
AEFactory::nuke();
|
|
AEUtilTempvars::reset();
|
|
$this->unsetNonce();
|
|
$this->unsetLock();
|
|
}
|
|
else
|
|
{
|
|
return 'none';
|
|
}
|
|
}
|
|
|
|
// Is there a backup running?
|
|
$this->getNonce();
|
|
|
|
$action = empty($this->nonce) ? 'start' : 'step';
|
|
$this->debugInfo .= '<br/>Action: '.$action;
|
|
|
|
// If there is no running backup, try to figure out if we should start
|
|
// a new backup.
|
|
if($action == 'start')
|
|
{
|
|
// Get the last backup time
|
|
$lastBackup = $this->getLastBackupTime();
|
|
$this->debugInfo .= '<br/>Last backup: '.$lastBackup.' ('.date('Y/m/d H:i:s',$lastBackup).' GMT)';
|
|
|
|
// Remove the time part of the backup time (we want the date starting at midnight!)
|
|
$deconstructedDate = getdate($lastBackup);
|
|
$lastBackup = mktime( 0,0,0, $deconstructedDate['mon'], $deconstructedDate['mday'], $deconstructedDate['year'] );
|
|
$this->debugInfo .= '<br/>Adjusted last backup time: '.$lastBackup.' ('.date('Y/m/d H:i:s',$lastBackup).' GMT)';
|
|
|
|
// Get the preferences and calculate the next backup time
|
|
$daysfreq = (int)$this->params->get('daysfreq',1);
|
|
if($daysfreq <= 0) $daysfreq = 1;
|
|
$this->debugInfo .= '<br/>Days frequency: '.$daysfreq;
|
|
$daysfreq *= 86400;
|
|
$backuptime = $this->params->get('backuptime','00:00');
|
|
$this->debugInfo .= '<br/>Backup time: '.$backuptime;
|
|
|
|
$backuptime = trim($backuptime);
|
|
$parts = explode(':',$backuptime);
|
|
if(count($parts) != 2) {
|
|
$backuptime = 0;
|
|
} else {
|
|
$hours = (int)$parts[0];
|
|
$mins = (int)$parts[1];
|
|
$backuptime = $hours * 3600 + $mins * 60;
|
|
}
|
|
$this->debugInfo .= ' ('.$backuptime.' seconds)';
|
|
$nextBackup = $lastBackup + $daysfreq + $backuptime;
|
|
$this->debugInfo .= '<br/>Next Backup: '.$nextBackup.' ('.date('Y/m/d H:i:s',$nextBackup).' GMT)';
|
|
|
|
// The next backup time is in GMT. Convert to local.
|
|
jimport('joomla.utilities.date');
|
|
$date = new JDate($nextBackup, 0);
|
|
$jreg =& JFactory::getConfig();
|
|
$offset = $jreg->getValue('config.offset');
|
|
$date->setOffset($offset);
|
|
$nextBackup = $date->toUnix(true);
|
|
|
|
$this->debugInfo .= '<br/>Next Backup: '.$nextBackup.' ('.date('Y/m/d H:i:s',$nextBackup).' LOCAL)';
|
|
$this->debugInfo .= '<br/>Time Now: '.time().' ('.date('Y/m/d H:i:s').' LOCAL)';
|
|
|
|
// Is it time for the next backup to run?
|
|
if( time() < $nextBackup ) {
|
|
$this->debugInfo .= '<br/>I will not start a new backup.';
|
|
} else {
|
|
$this->debugInfo .= '<br/><strong>Starting new backup.</strong>';
|
|
}
|
|
if( time() < $nextBackup ) return 'none';
|
|
|
|
// Create a new nonce
|
|
$this->setNonce();
|
|
}
|
|
|
|
return $action;
|
|
}
|
|
|
|
private function getCachingState()
|
|
{
|
|
// The exceptions to caching, as per plugins/system/cache.php
|
|
if(defined(JDEBUG)) if(JDEBUG) return false;
|
|
if (JFactory::getUser()->get('aid')) return false;
|
|
if ($_SERVER['REQUEST_METHOD'] != 'GET') return false;
|
|
|
|
// In any other case, caching is on if the cache plugin is on
|
|
jimport('joomla.plugin.helper');
|
|
return JPluginHelper::isEnabled('system','cache');
|
|
}
|
|
|
|
/**
|
|
* Creates a new nonce and saves it to the configuration
|
|
*/
|
|
private function setNonce()
|
|
{
|
|
jimport('joomla.user.helper');
|
|
$nonce = JUserHelper::genRandomPassword(64);
|
|
$this->nonce = $nonce;
|
|
|
|
// Set the profile and load the configuration
|
|
$profile = (int)$this->params->get('profile',1);
|
|
if($profile <= 0) $profile = 1;
|
|
AEPlatform::load_configuration($profile);
|
|
$config = AEFactory::getConfiguration();
|
|
$config->set('lazy.nonce', $this->nonce);
|
|
AEPlatform::save_configuration($profile);
|
|
}
|
|
|
|
/**
|
|
* Read the nonce from the database file and set the object's property
|
|
*/
|
|
private function getNonce()
|
|
{
|
|
return $this->nonce;
|
|
}
|
|
|
|
/**
|
|
* Remove the old nonce
|
|
*/
|
|
private function unsetNonce()
|
|
{
|
|
// Set the profile and load the configuration
|
|
$profile = (int)$this->params->get('profile',1);
|
|
if($profile <= 0) $profile = 1;
|
|
AEPlatform::load_configuration($profile);
|
|
$config = AEFactory::getConfiguration();
|
|
$config->set('lazy.nonce', null);
|
|
AEPlatform::save_configuration($profile);
|
|
}
|
|
|
|
/**
|
|
* Checks if there is a lock record
|
|
* @return bool
|
|
*/
|
|
private function isLocked()
|
|
{
|
|
if($this->locked != 1) return false;
|
|
|
|
if($this->tstamp != 0)
|
|
{
|
|
jimport('joomla.utilities.date');
|
|
$date = new JDate();
|
|
$now = $date->toUNIX();
|
|
|
|
$diff = abs( $now - $this->tstamp );
|
|
if($diff > 180)
|
|
{
|
|
$this->isCrashed = true;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Removes a lock record
|
|
*/
|
|
private function unsetLock()
|
|
{
|
|
// Set the profile and load the configuration
|
|
$profile = (int)$this->params->get('profile',1);
|
|
if($profile <= 0) $profile = 1;
|
|
AEPlatform::load_configuration($profile);
|
|
$config = AEFactory::getConfiguration();
|
|
$config->set('lazy.lock.status', 0);
|
|
$config->set('lazy.lock.stamp', 0);
|
|
AEPlatform::save_configuration($profile);
|
|
}
|
|
|
|
/**
|
|
* Creates a lock record
|
|
*/
|
|
private function setLock()
|
|
{
|
|
// Set the profile and load the configuration
|
|
$profile = (int)$this->params->get('profile',1);
|
|
if($profile <= 0) $profile = 1;
|
|
AEPlatform::load_configuration($profile);
|
|
$config = AEFactory::getConfiguration();
|
|
|
|
jimport('joomla.filesystem.date');
|
|
$date = new JDate();
|
|
$tstamp = $date->toUnix();
|
|
|
|
$config->set('lazy.lock.status', 1);
|
|
$config->set('lazy.lock.stamp', $tstamp);
|
|
AEPlatform::save_configuration($profile);
|
|
|
|
$this->locked = 1;
|
|
$this->tstamp = $tstamp;
|
|
}
|
|
|
|
/**
|
|
* When was the last backup time using this plugin?
|
|
* @return int The timestamp of the last backup
|
|
*/
|
|
private function getLastBackupTime()
|
|
{
|
|
// If we're in test mode, the last backup time is always 0, so as to
|
|
// trigger a backup.
|
|
if( $this->params->get('test',0) == 1 )
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
$db = JFactory::getDBO();
|
|
$sql = 'SELECT `backupstart` FROM `#__ak_stats` WHERE `origin` = "lazy" AND NOT(`status` = "failed") ORDER BY `backupstart` DESC LIMIT 0,1';
|
|
$db->setQuery($sql);
|
|
$tstamp = $db->loadResult();
|
|
if(empty($tstamp)) return 0;
|
|
|
|
jimport('joomla.utilities.date');
|
|
$date = new JDate($tstamp);
|
|
|
|
return $date->toUnix();
|
|
}
|
|
|
|
/**
|
|
* Get the Javascript to create the iFrame in the background, when page
|
|
* caching is enabled.
|
|
* @return string
|
|
*/
|
|
private function getJavascript()
|
|
{
|
|
$proxyurl = JURI::base().'index.php?aklazy=check';
|
|
|
|
return <<<ENDJS
|
|
<script type="text/javascript">
|
|
function aklazyinit()
|
|
{
|
|
var xhr = undefined;
|
|
if (typeof XMLHttpRequest == "undefined")
|
|
{
|
|
try { xhr = new ActiveXObject("Msxml2.XMLHTTP.6.0"); }
|
|
catch (e) {}
|
|
if(xhr == 'undefined') try { xhr = ActiveXObject("Msxml2.XMLHTTP.3.0"); }
|
|
catch (e) {}
|
|
if(xhr == 'undefined') try { xhr = new ActiveXObject("Msxml2.XMLHTTP"); }
|
|
catch (e) {}
|
|
}
|
|
else
|
|
{
|
|
xhr = new XMLHttpRequest();
|
|
xhr.open('GET', '$proxyurl', true);
|
|
xhr.onreadystatechange = function (aEvt) {
|
|
if (xhr.readyState == 4) {
|
|
if(xhr.status == 200)
|
|
{
|
|
var msg = xhr.responseText;
|
|
|
|
// Start processing the message
|
|
var junk = null;
|
|
var message = "";
|
|
|
|
// Get rid of junk before the data
|
|
var valid_pos = msg.indexOf('###');
|
|
if( valid_pos == -1 ) {
|
|
return;
|
|
} else if( valid_pos != 0 ) {
|
|
// Data is prefixed with junk
|
|
junk = msg.substr(0, valid_pos);
|
|
message = msg.substr(valid_pos);
|
|
}
|
|
else
|
|
{
|
|
message = msg;
|
|
}
|
|
message = message.substr(3); // Remove triple hash in the beginning
|
|
|
|
// Get of rid of junk after the data
|
|
var valid_pos = message.lastIndexOf('###');
|
|
if( valid_pos == -1 ) {
|
|
return;
|
|
} else if( valid_pos == 0 )
|
|
{
|
|
// No data
|
|
return;
|
|
}
|
|
message = message.substr(0, valid_pos); // Remove triple hash in the end
|
|
|
|
// Create the iFrame
|
|
var iframe = document.createElement('iframe');
|
|
iframe.setAttribute('width', '0');
|
|
iframe.setAttribute('height', '0');
|
|
iframe.setAttribute('src', message);
|
|
document.body.appendChild(iframe);
|
|
}
|
|
}
|
|
};
|
|
xhr.send(null);
|
|
}
|
|
}
|
|
window.onload=aklazyinit;
|
|
</script>
|
|
|
|
ENDJS;
|
|
}
|
|
|
|
}
|
|
|
|
function AkeebaBackupLazyShutdown()
|
|
{
|
|
if(connection_status() >= CONNECTION_TIMEOUT )
|
|
{
|
|
// Oops! We timed out. Try to clean up.
|
|
$this->unsetLock();
|
|
$this->unsetNonce();
|
|
AECoreKettenrad::reset();
|
|
}
|
|
}
|