first commit

This commit is contained in:
2024-07-15 11:28:08 +02:00
commit f52d538ea5
21891 changed files with 6161164 additions and 0 deletions

View File

@@ -0,0 +1,261 @@
<?php
/**
* Akeeba Engine
*
* @package akeebaengine
* @copyright Copyright (c)2006-2022 Nicholas K. Dionysopoulos / Akeeba Ltd
* @license GNU General Public License version 3, or later
*/
namespace Akeeba\Engine\Postproc;
defined('AKEEBAENGINE') || die();
use Akeeba\Engine\Platform;
use Akeeba\Engine\Postproc\Exception\BadConfiguration;
use Akeeba\Engine\Postproc\Exception\DeleteNotSupported;
use Akeeba\Engine\Postproc\Exception\DownloadToBrowserNotSupported;
use Akeeba\Engine\Postproc\Exception\DownloadToServerNotSupported;
use Akeeba\Engine\Postproc\Exception\OAuthNotSupported;
use Akeeba\Engine\Util\FileCloseAware;
use Exception;
/**
* Akeeba Engine post-processing abstract class. Provides the default implementation of most of the PostProcInterface
* methods.
*/
abstract class Base implements PostProcInterface
{
use FileCloseAware;
/**
* Should we break the step before post-processing?
*
* The only engine which does not require a step break before is the None engine.
*
* @var bool
*/
protected $recommendsBreakBefore = true;
/**
* Should we break the step after post-processing?
*
* @var bool
*/
protected $recommendsBreakAfter = true;
/**
* Does this engine processes the files in a way that makes deleting the originals safe?
*
* @var bool
*/
protected $advisesDeletionAfterProcessing = true;
/**
* Does this engine support remote file deletes?
*
* @var bool
*/
protected $supportsDelete = false;
/**
* Does this engine support downloads to files?
*
* @var bool
*/
protected $supportsDownloadToFile = false;
/**
* Does this engine support downloads to browser?
*
* @var bool
*/
protected $supportsDownloadToBrowser = false;
/**
* Does this engine push raw data to the browser when downloading a file?
*
* Set to true if raw data will be dumped to the browser when downloading the file to the browser. Set to false if
* a URL is returned instead.
*
* @var bool
*/
protected $inlineDownloadToBrowser = false;
/**
* The remote absolute path to the file which was just processed. Leave null if the file is meant to
* be non-retrievable, i.e. sent to email or any other one way service.
*
* @var string
*/
protected $remotePath = null;
/**
* Whitelist of method names you can call using customAPICall().
*
* @var array
*/
protected $allowedCustomAPICallMethods = ['oauthCallback'];
/**
* The connector object for this post-processing engine
*
* @var object|null
*/
private $connector;
public function delete($path)
{
throw new DeleteNotSupported();
}
public function downloadToFile($remotePath, $localFile, $fromOffset = null, $length = null)
{
throw new DownloadToServerNotSupported();
}
public function downloadToBrowser($remotePath)
{
throw new DownloadToBrowserNotSupported();
}
public final function customAPICall($method, $params = [])
{
if (!in_array($method, $this->allowedCustomAPICallMethods) || !method_exists($this, $method))
{
header('HTTP/1.0 501 Not Implemented');
exit();
}
return call_user_func_array([$this, $method], [$params]);
}
public function oauthOpen($params = [])
{
$callback = $params['callbackURI'] . '&method=oauthCallback';
$url = $this->getOAuth2HelperUrl();
$url .= (strpos($url, '?') !== false) ? '&' : '?';
$url .= 'callback=' . urlencode($callback);
$url .= '&dlid=' . urlencode(Platform::getInstance()->get_platform_configuration_option('update_dlid', ''));
Platform::getInstance()->redirect($url);
}
/**
* Fetches the authentication token from the OAuth helper script, after you've run the first step of the OAuth
* authentication process. Must be overridden in subclasses.
*
* @param array $params
*
* @return void
*
* @throws OAuthNotSupported
*/
public function oauthCallback(array $params)
{
throw new OAuthNotSupported();
}
public function recommendsBreakBefore()
{
return $this->recommendsBreakBefore;
}
public function recommendsBreakAfter()
{
return $this->recommendsBreakAfter;
}
public function isFileDeletionAfterProcessingAdvisable()
{
return $this->advisesDeletionAfterProcessing;
}
public function supportsDelete()
{
return $this->supportsDelete;
}
public function supportsDownloadToFile()
{
return $this->supportsDownloadToFile;
}
public function supportsDownloadToBrowser()
{
return $this->supportsDownloadToBrowser;
}
public function doesInlineDownloadToBrowser()
{
return $this->inlineDownloadToBrowser;
}
public function getRemotePath()
{
return $this->remotePath;
}
/**
* Returns the URL to the OAuth2 helper script. Used by the oauthOpen method. Must be overridden in subclasses.
*
* @return string
*
* @throws OAuthNotSupported
*/
protected function getOAuth2HelperUrl()
{
throw new OAuthNotSupported();
}
/**
* Returns an instance of the connector object.
*
* @param bool $forceNew Should I force the creation of a new connector object?
*
* @return object The connector object
*
* @throws BadConfiguration If there is a configuration error which prevents creating a connector object.
* @throws Exception
*/
final protected function getConnector($forceNew = false)
{
if ($forceNew)
{
$this->resetConnector();
}
if (empty($this->connector))
{
$this->connector = $this->makeConnector();
}
return $this->connector;
}
/**
* Resets the connector.
*
* If the connector requires any special handling upon destruction you must handle it in its __destruct method.
*
* @return void
*/
final protected function resetConnector()
{
$this->connector = null;
}
/**
* Creates a new connector object based on the engine configuration stored in the backup profile.
*
* Do not use this method directly. Use getConnector() instead.
*
* @return object The connector object
*
* @throws BadConfiguration If there is a configuration error which prevents creating a connector object.
* @throws Exception Any other error when creating or initializing the connector object.
*/
protected abstract function makeConnector();
}

View File

@@ -0,0 +1,94 @@
<?php
/**
* Akeeba Engine
*
* @package akeebaengine
* @copyright Copyright (c)2006-2022 Nicholas K. Dionysopoulos / Akeeba Ltd
* @license GNU General Public License version 3, or later
*/
namespace Akeeba\Engine\Postproc;
defined('AKEEBAENGINE') || die();
use Akeeba\Engine\Factory;
use Akeeba\Engine\Platform;
use Akeeba\Engine\Postproc\Exception\BadConfiguration;
use Awf\Text\Text;
use Joomla\CMS\Language\Text as JText;
use RuntimeException;
class Email extends Base
{
public function processPart($localFilepath, $remoteBaseName = null)
{
// Retrieve engine configuration data
$config = Factory::getConfiguration();
$address = trim($config->get('engine.postproc.email.address', ''));
$subject = $config->get('engine.postproc.email.subject', '0');
// Sanity checks
if (empty($address))
{
throw new BadConfiguration('You have not set up a recipient\'s email address for the backup files');
}
// Send the file
$basename = empty($remoteBaseName) ? basename($localFilepath) : $remoteBaseName;
Factory::getLog()->info(sprintf("Preparing to email %s to %s", $basename, $address));
if (empty($subject))
{
$subject = "You have a new backup part";
if (class_exists('\Awf\Text\Text'))
{
$subject = Text::_('COM_AKEEBA_COMMON_EMAIL_DEAFULT_SUBJECT');
if ($subject === 'COM_AKEEBA_COMMON_EMAIL_DEAFULT_SUBJECT')
{
$subject = JText::_('COM_AKEEBABACKUP_COMMON_EMAIL_DEAFULT_SUBJECT');
}
}
elseif (class_exists('\Joomla\CMS\Language\Text'))
{
$subject = JText::_('COM_AKEEBA_COMMON_EMAIL_DEAFULT_SUBJECT');
if ($subject === 'COM_AKEEBA_COMMON_EMAIL_DEAFULT_SUBJECT')
{
$subject = JText::_('COM_AKEEBABACKUP_COMMON_EMAIL_DEAFULT_SUBJECT');
}
}
}
$body = "Emailing $basename";
Factory::getLog()->debug("Subject: $subject");
Factory::getLog()->debug("Body: $body");
$result = Platform::getInstance()->send_email($address, $subject, $body, $localFilepath);
// Return the result
if ($result !== true)
{
// An error occurred
throw new RuntimeException($result);
}
// Return success
Factory::getLog()->info("Email sent successfully");
return true;
}
protected function makeConnector()
{
/**
* This method does not use a connector.
*/
return;
}
}

View File

@@ -0,0 +1,21 @@
<?php
/**
* Akeeba Engine
*
* @package akeebaengine
* @copyright Copyright (c)2006-2022 Nicholas K. Dionysopoulos / Akeeba Ltd
* @license GNU General Public License version 3, or later
*/
namespace Akeeba\Engine\Postproc\Exception;
defined('AKEEBAENGINE') || die();
use RuntimeException;
/**
* Indicates an error with the post-processing engine's configuration
*/
class BadConfiguration extends RuntimeException
{
}

View File

@@ -0,0 +1,20 @@
<?php
/**
* Akeeba Engine
*
* @package akeebaengine
* @copyright Copyright (c)2006-2022 Nicholas K. Dionysopoulos / Akeeba Ltd
* @license GNU General Public License version 3, or later
*/
namespace Akeeba\Engine\Postproc\Exception;
defined('AKEEBAENGINE') || die();
/**
* Indicates that the post-processing engine does not support deleting remotely stored files.
*/
class DeleteNotSupported extends EngineException
{
protected $messagePrototype = 'The %s post-processing engine does not support deletion of backup archives.';
}

View File

@@ -0,0 +1,20 @@
<?php
/**
* Akeeba Engine
*
* @package akeebaengine
* @copyright Copyright (c)2006-2022 Nicholas K. Dionysopoulos / Akeeba Ltd
* @license GNU General Public License version 3, or later
*/
namespace Akeeba\Engine\Postproc\Exception;
defined('AKEEBAENGINE') || die();
/**
* Indicates that the post-processing engine does not support downloading remotely stored files to the user's browser.
*/
class DownloadToBrowserNotSupported extends EngineException
{
protected $messagePrototype = 'The %s post-processing engine does not support downloading of backup archives to the browser.';
}

View File

@@ -0,0 +1,20 @@
<?php
/**
* Akeeba Engine
*
* @package akeebaengine
* @copyright Copyright (c)2006-2022 Nicholas K. Dionysopoulos / Akeeba Ltd
* @license GNU General Public License version 3, or later
*/
namespace Akeeba\Engine\Postproc\Exception;
defined('AKEEBAENGINE') || die();
/**
* Indicates that the post-processing engine does not support downloading remotely stored files to the server
*/
class DownloadToServerNotSupported extends EngineException
{
protected $messagePrototype = 'The %s post-processing engine does not support downloading of backup archives to the server.';
}

View File

@@ -0,0 +1,90 @@
<?php
/**
* Akeeba Engine
*
* @package akeebaengine
* @copyright Copyright (c)2006-2022 Nicholas K. Dionysopoulos / Akeeba Ltd
* @license GNU General Public License version 3, or later
*/
namespace Akeeba\Engine\Postproc\Exception;
defined('AKEEBAENGINE') || die();
use Akeeba\Engine\Postproc\Base;
use Exception;
use RuntimeException;
use Throwable;
class EngineException extends RuntimeException
{
protected $messagePrototype = 'The %s post-processing engine has experienced an unspecified error.';
/**
* Construct the exception. If a message is not defined the default message for the exception will be used.
*
* @param string $message [optional] The Exception message to throw.
* @param int $code [optional] The Exception code.
* @param Exception|Throwable $previous [optional] The previous throwable used for the exception chaining.
*/
public function __construct($message = "", $code = 0, $previous = null)
{
if (empty($message))
{
$engineName = $this->getEngineKeyFromBacktrace();
$message = sprintf($this->messagePrototype, $engineName);
}
parent::__construct($message, $code, $previous);
}
/**
* Returns the engine name (class name without the namespace) from the PHP execution backtrace.
*
* @return mixed|string
*/
protected function getEngineKeyFromBacktrace()
{
// Make sure the backtrace is at least 3 levels deep
$backtrace = debug_backtrace(DEBUG_BACKTRACE_PROVIDE_OBJECT, 5);
// We need to be at least two levels deep
if (count($backtrace) < 2)
{
return 'current';
}
for ($i = 1; $i < count($backtrace); $i++)
{
// Get the fully qualified class
$object = $backtrace[$i]['object'];
// We need a backtrace element with an object attached.
if (!is_object($object))
{
continue;
}
// If the object is not a Postproc\Base object go to the next entry.
if (!($object instanceof Base))
{
continue;
}
// Get the bare class name
$fqnClass = $backtrace[$i]['class'];
$parts = explode('\\', $fqnClass);
$bareClass = array_pop($parts);
// Do not return the base object!
if ($bareClass == 'Base')
{
continue;
}
return $bareClass;
}
return 'current';
}
}

View File

@@ -0,0 +1,21 @@
<?php
/**
* Akeeba Engine
*
* @package akeebaengine
* @copyright Copyright (c)2006-2022 Nicholas K. Dionysopoulos / Akeeba Ltd
* @license GNU General Public License version 3, or later
*/
namespace Akeeba\Engine\Postproc\Exception;
defined('AKEEBAENGINE') || die();
/**
* Indicates that the post-processing engine does not support OAuth2 or similar redirection-based authentication with
* the remote storage provider.
*/
class OAuthNotSupported extends EngineException
{
protected $messagePrototype = 'The %s post-processing engine does not support opening an authentication window to the remote storage provider.';
}

View File

@@ -0,0 +1,20 @@
<?php
/**
* Akeeba Engine
*
* @package akeebaengine
* @copyright Copyright (c)2006-2022 Nicholas K. Dionysopoulos / Akeeba Ltd
* @license GNU General Public License version 3, or later
*/
namespace Akeeba\Engine\Postproc\Exception;
defined('AKEEBAENGINE') || die();
/**
* Indicates that the post-processing engine does not support range downloads.
*/
class RangeDownloadNotSupported extends EngineException
{
protected $messagePrototype = 'The %s post-processing engine does not support range downloads of backup archives to the server.';
}

View File

@@ -0,0 +1,37 @@
<?php
/**
* Akeeba Engine
*
* @package akeebaengine
* @copyright Copyright (c)2006-2022 Nicholas K. Dionysopoulos / Akeeba Ltd
* @license GNU General Public License version 3, or later
*/
namespace Akeeba\Engine\Postproc;
defined('AKEEBAENGINE') || die();
class None extends Base
{
public function __construct()
{
// No point in breaking the step; we simply do nothing :)
$this->recommendsBreakAfter = false;
$this->recommendsBreakBefore = false;
$this->advisesDeletionAfterProcessing = false;
}
public function processPart($localFilepath, $remoteBaseName = null)
{
// Really nothing to do!!
return true;
}
protected function makeConnector()
{
// I have to return an object to satisfy the definition.
return (object) [
'foo' => 'bar',
];
}
}

View File

@@ -0,0 +1,192 @@
<?php
/**
* Akeeba Engine
*
* @package akeebaengine
* @copyright Copyright (c)2006-2022 Nicholas K. Dionysopoulos / Akeeba Ltd
* @license GNU General Public License version 3, or later
*/
namespace Akeeba\Engine\Postproc;
defined('AKEEBAENGINE') || die();
use Akeeba\Engine\Postproc\Exception\DeleteNotSupported;
use Akeeba\Engine\Postproc\Exception\DownloadToBrowserNotSupported;
use Akeeba\Engine\Postproc\Exception\DownloadToServerNotSupported;
use Akeeba\Engine\Postproc\Exception\OAuthNotSupported;
use Akeeba\Engine\Postproc\Exception\RangeDownloadNotSupported;
use Exception;
interface PostProcInterface
{
/**
* This function takes care of post-processing a file (typically a backup archive part file).
*
* If the process has ran to completion it returns true.
*
* If more work is required (the file has only been partially uploaded) it returns false.
*
* It the process has failed an Exception is thrown.
*
* @param string $localFilepath Absolute path to the part we'll have to process
* @param string|null $remoteBaseName Base name of the uploaded file, skip to use $absolute_filename's
*
* @return bool True on success, false if more work is required
*
* @throws Exception When an error occurred during post-processing
*/
public function processPart($localFilepath, $remoteBaseName = null);
/**
* Deletes a remote file
*
* @param string $path The absolute, remote storage path to the file we're deleting
*
* @return void
*
* @throws DeleteNotSupported When this feature is not supported at all.
* @throws Exception When an engine error occurs
*/
public function delete($path);
/**
* Downloads a remotely stored file back to the site's server. It can optionally do a range download. If range
* downloads are not supported we throw a RangeDownloadNotSupported exception. Any other type of Exception means
* that the download failed.
*
* @param string $remotePath The path to the remote file
* @param string $localFile The absolute path to the local file we're writing to
* @param int|null $fromOffset The offset (in bytes) to start downloading from
* @param int|null $length The amount of data (in bytes) to download
*
* @return void
*
* @throws DownloadToServerNotSupported When this feature is not supported at all.
* @throws RangeDownloadNotSupported When range downloads are not supported.
* @throws Exception On failure.
*/
public function downloadToFile($remotePath, $localFile, $fromOffset = null, $length = null);
/**
* Downloads a remotely stored file to the user's browser, without storing it on the site's web server first.
*
* If $this->inlineDownloadToBrowser is true the method outputs a byte stream to the browser and returns null.
*
* If $this->inlineDownloadToBrowser is false it returns a string containing a public download URL. The user's
* browser will be redirected to that URL.
*
* If this feature is not supported a DownloadToBrowserNotSupported exception will be thrown.
*
* Any other Exception indicates an error while trying to download to browser such as file not found, problem with
* the remote service etc.
*
* @param string $remotePath The absolute, remote storage path to the file we want to download
*
* @return string|null
*
* @throws DownloadToBrowserNotSupported When this feature is not supported at all.
* @throws Exception When an error occurs.
*/
public function downloadToBrowser($remotePath);
/**
* A proxy which allows us to execute arbitrary methods in this engine. Used for AJAX calls, typically to update UI
* elements with information fetched from the remote storage service.
*
* For security reasons, only methods whitelisted in the $this->allowedCustomAPICallMethods array can be called.
*
* @param string $method The method to call.
* @param array $params Any parameters to send to the method, in array format
*
* @return mixed The return value of the method.
*/
public function customAPICall($method, $params = []);
/**
* Opens an OAuth window (performs an HTTP redirection).
*
* @param array $params Any parameters required to launch OAuth
*
* @return void
*
* @throws OAuthNotSupported When not supported.
* @throws Exception When an error occurred.
*/
public function oauthOpen($params = []);
/**
* Fetches the authentication token from the OAuth helper script, after you've run the first step of the OAuth
* authentication process. Must be overridden in subclasses.
*
* @param array $params
*
* @return void
*
* @throws OAuthNotSupported
*/
public function oauthCallback(array $params);
/**
* Does the engine recommend doing a step break before post-processing backup archives with it?
*
* @return bool
*/
public function recommendsBreakBefore();
/**
* Does the engine recommend doing a step break after post-processing backup archives with it?
*
* @return bool
*/
public function recommendsBreakAfter();
/**
* Is it advisable to delete files successfully post-processed by this post-processing engine?
*
* Currently only the “None” method advises against deleting successfully post-processed files for the simple reason
* that it does absolutely nothing with the files. The only copy is still on the server.
*
* @return bool
*/
public function isFileDeletionAfterProcessingAdvisable();
/**
* Does this engine support deleting remotely stored files?
*
* Most engines support deletion. However, some engines such as “Send by email”, do not have a way to find files
* already processed and delete them. Or it may be that we are sending the file to a write-only storage service
* which does not support deletions.
*
* @return bool
*/
public function supportsDelete();
/**
* Does this engine support downloading backup archives back to the site's web server?
*
* @return bool
*/
public function supportsDownloadToFile();
/**
* Does this engine support downloading backup archives directly to the user's browser?
*
* @return bool
*/
public function supportsDownloadToBrowser();
/**
* Does this engine return a bytestream when asked to download backup archives directly to the user's browser?
*
* @return bool
*/
public function doesInlineDownloadToBrowser();
/**
* Returns the remote absolute path to the file which was just processed.
*
* @return string
*/
public function getRemotePath();
}

View File

@@ -0,0 +1,70 @@
<?php
/**
* Akeeba Engine
*
* @package akeebaengine
* @copyright Copyright (c)2006-2022 Nicholas K. Dionysopoulos / Akeeba Ltd
* @license GNU General Public License version 3, or later
*/
namespace Akeeba\Engine\Postproc;
use Akeeba\Engine\Platform;
trait ProxyAware
{
/**
* Apply the platform proxy configuration to the cURL resource.
*
* @param resource $ch The cURL resource, returned by curl_init();
*/
protected function applyProxySettingsToCurl($ch)
{
$proxySettings = Platform::getInstance()->getProxySettings();
if (!$proxySettings['enabled'])
{
return;
}
curl_setopt($ch, CURLOPT_PROXY, $proxySettings['host'] . ':' . $proxySettings['port']);
if (empty($proxySettings['user']))
{
return;
}
curl_setopt($ch, CURLOPT_PROXYUSERPWD, $proxySettings['user'] . ':' . $proxySettings['pass']);
}
protected function getProxyStreamContext()
{
$ret = [];
$proxySettings = Platform::getInstance()->getProxySettings();
if (!$proxySettings['enabled'])
{
return $ret;
}
$ret['http'] = [
'proxy' => $proxySettings['host'] . ':' . $proxySettings['port'],
'request_fulluri' => true,
];
$ret['ftp'] = [
'proxy' => $proxySettings['host'] . ':' . $proxySettings['port'],
// So, request_fulluri isn't documented for the FTP transport but seems to be required...?!
'request_fulluri' => true,
];
if (empty($proxySettings['user']))
{
return $ret;
}
$ret['http']['header'] = ['Proxy-Authorization: Basic ' . base64_encode($proxySettings['user'] . ':' . $proxySettings['pass'])];
$ret['ftp']['header'] = ['Proxy-Authorization: Basic ' . base64_encode($proxySettings['user'] . ':' . $proxySettings['pass'])];
return $ret;
}
}

View File

@@ -0,0 +1,30 @@
{
"_information": {
"title": "COM_AKEEBA_CONFIG_ENGINE_POSTPROC_EMAIL_TITLE",
"description": "COM_AKEEBA_CONFIG_ENGINE_POSTPROC_EMAIL_DESCRIPTION"
},
"engine.postproc.common.after_part": {
"default": "0",
"type": "bool",
"title": "COM_AKEEBA_CONFIG_POSTPROCPARTS_TITLE",
"description": "COM_AKEEBA_CONFIG_POSTPROCPARTS_DESCRIPTION"
},
"engine.postproc.common.delete_after": {
"default": "1",
"type": "bool",
"title": "COM_AKEEBA_CONFIG_DELETEAFTER_TITLE",
"description": "COM_AKEEBA_CONFIG_DELETEAFTER_DESCRIPTION"
},
"engine.postproc.email.address": {
"default": "",
"type": "string",
"title": "COM_AKEEBA_CONFIG_PROCEMAIL_ADDRESS_TITLE",
"description": "COM_AKEEBA_CONFIG_PROCEMAIL_ADDRESS_DESCRIPTION"
},
"engine.postproc.email.subject": {
"default": "",
"type": "string",
"title": "COM_AKEEBA_CONFIG_PROCEMAIL_SUBJECT_TITLE",
"description": "COM_AKEEBA_CONFIG_PROCEMAIL_SUBJECT_DESCRIPTION"
}
}

View File

@@ -0,0 +1,6 @@
{
"_information": {
"title": "COM_AKEEBA_CONFIG_ENGINE_POSTPROC_NONE_TITLE",
"description": "COM_AKEEBA_CONFIG_ENGINE_POSTPROC_NONE_DESCRIPTION"
}
}