Your IP : 216.73.216.61


Current Path : /home/w/u/e/wuectly/www/03cbe/
Upload File :
Current File : /home/w/u/e/wuectly/www/03cbe/com_akeeba.zip

PK�!]qv�����script.com_akeeba.phpnu&1i�<?php
/**
 * @package   akeebabackup
 * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

// Protect from unauthorized access
use Akeeba\Engine\Factory;
use Akeeba\Engine\Platform;
use FOF40\Container\Container as FOFContainer;
use Joomla\CMS\Installer\Adapter\ComponentAdapter;

defined('_JEXEC') or die();

// Load FOF if not already loaded
if (!defined('FOF40_INCLUDED') && !@include_once(JPATH_LIBRARIES . '/fof40/include.php'))
{
	throw new RuntimeException('This extension requires FOF 4.');
}

class Com_AkeebaInstallerScript extends \FOF40\InstallScript\Component
{
	/**
	 * The component's name
	 *
	 * @var   string
	 */
	public $componentName = 'com_akeeba';

	/**
	 * The title of the component (printed on installation and uninstallation messages)
	 *
	 * @var string
	 */
	protected $componentTitle = 'Akeeba Backup';

	/**
	 * The minimum PHP version required to install this extension
	 *
	 * @var   string
	 */
	protected $minimumPHPVersion = '7.2.0';

	/**
	 * The minimum Joomla! version required to install this extension
	 *
	 * @var   string
	 */
	protected $minimumJoomlaVersion = '3.9.0';

	/**
	 * The list of obsolete extra modules and plugins to uninstall on component upgrade / installation.
	 *
	 * @var array
	 */
	protected $uninstallation_queue = [
		// modules => { (folder) => { (module) }* }*
		'modules' => [
			'admin' => [
				'mod_akadmin',
			],
			'site'  => [],
		],
		// plugins => { (folder) => { (element) }* }*
		'plugins' => [
			'system' => [
				'aklazy',
				'srp',
			],
		],
	];

	protected $containerBroken = false;

	/**
	 * Obsolete files and folders to remove from the free version only. This is used when you move a feature from the
	 * free version of your extension to its paid version. If you don't have such a distinction you can ignore this.
	 *
	 * @var   array
	 */
	protected $removeFilesFree = [
		'files'   => [
			// Pro component features
			'administrator/components/com_akeeba/BackupEngine/Archiver/Directftp.php',
			'administrator/components/com_akeeba/BackupEngine/Archiver/directftp.ini',
			'administrator/components/com_akeeba/BackupEngine/Archiver/directftp.json',
			'administrator/components/com_akeeba/BackupEngine/Archiver/Directftpcurl.php',
			'administrator/components/com_akeeba/BackupEngine/Archiver/directftpcurl.ini',
			'administrator/components/com_akeeba/BackupEngine/Archiver/directftpcurl.json',
			'administrator/components/com_akeeba/BackupEngine/Archiver/Directsftp.php',
			'administrator/components/com_akeeba/BackupEngine/Archiver/directsftp.ini',
			'administrator/components/com_akeeba/BackupEngine/Archiver/directsftp.json',
			'administrator/components/com_akeeba/BackupEngine/Archiver/Directsftpcurl.php',
			'administrator/components/com_akeeba/BackupEngine/Archiver/directsftpcurl.ini',
			'administrator/components/com_akeeba/BackupEngine/Archiver/directsftpcurl.json',
			'administrator/components/com_akeeba/BackupEngine/Archiver/Jps.php',
			'administrator/components/com_akeeba/BackupEngine/Archiver/jps.ini',
			'administrator/components/com_akeeba/BackupEngine/Archiver/jps.json',
			'administrator/components/com_akeeba/BackupEngine/Archiver/Zipnative.php',
			'administrator/components/com_akeeba/BackupEngine/Archiver/zipnative.ini',
			'administrator/components/com_akeeba/BackupEngine/Archiver/zipnative.json',
			'administrator/components/com_akeeba/BackupEngine/Postproc/amazons3.ini',
			'administrator/components/com_akeeba/BackupEngine/Postproc/amazons3.json',
			'administrator/components/com_akeeba/BackupEngine/Postproc/Amazons3.php',
			'administrator/components/com_akeeba/BackupEngine/Postproc/azure.ini',
			'administrator/components/com_akeeba/BackupEngine/Postproc/azure.json',
			'administrator/components/com_akeeba/BackupEngine/Postproc/Azure.php',
			'administrator/components/com_akeeba/BackupEngine/Postproc/backblaze.ini',
			'administrator/components/com_akeeba/BackupEngine/Postproc/backblaze.json',
			'administrator/components/com_akeeba/BackupEngine/Postproc/Backblaze.php',
			'administrator/components/com_akeeba/BackupEngine/Postproc/box.ini',
			'administrator/components/com_akeeba/BackupEngine/Postproc/box.json',
			'administrator/components/com_akeeba/BackupEngine/Postproc/Box.php',
			'administrator/components/com_akeeba/BackupEngine/Postproc/cloudfiles.ini',
			'administrator/components/com_akeeba/BackupEngine/Postproc/cloudfiles.json',
			'administrator/components/com_akeeba/BackupEngine/Postproc/Cloudfiles.php',
			'administrator/components/com_akeeba/BackupEngine/Postproc/cloudme.ini',
			'administrator/components/com_akeeba/BackupEngine/Postproc/cloudme.json',
			'administrator/components/com_akeeba/BackupEngine/Postproc/Cloudme.php',
			'administrator/components/com_akeeba/BackupEngine/Postproc/dreamobjects.ini',
			'administrator/components/com_akeeba/BackupEngine/Postproc/dreamobjects.json',
			'administrator/components/com_akeeba/BackupEngine/Postproc/Dreamobjects.php',
			'administrator/components/com_akeeba/BackupEngine/Postproc/dropbox.ini',
			'administrator/components/com_akeeba/BackupEngine/Postproc/dropbox.json',
			'administrator/components/com_akeeba/BackupEngine/Postproc/Dropbox.php',
			'administrator/components/com_akeeba/BackupEngine/Postproc/dropbox2.ini',
			'administrator/components/com_akeeba/BackupEngine/Postproc/dropbox2.json',
			'administrator/components/com_akeeba/BackupEngine/Postproc/Dropbox2.php',
			'administrator/components/com_akeeba/BackupEngine/Postproc/ftp.ini',
			'administrator/components/com_akeeba/BackupEngine/Postproc/ftp.json',
			'administrator/components/com_akeeba/BackupEngine/Postproc/Ftp.php',
			'administrator/components/com_akeeba/BackupEngine/Postproc/ftpcurl.ini',
			'administrator/components/com_akeeba/BackupEngine/Postproc/ftpcurl.json',
			'administrator/components/com_akeeba/BackupEngine/Postproc/Ftpcurl.php',
			'administrator/components/com_akeeba/BackupEngine/Postproc/googledrive.ini',
			'administrator/components/com_akeeba/BackupEngine/Postproc/googledrive.json',
			'administrator/components/com_akeeba/BackupEngine/Postproc/Googledrive.php',
			'administrator/components/com_akeeba/BackupEngine/Postproc/googlestorage.ini',
			'administrator/components/com_akeeba/BackupEngine/Postproc/googlestorage.json',
			'administrator/components/com_akeeba/BackupEngine/Postproc/Googlestorage.php',
			'administrator/components/com_akeeba/BackupEngine/Postproc/googlestoragejson.ini',
			'administrator/components/com_akeeba/BackupEngine/Postproc/googlestoragejson.json',
			'administrator/components/com_akeeba/BackupEngine/Postproc/Googlestoragejson.php',
			'administrator/components/com_akeeba/BackupEngine/Postproc/idrivesync.ini',
			'administrator/components/com_akeeba/BackupEngine/Postproc/idrivesync.json',
			'administrator/components/com_akeeba/BackupEngine/Postproc/Idrivesync.php',
			'administrator/components/com_akeeba/BackupEngine/Postproc/onedrive.ini',
			'administrator/components/com_akeeba/BackupEngine/Postproc/onedrive.json',
			'administrator/components/com_akeeba/BackupEngine/Postproc/Onedrive.php',
			'administrator/components/com_akeeba/BackupEngine/Postproc/onedrivebusiness.ini',
			'administrator/components/com_akeeba/BackupEngine/Postproc/onedrivebusiness.json',
			'administrator/components/com_akeeba/BackupEngine/Postproc/Onedrivebusiness.php',
			'administrator/components/com_akeeba/BackupEngine/Postproc/ovh.ini',
			'administrator/components/com_akeeba/BackupEngine/Postproc/ovh.json',
			'administrator/components/com_akeeba/BackupEngine/Postproc/Ovh.php',
			'administrator/components/com_akeeba/BackupEngine/Postproc/pcloud.ini',
			'administrator/components/com_akeeba/BackupEngine/Postproc/pcloud.json',
			'administrator/components/com_akeeba/BackupEngine/Postproc/Pcloud.php',
			'administrator/components/com_akeeba/BackupEngine/Postproc/s3.ini',
			'administrator/components/com_akeeba/BackupEngine/Postproc/s3.json',
			'administrator/components/com_akeeba/BackupEngine/Postproc/S3.php',
			'administrator/components/com_akeeba/BackupEngine/Postproc/sftp.ini',
			'administrator/components/com_akeeba/BackupEngine/Postproc/sftp.json',
			'administrator/components/com_akeeba/BackupEngine/Postproc/Sftp.php',
			'administrator/components/com_akeeba/BackupEngine/Postproc/sftpcurl.ini',
			'administrator/components/com_akeeba/BackupEngine/Postproc/sftpcurl.json',
			'administrator/components/com_akeeba/BackupEngine/Postproc/Sftpcurl.php',
			'administrator/components/com_akeeba/BackupEngine/Postproc/sugarsync.ini',
			'administrator/components/com_akeeba/BackupEngine/Postproc/sugarsync.json',
			'administrator/components/com_akeeba/BackupEngine/Postproc/Sugarsync.php',
			'administrator/components/com_akeeba/BackupEngine/Postproc/swift.ini',
			'administrator/components/com_akeeba/BackupEngine/Postproc/swift.json',
			'administrator/components/com_akeeba/BackupEngine/Postproc/Swift.php',
			'administrator/components/com_akeeba/BackupEngine/Postproc/webdav.ini',
			'administrator/components/com_akeeba/BackupEngine/Postproc/webdav.json',
			'administrator/components/com_akeeba/BackupEngine/Postproc/Webdav.php',
			'administrator/components/com_akeeba/BackupEngine/Scan/large.ini',
			'administrator/components/com_akeeba/BackupEngine/Scan/large.json',
			'administrator/components/com_akeeba/BackupEngine/Scan/Large.php',

			'administrator/components/com_akeeba/Controller/Alice.php',
			'administrator/components/com_akeeba/Controller/Discover.php',
			'administrator/components/com_akeeba/Controller/IncludeFolders.php',
			'administrator/components/com_akeeba/Controller/MultipleDatabases.php',
			'administrator/components/com_akeeba/Controller/RegExDatabaseFilters.php',
			'administrator/components/com_akeeba/Controller/RegExFileFilters.php',
			'administrator/components/com_akeeba/Controller/RemoteFiles.php',
			'administrator/components/com_akeeba/Controller/Schedule.php',
			'administrator/components/com_akeeba/Controller/S3Import.php',
			'administrator/components/com_akeeba/Controller/Upload.php',

			'administrator/components/com_akeeba/Model/Alice.php',
			'administrator/components/com_akeeba/Model/Discover.php',
			'administrator/components/com_akeeba/Model/IncludeFolders.php',
			'administrator/components/com_akeeba/Model/MultipleDatabases.php',
			'administrator/components/com_akeeba/Model/RegExDatabaseFilters.php',
			'administrator/components/com_akeeba/Model/RegExFileFilters.php',
			'administrator/components/com_akeeba/Model/RemoteFiles.php',
			'administrator/components/com_akeeba/Model/Schedule.php',
			'administrator/components/com_akeeba/Model/S3Import.php',
			'administrator/components/com_akeeba/Model/Upload.php',

			'administrator/components/com_akeeba/BackupPlatform/Joomla3x/Filter/Components.php',
			'administrator/components/com_akeeba/BackupPlatform/Joomla3x/Filter/Extensiondirs.php',
			'administrator/components/com_akeeba/BackupPlatform/Joomla3x/Filter/Extensionfiles.php',
			'administrator/components/com_akeeba/BackupPlatform/Joomla3x/Filter/Languages.php',
			'administrator/components/com_akeeba/BackupPlatform/Joomla3x/Filter/Modules.php',
			'administrator/components/com_akeeba/BackupPlatform/Joomla3x/Filter/Plugins.php',
			'administrator/components/com_akeeba/BackupPlatform/Joomla3x/Filter/Templates.php',

			// Additional ANGIE installers which are not used in Core
			'administrator/components/com_akeeba/Master/Installers/abi.ini',
			'administrator/components/com_akeeba/Master/Installers/abi.jpa',
			'administrator/components/com_akeeba/Master/Installers/angie-generic.ini',
			'administrator/components/com_akeeba/Master/Installers/angie-generic.jpa',

			// PostgreSQL and MS SQL Server support
			'administrator/components/com_akeeba/BackupEngine/Driver/Pgsql.php',
			'administrator/components/com_akeeba/BackupEngine/Driver/Postgresql.php',
			'administrator/components/com_akeeba/BackupEngine/Driver/Sqlazure.php',
			'administrator/components/com_akeeba/BackupEngine/Driver/Sqlsrv.php',

			'administrator/components/com_akeeba/BackupEngine/Driver/Query/Pgsql.php',
			'administrator/components/com_akeeba/BackupEngine/Driver/Query/Postgresql.php',
			'administrator/components/com_akeeba/BackupEngine/Driver/Query/Sqlazure.php',
			'administrator/components/com_akeeba/BackupEngine/Driver/Query/Sqlsrv.php',

			'administrator/components/com_akeeba/BackupEngine/Dump/reverse.json',
			'administrator/components/com_akeeba/BackupEngine/Dump/Reverse.php',
			'administrator/components/com_akeeba/BackupEngine/Dump/Native/Postgresql.php',
			'administrator/components/com_akeeba/BackupEngine/Dump/Native/Sqlsrv.php',

			'administrator/components/com_akeeba/sql/xml/postgresql.xml',
			'administrator/components/com_akeeba/sql/xml/sqlsrv.xml',

			// Version 7
			// -- Integrated restoration
			'administrator/components/com_akeeba/Controller/Restore.php',
			'administrator/components/com_akeeba/Model/Restore.php',
			'administrator/components/com_akeeba/restore.php',
			'media/com_akeeba/js/Restore.min.js',
			'media/com_akeeba/js/Restore.js',

			// -- Site Transfer Wizard
			'administrator/components/com_akeeba/Controller/Transfer.php',
			'administrator/components/com_akeeba/Model/Transfer.php',
			'media/com_akeeba/js/Transfer.min.js',
			'media/com_akeeba/js/Transfer.js',

			// -- other non-Core JS
			'media/com_akeeba/js/IncludeFolders.min.js',
			'media/com_akeeba/js/IncludeFolders.js',
			'media/com_akeeba/js/MultipleDatabases.min.js',
			'media/com_akeeba/js/MultipleDatabases.js',
			'media/com_akeeba/js/RegExDatabaseFilters.min.js',
			'media/com_akeeba/js/RegExDatabaseFilters.js',
			'media/com_akeeba/js/RegExFileFilters.min.js',
			'media/com_akeeba/js/RegExFileFilters.js',
			'media/com_akeeba/js/RegExFileFilters.min.js',
			'media/com_akeeba/js/RegExFileFilters.js',

			// Pro features of the Console plugin
			'administrator/components/com_akeeba/CliCommands/BackupFetch.php',
			'administrator/components/com_akeeba/CliCommands/BackupTake.php',
			'administrator/components/com_akeeba/CliCommands/BackupUpload.php',
			'administrator/components/com_akeeba/CliCommands/FilterIncludeDatabase.php',
			'administrator/components/com_akeeba/CliCommands/FilterIncludeDirectory.php',

			// Version 8
			// -- Older CSS and JS files
			'media/com_akeeba/css/akeebaui.min.css',
			'media/com_akeeba/css/akeebaui.min.css.map',
			'media/com_akeeba/css/dark.min.css',
			'media/com_akeeba/css/dark.min.css.map',
		],
		'folders' => [
			// Pro component features
			'administrator/components/com_akeeba/Alice',

			'administrator/components/com_akeeba/BackupPlatform/Joomla3x/Config/Pro',

			'administrator/components/com_akeeba/View/Alice',
			'administrator/components/com_akeeba/View/Discover',
			'administrator/components/com_akeeba/View/IncludeFolders',
			'administrator/components/com_akeeba/View/MultipleDatabases',
			'administrator/components/com_akeeba/View/RegExDatabaseFilters',
			'administrator/components/com_akeeba/View/RegExFileFilter',
			'administrator/components/com_akeeba/View/RemoteFiles',
			'administrator/components/com_akeeba/View/Schedule',
			'administrator/components/com_akeeba/View/S3Import',
			'administrator/components/com_akeeba/View/Upload',

			'administrator/components/com_akeeba/BackupEngine/Postproc/Connector',

			// PostgreSQL and MS SQL Server support
			'administrator/components/com_akeeba/BackupEngine/Dump/Reverse',

			// Version 7
			// -- Integrated restoration
			'administrator/components/com_akeeba/View/Restore',

			// -- Site Transfer Wizard
			'administrator/components/com_akeeba/View/Transfer',

			// -- JSON API, legacy backup feature, failed backup checks
			'components/com_akeeba/Controller',
			'components/com_akeeba/Model',

			// -- Archive integrity check
			'administrator/components/com_akeeba/BackupPlatform/Joomla3x/Finalization',

		],
	];

	/**
	 * Obsolete files and folders to remove from both paid and free releases. This is used when you refactor code and
	 * some files inevitably become obsolete and need to be removed.
	 *
	 * @var   array
	 */
	protected $removeFilesAllVersions = [
		'files'   => [
			// Outdated CLI scripts
			'cli/akeeba-update.php',

			// Outdated media files
			'media/com_akeeba/icons/akeeba-48.png',
			'media/com_akeeba/icons/akeeba-warning-48.png',
			'media/com_akeeba/icons/arrow_small.png',
			'media/com_akeeba/icons/error_small.png',
			'media/com_akeeba/icons/ok_small.png',
			'media/com_akeeba/icons/reload.png',
			'media/com_akeeba/icons/scheduling-32.png',
			'media/com_akeeba/icons/update.png',

			'media/com_akeeba/js/akeebajq.js',
			'media/com_akeeba/js/akeebajqui.js',
			'media/com_akeeba/js/akeebaui.js',
			'media/com_akeeba/js/akeebauipro.js',
			'media/com_akeeba/js/alice.js',
			'media/com_akeeba/js/backup.js',
			'media/com_akeeba/js/configuration.js',
			'media/com_akeeba/js/confwiz.js',
			'media/com_akeeba/js/dbef.js',
			'media/com_akeeba/js/eff.js',
			'media/com_akeeba/js/encryption.js',
			'media/com_akeeba/js/fsfilter.js',
			'media/com_akeeba/js/gui-helpers.js',
			'media/com_akeeba/js/jquery.js',
			'media/com_akeeba/js/jquery-ui.js',
			'media/com_akeeba/js/multidb.js',
			'media/com_akeeba/js/regexdbfilter.js',
			'media/com_akeeba/js/regexfsfilter.js',
			'media/com_akeeba/js/restore.js',
			'media/com_akeeba/js/stepper.js',
			'media/com_akeeba/js/system.js',
			'media/com_akeeba/js/transfer.js',

			// Old CLI backup scripts, obsolete since 3.5.0, removed in 4.0.0
			'administrator/components/com_akeeba/backup.php',
			'administrator/components/com_akeeba/altbackup.php',

			// Files used in version 4.2, but before 5.0
			// -- Back-end
			'administrator/components/com_akeeba/dispatcher.php',
			'administrator/components/com_akeeba/toolbar.php',

			// -- Front-end
			'components/com_akeeba/dispatcher.php',

			// Integrity check (obsolete)
			'administrator/components/com_akeeba/fileslist.php',

			// Dropbox v1 integration
			'administrator/components/com_akeeba/BackupEngine/Postproc/dropbox.ini',
			'administrator/components/com_akeeba/BackupEngine/Postproc/Dropbox.php',
			'administrator/components/com_akeeba/BackupEngine/Postproc/Connector/Dropbox.php',

			// Obsolete Azure files
			'administrator/components/com_akeeba/BackupEngine/Postproc/Connector/Azure/Credentials/Sharedsignature.php',

			// Obsolete AES-128 CTR implementation in Javascript
			'media/com_akeeba/js/Encryption.min.js',
			'media/com_akeeba/js/Encryption.min.map',

			// PHP 7.2 compatibility
			'administrator/components/com_akeeba/BackupEngine/Base/Object.php',

			// Obsolete media files
			'media/com_akeeba/icons/akeeba-ui-32.png',
			'media/com_akeeba/changelog.png',

			// Old FOF 3 XML manifest files
			'libraries/fof30/lib_fof30.xml',
			'administrator/manifests/libraries/lib_fof30.xml',

			// Migration of Akeeba Engine to JSON format
			"administrator/components/com_akeeba/BackupEngine/Dump/native.ini",
			"administrator/components/com_akeeba/BackupEngine/Dump/reverse.ini",
			"administrator/components/com_akeeba/BackupEngine/Postproc/none.ini",
			"administrator/components/com_akeeba/BackupEngine/Postproc/webdav.ini",
			"administrator/components/com_akeeba/BackupEngine/Postproc/sugarsync.ini",
			"administrator/components/com_akeeba/BackupEngine/Postproc/email.ini",
			"administrator/components/com_akeeba/BackupEngine/Postproc/box.ini",
			"administrator/components/com_akeeba/BackupEngine/Postproc/dropbox2.ini",
			"administrator/components/com_akeeba/BackupEngine/Postproc/ovh.ini",
			"administrator/components/com_akeeba/BackupEngine/Postproc/cloudme.ini",
			"administrator/components/com_akeeba/BackupEngine/Postproc/idrivesync.ini",
			"administrator/components/com_akeeba/BackupEngine/Postproc/ftpcurl.ini",
			"administrator/components/com_akeeba/BackupEngine/Postproc/dreamobjects.ini",
			"administrator/components/com_akeeba/BackupEngine/Postproc/azure.ini",
			"administrator/components/com_akeeba/BackupEngine/Postproc/sftp.ini",
			"administrator/components/com_akeeba/BackupEngine/Postproc/amazons3.ini",
			"administrator/components/com_akeeba/BackupEngine/Postproc/cloudfiles.ini",
			"administrator/components/com_akeeba/BackupEngine/Postproc/googlestorage.ini",
			"administrator/components/com_akeeba/BackupEngine/Postproc/googlestoragejson.ini",
			"administrator/components/com_akeeba/BackupEngine/Postproc/swift.ini",
			"administrator/components/com_akeeba/BackupEngine/Postproc/sftpcurl.ini",
			"administrator/components/com_akeeba/BackupEngine/Postproc/onedrive.ini",
			"administrator/components/com_akeeba/BackupEngine/Postproc/googledrive.ini",
			"administrator/components/com_akeeba/BackupEngine/Postproc/backblaze.ini",
			"administrator/components/com_akeeba/BackupEngine/Postproc/ftp.ini",
			"administrator/components/com_akeeba/BackupEngine/Archiver/zipnative.ini",
			"administrator/components/com_akeeba/BackupEngine/Archiver/directftp.ini",
			"administrator/components/com_akeeba/BackupEngine/Archiver/directsftpcurl.ini",
			"administrator/components/com_akeeba/BackupEngine/Archiver/zip.ini",
			"administrator/components/com_akeeba/BackupEngine/Archiver/directftpcurl.ini",
			"administrator/components/com_akeeba/BackupEngine/Archiver/directsftp.ini",
			"administrator/components/com_akeeba/BackupEngine/Archiver/jps.ini",
			"administrator/components/com_akeeba/BackupEngine/Archiver/jpa.ini",
			"administrator/components/com_akeeba/BackupEngine/Scan/smart.ini",
			"administrator/components/com_akeeba/BackupEngine/Scan/large.ini",
			"administrator/components/com_akeeba/BackupEngine/Filter/Stack/dateconditional.ini",
			"administrator/components/com_akeeba/BackupEngine/Filter/Stack/errorlogs.ini",
			"administrator/components/com_akeeba/BackupEngine/Filter/Stack/hoststats.ini",
			"administrator/components/com_akeeba/BackupEngine/Core/04.quota.ini",
			"administrator/components/com_akeeba/BackupEngine/Core/02.advanced.ini",
			"administrator/components/com_akeeba/BackupEngine/Core/01.basic.ini",
			"administrator/components/com_akeeba/BackupEngine/Core/scripting.ini",
			"administrator/components/com_akeeba/BackupEngine/Core/05.tuning.ini",

			"administrator/components/com_akeeba/BackupPlatform/Joomla3x/Config/02.advanced.ini",
			"administrator/components/com_akeeba/BackupPlatform/Joomla3x/Config/Pro/04.quota.ini",
			"administrator/components/com_akeeba/BackupPlatform/Joomla3x/Config/Pro/02.advanced.ini",
			"administrator/components/com_akeeba/BackupPlatform/Joomla3x/Config/Pro/01.basic.ini",
			"administrator/components/com_akeeba/BackupPlatform/Joomla3x/Config/Pro/02.platform.ini",
			"administrator/components/com_akeeba/BackupPlatform/Joomla3x/Config/Pro/03.filters.ini",
			"administrator/components/com_akeeba/BackupPlatform/Joomla3x/Config/Pro/05.tuning.ini",
			"administrator/components/com_akeeba/BackupPlatform/Joomla3x/Filter/Stack/finder.ini",
			"administrator/components/com_akeeba/BackupPlatform/Joomla3x/Filter/Stack/myjoomla.ini",
			"administrator/components/com_akeeba/BackupPlatform/Joomla3x/Filter/Stack/actionlogs.ini",

			// Obsolete eAccelerator warning
			"administrator/components/com_akeeba/View/eaccelerator.php",

			// Engine 7
			'administrator/components/com_akeeba/BackupEngine/Base/BaseObject.php',

			// ALICE refactoring
			'media/com_akeeba/js/Alice.js',
			'media/com_akeeba/js/Alice.min.js',
			'media/com_akeeba/js/Stepper.js',
			'media/com_akeeba/js/Stepper.min.js',

			// Version 7 -- Remove non-RAW encapsulation
			"components/com_akeeba/Model/Json/Encapsulation/AesCbc128.php",
			"components/com_akeeba/Model/Json/Encapsulation/AesCbc256.php",
			"components/com_akeeba/Model/Json/Encapsulation/AesCtr128.php",
			"components/com_akeeba/Model/Json/Encapsulation/AesCtr256.php",

			// Optimize JavaScript support
			"administrator/components/com_akeeba/Helper/JsBundler.php",

			// Obsolete cacert.pem in the Engine
			"administrator/components/com_akeeba/BackupEngine/cacert.pem",

			// Workaround for CloudFlare RocketLoader. No longer needed, our JS is being loaded deferred anyway.
			"administrator/components/com_akeeba/Dispatcher/after_render.php",

			// Moving to FEF 2
			"media/com_akeeba/js/Ajax.min.js",
			"media/com_akeeba/js/Ajax.min.map",
			"media/com_akeeba/js/Modal.min.js",
			"media/com_akeeba/js/Modal.min.map",
			"media/com_akeeba/js/System.min.js",
			"media/com_akeeba/js/System.min.map",
			"media/com_akeeba/js/Tooltip.min.js",
			"media/com_akeeba/js/Tooltip.min.map",

			// Obsolete plugin file
			'plugins/console/akeebabackup.xml',

			// Remove “Archive integrity check” feature
			'administrator/components/com_akeeba/BackupPlatform/Joomla3x/Finalization',

			// Remove PieCon.js
			'media/com_akeeba/js/piecon.min.js',
			'media/com_akeeba/js/piecon.min.map',

			// Remove obsolete common PHP version warnings
			'administrator/components/com_akeeba/tmpl/CommonTemplates/phpversion_warning.php',
			'administrator/components/com_akeeba/tmpl/CommonTemplates/wrongphp.php',

			// Remove pCloud
			'administrator/components/com_akeeba/BackupEngine/Postproc/pcloud.json',
			'administrator/components/com_akeeba/BackupEngine/Postproc/Pcloud.php',
			'administrator/components/com_akeeba/BackupEngine/Postproc/Connector/Pcloud.php',

			// Remove iDriveSync — the service has been discontinued
			'administrator/components/com_akeeba/BackupEngine/Postproc/idrivesync.json',
			'administrator/components/com_akeeba/BackupEngine/Postproc/Idrivesync.php',
			'administrator/components/com_akeeba/BackupEngine/Postproc/Connector/Idrivesync.php',

			// Legacy filters
			'administrator/components/com_akeeba/BackupPlatform/Joomla3x/Filter/Stack/myjoomla.json',
			'administrator/components/com_akeeba/BackupPlatform/Joomla3x/Filter/Stack/StackMyjoomla.php',

		],
		'folders' => [
			// Directories used up to version 4.1 (inclusive)
			// -- Back-end
			'administrator/components/com_akeeba/akeeba',
			'administrator/components/com_akeeba/plugins',
			// -- Front-end
			'components/com_akeeba/views',

			// Directories used in version 4.2, but before 5.0
			// -- Back-end
			'administrator/components/com_akeeba/alice',
			'administrator/components/com_akeeba/assets',
			'administrator/components/com_akeeba/controllers',
			'administrator/components/com_akeeba/engine',
			'administrator/components/com_akeeba/helpers',
			'administrator/components/com_akeeba/models',
			'administrator/components/com_akeeba/platform',
			'administrator/components/com_akeeba/tables',
			'administrator/components/com_akeeba/views',
			// -- Front-end
			'components/com_akeeba/controllers',
			'components/com_akeeba/models',

			// Outdated media directories
			'media/com_akeeba/theme',

			// Dropbox v1 integration
			'administrator/components/com_akeeba/BackupEngine/Postproc/Connector/Dropbox',

			// Common tables (they're installed by FOF)
			'administrator/components/com_akeeba/sql/common',

			// ALICE refactoring
			"administrator/components/com_akeeba/AliceEngine",

			// Convert views to Blade
			"administrator/components/com_akeeba/View/Alice/tmpl",
			"administrator/components/com_akeeba/View/Backup/tmpl",
			"administrator/components/com_akeeba/View/Browser/tmpl",
			"administrator/components/com_akeeba/View/CommonTemplates",
			"administrator/components/com_akeeba/View/Configuration/tmpl",
			"administrator/components/com_akeeba/View/ConfigurationWizard/tmpl",
			"administrator/components/com_akeeba/View/ControlPanel/tmpl",
			"administrator/components/com_akeeba/View/DatabaseFilters/tmpl",
			"administrator/components/com_akeeba/View/Discover/tmpl",
			"administrator/components/com_akeeba/View/FileFilters/tmpl",
			"administrator/components/com_akeeba/View/IncludeFolders/tmpl",
			"administrator/components/com_akeeba/View/Log/tmpl",
			"administrator/components/com_akeeba/View/Manage/tmpl",
			"administrator/components/com_akeeba/View/MultipleDatabases/tmpl",
			"administrator/components/com_akeeba/View/Profiles/tmpl",
			"administrator/components/com_akeeba/View/RegExDatabaseFilters/tmpl",
			"administrator/components/com_akeeba/View/RegExFileFilter/tmpl",
			"administrator/components/com_akeeba/View/RemoteFiles/tmpl",
			"administrator/components/com_akeeba/View/Restore/tmpl",
			"administrator/components/com_akeeba/View/S3Import/tmpl",
			"administrator/components/com_akeeba/View/Schedule/tmpl",
			"administrator/components/com_akeeba/View/Transfer/tmpl",
			"administrator/components/com_akeeba/View/Upload/tmpl",

			// Base CLI script -- replaced with FOF
			'administrator/components/com_akeeba/Master/Cli',

			// 7.0.0 alpha base plugin
			'administrator/components/com_akeeba/Master/AkeebaPlugin',

			// Backup on Update view templates
			'plugins/system/backuponupdate/tmpl',

			// Changelog PNG images
			'media/com_akeeba/icons/changelog.png',

			// Rename ViewTemplates to tmpl
			'administrator/components/com_akeeba/ViewTemplates',
		],
	];

	/**
	 * @var string
	 */
	private $currentlyBeingInstalledCustomContainerFile;

	/**
	 * Runs on installation
	 *
	 * @param   JInstallerAdapterComponent  $parent  The parent object
	 *
	 * @return  void
	 */
	public function install($parent)
	{
		if (!defined('AKEEBA_THIS_IS_INSTALLATION_FROM_SCRATCH'))
		{
			define('AKEEBA_THIS_IS_INSTALLATION_FROM_SCRATCH', 1);
		}
	}

	/**
	 * Joomla! pre-flight event. This runs before Joomla! installs or updates the component. This is our last chance to
	 * tell Joomla! if it should abort the installation.
	 *
	 * @param   string                      $type    Installation type (install, update, discover_install)
	 * @param   JInstallerAdapterComponent  $parent  Parent object
	 *
	 * @return  boolean  True to let the installation proceed, false to halt the installation
	 */
	public function preflight(string $type, ComponentAdapter $parent): bool
	{
		if ($type === 'uninstall')
		{
			return true;
		}

		$this->isPaid                                     = is_dir($parent->getParent()->getPath('source') . '/backend/AliceEngine');
		$this->currentlyBeingInstalledCustomContainerFile = $parent->getParent()->getPath('source') . '/backend/Container.php';

		$result = parent::preflight($type, $parent);

		if (!$result)
		{
			return $result;
		}

		// Kill the old custom container file. If the update fails, install a second time and be bloody done with it.
		$customContainerFile = JPATH_ADMINISTRATOR . '/components/com_akeeba/Container.php';
		if (!@unlink($customContainerFile) && @file_exists($customContainerFile))
		{
			\Joomla\CMS\Filesystem\File::delete($customContainerFile);
		}

		// Move the server key file from /akeeba or /engine to /BackupEngine
		$componentPath = JPATH_ADMINISTRATOR . '/components/com_akeeba';
		$fromFile      = $componentPath . '/akeeba/serverkey.php';
		$toFile        = $componentPath . '/BackupEngine/serverkey.php';

		if (!file_exists($fromFile))
		{
			$fromFile = $componentPath . '/engine/serverkey.php';
		}

		if (@file_exists($fromFile) && !@file_exists($toFile))
		{
			$toPath = $componentPath . '/BackupEngine';

			if (@is_dir($componentPath) && !@is_dir($toPath))
			{
				JFolder::create($toPath);
			}

			if (@is_dir($toPath))
			{
				JFile::copy($fromFile, $toFile);
			}
		}

		return $result;
	}

	/**
	 * Runs after install, update or discover_update. In other words, it executes after Joomla! has finished installing
	 * or updating your component. This is the last chance you've got to perform any additional installations, clean-up,
	 * database updates and similar housekeeping functions.
	 *
	 * @param   string                      $type    install, update or discover_update
	 * @param   JInstallerAdapterComponent  $parent  Parent object
	 */
	public function postflight(string $type, ComponentAdapter $parent): void
	{
		if ($type === 'uninstall')
		{
			return;
		}

		// Let's make sure the custom container file is up to date.
		$this->containerBroken = $this->figureOutIfContainerIsBroken();

		// Parent method
		parent::postflight($type, $parent);

		if (!$this->containerBroken)
		{
			// Let's install common tables
			$this->installCommonTables();
		}

		// Add ourselves to the list of extensions depending on Akeeba FEF
		$this->addDependency('file_fef', $this->componentName);

		// Uninstall post-installation messages we are no longer using
		$this->uninstallObsoletePostinstallMessages();

		// Remove the update sites for this component on installation. The update sites are now handled at the package
		// level.
		$this->removeObsoleteUpdateSites($parent);

		// Remove the FOF 2.x update sites (annoying leftovers)
		$this->removeFOFUpdateSites();

		// If this is a new installation tell it to NOT mark the backup profiles as configured.
		if (defined('AKEEBA_THIS_IS_INSTALLATION_FROM_SCRATCH'))
		{
			$this->markProfilesAsNotConfiguredYet();
		}

		// This is an update of an existing installation
		if (!defined('AKEEBA_THIS_IS_INSTALLATION_FROM_SCRATCH') && !$this->containerBroken)
		{
			// Migrate profiles if necessary
			$this->migrateProfiles();
			// Migrate remote quota options
			$this->migrateRemoteQuotaOptions();
		}

		// Replace the system plugin with the actionlog plugin for logging user actions
		$this->switchActionLogPlugins();

		// Upgrade the old, single switch for front-end backups to the new two, separate switches
		if (!$this->containerBroken)
		{
			$this->upgradeFrontendEnableOption();
		}
	}

	/**
	 * Override this method to display a custom component installation message if you so wish
	 *
	 * @param   \JInstallerAdapterComponent  $parent  Parent class calling us
	 */
	protected function renderPostInstallation(ComponentAdapter $parent): void
	{
		try
		{
			$this->warnAboutJSNPowerAdmin();
		}
		catch (Exception $e)
		{
			// Don't sweat if the site's db croaks while I'm checking for 3PD software that causes trouble
		}

		// Load the version file
		if (!defined('AKEEBA_PRO'))
		{
			@include_once JPATH_ADMINISTRATOR . '/components/com_akeeba/version.php';
		}

		if (!defined('AKEEBA_PRO'))
		{
			define('AKEEBA_PRO', '0');
		}

		?>
		<img src="../media/com_akeeba/icons/logo-48.png" width="48" height="48" alt="Akeeba Backup" align="right" />

		<h2>Welcome to Akeeba Backup!</h2>

		<fieldset>
			<p>
				We strongly recommend watching our <a href="http://akee.ba/abfirstvideo">video tutorials</a> before
				using this component.
			</p>

			<p>
				If this is the first time you install Akeeba Backup on your site please run the <a
						href="index.php?option=com_akeeba&view=ConfigurationWizard">Configuration Wizard</a>. Akeeba
				Backup will configure itself optimally for your site.
			</p>

			<p>
				By installing this component you are implicitly accepting <a href="https://www.akeeba.com/license.html">
					its license (GNU GPLv3)</a> and our <a href="https://www.akeeba.com/privacy-policy.html">Terms of
																											 Service</a>,
				including our Support Policy.
			</p>
		</fieldset>
		<?php
		if (!$this->figureOutIfContainerIsBroken())
		{
			$container = null;
			$model     = null;

			if (class_exists('FOF40\\Container\\Container'))
			{
				try
				{
					$container = FOFContainer::getInstance('com_akeeba');
				}
				catch (\Exception $e)
				{
					$container = null;
				}
			}

			if (is_object($container) && class_exists('FOF40\\Container\\Container') && ($container instanceof FOFContainer))
			{
				/** @var \Akeeba\Backup\Admin\Model\UsageStatistics $model */
				try
				{
					$model = $container->factory->model('UsageStatistics')->tmpInstance();
				}
				catch (\Exception $e)
				{
					$model = null;
				}
			}

			/** @var \Akeeba\Backup\Admin\Model\UsageStatistics $model */
			try
			{
				if (is_object($model) && class_exists('Akeeba\\Backup\\Admin\\Model\\UsageStatistics')
					&& ($model instanceof Akeeba\Backup\Admin\Model\UsageStatistics)
					&& method_exists($model, 'collectStatistics'))
				{
					$iframe = $model->collectStatistics(true);

					if ($iframe)
					{
						echo $iframe;
					}
				}
			}
			catch (\Exception $e)
			{
			}
		}
	}

	/**
	 * Override this method to display a custom component uninstallation message if you so wish
	 *
	 * @param   \JInstallerAdapterComponent  $parent  Parent class calling us
	 */
	protected function renderPostUninstallation(ComponentAdapter $parent): void
	{
		?>
		<h2>Akeeba Backup Uninstallation Status</h2>
		<p>We are sorry that you decided to uninstall Akeeba Backup. Please let us know why by using the <a
					href="https://www.akeeba.com/contact-us.html" target="_blank">Contact Us form on our site</a>. We
		   appreciate your feedback; it helps us develop better software!</p>
		<?php
	}

	/**
	 * Removes obsolete update sites created for the component (we are now using an update site for the package, not the
	 * component).
	 *
	 * @param   JInstallerAdapterComponent  $parent  The parent installer
	 */
	protected function removeObsoleteUpdateSites($parent)
	{
		$db = $parent->getParent()->getDbo();

		$query = $db->getQuery(true)
			->select($db->qn('extension_id'))
			->from($db->qn('#__extensions'))
			->where($db->qn('type') . ' = ' . $db->q('component'))
			->where($db->qn('name') . ' = ' . $db->q($this->componentName));

		try
		{
			$extensionId = $db->setQuery($query)->loadResult();
		}
		catch (Exception $e)
		{
			// Your database is broken.
			return;
		}

		if (!$extensionId)
		{
			return;
		}

		$query = $db->getQuery(true)
			->select($db->qn('update_site_id'))
			->from($db->qn('#__update_sites_extensions'))
			->where($db->qn('extension_id') . ' = ' . $db->q($extensionId));

		try
		{
			$ids = $db->setQuery($query)->loadColumn(0);
		}
		catch (Exception $e)
		{
			// Your database is broken.
			return;
		}

		if (!is_array($ids) && empty($ids))
		{
			return;
		}

		foreach ($ids as $id)
		{
			$query = $db->getQuery(true)
				->delete($db->qn('#__update_sites'))
				->where($db->qn('update_site_id') . ' = ' . $db->q($id));
			$db->setQuery($query);

			try
			{
				$db->execute();
			}
			catch (\Exception $e)
			{
				// Do not fail in this case
			}
		}
	}

	protected function installCommonTables(): void
	{
		$container = null;
		$model     = null;

		if (class_exists('FOF40\\Container\\Container'))
		{
			try
			{
				$container = FOFContainer::getInstance('com_akeeba');
			}
			catch (\Exception $e)
			{
				$container = null;
			}
		}

		if (is_object($container) && class_exists('FOF40\\Container\\Container') && ($container instanceof FOFContainer))
		{
			/** @var \Akeeba\Backup\Admin\Model\UsageStatistics $model */
			try
			{
				$model = $container->factory->model('UsageStatistics')->tmpInstance();
			}
			catch (\Exception $e)
			{
				$model = null;
			}
		}
	}

	private function uninstallObsoletePostinstallMessages()
	{
		$db = JFactory::getDbo();

		$obsoleteTitleKeys = [
			// Remove "Upgrade profiles to ANGIE"
			'AKEEBA_POSTSETUP_LBL_ANGIEUPGRADE',
			// Remove "Enable System Restore Points"
			'AKEEBA_POSTSETUP_LBL_SRP',
			'AKEEBA_POSTSETUP_LBL_BACKUPONUPDATE',
			'AKEEBA_POSTSETUP_LBL_CONFWIZ',
			'AKEEBA_POSTSETUP_LBL_ACCEPTLICENSE',
			'AKEEBA_POSTSETUP_LBL_ACCEPTSUPPORT',
			'AKEEBA_POSTSETUP_LBL_ACCEPTBACKUPTEST',
		];

		foreach ($obsoleteTitleKeys as $obsoleteKey)
		{

			// Remove the "Upgrade profiles to ANGIE" post-installation message
			$query = $db->getQuery(true)
				->delete($db->qn('#__postinstall_messages'))
				->where($db->qn('title_key') . ' = ' . $db->q($obsoleteKey));
			try
			{
				$db->setQuery($query)->execute();
			}
			catch (Exception $e)
			{
				// Do nothing
			}
		}
	}

	/**
	 * The PowerAdmin extension makes menu items disappear. People assume it's our fault. JSN PowerAdmin authors don't
	 * own up to their software's issue. I have no choice but to warn our users about the faulty third party software.
	 */
	private function warnAboutJSNPowerAdmin()
	{
		$db = JFactory::getDbo();

		$query         = $db->getQuery(true)
			->select('COUNT(*)')
			->from($db->qn('#__extensions'))
			->where($db->qn('type') . ' = ' . $db->q('component'))
			->where($db->qn('element') . ' = ' . $db->q('com_poweradmin'))
			->where($db->qn('enabled') . ' = ' . $db->q('1'));
		$hasPowerAdmin = $db->setQuery($query)->loadResult();

		if (!$hasPowerAdmin)
		{
			return;
		}

		$query      = $db->getQuery(true)
			->select('manifest_cache')
			->from($db->qn('#__extensions'))
			->where($db->qn('type') . ' = ' . $db->q('component'))
			->where($db->qn('element') . ' = ' . $db->q('com_poweradmin'))
			->where($db->qn('enabled') . ' = ' . $db->q('1'));
		$paramsJson = $db->setQuery($query)->loadResult();

		$className = class_exists('JRegistry') ? 'JRegistry' : '\Joomla\Registry\Registry';

		/** @var \Joomla\Registry\Registry $jsnPAManifest */
		$jsnPAManifest = new $className();
		$jsnPAManifest->loadString($paramsJson, 'JSON');
		$version = $jsnPAManifest->get('version', '0.0.0');

		if (version_compare($version, '2.1.2', 'ge'))
		{
			return;
		}

		echo <<< HTML
<div class="well" style="margin: 2em 0;">
<h1 style="font-size: 32pt; line-height: 120%; color: red; margin-bottom: 1em">WARNING: Menu items for {$this->componentName} might not be displayed on your site.</h1>
<p style="font-size: 18pt; line-height: 150%; margin-bottom: 1.5em">
	We have detected that you are using JSN PowerAdmin on your site. This software ignores Joomla! standards and
	<b>hides</b> the Component menu items to {$this->componentName} in the administrator backend of your site. Unfortunately we
	can't provide support for third party software. Please contact the developers of JSN PowerAdmin for support
	regarding this issue.
</p>
<p style="font-size: 18pt; line-height: 120%; color: green;">
	Tip: You can disable JSN PowerAdmin to see the menu items to Akeeba Backup.
</p>
</div>

HTML;

	}

	/**
	 * Loads the Akeeba Engine if it's not already loaded
	 */
	private function loadAkeebaEngine()
	{
		if (class_exists('\\Akeeba\\Engine\\Platform'))
		{
			return;
		}

		// Load the language files
		$paths = [JPATH_ADMINISTRATOR, JPATH_ROOT];
		$jlang = JFactory::getLanguage();
		$jlang->load('com_akeeba', $paths[0], 'en-GB', true);
		$jlang->load('com_akeeba', $paths[1], 'en-GB', true);
		$jlang->load('com_akeeba' . '.override', $paths[0], 'en-GB', true);
		$jlang->load('com_akeeba' . '.override', $paths[1], 'en-GB', true);

		// Load the version file
		@include_once JPATH_ADMINISTRATOR . '/components/com_akeeba/version.php';

		if (!defined('AKEEBA_PRO'))
		{
			define('AKEEBA_PRO', '0');
		}

		// Enable Akeeba Engine
		if (!defined('AKEEBAENGINE'))
		{
			define('AKEEBAENGINE', 1);
		}

		// Load the engine
		$factoryPath = JPATH_ADMINISTRATOR . '/components/com_akeeba/BackupEngine/Factory.php';
		define('AKEEBAROOT', JPATH_ADMINISTRATOR . '/components/com_akeeba/BackupEngine');

		require_once $factoryPath;

		// Assign the correct platform
		Platform::addPlatform('joomla3x', JPATH_ADMINISTRATOR . '/components/com_akeeba/BackupPlatform/Joomla3x');
	}

	/**
	 * Migrates existing backup profiles. The changes currently made are:
	 * – Change post-processing from "s3" (legacy) to "amazons3" (current version)
	 * – Fix profiles with invalid embedded installer settings
	 *
	 * @return  void
	 */
	private function migrateProfiles()
	{
		$this->loadAkeebaEngine();

		// Get a list of backup profiles
		$db = JFactory::getDbo();

		try
		{
			$query    = $db->getQuery(true)
				->select($db->qn('id'))
				->from($db->qn('#__ak_profiles'));
			$profiles = $db->setQuery($query)->loadColumn();
		}
		catch (Exception $e)
		{
			// Eh, we couldn't load the profiles. Something's broken in the database. It will be fixed when the
			// installation continues but for now we have to just return without doing anything.
			return;
		}

		// Normally this should never happen as we're supposed to have at least profile #1
		if (empty($profiles))
		{
			return;
		}

		// Migrate each profile
		foreach ($profiles as $profile)
		{
			// Initialization
			$dirty = false;

			// Load the profile configuration
			try
			{
				Platform::getInstance()->load_configuration($profile);
				$config = \Akeeba\Engine\Factory::getConfiguration();
			}
			catch (Exception $e)
			{
				// Your database is broken :(
				continue;
			}

			// -- Migrate obsolete "s3" engine to "amazons3"
			$postProcType = $config->get('akeeba.advanced.postproc_engine', '');

			if ($postProcType == 's3')
			{
				$config->setKeyProtection('akeeba.advanced.postproc_engine', false);
				$config->setKeyProtection('engine.postproc.amazons3.signature', false);
				$config->setKeyProtection('engine.postproc.amazons3.accesskey', false);
				$config->setKeyProtection('engine.postproc.amazons3.secretkey', false);
				$config->setKeyProtection('engine.postproc.amazons3.usessl', false);
				$config->setKeyProtection('engine.postproc.amazons3.bucket', false);
				$config->setKeyProtection('engine.postproc.amazons3.directory', false);
				$config->setKeyProtection('engine.postproc.amazons3.rrs', false);
				$config->setKeyProtection('engine.postproc.amazons3.customendpoint', false);
				$config->setKeyProtection('engine.postproc.amazons3.legacy', false);

				$config->set('akeeba.advanced.postproc_engine', 'amazons3');
				$config->set('engine.postproc.amazons3.signature', 's3');
				$config->set('engine.postproc.amazons3.accesskey', $config->get('engine.postproc.s3.accesskey'));
				$config->set('engine.postproc.amazons3.secretkey', $config->get('engine.postproc.s3.secretkey'));
				$config->set('engine.postproc.amazons3.usessl', $config->get('engine.postproc.s3.usessl'));
				$config->set('engine.postproc.amazons3.bucket', $config->get('engine.postproc.s3.bucket'));
				$config->set('engine.postproc.amazons3.directory', $config->get('engine.postproc.s3.directory'));
				$config->set('engine.postproc.amazons3.rrs', $config->get('engine.postproc.s3.rrs'));
				$config->set('engine.postproc.amazons3.customendpoint', $config->get('engine.postproc.s3.customendpoint'));
				$config->set('engine.postproc.amazons3.legacy', $config->get('engine.postproc.s3.legacy'));

				$dirty = true;
			}

			// Fix profiles with invalid embedded installer settings
			$embeddedInstaller = $config->get('akeeba.advanced.embedded_installer');

			if (empty($embeddedInstaller) || ($embeddedInstaller == 'angie-joomla') || (
					(substr($embeddedInstaller, 0, 5) != 'angie') && ($embeddedInstaller != 'none')
				))
			{
				$config->setKeyProtection('akeeba.advanced.embedded_installer', false);
				$config->set('akeeba.advanced.embedded_installer', 'angie');
				$dirty = true;
			}

			// Save dirty records
			if ($dirty)
			{
				try
				{
					Platform::getInstance()->save_configuration($profile);
				}
				catch (Exception $e)
				{
					// Your database is broken!
					continue;
				}
			}
		}
	}

	/**
	 * Remove FOF 2.x update sites
	 */
	private function removeFOFUpdateSites()
	{
		$db    = JFactory::getDbo();
		$query = $db->getQuery(true)
			->delete($db->qn('#__update_sites'))
			->where($db->qn('location') . ' = ' . $db->q('http://cdn.akeeba.com/updates/fof.xml'));
		try
		{
			$db->setQuery($query)->execute();
		}
		catch (\Exception $e)
		{
			// Do nothing on failure
		}

	}

	private function markProfilesAsNotConfiguredYet()
	{
		try
		{
			$db    = JFactory::getDbo();
			$query = $db->getQuery(true)
				->select($db->qn('params'))
				->from($db->qn('#__extensions'))
				->where($db->qn('type') . ' = ' . $db->q('component'))
				->where($db->qn('element') . ' = ' . $db->q('com_akeeba'));

			$jsonData = $db->setQuery($query)->loadResult();

			if (class_exists('JRegistry'))
			{
				$reg = new JRegistry($jsonData);
			}
			else
			{
				$reg = new \Joomla\Registry\Registry($jsonData);
			}

			$reg->set('confwiz_upgrade', 1);
			$jsonData = $reg->toString('JSON');

			$query = $db->getQuery()
				->update($db->qn('#__extensions'))
				->set($db->qn('params') . ' = ' . $db->q($jsonData))
				->where($db->qn('type') . ' = ' . $db->q('component'))
				->where($db->qn('element') . ' = ' . $db->q('com_akeeba'));
			$db->setQuery($query)->execute();
		}
		catch (Exception $e)
		{
			// If that fails it's not the end of the world. The component is still usable, so just swallow any
			// exception.
		}
	}

	private function switchActionLogPlugins()
	{
		$db = \Joomla\CMS\Factory::getDbo();

		// Does the plg_system_akeebaactionlog plugin exist? If not, there's nothing to do here.
		$query = $db->getQuery(true)
			->select('*')
			->from('#__extensions')
			->where($db->qn('type') . ' = ' . $db->q('plugin'))
			->where($db->qn('folder') . ' = ' . $db->q('system'))
			->where($db->qn('element') . ' = ' . $db->q('akeebaactionlog'));
		try
		{
			$result = $db->setQuery($query)->loadAssoc();

			if (empty($result))
			{
				return;
			}

			$eid = $result['extension_id'];
		}
		catch (Exception $e)
		{
			return;
		}

		// If plg_system_akeebaactionlog is enabled: enable plg_actionlog_akeebabackup
		if (\Joomla\CMS\Plugin\PluginHelper::isEnabled('system', 'akeebaactionlog'))
		{
			$query = $db->getQuery(true)
				->update($db->qn('#__extensions'))
				->set($db->qn('enabled') . ' = ' . $db->q(1))
				->where($db->qn('type') . ' = ' . $db->q('plugin'))
				->where($db->qn('folder') . ' = ' . $db->q('actionlog'))
				->where($db->qn('element') . ' = ' . $db->q('akeebabackup'));
			try
			{
				$db->setQuery($query)->execute();
			}
			catch (Exception $e)
			{
			}
		}

		// Deactivate plg_system_akeebaactionlog
		$query = $db->getQuery(true)
			->update($db->qn('#__extensions'))
			->set($db->qn('enabled') . ' = ' . $db->q(0))
			->where($db->qn('type') . ' = ' . $db->q('plugin'))
			->where($db->qn('folder') . ' = ' . $db->q('system'))
			->where($db->qn('element') . ' = ' . $db->q('akeebaactionlog'));
		try
		{
			$db->setQuery($query)->execute();
		}
		catch (Exception $e)
		{
		}

		/**
		 * Here's a bummer. If you try to uninstall the plg_system_akeebaactionlog plugin Joomla throws a nonsensical
		 * error message about the plugin's XML manifest missing -- after it has already uninstalled the plugin! This
		 * error causes the package installation to fail which results in the extension being installed BUT the database
		 * record of the package NOT being present which makes it impossible to uninstall.
		 *
		 * So I have to hack my way around it which is ugly but the only viable alternative :(
		 */
		try
		{
			// Safely delete the row in the extensions table
			$row = JTable::getInstance('extension');
			$row->load((int) $eid);
			$row->delete($eid);

			// Delete the plugin's files
			$pluginPath = JPATH_PLUGINS . '/system/akeebaactionlog';

			if (is_dir($pluginPath))
			{
				JFolder::delete($pluginPath);
			}

			// Delete the plugin's language files
			$langFiles = [
				JPATH_ADMINISTRATOR . '/language/en-GB/en-GB.plg_system_akeebaactionlog.ini',
				JPATH_ADMINISTRATOR . '/language/en-GB/en-GB.plg_system_akeebaactionlog.sys.ini',
			];

			foreach ($langFiles as $file)
			{
				if (@is_file($file))
				{
					JFile::delete($file);
				}
			}
		}
		catch (Exception $e)
		{
			// I tried, I failed. Dear user, do NOT try to enable that old plugin. Bye!
		}
	}

	/**
	 * Upgrades the frontend_enable option to the two separate legacyapi_enabled and jsonapi_enabled options.
	 *
	 * Akeeba Backup 5 and 6 had a single component option, 'frontend_enable', to control both the Legacy Front-end
	 * Backup Feature. Since Akeeba Backup 7.0.0.b1 we have two separate options, legacyapi_enabled and jsonapi_enabled.
	 * This method detects the old-style component option and upgrades it to the two separate ones.
	 *
	 * @return  void
	 * @since   7.0.0.b1
	 */
	private function upgradeFrontendEnableOption()
	{
		$container = FOFContainer::getInstance('com_akeeba');
		$container->params->reload();
		$oldValue = $container->params->get('frontend_enable', -1);

		if (!in_array($oldValue, [0, 1]))
		{
			return;
		}

		$container->params->set('legacyapi_enabled', $oldValue);
		$container->params->set('jsonapi_enabled', $oldValue);
		$container->params->set('frontend_enable', -1);

		$container->params->save();
	}

	/**
	 * Figure out if there is a custom container class which is not the correct FOF version.
	 *
	 * @return  bool
	 */
	private function figureOutIfContainerIsBroken()
	{
		// FOF 4 is not loaded. Everything is broken. HOW THE HECK AM I EVEN RUNNING?!
		if (!class_exists('FOF40\\Container\\Container'))
		{
			return true;
		}

		// This is the custom container file that gives us grief on update
		$originalContainerFile = JPATH_ADMINISTRATOR . '/components/com_akeeba/Container.php';
		$customContainerFile = JPATH_ADMINISTRATOR . '/components/com_akeeba/Container.php';

		// Do I have this in the installation package's temporary extracted directory?
		if (
			!empty($this->currentlyBeingInstalledCustomContainerFile) &&
			@file_exists($this->currentlyBeingInstalledCustomContainerFile) &&
			!@is_file($this->currentlyBeingInstalledCustomContainerFile)
		)
		{
			$customContainerFile = $this->currentlyBeingInstalledCustomContainerFile;
		}

		/**
		 * Let's invalidate the opcache for the custom container file. We need to do that before checking if the file
		 * exists since opcache may also be caching whether the file exists and we can't rely on ini_get to find that
		 * out since some servers disable it.
		 */
		if (function_exists('opcache_invalidate'))
		{
			/** @noinspection PhpComposerExtensionStubsInspection */
			opcache_invalidate($originalContainerFile, true);
			/** @noinspection PhpComposerExtensionStubsInspection */
			opcache_invalidate($customContainerFile, true);
		}

		// Clear the stat cache just in case
		@clearstatcache(true);

		// Does the file exist?
		if (!@file_exists($customContainerFile))
		{
			// The file is not there so it can't be broken (duh!)
			return false;
		}

		// Now let's try to load it if the same class doesn't already exist.
		if (!class_exists('Akeeba\Backup\Admin\Container', false))
		{
			@include_once $customContainerFile;
		}

		// Did it really load?
		if (!class_exists('Akeeba\Backup\Admin\Container'))
		{
			// No? OK, what doesn't exist can't be broken.
			return false;
		}

		return !is_subclass_of('Akeeba\Backup\Admin\Container', FOFContainer::class);
	}

	/**
	 * Migrate profiles' quota options after local and remote quotas were split to separate options.
	 *
	 * @return  void
	 * @since   8.4.0
	 */
	private function migrateRemoteQuotaOptions()
	{
		// Get a list of backup profiles
		$db = JFactory::getDbo();

		try
		{
			$query    = $db->getQuery(true)
				->select($db->qn('id'))
				->from($db->qn('#__ak_profiles'));
			$profileIds = $db->setQuery($query)->loadColumn();
		}
		catch (Exception $e)
		{
			// Eh, we couldn't load the profiles. Something's broken in the database. It will be fixed when the
			// installation continues but for now we have to just return without doing anything.
			return;
		}

		// Normally this should never happen as we're supposed to have at least profile #1
		if (empty($profileIds))
		{
			return;
		}

		$this->loadAkeebaEngine();

		$platform       = Platform::getInstance();
		$currentProfile = $platform->get_active_profile();

		foreach ($profileIds as $profile)
		{
			// Load the profile configuration
			try
			{
				$platform->load_configuration($profile);
				$config = Factory::getConfiguration();
			}
			catch (\Throwable $e)
			{
				// Your database is broken :(
				continue;
			}

			if ($config->get('akeeba.quota.remote', 0) != 1)
			{
				continue;
			}

			// Transcribe the local quota to remote quota settings if the legacy "Enable remote quotas" option is on.
			$protected = $config->getProtectedKeys();
			$config->setProtectedKeys([]);

			$config->set('akeeba.quota.remote', null);
			$config->set('akeeba.quota.remotely.maxage.enable', $config->get('akeeba.quota.maxage.enable', 0));
			$config->set('akeeba.quota.remotely.maxage.maxdays', $config->get('akeeba.quota.maxage.maxdays', 31));
			$config->set('akeeba.quota.remotely.maxage.keepday', $config->get('akeeba.quota.maxage.keepday', 1));
			$config->set('akeeba.quota.remotely.enable_size_quota', $config->get('akeeba.quota.enable_size_quota', 0));
			$config->set('akeeba.quota.remotely.size_quota', $config->get('akeeba.quota.size_quota', 15728640));
			$config->set('akeeba.quota.remotely.enable_count_quota', $config->get('akeeba.quota.enable_count_quota', 1));
			$config->set('akeeba.quota.remotely.count_quota', $config->get('akeeba.quota.count_quota', 3));

			$config->setProtectedKeys($protected);

			// Save the changes
			try
			{
				$platform->save_configuration($profile);
			}
			catch (\Throwable $e)
			{
				// Your database is broken!
				continue;
			}
		}

		$platform->load_configuration($currentProfile);
	}

}
PK�!]|��Nfields/web.confignu&1i�<?xml version="1.0"?>
<!--
    This only works on IIS 7 or later. See https://www.iis.net/configreference/system.webserver/security/requestfiltering/fileextensions
-->
<configuration>
    <system.webServer>
        <security>
            <requestFiltering>
                <fileExtensions allowUnlisted="false" >
                    <clear />
                    <add fileExtension=".html" allowed="true"/>
                </fileExtensions>
            </requestFiltering>
        </security>
    </system.webServer>
</configuration>PK�!]�J���fields/urlencoded.phpnu&1i�<?php
/**
 * @package   akeebabackup
 * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

defined('_JEXEC') || die();

use Joomla\CMS\Form\FormHelper;

if (class_exists('JFormFieldUrlencoded'))
{
	return;
}

FormHelper::loadFieldClass('text');

class JFormFieldUrlencoded extends JFormFieldText
{
	protected function getInput()
	{
		$this->value = urlencode($this->value);

		return parent::getInput();
	}
}
PK�!]��ѡ��fields/.htaccessnu&1i�<IfModule !mod_authz_core.c>
Order deny,allow
Deny from all
</IfModule>
<IfModule mod_authz_core.c>
  <RequireAll>
    Require all denied
  </RequireAll>
</IfModule>
PK�!]	�}^fields/fancyradio.phpnu&1i�<?php
/**
 * @package   akeebabackup
 * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

defined('_JEXEC') || die();

require_once JPATH_LIBRARIES . '/fof40/Html/Fields/fancyradio.php';PK�!]�s���fields/oauth2url.phpnu&1i�<?php
/**
 * @package   akeebabackup
 * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

defined('_JEXEC') || die();

use Joomla\CMS\Form\FormHelper;

if (class_exists('JFormFieldNote'))
{
	return;
}

FormHelper::loadFieldClass('note');

class JFormFieldOauth2url extends JFormFieldNote
{
	protected function getLabel()
	{
		return '';
	}

	protected function getInput()
	{
		$engine = $this->element['engine'] ?? 'example';
		$uri = rtrim(\JUri::base() ,'/');

		if (substr($uri, -14) === '/administrator')
		{
			$uri = substr($uri, 0, -14);
		}

		$text1 = JText::_('COM_AKEEBA_CONFIG_OAUTH2URLFIELD_YOU_WILL_NEED');
		$text2 = JText::_('COM_AKEEBA_CONFIG_OAUTH2URLFIELD_CALLBACK_URL');
		$text3 = JText::_('COM_AKEEBA_CONFIG_OAUTH2URLFIELD_HELPER_URL');
		$text4 = JText::_('COM_AKEEBA_CONFIG_OAUTH2URLFIELD_REFRESH_URL');

		return <<< HTML
<div class="alert alert-info mx-2 my-2">
	<p>
		$text1
	</p>
	<p>
		<strong>$text2</strong>:
		<br/>
		<code>$uri/index.php?option=com_akeeba&view=oauth2&task=step2&format=raw&engine={$engine}</code>
	</p>
	<p>
		<strong>$text3</strong>:
		<br/>
		<code>$uri/index.php?option=com_akeeba&view=oauth2&task=step1&format=raw&engine={$engine}</code>
	</p>
	<p>
		<strong>$text4</strong>:
		<br/>
		<code>$uri/index.php?option=com_akeeba&view=oauth2&task=refresh&format=raw&engine={$engine}</code>
	</p>
</div>
HTML;

	}
}
PK�!]�r��==fields/backupprofiles.phpnu&1i�<?php
/**
 * @package   akeebabackup
 * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

defined('_JEXEC') || die();

use Joomla\CMS\Form\FormField;
use Joomla\CMS\HTML\HTMLHelper;

if (class_exists('JFormFieldBackupprofiles'))
{
	return;
}

/**
 * Our main element class, creating a multi-select list out of an SQL statement
 */
class JFormFieldBackupprofiles extends FormField
{
	/**
	 * Element name
	 *
	 * @var        string
	 */
	protected $name = 'Backupprofiles';

	function getInput()
	{
		$db = \Joomla\CMS\Factory::getDBO();

		$query = $db->getQuery(true)
			->select([
				$db->qn('id'),
				$db->qn('description'),
			])->from($db->qn('#__ak_profiles'));
		$db->setQuery($query);
		$key = 'id';
		$val = 'description';

		$objectList = $db->loadObjectList();

		if (!is_array($objectList))
		{
			$objectList = [];
		}

		foreach ($objectList as $o)
		{
			$o->description = "#{$o->id}: {$o->description}";
		}

		$showNone = $this->element['show_none'] ? (string) $this->element['show_none'] : '';
		$showNone = in_array(strtolower($showNone), ['yes', '1', 'true', 'on']);

		if ($showNone)
		{
			$defaultItem = (object) [
				'id'          => '0',
				'description' => \Joomla\CMS\Language\Text::_('COM_AKEEBA_FORMFIELD_BACKUPPROFILES_NONE'),
			];

			array_unshift($objectList, $defaultItem);
		}

		HTMLHelper::_('formbehavior.chosen');

		return HTMLHelper::_('select.genericlist', $objectList, $this->name, 'class="inputbox advancedSelect"', $key, $val, $this->value, $this->id);
	}
}
PK�!]7=v3fields/akencrypted.phpnu&1i�<?php
/**
 * @package   akeebabackup
 * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

defined('_JEXEC') || die();

use Joomla\CMS\Form\FormHelper;

if (class_exists('JFormFieldUrlencoded'))
{
	return;
}

FormHelper::loadFieldClass('text');

class JFormFieldAkencrypted extends JFormFieldText
{
	protected function getInput()
	{
		$this->value = $this->conditionalDecrypt($this->value);

		return parent::getInput();
	}

	private function conditionalDecrypt($value)
	{
		// If the Factory is not already loaded we have to load the
		if (!class_exists('Akeeba\Engine\Factory'))
		{
			if (!defined('FOF40_INCLUDED') && !@include_once(JPATH_LIBRARIES . '/fof40/include.php'))
			{
				return $value;
			}

			$container = \FOF40\Container\Container::getInstance('com_akeeba', [], 'admin');

			/** @var \Akeeba\Backup\Admin\Dispatcher\Dispatcher $dispatcher */
			$dispatcher = $container->dispatcher;

			try
			{
				$dispatcher->loadAkeebaEngine();
				$dispatcher->loadAkeebaEngineConfiguration();
			}
			catch (Exception $e)
			{
				return $value;
			}
		}

		$secureSettings = \Akeeba\Engine\Factory::getSecureSettings();

		return $secureSettings->decryptSettings($this->value);
	}
}
PK�!]/y-g��
Container.phpnu&1i�<?php
/**
 * @package   akeebabackup
 * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

namespace Akeeba\Backup\Admin;

defined('_JEXEC') || die;

use FOF40\Container\Container as TheFOF4BaseContainer;

/**
 * Akeeba Backup backend component Container
 *
 * @since  7.1.0
 */
class Container extends TheFOF4BaseContainer
{

}
PK�!]
��}}$views/Configuration/tmpl/default.xmlnu�[���<?xml version="1.0" encoding="utf-8"?>
<!--~
  ~ @package   akeebabackup
  ~ @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
  ~ @license   GNU General Public License version 3, or later
  -->
<metadata>
	<layout title="COM_AKEEBA_VIEW_CONFIGURATION_TITLE">
		<message>
			<![CDATA[COM_AKEEBA_VIEW_CONFIGURATION_DESC]]>
		</message>
	</layout>
</metadata>
PK�!]����oo#views/ControlPanel/tmpl/default.xmlnu�[���<?xml version="1.0" encoding="utf-8"?>
<!--~
  ~ @package   akeebabackup
  ~ @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
  ~ @license   GNU General Public License version 3, or later
  -->
<metadata>
	<layout title="COM_AKEEBA_VIEW_CPANEL_TITLE">
		<message>
			<![CDATA[COM_AKEEBA_VIEW_CPANEL_DESC]]>
		</message>
	</layout>
</metadata>
PK�!]��S�ooviews/Manage/tmpl/default.xmlnu�[���<?xml version="1.0" encoding="utf-8"?>
<!--~
  ~ @package   akeebabackup
  ~ @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
  ~ @license   GNU General Public License version 3, or later
  -->
<metadata>
	<layout title="COM_AKEEBA_VIEW_MANAGE_TITLE">
		<message>
			<![CDATA[COM_AKEEBA_VIEW_MANAGE_DESC]]>
		</message>
	</layout>
</metadata>
PK�!]�Aͼ��views/Backup/tmpl/default.xmlnu�[���<?xml version="1.0" encoding="utf-8"?>
<!--~
  ~ @package   akeebabackup
  ~ @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
  ~ @license   GNU General Public License version 3, or later
  -->
<metadata>
	<layout title="COM_AKEEBA_VIEW_BACKUP_TITLE">
		<message>
			<![CDATA[COM_AKEEBA_VIEW_BACKUP_DESC]]>
		</message>
	</layout>
	<fields name="request" addfieldpath="administrator/components/com_akeeba/fields">
		<fieldset name="request">
			<field name="profileid" type="backupprofiles"
				   show_none="yes"
				   default="0"
				   label="COM_AKEEBA_VIEW_BACKUP_PROFILE_LABEL"
				   description="COM_AKEEBA_VIEW_BACKUP_PROFILE_DESC"
			/>

			<field name="autostart" type="fancyradio"
				   default="0"
				   class="btn-group"
				   label="COM_AKEEBA_VIEW_BACKUP_AUTOSTART_LABEL"
				   description="COM_AKEEBA_VIEW_BACKUP_AUTOSTART_DESC"
			>
				<option value="0">JNo</option>
				<option value="1">JYes</option>
			</field>

			<field name="akeeba_hide_toolbar" type="fancyradio"
				   default="0"
				   class="btn-group"
				   label="COM_AKEEBA_VIEW_BACKUP_HIDETOOLBAR_LABEL"
				   description="COM_AKEEBA_VIEW_BACKUP_HIDETOOLBAR_DESC"
			>
				<option value="0">JNo</option>
				<option value="1">JYes</option>
			</field>

			<field name="returnurl" type="urlencoded"
				   default=""
				   label="COM_AKEEBA_VIEW_BACKUP_RETURNURL_LABEL"
				   description="COM_AKEEBA_VIEW_BACKUP_RETURNURL_DESC"
				   />
		</fieldset>
	</fields>
</metadata>
PK�!]
�!CliCommands/ProfileCopy.phpnu&1i�<?php
/**
 * @package   akeebabackup
 * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

namespace Akeeba\Backup\Admin\CliCommands;

defined('_JEXEC') || die;

use Akeeba\Backup\Admin\Model\Profiles;
use Exception;
use FOF40\Container\Container;
use FOF40\Model\DataModel\Exception\RecordNotLoaded;
use Joomla\Console\Command\AbstractCommand;
use Akeeba\Backup\Admin\CliCommands\MixIt\ArgumentUtilities;
use Akeeba\Backup\Admin\CliCommands\MixIt\ConfigureIO;
use Akeeba\Backup\Admin\CliCommands\MixIt\PrintFormattedArray;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * akeeba:profile:copy
 *
 * Creates a copy of an Akeeba Backup profile
 *
 * @since   7.5.0
 */
class ProfileCopy extends AbstractCommand
{
	use ConfigureIO, ArgumentUtilities, PrintFormattedArray;

	/**
	 * The default command name
	 *
	 * @var    string
	 * @since  7.5.0
	 */
	protected static $defaultName = 'akeeba:profile:copy';

	/**
	 * Internal function to execute the command.
	 *
	 * @param   InputInterface   $input   The input to inject into the command.
	 * @param   OutputInterface  $output  The output to inject into the command.
	 *
	 * @return  integer  The command exit code
	 *
	 * @since   7.5.0
	 */
	protected function doExecute(InputInterface $input, OutputInterface $output): int
	{
		$this->configureSymfonyIO($input, $output);

		$format      = (string) $this->cliInput->getOption('format') ?? 'text';
		$format      = in_array($format, ['text', 'json']) ? $format : 'text';
		$id          = (int) $this->cliInput->getArgument('id') ?? 0;
		$withFilters = (bool) $this->cliInput->getArgument('filters') ?? false;

		$container = Container::getInstance('com_akeeba');

		/** @var Profiles $model */
		$model = $container->factory->model('Profiles')->tmpInstance();

		try
		{
			$source = $model->findOrFail($id);
		}
		catch (RecordNotLoaded $e)
		{
			$this->ioStyle->error(sprintf("Cannot copy profile %s; profile not found.", $id));

			return 1;
		}

		$profileData = $source->getData();
		unset($profileData['id']);

		if (!$withFilters)
		{
			$profileData['filters'] = '';
		}

		$description = (string) $this->cliInput->getArgument('description') ?? 0;

		if (!is_null($description))
		{
			$profileData['description'] = trim($description);
		}

		$profileData['quickicon'] = (bool) $this->cliInput->getArgument('quickicon') ?? $profileData['quickicon'];

		try
		{
			$newProfile = $model->create($profileData);
		}
		catch (Exception $e)
		{
			$this->ioStyle->error(sprintf("Cannot copy profile #%s: %s", $id, $e->getMessage()));

			return 2;
		}

		if ($format == 'json')
		{
			echo json_encode($newProfile->getId());

			return 0;
		}

		$this->ioStyle->success(sprintf("Copy successful. Created new profile with ID %s.", $newProfile->getId()));

		return 0;
	}

	/**
	 * Configure the command.
	 *
	 * @return  void
	 *
	 * @since   7.5.0
	 */
	protected function configure(): void
	{
		$help = "<info>%command.name%</info> will create a copy of an Akeeba Backup profile.
		\nUsage: <info>php %command.full_name%</info>";

		$this->addArgument('id', InputOption::VALUE_REQUIRED, 'The numeric ID of the profile to copy');
		$this->addOption('filters', null, InputOption::VALUE_NONE, 'Include filters in the copy.', false);
		$this->addOption('description', null, InputOption::VALUE_OPTIONAL, 'Description for the new backup profile. Uses the old profile\'s description if not specified.', null);
		$this->addOption('quickicon', null, InputOption::VALUE_OPTIONAL, 'Should the new backup profile have a one-click backup icon? Copies the old profile\'s setting if not specified.', null);
		$this->addOption('format', null, InputOption::VALUE_OPTIONAL, 'The format for the response. Use JSON to get a JSON-parseable numeric ID of the new backup profile. Values: text, json', 'text');

		$this->setDescription('Creates a copy of an Akeeba Backup profile');
		$this->setHelp($help);
	}
}
PK�!]:�llCliCommands/FilterList.phpnu&1i�<?php
/**
 * @package   akeebabackup
 * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

namespace Akeeba\Backup\Admin\CliCommands;

defined('_JEXEC') || die;

use Akeeba\Backup\Admin\Model\DatabaseFilters;
use Akeeba\Backup\Admin\Model\FileFilters;
use Akeeba\Backup\Admin\Model\IncludeFolders;
use Akeeba\Backup\Admin\Model\MultipleDatabases;
use Akeeba\Backup\Admin\Model\RegExDatabaseFilters;
use Akeeba\Backup\Admin\Model\RegExFileFilters;
use FOF40\Container\Container;
use Joomla\Console\Command\AbstractCommand;
use Akeeba\Backup\Admin\CliCommands\MixIt\ArgumentUtilities;
use Akeeba\Backup\Admin\CliCommands\MixIt\ConfigureIO;
use Akeeba\Backup\Admin\CliCommands\MixIt\FilterRoots;
use Akeeba\Backup\Admin\CliCommands\MixIt\IsPro;
use Akeeba\Backup\Admin\CliCommands\MixIt\PrintFormattedArray;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * akeeba:filter:list
 *
 * Get the filter values known to Akeeba Backup.
 *
 * @since   7.5.0
 */
class FilterList extends AbstractCommand
{
	use ConfigureIO, ArgumentUtilities, PrintFormattedArray, IsPro, FilterRoots;

	/**
	 * The default command name
	 *
	 * @var    string
	 * @since  7.5.0
	 */
	protected static $defaultName = 'akeeba:filter:list';

	/**
	 * Internal function to execute the command.
	 *
	 * @param   InputInterface   $input   The input to inject into the command.
	 * @param   OutputInterface  $output  The output to inject into the command.
	 *
	 * @return  integer  The command exit code
	 *
	 * @since   7.5.0
	 */
	protected function doExecute(InputInterface $input, OutputInterface $output): int
	{
		$this->configureSymfonyIO($input, $output);

		$profileId = (int) ($this->cliInput->getOption('profile') ?? 1);

		define('AKEEBA_PROFILE', $profileId);

		$root   = (string) ($this->cliInput->getOption('root') ?? '');
		$target = (string) ($this->cliInput->getOption('target') ?? 'fs');
		$type   = (string) ($this->cliInput->getOption('type') ?? 'exclude');
		$format = (string) ($this->cliInput->getOption('format') ?? 'table');

		if (!in_array($target, ['fs', 'db']))
		{
			$target = 'fs';
		}

		if (!in_array($type, ['include', 'exclude', 'regex']))
		{
			$type = 'exclude';
		}

		if (!$this->isPro())
		{
			$type = 'exclude';
		}

		$roots = $this->getRoots($target);

		if (empty($root))
		{
			$root = ($target == 'fs') ? '[SITEROOT]' : '[SITEDB]';
		}

		$output = [];

		if (!in_array($root, $roots))
		{
			$this->ioStyle->error(sprintf("Unknown %s root '%s'.", $target, $root));

			return 1;
		}


		if ($format === 'table')
		{
			$this->ioStyle->title('List of Akeeba Backup filters matching your criteria');
		}

		$container = Container::getInstance('com_akeeba', [], 'admin');

		switch ("$target.$type")
		{
			case "fs.exclude":
				/** @var FileFilters $model */
				$model      = $container->factory->model('FileFilters')->tmpInstance();
				$allFilters = $model->get_filters($root);

				foreach ($allFilters as $item)
				{
					$output[] = [
						'filter' => $item['node'],
						'type'   => $item['type'],
					];
				}

				break;

			case "fs.regex":
				/** @var RegExFileFilters $model */
				$model      = $container->factory->model('RegExFileFilters')->tmpInstance();
				$allFilters = $model->get_regex_filters($root);

				foreach ($allFilters as $item)
				{
					$output[] = [
						'filter' => $item['item'],
						'type'   => $item['type'],
					];
				}

				break;

			case "fs.include":
				/** @var IncludeFolders $model */
				$model      = $container->factory->model('IncludeFolders')->tmpInstance();
				$allFilters = $model->get_directories();

				foreach ($allFilters as $uuid => $item)
				{
					$output[] = [
						'filter'               => $uuid,
						'type'                 => 'extradirs',
						'filesystem_directory' => $item[0],
						'virtual_directory'    => $item[1],
					];
				}

				break;

			case "db.exclude":
				/** @var DatabaseFilters $model */
				$model      = $container->factory->model('DatabaseFilters')->tmpInstance();
				$allFilters = $model->get_filters($root);

				foreach ($allFilters as $item)
				{
					$output[] = [
						'filter' => $item['node'],
						'type'   => $item['type'],
					];
				}

				break;

			case "db.regex":
				/** @var RegExDatabaseFilters $model */
				$model      = $container->factory->model('RegExDatabaseFilters')->tmpInstance();
				$allFilters = $model->get_regex_filters($root);

				foreach ($allFilters as $item)
				{
					$output[] = [
						'filter' => $item['item'],
						'type'   => $item['type'],
					];
				}

				break;

			case "db.include":
				/** @var MultipleDatabases $model */
				$model      = $container->factory->model('MultipleDatabases')->tmpInstance();
				$allFilters = $model->get_databases();

				foreach ($allFilters as $uuid => $item)
				{
					$output[] = [
						'filter'   => $uuid,
						'type'     => 'multidb',
						'host'     => $item['host'],
						'driver'   => $item['driver'],
						'port'     => $item['port'],
						'username' => $item['username'],
						'password' => $item['password'],
						'database' => $item['database'],
						'prefix'   => $item['prefix'],
						'dumpFile' => $item['dumpFile'],
					];
				}

				break;
		}

		return $this->printFormattedAndReturn($output, $format);
	}

	/**
	 * Configure the command.
	 *
	 * @return  void
	 *
	 * @since   7.5.0
	 */
	protected function configure(): void
	{
		$help = "<info>%command.name%</info> will list filter values for an Akeeba Backup profile.
		\nUsage: <info>php %command.full_name%</info>";


		$this->addOption('root', null, InputOption::VALUE_OPTIONAL, 'Which filter root to use. Defaults to [SITEROOT] or [SITEDB] depending on the --target option. Ignored for --type=include. Tip: the filesystem and database roots are the "filter" column for --type=include. There are two special roots, [SITEROOT] (the filesystem root of the Joomla site) and [SITEDB] (the main database of the Joomla site).', '');
		$this->addOption('profile', null, InputOption::VALUE_OPTIONAL, 'The backup profile to use. Default: 1.', 1);
		$this->addOption('target', null, InputOption::VALUE_OPTIONAL, 'The target of filters you want to list: fs (files and folders) or db (database)', 'fs');
		$this->addOption('type', null, InputOption::VALUE_OPTIONAL, 'The type of filters you want to list: exclude, include or regex', 'exclude');
		$this->addOption('format', null, InputOption::VALUE_OPTIONAL, 'Output format: table, json, yaml, csv, count.', 'table');

		$this->setDescription('Get the filter values known to Akeeba Backup.');
		$this->setHelp($help);
	}
}
PK�!]�ɮ�ttCliCommands/BackupDownload.phpnu&1i�<?php
/**
 * @package   akeebabackup
 * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

namespace Akeeba\Backup\Admin\CliCommands;

defined('_JEXEC') || die;

use Akeeba\Backup\Admin\Model\Statistics;
use Akeeba\Engine\Factory;
use Akeeba\Engine\Platform;
use FOF40\Container\Container;
use Joomla\Console\Command\AbstractCommand;
use Akeeba\Backup\Admin\CliCommands\MixIt\ArgumentUtilities;
use Akeeba\Backup\Admin\CliCommands\MixIt\ConfigureIO;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * akeeba:backup:download
 *
 * Returns a backup archive part for a backup record known to Akeeba Backup
 *
 * @since   7.5.0
 */
class BackupDownload extends AbstractCommand
{
	use ConfigureIO, ArgumentUtilities;

	/**
	 * The default command name
	 *
	 * @var    string
	 * @since  7.5.0
	 */
	protected static $defaultName = 'akeeba:backup:download';

	/**
	 * Internal function to execute the command.
	 *
	 * @param   InputInterface   $input   The input to inject into the command.
	 * @param   OutputInterface  $output  The output to inject into the command.
	 *
	 * @return  integer  The command exit code
	 *
	 * @since   7.5.0
	 */
	protected function doExecute(InputInterface $input, OutputInterface $output): int
	{
		$this->configureSymfonyIO($input, $output);

		$container = Container::getInstance('com_akeeba', [], 'admin');

		$id      = (int) $this->cliInput->getArgument('id') ?? 0;
		$part    = (int) $this->cliInput->getArgument('part') ?? 0;
		$outFile = $this->cliInput->getOption('file');

		if (!empty($outFile))
		{
			$this->ioStyle->title(sprintf('Retrieving part #%d of Akeeba Backup record #%d', $part, $id));
		}

		if ($id <= 0)
		{
			$this->ioStyle->error('Invalid backup record');

			return 1;
		}


		/** @var Statistics $model */
		$model = $container->factory->model('Statistics')->tmpInstance();
		$model->setState('id', $id);

		$stat         = Platform::getInstance()->get_statistics($id);
		$allFileNames = Factory::getStatistics()->get_all_filenames($stat);

		if (empty($allFileNames))
		{
			$this->ioStyle->error(sprintf("Backup record '%s' does not have any files available for download. Have you already deleted them?", $id));

			return 1;
		}

		if (is_null($allFileNames))
		{
			$this->ioStyle->error(sprintf("Backup record '%s' does not have any files available for download on the server. If they are stored remotely you may need to use the fetch command first.", $id));

			return 2;
		}

		if (($part >= (is_array($allFileNames) || $allFileNames instanceof \Countable ? count($allFileNames) : 0)) || !isset($allFileNames[$part]))
		{
			$this->ioStyle->error(sprintf("There is no part '%s' of backup record '%s'.", $part, $id));

			return 3;
		}

		$fileName = $allFileNames[$part];

		if (!@file_exists($fileName))
		{
			$this->ioStyle->error(sprintf("Can not find part '%s' of backup record '%s' on the server.", $part, $id));

			return 4;
		}

		$basename  = @basename($fileName);
		$fileSize  = @filesize($fileName);
		$extension = strtolower(str_replace(".", "", strrchr($fileName, ".")));

		if (empty($outFile))
		{
			readfile($fileName);

			return 0;
		}

		if (is_dir($outFile))
		{
			$outFile = rtrim($outFile, '//\\') . DIRECTORY_SEPARATOR . $basename;
		}
		else
		{
			$dotPos  = strrpos($outFile, '.');
			$outFile = ($dotPos === false) ? $outFile : (substr($outFile, 0, $dotPos) . '.' . $extension);
		}

		// Read in 1M chunks
		$blocksize = 1048576;
		$handle    = @fopen($fileName, "r");

		if ($handle === false)
		{
			$this->ioStyle->error(sprintf("Cannot open '%s' for reading. Check the permissions / ACLs of the file.", $fileName));

			return 5;
		}

		$fp = @fopen($outFile, 'w');

		if ($fp === false)
		{
			fclose($handle);

			$this->ioStyle->error(sprintf("Cannot open '%s' for writing. Check whether the folder exists and the permissions / ACLs of both the enclosing folder and the file.", $outFile));

			return 6;
		}

		$progress    = $this->ioStyle->createProgressBar($fileSize);
		$runningSize = 0;
		$progress->display();

		while (!@feof($handle))
		{
			$data        = @fread($handle, $blocksize);
			$readLength  = strlen($data);
			$runningSize += $readLength;

			fwrite($fp, $data);

			$progress->setProgress($readLength);
		}

		$progress->finish();

		$this->ioStyle->newLine(2);

		@fclose($handle);
		@fclose($fp);

		$this->ioStyle->success(sprintf('Downloaded part %d of backup record #%d into file %s', $part, $id, $outFile));

		return 0;
	}

	/**
	 * Configure the command.
	 *
	 * @return  void
	 *
	 * @since   7.5.0
	 */
	protected function configure(): void
	{
		$help = "<info>%command.name%</info> will output or write a file with a backup archive part of a backup record known to Akeeba Backup
		\nUsage: <info>php %command.full_name%</info>";

		$this->addArgument('id', InputArgument::REQUIRED, 'The id of the backup record to retrieve archives for');
		$this->addArgument('part', InputArgument::OPTIONAL, 'The part number of the backup archive to retrieve');
		$this->addOption('file', null, InputOption::VALUE_OPTIONAL, 'File path to write to. Will output to STDOUT if not defined.');
		$this->setDescription('Returns a backup archive part for a backup record known to Akeeba Backup');
		$this->setHelp($help);
	}
}
PK�!]�X����CliCommands/ProfileImport.phpnu&1i�<?php
/**
 * @package   akeebabackup
 * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

namespace Akeeba\Backup\Admin\CliCommands;

defined('_JEXEC') || die;

use Akeeba\Backup\Admin\Model\Profiles;
use Exception;
use FOF40\Container\Container;
use Joomla\Console\Command\AbstractCommand;
use Akeeba\Backup\Admin\CliCommands\MixIt\ArgumentUtilities;
use Akeeba\Backup\Admin\CliCommands\MixIt\ConfigureIO;
use Akeeba\Backup\Admin\CliCommands\MixIt\PrintFormattedArray;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * akeeba:profile:import
 *
 * Imports an Akeeba Backup profile from a JSON string.
 *
 * @since   7.5.0
 */
class ProfileImport extends AbstractCommand
{
	use ConfigureIO, ArgumentUtilities, PrintFormattedArray;

	/**
	 * The default command name
	 *
	 * @var    string
	 * @since  7.5.0
	 */
	protected static $defaultName = 'akeeba:profile:import';

	/**
	 * Internal function to execute the command.
	 *
	 * @param   InputInterface   $input   The input to inject into the command.
	 * @param   OutputInterface  $output  The output to inject into the command.
	 *
	 * @return  integer  The command exit code
	 *
	 * @since   7.5.0
	 */
	protected function doExecute(InputInterface $input, OutputInterface $output): int
	{
		$this->configureSymfonyIO($input, $output);

		$filename = (string) $this->cliInput->getArgument('fileOrJSON') ?? '';
		$json     = $this->getJSON($filename);

		try
		{
			$decoded = @json_decode($json, true);
		}
		catch (Exception $e)
		{
			$decoded = '';
		}

		if (empty($decoded))
		{
			$this->ioStyle->error("Cannot process input; invalid JSON string or file not found.");

			return 1;
		}

		// We must never pass an ID, forcing the model to create a new record
		if (isset($decoded['id']))
		{
			unset($decoded['id']);
		}

		$container = Container::getInstance('com_akeeba');

		/** @var Profiles $model */
		$model = $container->factory->model('Profiles')->tmpInstance();

		try
		{
			$newProfile = $model->create($decoded);
		}
		catch (Exception $e)
		{
			$this->ioStyle->error(sprintf("Cannot import profile: %s", $e->getMessage()));

			return 2;
		}

		$id     = $newProfile->getId();
		$format = (string) $this->cliInput->getOption('format') ?? 'text';

		if ($format == 'json')
		{
			echo json_encode($id);

			return 0;
		}

		$this->ioStyle->success(sprintf("Successfully imported JSON as profile #%s", $id));

		return 0;
	}

	/**
	 * Configure the command.
	 *
	 * @return  void
	 *
	 * @since   7.5.0
	 */
	protected function configure(): void
	{
		$help = "<info>%command.name%</info> will import an Akeeba Backup profile from a JSON string.
		\nUsage: <info>php %command.full_name%</info>";

		$this->addArgument('fileOrJSON', InputOption::VALUE_OPTIONAL, 'A path to an Akeeba Backup profile export JSON file or a literal JSON string. Uses STDIN if omitted.');
		$this->addOption('format', null, InputOption::VALUE_OPTIONAL, 'The format for the response. Use json to get a JSON-parseable numeric ID of the new backup profile. Values: json, text', 'text');

		$this->setDescription('Imports an Akeeba Backup profile from a JSON string');
		$this->setHelp($help);
	}

	/**
	 * Get the JSON input
	 *
	 * @param   string|null  $filename  The filename to read from, raw JSON data or an empty string
	 *
	 * @return  string  The JSON data
	 *
	 * @since   7.5.0
	 */
	private function getJSON(?string $filename): string
	{
		// No filename or JSON string passed to script; use STDIN
		if (empty($filename))
		{
			$json = '';

			while (!feof(STDIN))
			{
				$json .= fgets(STDIN) . "\n";
			}

			return rtrim($json);
		}

		// An existing file path was passed. Return the contents of the file.
		if (@file_exists($filename))
		{
			$ret = @file_get_contents($filename);

			if ($ret === false)
			{
				return '';
			}
		}

		// Otherwise assume raw JSON was passed back to us.
		return $filename;
	}

}
PK�!]Qx���
�
CliCommands/SysconfigGet.phpnu&1i�<?php
/**
 * @package   akeebabackup
 * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

namespace Akeeba\Backup\Admin\CliCommands;

defined('_JEXEC') || die;

use Joomla\Console\Command\AbstractCommand;
use Akeeba\Backup\Admin\CliCommands\MixIt\ArgumentUtilities;
use Akeeba\Backup\Admin\CliCommands\MixIt\ComponentOptions;
use Akeeba\Backup\Admin\CliCommands\MixIt\ConfigureIO;
use Akeeba\Backup\Admin\CliCommands\MixIt\PrintFormattedArray;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * akeeba:sysconfig:get
 *
 * Gets the value of an Akeeba Backup component-wide option
 *
 * @since   7.5.0
 */
class SysconfigGet extends AbstractCommand
{
	use ConfigureIO, ArgumentUtilities, PrintFormattedArray, ComponentOptions;

	/**
	 * The default command name
	 *
	 * @var    string
	 * @since  7.5.0
	 */
	protected static $defaultName = 'akeeba:sysconfig:get';

	/**
	 * Internal function to execute the command.
	 *
	 * @param   InputInterface   $input   The input to inject into the command.
	 * @param   OutputInterface  $output  The output to inject into the command.
	 *
	 * @return  integer  The command exit code
	 *
	 * @since   7.5.0
	 */
	protected function doExecute(InputInterface $input, OutputInterface $output): int
	{
		$this->configureSymfonyIO($input, $output);

		$key     = (string) $this->cliInput->getArgument('key') ?? '';
		$format  = (string) $this->cliInput->getOption('format') ?? 'table';
		$options = $this->getComponentOptions();

		if (!array_key_exists($key, $options))
		{
			$this->ioStyle->error(sprintf('Cannot find option “%s”.', $key));

			return 1;
		}

		$value = $options[$key] ?? '';

		switch ($format)
		{
			case 'text':
			default:
				echo $value;
				break;

			case 'json':
				echo json_encode($value);
				break;

			case 'print_r':
				print_r($value);
				break;

			case 'var_dump':
				var_dump($value);
				break;

			case 'var_export':
				var_export($value);
				break;
		}

		return 0;
	}

	/**
	 * Configure the command.
	 *
	 * @return  void
	 *
	 * @since   7.5.0
	 */
	protected function configure(): void
	{
		$help = "<info>%command.name%</info> will get the value of an Akeeba Backup component-wide option.
		\nUsage: <info>php %command.full_name%</info>";


		$this->addArgument('key', null, InputOption::VALUE_REQUIRED, 'The option key to retrieve');
		$this->addOption('format', null, InputOption::VALUE_OPTIONAL, 'Output format: text, json, print_r, var_dunp, var_export.', 'text');

		$this->setDescription('Gets the value of an Akeeba Backup component-wide option');
		$this->setHelp($help);
	}
}
PK�!]�u	u	CliCommands/BackupInfo.phpnu&1i�<?php
/**
 * @package   akeebabackup
 * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

namespace Akeeba\Backup\Admin\CliCommands;

defined('_JEXEC') || die;

use Akeeba\Engine\Platform;
use Joomla\Console\Command\AbstractCommand;
use Akeeba\Backup\Admin\CliCommands\MixIt\ArgumentUtilities;
use Akeeba\Backup\Admin\CliCommands\MixIt\ConfigureIO;
use Akeeba\Backup\Admin\CliCommands\MixIt\PrintFormattedArray;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * akeeba:backup:info
 *
 * Lists a backup record known to Akeeba Backup
 *
 * @since   7.5.0
 */
class BackupInfo extends AbstractCommand
{
	use ConfigureIO, ArgumentUtilities, PrintFormattedArray;

	/**
	 * The default command name
	 *
	 * @var    string
	 * @since  7.5.0
	 */
	protected static $defaultName = 'akeeba:backup:info';

	/**
	 * Internal function to execute the command.
	 *
	 * @param   InputInterface   $input   The input to inject into the command.
	 * @param   OutputInterface  $output  The output to inject into the command.
	 *
	 * @return  integer  The command exit code
	 *
	 * @since   7.5.0
	 */
	protected function doExecute(InputInterface $input, OutputInterface $output): int
	{
		$this->configureSymfonyIO($input, $output);

		$id     = (int) $this->cliInput->getArgument('id') ?? 0;
		$format = (string) ($this->cliInput->getOption('format') ?? 'table');

		if ($format === 'table')
		{
			$this->ioStyle->title(sprintf('Information for Akeeba Backup record #%d', $id));
		}

		$record = Platform::getInstance()->get_statistics($id);

		return $this->printFormattedAndReturn($record, $format);
	}

	/**
	 * Configure the command.
	 *
	 * @return  void
	 *
	 * @since   7.5.0
	 */
	protected function configure(): void
	{
		$help = "<info>%command.name%</info> will list a backup record known to Akeeba Backup
		\nUsage: <info>php %command.full_name%</info>";

		$this->addArgument('id', InputArgument::REQUIRED, 'The id of the backup record to list');
		$this->addOption('format', null, InputOption::VALUE_OPTIONAL, 'Output format: table, json, yaml, csv, count.', 'table');
		$this->setDescription('Lists a backup record known to Akeeba Backup');
		$this->setHelp($help);
	}
}
PK�!]����
�
CliCommands/ProfileDelete.phpnu&1i�<?php
/**
 * @package   akeebabackup
 * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

namespace Akeeba\Backup\Admin\CliCommands;

defined('_JEXEC') || die;

use Akeeba\Backup\Admin\Model\Profiles;
use Exception;
use FOF40\Container\Container;
use FOF40\Model\DataModel\Exception\RecordNotLoaded;
use Joomla\Console\Command\AbstractCommand;
use Akeeba\Backup\Admin\CliCommands\MixIt\ArgumentUtilities;
use Akeeba\Backup\Admin\CliCommands\MixIt\ConfigureIO;
use Akeeba\Backup\Admin\CliCommands\MixIt\PrintFormattedArray;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * akeeba:profile:delete
 *
 * Delete an Akeeba Backup profile
 *
 * @since   7.5.0
 */
class ProfileDelete extends AbstractCommand
{
	use ConfigureIO, ArgumentUtilities, PrintFormattedArray;

	/**
	 * The default command name
	 *
	 * @var    string
	 * @since  7.5.0
	 */
	protected static $defaultName = 'akeeba:profile:delete';

	/**
	 * Internal function to execute the command.
	 *
	 * @param   InputInterface   $input   The input to inject into the command.
	 * @param   OutputInterface  $output  The output to inject into the command.
	 *
	 * @return  integer  The command exit code
	 *
	 * @since   7.5.0
	 */
	protected function doExecute(InputInterface $input, OutputInterface $output): int
	{
		$this->configureSymfonyIO($input, $output);

		$id = (int) $this->cliInput->getArgument('id') ?? 0;

		if ($id === 1)
		{
			$this->ioStyle->error('You cannot delete the default backup profile (#1)');
		}

		$container = Container::getInstance('com_akeeba');

		/** @var Profiles $model */
		$model = $container->factory->model('Profiles')->tmpInstance();

		try
		{
			$profile = $model->findOrFail($id);
		}
		catch (RecordNotLoaded $e)
		{
			$this->ioStyle->error(sprintf("Cannot delete profile %s; profile not found.", $id));

			return 2;
		}

		try
		{
			$newProfile = $model->forceDelete($profile->getId());
		}
		catch (Exception $e)
		{
			$this->ioStyle->error(sprintf("Cannot delete profile #%s: %s", $id, $e->getMessage()));

			return 3;
		}

		$this->ioStyle->success(sprintf("Profile #%d has been deleted.", $newProfile->getId()));

		return 0;
	}

	/**
	 * Configure the command.
	 *
	 * @return  void
	 *
	 * @since   7.5.0
	 */
	protected function configure(): void
	{
		$help = "<info>%command.name%</info> will delete an Akeeba Backup profile.
		\nUsage: <info>php %command.full_name%</info>";

		$this->addArgument('id', InputOption::VALUE_REQUIRED, 'The numeric ID of the profile to delete');

		$this->setDescription('Delete an Akeeba Backup profile');
		$this->setHelp($help);
	}
}
PK�!]k2��..CliCommands/SysconfigSet.phpnu&1i�<?php
/**
 * @package   akeebabackup
 * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

namespace Akeeba\Backup\Admin\CliCommands;

defined('_JEXEC') || die;

use Akeeba\Backup\Admin\Helper\SecretWord;
use FOF40\Container\Container;
use Joomla\Console\Command\AbstractCommand;
use Akeeba\Backup\Admin\CliCommands\MixIt\ArgumentUtilities;
use Akeeba\Backup\Admin\CliCommands\MixIt\ComponentOptions;
use Akeeba\Backup\Admin\CliCommands\MixIt\ConfigureIO;
use Akeeba\Backup\Admin\CliCommands\MixIt\PrintFormattedArray;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * akeeba:sysconfig:set
 *
 * Sets the value of an Akeeba Backup component-wide option
 *
 * @since   7.5.0
 */
class SysconfigSet extends AbstractCommand
{
	use ConfigureIO, ArgumentUtilities, PrintFormattedArray, ComponentOptions;

	/**
	 * The default command name
	 *
	 * @var    string
	 * @since  7.5.0
	 */
	protected static $defaultName = 'akeeba:sysconfig:set';

	/**
	 * Internal function to execute the command.
	 *
	 * @param   InputInterface   $input   The input to inject into the command.
	 * @param   OutputInterface  $output  The output to inject into the command.
	 *
	 * @return  integer  The command exit code
	 *
	 * @since   7.5.0
	 */
	protected function doExecute(InputInterface $input, OutputInterface $output): int
	{
		$this->configureSymfonyIO($input, $output);

		$key     = (string) $this->cliInput->getArgument('key') ?? '';
		$value   = (string) $this->cliInput->getArgument('value') ?? '';
		$format  = (string) $this->cliInput->getOption('format') ?? 'table';
		$options = $this->getComponentOptions();

		if (!array_key_exists($key, $options))
		{
			$this->ioStyle->error(sprintf('Cannot find option “%s”.', $key));

			return 1;
		}

		if ((string) $options[$key] === $value)
		{
			return 0;
		}

		$container = Container::getInstance('com_akeeba');
		$container->params->set($key, $value);
		$container->params->save();

		// Make sure the front-end backup Secret Word is stored encrypted
		$params = $container->params;
		SecretWord::enforceEncryption($params, 'frontend_secret_word');

		$this->ioStyle->success(sprintf('Set component option “%s” to “%s”', $key, $value));

		return 0;
	}

	/**
	 * Configure the command.
	 *
	 * @return  void
	 *
	 * @since   7.5.0
	 */
	protected function configure(): void
	{
		$help = "<info>%command.name%</info> will set the value of an Akeeba Backup component-wide option.
		\nUsage: <info>php %command.full_name%</info>";


		$this->addArgument('key', null, InputOption::VALUE_REQUIRED, 'The option key to set');
		$this->addArgument('value', null, InputOption::VALUE_REQUIRED, 'The option value to set');
		$this->addOption('format', null, InputOption::VALUE_OPTIONAL, 'Output format: text, json, print_r, var_dunp, var_export.', 'text');

		$this->setDescription('Sets the value of an Akeeba Backup component-wide option');
		$this->setHelp($help);
	}
}
PK�!]wt�`W
W
CliCommands/ProfileReset.phpnu&1i�<?php
/**
 * @package   akeebabackup
 * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

namespace Akeeba\Backup\Admin\CliCommands;

defined('_JEXEC') || die;

use Akeeba\Backup\Admin\Model\Profiles;
use Akeeba\Engine\Platform;
use Exception;
use FOF40\Container\Container;
use FOF40\Model\DataModel\Exception\RecordNotLoaded;
use Joomla\Console\Command\AbstractCommand;
use Akeeba\Backup\Admin\CliCommands\MixIt\ArgumentUtilities;
use Akeeba\Backup\Admin\CliCommands\MixIt\ConfigureIO;
use Akeeba\Backup\Admin\CliCommands\MixIt\PrintFormattedArray;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * akeeba:profile:reset
 *
 * Resets an Akeeba Backup profile
 *
 * @since   7.5.0
 */
class ProfileReset extends AbstractCommand
{
	use ConfigureIO, ArgumentUtilities, PrintFormattedArray;

	/**
	 * The default command name
	 *
	 * @var    string
	 * @since  7.5.0
	 */
	protected static $defaultName = 'akeeba:profile:reset';

	/**
	 * Internal function to execute the command.
	 *
	 * @param   InputInterface   $input   The input to inject into the command.
	 * @param   OutputInterface  $output  The output to inject into the command.
	 *
	 * @return  integer  The command exit code
	 *
	 * @since   7.5.0
	 */
	protected function doExecute(InputInterface $input, OutputInterface $output): int
	{
		$this->configureSymfonyIO($input, $output);

		$id            = (int) $this->cliInput->getArgument('id') ?? 0;
		$filters       = (bool) $this->cliInput->getOption('filters') ?? false;
		$configuration = (bool) $this->cliInput->getOption('configuration') ?? false;

		$container = Container::getInstance('com_akeeba');

		/** @var Profiles $model */
		$model = $container->factory->model('Profiles')->tmpInstance();

		try
		{
			$profile = $model->findOrFail($id);
		}
		catch (RecordNotLoaded $e)
		{
			$this->ioStyle->error(sprintf("Cannot modify profile %s; profile not found.", $id));

			return 1;
		}

		if ($filters)
		{
			$profile->filters = '';
		}

		if ($configuration)
		{
			$profile->configuration = '';
		}

		try
		{
			$newProfile = $profile->save();
		}
		catch (Exception $e)
		{
			$this->ioStyle->error(sprintf("Cannot reset profile #%s: %s", $id, $e->getMessage()));

			return 2;
		}

		/**
		 * Loading the new profile's empty configuration causes the Platform code to revert to the default options and
		 * save them automatically to the database.
		 */
		if ($configuration)
		{
			Platform::getInstance()->load_configuration($id);
		}

		$this->ioStyle->success(sprintf("Profile #%s reset successfully.", $newProfile->getId()));

		return 0;
	}

	/**
	 * Configure the command.
	 *
	 * @return  void
	 *
	 * @since   7.5.0
	 */
	protected function configure(): void
	{
		$help = "<info>%command.name%</info> will resets an Akeeba Backup profile.
		\nUsage: <info>php %command.full_name%</info>";

		$this->addArgument('id', InputOption::VALUE_REQUIRED, 'The numeric ID of the profile to modify');
		$this->addOption('filters', null, InputOption::VALUE_NONE, 'Reset the filters?', false);
		$this->addOption('configuration', null, InputOption::VALUE_NONE, 'Reset the configuration?', false);

		$this->setDescription('Resets an Akeeba Backup profile');
		$this->setHelp($help);
	}
}
PK�!]��]�YYCliCommands/FilterDelete.phpnu&1i�<?php
/**
 * @package   akeebabackup
 * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

namespace Akeeba\Backup\Admin\CliCommands;

defined('_JEXEC') || die;

use Akeeba\Engine\Factory;
use Joomla\Console\Command\AbstractCommand;
use Akeeba\Backup\Admin\CliCommands\MixIt\ArgumentUtilities;
use Akeeba\Backup\Admin\CliCommands\MixIt\ConfigureIO;
use Akeeba\Backup\Admin\CliCommands\MixIt\FilterRoots;
use Akeeba\Backup\Admin\CliCommands\MixIt\IsPro;
use Akeeba\Backup\Admin\CliCommands\MixIt\PrintFormattedArray;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * akeeba:filter:delete
 *
 * Delete a filter value known to Akeeba Backup.
 *
 * @since   7.5.0
 */
class FilterDelete extends AbstractCommand
{
	use ConfigureIO, ArgumentUtilities, PrintFormattedArray, IsPro, FilterRoots;

	/**
	 * The default command name
	 *
	 * @var    string
	 * @since  7.5.0
	 */
	protected static $defaultName = 'akeeba:filter:delete';

	/**
	 * Internal function to execute the command.
	 *
	 * @param   InputInterface   $input   The input to inject into the command.
	 * @param   OutputInterface  $output  The output to inject into the command.
	 *
	 * @return  integer  The command exit code
	 *
	 * @since   7.5.0
	 */
	protected function doExecute(InputInterface $input, OutputInterface $output): int
	{
		$this->configureSymfonyIO($input, $output);

		$profileId = (int) ($this->cliInput->getOption('profile') ?? 1);

		define('AKEEBA_PROFILE', $profileId);

		$filterType = (string) ($this->cliInput->getOption('filterType') ?? 'files');
		$target     = (in_array($filterType, [
			'tables', 'tabledata', 'regextables', 'regextabledata', 'multidb',
		])) ? 'db' : 'fs';
		$root       = (string) ($this->cliInput->getOption('root') ?? (($target == 'fs') ? '[SITEROOT]' : '[SITEDB]'));

		if (!in_array($root, $this->getRoots($target)))
		{
			$this->ioStyle->error(sprintf("Unknown %s root '%s'.", $target, $root));

			return 1;
		}

		$filter = (string) $this->cliInput->getArgument('filter') ?? '';

		$this->ioStyle->title(sprintf(
			'Deleting %s filter “%s” of type %s from profile #%d',
			$target === 'db' ? 'database' : 'filesystem',
			$filter,
			$filterType,
			$profileId
		));

		// Delete the filter
		$filterObject = Factory::getFilterObject($filterType);

		if ((stripos($filterType, 'regex') !== false) && !$this->isPro())
		{
			$this->ioStyle->error(sprintf("Filters of the '%s' type are only available with Akeeba Backup Professional.", $filterType));

			return 2;
		}

		switch ($filterType)
		{
			case 'extradirs':
			case 'multidb':
				if (!$this->isPro())
				{
					$this->ioStyle->error(sprintf("Filters of the '%s' type are only available with Akeeba Backup Professional.", $filterType));

					return 2;
				}

				$success = $filterObject->remove($filter);
				break;

			default:
				$success = $filterObject->remove($root, $filter);
				break;
		}

		if (!$success)
		{
			$this->ioStyle->error(sprintf("Could not delete filter '%s' of type '%s'.", $filter, $filterType));

			return 3;
		}

		Factory::getFilters()->save();

		$this->ioStyle->success(sprintf("Deleted filter '%s' of type '%s'.", $filter, $filterType));

		return 0;
	}

	/**
	 * Configure the command.
	 *
	 * @return  void
	 *
	 * @since   7.5.0
	 */
	protected function configure(): void
	{
		$help = "<info>%command.name%</info> will delete a filter value known to Akeeba Backup.
		\nUsage: <info>php %command.full_name%</info>";

		$this->addArgument('filter', InputArgument::REQUIRED, 'The filter name to delete');
		$this->addOption('root', null, InputOption::VALUE_OPTIONAL, 'Which filter root to use. Defaults to [SITEROOT] or [SITEDB] depending on the fitler type.', '');
		$this->addOption('filterType', null, InputOption::VALUE_REQUIRED, 'The type of filter you want to delete: files, directories, skipdirs, skipfiles, regexfiles, regexdirectories, regexskipdirs, regexskipfiles, tables, tabledata, regextables, regextabledata, extradirs, multidb', 'files');
		$this->addOption('profile', null, InputOption::VALUE_OPTIONAL, 'The backup profile to use. Default: 1.', 1);


		$this->setDescription('Delete a filter value known to Akeeba Backup.');
		$this->setHelp($help);
	}
}
PK�!]TFz_??CliCommands/MixIt/IsPro.phpnu&1i�<?php
/**
 * @package   akeebabackup
 * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

namespace Akeeba\Backup\Admin\CliCommands\MixIt;

defined('_JEXEC') || die;

/**
 * Is this Akeeba Backup Pro?
 *
 * @since   7.5.0
 */
trait IsPro
{
	/**
	 * Caches whether this is the Pro version of the software.
	 *
	 * @var   null|bool
	 * @since 7.5.0
	 */
	private $isPro = null;

	/**
	 * is this the Professional version of the software?
	 *
	 * @return  bool
	 * @since   7.5.0
	 */
	private function isPro(): bool
	{
		if (!is_null($this->isPro))
		{
			return $this->isPro;
		}

		$componentFolder = JPATH_ADMINISTRATOR . '/components/com_akeeba';
		$this->isPro     = is_dir($componentFolder . '/AliceEngine');

		return $this->isPro;
	}
}
PK�!]E!|
MM!CliCommands/MixIt/FilterRoots.phpnu&1i�<?php
/**
 * @package   akeebabackup
 * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

namespace Akeeba\Backup\Admin\CliCommands\MixIt;

defined('_JEXEC') || die;

use Akeeba\Backup\Admin\Model\DatabaseFilters;
use Akeeba\Engine\Factory;
use FOF40\Container\Container;

trait FilterRoots
{
	/**
	 * @param   string  $target
	 *
	 * @return  array
	 *
	 * @since   7.5.0
	 */
	private function getRoots(string $target): array
	{
		$container = Container::getInstance('com_akeeba', [], 'admin');
		$filters   = Factory::getFilters();
		$output    = [];

		switch ($target)
		{
			case 'fs':
				$rootInfo = $filters->getInclusions('dir');

				foreach ($rootInfo as $item)
				{
					$output[] = $item[0];
				}

				break;

			case 'db':
				/** @var DatabaseFilters $model */
				$model    = $container->factory->model('DatabaseFilters')->tmpInstance();
				$rootInfo = $model->get_roots();

				foreach ($rootInfo as $item)
				{
					$output[] = $item->value;
				}

				break;
		}

		return $output;
	}

}
PK�!]��c2��'CliCommands/MixIt/JsonGuiDataParser.phpnu&1i�<?php
/**
 * @package   akeebabackup
 * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

namespace Akeeba\Backup\Admin\CliCommands\MixIt;

defined('_JEXEC') || die;

use Akeeba\Engine\Factory;

trait JsonGuiDataParser
{
	/**
	 * Parse the JSON GUI definition returned by Akeeba Engine into something I can use to provide information about
	 * the options.
	 *
	 * @return  array
	 *
	 * @since   7.5.0
	 */
	private function parseJsonGuiData(): array
	{
		$jsonGUIData = Factory::getEngineParamsProvider()->getJsonGuiDefinition();
		$guiData     = json_decode($jsonGUIData, true);

		$ret = [
			'engines'    => [],
			'installers' => [],
			'options'    => [],
		];

		// Parse engines
		foreach ($guiData['engines'] as $engineType => $engineRecords)
		{
			if (!isset($ret['engines'][$engineType]))
			{
				$ret['engines'][$engineType] = [];
			}

			foreach ($engineRecords as $engineName => $record)
			{
				$ret['engines'][$engineType][$engineName] = [
					'title'       => $record['information']['title'],
					'description' => $record['information']['description'],
				];

				foreach ($record['parameters'] as $key => $optionRecord)
				{
					$ret['options'][$key] = array_merge($optionRecord, [
						'section' => $record['information']['title'],
					]);
				}
			}
		}

		// Parse installers
		foreach ($guiData['installers'] as $installerName => $installerInfo)
		{
			$ret['installers'][$installerName] = $installerInfo['name'];
		}

		// Parse GUI sections
		foreach ($guiData['gui'] as $section => $options)
		{
			foreach ($options as $key => $optionRecord)
			{
				$ret['options'][$key] = array_merge($optionRecord, [
					'section' => $section,
				]);
			}
		}

		return $ret;
	}

	/**
	 * Flattens the option tree returned by exportToJson into an array with dotted notation for each option.
	 *
	 * @param   array   $rawOptions  The option tree
	 * @param   string  $prefix      Current prefix, used for recursion
	 *
	 * @return  array
	 * @since   7.5.0
	 */
	private function flattenOptions(array $rawOptions, string $prefix = ''): array
	{
		$ret = [];

		foreach ($rawOptions as $k => $v)
		{
			if (is_array($v))
			{
				$ret = array_merge($ret, $this->flattenOptions($v, $prefix . $k . '.'));

				continue;
			}

			$ret[$prefix . $k] = $v;
		}

		return $ret;
	}

	/**
	 * Get the information for an option record.
	 *
	 * @param   string  $key   The option key
	 * @param   array   $info  The array returned by parseJsonGuiData
	 *
	 * @return  array
	 *
	 * @since   7.5.0
	 */
	private function getOptionInfo(string $key, array &$info): array
	{
		$ret = [];

		if (!isset($info['options'][$key]))
		{
			return $ret;
		}

		$keyInfo = $info['options'][$key];

		$ret = [
			'title'        => $keyInfo['title'],
			'description'  => $keyInfo['description'],
			'section'      => $keyInfo['section'],
			'type'         => $keyInfo['type'],
			'default'      => $keyInfo['default'],
			'options'      => [],
			'optionTitles' => [],
			'limits'       => [],
		];

		switch ($keyInfo['type'])
		{
			case 'integer':
				if (isset($keyInfo['shortcuts']))
				{
					$ret['options'] = explode('|', $keyInfo['shortcuts']);
				}

				$ret['limits'] = [
					'min' => $keyInfo['min'],
					'max' => $keyInfo['max'],
				];
				break;

			case 'bool':
				$ret['type']    = 'integer';
				$ret['options'] = [0, 1];
				$ret['limits']  = [
					'min' => 0,
					'max' => 1,
				];
				break;

			case 'engine':
				$ret['type']         = 'enum';
				$ret['type']         = 'string';
				$ret['options']      = array_keys($info['engines'][$keyInfo['subtype']]);
				$ret['optionTitles'] = [];

				foreach ($info['engines'][$keyInfo['subtype']] as $k => $details)
				{
					$ret['optionTitles'][$k] = $details['title'];
				}

				break;

			case 'installer':
				$ret['type']         = 'enum';
				$ret['type']         = 'string';
				$ret['options']      = array_keys($info['installers']);
				$ret['optionTitles'] = $info['installers'];

				break;

			case 'enum':
				$ret['type']         = 'string';
				$ret['options']      = explode('|', $keyInfo['enumvalues']);
				$ret['optionTitles'] = explode('|', $keyInfo['enumkeys']);

				break;

			case 'hidden':
			case 'button':
			case 'separator':
				$ret['type'] = 'hidden';
				break;

			case 'string':
			case 'browsedir':
			case 'password':
			default:
				$ret['type'] = 'string';
				break;
		}

		return $ret;
	}

}
PK�!]~�1�!!)CliCommands/MixIt/PrintFormattedArray.phpnu&1i�<?php
/**
 * @package   akeebabackup
 * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

namespace Akeeba\Backup\Admin\CliCommands\MixIt;


trait PrintFormattedArray
{
	/**
	 * Prints the array formatted with the specific format and returns an integer result
	 *
	 * @param   array   $data    The data to format and print
	 * @param   string  $format  One of table, json, yaml, csv, count
	 *
	 * @return  int
	 * @since   7.5.0
	 */
	private function printFormattedAndReturn(?array $data, string $format): int
	{
		if (empty($data) && ($format != 'count'))
		{
			return 0;
		}
		elseif (empty($data))
		{
			$data = [];
		}

		if (!empty($data))
		{
			$keys     = array_keys($data);
			$firstKey = array_shift($keys);
			$row      = $data[$firstKey];

			if (is_array($row))
			{
				$headers = array_keys($row);
			}
			else
			{
				$headers = array_keys($data);

				if (!in_array($format, ['json', 'yaml']))
				{
					$data = [$data];
				}
			}
		}

		switch ($format)
		{
			default:
			case 'table':
				$this->ioStyle->table($headers, $data);
				break;

			case 'json':
				$this->ioStyle->writeln(json_encode($data, JSON_PRETTY_PRINT));
				break;

			case 'yaml':
				if (!function_exists('yaml_emit'))
				{
					$this->ioStyle->error(<<< ERROR
Cannot generate YAML

Your PHP installation does not have the PHP YAML extension installed or enabled.

ERROR

					);

					return 1;
				}

				$this->ioStyle->writeln(yaml_emit($data));
				break;

			case 'csv':
				$this->ioStyle->writeln($this->toCsv($data));
				break;

			case 'count':
				$this->ioStyle->writeln(count($data));
				break;
		}

		return 0;
	}

	/**
	 * Converts an array to its CSV representation
	 *
	 * @param   array  $data       The array data to convert to CSV
	 * @param   bool   $csvHeader  Should I print a CSV header row?
	 *
	 * @return  string
	 * @since   7.5.0
	 */
	private function toCsv(array $data, bool $csvHeader = true): string
	{
		$output = '';
		$item   = array_pop($data);
		$data[] = $item;
		$keys   = array_keys($item);

		if ($csvHeader)
		{
			$csv = [];

			foreach ($keys as $k)
			{
				$k = str_replace('"', '""', $k);
				$k = str_replace("\r", '\\r', $k);
				$k = str_replace("\n", '\\n', $k);
				$k = '"' . $k . '"';

				$csv[] = $k;
			}

			$output .= implode(",", $csv) . "\r\n";
		}

		foreach ($data as $item)
		{
			$csv = [];

			foreach ($keys as $k)
			{
				$v = $item[$k];

				if (is_array($v))
				{
					$v = 'Array';
				}
				elseif (is_object($v))
				{
					$v = 'Object';
				}

				$v = str_replace('"', '""', $v);
				$v = str_replace("\r", '\\r', $v);
				$v = str_replace("\n", '\\n', $v);
				$v = '"' . $v . '"';

				$csv[] = $v;
			}

			$output .= implode(",", $csv) . "\r\n";
		}

		return $output;
	}
}
PK�!]�5K�EE&CliCommands/MixIt/ComponentOptions.phpnu&1i�<?php
/**
 * @package   akeebabackup
 * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

namespace Akeeba\Backup\Admin\CliCommands\MixIt;

defined('_JEXEC') || die;

use Akeeba\Engine\Platform;
use Joomla\CMS\Form\Form;
use Joomla\CMS\Form\FormField;

trait ComponentOptions
{
	private function getComponentOptions(bool $defaultValuesOnly = false): array
	{
		$output     = [];
		$fieldNames = [];

		$form = new Form('config');
		$form->loadFile(JPATH_ADMINISTRATOR . '/components/com_akeeba/config.xml', true, '//config');

		foreach ($form->getFieldsets() as $group => $fieldSetInfo)
		{
			$fields = $form->getFieldset($group);

			if (empty($fields))
			{
				continue;
			}

			foreach ($fields as $fieldName => $v)
			{
				if (!is_object($v) || !($v instanceof FormField))
				{
					continue;
				}

				if (substr((string) $v->type, -5) === 'Rules')
				{
					continue;
				}

				if (in_array(strtolower((string) $v->type), ['hidden', 'rules', 'spacer']))
				{
					continue;
				}

				$fieldNames[$fieldName] = $v->value ?? null;
			}
		}

		if (!$defaultValuesOnly)
		{
			foreach ($fieldNames as $k => $default)
			{
				$output[$k] = Platform::getInstance()->get_platform_configuration_option($k, $default);
			}
		}

		return $output;
	}
}
PK�!]��\!CliCommands/MixIt/ConfigureIO.phpnu&1i�<?php
/**
 * @package   akeebabackup
 * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

namespace Akeeba\Backup\Admin\CliCommands\MixIt;

defined('_JEXEC') || die;

use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;

/**
 * Set up the Symfony I/O objects
 *
 * @since   7.5.0
 */
trait ConfigureIO
{
	/**
	 * @var   SymfonyStyle
	 * @since 7.5.0
	 */
	private $ioStyle;

	/**
	 * @var   InputInterface
	 * @since 7.5.0
	 */
	private $cliInput;

	/**
	 * Configure the IO.
	 *
	 * @param   InputInterface   $input   The input to inject into the command.
	 * @param   OutputInterface  $output  The output to inject into the command.
	 *
	 * @return  void
	 *
	 * @since   7.5.0
	 */
	private function configureSymfonyIO(InputInterface $input, OutputInterface $output)
	{
		$this->cliInput = $input;
		$this->ioStyle  = new SymfonyStyle($input, $output);
	}

}
PK�!]7^P���CliCommands/MixIt/TimeInfo.phpnu&1i�<?php
/**
 * @package   akeebabackup
 * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

namespace Akeeba\Backup\Admin\CliCommands\MixIt;

defined('_JEXEC') || die;

/**
 * Utility methods to get time information
 *
 * @since   7.5.0
 */
trait TimeInfo
{
	/**
	 * Returns a fancy formatted time lapse code
	 *
	 * @param   integer   $referenceDateTime  Timestamp of the reference date/time
	 * @param   int|null  $currentDateTime    Timestamp of the current date/time
	 * @param   string    $measureBy          One of s, m, h, d, or y (time unit)
	 * @param   boolean   $autoText           Append text automatically?
	 *
	 * @return  string
	 *
	 * @since   7.5.0
	 */
	private function timeAgo(int $referenceDateTime = 0, ?int $currentDateTime = null, string $measureBy = '', bool $autoText = true): string
	{
		if (is_null($currentDateTime))
		{
			$currentDateTime = time();
		}

		// Raw time difference
		$raw   = $currentDateTime - $referenceDateTime;
		$clean = abs($raw);

		$calcNum = [
			['s', 60],
			['m', 60 * 60],
			['h', 60 * 60 * 60],
			['d', 60 * 60 * 60 * 24],
			['y', 60 * 60 * 60 * 24 * 365],
		];

		$calc = [
			's' => [1, 'second'],
			'm' => [60, 'minute'],
			'h' => [60 * 60, 'hour'],
			'd' => [60 * 60 * 24, 'day'],
			'y' => [60 * 60 * 24 * 365, 'year'],
		];

		if ($measureBy == '')
		{
			$usemeasure = 's';

			for ($i = 0; $i < count($calcNum); $i++)
			{
				if ($clean <= $calcNum[$i][1])
				{
					$usemeasure = $calcNum[$i][0];
					$i          = count($calcNum);
				}
			}
		}
		else
		{
			$usemeasure = $measureBy;
		}

		$datedifference = floor($clean / $calc[$usemeasure][0]);

		if ($autoText == true && ($currentDateTime == time()))
		{
			if ($raw < 0)
			{
				$prospect = ' from now';
			}
			else
			{
				$prospect = ' ago';
			}
		}
		else
		{
			$prospect = '';
		}

		if ($referenceDateTime != 0)
		{
			if ($datedifference == 1)
			{
				return $datedifference . ' ' . $calc[$usemeasure][1] . ' ' . $prospect;
			}
			else
			{
				return $datedifference . ' ' . $calc[$usemeasure][1] . 's ' . $prospect;
			}
		}
		else
		{
			return 'No input time referenced.';
		}
	}
}
PK�!]�I��{{'CliCommands/MixIt/ArgumentUtilities.phpnu&1i�<?php
/**
 * @package   akeebabackup
 * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

namespace Akeeba\Backup\Admin\CliCommands\MixIt;

defined('_JEXEC') || die;

/**
 * Utility methods to manage command arguments
 *
 * @since   7.5.0
 */
trait ArgumentUtilities
{
	/**
	 * Parse the overrides provided in the command line.
	 *
	 * Input: "key1=value1, key2= value2, key3 = value3"
	 * Output: ['key1' => 'value1', 'key2' => 'value2', 'key3' => 'value3']
	 *
	 * @param   string  $rawString  The raw string
	 *
	 * @return  array  The parsed overrides
	 *
	 * @since   7.5.0
	 */
	private function commaListToMap(string $rawString): array
	{
		if (empty($rawString) || (trim($rawString) == ''))
		{
			return [];
		}

		$rawString = trim($rawString);
		$ret       = [];
		$lines     = explode($rawString, ",");

		foreach ($lines as $line)
		{
			if (strpos($line, '=') === false)
			{
				continue;
			}

			[$key, $value] = explode('=', $line);
			$key       = trim($key);
			$value     = trim($value);
			$ret[$key] = $value;
		}

		return $ret;
	}
}
PK�!]���}} CliCommands/MixIt/MemoryInfo.phpnu&1i�<?php
/**
 * @package   akeebabackup
 * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

namespace Akeeba\Backup\Admin\CliCommands\MixIt;

defined('_JEXEC') || die;

/**
 * Utility methods to get memory information
 *
 * @since   7.5.0
 */
trait MemoryInfo
{
	/**
	 * Returns the current memory usage
	 *
	 * @return  string
	 *
	 * @since   7.5.0
	 */
	private function memUsage(): string
	{
		if (function_exists('memory_get_usage'))
		{
			$size = memory_get_usage();
			$unit = ['b', 'KB', 'MB', 'GB', 'TB', 'PB'];

			return @round($size / 1024 ** ($i = floor(log($size, 1024))), 2) . ' ' . $unit[$i];
		}
		else
		{
			return "(unknown)";
		}
	}

	/**
	 * Returns the peak memory usage
	 *
	 * @return  string
	 *
	 * @since   7.5.0
	 */
	private function peakMemUsage(): string
	{
		if (function_exists('memory_get_peak_usage'))
		{
			$size = memory_get_peak_usage();
			$unit = ['b', 'KB', 'MB', 'GB', 'TB', 'PB'];

			return @round($size / 1024 ** ($i = floor(log($size, 1024))), 2) . ' ' . $unit[$i];
		}
		else
		{
			return "(unknown)";
		}
	}
}
PK�!]i<��'	'	CliCommands/SysconfigList.phpnu&1i�<?php
/**
 * @package   akeebabackup
 * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

namespace Akeeba\Backup\Admin\CliCommands;

defined('_JEXEC') || die;

use Joomla\Console\Command\AbstractCommand;
use Akeeba\Backup\Admin\CliCommands\MixIt\ArgumentUtilities;
use Akeeba\Backup\Admin\CliCommands\MixIt\ComponentOptions;
use Akeeba\Backup\Admin\CliCommands\MixIt\ConfigureIO;
use Akeeba\Backup\Admin\CliCommands\MixIt\PrintFormattedArray;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * akeeba:sysconfig:list
 *
 * Lists the Akeeba Backup component-wide options
 *
 * @since   7.5.0
 */
class SysconfigList extends AbstractCommand
{
	use ConfigureIO, ArgumentUtilities, PrintFormattedArray, ComponentOptions;

	/**
	 * The default command name
	 *
	 * @var    string
	 * @since  7.5.0
	 */
	protected static $defaultName = 'akeeba:sysconfig:list';

	/**
	 * Internal function to execute the command.
	 *
	 * @param   InputInterface   $input   The input to inject into the command.
	 * @param   OutputInterface  $output  The output to inject into the command.
	 *
	 * @return  integer  The command exit code
	 *
	 * @since   7.5.0
	 */
	protected function doExecute(InputInterface $input, OutputInterface $output): int
	{
		$this->configureSymfonyIO($input, $output);

		$format = (string) $this->cliInput->getOption('format') ?? 'table';
		$output = $this->getComponentOptions();

		if (in_array($format, ['table', 'csv']))
		{
			$temp = [];

			foreach ($output as $k => $v)
			{
				$temp[] = [
					'key'   => $k,
					'value' => $v,
				];
			}

			$output = $temp;
		}

		return $this->printFormattedAndReturn($output, $format);
	}

	/**
	 * Configure the command.
	 *
	 * @return  void
	 *
	 * @since   7.5.0
	 */
	protected function configure(): void
	{
		$help = "<info>%command.name%</info> will list the Akeeba Backup component-wide options.
		\nUsage: <info>php %command.full_name%</info>";


		$this->addOption('format', null, InputOption::VALUE_OPTIONAL, 'Output format: table, json, yaml, csv, count.', 'table');

		$this->setDescription('Lists the Akeeba Backup component-wide options');
		$this->setHelp($help);
	}
}
PK�!]�[��CliCommands/BackupModify.phpnu&1i�<?php
/**
 * @package   akeebabackup
 * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

namespace Akeeba\Backup\Admin\CliCommands;

defined('_JEXEC') || die;

use Akeeba\Engine\Platform;
use Joomla\Console\Command\AbstractCommand;
use Akeeba\Backup\Admin\CliCommands\MixIt\ArgumentUtilities;
use Akeeba\Backup\Admin\CliCommands\MixIt\ConfigureIO;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * akeeba:backup:modify
 *
 * Modifies a backup record known to Akeeba Backup
 *
 * @since   7.5.0
 */
class BackupModify extends AbstractCommand
{
	use ConfigureIO, ArgumentUtilities;

	/**
	 * The default command name
	 *
	 * @var    string
	 * @since  7.5.0
	 */
	protected static $defaultName = 'akeeba:backup:modify';

	/**
	 * Internal function to execute the command.
	 *
	 * @param   InputInterface   $input   The input to inject into the command.
	 * @param   OutputInterface  $output  The output to inject into the command.
	 *
	 * @return  integer  The command exit code
	 *
	 * @since   7.5.0
	 */
	protected function doExecute(InputInterface $input, OutputInterface $output): int
	{
		$this->configureSymfonyIO($input, $output);

		$id          = (int) $this->cliInput->getArgument('id') ?? 0;
		$description = $this->cliInput->getOption('description');
		$comment     = $this->cliInput->getOption('comment');

		$this->ioStyle->title(sprintf('Modifying Akeeba Backup record #%d', $id));

		if ($id <= 0)
		{
			$this->ioStyle->error('Invalid backup record');

			return 1;
		}

		if (is_null($description) && is_null($comment))
		{
			$this->ioStyle->error('You must specify one or both of --description and --comment');

			return 2;
		}

		$record = Platform::getInstance()->get_statistics($id);

		if (empty($record))
		{
			$this->ioStyle->error('Invalid backup record');

			return 1;
		}

		if (!is_null($description))
		{
			$record['description'] = (string) $description;
		}

		if (!is_null($comment))
		{
			$record['comment'] = (string) $comment;
		}

		$result = Platform::getInstance()->set_or_update_statistics($id, $record);

		if ($result === false)
		{
			$this->ioStyle->error(sprintf('Cannot modify backup record #%d', $id));

			return 3;
		}

		$this->ioStyle->success(sprintf('Backup record #%d has been modified.', $id));

		return 0;
	}

	/**
	 * Configure the command.
	 *
	 * @return  void
	 *
	 * @since   7.5.0
	 */
	protected function configure(): void
	{
		$help = "<info>%command.name%</info> will modify a backup record known to Akeeba Backup
		\nUsage: <info>php %command.full_name%</info>";

		$this->addArgument('id', InputArgument::REQUIRED, 'The id of the backup record to modify');
		$this->addOption('description', null, InputOption::VALUE_OPTIONAL, 'Change the short description to this value.');
		$this->addOption('comment', null, InputOption::VALUE_OPTIONAL, 'Change the backup comment to this value (accepts HTML).');
		$this->setDescription('Modifies a backup record known to Akeeba Backup');
		$this->setHelp($help);
	}
}
PK�!]EK��CliCommands/ProfileCreate.phpnu&1i�<?php
/**
 * @package   akeebabackup
 * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

namespace Akeeba\Backup\Admin\CliCommands;

defined('_JEXEC') || die;

use Akeeba\Backup\Admin\Model\Profiles;
use Akeeba\Engine\Platform;
use Exception;
use FOF40\Container\Container;
use Joomla\Console\Command\AbstractCommand;
use Akeeba\Backup\Admin\CliCommands\MixIt\ArgumentUtilities;
use Akeeba\Backup\Admin\CliCommands\MixIt\ConfigureIO;
use Akeeba\Backup\Admin\CliCommands\MixIt\PrintFormattedArray;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * akeeba:profile:create
 *
 * Creates a new Akeeba Backup profile
 *
 * @since   7.5.0
 */
class ProfileCreate extends AbstractCommand
{
	use ConfigureIO, ArgumentUtilities, PrintFormattedArray;

	/**
	 * The default command name
	 *
	 * @var    string
	 * @since  7.5.0
	 */
	protected static $defaultName = 'akeeba:profile:create';

	/**
	 * Internal function to execute the command.
	 *
	 * @param   InputInterface   $input   The input to inject into the command.
	 * @param   OutputInterface  $output  The output to inject into the command.
	 *
	 * @return  integer  The command exit code
	 *
	 * @since   7.5.0
	 */
	protected function doExecute(InputInterface $input, OutputInterface $output): int
	{
		$this->configureSymfonyIO($input, $output);

		$format = (string) $this->cliInput->getOption('format') ?? 'text';
		$format = in_array($format, ['text', 'json']) ? $format : 'text';

		$container = Container::getInstance('com_akeeba');

		/** @var Profiles $model */
		$model = $container->factory->model('Profiles')->tmpInstance();

		// Set up the new profile data
		$profileData = [
			'description'   => 'New backup profile',
			'quickicon'     => '1',
			'configuration' => '',
			'filters'       => '',
		];

		$description = (string) $this->cliInput->getArgument('description') ?? 0;

		if (!is_null($description))
		{
			$profileData['description'] = trim($description);
		}

		$profileData['quickicon'] = ((bool) $this->cliInput->getArgument('quickicon') ?? true) ? 1 : 0;

		try
		{
			$newProfile = $model->create($profileData);
		}
		catch (Exception $e)
		{
			$this->ioStyle->error(sprintf("Cannot create profile: %s", $e->getMessage()));

			return 2;
		}

		/**
		 * Create a new profile configuration.
		 *
		 * Loading the new profile's empty configuration causes the Platform code to revert to the default options and
		 * save them automatically to the database.
		 */
		$profileId = $newProfile->getId();
		Platform::getInstance()->load_configuration($profileId);

		if ($format == 'json')
		{
			echo json_encode($newProfile->getId());

			return 0;
		}

		$this->ioStyle->success(sprintf("Created new profile with ID %s.", $newProfile->getId()));

		return 0;
	}

	/**
	 * Configure the command.
	 *
	 * @return  void
	 *
	 * @since   7.5.0
	 */
	protected function configure(): void
	{
		$help = "<info>%command.name%</info> will create a new Akeeba Backup profile.
		\nUsage: <info>php %command.full_name%</info>";

		$this->addOption('description', null, InputOption::VALUE_OPTIONAL, 'Description for the new backup profile. Default: "New backup profile".', 'New backup profile');
		$this->addOption('quickicon', null, InputOption::VALUE_OPTIONAL, 'Should the new backup profile have a one-click backup icon? Default: 1', 1);
		$this->addOption('format', null, InputOption::VALUE_OPTIONAL, 'The format for the response. Use JSON to get a JSON-parseable numeric ID of the new backup profile. Values: text, json', 'text');

		$this->setDescription('Creates a new Akeeba Backup profile');
		$this->setHelp($help);
	}
}
PK�!]_Eۦ��CliCommands/OptionsList.phpnu&1i�<?php
/**
 * @package   akeebabackup
 * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

namespace Akeeba\Backup\Admin\CliCommands;

defined('_JEXEC') || die;

use Akeeba\Backup\Admin\Model\Profiles;
use Akeeba\Engine\Factory;
use Akeeba\Engine\Platform;
use FOF40\Container\Container;
use FOF40\Model\DataModel\Exception\RecordNotLoaded;
use Joomla\Console\Command\AbstractCommand;
use Akeeba\Backup\Admin\CliCommands\MixIt\ArgumentUtilities;
use Akeeba\Backup\Admin\CliCommands\MixIt\ConfigureIO;
use Akeeba\Backup\Admin\CliCommands\MixIt\JsonGuiDataParser;
use Akeeba\Backup\Admin\CliCommands\MixIt\PrintFormattedArray;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * akeeba:option:list
 *
 * Lists the configuration options for an Akeeba Backup profile, including their titles
 *
 * @since   7.5.0
 */
class OptionsList extends AbstractCommand
{
	use ConfigureIO, ArgumentUtilities, PrintFormattedArray, JsonGuiDataParser;

	/**
	 * The default command name
	 *
	 * @var    string
	 * @since  7.5.0
	 */
	protected static $defaultName = 'akeeba:option:list';

	/**
	 * Internal function to execute the command.
	 *
	 * @param   InputInterface   $input   The input to inject into the command.
	 * @param   OutputInterface  $output  The output to inject into the command.
	 *
	 * @return  integer  The command exit code
	 *
	 * @since   7.5.0
	 */
	protected function doExecute(InputInterface $input, OutputInterface $output): int
	{
		$this->configureSymfonyIO($input, $output);

		$container = Container::getInstance('com_akeeba');
		$profileId = (int) ($this->cliInput->getOption('profile') ?? 1);

		define('AKEEBA_PROFILE', $profileId);

		$format = (string) $this->cliInput->getOption('format') ?? 'table';

		/** @var Profiles $model */
		$model = $container->factory->model('Profiles')->tmpInstance();

		try
		{
			$model->findOrFail($profileId);
		}
		catch (RecordNotLoaded $e)
		{
			$this->ioStyle->error(sprintf("Could not find profile #%s.", $profileId));

			return 1;
		}

		unset($model);

		// Get the profile's configuration
		Platform::getInstance()->load_configuration($profileId);
		$config  = Factory::getConfiguration();
		$rawJson = $config->exportAsJSON();

		unset($config);

		// Get the key information from the GUI data
		$info = $this->parseJsonGuiData();

		// Convert the INI data we got into an array we can print
		$rawValues = json_decode($rawJson, true);

		unset($rawJson);

		$output = [];

		$rawValues = $this->flattenOptions($rawValues);

		foreach ($rawValues as $key => $v)
		{
			$output[$key] = array_merge([
				'key'          => $key,
				'value'        => $v,
				'title'        => '',
				'description'  => '',
				'type'         => '',
				'default'      => '',
				'section'      => '',
				'options'      => [],
				'optionTitles' => [],
				'limits'       => [],
			], $this->getOptionInfo($key, $info));
		}

		// Filter the returned options
		$filter = (string) $this->cliInput->getOption('filter') ?? '';

		$output = array_filter($output, function ($item) use ($filter) {
			if (!empty($filter) && strpos($item['key'], $filter) === false)
			{
				return false;
			}

			return $item['type'] != 'hidden';
		});

		// Sort the results
		$sort  = (string) $this->cliInput->getOption('sort-by') ?? 'none';
		$order = (string) $this->cliInput->getOption('sort-order') ?? 'asc';

		if ($sort != 'none')
		{
			usort($output, function ($a, $b) use ($sort, $order) {
				if ($a[$sort] == $b[$sort])
				{
					return 0;
				}

				$signChange = ($order == 'asc') ? 1 : -1;
				$isGreater  = $a[$sort] > $b[$sort] ? 1 : -1;

				return $signChange * $isGreater;
			});
		}

		// Output the list
		if (empty($output))
		{
			$this->ioStyle->error("No options found matching your criteria.");

			return 2;
		}

		if ($format === 'table')
		{
			$output = array_map(function (array $optionDef) {
				return array_map(function ($value) {
					return is_array($value) ? implode(', ', $value) : $value;
				}, $optionDef);
			}, $output);
		}

		return $this->printFormattedAndReturn($output, $format);
	}

	/**
	 * Configure the command.
	 *
	 * @return  void
	 *
	 * @since   7.5.0
	 */
	protected function configure(): void
	{
		$help = "<info>%command.name%</info> will list the configuration options for an Akeeba Backup profile, including their titles.
		\nUsage: <info>php %command.full_name%</info>";


		$this->addOption('profile', null, InputOption::VALUE_OPTIONAL, 'The backup profile to use. Default: 1.', 1);
		$this->addOption('filter', null, InputOption::VALUE_OPTIONAL, 'Only return records whose keys begin with the given filter.', '');
		$this->addOption('sort-by', null, InputOption::VALUE_OPTIONAL, 'Sort the output by the given column: none, key, value, type, default, title, description, section', 'none');
		$this->addOption('sort-order', null, InputOption::VALUE_OPTIONAL, 'Sort order: asc, desc.', 'desc');
		$this->addOption('format', null, InputOption::VALUE_OPTIONAL, 'Output format: table, json, yaml, csv, count.', 'table');

		$this->setDescription('Lists the configuration options for an Akeeba Backup profile, including their titles');
		$this->setHelp($help);
	}
}
PK�!]@iG0CliCommands/BackupList.phpnu&1i�<?php
/**
 * @package   akeebabackup
 * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

namespace Akeeba\Backup\Admin\CliCommands;

defined('_JEXEC') || die;

use Akeeba\Backup\Admin\Model\Statistics;
use FOF40\Container\Container;
use Joomla\Console\Command\AbstractCommand;
use Akeeba\Backup\Admin\CliCommands\MixIt\ArgumentUtilities;
use Akeeba\Backup\Admin\CliCommands\MixIt\ConfigureIO;
use Akeeba\Backup\Admin\CliCommands\MixIt\PrintFormattedArray;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * akeeba:backup:list
 *
 * Lists backup records known to Akeeba Backup
 *
 * @since   7.5.0
 */
class BackupList extends AbstractCommand
{
	use ConfigureIO, ArgumentUtilities, PrintFormattedArray;

	/**
	 * The default command name
	 *
	 * @var    string
	 * @since  7.5.0
	 */
	protected static $defaultName = 'akeeba:backup:list';

	/**
	 * Internal function to execute the command.
	 *
	 * @param   InputInterface   $input   The input to inject into the command.
	 * @param   OutputInterface  $output  The output to inject into the command.
	 *
	 * @return  integer  The command exit code
	 *
	 * @since   7.5.0
	 */
	protected function doExecute(InputInterface $input, OutputInterface $output): int
	{
		$this->configureSymfonyIO($input, $output);

		$from    = (int) ($this->cliInput->getOption('from') ?? 0);
		$limit   = (int) ($this->cliInput->getOption('limit') ?? 0);
		$format  = (string) ($this->cliInput->getOption('format') ?? 'table');
		$filters = $this->getFilters();
		$order   = $this->getOrdering();

		if ($format === 'table')
		{
			$this->ioStyle->title('List of Akeeba Backup records matching your criteria');
		}

		$container = Container::getInstance('com_akeeba', [], 'admin');

		/** @var Statistics $model */
		$model = $container->factory->model('Statistics')->tmpInstance();

		$model->setState('limitstart', $from);
		$model->setState('limit', $limit);

		$output = $model->getStatisticsListWithMeta(false, $filters, $order);

		return $this->printFormattedAndReturn($output, $format);
	}

	/**
	 * Configure the command.
	 *
	 * @return  void
	 *
	 * @since   7.5.0
	 */
	protected function configure(): void
	{
		$help = "<info>%command.name%</info> will list backup records known to Akeeba Backup
		\nUsage: <info>php %command.full_name%</info>";

		$this->addOption('from', null, InputOption::VALUE_OPTIONAL, 'How many backup records to skip before starting the output.', 0);
		$this->addOption('limit', null, InputOption::VALUE_OPTIONAL, 'Maximum number of backup records to display.', 50);
		$this->addOption('format', null, InputOption::VALUE_OPTIONAL, 'Output format: table, json, yaml, csv, count.', 'table');
		$this->addOption('description', null, InputOption::VALUE_OPTIONAL, 'Listed backup records must match this (partial) description.');
		$this->addOption('after', null, InputOption::VALUE_OPTIONAL, 'List backup records taken after this date.');
		$this->addOption('before', null, InputOption::VALUE_OPTIONAL, 'List backup records taken before this date.');
		$this->addOption('origin', null, InputOption::VALUE_OPTIONAL, 'List backups from this origin only: backend, frontend, json, cli.');
		$this->addOption('profile', null, InputOption::VALUE_OPTIONAL, 'List backups taken with this profile. Give the numeric profile ID.');
		$this->addOption('sort-by', null, InputOption::VALUE_OPTIONAL, 'Sort the output by the given column: id, description, profile_id, backupstart', 'id');
		$this->addOption('sort-order', null, InputOption::VALUE_OPTIONAL, 'Sort order: asc, desc.', 'desc');
		$this->setDescription('Lists backup records known to Akeeba Backup');
		$this->setHelp($help);
	}

	private function getFilters(): ?array
	{
		$filters = [];

		$description = $this->cliInput->getOption('description') ?? '';

		if ($description)
		{
			$filters[] = [
				'field'   => 'description',
				'operand' => 'LIKE',
				'value'   => $description,
			];
		}

		$after  = $this->cliInput->getOption('after') ?? '';
		$before = $this->cliInput->getOption('before') ?? '';

		if (!empty($after) && !empty($before))
		{
			$filters[] = [
				'field'   => 'backupstart',
				'operand' => 'BETWEEN',
				'value'   => $after,
				'value2'  => $before,
			];
		}
		elseif (!empty($after))
		{
			$filters[] = [
				'field'   => 'backupstart',
				'operand' => '>=',
				'value'   => $after,
			];
		}
		elseif (!empty($before))
		{
			$filters[] = [
				'field'   => 'backupstart',
				'operand' => '<=',
				'value'   => $before,
			];
		}

		$origin = $this->cliInput->getOption('origin') ?? '';

		if (!empty($origin))
		{
			$filters[] = [
				'field'   => 'origin',
				'operand' => '=',
				'value'   => $origin,
			];
		}

		$profile = (int) ($this->cliInput->getOption('profile') ?? 0);

		if ($profile > 0)
		{
			$filters[] = [
				'field'   => 'profile_id',
				'operand' => '=',
				'value'   => $profile,
			];
		}

		return !empty($filters) ? $filters : null;
	}

	private function getOrdering(): array
	{
		$order = strtolower($this->cliInput->getOption('sort-order') ?? 'desc');
		$order = in_array($order, ['asc', 'desc']) ?: 'desc';

		return [
			'by'    => $this->cliInput->getOption('sort-by') ?? 'id',
			'order' => $order,
		];
	}
}
PK�!]����]]CliCommands/FilterExclude.phpnu&1i�<?php
/**
 * @package   akeebabackup
 * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

namespace Akeeba\Backup\Admin\CliCommands;

defined('_JEXEC') || die;

use Akeeba\Engine\Factory;
use Joomla\Console\Command\AbstractCommand;
use Akeeba\Backup\Admin\CliCommands\MixIt\ArgumentUtilities;
use Akeeba\Backup\Admin\CliCommands\MixIt\ConfigureIO;
use Akeeba\Backup\Admin\CliCommands\MixIt\FilterRoots;
use Akeeba\Backup\Admin\CliCommands\MixIt\IsPro;
use Akeeba\Backup\Admin\CliCommands\MixIt\PrintFormattedArray;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * akeeba:filter:exclude
 *
 * Set an exclusion filter to Akeeba Backup.
 *
 * @since   7.5.0
 */
class FilterExclude extends AbstractCommand
{
	use ConfigureIO, ArgumentUtilities, PrintFormattedArray, IsPro, FilterRoots;

	/**
	 * The default command name
	 *
	 * @var    string
	 * @since  7.5.0
	 */
	protected static $defaultName = 'akeeba:filter:exclude';

	/**
	 * Internal function to execute the command.
	 *
	 * @param   InputInterface   $input   The input to inject into the command.
	 * @param   OutputInterface  $output  The output to inject into the command.
	 *
	 * @return  integer  The command exit code
	 *
	 * @since   7.5.0
	 */
	protected function doExecute(InputInterface $input, OutputInterface $output): int
	{
		$this->configureSymfonyIO($input, $output);

		$profileId = (int) ($this->cliInput->getOption('profile') ?? 1);

		define('AKEEBA_PROFILE', $profileId);

		$filterType = (string) ($this->cliInput->getOption('filterType') ?? 'files');
		$target     = (in_array($filterType, [
			'tables', 'tabledata', 'regextables', 'regextabledata', 'multidb',
		])) ? 'db' : 'fs';
		$root       = (string) ($this->cliInput->getOption('root') ?? (($target == 'fs') ? '[SITEROOT]' : '[SITEDB]'));

		if (!in_array($root, $this->getRoots($target)))
		{
			$this->ioStyle->error(sprintf("Unknown %s root '%s'.", $target, $root));

			return 1;
		}

		$filter = (string) $this->cliInput->getArgument('filter') ?? '';

		$this->ioStyle->title(sprintf(
			'Adding %s filter “%s” of type %s to profile #%d',
			$target === 'db' ? 'database' : 'filesystem',
			$filter,
			$filterType,
			$profileId
		));

		// Delete the filter
		$filterObject = Factory::getFilterObject($filterType);

		if ((stripos($filterType, 'regex') !== false) && !$this->isPro())
		{
			$this->ioStyle->error(sprintf("Filters of the '%s' type are only available with Akeeba Backup Professional.", $filterType));

			return 1;
		}

		$success = $filterObject->set($root, $filter);

		if (!$success)
		{
			$this->ioStyle->error(sprintf("Could not add filter '%s' of type '%s'.", $filter, $filterType));

			return 2;
		}

		Factory::getFilters()->save();

		$this->ioStyle->success(sprintf("Added filter '%s' of type '%s'.", $filter, $filterType));

		return 0;
	}

	/**
	 * Configure the command.
	 *
	 * @return  void
	 *
	 * @since   7.5.0
	 */
	protected function configure(): void
	{
		$help = "<info>%command.name%</info> will set a file, folder or table exclusion filter to Akeeba Backup.
		\nUsage: <info>php %command.full_name%</info>";

		$this->addArgument('filter', InputArgument::REQUIRED, 'The filter target to add. This is the full path to a file/directory, a table name or a regular expression, depending on the filter type.');
		$this->addOption('root', null, InputOption::VALUE_OPTIONAL, 'Which filter root to use. Defaults to [SITEROOT] or [SITEDB] depending on the filter type.', '');
		$this->addOption('filterType', null, InputOption::VALUE_REQUIRED, 'The type of filter you want to add: files, directories, skipdirs, skipfiles, regexfiles, regexdirectories, regexskipdirs, regexskipfiles, tables, tabledata, regextables, regextabledata', 'files');
		$this->addOption('profile', null, InputOption::VALUE_OPTIONAL, 'The backup profile to use. Default: 1.', 1);

		$this->setDescription('Set an exclusion filter to Akeeba Backup.');
		$this->setHelp($help);
	}
}
PK�!]�g)���CliCommands/ProfileModify.phpnu&1i�<?php
/**
 * @package   akeebabackup
 * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

namespace Akeeba\Backup\Admin\CliCommands;

defined('_JEXEC') || die;

use Akeeba\Backup\Admin\Model\Profiles;
use Exception;
use FOF40\Container\Container;
use FOF40\Model\DataModel\Exception\RecordNotLoaded;
use Joomla\Console\Command\AbstractCommand;
use Akeeba\Backup\Admin\CliCommands\MixIt\ArgumentUtilities;
use Akeeba\Backup\Admin\CliCommands\MixIt\ConfigureIO;
use Akeeba\Backup\Admin\CliCommands\MixIt\PrintFormattedArray;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * akeeba:profile:modify
 *
 * Modifies an Akeeba Backup profile
 *
 * @since   7.5.0
 */
class ProfileModify extends AbstractCommand
{
	use ConfigureIO, ArgumentUtilities, PrintFormattedArray;

	/**
	 * The default command name
	 *
	 * @var    string
	 * @since  7.5.0
	 */
	protected static $defaultName = 'akeeba:profile:modify';

	/**
	 * Internal function to execute the command.
	 *
	 * @param   InputInterface   $input   The input to inject into the command.
	 * @param   OutputInterface  $output  The output to inject into the command.
	 *
	 * @return  integer  The command exit code
	 *
	 * @since   7.5.0
	 */
	protected function doExecute(InputInterface $input, OutputInterface $output): int
	{
		$this->configureSymfonyIO($input, $output);

		$id = (int) $this->cliInput->getArgument('id') ?? 0;

		$container = Container::getInstance('com_akeeba');

		/** @var Profiles $model */
		$model = $container->factory->model('Profiles')->tmpInstance();

		try
		{
			$profile = $model->findOrFail($id);
		}
		catch (RecordNotLoaded $e)
		{
			$this->ioStyle->error(sprintf("Cannot modify profile %s; profile not found.", $id));

			return 1;
		}

		$description = (string) $this->cliInput->getArgument('description') ?? 0;

		if (!is_null($description))
		{
			$profile->description = $description;
		}

		$profile->quickicon = (bool) $this->cliInput->getArgument('quickicon') ?? $profile->quickicon;

		try
		{
			$newProfile = $profile->save();
		}
		catch (Exception $e)
		{
			$this->ioStyle->error(sprintf("Cannot modify profile #%s: %s", $id, $e->getMessage()));

			return 2;
		}

		$this->ioStyle->success(sprintf("Profile #%s modified successfully.", $newProfile->getId()));

		return 0;
	}

	/**
	 * Configure the command.
	 *
	 * @return  void
	 *
	 * @since   7.5.0
	 */
	protected function configure(): void
	{
		$help = "<info>%command.name%</info> will modify an Akeeba Backup profile.
		\nUsage: <info>php %command.full_name%</info>";

		$this->addArgument('id', InputOption::VALUE_REQUIRED, 'The numeric ID of the profile to modify');
		$this->addOption('description', null, InputOption::VALUE_OPTIONAL, 'Description for the new backup profile. Uses the old profile\'s description if not specified.', null);
		$this->addOption('quickicon', null, InputOption::VALUE_OPTIONAL, 'Should the new backup profile have a one-click backup icon? Copies the old profile\'s setting if not specified.', null);

		$this->setDescription('Modifies an Akeeba Backup profile');
		$this->setHelp($help);
	}
}
PK�!]&ފcCliCommands/ProfileExport.phpnu&1i�<?php
/**
 * @package   akeebabackup
 * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

namespace Akeeba\Backup\Admin\CliCommands;

defined('_JEXEC') || die;

use Akeeba\Backup\Admin\Model\Profiles;
use Akeeba\Engine\Factory;
use FOF40\Container\Container;
use FOF40\Model\DataModel\Exception\RecordNotLoaded;
use Joomla\Console\Command\AbstractCommand;
use Akeeba\Backup\Admin\CliCommands\MixIt\ArgumentUtilities;
use Akeeba\Backup\Admin\CliCommands\MixIt\ConfigureIO;
use Akeeba\Backup\Admin\CliCommands\MixIt\PrintFormattedArray;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * akeeba:profile:export
 *
 * Exports an Akeeba Backup profile as a JSON string.
 *
 * @since   7.5.0
 */
class ProfileExport extends AbstractCommand
{
	use ConfigureIO, ArgumentUtilities, PrintFormattedArray;

	/**
	 * The default command name
	 *
	 * @var    string
	 * @since  7.5.0
	 */
	protected static $defaultName = 'akeeba:profile:export';

	/**
	 * Internal function to execute the command.
	 *
	 * @param   InputInterface   $input   The input to inject into the command.
	 * @param   OutputInterface  $output  The output to inject into the command.
	 *
	 * @return  integer  The command exit code
	 *
	 * @since   7.5.0
	 */
	protected function doExecute(InputInterface $input, OutputInterface $output): int
	{
		$this->configureSymfonyIO($input, $output);

		$id      = (int) $this->cliInput->getArgument('id') ?? 0;
		$filters = (bool) $this->cliInput->getOption('filters') ?? false;

		$container = Container::getInstance('com_akeeba');

		/** @var Profiles $model */
		$model = $container->factory->model('Profiles')->tmpInstance();

		try
		{
			$profile = $model->findOrFail($id);
		}
		catch (RecordNotLoaded $e)
		{
			$this->ioStyle->error(sprintf("Cannot export profile %s; profile not found.", $id));

			return 1;
		}

		$data = $profile->toArray();

		if (!$filters)
		{
			unset($data['filters']);
		}

		unset($data['id']);

		// Decrypt configuration data if necessary
		if (substr($data['configuration'], 0, 12) == '###AES128###')
		{
			// Load the server key file if necessary
			$key = Factory::getSecureSettings()->getKey();

			$data['configuration'] = Factory::getSecureSettings()->decryptSettings($data['configuration'], $key);
		}

		echo json_encode($data);

		return 0;
	}

	/**
	 * Configure the command.
	 *
	 * @return  void
	 *
	 * @since   7.5.0
	 */
	protected function configure(): void
	{
		$help = "<info>%command.name%</info> will exports an Akeeba Backup profile as a JSON string.
		\nUsage: <info>php %command.full_name%</info>";

		$this->addArgument('id', InputOption::VALUE_REQUIRED, 'The numeric ID of the profile to modify');
		$this->addOption('filters', null, InputOption::VALUE_NONE, 'Include the filter settings?', false);

		$this->setDescription('Exports an Akeeba Backup profile as a JSON string');
		$this->setHelp($help);
	}
}
PK�!]MI[��CliCommands/LogList.phpnu&1i�<?php
/**
 * @package   akeebabackup
 * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

namespace Akeeba\Backup\Admin\CliCommands;

defined('_JEXEC') || die;

use Akeeba\Backup\Admin\Model\Log;
use Akeeba\Engine\Factory;
use FOF40\Container\Container;
use Joomla\Console\Command\AbstractCommand;
use Akeeba\Backup\Admin\CliCommands\MixIt\ArgumentUtilities;
use Akeeba\Backup\Admin\CliCommands\MixIt\ConfigureIO;
use Akeeba\Backup\Admin\CliCommands\MixIt\PrintFormattedArray;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * akeeba:log:list
 *
 * Lists log files known to Akeeba Backup
 *
 * @since   7.5.0
 */
class LogList extends AbstractCommand
{
	use ConfigureIO, ArgumentUtilities, PrintFormattedArray;

	/**
	 * The default command name
	 *
	 * @var    string
	 * @since  7.5.0
	 */
	protected static $defaultName = 'akeeba:log:list';

	/**
	 * Internal function to execute the command.
	 *
	 * @param   InputInterface   $input   The input to inject into the command.
	 * @param   OutputInterface  $output  The output to inject into the command.
	 *
	 * @return  integer  The command exit code
	 *
	 * @since   7.5.0
	 */
	protected function doExecute(InputInterface $input, OutputInterface $output): int
	{
		$this->configureSymfonyIO($input, $output);

		$profile_id = max(1, (int) $this->cliInput->getArgument('profile_id') ?? 1);
		$format     = (string) ($this->cliInput->getOption('format') ?? 'table');

		define('AKEEBA_PROFILE', $profile_id);

		$configuration   = Factory::getConfiguration();
		$outputDirectory = $configuration->get('akeeba.basic.output_directory');

		if ($format === 'table')
		{
			$this->ioStyle->title(sprintf('List of Akeeba Backup log files for output directory %s', $outputDirectory));
		}

		$container = Container::getInstance('com_akeeba', [], 'admin');

		/** @var Log $model */
		$model = $container->factory->model('Log')->tmpInstance();

		$output = array_map(function ($tag) use ($outputDirectory) {
			$possibilities = [
				$outputDirectory . '/akeeba.' . $tag . '.log',
				$outputDirectory . '/akeeba.' . $tag . '.log.php',
				$outputDirectory . '/akeeba' . $tag . '.log',
				$outputDirectory . '/akeeba' . $tag . '.log.php',
			];

			$path = null;

			foreach ($possibilities as $possiblePath)
			{
				if (@is_file($possiblePath))
				{
					$path = $possiblePath;

					break;
				}
			}

			if (empty($path))
			{
				return null;
			}

			return [
				'tag'           => $tag,
				'absolute_path' => $path,
			];

		}, $model->getLogFiles());

		$output = array_filter($output, function ($x) {
			return !is_null($x);
		});

		return $this->printFormattedAndReturn(array_values($output), $format);
	}

	/**
	 * Configure the command.
	 *
	 * @return  void
	 *
	 * @since   7.5.0
	 */
	protected function configure(): void
	{
		$help = "<info>%command.name%</info> will list all log files in the output directory of the Akeeba Backup profile specified. Note: log files from other backup profiles or Akeeba Backup installations sharing the same output directory will also be listed.
		\nUsage: <info>php %command.full_name%</info>";

		$this->addArgument('profile_id', InputArgument::OPTIONAL, 'Log files in the output directory of this Akeeba Backup profile will be listed', 1);
		$this->addOption('format', null, InputOption::VALUE_OPTIONAL, 'Output format: table, json, yaml, csv, count.', 'table');
		$this->setDescription('Lists log files known to Akeeba Backup');
		$this->setHelp($help);
	}
}
PK�!]cVA��	�	CliCommands/ProfileList.phpnu&1i�<?php
/**
 * @package   akeebabackup
 * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

namespace Akeeba\Backup\Admin\CliCommands;

defined('_JEXEC') || die;

use Akeeba\Backup\Admin\Model\Profiles;
use FOF40\Container\Container;
use Joomla\Console\Command\AbstractCommand;
use Akeeba\Backup\Admin\CliCommands\MixIt\ArgumentUtilities;
use Akeeba\Backup\Admin\CliCommands\MixIt\ConfigureIO;
use Akeeba\Backup\Admin\CliCommands\MixIt\PrintFormattedArray;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * akeeba:profile:list
 *
 * Lists the Akeeba Backup backup profiles
 *
 * @since   7.5.0
 */
class ProfileList extends AbstractCommand
{
	use ConfigureIO, ArgumentUtilities, PrintFormattedArray;

	/**
	 * The default command name
	 *
	 * @var    string
	 * @since  7.5.0
	 */
	protected static $defaultName = 'akeeba:profile:list';

	/**
	 * Internal function to execute the command.
	 *
	 * @param   InputInterface   $input   The input to inject into the command.
	 * @param   OutputInterface  $output  The output to inject into the command.
	 *
	 * @return  integer  The command exit code
	 *
	 * @since   7.5.0
	 */
	protected function doExecute(InputInterface $input, OutputInterface $output): int
	{
		$this->configureSymfonyIO($input, $output);

		$format    = (string) $this->cliInput->getOption('format') ?? 'table';
		$container = Container::getInstance('com_akeeba');

		/** @var Profiles $model */
		$model    = $container->factory->model('Profiles')->tmpInstance();
		$profiles = $model->get(true);

		$output = array_map(function (array $profile) {
			return [
				'id'          => $profile['id'],
				'description' => $profile['description'],
				'quickicon'   => $profile['quickicon'],
			];
		}, $profiles->toArray());

		return $this->printFormattedAndReturn($output, $format);
	}

	/**
	 * Configure the command.
	 *
	 * @return  void
	 *
	 * @since   7.5.0
	 */
	protected function configure(): void
	{
		$help = "<info>%command.name%</info> will list the Akeeba Backup backup profiles.
		\nUsage: <info>php %command.full_name%</info>";


		$this->addOption('format', null, InputOption::VALUE_OPTIONAL, 'Output format: table, json, yaml, csv, count.', 'table');

		$this->setDescription('Lists the Akeeba Backup backup profiles');
		$this->setHelp($help);
	}
}
PK�!]�ST��CliCommands/OptionsGet.phpnu&1i�<?php
/**
 * @package   akeebabackup
 * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

namespace Akeeba\Backup\Admin\CliCommands;

defined('_JEXEC') || die;

use Akeeba\Backup\Admin\Model\Profiles;
use Akeeba\Engine\Factory;
use Akeeba\Engine\Platform;
use FOF40\Container\Container;
use FOF40\Model\DataModel\Exception\RecordNotLoaded;
use Joomla\Console\Command\AbstractCommand;
use Akeeba\Backup\Admin\CliCommands\MixIt\ArgumentUtilities;
use Akeeba\Backup\Admin\CliCommands\MixIt\ConfigureIO;
use Akeeba\Backup\Admin\CliCommands\MixIt\JsonGuiDataParser;
use Akeeba\Backup\Admin\CliCommands\MixIt\PrintFormattedArray;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * akeeba:option:get
 *
 * Gets the value of a configuration option for an Akeeba Backup profile
 *
 * @since   7.5.0
 */
class OptionsGet extends AbstractCommand
{
	use ConfigureIO, ArgumentUtilities, PrintFormattedArray, JsonGuiDataParser;

	/**
	 * The default command name
	 *
	 * @var    string
	 * @since  7.5.0
	 */
	protected static $defaultName = 'akeeba:option:get';

	/**
	 * Internal function to execute the command.
	 *
	 * @param   InputInterface   $input   The input to inject into the command.
	 * @param   OutputInterface  $output  The output to inject into the command.
	 *
	 * @return  integer  The command exit code
	 *
	 * @since   7.5.0
	 */
	protected function doExecute(InputInterface $input, OutputInterface $output): int
	{
		$this->configureSymfonyIO($input, $output);

		$container = Container::getInstance('com_akeeba');
		$profileId = (int) ($this->cliInput->getOption('profile') ?? 1);

		define('AKEEBA_PROFILE', $profileId);

		$format = (string) $this->cliInput->getOption('format') ?? 'text';

		/** @var Profiles $model */
		$model = $container->factory->model('Profiles')->tmpInstance();

		try
		{
			$model->findOrFail($profileId);
		}
		catch (RecordNotLoaded $e)
		{
			$this->ioStyle->error(sprintf("Could not find profile #%s.", $profileId));

			return 1;
		}

		unset($model);

		// Get the profile's configuration
		Platform::getInstance()->load_configuration($profileId);
		$config = Factory::getConfiguration();

		$key   = (string) $this->cliInput->getArgument('key') ?? '';
		$value = $config->get($key, null, false);

		if (!is_null($value) && !is_scalar($value))
		{
			$this->ioStyle->error(sprintf("This command cannot return multiple values given the partial key prefix “%s”. Please supply they exact key name you want to retrieve. Use the akeeba:option:list command to see the available keys with the prefix %s.", $key, $key));

			return 2;
		}

		if (is_null($value))
		{
			$this->ioStyle->error(sprintf("Invalid key “%s”.", $key));

			return 3;
		}

		switch ($format)
		{
			case 'text':
			default:
				echo $value . PHP_EOL;
				break;

			case 'json':
				echo json_encode($value) . PHP_EOL;
				break;

			case 'print_r':
				print_r($value);
				echo PHP_EOL;
				break;

			case 'var_dump':
				var_dump($value);
				echo PHP_EOL;
				break;

			case 'var_export':
				var_export($value);
				break;

		}

		return 0;
	}

	/**
	 * Configure the command.
	 *
	 * @return  void
	 *
	 * @since   7.5.0
	 */
	protected function configure(): void
	{
		$help = "<info>%command.name%</info> will get the value of a configuration option for an Akeeba Backup profile.
		\nUsage: <info>php %command.full_name%</info>";

		$this->addArgument('key', InputOption::VALUE_REQUIRED, 'The option key to retrieve');
		$this->addOption('profile', null, InputOption::VALUE_OPTIONAL, 'The backup profile to use. Default: 1.', 1);
		$this->addOption('format', null, InputOption::VALUE_OPTIONAL, 'Output format: text, json, print_r, var_dump, var_export.', 'text');

		$this->setDescription('Gets the value of a configuration option for an Akeeba Backup profile');
		$this->setHelp($help);
	}
}
PK�!]
���CliCommands/BackupDelete.phpnu&1i�<?php
/**
 * @package   akeebabackup
 * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

namespace Akeeba\Backup\Admin\CliCommands;

defined('_JEXEC') || die;

use Akeeba\Backup\Admin\Model\Statistics;
use FOF40\Container\Container;
use Joomla\Console\Command\AbstractCommand;
use Akeeba\Backup\Admin\CliCommands\MixIt\ArgumentUtilities;
use Akeeba\Backup\Admin\CliCommands\MixIt\ConfigureIO;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * akeeba:backup:delete
 *
 * Deletes a backup record known to Akeeba Backup, or just its files
 *
 * @since   7.5.0
 */
class BackupDelete extends AbstractCommand
{
	use ConfigureIO, ArgumentUtilities;

	/**
	 * The default command name
	 *
	 * @var    string
	 * @since  7.5.0
	 */
	protected static $defaultName = 'akeeba:backup:delete';

	/**
	 * Internal function to execute the command.
	 *
	 * @param   InputInterface   $input   The input to inject into the command.
	 * @param   OutputInterface  $output  The output to inject into the command.
	 *
	 * @return  integer  The command exit code
	 *
	 * @since   7.5.0
	 */
	protected function doExecute(InputInterface $input, OutputInterface $output): int
	{
		$this->configureSymfonyIO($input, $output);

		$id        = (int) $this->cliInput->getArgument('id') ?? 0;
		$onlyFiles = $this->cliInput->getOption('only-files');

		$this->ioStyle->title(sprintf('Deleting Akeeba Backup record #%d', $id));

		if ($id <= 0)
		{
			$this->ioStyle->error('Invalid backup record');

			return 1;
		}

		$container = Container::getInstance('com_akeeba', [], 'admin');
		/** @var Statistics $model */
		$model = $container->factory->model('Statistics')->tmpInstance();
		$model->setState('id', $id);

		try
		{
			if ($onlyFiles)
			{
				$model->deleteFile();

				$this->ioStyle->success(sprintf('The files of backup record #%d have been deleted.', $id));

				return 0;
			}

			$model->delete();

			$this->ioStyle->success(sprintf('The backup record #%d has been deleted.', $id));

		}
		catch (\RuntimeException $e)
		{
			if ($onlyFiles)
			{
				$this->ioStyle->error(sprintf('Cannot delete the files of backup record #%d: %s', $id, $e->getMessage()));
			}
			else
			{
				$this->ioStyle->error(sprintf('Cannot delete backup record #%d: %s', $id, $e->getMessage()));
			}

			return 1;
		}

		return 0;
	}

	/**
	 * Configure the command.
	 *
	 * @return  void
	 *
	 * @since   7.5.0
	 */
	protected function configure(): void
	{
		$help = "<info>%command.name%</info> will delete a backup record known to Akeeba Backup, or just its files
		\nUsage: <info>php %command.full_name%</info>";

		$this->addArgument('id', InputArgument::REQUIRED, 'The id of the backup record to delete');
		$this->addOption('only-files', null, InputOption::VALUE_NONE, 'Only delete the backup files stored on the site\'s server, not the record itself.');
		$this->setDescription('Deletes a backup record known to Akeeba Backup, or just its files');
		$this->setHelp($help);
	}
}
PK�!]�=İY
Y
CliCommands/LogGet.phpnu&1i�<?php
/**
 * @package   akeebabackup
 * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

namespace Akeeba\Backup\Admin\CliCommands;

defined('_JEXEC') || die;

use Akeeba\Backup\Admin\Model\Log;
use FOF40\Container\Container;
use Joomla\Console\Command\AbstractCommand;
use Akeeba\Backup\Admin\CliCommands\MixIt\ArgumentUtilities;
use Akeeba\Backup\Admin\CliCommands\MixIt\ConfigureIO;
use Akeeba\Backup\Admin\CliCommands\MixIt\PrintFormattedArray;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * akeeba:log:get
 *
 * Retrieves log files known to Akeeba Backup
 *
 * @since   7.5.0
 */
class LogGet extends AbstractCommand
{
	use ConfigureIO, ArgumentUtilities, PrintFormattedArray;

	/**
	 * The default command name
	 *
	 * @var    string
	 * @since  7.5.0
	 */
	protected static $defaultName = 'akeeba:log:get';

	/**
	 * Internal function to execute the command.
	 *
	 * @param   InputInterface   $input   The input to inject into the command.
	 * @param   OutputInterface  $output  The output to inject into the command.
	 *
	 * @return  integer  The command exit code
	 *
	 * @since   7.5.0
	 */
	protected function doExecute(InputInterface $input, OutputInterface $output): int
	{
		$this->configureSymfonyIO($input, $output);

		$profile_id = max(1, (int) $this->cliInput->getArgument('profile_id') ?? 1);
		$log_tag    = (string) $this->cliInput->getArgument('log_tag') ?? 1;

		define('AKEEBA_PROFILE', $profile_id);

		$container = Container::getInstance('com_akeeba', [], 'admin');

		/** @var Log $model */
		$model = $container->factory->model('Log')->tmpInstance();
		$model->setState('tag', $log_tag);
		$model->echoRawLog(true);

		return 0;
	}

	/**
	 * Configure the command.
	 *
	 * @return  void
	 *
	 * @since   7.5.0
	 */
	protected function configure(): void
	{
		$help = "<info>%command.name%</info> will retrieve a log file from the output directory of the Akeeba Backup profile specified. Note: log files from other backup profiles or Akeeba Backup installations sharing the same output directory can also be retrieved.
		\nUsage: <info>php %command.full_name%</info>";

		$this->addArgument('profile_id', InputArgument::REQUIRED, 'Log files in the output directory of this Akeeba Backup profile will be retrieved');
		$this->addArgument('log_tag', InputArgument::REQUIRED, 'The tag of the log file to retrieve');
		$this->setDescription('Retrieve a log file known to Akeeba Backup');
		$this->setHelp($help);
	}
}
PK�!]\�7�CliCommands/OptionsSet.phpnu&1i�<?php
/**
 * @package   akeebabackup
 * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

namespace Akeeba\Backup\Admin\CliCommands;

defined('_JEXEC') || die;

use Akeeba\Backup\Admin\Model\Profiles;
use Akeeba\Engine\Factory;
use Akeeba\Engine\Platform;
use FOF40\Container\Container;
use FOF40\Model\DataModel\Exception\RecordNotLoaded;
use Joomla\Console\Command\AbstractCommand;
use Akeeba\Backup\Admin\CliCommands\MixIt\ArgumentUtilities;
use Akeeba\Backup\Admin\CliCommands\MixIt\ConfigureIO;
use Akeeba\Backup\Admin\CliCommands\MixIt\JsonGuiDataParser;
use Akeeba\Backup\Admin\CliCommands\MixIt\PrintFormattedArray;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * akeeba:option:set
 *
 * Sets the value of a configuration option for an Akeeba Backup profile
 *
 * @since   7.5.0
 */
class OptionsSet extends AbstractCommand
{
	use ConfigureIO, ArgumentUtilities, PrintFormattedArray, JsonGuiDataParser;

	/**
	 * The default command name
	 *
	 * @var    string
	 * @since  7.5.0
	 */
	protected static $defaultName = 'akeeba:option:set';

	/**
	 * Internal function to execute the command.
	 *
	 * @param   InputInterface   $input   The input to inject into the command.
	 * @param   OutputInterface  $output  The output to inject into the command.
	 *
	 * @return  integer  The command exit code
	 *
	 * @since   7.5.0
	 */
	protected function doExecute(InputInterface $input, OutputInterface $output): int
	{
		$this->configureSymfonyIO($input, $output);

		$container = Container::getInstance('com_akeeba');
		$profileId = (int) ($this->cliInput->getOption('profile') ?? 1);

		define('AKEEBA_PROFILE', $profileId);

		$format = (string) $this->cliInput->getOption('format') ?? 'text';

		/** @var Profiles $model */
		$model = $container->factory->model('Profiles')->tmpInstance();

		try
		{
			$model->findOrFail($profileId);
		}
		catch (RecordNotLoaded $e)
		{
			$this->ioStyle->error(sprintf("Could not find profile #%s.", $profileId));

			return 1;
		}

		unset($model);

		// Get the profile's configuration
		Platform::getInstance()->load_configuration($profileId);
		$config = Factory::getConfiguration();

		$key   = (string) $this->cliInput->getArgument('key') ?? '';
		$value = (string) $this->cliInput->getArgument('value') ?? '';

		// Get the key information from the GUI data
		$info = $this->parseJsonGuiData();

		// Does the key exist?
		if (!array_key_exists($key, $info['options']))
		{
			$this->ioStyle->error(sprintf("Invalid option key '%s'.", $key));

			return 2;
		}

		// Validate / sanitize the value
		$optionInfo = $this->getOptionInfo($key, $info);

		switch ($optionInfo['type'])
		{
			case 'integer':
				$value = (int) $value;

				if (($value < $optionInfo['limits']['min']) || ($value > $optionInfo['limits']['max']))
				{
					$this->ioStyle->error(sprintf("Invalid value '%s': out of bounds.", $value));

					return 3;
				}
				break;

			case 'bool':
				if (is_numeric($value))
				{
					$value = (int) $value;
				}
				elseif (is_string($value))
				{
					$value = strtolower($value);
				}

				if (in_array($value, [false, 0, '0', 'false', 'no', 'off'], true))
				{
					$value = 0;
				}
				elseif (in_array($value, [true, 1, '1', 'true', 'yes', 'on'], true))
				{
					$value = 1;
				}
				else
				{
					$this->ioStyle->error(sprintf("Invalid boolean value '%s': use one of 0, false, no, off, 1, true, yes or on.'", $value));

					return 3;
				}

				break;

			case 'enum':
				if (!in_array($value, $optionInfo['options']))
				{
					$options = array_map(function ($v) {
						return "'$v'";
					}, $optionInfo['options']);
					$options = implode(', ', $options);

					$this->ioStyle->error(sprintf("Invalid enumerated value '%s'. Must be one of %s.", $value, $options));

					return 3;
				}

				break;

			case 'hidden':
				$this->ioStyle->error(sprintf("Setting hidden option '%s' is not allowed.", $key));

				return 3;
				break;

			case 'string':
				break;

			default:
				$this->ioStyle->error(sprintf("Unknown type %s for option '%s'. Have you manually tampered with the option JSON files?", $optionInfo['type'], $key));

				return 3;
				break;
		}

		$protected = $config->getProtectedKeys();
		$force     = isset($assoc_args['force']) && $assoc_args['force'];

		if (in_array($key, $protected) && !$force)
		{
			$this->ioStyle->error(sprintf("Cannot set protected option '%s'. Please use the --force option to override the protection.", $key));

			return 4;
		}

		if (in_array($key, $protected) && $force)
		{
			$config->setKeyProtection($key, false);
		}

		$result = $config->set($key, $value, false);

		if ($result === false)
		{
			$this->ioStyle->error(sprintf("Could not set option '%s'.", $key));

			return 5;
		}

		Platform::getInstance()->save_configuration($profileId);

		$this->ioStyle->success(sprintf("Successfully set option '%s' to '%s'", $key, $value));

		return 0;
	}

	/**
	 * Configure the command.
	 *
	 * @return  void
	 *
	 * @since   7.5.0
	 */
	protected function configure(): void
	{
		$help = "<info>%command.name%</info> will set the value of a configuration option for an Akeeba Backup profile.
		\nUsage: <info>php %command.full_name%</info>";

		$this->addArgument('key', InputOption::VALUE_REQUIRED, 'The option key to set');
		$this->addArgument('value', InputOption::VALUE_REQUIRED, 'The value to set');
		$this->addOption('profile', null, InputOption::VALUE_OPTIONAL, 'The backup profile to use. Default: 1.', 1);
		$this->addOption('force', null, InputOption::VALUE_NONE, 'Allow setting the value of protected options.', false);

		$this->setDescription('Sets the value of a configuration option for an Akeeba Backup profile');
		$this->setHelp($help);
	}
}
PK�!]��ѡ��Master/.htaccessnu&1i�<IfModule !mod_authz_core.c>
Order deny,allow
Deny from all
</IfModule>
<IfModule mod_authz_core.c>
  <RequireAll>
    Require all denied
  </RequireAll>
</IfModule>
PK�!]|��NMaster/web.confignu&1i�<?xml version="1.0"?>
<!--
    This only works on IIS 7 or later. See https://www.iis.net/configreference/system.webserver/security/requestfiltering/fileextensions
-->
<configuration>
    <system.webServer>
        <security>
            <requestFiltering>
                <fileExtensions allowUnlisted="false" >
                    <clear />
                    <add fileExtension=".html" allowed="true"/>
                </fileExtensions>
            </requestFiltering>
        </security>
    </system.webServer>
</configuration>PK�!]h�+��(Master/Installers/kickstart.transfer.phpnu&1i�<?php
/**
 * @package   akeebabackup
 * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

defined('KICKSTART') or die;

/**
 * Akeeba Kickstart Site Transfer Helper add-on feature
 *
 * This file allows to remotely transfer files and perform other tasks required during site transfer using Akeeba
 * Backup's Site Transfer Wizard. The features inside this file can only be accessed through Kickstart. Trying to access
 * this file directly will of course fail.
 */
class AKFeatureTransfer
{
	/**
	 * Returns information about the server we're running on.
	 *
	 * @param   array  $params
	 *
	 * @return  array
	 */
	public function serverInfo($params)
	{
		$maxExecTime    = 5;
		$memLimit       = '8M';
		$baseDir        = '';
		$disabled       = '';
		$maxPostSize    = '2M';
		$uploadMaxSize  = '2M';

		if (function_exists('ini_get'))
		{
			$maxExecTime    = ini_get("max_execution_time");
			$memLimit       = ini_get("memory_limit");
			$baseDir        = ini_get('open_basedir');
			$disabled       = ini_get("disable_functions");
			$maxPostSize    = ini_get("post_max_size");
			$uploadMaxSize  = ini_get("upload_max_filesize");

			if (empty($maxExecTime))
			{
				$maxExecTime = 5;
			}
		}

		$server = 'n/a';

		if (isset($_SERVER['SERVER_SOFTWARE']))
		{
			$server = $_SERVER['SERVER_SOFTWARE'];
		}
		elseif (($sf = getenv('SERVER_SOFTWARE')))
		{
			$server = $sf;
		}

		$infoArray = array(
			'freeSpace'     => disk_free_space(dirname(__FILE__)),
			'phpVersion'    => PHP_VERSION,
			'phpSAPI'       => PHP_SAPI,
			'phpOS'         => PHP_OS,
			'osVersion'     => php_uname('s'),
			'server'        => $server,
			'canWrite'      => $this->canWriteToFiles(),
			'canWriteTemp'  => $this->canWriteToFiles('kicktemp'),
			'maxExecTime'   => $maxExecTime,
			'memLimit'      => $this->memoryToBytes($memLimit),
			'maxPost'       => $this->memoryToBytes($maxPostSize),
			'maxUpload'     => $this->memoryToBytes($uploadMaxSize),
			'baseDir'       => $baseDir,
			'disabledFuncs' => $disabled,
		);

		return $infoArray;
	}

	public function uploadFile($params)
	{
		// Get the parameters describing the upload
		$file      = isset($_GET['file']) ? $_GET['file'] : '';
		$directory = isset($_GET['directory']) ? $_GET['directory'] : '';
		$frag      = isset($_GET['frag']) ? $_GET['frag'] : 0;
		$fragSize  = isset($_GET['fragSize']) ? $_GET['fragSize'] : 1048576;
		$data      = isset($_POST['data']) ? $_POST['data'] : '';
		$dataFile  = isset($_GET['dataFile']) ? $_GET['dataFile'] : '';

		// We need a file
		if (empty($file))
		{
			return array(
				'status'    => false,
				'message'   => 'You have not specified a file'
			);
		}

		// Let's make sure the remote end is not trying to do something nasty
		$file = basename($file);
		$pos = strrpos($file, '.');

		if ($pos === false)
		{
			return array(
				'status'    => false,
				'message'   => 'Invalid file name specified'
			);
		}

		$extension = substr($file, $pos + 1);

		if (empty($extension))
		{
			return array(
				'status'    => false,
				'message'   => 'Invalid file name specified'
			);
		}

		if (!preg_match('(jpa|zip|jps|j[\d]{2,}|z[\d]{2,})', $extension))
		{
			return array(
				'status'    => false,
				'message'   => 'Invalid file name specified'
			);
		}

		// We only allow very specific directories
		$directory = trim($directory, '/');

		if (!in_array($directory, array('', 'kicktemp')))
		{
			return array(
				'status'    => false,
				// Yes, the message is intentionally vague
				'message'   => 'Invalid directory name specified'
			);
		}

		// If a data file was given, read it to memory
		if (empty($data) && !empty($dataFile))
		{
			$slashedDir = ($directory === '') ? '/' : sprintf('/%s/', $directory);

			// Do not remove the basename(). It makes sure we won't try to read a file outside our directory.
			$data = @file_get_contents(__DIR__ . $slashedDir . basename($dataFile));

			if (empty($data))
			{
				return array(
					'status'    => false,
					'message'   => 'The partial data file ' . basename($dataFile) . ' does not seem to have been uploaded.'
				);
			}
		}

		// We need some data to write, yes?
		if (empty($data))
		{
			return array(
				'status'    => false,
				'message'   => 'No data specified'
			);
		}

		if (!empty($directory))
		{
			$directory = '/' . $directory;
		}

		$filename = __DIR__ . $directory . '/' . $file;

		// Open the file for writing or append
		$mode = ($frag == 0) ? 'w' : 'a';
		$fp = @fopen($filename, $mode);

		if ($fp === false)
		{
			$modeHuman = ($mode == 'w') ? 'write' : 'append';

			return array(
				'status'    => false,
				'message'   => "Cannot open $file for $modeHuman"
			);
		}

		// Seek to the correct offset
		$offset = $frag * $fragSize;
		@fseek($fp, $offset);

		// Write to the file
		$written = @fwrite($fp, $data);

		@fclose($fp);

		if (!$written || ($written != strlen($data)))
		{
			return array(
				'status'    => false,
				'message'   => "Cannot write to $file"
			);
		}

		return array(
			'status'    => true,
			'message'   => ''
		);
	}

	/**
	 * Can I write to arbitrary files in the Kickstart directory?
	 *
	 * @return   bool
	 */
	private function canWriteToFiles($directory = '')
	{
		// Try to create a temporary file
		$directory = dirname(__FILE__) . '/' . $directory;
		$directory = rtrim($directory, '/');

		$testFilename = tempnam($directory, 'kst');

		// Failed completely?
		if ($testFilename === false)
		{
			return false;
		}

		// File created in another directory?
		if (dirname($testFilename) != $directory)
		{
			@unlink($testFilename);

			return false;
		}

		@unlink($testFilename);

		return true;
	}

	/**
	 * Converts a human formatted size to integer representation of bytes,
	 * e.g. 1M to 1024768
	 *
	 * @param   string  $setting  The value in human readable format, e.g. "1M"
	 *
	 * @return  integer  The value in bytes
	 */
	private function memoryToBytes($setting)
	{
		$val = trim($setting);
		$last = strtolower(substr($val, -1));

		if (is_numeric($last))
		{
			return $setting;
		}

		switch ($last)
		{
			case 't':
				$val *= 1024;
			case 'g':
				$val *= 1024;
			case 'm':
				$val *= 1024;
			case 'k':
				$val *= 1024;
		}

		return (int) $val;
	}
}
PK�!].Vߙ��Master/Installers/angie.jpanu&1i�JPA�hz��JPF.installation/offline.html����T�N�0��>�Y~Q)�
!�.T�Cc
�Nr�x��n!���w�P�&��vs��|��7�=�����0�����7�{a4�Š�Ce,伸Y7�mQ�
:(,r�%�-�� ����J��4!ILtܐ���LV;aa�֊U���㿽b4�N�|:߇Q�Fr_��0��4f-���6͹/�`R�]�pz�NQ��.�9)�WnкP�~
T��",y3���+I"�:JP'K /��A慗��F�5V�LU1)4Frڣ�l�ْ�B�As�GIALX�&P�`��-��I���m��l�7�zz*�aW�{�7�D�Z�Q���|��U��L�)$W�2?Β���x�����)�X�e
�k�ZQ��IJ�@P�2?E����V�F&��zF��A���/\�l|�*^�B�!�F-�Q�[�}��ZP�)J����M��g��4�@��E}�Lc��8<��Qp+J_���]
�HNtV����yQ��?�P�PD��Ҍ&D�]9A���\w��\	���鴡��P��j���x�Y�A��5k].�q3��73T�����?�=�K�v1�S�H���Rp�k,V4Hl����G]����Ӈ�N}
�t�ۢ���Ey�óӃt�&�l���ذf�~$�Iwda2i+�
��\���(:�A:��Y�׵p�-K��m2Γ�;kb�K�����(�L�'�
o�Zn��u>�8����z�p�-�JPF-installation/defines.php�e��}R�n�0��wQ�4J�(]U�ь���*��+d�6X%ز�j�5����ze����,�3	�ԃHJ�
�Bm�b��t��4�&d,�I`*/�j�2�d{ ��YǨ��I,*a3���Ңl����؞�B�ߔ��� ��N&�7������b�oaa�k!ŮƇ��W�s�u�^`�5*V�z�Y��@��^w#��*[@Y��q
|�5�6��0�{�4�☆A�콺#p��3�$O�Ğ��+M4JSK��� N����5�Ϲ�L
?��J8��7O����ID�v�8�aJ�}*�|��sEa���ŷ�xw&}��&�N޽��0x�˗���*Y�h@�M���-�cVly͵�?P(����,"��n����Su_�<y���<����+�1�a���u���V6�OJPF+installation/index.php�	d���Xms�6�,��m�IKrҤ�Ų/���Jeǵ���u:��,�I�!@˺&���$E���L�
`_�����ʖ�3x�ġ'4:y7v�G�� iA�PZ澎dJ*ȣL�B�4���"#?�ѥP���"���FB�}zS�H�je*c	,�u�]�\�y$�Z �u�/5�_���lg矽g;Ͼ��(X��W�S���Z�L�T4�dLthx�Q R�ߝ|�w"��i1�M��K�+��ۄ+Ÿ@���1
d��T��)2��*J�)�RAZ�H�y,�P̋s:��Q(��G?�w����MO����2��%�*�5�C�5-aJD"�Ͷ̋TG�Qi�loJOGo&�w4~w�M���ٴ���7��)�-�6��x]^)$X$�XZJ��LY�r)b��ʉ��
oܹK��?.����ٿݳ_�G�٩w�a:k�f�;w�����k���g�b�t~7�6���t������v��u~wZ_�����+j��2�r�ɜ]�q��dB��N>����<tO�܃��=d#���HO\EJ�N;J#�`�-�m����He����$Uj���]�,�c�(��P�m@4��G�«%�)�-�d�-<S f��c��̪�|]o��mo߁5k�������k?�mw�� N����x��c}dP�r�JĜn�|��Ѹ�b�Tn��fͨS6-1�ˀ�#�2c@NY&�q^{/e��7߻�q���xR$tztZ'�`)�����D)/W��N{��ʅ
�P�|[/�ϱa�^��p�/�	(��?�lS���uiݭ��-���(O�Dt<����ԧ��q[jmgwL�}Dա�e$VjZ�&��WȔV��|�H��,�{'�\|*y|��"��J��#V%r��"X��&�뒛�S��d�����
�M�m��Ssτ��M3����[
�ŸIe8�D��-��~;�
͆�=3IRY�z�ydQ.E��~
��u��9Z���f?���kZ�E_PP�|H,��Z�$����-��Y�!�fl��
�+�f��G��{��9n�;��?�*�&BMR�q(XYpCYSՈ؟�8�<�TH��.�v"���ݼb6>s
�-�r�b�CZ;�<?Ӝ�"�`�"��[�c�QSOc,��ћ�%׊���O2է�����[�����R`c�[�n�^�E'0m�-L�L������،�*��s�~���J�%����R����S�M�&��e��r�v�/y:�y����퇳�����6���d��lt6v�%mk|�CQ�i�c)3�/VU��`�c���r��s�AS���n:�N�M�T��j�F���O�342�w2:vk�u�t�!D�i���]���qe�}�R���A���ڍ�{��۴�Om������K݁�L05H �i�=�����0*���HG~�����d���m����C�ay�&<㒞�<����2c�+�!�,4�ru�w�I�h����]!���L�c�lo�X�TH�\����^"3��W�u��ns��{�_i�+%�p��=�U�$>|�ʹm�Z�-|�-�
��{|Z�͠�(�N��!lA�}�
�CrOk�Q�N�^9�L �E8{��8�p2�H�`�լ�G��~Y�m��nl|���8O�'7ˊ�tt��5
u�����i5Y�i��_ⲱ�V]�"]��w!4a�
`k6�څm��~za>qk�D(�i����������VD�p���“��)�A"�zE��ꄓ��d��:!#55m��Ìl[��8z�26���}�k�0�PN�EW�YNٍ���������Q#l���U5�c��$ֿܳ��V;\��{�NM��a��窐�׮�X��4*[�<6G�fX]굜{�u�;�	y�N	)�/�i�i ßd`���U�<�}��$,��m3&X��S���x��O�oe����3���Xs��7�D3i(�}3��f��\� ���[6m�('�̀^��vF���y��G �����Wt�S	rң}����D������~���w!�|*P4k+c��m1k���C��"�4�L�"@nj���R�0�$cʽG�y���c\�z,r�Z
�/�o:�q�j�Kw%X��Av3�zuG���:��1f���Q���w����۲�F�K���h�F�r3�U���-�肗>'�F��C�0Z,�y�('7c`�
���#5�U�}�6w��F�i����ɯ_v�޻�r�i�ÍKnѹ��/������5<��}���Q�T�����)�ᵦ%���ZТ�r���"��9�fC$u�ަkH>�T�SwA]�����cpeg���B�.̓�I�W�m���n<n���mܲ#/�Qx$L�Q0�m��qc�������˧����"�S��
���������N��|�|m��A�c@`���f���.��y>����f�JPF@+installation/framework/database/factory.php�]���Wmo�6�l��+�Ar��i�}h��M�,�ڤ��b��.����E&U���
��#)Y��t���_��{��9�����>��C8�:�8�}x7G��!�N�V`3#KSm`"���a��\��̠p��d
'7���	��+ou���yVҮ�!�I�k�.�F��^4��lxxp�d����1\�l�a��Nɡ�ե�
ma\�x�r�U��e��p�
�(�M5�
x7�h,�x(��0ty���8�
�4�>yyv��$�\b:��_S�n.-Le��kȴrB*���F/��?i�(�xC�D�bo;4�ؾ�}F;<xt�KTDSe2�K���*������wP
�@Oo�2b��Gs4rI�["������
�X���RF|�*��g���F/%Sc�	���Q`-��i��S��D�?��J���Z����D<���c����Z�VV��u.pBe�W��gKa(�]h�i%�xt8z��~��,Q�;�*��u�Ǡ����7.]������p�Q 6fN	�8ln�ZTb���{�rJ� uҦA�.􄨍�>StKy�|�;��%frJn�����޺G��^M""��ӂ���jN���Guⴷ3A��cnqϸV
3�Q-���n]#;����^0���� M`�Ԗ���5Y��4Zv��
렷��d)\���I��Vd��%�1�k�P�J뭕T�tR�ha��<��{T�e�v��q�U�h5��ts¦��ZA�P�!���;�n$4bAuj��j0Pb��p�_b�$ֺJr(�g#���6h��@�p�
�p�8������3Vz����\H��n� �Jf���4�z�bm?2��y\O��~���7�[���ᶟ�:���N�0��Q�88&M�ٵ�����'���?��}��q��v��"��B��#���s�m��!<�[�E
i��F
�uP�����8S!��s�"�	��m�P��� l�;�L`U6�ƺ�j�Ѥ^q�:T��ыiˠu�(�'���,�o�� ��vs.���+-�ʫ��^Q���� 1�+y+I0+za�ܱ�r
�o����4D��᫣�����xV#���]�i��WbR�v)��73)���Vn���}�!��b�ck�Ͱ��-ٯ��+G�x��9C�!���h��˄#�Nw��G��u�ב�D�ԩ��f3z�h�J�8N_Z��Y}�fF	��I�R�;��5�7��r[F��F��@�"ӣ���]:�N_�؍d��As��_�b���=�E�ߒ�F�aE��i\�ڐ^�@m#v�x�O���9=$�z���}�O��Î��gG�c�w`|)���JCh�<�'��N8nx�m��Bo�.�c���KA����Ɖ�S�
�'~}7��bAR��(?fD]<������+*�4����3�[�<��x[����_t�����#�>V��U��o�V#��5���Fw���پS]�Z�+�m)Jw��s V\GX�wt�K�JPFC.installation/framework/database/driver/pdo.php�^���<�SG�?�_1��H�8����	�pgA�R�B�펤=�v�����~�13;�˜佼�rٰ;�����ӳ���l2[��u�88���Xl��D�4ȔHT�ʼn̂8���L��D��n>2�&��J��(�)_���RC)^��o���0�5p�og�V��0`(z�ųe�'�8�?���ݝ��m���>��7�C��t� �L�Y<�Tl�5�d>�
OE)���R|�"��P�χ�B��/oT�⾞wl)�
$0y{�c0	R1
B%�r)�8�d����$����8���3qӁ0�Nyؿ[mw�ٮ8��1O<%��VL;�$��b���@�d��xT�J�d8�WIp��Qb'��^�d.��0(�ν	�?S	�%�M��O���T�]Ώ�đ��P�`yz|I
wS���,����A���ʶg~�o�v/�uw��Y"���@f��~,ԇLE~����k3F3̀�����,��[__C�׈�JDr�E}��O��8�~{#D�
�1?r0^C�Ͳ�f5)EK��@�"�:�j� t
�$�Q�6�Q.�C���6�͓DE�x?W�H��9+)�b�V�#�D.�v�/�p�}1�a��$�&y��v�)�)�b@�ohq5E��2���C�������k�@�4@/�>�Ο�~� � R�m�w��Qi*�%�9		�*�r�y$�>E6�o
�N�,�&0�WFx7�]G(�=I�90A�!���p`�k3QP�i���� ��t_�˹�8�@��'�'y����/*�A�f p���1�-����,be�l�4�~��@cX`�hޱ���$�72�+���=dװ�#I�n�-�+vvz��o��0�Jgum��w�n��*32�
�u��w��!�mQ� �PF���5*�
s\��#�'�F��؈gYn��)��($,2
��E77&9H�êI�#�N�מA�mV�\_����
n"iZ����i��x�Ú��S�E�����E;���C67�7�ffO�b����(������4Y7��̅W�����y0�=ү%D�v�u��!el�U����(�P=�
p

����Pgn0����B`���B�'~�U��u�0�B�(��1�,�k��Q���J5`��k3�&�a�O@?NQŋ�nd�S#5g�RN�����U ��Δ�@Q(t����'*�'�gC�?�������[
�J%� u�g�&��*t���Y��ujkE��j{�n]]~��`pq}tq����Ӄ�ǭ2��T��ZŤ�z�A`]���Q�ԅJ�!�@q�G$�27_e4��~v'����kI@BP%���VG8
��Yl-l�|�J��7���Q�“H-���
�(8�lYE:ˡ���Du�Q��]Ȭ��r���6AS⭶;	���m�>p����fv��Md�"����r�%�V{S�k&>�\|f��3#��@A�ذI��MD�1Y#�\���]Kގ(A#1X#Q0"\;�$.?�wJ`J:�QO�E�YY���b'�ǜ9���%�G
�H^b4��Sr��[ʿ��dz��63#���l��3�.����<53�r^_F�Ì ��Ұ�I�1�#q�辑]���?��-�55�N�T��=��M6	ab>����z#�h���Z�z+i��j�.Ѣ���]瀊�;�VG<�t�/����9���x
��$�~���E��B��dq/ �*��19�-�(cx!��d�a�M2|�!�[�|���#,���\s�{��S{�s���,�ol߬���h��g���=�����~����O�^��y�2 ����O-�Tm�$��L�y�'�&��TF�ôN���y�Fgv�����A���H�g_>^�%��g#%��j$�*�;/j$�,�[�p� �
���G�������d��t�-���S$ƾ�%��d�Z�����4En'	.��F�b�喕QИ+5���[��e��w��7|�]�Y����_�կ��Y��ᭂf�ԯ���=Jc/�f�K�&�%zfy��B�xx�Q�h�J����/ �aմ�g��ó7#�����#,��m�
�?^�;
y��0�H�w��4M߇���U�N�1�;�|����%3~_Րyy2�a���z���E�x��ǟ��.�?�~�蝘��r���^p�sx�oxV����Cj���c8x�|�r��;�H���n�Y|{����"���$1���n���D��{M�+��{euǧ��޻<9�=��3<���������)�F�!ih
���X%�FfV������zu�.g��5�/�|�[5����7处-�ҴD�������am��-V w��/��sH��~��G��%
����b _��<̘�NU����n�	r�e�g�]xͿ��_j*��V��wHMR�3Y}�9m}n`�Ƿך�mCr���cr�?K��X���#��63��T�V�ɬ�Gk6�ߗ�p���;�5Kj��V��R/Zo+�	���1����6��jov�n��gY��/R�)�;d�wNr����N�;��R^]/��+�+W��;�F^�=p+�
L7ΌB9�(H\�U�v]��ie�`O{w��Bysn�Q��s���/vv
��{��ۙ�S���2�h� <�#�0�Y�
=�������'���ؗnO��G�Z�ȧ�!�+H��Hg�1��[i:�N-��������� �v~�ɇb��.�q��%��{��j`Y�͓�X24�1Q	x7��i��9F�Z�c��
.9�/��TղڲW���>����w^�HA��Q�8��Z۽�BҾˇ�A��<�4���,��9P��ILK���)VvDx�6G[�M_ �f�|'g��@�i6NK�D�ѣ׺!t�Xt�D��O���`^�>��������H]3��=!�k�}�^/&*�^$6R]�\�@���-2�;�-2 �*� ��3�F�&�4|��ٗ���0����I}q���<�ov�uq��ڹ��bO\�V?��x�/�������$��BLm�دT��I�:�*�5
a�9]}@Y��m��r2��8�g��LK���������
�J�XC��5� za��^Ύ��iO��V7��ue<lS���@���:�tb�4��HC�^†���7oZv���=i=�'-��a��@����2����������Ut�\]]�<�}Rֿc�%�7h�%�4��f�9��jjZ�C�㺏�y��N�����V�'�d����3����Qc�V

�4�$q�6ۼ��Χ��c�mRP����"�����e&���X��$cw��c겗sl�HSl}��:`����s���C��ʰ�D2�����A�.�2xX8��q{-������ɠ���L��;�c�+
-N"/���1{`�2��YO���a������L�n�@�U����b��S��n����c��gs�E�����	0t�FLם��&�,�ܝ��c�̇[f��>�-�	5���ldQj~��v��ʴ�Xg@�y�@�W!(<��p��Y�A���8
_��{n瘙�mc��R쿄�i��b������C��=F�th��K�����`9����Q�o�Tqө�pͥJ�

���zD�+�A�Qk�/&��x�&��?%�._�+�R4��%��O9���C�����%؍�&`�[�p��
^˚���i<�}�=�eҚ���q1���:��N�QL���ѡIȜ�v��CI��q�O��ӏ����ս/$C:˲2��ex����i݂����^�]�yN�W�t��)%�_Pш�A����3R/��KXʢ�
�%<��}���Y�O#)�!�[5w�
~�G��U̷)�X�_��	��@tD9t�%�X����j�XPoC�&�my82`�w����F5œ��J���(|�"�)�I���*2��U�XXc���~/�>���^�rѥi��=�шSn�C�-"�~��TX�P���Y.�&ģ$J8�����#NN����yy~t08�'��N�1 �oV�"w�4��|s���Wԓ��xq��\^���ک)�"kd��&���(9oMN�"����bM%l�d��?zu_ʖd���>���@<k�){<�cd��qۤLl�4MDy�ݘ�e1��#��HQ8Y�ۤ�$��}ܗ���$�uv�wʫ����&����
m�
&�t���Z��`���\i2���2��p[Ex#��3p���%z��i�����v~A����EM(|ho<�3�=C	����pOѸᒷIE�n�����4hf�ֳNA�$y���5J���p�xM��W����o]�б����ͷb�����^���z3��G���������8֕Ĺ�=Ư�kl��#�)}�@, Z�ąo�C&]JS�-�ҭ
t&�T�}�Ymk�悾�I����$�ͽP��|���0,��H\w��"���2|j�n?�?`��."��g}꫃Z-�����
O�뷦�X넜g�V`*8�7׭�e\X4��έE�%3m9���E�`�U�à{�����?X���O�7�,T�c���L��\�`�����Ȃ�$H�m���mӷ5�	7xBh�P��vDX'F�	ؤ��R�f�o�,&E	�+g͕�A�~��4����>a��b�G�<��˺x�x
{=H�ػ�}�JE��d�8��6C/���εE]���7�U�^~4��'N�f�hY���7�h��)��T �y6��̛h��)�̭^�N]WL�)��
	S��qʑ��\��6]��+��*s1��=U�3�Qذc�?�Bw�	t^c�D‚?Uc/#jx��\�ٶ;�Mt��k��~N	�̺C�H�5�6x�bn(Zx\��+��ĊiC�����<??��m����jd%����@��u ��D!���$*�N
~)��LJ�v��IU�V\�mb�ƹ�ŏ�w�r�k��	Q�8�=������?5*�3�-��3��%
��)@�AvN��ǺA�s�['A�\ͭ����l�Rj�@(S�!��-/�j��O���r��M��pV��U
qQGW�Ŕ�o"����y�c)�yv���Z�
~`Vq�����.�">C����'�߀�b��ҹ�I�$��.Y��7�
m��r�ml����zr���	��D��3�6��~��4�OP�K���Ns
�땳�|����m���i#�>�I�c���h���6�8i'#GU�T�95}��^^o}�|���^��O��W3`�R��.�ɣp/���H��2J��`�R@�!��A�����P6;�&:�8�vg�փ#�CB�]@���7�S&���#�k�=��r��+�9[m���Ȗ$�ϥI�݃9���ϛ���$6�>�?�I��{0H�UQvRA]sa��������L�%=�	�ߕ�tϮ]����s'�>k �m��q
�	w��oh@����Eo|��^֒�S�FS��K����r��lB�*0Zg1	`a��>�h.~sDzgj��W.�Ԉ��Ο%,�����
#��K{�A�C\����_�^�-�F��䶵>���?N�Yz���I�A�v��ɏ�ڧ��Ƴ��~*���x�9�i��@.�o�����`Ɍ�2�/��*,��'�).o�?Z:�t�mA<;"?Ĺ�J3��8Z�)�:�yD��[�IVu
91U@�e�c�9w~o�v�U�n���N�e��+~�X�/�����Ø\�n�r�t<_����JPFE0installation/framework/database/driver/mysql.php��*���Z�w�F���'�d0rH�sZ�!	4[X���Kc{I#f$;>��{�������`G���߽s_��?+����]r�]�:;%�՜�KF$S����"'*��(�TH2�� T�s�`�Ēђ%d�"G��P�®���R�x ����3F,�q��ŢXI>���������!<���*�SDN@����R����x]&�V�c�+����=y�r&iJ�VxA^ۗ&�͐�J)( a�hw7aS��$�G?���8
� �,<�uP]͹"S�2���E^R��g��T����?��Rz���[6l�F@���C�v��耼)X0U2f䜖 ���<������)�,���r���Y�K&���`d)�'m�8eT�+[~�����Y0�	R,8B�,$��Ƞfq9_]��5IhI'@N@����bh�3/���h��E����4�E6JD<��b0�"G'�Ή&s���.Y�����AIv�݂J�J���^�+Y<�%g+2�@TUB�	�f�R
I���vw
��*Ә�%�BI����92�iƜ�:�D��G�6�=M�E��ed��w�.gq����b-��RD�)�u�6pj���<���5D���dN�D��r%+�$GW����̈�r�U�w
�i��:|pu��;`��&tk�욫R�Fݱ3������	(&+T���g8'v�� �\�!��C�p�>u(�|��+D��e Ԓ�ؼUx��4g,aI��B��ߔ��w��r��j��Vi�j�*�R,P��g��:f
�K�	ɺ~#���� .}��PI�p'} u(���Ƿ�V<HS�0tX��h�wn���@k.���c��	�E�B�HQ0h
}�4+���JT@1i�k
8W�IK8�Fh)ԯ�\�2�8$���{^@d�h��;pO6����d%Ay3�E�&�1��|��s��*�{yzE�?�<��<{s��t�'w��-�7|Φ�V�x�X�Ҁͭ��}�� ��ޛ���yH̆�#�w��AS�֖8��.���޶�O<��Ƙ`��МO�ƣ��E(��Q��z�G" �A���m�N��t��1�g�R}��A������	����=+�s�!$��ѯ��6��*��!�� �E��Sr���10w�vo۵A[G<���Q�s��g*�#���&���
>0:&;���l�5��%����!�!��0�$�z�w�=�&)��Ņ�K�@���Z�kiR@��fd9�gۼɬ��C'�-6�=ڈ�f�t��ټ�d��ׯ�L{�UZmcDh)ұ�>6bz!�+���A��YG	O�&I����3ڧC�m���}���kW��MVwhg��%G�L^I��a��r��e~�)dٯ�����d�;��[R��Ť�\ͼ��*k��+, (IE��=Q�hI�%D�,ŧ+���f����de�ZaU�TA�tm�6���dEJc�VB#w��{�-,�5Q�3^��d�������)}|X{r� 3��g�gW�uVG$6k�M�����e�zF�X��
��'42m�&�l�緑g�DNRV���;iV�J`=�$
�6Èɿ1ióJ�i'P��#b�75y�Paܩ�L4�4�p懽�O:��ML�a�5O�Q��j�ؔ�{*�A@����`&�"��2d��#hI�q�N2�C�;a��kM�ͩ<t9�TE{���^��Vq
�r�{b�^�z�ţ[�4�	y^l܃ǹؐu{0���wٳ\�����]�NmK�
�sr�-V��V�xM��V�.2t�f��R�ب�����H�Sʕ�4:u�t�"�vK_�e���|��#-D`>˅�j�u�m:�ȇ����k#KL�xN�nX�\9���u`�*���"����������a�D�r8�@��t�bbI�?�5l��Ky�C]�?0�Ė����F��3ʭ`�&h���瓭��'.;=��u7�U�\a�v�P�O˭}����[�w���qH4c_��r�Y�D����;���L"ق�J�j&��삃�f@����o+w��<�k�ҭ��i���0F��s;���#��__�=���QN��6����`�lw�]��q�6�;P��jp&�����o�90�p�ϼΏ��	~w��	�
}�G��6�n�_�gl�Ș��
	����Ye����,�K���:���]\�����34,~md�Y�t�s�Ҟ�m�u<�=Bf��܈ͥP��~��g[�=�v�l_�5�uzg=-
Ks���^kz�����97v�͕���
zL��k
��8��\��篟s�Y�Z�n
Ȏuת �]������H��k�NK6]}l�Y7$��2������I�/��g�o��~F��@��M�TO2�;�˱%:��c[Cbm�ꄸz����o��fc��/��_��LXN�n���ҝ[9Y	�	����dch����T�T?��L�D��vCiV�K���o�@Ƴ�RU��
�|`/�d3���ڋ�}m�0��R�:	��/B&o��
ȲH&�Cs:�i�7���q��u�5���>+�3��c�6��$t��F��CI��Y:v�62�}�3�w��G�E��j����'LO��/��ԌE�}��Χ��[��r�"���ŽDCh8�yzԲ\@8�>$� �e�+�6�p�D�C�oD�y�3��h�aP72��xo�u�U#0Խ+���BȺ���-4��nL�y��l`F>���[]��?�%�CLZ��.�?�J9T�uu��xu�f;��	���[�mt��	�Y^��'����_����<O��sX�� b�k�Ğ���0F��p� ��C}|0����{�0��7�ְ������$�=�U�k
���:G<'	G'�k�/�o�~Ĭ�p}�e�mv��)ǻ�V����޽u���Pӻ~'����}��S�Trz��}7Iu;e�խ�/*mr”jK�r8R�UO9g��7�Z�7G��4��c/�m�6�O9�]�����g��H_�M�3�];�p��?IQ�&���9BM�[�V}�c�������@)s�G�G�@�om�Z�����o6ßf����I��l��ʖ�n�����1�_l�7Z��i�N3��L��{��ޢMc���-�����ЪfY��I��_}�7�=p���4���b�Uc�^v�	�}��JPFF1installation/framework/database/driver/mysqli.php��V���<is�F���_��h�dLQ�c�yd[��Dc�H'�R��&�$1�df�>�>�8E)��T��eQ��w�����������_o�����C����/,I�<�€%N�E)��1�r�*����w-�Ă��e�;�b��k5#p��Q臀Ѽ�`��c0a�	z��*�拔�O]��xw���ǻ���3�Y�>O��{��0
3?L؎�q���$�����{����]6�v��E� _����1,���t�����L���:=��z��{��E5^x	�y�`K�bN���+�,�,�=�>���� �e��ޞ�m��x��cv�ĔŎ`�<z�>;���~���Y�㔅�Z,x��TW��5h�Z��0�"u8�౿��+P�3ɜ��DL�8��P4�B��9�RSr9]�~<��S>� e( ��b�J�?�4��vv���Hw�<ȸ�#��i^
����
�8��O��
A=�9L|JE�VF7��������7��
���HL��^0gd���T8��v�b3��@hIEa��gb00��g�~!��͍H
'IA��r�����܆|��2[�rr��4�8�8#�u�V�����:�ІS�������A��x΂����I���]���c�20"U,E�%��%�P�`��<���>�)m��h�`�|DD;��`�pD��x5`�x�8��`�[�i��m#�y�G
QN�.L��z3Hf$q`>�	<�yq�Z0n<�gS	�É!�r�-e�;�&\H>��f&ݦc��	.(���π&��Cp�xwЄ��!�)�@�)_F����0鋔��a2t����eKy�F��g��τ�! w��x�%���M�����#�#r|�my��L"E���N�ْ9G�ƙ�΢<,_b��c��l+�R��r����q@2��Y,�A�
2�B�,�6�8wW#�mn�����o��$���*�Y�5H��k/;�0I;ȽJ�!��^��d�%{��������a�T����%4�2��‘j��%��2�[���f>Z�i-��L�*$�k`�Q�؅��X��=�%�co��`� ����{��KĆΕh&V���7�� f���r�A֩]��5oVi΋d&>��B >/GS����
 &�9ׇ`��!�{	l�\�#�@�>�S8����/>�K�/Z���t@a�D1V'�|"o�t%Vf��\K�	h�ډ୽�j>M
��Q9���[i����
�_w`�������|0�}����"�_�i��K�N�f̲Xc$�E�@�u�
��u
{6@I�3�B+�XЮ
(%N�B�#��5����U7P�^'�֞4
����f
je����kQXR���өɼ��ļ	)_a������
H�u�7��ڌ���b����U���4��Sa��v�5�6��L�%��E��1�:�9\�A�)1�\,���3W&��~a�D5r�������x����J�կ��~MD�7o�^Nj�}�7$y�J�1#��2������ٮ ��ksO�8T��6���_s�G�9i�a�#8|C�g�*���TXaPB���K�:���^2ҕoW��
��2� %]U�N��2_oD%���]5O���M &~�J�yFa�3�陰X��q
v��i;���<*�X�9��F�ס�2vA%�/�T�+���򟁅�,X�"o��������F4
�-�
�F�N��<O�Ե� R}!��P�
��w?��{����5��s����V��?�M��ngl̚�<�
]�A�A�����q��-x#��\�rg8;�w��y�w�VfG������*
Y�h�.��_��C��WV;n�}��iwn�U�*�~g���d�'�'�ó�(%‘��y2�<:Oo�9XZ���_�C׈}:�v��ۇ6��]�dog���f�[}���I8�~��" B�����3Z��Y�-R|�'�ǃ4�45�)d)��L/�"��OË�_&��|�/�Ҕ5+�U]
`�>�
�v��Ñt��,�9��6���1W��ݺ2�:r%��M���h-�?��v��M��1��!%�XG%���!��{(O[��[
�V�6�^�y;�$~=�V��>�^��j�}J<B���� M�2JU����iW}�D2�j�� �cfv��F�c�9�0�e�������	@��
J ̖��ϾB:V5�z�W5���p-
�,�׎�^b���,�%�TY![h0��ov���c�b�e�4��vL6�Kr���옠X`)�t
�1�n�G��H�@„
tX��`��\>f"�Q^�=
��ի�p4:>?`��}`8��,
�e����1���{�"0�icr���`7E���ܖ�m��mEY>�t�lº2צDTf�O������[--�"L��p+�+�J'O`o���Ǜ]V.
Jg$��z^6��A!N�a��ڮ��=o�h�)����N���8tПx�O0�z6x�ի�'a�/����0FI�&(�p�.� �fg��*���,�5T?��aH�N���
8�-"�$V�gq�o�6��hx2<�G��~&�x�~1ߜ���V�S��qR��a�֊6�%�)4-Sj�-[,�5�����!��%�׆p��YtT1�k��Sݤ�q%x�IE�90��>����q�7�K��C]@4(B2[z˛
�^���;��Q�p��J\zJ	l�%)���M�Q0O^�Qm?��*�i�Q���Ȟ
z@S��7���Ҭ�U��֔���(�+
}�v��\�?Ҟ���-E'z�����>�@$1�^������h<�1<���d��#����I8Y*qJ�4V2�S�.B��"��P�i7A�/4A�	�/\th77�)e���,@ap���<7,F30(�z���[N"PE�k��(��Y[��3�D{_����6�+� �9pq%u�ޟ�t��.�d�t�<R��H:��
���ds���<�0���:�ϓ�H��)8���Xɕ6	9Z2�����Mj��^z�@��������/�u���Z����X��K�EI�%
-Y��YX��&sSӃ�lYH���&�.0�ȒP���aСV�L�C�ͱ�ͤ�m���z��Ƈ������"�#��b�}�v����(L%�����_�]���r��!��܎�dvr|z<&�T�=`���	��q���B��[�\9!HT�F���C+w�4��Uf�3p0JD8�!�pZk�P}���=U�g�����e��*]�Ʌ@��B��ŠńV�l����BC���d�
+�������O\�$u��u���=$m��a��(/�=mQ��nI��ˢ@q:$F�>�/��埍�\�X�7?�<�R��}��ø�\��R�����F��n!�+]ܨR�$�炪U�*/A��S��W��L�v\e�] �\�Ҏ��AC�.���]��U��l��y���L����z?�X�?2Ò���]��PJ��{)���C�������2���.��
�����\um��gqX���!]����x����׵�Z~�[�u`�*nl�T�6�s�.�E�h�ײ,�U��R��r�A��x�s6���!�]Lt�^�%:Ypo�j�^�@������IH����ښ�(�ʸ5�	���%���\	�:9�jU?�F���Np䣍@!�Y���,���Ӯ[�j��ѵ��Υ;�vꇧ�`n�B��@ʒ�r��_��fG�����g#vtq~�2�`w6���+kv�r� IB�̖�+�z�y�-pCS�íV+���ju)eRn�;@� Pg.��3t����P}N�є�Q��mw��G��[�8�y��N

�#Ԗj7��/
{E�P��O��<�н�%�&˧x���nZ���D!o��Ci,o�U$���KvFA�t
�;^D �Ij$�.l�6�'�L��2'w�N�	�͖A�:}��5�!�UTU����k�Z�ۺ�ްV%bwFd��u=b��V�
{��
kS���FH-�pU̠�ݎ����6���dB.��R�(Jt&56v5)x)�>ډ����~q���F�0���`粻��]����ɠ.��ǀ��G���E�eO��#N�S��|�b�ړ-Њs�9���֫7D�|g/��khvR��U���`#]�'�i���l�|:FF���ޠB�1	�����ې�:�^e�_�}
VT�Hxx�^�Ӛ�M�V�т��"#sSP¤;ִ�z�PV�]��\��*��u��X ��>"%2��]e�q��P�PJ���]+Kņ�d�i<�$Kt��>��yӹ�+R,֌M]��t���H�U9J�׵��T@��Z�x7J_o��uOS~\u��4ڱzmȖ��.�zs�x;��=RH��@�nZ�8�5�H��+�F�]�Zo�H�{�B�׮��
�V�ӎ�ʬղ��e�Q>���
[E��\N��E�[��
�6��'9��,���p�S��-O�W����y��a����\	C	��A%E���*�ۄ$�y�D$�L`ͭ�9	��d��Ne�>��:�Dr��ɢ8�-vr~�Vm�VWG���/���N�g�B��|���;�E�S�8�6�č
C�H8՘T�V}������OW��QÒ��U���g���2-�~.���]�}�X�஘�����th�N�*D��ˇ�]�h$oT���ʣ�V2s[��P_֪�k��T�p#�7¼e��ϥq�zLi�l��Ds�ϔ8&�<����<�ë��bOQ��c)t� ���g��	��	�S���o����V�������I�*e�r�y
�披�.p�!2�'����"����J�^460�~�귺���m��v5zێ���B�|�������a
��5/�	��fߩ3�����,�%VW���7�_��)m�:aj���t�t�
�җa�TQ�o�H�����$p����-u�,)?�$�+r�c��v�M�N���EݞZTo�M�]���K]��I�~��K+�7��p�soIh�Uu�Bh�X�p��
 Y�y�p}���ޝ�����������x����[=_q�߁1�Oc�0"»1wq~r�����ӯ]�&�4�F)�塀�
������p||~���)�O�/���:Y�PfU>;�e�����+�9;��߼62xY�A�W����N+&mQXsx��U�}�Y�rw��ѻ�:���L��N�S�U�:�i���|����|��k�����
ꀬ�`t����X�F��(4��><Ƌl��3*'h曂�����e!���l<F��)�}���������G
A��W/L�w�D��f=|1E�Tp������LU�*
to!W�-i�OV�>Z�m_s��:I���N���"�F�k�-���`P�#�K����ɖ�go��5��/
��J�{%���-iV]XNm����WX0��H0�A�{"H'�F1�y��hB�5
re�ݝ6%a�M���>~�"�J巙�FP����.�O�W���8I�E���74�->�k�2,�wv���7�����{��w���G�j���K�*4Qi��^�W��u�+���7?o�JPFF1installation/framework/database/driver/sqlite.php�*���Zms��,��MƓ#����L+Gnh����EY��f�ށ"���r������]ǻ�ʼn3i=�,v�Ͼ�wM�����_��л|uч#�%�KȤ�u&r�0a��f:���)�,��;i ̤�e�%��I9��Q$��X��}���V ���<�t���yg�_������=}��[�T�\���.��BK�S]����1�#��P&������W2���᪘��������b�@����1�se`�b	��P'�P	~Ff�^@�F��֋X|W�
��ԕ�OpDܞ>~���L�EJx#r�h:p��݊0���!Yz�QJ�^�D�Lݡ�$���<����%���C���L��3�3L3}�h�Ɖ ��Mխ�e�v@ЈD.�����i��\%�pu>���j�	>��H���<=9>F�u�/DR��X&Ǚ�u�H�_b�%t�2����I�1+"�Ν�È��|��$Z����ᯇ������D,��rcC]"b���DF*�<��١�:��Aj-��9�B`���S&G?!E4�%��NSU*��L���iCAf�5�Rh�ڞ�ȅLr�~�\�>�n=S2�����9���P���s��4S�DC ��zM#�e�b�ArWts�`�wC�^�"֜����>�mv3V���H�����*�ajٗ̈Pc�lk�<��h[��J�}��ᚐTd�e9��|u��1+BL����:2fE�I��ڇ�<"�=�eR^KS�8�����&W�Ds�j(�&M
��2�Yp�U���Q�3�����Zf�
c���M�x�%�lk�Z�+���L�Ƭ��#5�WM�ɘT1fJ��P͖�N��ӲZ�GI�C�&g�t�*�
f\�Wc�IO��� �\�$�+�^�!�cR���E���S@Lɺ�J�l�R�l��n�V�oi��-��1~&8�^���bЇ��Z�+/�����x�s'm��B��sؑ�u�5!��\:�y���72�kN�҄"���,,(�C�Vf�+Me�\��9����-�f��.0u`jP��!'�X��:H(�2�;1��l�䒣�EN�h"qQ&�&��(��$�Ad�:�ʆ�̯�?�
��ĺ���f��xC��)��j�05��2�䖬
>��&�X?��L�r.�R� �zI�[�˛� (�Q��oON��#ޞ�9��z�u۾�����kX��0C�+m	ճ2c!�'2�B�G��2�ABM.�lY@�$:�i��`>�3�ᙗ�u5�0`e��n�h4����ή��q��epR�����(�Ԧa�Z�$�A`��ݹ���g>�ݷE�r6Y`�Vig�PbJÔ�Wm���,�f����	V��'�4�o���~�.�\-d�}(98�۟��sY������0���Yi�5�`�@m
*#��k�iW���1_����(h�R�l�kS�$��,�UBG
T�y}W��'k25S�K��G;&X~�Xh8�rD����X(���T�܎�q�H��A������.FRހ�R�y�g�Y��v3����ur���'g�Q�Y�7Su��ߓ�����_�>%��C�$v��PTۅ4��-'��u
�O�v��ϰ���#��aA��\��~g@*��˺A=���P�3�����H,Mi�������ޛ�?I��>��O�`��tB�KF�9�{#"RY�y?�q���fI�c
���[���S�&L{�$�A|��:n~�����<���_b��pL�N��$���r�}ݏ�Q�K��L`�TY���'?�7���r�k�^Wx_]_�����+b��P�zΒ����72*6e}x'�6i�O�;��f�k�ڑ�r��v�ġB�ZU�=r��'��i�����`sN ���*�Z�Iyp	:P�{;��gw�Z�&�bQQ�1�nv͓̇j#7�T���|�H"1;;jQ7�]W�l��e¹\���(��	s*{�M�g�]�1Ǥ
�KEv��qtAB"v�&V�
y�A���$6�� M�%-wg8Y��a��~.3�
���xXӄ�Wj�:�0�Jѻ�^Fb�)j��7��>!�k�f��P�\�t�]_�Y�{|��.�	��A����18��y_��jXUn�j�0v;�r;�ncW=*I���؄X��ܼ-};n�p��po_�2xV�U��u6�?��
=��R�ӕ!�&�h[�q�J���$��f���ϕ�g��:�]��={PE������C\��C�*�v���|�1^��E:�`�&
�x�*��>�9��u�^�G����'2�zä�3�٣�b�$12�z����W��(�S�}�p��vζO1�G��x�}��2K3�f���p����z���ҽ�]��G��l�۲��I��+�Gh��\�-�Y��S��J�S��~�Ȫ��
���
��{�Z�u��3�����c�|���Ml��7h6�@666�ƪ$˲C���g�ZNϑwxb�r�ږ;.Skd�ݫ]m�q�ܐ2#��?�N�iȴ�������W螉=<��]�����aߣ�F��C�X(���@�>R��.M�ĝL�Jrz�����L������T������g�H��e�9ac�c��E>�^��՘�w��G$��L��:��m������o�W���ot�a6�Ӯ�*��6j��q��d0����-r�P쳊�W�z8�蝽.��%�<h��܃�����jV�� �o7�J�����f
X��Pc��5��2Z��!��
i��;���y�\d��}<v�E�/��~���?o[�mFڨ�C�jxq9�}q��7
���,�����N�a��-j���Zȷ�Ѯ_�wi���KZF��3�*,,�-6������w�E�Ev�D���wL��—��gK�ʺ�DK�Iע��.���
P�v��-�픏Q�~�6_��Q�r��;m����_S9Y��5Qۃ�R�熻
w}CB�P"�_�B����v9������ꛮ���J�lB��B�[ı��X��A�Yh��[����JPFH3installation/framework/database/driver/pdomysql.php\�<���iw����+ֱ�1E��ʡl��m5�J�i���$*�`�l��ޙ�')��������׮�}��/����b�}���N�l���� �,�2��"bҋ�e¦"f׻N�̍�yp�%�b�&�g��^s>q��"���P��c�/a֝q�`�,�4��*f�짆�<������cvxs��}�f'��J��HC!ٞ9�,�	Vx<������G<vCv�N`����K��q�I!���m�O���
g����u���x��t۰j4$�!gw�<%n�O��i,,�M�E�>`WضhIc@�Wl����%��Mi�qv�&��l���k����#�tㄉi�)mx��R��
H[_�8���q���k�S��.yL���	�5R��3�����r�����n�N\`��X��9�:����Q��^vB��|�XɟB��'<�+��8l����gm��o���dь��ڣ��#P�يM�E$�t�1(j����[H�����4��<��O
�0��: {8�n�P����j�,�C�Ro�P�4a=E�#+��n�z�L�DA� �淃�8�ތ^>)��+MP]
<�p�qePْ��0,����GI��}^K0k ��xծ�FKG�q��$��2!}D�t��+�_�E���Z��s��7x7;���eY�ٻi�0/XΑ�):@<pt6̑%��%T�\-VV���T�p{k���Ov_��w����_}��0|6Ï�|i�qu�E�n���O�C{��8�@.�u�P!�(J{��j2�h����$�9�c��q��D�#�a�`:�KpX�[&f|�loŸ�i��$�Cc���� �x`�0H��;
�'����E���4a--��Hi�nm�
��� �xڂ[���x�5�)��&�w�k�tl�SdG
�������r,&�^6v�޻Ǚe��)?�z#�_@�f#���7�ë�)�cYr�4��X0�U, $�hk'���B�o�@}8=�ߊ�&��ۃ�`�0@�Wˎ�I��G��9��cH�AC��.��F~*�ue	ǟ�CP�d�&�N�gK	��=$�l4YG;�����Ϲ���%�<DH�Bd��?^�rިen�����Q����[R��לa�5U�@m�d�C��@�V+�0s��m���1Li��C�A�-H.�Φ#A�>�k|ˍ|ʆߤ��B�F
���;S�P�,L8n%@I�$X�{�/
cj8`�/3�٢k*�m�[��N�V\=G����alO�����>
���D���y�+�/
Xt"T}n��P�#	��%[eWU�04Cy)�j%y�V9���c	;�>�����N��xv�?�Z���Ml>w42�<|}9=|��;�.�ٟ�iv���P��&��;�G)���=V
���ʇ������w�{V�r&�z��z��I�i����(Iqk h:�1�I�M�5��٢��ݹ
�9fG*٩�
Y�k�[u��:c��U5�ޙգ:Uť̹�$�q�F�*����X��ax8 u�Cɖ,��NY��4�W�g#�K�Æó�b�x��+"��Ne�}�@���v���#L�x`%�s��,�ߝ@%v*/��2��0C�N��e"��A9�~!on1�U+��9}SLN~�Wټ�K1�Mg�����$;K@��dd���^���'W���9f;7n���q{-ߪ���X�&������FI�������)�q��F�Hxf74*�:V�@1�^86�U1Iy Тh+��P�O8C�G�`��&���`���|"⻷�͡��E始�k��@K]4��@:�B��mӭ*�9����f-{�W��dY�X���:I���訦�2yV�c��<{`r%����q���}��o|��x�M����&v��骠��m���G.��F�h�zUs䱊�c��F�ВmMT���N_�0�X%�����(�V���Z��I-�[��I�b�J�U9�(t �
���l�ɲh�\ǂj�Uȭ���|�%8a�-�.:��&Yl��+�4�)��r����:��/��T��C��x��b���q��֐k�u�-FS\Y~���c��{�� <��χ�����
�Ș�Aj�<���L�1����F�7$�IQ��ZTU63nձ����ں	Hs�&��U������u�m�g�5�UĽ?��7g�Q|5�_u�aKy��IǪԡ�/�ɪ��ڬ��$�ݛv2o�n�JH�u��h�M����I����KgCfn�<�P�����S��4�4�h����s~H��,g��6��
�;{H)����"�fpܦ.L�N˵��Յ�me�+���\6�{#v�ژboc�]r�f@����
B�n�<b���k�@ג����`;���D�ك\K@�_EAX�����ؗW��;s��5�-��V+E�����G_�JV�e.xΓ� nq	��hZ��S��.�FiZ�9��n�.rW`S��0uW����TC"�g�t��.c*H�!�h	���L���E
 ��!U�O�c��[�l�-�����WŞ3�Q��6�/��M���p�P�h��`�f�z����Y�D��i`�N��������DS���vr������ƒ"Ó�+�\6�(���5[R�>��T���ňi���n��n3�}I��m�D��#2�4�ߐ�
��]½��ې��]<�E|.g�����tqG\P�k�}-������\&���|����3#�E�7���MR����2����b��)̲N]F_Ő�o4��4�,,�D�0X	;f���&O�$a0W��1m����t�R���6�"2&��Hq
��˼�'��t
�IF=2L�
��t6CqRU�>��X�8!f�R�O{�
����T��������-��@|}�I
���9"�^����a��Jj`y��~}��=I0$�n���K�'�7��[7��3rbp��\t�
�����j͚$X٭գ��;Jy
���V	l]�Gxt�����ͷpux�XѾ�h*Sݱ�dݬ)�j9�g`����Q�ȭ9|�J�^�CzLR�]iD�Ck>�%�s��dv:��Fg9E��Y�EI�m`�^��h�j6�2iY�V�x�`2�j�m�B#�.��Nk��n���*��TDjgK �Vʝ"w"l��ٴBF�P��jU�4*CR�[����R
G����o���gg��i��#��
4&��
lC�Ӎβɚp�՟���a"^�?}�yov~27+�uyK/%��;����@�;�SUPB�y�zt�o�J��;SH�@��(��7�x��`i�ݗr���N�7�~8���Q���d�Z��,2ɻM����"݋�X��a��I��Cu��MoY6��$�w���İc�n6[�|�86�ϓ�ytW~3e��Q2U7�w��{��û�$���>�7�~�:Q��
������A"�į�B¹p�'N�?�ֳ��9N��8b�]G�ԭ�B�Gv9_��ϮC�̶@�9I��,����W?�����xTk+=��'�
|��o&�}R��;i�ތ^�>ɽi�'��¹�-�4�5ZRj�e�Rhn� ��V��/+I^)��bV��J
[�5����D]�/EQ�u���і%�+�f�V��E*`Q5�+o���[�T���1�1u�c^E5�1��q��Ug�`H�q�߆?��܃������n�L�#7M�n`�7p�T,�^�ю����'�i�C�|/�u�ޤ�j]�ol
`�U���z�
+�O��T^YY�o����f�_ �&��D?��zH�)�y�W��Cj�����k"��;v���@W���{ܪW�!��Wx��
s`��$�{��8o��|�:hxǘ;�>5�V������qGy��ѬZ�3�J���G����|�І��G0�Rx���@*I�K�iN��_sfnc������E'aH/#��m�	�|AK�bDA�_|�6h
����&R�Q���V��R�h%��0_M��5�O9�K��)�J�>�N�����W�[ݶBB?kK�*0:G�UYt�����(%�*�_�!�𩇮<t���"�؊�TQ����9�ӥߵ��j�~�A�:�ӝ:�x�gY���Z��W��/�����śs떱]�ᕗ9�Xym8h;�W)߁|��H)�@�&��!�z�/{�&�=tI����7Ӡ�lR�ޙ��>G��V��Q�yֶ��[R�?X'/uâ��-CY�92��3�?PS-Rx�痢u��WCcN�+�2Yp�J�:e"7r��=[�aF�ܳ�OnZ�U�elaB�� ����<���JPFA,installation/framework/database/fixmysql.php_�!���YmW�F��bJ��N��K�m�J�4�����	$g,��6�F�H8n���Ͻ3�c'�~*9�F���}{�O���G��8>{~�L�˩&)�(�)u!�Dg�DE��b�1���*���ɝ2"*�,U,�q�V��߻Y�\�T���.�]y�����D�H�"����i����ww�����?gI4թ4�P��F�J�C�㴌�V�D*3D��ٕx�2U�T�Wc����*�u0P)�nn�j�d*�o�z���O�D���ln��LJq��,�X�c�n�0���ln�7���H`�R�\`$�T���\%E,�ڔ��)�l2�a;e���4�]��H20z��i���,��ϋ��&"�Y�"8�9��*����TA8�,U�hQ�hB����
J�u[�".��bD���#q����4:z�J���ť+��.��y��+L�.Ĥ�3&�5
���@+�� �	={��w�oo����,^�����nU!d��NU��E_l�:�)��%���(��gJf�](NJ�n�VEF�3�ȓ\Q�X��V(�����Jq
'��|z><9�D�������b.d=�7Y�(^��jF�s�{[�_u%.^��:�A���$�B����!���yRN�k�C�#��o
B�C�J��N�&))B�z�b�θ�5�YkA1�����8����F$��� ��>��2aؖ�4y��	�^@���g�.d�-s 1�DWMJ����:k���?�4�pXg�g��-I҉I@3T�6�t��u7�<H!q�|�&m7�5c�����H9���u�B�6GHB�qD�iR?�(C�Ɯ��P�.�H�&ƑY^.��p�@�2�l�l��l'��;�V�����Ks���B�h*��I/�p�"�ig�œ��R�V�F�*SZ�̽k��J)R�B� ��wТQn*���]����<=:
f�NPNH��6���zK!�&0�S���KB��<����J;���yk.M��w���r�c��*\� ���V)��j��cZ+�,��E�X�r�G���̞Tѱ-�Y��H�<Σ����ي*EF�O�q̕��#g�䶲U�6���FF��]
4��l�l�t�D�I��ِ`�5��$T��'��
0�M"	��v1z�x ��<U�2Z�7G.�&:��[�:�C�&&b�v�lp�����#��eQ�7�Y
���]�	,�O�HG;9&PH81�oU�-�O/ς������L�;��R��\碜KclD#Km��77��HܝT�M$����8\����쏁xȆ�O+�@lC�Sq(&HT���@3�1�c��\��G�DN2�0��C8[�h��}
s��`��vbZdE�Tc��������8<d�BC�|��ϡ�8����Ȇ&�I�eWT#��X\�����`�T�-�E��9�znr�4u��'�D���of���a����ͣa��>˾;��7|pz�6�R��
]��p���H�V�sӮt�����k]\ˌ�P���O]qE���
�Vh-����``#-��u�������}���ʢR�|J� m��W���7.�zІ/$�Q�r�&�Z�25�����iU=�FYf\�(sߡ	�J;tZj�	!��٘ۡ������G���#qp��emF�(�o��������+
B���/'�Bt�A�%�FCB�a������!}x�:�����zG����O^��|������U����kDk�ѝ~�K��7�2�:'��O��S�C)6�c���5��$ѓ^o��W�;_ܼ��������ݽ�#�ʿ���a����l�Ƕ!�O����Ն��X�mv�
{�]��>@��&ai�~_>�v��X��
)�3Fh��Hy��ˏ���ʫ��jw���?�аw3������_���o��6�#
s�AWd���V#�,-|���j�]�?]��|�˝�'��L�,6��2���4�x,�����VD�AbY�?���(-X-�s���Q\����*�=�w�|��&x�-l)�����-R�������sZ��Ig�s�ڶE+ʴ�3�0�/c�ߋć�҅.�{w�1W�Ƅ�y��I���Z.#�(���f7U�_p
��<�DS��j�I��I���y�6 ��=|XW9�D�#��J�\��
��M	3t=�JnK������%�jP����z�R��_'{S��k�i[���:������nN�c���[˄���)-�����>8Ҋ[���<���Y��|
:\�z�n�k�y��C�}�k�a�H��wR�N��6�Z�z����Qп,s3cur�Ie�h�혩J��W��Pe�K;
��vlZ�����y�O�0S��.kRI�[�Dom�c[�����'����V��.XӒL����EO�@���Do�^������/�;��t�A
d%;���9���u��KD�( ��92���Ѵ��E�,���=��G6?�b�����������:�����#���,�*R�sm�:7�{�V��O�)Ę���o�9�P�`��(��/>#M�h�Z����n�]2�s��	|�}�0�c��>y _!�l�P2�dd7�]�`�h �l"�ם�)0�)GpD#�V>ڶ�
Ꙝe�or���8�x�e;����!���l*W�q�����h_s��)5�珜�K����Z���v���]c�S��؞JC=�ݽ �TAoZ�@���}��2��M��[u�Ӹ��iMAћ���$��JpX�c�������헚��S��^ڦ��Γջ��S�67D��E��w�t�6&���d�G�;$�X��ͺ��cMY���'k,���l-YZ߷l�̖��ud�
k}�#R�zlzE�\���J��ev[��Ľ喺�8��jm�r��H���lDA�)���\uRB-c��u�݂߱�U���ƛ6"��v����*l�҄�^���8X�!a�D�f��*Ki&�.+�w�^Qp�$K+�v�u/��I�w�+-�݀��^�*(T
-�e#e��U�:��!_ȷ�4~l ����6Մ�H+�Z�l�U��"�4ۄM1
<�ވ�/*�e��Qu�UK˯�O�^��Q�A�=����P�e�mi��?JPF?*installation/framework/database/driver.phpy$����={�Ƒ۟b�%�P��&i*GvII|�+��ܝ�S!r)�%+M���k_���%�q���F���������~�`~:�y������v�|�p_m��S���֪�U]�I����e:�մ(�q2>[�UR�O�s]�q��ZO���9�8Q_���<9(���n�>��ɉV
>8I5=��2=9�ծ�m0~t���6>���_ԓt|ZdI���T{��eŰEVT���Q=!XY:�y��z�B}�s]&�z�8�ꑼ<�e���H��2@	��ܼ9��4דA�h��/v�C�`����MC��ӴR�4�j�\�q��I��ωVӲ��H�E1˒[���F�������>���G��\�@�E9��qR>�H=�Ǜ^g����C5O�Z�h/��a��Nt����kuQ�g4�L'ev	h�g0e@�j1>şs]�yY��H�J�@�݄�d�	]���K��8r?�a@�d�����v��ݼ��n ��U]�J��y�@�\��-q��y�f�qX����葉e��qQ�X��r�r�b<�u�d�Q�ʋ��wnޘ�H6V�E>&�O���|^����?������Z�y^I�MӐ�W�c��d��t}ZL�)`W���RoC����m=R=]���V۪��«�,�9��fQ�Rf��r�����L<h���E�
��'�y��^~��e���%4O�����Nu��fr}$�8���[���晞鼮:X�x�`������<z���g;�������û�}��ǰ4-��<)���<��ɩ��4atr	|0Ka)TfzGJo�l��e�&�Y?��ћ#��_��,���b�:q����Wa����1��u��%H�6=�ZI>F��BI�������W~/�:r��2�	u�-�`�!���ց�
�E�wd��H�=�\ژp��A��|mS�羊AFyu��,f��;����GDKa`�V������u��@���N
���/�
���8��jX�����E�:r���4(����	�3�4���A�X��W��Q�%�?���
�^?�N:��Ҁ����	,��0�锛�Ņ��Y�)�EY´�L!���LV1v"	y�2Q����)��\�1�4>MP.�rP
բ���hYY]ӊufB��V�!�:��#�O�`�H�i6a5��N�ziH�
�y�������z8U	ء�I�!k��vI��
ŋ�����l��&�6<Z�d?�t������4��q�;���7&"Z[��Ұb�Gc0Y�G��Cd��w���3��,@V�A\�l��^ ��&�Nfs�w�y��2C{X�}� �X�<�dC��)�:O20�L�L�<:P��Wt��k��N+M6!t	�]W��_���iċ9Z��&�i��Z%�z�>�O��Y�����$k?�t�@���������ᗟ=���-��2�G$y6�����}+�G1�5�]Er�p����^?���U�i��`2��p��y+T��yf�~�T�b��]4*����nU2s\`fS��S��ei	{��,J�{&�EhYO��hl��Û7�Ƚq�xH)�X��kr��B�d��=����Q
R`S�e߼��{���h5�����	m4���:�"5��֙}cUo�
*-e۰�B�h�8H�)�ll�H�s���ci�h���fD\ʋ�ؘ�Z�)P��A�,.]4����m���yq�iW���BO�[�0�O#i *9
O>�CO�I�nq���'C#���||���u�5��㧛�?�N���{�����2F��%Oߒ�,r�� ��"���E

ص�&5ieꋕ#N�~@*�@�[�1 �+���k��N(��X�YZ_��Y
��-�:�Vr0H�

�IJ��l��\�	鹋S���6/j��>$b��c�2y��UuY�`��%���o+	�<<q��]�-��1%ϝEKϣ���hn�GA�&H$wVs j�89e	�>��֞�:�j/-	��Cy>8:�{���Hm��n�R���u�
�n[�m�/�.l�0�/�����ܕ	V��9�E|��V��-�����9��E�}�L��V�l�e�S��K�`�V��`�~���4�*�r^T���f�>Pխm��8H%5@̠N�q�!���:�-�wW_�5���o���#�<K�z���N�c�#,Ї�X�ɰ��9D�CGS�}&Fޯy1��u���)�S�X&�����L%�0�+�֌��8(�U��V�%*uq��|���$���-��.�@P�@��|�ds��B�~�y�� V<9bb���X���!KJ3�1 ��~`�x_�(�s�h~P/D��:���+�q$SZ�Re	F��ː�$�-%q0Vb�=�m4B
6k�NxU����e��K&P�	����,��B<Ӯ���g$p��,r��@��3f���Yq�d�~͒[��,��z�NQ���V}�kFϳ�T��Har�(3���8�^(gc�����P��Y}ۧ��\��q3�c�,r`A����р]�@Y�Qwa4�f���C�ĩ��|�Hx�
8"�y�f�
��9�)B�¦0O��l6v��6#�*��7Nz�E/��6X�aiO
w��bΦ�"�ሥ��j��@�Vb0v�ֶ�l�v\#��jK����d8� A'���&�7s�����m�Dޒn�ɑw^�����������F|�ۢ�!��^طsl�����C�5�1"��6�wӇh��(D�~@�V[օ5��x��$Oj�C[�;w4g��m��N���:�ښM>T������1��V�%��ɥ��#�a���Q9Q޲z^P�-��7�g�r�67h3�%��!kܚ��$��<Ci�=j��|��(�ƭ��F�l$�D`��ŭߎ�\D�U-$��98��L���jfH=�_��=!;��=���X��04�vI���_K���4��v��4���t��y7�G�8�7�I�>Cs����LAو^3 C�9��`ؤǁ6v�7x����pk�(���˸]��^�vZ֮aL�34w�?�|�y�i�(E{Uc�y�i>I���� �Dt�	bTR�N�/L,����d���
�h)�{|x갴K;����J#>�Q�f+� �s��k@�x��QY{1)�s$J��P��r��7}�Z
���!�+��u�Q��K���6�_?����&.�m=Lڈ�#p#��0p�.������}Q���G,�n�����Ն���YŎ�}��?�S@5
��^���|�ܒ\�Q}�M�]$g�?a��!M�o�G2�,�S�
3ν1?p#'��1zهb�hxJvxm��#���a�S2��#�0�K͉�F��Ex�K*��*�KIMG*9��Yw�rz&�E�R��ج����n��1.[�&��6���h��%�zIyR�hOdž�ʓop���*����ۯ���;P��e����ln<b�D"8����5�X�\�������Q���h2�\qH�IMp_�}��2��_�(���',qz��=g�W��3��5;��a��FPx#��{��x�8Ƽ����:Ɍ�w;33����lrL��po�l=v�x[�z֓�˜p����dO��
�ئI�Ow�x����c�ʆw�e�%&L/��b\E��'�-C����8���
������
9G�"1�̾�����
����-���\�\���Ws�OL)�N�<�)�rs��yf�y�;��.��b�l�bb��(W��SoM�૰�\C��l�'� �2	��ys��u�9,е"	�rB��~H��=��J�'�b��
�ƺ-�q�V�A��ld_��Z�'�G�6u�aϷQ5���)��!�v��V3>mn7�k��WU��2���J#�;&h�3���i�N��a3�ӓ�p�L�5�� ;�!b��)�{�X��t�T�
&E�3�N�u/��V��$}��J�L,���+�9z����"'�ޚJY9���9��q�'�O�:��}��UO�^H���,��"X4Ce9ָ��{:�{R��Q�ӊ�*�jO���*v0,p�������#<�u���^#o��ƨ��{H�u��6_`���l���y�m8�$�2Gag�i��i�fe��D�M��#�#�)��R�T����l���	ȶϫ����0!I��b�<k�l�
C��_Ҍc7�5G���Y�Aj���Vٰ���i��:�D#���\��f��FR=�6�>��xv]��m��t��l�ٜR\@!�r)'-�zO�U��O����@������
:��ƮK�]�S|�(�d��͈�d�E>�ev�c��$E�g��G�����S�rHa�{�%�RFh(l3�
�O�oe�\.j�J��2�*�a�ua�� ����	�v��Lm��$��b#�3Wv��'�=	�=Lz���o���_����z+ݪ�to��9�Y������8���Ё�,y{�`���a��n��/]�S��B$Lv$�x�3E]h�VvS��@�LMv2p�?>��a��'o���O&��
��A��_?�N�c�Ý/��G��W�6
��)�҃����<hY�t��MP�b6�ZIJ?���W���${ֳ��f�K}x���>�맶�K�201w��m)���^�}��1Z�!��@��i��h��ae��ꬺW�`�BE��|�+��"(e�vE�R���dm�+�D��r���w�hnvuKǘ��\JJ�(Dz0vl��Gy���)��m=�`F�h�E�0��q�kQ<Y�Ț2(o�l�b�p���_QY�L��sN��x���t#�YJ��i�������D;_[�/ɤs9c]�8��'���kˡ��B��x�oJ������~g�ӟ�ء�\[�|��	��Ϝ0+�3�������Jm�!�Cd����kg<p����J��\�m"a>h$�bU�����p��)ȃ��d[P���]�Q���J5z�M�TS�L5f��3�u��bQ{9r�)
�~��3�	��cͫf ��C\?��'�bl��%��~�;5�r�F�U=jXk�l1�+r�c��\N�/a�}��s��c���5h/)w^a��UJ�L0e^����q�r�U��-D�M>�C�W�z/��	��ԙ��B���{$�7?J�0��'Ҁ�+�X#�+	��v�����،7����n
���ʈ�6-����?�ع��\�b0�{,E��ڧ	�}I�AܖZ�ӆ
�0�tI�\��8�IV��1����W��38�R���)!@=7��:�O=�����ۈ�k��Y0� E5#/։c��]XÂ�*�y��0[�o�� �8�#��K��1��?p����c�n�1y��j)C��*,�e�yd�7/�2..m�%��Y<Jq�E*��l�	\�4e����K�ʤ?����w�[��c���̋|���j�dIYY/��H�
����{��wN��Z��'9�%�%�c%zx�g�=����.��eNv$w{I�-J�}.s�/J�j'��(���`�-.���
^�l�M��l��q�[�X���AT1�x ��Q=�D�#̒�.Eܤ�2FH/�m�p�^�n3�u囹�k�6S6�������/v'�pq���褓��f<�g����
���j�e7��b�ϛ)��d�k2�#Ty��`��T�94!8��ᓃ���o���f�1�B�L�X���}��F7>�㳠n��)7���r���xW��D���ᬡ�b�v�� �]��(�b
�ݕ�����
+��H��#oVUE6fU��u#�J=��}�eʛ裘��#���{h��IC^QI�����wsC�p�IJ{{%�a������:�b��Q�Ch���Skɔ�P	��^���$;%^��a0�1o��j�q���Џ�㸹��ɗX���K9�+9#"0��T����	A�E�}?�2��4���Ƽ�zK��:��6���q�"�|��_�wш
m$Tak ���\h�R���ak�'O����)�̓��ҧ�Cݡ�#U��g�P��]�"�E����y*����`O��Z�13׌�����!�a;�XŇ�qy�]lR��N&��b�(*>�������r=m��(�Q��K�$r�=�{d���Q�e����-��A�r���ľ���Ku=��s��5DL7U��<�dr��ڢ������U�r��Kn
�^�qL��J0S�4�|��N#����W0�98R������6���f����GSnXcМr��(85k�{%�!\sA��+��郞���#��V,D��[�czZ+L�W?�Ű��g�v��9��i�ផ��q����=�_^��`��M3y�ߙl6?��$�f-ay�
!8Rޞ_`s�߉Q,����*6']\��}7;�7i�W;��b#���k�
�,��1�Q���S�f
��H��\%F����3
uc���׹6u��j��Z��4bZ������ݟѰ[������+%9�����4�
QO\�Sx���-��^V��.\����i"�S�=��9�[�����L�d�߉g�:f;o��!���pW��~m����G6���5�v�V{{>��M�R���Y�F�Y�é�;��O<�Y2�<�����TD�/H}brvۢ��^�f]�_�_��JK�*��)p�MLƍc��"q�j�f�]�-We�"Y����Ox ���䴆�"�'�������T�2�$�#z7SX�}3��VxE�=�_6�E���+�y�8X��̇��<��	��$96�+.���,펶WeZ��I�dL�	��t����v�4�s�q$��-8�,lQ��l.ۂ�GN~�Mߛ�F^H��űN��E;"��6���g_�d�&
{l���c!����؞�����E�t�I���+Q�K��&�v��L�\	K\�
T����U�vE��
B�l�\�H8	�u�Xp����	����4��7��r��J��&�Nrwҍi�S�c��×���o��՘O��~}sP���	��⸞c� �v�?�Me���z�4wGx�G��+֣��i{@��7֑�����~
tx��G��T�UK�`!�߈�o(������t���q5�=o���^���	���DK0X�\�2(��!'�vǨ(�ͤF�/|�B�s����`��ȉ"ʫ��Xj��ߗ>l�a��aD�V6HMZ��F�$��2s~9+��;���ȑvd)e�����V�ߺ�@ӡ��y��2����]:���6{����S�bN�q����.c9:{StR�"�9��v���
���
W�B���Y��B=��#-�8:��lsXHx�&��������w�� {�+�)sH'52��w��9s�>��s�lg �r�o�97���!����;��>�v%yF��c�X&�^7����nAĴ���~�_���O�~I@�#�k:�r����Hz[Nׄ��?���[�7�-E��͠S����䰓��7����[զ��ٚ@���=]�$���}���f����6=��o'tL"��tg�&��*+0�A�L�@y�D�T�L\K�3K��,Yq0��0k$8������t�	����,�dž�3J_�Th��e/{e�
q��qO~u��2��]b�<��q0��6����E�a2@��Տ���N��E�@���K�:=d�.�:󹊎mm3��O&Z�!VL��:�6nf�
����j�Ӥ���풊�B�{��[/O������<
�w��u�3��HX���5�7u��B�v�!�8S��\���R�@�~iz'`�T(� {����5�Ҟ���e#���d��"0̃8��b!�Whax�����R���p�����DMS�
0Z���:Q/��*��������9��*��$o�P��4.�
�0n����-�7>�~��ȿ�r�].��8��{� ���#Wp�g�}g{	����#
�|Cl���W.Č�-5t��ػ�xs[�� g�L�S��C0[���h��x|�:�� �Ş�(��
�@�)�q	k ���9h���&�A�Ҍ)����C�*&!������Y�ד�^��oWRY`B:8z�<bϡk��T.��A����Ѕأ���n���ai���5��F���{��id��'�x��!�����rGo%v���vr����[��|��q2#WG���Q4�.U�\a�Q0��W����,��j4���ǣb�/�x��^���Z���y�0:K�Gf�!A�
sNvpa�F>,��r��N��0�ݱ��!!�og7K���aɳ��-}�lW����{f�`�P��R�}��|��y��w8#a���{,���ye��휮��%��Ro�6��Z�o�n�,Yp!�)3)�B�r��3`i".�,���nN�)��y+��Q��C�n�y7;oϜL؍�$��y�ft�7{ə\�.�H�f�[���>Vt:)�=����(��{
{Cο:�.?	Y
�o��v(�{�2�&���7�<��T��ۙL�\�����Vps�	2ߙ{�$��}p�w�|�L�17��O;p��F����I��2�*�ݦ�S	���v��)�Ҫ�Ӂ���1b)B����um�)�a�H�����H�R���>�S�J�a�p9�$��慚!�w�hﺰ곝���*)#����W��G�v֍�p�"fwi�{-I���',��ۅ&�&�z��L���A��/��߹��,��>w�[��ҾS�뺘���Z���7Z��������M��p<�+��({_-^׋���9�����p���}狝�.�]:&�}���;��
�R�
��g?P�&��>>:ir�"fZ�^=�5���B(�Ǘ�>�{��y(�Z���
%��qQ��S4��}�>�Y�V���5�����?���X��sFW���eJ뮆H��u�n�6Ɩ�Wf�(
"�=�g��@Z/9�}�<�[���'��H�=w�x�쵄v�t�ed���ܡ1�J혔���0���W@�߉�o�ɱI���z�(a�!�i����1��f����VRe��@A5��;�7�#��I�yC	����;vo�!)a3!;3M���u]`�)J%�*�t�JPF@+installation/framework/database/restore.php7�q���=kw�Ʊ��_�vՂ����&��#یD':�%E�ۛ+���RDE(ZV���;3;�@IvNnrNd؝�ݝ��.�}����?�y]�Y�?�Mq>��L*)
YVYWI��r\$y%�Y!F�z���ϒ���Bƕ��ѭ�_K9��w�"��'g�<��uo�+)4�J$=g�m�\�*�g>u��g;;�|���+q��g�<.��-�ݖY�-�Y)�5��jB���X�%���^�����d9��_~�E��'`Hs@����'r��r҉�����w���
&��t_��'S�yd���ý����?�!�v����4��o����o�/�����!��8��I�`J�&?��\��dnA�����ӟ��G�3���N;�9t*�#�b��e2�� �Y*ſ����i,a}+��R��뿬�ǣ�*�q%�0�����U<�KyJ�!q��<k�=Э�D6�(`���0w��bS�73XAQ�`�&"�*1�g�k1����L���Y<���F�+�i����[7q�&�Uɰ'�%�-/��\.�}R��p��[؈����E���5�Z�1r�F<�g7r2@�{4�]qq	��0�q�X�T͠��y�<E�l��PVqQņ :L0�!.��u��7�%�t��DkB�I��ȁ1M�@?���E.n$�%�E��ÍP�wq^C����*،e�o�(an�DM�'I�:t�pv�N��L��E1�
�S	Ʋ�
p�eh��eA�vE���C<�tZ�
	�	�[㈦�a<��!���L�y)nƄ��>kq,>ؐ����؊eڶV4�<�n��<�g?6/��Nx�-��lj���4^�)}(�6�1p��!@BB�G$��� 5LQT�ay%�:f݂��,���X,K���q1�U�� *h�-�p���
[� 6�3�C3z-o
ʉ��^��Yl+IZ<�M�LOF�N�^<��{4*��.��*��,i��?K�3�E0��)���œ�Bs�lM�從+��).m��4���zy��7�Є�v;��ں�������^�w��x�n	ٌw
C��<2��a����Y��}��<�RA���`�p�D�}�i~UN�	�	�	���<�R���w+a���ČV��������պ���q�l�2��I
�f&S l�t�?��Ԕ�B��,������>N���ȅ�	���iH�����\t�F̠Mv`��(�~)i�-�P
I366�
�n�\�HB�)S�������F�I��2�����-D	6/B�E*o�N��lj��߶�kW<��>v�P�v�-�S�m���2��d
l��¹�fq���&N+��Z�X������u���a:�!ʿ��) 5kx���S��NZ��kSc�)�5h�18�����u�Izn�B��\�쇢/5��n���2�Yrԁ�����uZ�،fK+ߏU����-���5�:-�]��
��aM`a<��jɴ¤%��E����eE�pd"��C�V�"��t@�+4��zU�LO��Z���LzG�S�Xϥ�!C����kH�!>��6���5�_ߡ���+Y�4�b�Nw�%|5�0���7@]����	��[�]^��{�����ɳ|����(С� �'��e'Zğ�'`s��"��:g�//���@��f�5T����(���B�GT]�K��!��!���BX�X�vP5�P������1�����[f�n��b�O�,2\��,�[@]}w_R��

�n��L^���F���!7��������|�������v����l6�FmЌ���v��9�~�<:��W�F�D����Hд�At��ʖ�&���f��	Q�����w�jU�M|<�24�.$m+�"͕�2�*A��c{ˀO�8����8N��(���8
@U�غ��	���zC	#����c�ɩ�=,8&�.����f���2
Y-�T��ӡ�`��DPo-WQ7Z
l��``��Rj3TI4�ϡ#�F��
3�A�rg�&�T�5*1�m�.?Uϟ��������t0�����=:����OQO�:H	Fm���Q;�p�:4�nh}�`��)
�*F��6�/���[P�/�Ӥ(��]D�Qu�K%e�Y�5Q�,��6����7̹u��8c�a"�Ӱ7��_�r�g c�hh5}uώo�����+��'a���d�<Y��j��Rr��,����D��&�����roo�#p�1�&�!�?��N��b�oDť\�1L{ֺ�?�Z�LncE��u�X��+vH�����1&��2w�LV�V.(��tz@���1G�9ŜɆPs v�^m˒�3n:�j/6�`���h��\��z��<;Ci%;Q��c��0;������,��%�]G��0i�x�@A4�ۣx!yd��-Mҳ�q�F�x2�-q�ͭTk>��뙩�k?3�c�Ɂmg���Ze��h`�$hkD��J$�f�tH��nŴ�E�1��n�R�	"n�1��O�"��b2c]OR��J'O�lA/�(3��fA�,�7�_�fFC}�C�(��q�?�Ah����G=(��s�#V�lG�ц��[�$e��9�p�gi_��d�b�E\
�f���j:��倘�Q�yn�z���)�8-�oS�\�)��j���?e8���܀��EeBk�1��䧖���v�@_��t�u���u�
�6t�:�S'�1�놸J<5�t�V�X�mq�&d$��K�a����r��k�,e���R�W9W�@Lv�ȶxkubrʀ�S�&�/BXm� ��v���d-	��}�f�#�p�:���VJ4�iM��:��:}�����)`/a�&�\p�h�R�1QQy\˺	�ޙ��<�>�Pz�%�S���0�@g��g;D�n�F1AU`�$H�־O�����TMwW'��%;َ��)b-��Be�5C����b5!:B�@��Ph��m�����t��}�d��L-��)���&x���c��ݘ��1����N���ܽl�H*��̫�V;wX�0֠Ö^r2�R	��
~1���J����z��ֻL��í�@y.Zm�#�9o��7lt���G秃������4
"��9�DTM:y�+:�F�lC0]H���l
��#�m�HH��WbG<��'���D��hp����%��F�]eV(��̀
jo&�ה���g�x.�oZ�K$+�MЁ���s5;'>=v������HC��Vg��U��MvW�
�=�Aд��6Z�ĭ=<S�[�g�&ugp��R{sI�R*t�r�5*�$M�zh@UJ*8R؉^xϫ�5Q��ي�Į��`��ݦ�sE�j��5���h1���1�?ɂB���͎8�řD���!���J�y�	W�B�VcK)Y��02 :�h=>}���Tի���o��E����n!
��;���o�9���-q��I�hM���s6,��_?��ɩ��q�ӥ�Գ.�r�r	xa38a��<'Owv��D��nZ�u�(WΔ�MD.4���b�PJ��ϡcu_;�֛�R`{�[B�v�@
�}���L]�����I\�:L�V�������~[�F��,���*Ȓ@ߙ�*�f�O�ٿ�0�����I�⼬�Gд��t-�'����c�����U,�����
,�Nb���2M@�ҟxYe(����#�eE���-�^�_�8��h��8��F�j����#��2=�����u����U5��q-��
�;�.�Xm�А�Ua�X:�%~ʖ`rQ!9�(���Nw�!&�1K&+;�k�0�'9��<��5
=�
i�z�–��7EG:*c�E�Pv��:�5j�0�������3uv
$�X���5�6�LI��ܪfm���'*&��4^�+���EE�*@r�@�w�s�p�DQ���j��9r���g)�g&�vW��l)m��{F�,�0�|FId�.M�<pA2�Q�k������<"��I��W>d������iH�0�a�:�n�f*B�&�d�ۦ�*�eu��of>������w���b��2l��s`U���'��~n�Ŕ����$GV���1�Yh�7�c9�0+��4���� Wu�$�cx��t���j4�	�V-x�`�'��"�S�Ѥv���	N�ٚ�����;�O��qq7�"{�	��!8��K/DZ�{�09�
m�X� ��,�>t ���P��j�@��)¾�/Ꮵ)��2��*-��̃��O�	�#�d�5�i���*�%�"�	V��_sv4+��u��V��i�GU�����寭*������U����V[��_
['x�s��a��M���2{`�'�D~���?��>�~��I���No29�
T�|^L/D��hk�U��݀�zb�/]R�%jhS��ǝg˨Ǵu5|�G��S+bJ5�f�<��ƫ
e��	�/]��������r?����R;��Vs���l�����Ɔ�g����V'��8�6܉희l�yE��Q����(���A�{����%��N�c�N����#F �	]ϒ��8/�x$6��m+m6�G���S�N>�-�XL&��pU_�hkJ��?=����Pnl+�\�p*I���x&4ƴ�Cn��C�շ��b���Ei"\���{�O�c��'G/��
R@�R�
��|�ߚC�&3QP�1�W��V�Q�s;dc}��A���u�QM����*p����7\B�G����q���%氿�08���4���)��z=�I��z�q��)����]L����߿;ysp8p�*��I]�'s��D%j4�E��k�\2.���C@Եv���7��9I��B>8e͵�8Գ��D]ڤq(�j7��7x����O~���R���E��Z"���88�{��`���78��������gY�S�(��v��6�b��2Ǻ�f�v�m^�`i?��70��w����U�M�z+3#��<�gA�
Ώ߼��.}~�d[X����:��SgC��d�{��@�I�in8��	�Su���
�*P�߹��	5=j*��2#\�,s-R�����j���qF��R�tP3�~���Y�P
+���@mt�<.Kp�'���6�C!�ɧU3�-�ʹM���\�@bc��w�_�*���P��L��-�����|&��@w����.P���P\r-okPV�uRdQ�`V��`�a���V>
�;���~���� �8@�Կ��]	�������n�ˆC+�A�}�h��
x�U��S.<����Ts�]pf�{��4�����4^��ǫ�sDpخcL�����{�H��\��LI�;,�Ă�P߇��@R>��m�Lt,�x;VO��X�[Ŵu^��k�K�Z���\%<8���f�N�dY�x��1�{��'��\6i�cץ?�@q"�Xo�h�3n513U�*��Vw]�����3ch�����mnHP�4$�����Jf+�i�p�u��-��]c
	F���n���\ISz�pE,.��N���
�U�@�yO
��"����xb�t3�I ԮjJ1+�R�h�t^]�__|�|����n�ﺩ�ns�Б{T�\�w@��\vl�7u~��T�ZQ����W��_�fEϢ�|�����X�k	��pӏ\�W�bL�i)D�t�=WU9�~AG�O���zVx@Q�_��լ;��
��@"Iu���4Z)�Z��떊�*|�ί�_P�[/��m��w�@��	^\)��|\L��2�{\�O���(��|\��›&�l5`(�1m�?��D�5�(�`�>W�APA~�eb��k�@"ЉG.�Q��8fA�z�D�_���o�n�Ӆ�D�)��2'qK��
������Չ޽������YiD��Xꕒ�#
�(����΋��-�6�oO9�����?����g$��`H�
A@<d��y����>��=ᜦhk]o��/J��S�k:���5_ݪ��u��!�T�T�� ��q�f78�潤Si�y�kF���0+��Z�^�`~�^hȡT�0r��xGsM�06�.y�6���C����Vx
�Q�+/��/r��������H�o-@�J'�
]�I�:���fلB��&[U�U�5Xg@fF#�/Z��D9��h�x�W,c��1�����@j��v|
Q�mj(�Њ���^�-q@5�v�G �<O�^I��W[==6�tvF�����н�7�����f�`��ʜ���V���yhZ�����UV��l�G�J^�H��bKq<��d�r�G��8��N�3��8)E�`�P>q�$_#".P!B
�
=�&����>���lAj*�j�ND�9��o{�����ȿl-Ω�t�zN���q�|��]1��W1:�4�h��ț��y�\]��#~Ì��c&�;w��D�	uo��w�>�{��+oD��6/���q@��g��D�l�ڥ>��Ȭ�������U�!~��[�KƧ�舄/an���o���y��"��ޜ����;��
X��y��h��O<�B���%i6�Ȅ�`�PW֌�h�Й��#9����E��o0t�hfy�'�s���/��m�K���7__�� M������O�
:�ās �^|�\�ӽ�Y���x=V��x/���[���@��nè�q��=N��?���wT�y���P|+�~����O�E�j�Z��]l$�O/�B:��rGUOB�ĭ�qS�_�w��ᛑ[w	kP&	X��n��M++�M�̩p
��9'�6["T4X��z�s���2r
�j{�vj��E��o�<3�`�E�@�|4Bs��w��������ߓ���D8��yv�P��ӝgQIu\<!�*☈��k'L�x�����|�T�:�즃0{ؿ��k�	iF�
�Z�͍�k�K�-C�
ua-P�&�V�㉾*a���<I���
��L��ʍ�MO�Q��[���߱�[�}Ư��po$@�G���)�re�;�v�C����7�'�����>/�O�q��QŌ|�X�P� �=��3�qC�%��҈U�>p?<O�U�O&X�m���]�ݚS\���&�a����*tA)5d���ߌr�!w(�`M������ӼV��}`���jزv�GN�8�D)#���v{��C������	f�yO���)q��a�!�&c�ju�?/CS����O��t��on�'�t���<���jX8.ti_��r��ҮL�Ո���Sh
�����}�����?�w�|sCc�����W":H����̰�p�����%�~c���R��#-;�9
�����w�l�Y��T���fm;M?�����&�2��5\Fl��G��,�ӝ���&˶(M��U�?�7t�j�Q<&˱vwˮ�/Q�7�&t�hg�oR��\`�����Q0�Ug��� ��Q��~MB̖�X�s)��e�u¥����8�8��ٳ����QDJ��r�~�F��`�v���K�
�-D������ۀ>F1_��K�jG����U�9�R�iF�RI�V�Y����l�	�X��w��O���_�Ϡ��C�l"h�9��*]���JPF>)installation/framework/database/query.php9����=�s�6�?���9REN��yI�{��&�9rj˽׹�x(��P�BRv|w���~$�J��6�\g�D$��],��,�˽����_����H<��Y�K��,OR/�Xd~.s1KR1������R^�L���r����r��E�'�I��8�.�w%��W��g~��Mëy.��~��G�����߈q�ϓ���_��͒e���L�1N�`E�/��_��2���7�)�'��L3�뛾�"  ��{{����\�r�������a� �n��f�dfbFR,�[�'q�1�H1K��ȁ�?$�"�o8�mѯ�&��o�C����׏��R��U�K�����/�c`�O'b饹Hf���q�M���0{�R�$�[�?�^���[�2�g���R�p�&�!�&SC @;#�5ŗW2��H.dS	�
�iO#�}���,O�J���[�l�(z�KQ'Ҡ����}��eBc�觖���W�J
�������1<�ro�e��S��k��a_{�	�J��0ɥ�+�)&�U�p5*ۉj+�`�14	m�!~4a�H����@�0Ȣ���dV:.�2�N�9ÂeGd,#��#�/TO
���V�O����ה��m��_P�������9����
�V���j	��X�"��J��[�b�bX��`�k���8OPŀ�ʉ��h�����S<1�%��*�5����浫h
g��VS�������=���+���ͫ7���1mq�tu��p��@I���US랃l���(��!M�C/��
���!MJV0�l��E��1(��_fy��+)^#�չ�N�23
)�'�.Y�ʗŔXD�r!ӫ�!����Qa��5�ja�K�-U��b¬��\"�6]�\��LK��VC���d>O�[��`��\
4B@���$�u����L͘�I�d�f�%q���]�@�Mj�AiI6a�)��oų�b���X5�/Ŀ�-JI�&�z������
��4���-��=p~h�i�>���0
P�����9'������~.��(U��-Q��j�@��w+�dð��޻�PΞ��zԮn�k
HI��y�Й�ޫ�T��<�j���6k��(Z��VmO�z&zز��
6nu���\�l�@�G��
Zt,�A�e��]��h6�]��
�7K�
J��d՚��b���"M��y�;Dt�Ao��.S
�@߼��S
��Z� uW
����b�j�p���V�M�����[JI��l��7Ү�[��Jp���й�V���5j;l�`+�~�1�W�bq<>�M@�@[F�^]Oǐ����]�T��1I�'�` �����Ay+0P��O�Z��PFA���Xw��o7[^uC��,�K�0��Nq�hyu�Mz�ͮآa0�'�f,��7�Z,���2k�Es��G5Ϸ�wx�U���"2WlT/�N��v]Pm����t�:�P�H�щA�txԀ����3~��"��TzZs�I�?��0'���|���w��m��=����/}0�A)��_�����	�d�>������q
>�ç��p2V,c��!���>۫�>�����4,T4>;�ԎJXi�V(�^n��$��wp�%.h�hG���灴)Kb��S��(�4�_��Z�a�{��τ��j>�E��2]��=]�
�.ncЋ��ʟ|pp Η��Q��QƯ��+�''��!����	�F?+(�IT��N�}jD�φ���
�:B��6P��o��V5�6���A�,"��Z�l3���Xs�[?���eP��ɛhd&�U����La���Lpj��bG�����k<��c�����P�x�\�
�Ҿ�����z��>-��΂��C�žˑ�9�����\8u$�H3.ݓ�6�ک�F6�
|���)4j�K�n��>���~F�>^	
8���2j���"����W2����k;���5�`FE骀�"Zl����ճp�{�!EI��t%U��iW+����h5~+øF�0ШT#��l	"�}{��ON0��
RJ� �1��Ͻ\�&+�X�['9R���
��u�}4�S>�[ơ����{�@��[t��ɍ��/�����lQ���RFgȎ��w%��?YX�]�|���� �z3NP�P͸]{�J�U��p��;�p���!��y�Y�@s=���Ͻ�H�����F4�Y&-Q��av���ڡ���y�p%`V1pG�����j���?P��C/Ü��Ƀv��(�V��\���n�+�[ T���W��eq�[ĈE�-�%ˇ�! ��.$��`d�0�e�m�:!��B)�;�h�L={jI$ƫ�|NT��l[�]D�
w�H�W��A�f!��*��m.-06�1`qB������Dy�g���πq�ө�o$&#��)_}��䡞ፃ9n�x/�q�7��8�4Gw�&���w�0	�n��Nk�*
%��;}�v�)�MD+a��s�jxvy2������g**�?]�ai�=�?�M���[W���c�'�q��XaEq�����reh�π�έ�ev��8]��C05��֮��|!���8WZ�RÛy5�܍��A]�~[\���,)ܥD��?�XG�xc�7�uM�v;xU����W��Lwg#x����o�:f%=��=�j�J歵,��T��=�i�����Z{Q(�ګ�k��a�j�Z���i�ݪ��vw�m�j�z~��';[Վ�,�Z��Sә��aw�E�S��?A�GXԲ|�3_��9�n҉�Z�c�vM`_Ӎ�g[]�%d]%օ`u�4w��] 7SNkD�&f�y�F�֛��ƒElu`ٓb3�&YEhϣ�r����i-�ց3F�����qm�j���J��;mnoS�M���ڒ؇)�=�L��2��2��;�%��i'�܄6?����C~���z�	3
��Ad�1�D�3ڑ��‡�(r�؍{c��F����+j�&J��"sŻz��9<'�1�_�(v�"�)K�Un�D�;��k��/4^3�R�����h@�B��C�u���\����Π�'�8��R�-�nE�6*o���Rgg���rr�zt>�~��&�ZuxX�MԹ=s�a����*�I�d�
��:��"T�Hэ����Xe�f�Wb��I�nf�Er<�8!lsR�Pz>�su��@���EHC@�d:	B�ra��As��F�=���,s1)���

�f��Z���X6g�K���SP�h�V1
��/ɩt:vy4�_�G����ӳ���OÓ�ˣ��/~N�vM�P�����^�JS��#X-�7��+����2h�J�Ifѵҟ'ŊAH���
�w�Tr��/N�(_>G=/SI��u������x�&����S�IA|wP���)�����h2��#�6���z��J]�h�}�t���Ff��
W�|wm�(*Jm�	�V���{-v��j�Y�P�AzѪ�(�/��!$\���h)�3��u�T
[b<3����Է�J�E[.G:͹��?7H�G<0��7;Br�N�*<�(�j���{�$�$ࠏ�<A-@�R������'U6�#6k�Hۃ�X�"�U�5��OH"'P�P�"��_l+HW����d��6���]3�������,��e�(2���-CIo>�%Н��0�-C�/���T,þm�(m��"��|]��P"�����ME�f�G��y!P
���E��|�穀io�!��R�vIR��Μf3���!Mb��]**���)	s)���$�{�f1��(�i�VS�ray���E	��
�y�b�]m�t��0��o�"5�pˬQ���fX>"� ���v.�W�(J�N؀�*p*�*\wޏ�''��/~���|	[����U*�4vx��BF����EO��
.J��K�t-tl�3(R��x]ja�j�Ҿ[�\(���<�N�-d����)�X��%�vK	F��䎃�f�`+�
,������Sv�,�Sh��"�#Rmk��5�����3ڄ�9݅��k~/n���1��6h�f�"���OǺ�`k���܇���������nV1-�>�|O6ϓ���!�	�����nV3=-�~uzqo5�+��Z��]*��h��5�DQզK��*��=�3��Gٽ8�0v�y�p^�ڀ�Ο�O�Gk8��%�E�r�^��^��Z�N�0"��X�]�P��Dh�.�>����mQK`�8��/~�л%�[���(�+B4��?�_�g&u]���^�'n��s�m�j �>���v�te�u5�E�'c��Ar�ʪ�∫Y���hע��bkܦ�;viR��JqR݀9p~)a'�G��4�S�����E��՗
�/�$�����l\����L�pz<6�37�0�e�L��L��X`�/a��}|��k#
g&�η�>�x�k!)�6�m+a�_�$�[e�8y�a8�L�����=0k�g�L�a%r5��ڴWT�`���<�؟K\��������M���¸�/X��+�|��\��>U�F5�a�'��IX-k@�0
c�v��5q�	���|f�Bj�cU�k$�B@�V3*Ɗ�yakE]
�mW������p��j�YS�YS;ZS���7[T�`�xUmeT�ņI�]p��1$�~�"Lu��jK���,��}+oo��Y�X��a�c�!F���n}���+�ʫ��^?Tm����)O��J��P0YY�F74kNF�O���H�r�Y������nyv7�䣘�^�T,���(,ϭ��;�hL\������L�����1�o�A�����i��ae5��k9��g�x�����)�8���i��=���\���*�?h}(2�"f֊%{0c'�K�_}��(�`�Vˡ���ab���I�eVN��.�	���p�:�k� -y�gٶ�z4���O��h(��@���q
��O?�Ֆ�U�:CnF�y�Pj�<=;��9�E�\g�$�3�_S/���F��غϭ>�('S�>��W3�M��ݘ�	�8�I�Q��Ӌɶq�d�wř�ן��Pb�m#��f$�ն|�5z�I�T�%�XϬ�:���Î	�j��g�ۙW}w�Ź-b�Jՙ�r�"Ϻ{�x�.�E�o͘"�
_1I���;/@�@�A�9�4���T�D�D!^��!�T�����Od���F�/�b�x�Sޫ��
��F
���؂m�r�DNG�s@&I��G�X�WT�-��_仾�#
�������k�σ���&$�*¸����w�'8�mԓ���u��M[uqΠ�]Nw�^�\�5�tV���&�s�%ę��!�f6��k��Cxx�䂾,��!��\��~RJU���>"���fB����q��s�4/=m�vj)TF�F�$�7!���Ʒ�+a��_7%�_Q4�X�'&��4ᢣb��~S��5��o^|���t���㗯��^����Ʃ�32NK���S��6I��Ϭ��N`��D��Y�]�[ѧ������z�9�^ѕ_��WԕJ�,����ןؽ`�
}`x��,��륭M���cc	���V1B�t�����?n�P)@Ԛ27e�V� ��Ւ*�rH�Z@���lL�)r=��d.ШS�tx%�խ6u����Bn�M�wT&᭭w+KR��t��I� ��?�
j���Iȥ���(.�,���!O9���<%nO�/=L�E�'�������U�&+2��ų�y�A�^�WT�	68�N�u��x�ޑ�.AٽL1����C��v���*-.ހy�	_���;R!c\b��`���òR���z�+l����-����H���uٗ3O�Y1�v�q�eT�����ހ��g�;�Zf˨`�c�����m���V�<��֥ڮO=�����]O�"B��c���}��)o,[w)�����]�K�!����ۛ8
ۦY�W��5ZĞw,L�����w3�~vǭvb�X
�?cGUlQ͟m���S\(h_$z[_�M��?�J����ϻ�Ia�]��O�|�ܼ����_k��T��8���7��r∵O=UTw�6)b
N��Ct�]h�N��N�q
���Q)��8�mWjj�B�_P/���{K.IQf��8��h+�R���q�
d��j�r�:ei
�vRc>�RxM�"�3_�/�0�Im&MS�]��Ru����X���gZ�k��$���(��w��b�����A ��[0�~�8� ���|;xt �f�<_D:�Q�_��..՗�L�jɗ}�O�� k�Ȼ2.��u�7���(C�S�eW|C#W�h֔c��;Lp���+5<_�^~O�Ѿ�v�5�]5FZ�uYX�'1�� �E�����f�dz�W��J��$��M	P<��M��9�Cg��D����(	�r/���`��4u15�-���[�{�{�H�(��u��b�B��)f�����=ta��Jk�:�`X����
D�Pf�7�T�p�sG!�5���5sj���T�rN���JPFB-installation/framework/database/query/pdo.php|��uQ]oA|�_1�A�/��R%H[J�������*�ݕw����&�V�d���㫏�	�l4*0�b�\}�ۆM"0��Y%��fv�Q)}h��t��T�U�Ł�R�>!\}�|�֋F�����`oh�iz6�&��O�V�+���I9//�6��VE|��V���>bvָK��e�&3�r�Kr��b�V�ݩ��|��r��X�g�ٌmc"v�����.)�$ք�#�����Uo��q1�8~�<d��d�r���} 'F��	�T�8����_b�[�	~�O�i&\���M'������ڒb���|���c��M��x �;���'�L���/|yh�{\�����q#/���S,nUR��47�=%r��N�U�JPFI4installation/framework/database/query/preparable.php�����V]o�F|�~�0ېe�A�֨�0��C��ʼn\��w��Q���w�HJ���
_(������ҿ�^fe���GG4���A�4͘�L�}�Nm
���2��:���*I�$�K��8V�S��i��<S�9a�v���9$�E�]�`"Xh�k�-�N/�@��_���p������-��$����瀮h�mi��z:is܄4��u��K��'�f�N�4�fؠ�fs����}�.��Z1���4�9S�֔X�6x�Lsg
齵E�~�	�C������g:�h��7�t_���K�nU@Fߧ�I�dx��O�T.��3�@��M��%�dZY�OrV._�{FQ����L�%��tv���oRH��K5���J5S�C�nM��f�>6`4WI�����fڤ'��2�5`Yq��[т�;[��Ӄ��\�N3�	hq�x�>JK�d
a^:�R�}��'�ط�tᵁ��f0lW���v�%��,��n�Ш�o	����({�rqh�B�
��b(3PV97�LZi(0�Q<�?܀0<Y�	8���W�h�{�;.�4b7��� �a�V��s7�ڝ�*��h�=V�ʫhKS�@�Ŕ"B��Y|������yM�G��,�p���k4C$[s�5��c�������+���vs��GBH���3@xݧY(A=�Mi|�˪П�p��گu貪W��堳�)1;�,��,�4y�*����e~lz�CO��{-�_鐵-�vO8�0�_��Ԭy�S}�.[���A���u�]Z��ԵU�hr<����,��]��L�ֵ�J;�Xi/ߛ����i�a�"a�]��Tdw_�8����d��`[/v|��S9C����f��Y�'#c����2I�f��;�
qۧ��Q��V�s�kruv6=�n�~�>�7Ӱ���y������v=4�GY���1K��T���Q��ÎS4������i�9��߄[��;j��z8L[l�9^ψ��FEb���U��ҩ��JPFD/installation/framework/database/query/mysql.php����uQ]o1|�_1<5���U< ���FQh��jxF>�^Ίc�8U�w���T�v����~����F�#��z�	�
!�H���"*k�W.������Ax٨#HO"R��C�'*'���/OV[�H2�OŎ��oҺΫ]q���a>�����%�E��׼P�������m�z.�$���W��X�!/4ڒ�=
��C�u9[�l���Y�UT+C���[q�\.��aT���9�m�j�	�AZ�2\+B���#�b�A�7x`r��0����;L[>��ޑ�Z/	w"�>a���ӿĸn�p�G�U�i"\��ȫ#_�H�a��?��$��xm��q���M��|O�=�M8I$�9}I��cK�âU�Rf�+>XxA�Ԣ�Q�"P���w
��T��T����~JPFE0installation/framework/database/query/mysqli.phpBS
���V[O#7~���S	)	
!��-��E�].ےU�c�!.��`{B�.�}�/�LҀ��4�}����c~���W���������`:G��!�N�.�r#+�60c����>���
2�fK�<"��&�R�v���û���S��d� 1�q]-�|�;8kV=��Fƣ�{��|���e�(��Օ���p��q�D�R�ci=���7��
S��\����z?JIQ�.v:sY��u�'_��O'ݾ7{���U��si!�
�`K�tL����8*�g��~��Ne+���vm<z7��
K*Sm8�5s��eɇ-g�BŌ���2�Λ
4rA�-��ytp�̨%�m�2�����+4�2z!}ilr�_/�0V-���Nk��,��т�%L>1�f�b0�^�'%�vX��C�E����m�\�B�O��:�w�y��� K���d���bT�ύ]+�5�;e�{��E[�x'f�����Lۀ���Z��Ph!�%0x
�2E=$(gj4g|�<���t��D(
B�@�b�"Pp��:��V�EY3�@�!Fm���j"W��!�X
:c�"��Ƣ�v�7�*ƺ���|�nb�mv�at�Y���,E�u��x#�8Z�Ջ�
R����1����D��z)�����I�	FY*���puy}9�ﰱBw7�N�셴����e�}i	�L��FUI���f���C}ʵ��JV��c�T�v�2Zxyä��I?|�k�[{���A���E�+�h���=#�5d4d��_�4p�ҖS����Z潔Ѡ�1	_�6i�ٚ�r�� ��no�&�?�z�G�������o�,#��V�(�K� z�v�������}C���Q Y���*�d�"�I�P�; )��B[PwH�խ{ɿ���d|sD�� 聡��uUiC@�51�Yz��(�ˆ
�n4P�|�%`$�E[`�3��ϚfR.�u�F�wc��&��.��M��g8m?|�G���+�g�s�R����ՠ��j��8)\��XRv�z�I�5��ys/���F��0����JPFE0installation/framework/database/query/sqlite.phpw����Xmo7�,��) DR ˊ8���q��W�vc��\�KY<�.�$׊p��̐��ޚ8'��D���?��}��e^���˟�&s	V9	FZ��pJ`�J3m`*�Ǫa��z�#��)L�p�(�T��@Q��ʝ�4� 1?��+$<(�k�.�F=�\4�zI�h4�����5\�d�3a��!�E��V��ʴ��ZƕK�W�YX����B��m5�
�
���X��Ф
0x��];c2Wf*���%$�pB�?�03:�N���y&~�[<�����j��h��nJY�#*�Hx/J��,�a$�_�L�Ɓ��2$���HSi�3��Y�B�Gvx�Ia�%�m1(�1[%s�_J�K��o�b��U�5��vE���f	o*���x������@��p�Gڄ�‰�����=e�F~r�H77A�e&sY�͝+�+����ƭ��3�i���"][D	aܒ��*s`�S��,�T�� �aC��h�לL�fy�^��g3��yƒ~���Q.��"�0�B��TW}=����2s�I�_��\��)���^?�^��N�i)"0�\�+�e
�E/�e0�A;�C�Q�PB	��̣H#�N�@~�IEug��,��\S��咈9B{*1�jݛ�l�w�b�9s" .q�ڻ3���,ra*G�Yg�C�:��C��5���|�)��z��	FΤ�R����V"���3���bAu�cT�;�i� �x��B��׭bLD�^t�b���v,��.�-�[@d���S��JY���}�]���*�T0���R[�(���@-
%#��V�vF61�N�uc�,k3/�:�L��ӥ.��1T)hD�kl3Y<�:���R��5jW�6�R�<�����$�zC��.Dv`��t\�A��u�a�����t�)`wQ��K������2��d��=q%�(��̪"��a����Is�)6h�V�^��~�}{3ߞ8��n�a�8�F�MÛJ�o����
�Wc�9/�~E�=�,��Z!�-5��$2֎x0�V�l?8�Y[��>&:��/�H�јE�R�ų60dJ�*HrW��
)����VJ�=d�[W�_������X�n*��+�ی��"�u)w��z�଎Z��I�ů��j�	`etn#���=�=�zw4,��PN����kP���5��7#�@bP83�p—��a%��4��u
h�<���A8y�%q�3,+��!�/��~9n�~G<��v�p�.4��n:#7#j4ނ��ߚ�/�{��ĩ�n_"��m���j�����N��,����H�������y	�	{�-~�k��T7��!�%��T�n0�.������=g�_;�ά̰��꟤���uE�Oٹ{��hϔ�B�>C�J�������-�me1���T�
L+�M;��M�$�	)�{�4�����4�)a�2�@�zkv��C?�cu�L��D���~�FN�ND.i�J�5 �B�{]֞B�O?k��,������i~�.��F�^Y�C���(;t��c5��y �J��ڋ�*�g/�b��J���͠�-�X�j&��"HJ�ݸИh~"�E&\�e|[�_=V�>�ڲ�0Z���fS�j$D7�Z������H��t�`�p����"��M!�6�,ν�ʎΪ�`�[�.������b���b�@w��cW��G߰���Fa���Ἧ�X�fsi9ǹ�����L`���"zU��'}$4��Ʒ�{�`� �t�O��6�M:����?�T��x�41_��\&�A�������֑8�;v�S5�Q'����Mp]��Z�U�Y�4��q>R���G~���e\��*ôn:Z�>��r����P����W�W��M��P9�����:�%����w�~���ќ�Jz��~�G)��D<�G j;f������(���
��w��.��i�����OC�2�{j _|�e��1�5����s#�cMs�4��5<E�_]����N�)��a^/a���(�w�xЈ�ϧ�-����w��@s�W*��x5��B@?	.��D6ÚP���)c��h��w�:FV�뼎w�:a>F����W���g�Ho��b(�5M��G�
�ͱ��zݾ�}i�JPFG2installation/framework/database/query/pdomysql.php����uQ]o1|�_1<5��E*Hpi�(�M���gorV�ؾ�S�g�&<��3�ٝ}��׾�(���#lkBԉ(&D��"ʠ}��TBd�[���D"��Cy ��g�U��O�8��6�=�bO��No�.�}�p���l:}=�Mg�XkY;#"�Ƹ偺�k���\<�:i-�Ƭ�\ƒ,a�i*p[
1�u=�dx���'E�h�-��՗�n���W�LP�z�7�%�m�#v����t6	m�*�.�#G����/�aq��8�s5�n�0�j���=Y��	�� ��XY9�͌�q/B���e�W)S��Z�7�sHC"���>�Y��)�}p���ijE�����ι|l(t�7�(m����g��-�[�D%"���]�j@�Y�7��1]<?��JPFH3installation/framework/database/query/limitable.phph����U�n7}�~�ɐ%E�z���sq��y6(rVˊKny�*��̐�J�}���̙3g�?��5]5;?��V��7Wp�
B��c�΋��� ��"���Z�m�@x���ED����k�}�U�Ν3�rp��::���Ƽ']w�z�D���5���|���b�x	�Z6Έo����\2.�l��6��e�D���#\�E/|Hk:����}�^N�J2T��˳�RXk�j�a����r�|�J�h�c5Hu���6�8�t6
miU�w-D��s��'���ii@�-���xߡ%����D$>a7VN�JF��{脏��G�L�&r�B�wԽ��mn�4(�9m�����!Ɇ�}��i�&�)�i!�E�^�W"�� y�H��j� �7�
����R*�Z[5K�h16������U��KVQ3�}_����F��	�G�}%v�kNI�-I�r՝������33�ebc�z����\������k<#�w�
�h����Pah��
��
�K+"�ul2W�H3�PJ�,3�m)�N�9����2�vMmruF��L&�l����$��u��;�(��Ha�#~Z
C���e
��e�G�Aנ#�QF"�P"v��QO�v[���IA����9�a�%xV !?D=�7���d�
�ϲp����3*b����"V��?~�����aV�ue>�de~9i�$��E��&=��1��0����;�����O4���O��SM!u�����?z{ȟ���/�Qؽ��'LF�	�-�nO.��P�k�C$
�y5~kIP�S��cP�9��WH��֞��ϼ�}�!r�h�� �PM���Q����T}JPFF1installation/framework/database/restore/mysql.phpr��uP�N�0<㯘[ӈ��=!� �Q�Z"��{�X�bc;��q�����������ֲ,MR�U�X`����U 8�8���S6`gj.��w�U��x �z@�'�9V'E'�ȫ�&z�6�6��! 
EGL;8մS"����z��/���h���+�c��kzm<���6��-�u~�_V�(�#�5��:؞�9?�Z^"Vұ���c�v�#�L>�MQ���tHE��1#x�kx�=�?DO��Ԡ�@���U�]��JPFG2installation/framework/database/restore/mysqli.php�!z����=�zǒ���h+:g�$9vbǫ8XB6kI������
3df&�~�s������u���/�r9���0�]U]]]U]U�ǣ�d}��uq[ԏ�4�*:�H�����Q�~����T�X���t"ܸ�_z��Ǟ�zћ����\�X��I;
"��h~��[w�	
F�G��d���T��O�~������;߈c�n"���>4O�I4
�Dl)��`~����L<�B/vq2�q(_^zq��"`H ��[��o�ޠ�t����u��
�W*?\_�	��ﻩ�s�8�͓__xoS/,�^���<^�Z�8����IO�Ф"� �f��&"�D����1��G`2�:�^?�*�yHL5|BO���;��l?��A�›�����Ļ<�Wٞn��z�+���n0V��Z_�0w�ӰO����ѕ��J^y}
��(�0�������ֶ�D�yC�&Z<���鹟T�/�n�ط˃*A���;�ީ�0X'�Ix�h&f�^(��$�S��^��(%� e��MD��4.K�H��*8q|vxH��ա/�ZMv�+������"�A��Q�~Lǡl{O�����37�:��[5�xz%��\͛��;@��0��s��)��I�1�y��o����}3
���4��N��D�ɄcchT;w��]3�F��p������_?�A��1(�ؚF�Ξ<�+�B��r�U!?W�U�i�-�����5�Y/��K�L���	pV:̋�B�K��/�<(J��-�/�;�QNP��ן��?�^<�	�'����T��v�#Z�����Ƌ����޳6�{���֮�d�SK,�_�8ZEVa{j��t��c��n7[�5�q�1�&��s����:����Y��:�q�z�(z���7��9�s� �2��Q��� ���N
z�OҲ��b/�ơ0�v?K+]G)L9`��Ȟ��������I}�<�n����Hq���&Q�7zz�x�9;�T��l�C7H<�2Bu�������!<d�~U"�"_�X���R�mߛ�6=M`z��,�ffH7��M/.9NE��ޮ`o=�
��,�?�ݭ���{�N�W,]Sh9��ɢqm��^��41��p�����m��^�=��t�o�F��c=6Z ��6�}Xm?�BE�"�/V#5q��{��y�ܽ��gߡ'iMz���I��!Xی/�ZY��7k0�.
Z� V2�b:�d��@�e��-�"Jͺ�dUx=X
o<I4KˤS2�F�0-�\�3k9�j��s�9Ռ6{�[ ���m��D��,e��
b�����흃�k7��)bA6����p��lB�̀���pO�fB���;$đ_P�/u�&�'��??'iQ���(�\�.�d�϶��N��r�,��	:�ĔH���0y}�y4o����=�Mro��pc��l+ t	Ў���gG��v�&NA<�����Y8��n	����GW�{��v��w�W�8���)ߴ���T��@�M��&�ox���H9*�m�\ �����"d���m4�l��dX��l��G�B�_���{A�l	^����pzni
����w��8ޑg�w&��!�is�[��}��q����F{0@��k�V�:j�
�A��-kd	8p��H���p��j� 6�w
�����e��j��\���u��Z�����i��3�iG�MI��F7Z9�vG�d��hV#�[r��i4�DZ�������1�
�=�t���.�(ѽ��[����ڕ��� & e��~
�,���F�����Æ ����7��*ڎ�
��Ҳ~؁}\�+���m͒�ԴG��Tn;L�L[��Xja%�+�ܛ�@ZॠT���
Erp�a?V�Mc��>�=}0�1���R��qJSo<�����xw�M���e	�z�uԻ�����3>�q.cP$���}��~��x����ʓS?��
�_--:9
���H[�&����P�ŵ(���$p���יO����+�p���[�
F[)��H��f�e��!�
�������G5�_�^�U�']��}�Kz�`��sh�@W��PHQP��=������%<+Q'�����i����g*�E8�o>��s,�9z���q{P�)�R-
���ʼn��(��6��J�,�>H�r�%���X�Ύ�:����;
��(vfp_��#��s�|�q���N�+ʜ�����)yZ�oY�����i'��9d����"����R�5�ċ�?��6�*y8��Sv曜<��L�홓޸}[�F~�L8B.N�'�_�aΟ�I���h0o�&����W3��(�+�O['"Q�0�Ƽ�0��P�x�c���ik*��v��1��5ѵ?�f��H�x���i5uW��&)��3�h(�8�1��<Em$wQ�=T�n<�(Cj�@�*
�c� q�Oo�=�6����}�r\�
��7�p�T�p���v�6�����v��4���ݫݡv�-F!M��BÈ>�p�`�9uG^��/�ȌF4S�7F���*|�0T�bO�ꗣ�٠m��soNHq;%�e�&��x�̟"r�&'�`ECM<6
L\E�D!�~(�I�7r��Y�Xc9�CԐT�cw� ��"�N3q���C�w�u��mh�F��ǕA30
�pB�K�@�H5�����$�~kk��0�,A$@u�k�h�z����$U?�R���vu{˂$cU��<2�V �Wn�U�	�?_��0|
zjRpP����� ��:������w�x�oa�:���GHД.#cF�'��0P�-���8�maв�B�
Dp.�&��x��vd�3��Ԓ}�R�G����������a��I4#NJ��?���a��(���ܽ���ݑ�:��B�|6Xj`*�����FCU�l$:��6��ұoSMob�5n#[�I�`=��V"�H̓�K8�k��0��UR�}��VJ��p �q�K7�]�����~���C`����,��q��=)bl�/QΥN5-�H��Rv(1�d[ʬx�e@G��屁L{o'���a�S<�(��'	����Q�u@ R~���r�BV��lԕ.9[���K���v���w;��n��[>�����K�vK���s�[���j��D(Wi-���b�Z�e|ť0FWw����^f�>��.`	��5 �`v�8J��Ws�I�s>�ʡes֋�RoU��ӓ� �~���4��m�4�rL�W
$�@�GH�_`����_D0/C�-d�H?��m�fgg����y4��QUVHI��y4@]�hm�
��s>r)/���7m�>FS��p���G#���>�ʽe������H�g�q�<jv`��64�FQ����q���(E�X�ܴ�sŚ3Ѩ�=�����Ɠ�1���[~�~q������{M�x,�$��)!d9�B�����%�.��/i4G��G�R�CM�#�-7��Z�~��D�y$�vsX�l��-F��L�lK3p�p��KT��5D�$����g��߄�H<��؝�Ln-�_š�%�E�O�� "�u��r��Ž�=�)��?�Q㢙�CB��
B�f�o�`�Q�ʊ��$-7&7@3��M�&�;�
�d�8*I>���@��0簉�Y>��,��zt���(���~�V4�#,���#�d+���-p�,L��Nm.h{�N�R���U	CBE<:v���\._>���,E7T�ż�z~k��4ަh�\-��(�f3�R][�C�*�ŊK�	�ba�XYJ�$���:�y�Š'-cZ��s��&�wZ�HC��X��T�q�
��bb^�p3��.e�S���R���p^��koL\
k�d�D�VO�h�+��0�2�vf��d��ER0���U1�R��h.��
I�j7Y�]�_bQ�<�|��&'ɋ�E���[8�=��_���O��������<���MA��%!�Y���a����$��zAd��h=����/���e=PVD��'�D��J���9���Ҟr��L^<d,��w�B1C��b��&
��e������5�X�t~�ьM��Z?~/��Q�5V����hre����D-��"�,���C>�K�q�}��:����kb���S9��PK}Ͷ�Z`,��3�{�3%]5	��=�#�'�Wͅ&Y���Ij~�'�h���^�!^�Ҩ�"iD)gڂ���$.��r7(�ƺ}*Б��*\;������ѹ�l1��;�p/�L[A�y�y9s�����f�U��"?��qݨ\[[���^�
%�� �+��b��{x3�e�}-��%�YE+��w�j��A@gG�$��r�2g��h�p��"��M���|雱?�
͜�F�_I�C9[��h��lw��#j�)���<t*5���%.����>FJ�oW��R�~U�k�"�����jY�a�d��D��9�t3ud�,(��>�b�#iK������L�W�Ć��l�?J�ݠ�DJ�|J+hA�-���� �Mh��Z��9p�4���H��ɛi�l��g֕�Q�,o�.?��(Ln�ʼ�^�X��a/��J[
�������oT>�IZ��rn䠙�j��)�mmO]�2֔�Ȅ.XmR%�+N'���F�������F����:��I�ej�s����g�URr���!�%X��-�6�������(�0;��*�4�+G�ǩͩ���FD�;z��꧙u�̮
%��T�|���(��]=WVE�5��*i��Tm*K�ܯ�k����ʕ��OL�.���x?A�����*�V&>�&o��E��M�k}M����/ m�tbDZUG����<.^ۂ�qy�|���\���^mfV�h]_mP\g�����6����o�G߿z{�������Wo������ww���׃�z�M��7W���]��~�_�oo����j�dS7.獐��%t@�2�/R��Hz�:�@.�8
�w7�mƘ��l�-=bo�K�Q��	J�|v!���\E~���-�������8���®)��bN6�B������n�7^�����ѱ
�'�)a���0!0<y��Vin���x�?͆z��0Y���q���Yv<���-����TumƱ(�j_� ��H�U�
ϫ?쩾e��u�����i��Ǣ��B���;��/mG<���ჼ2�y(�c���,�Ч�	�>?��v��o��;��@�ܐ����l{p�
}3X��A�3ʈ��~	&U9����2��5��Ȋ���7���$�sH�������*��/�Z����[�T�4
.{�C��s���7bC���`�E�@έ%CUAr�qh��vDR��[��K	�h6j��cE8-��`2�[(1��M7� =+�&SlF�(��{Z?��!��� ��=��B���|k�^.�-�&��F*@`�r�
z��E�pg�E!�	
C_("�G:����˒VdZ.	" ��bŵM��5����|5{MÏa2r���d�9�X���]�7kGSXl]0X[��]$Ҭ�(B
��n��~���a"-䒟x1�u�5PUz��&�	h*��S���Y֋�z���z��&�l��~4
�_��=WzK�*s#��2���,�A°�i悫�����T��-���ٳ��HU�
�aRp�����d����q����ПAIЍ̜x��$Q���3�RVEd��H����r�d#yWQgrh�cwRr��iD��
,Gv��6�|9�PvOIT"�RX��EsX�,JX���B>N�g6,�5-'�i4��
+�v@�v�wE�T?���ԟhD�F�v6Yb���Tt��#��`��!���
��O]L����UT�M�tb;�
�p��w����������ò��K�Fl&Y���P��t�C�PpX'�Ko����� i-���mmK�<�(9V�.e�r�1Jx�wh06��[v8�qs�pJ�,ɷ�vD�
Iά�?=КhaLy�'���W��Ԕ����d�X��m�kN�I�+csرb�K���}
^u^�a��E8��0��3��̈y�l��
к��D��E��r���eMj�)��}Da�Aʠ��u&�x�X�n�֭<9]l���'�s����")RxB�X������=�2ɞr/�0�UɄ:5$UG2�U�9"�L����x����:^n��K,�w��ӾY�]X$,pVUX�ܛgž�D�e|��x��B�1��2x�;�]���,*
?���R_׏p�c[�$��ÜեW�I8��nx}Tr��7:&I�K�vf��
����������Y���:jp�W]!�h��s���8�%SVs��$�I�|�d�qB�e��:�?i�w����G���鋫���d<�}�G�#����P~����.6G��_(m�q*����5��� pG�=!��Ԁ��Q�RM�<`�T��n�}�M�ry�7��!�k�l�#�*��%�	eG&QG�逘�2��^9j��6�����e��+�"!֘���3AO�R��U�bMu�W���7��?_���ic�";��dڕ�z�Ur{����n;@�C[�?����O݃��Q������*�����'����n|r�����Q�H4���^�'E�?��:3U�bN��
��U��s���ct��{�2�L4D���ʖ�Z���i�y�y���"3
o�I`7��x��o7�[s�O7F7��uz�r�0�!O{��>j�.Z��=g�����Yg]�^���=�����ń/R�'���ѡ:%5��[�6pO��վ���-�5����@v�JS�SF�C�U'E�L�W��ⲑ�R���ô7ߴԏbw=
��~0_4.��z^n�h3x�&�<'�]}Pn��Qǿ#�i���&N�x ���k6��v�=y���qz�>��#�qg�i�N���]�����K9����Gӭ�B�Lʉ~�:�h�)h@���A�/M�2?����]�Cܦq�+f�����'ƒ�˟pi��}?e����W���ۢ
��ޥ7ݎ��2VՃ$����j�>�}?���ęG��Y���9�D���UJdn\�D���F]_H�%$�7:ä���t<aR0�G\v�Í&�t�H�z�U�l���i���6X�m�z�8��W�	<ZH}<~�&���Ёq�x d�������;l���K���t�C���k�UB|�w2�,��Ƌ�F���CA��2��hQ��ߛ4���Mf/�`�@�-ۏ�멖��l�5�V��}���)z�	�j�7�l���B0�&������i�{��ⰾ�%s9Δwm�<ʶ��}�K���
0c���U>�r��NNn���RZ^��$_�l^=��nvx���L.rI/YM�	�J)\��[
�[���8&�b90���{Ϟ���W�T?�{�|Nى��ܮJ����#"�5mg!4�����z����h~��y�z�D�Ct��W���|���oz9.�L#C0�A3���[��J��Ȝ�v-�=����r���"N�Q�<(�gLK�(I��mE'�����}�)_C���h#4�t
�D�7d��c���4�b�9�TD1�eR�� ����(R�"zQ�Fc�Zq�Z�ȹ�BlZ|������]#y�R=��מE�C�z�3�)��/��j&
AS�~49(�X������N[��~��&Ԑ?�>� �e~��x|���I��UȺ�u���Rt�6��/]�mPc��Il���B�:�\
������A���U�lQ&��4A�W��B{x���c��囱��(�W��V������B�s�nSv�O�:�|49�&��į$�a���#Cέe�<�ݞ��X���-��
r��oA�r��"/�z h���.�(,2V	�9�dQ0��L��J�`�:k�g�0^E�a:F}u����M#���Uڞ�J�V��<4��yȷ�^��W2<)�u���*
�j�T�����%,94Q��1e�3ߦ�Q�&�y�զ�U,��$�H��*�A-��哲)����9�1`	<JB�|��P g��)[�!y�qb3��׾���f���ǹ�)
/��r��z�+�JRS�wxn�:���X�0�3��c�gp���bgt�M��S<��qI����4��Eq|n�A���,ſ��`ܨ5���!7�E�����u~�)����y��7�6*�mPQ�?��x{h�['�uY
P1��g�++�cF��
�1���D�dGA�1���Lc�E��gp�$�/ZP+y�W��~�4J�v�*T�� �G��Ef��n���ɑҫ])�r��nr+V�
�_U;*E�t�i_T�^����N���Hj/SE
�G���PeA�L��+���eYf}�.3��Qf?����ש3}Ge�>Ӑ��
ͺ����N4m_Tڿ�Jӗ$��:�Z��R��fN����j̀��赫��JPFQ<installation/framework/database/restore/exception/dbname.php����}R]k1|>��!��}�ے^bcJ�)�x6���O�"	IgbJ�{��?RZ���������VLF#�������Q3�
�>'�2����PJzl-��Z�؃��=�G�R��ձ��4&�Hc>ۈ�-��U��ؽS�:��T
h8���|z����4��
���k��xL�3B�i5�X���\�Ē5;��k[Fp��'_�cDKM4���o��j�_���m�&B�x0� �Q�I�M���A%�,����D�߉S4�Q��o]��x&�)�y������<v�8�Kd��զ��=�zw����@
�..�葩R�.��8i�g�i}�t�4C�E����Sy3C�o�!~����'tv���d�K����s��_�Tk��cp��:xՏ�g���u��e��q|��,{�E�JPFQ<installation/framework/database/restore/exception/dbuser.php����}��j1��WO�>���C/궤NlLI0��k���^��FH�mLȻW������
s����7�tb<	�0_�>-q�o%!�H�"{
[卋زG!�c� �*MCʓ��Q0$*$n�V�<_��4��ѥ���v�:�bw�fWFܝ��N'������k�J�d��i�C`�u��ӌ���^�QdC����Y���H<�
��r]�HHU�x,�������~�����m�64�'���ZR��e��4K�hԁ��{jK��{ݵW	!`�8f���"�ʼ(�J�S$���G޳�"s����Uݡ6��U��Vq��S��pq���X����$ǹz�Sc�)d��,��~+>�73�f��a^��v��/x�G%��V����3�79���(eCp���[�c{��]��
��IO6�f�b=b�X�nً/�'JPFR=installation/framework/database/restore/exception/dberror.php���MP]O�0}�8o"����D�[����l���5̶i;1�w��Է�{��=w���,�L&ȶ��S������񠌆Nـ�q(�8�܉Z�C8�$�3�Qɱ�0�6/�11��y���P)�w�سSU�;%b<��n���|���i��y<��5mc<�!cd��(A�w���V����ml.��Z\!���\��I�+M2�g�Xf�qG����-��y�%�6�hM�o��눭S��_]o+��٠�0'A���叚N���ص:��?�}2��
JPFI4installation/framework/database/restore/pdomysql.phpu��eP�N�0<㯘[����=!� �U�Z���9�6�jbc;��q�����������ڲt2a� +��5�x�	^�#��A�^8e�ơ���Zp'ju$��(;d��cyR4�m�Goso#�+��R4`��Ω�x���"��f��l�@�Dm4��\au�X�j㑞=�A����~^�!���صe$�=�Gr�ﵸD��c�S�$�UCr<z�6�2%�@*'7���#[�K��y���}j��S�*��.��JPF7"installation/framework/ftp/ftp.php�X���<kw�6���_��>��Ȋ}�]�Qd9ѭ-�H����:4I�P�J�v�M�����D�J����i{NL��`0O`�����-��=y�-��f�U�-��p&�rB)�B?�B���g����~-��3�V*a�
�X�܋�[)o,�RCxc�f�>��üX@�5�B�ԑ����3���?U������#�u��ZJ���@н�~�J<3c��c��:����}#^IO�+.�h��V
�uT0%&@�g��c9q<9�VF͟���J
Ǝ�a�0j8s��8�s�^ؾZ��RL.B`���ܵ����6�g'&`f߈=�v�p(z�������
�U�n�����P,� �$w�"�:��skw+ŝ��Ű]i�=���n�Ȟ�߅�"�od��C �b66�gϞ�v���xۙT���x6����^�jva�^��n
+���[VU�:گ� `�7�u\}�'�g�
�0�v�5l�"���C�[����V��٥����+����sd)�@��l������E0m1���XD�˳沠�es0���?�n_'��R����C�~{p��x�o��C����9o�`�r�K聲3'��@�r;�6��a�����Ȭ��Z",OM@�$�F^��^�y~6�q�����
b���U(�Ž��!w}�]/eK׵<�GJ 聊��*2���mO���V��lI�U]�����~�>�.;HC�Ͱ��:��Lߥ���;(@��m���ꢨg�z�}�Z�a6�w!h+�;׷�r�����@��E_'�b�CW+�p�9�*B?Z�ɨ��~U/__�z�.�j5qrr"*�t�����z!�n��f�c׭�[����d���[yg>�D���2��8m���H�sT�����q7���A$k����ۮ#=�{�F���D�,\�r�ƭ��@��v�V���z��� �*��-�8^�0b	"R��������@mx�XISX����`nK��J:�T��T}�T
�
���r�PNA��Й�6��u� _˘B�z"�)Gcl��~�JP/טX��ao���?��&�!���G0��GǺ�V�[Q�7��	c����Xj�SLJ+�[�Y�[���L�	���O*yt<�<hD�?�P���H?�^��1*��w�"|��z��je\"����l9B�A#f�����5��nl���O����l����Wz|��yB���)�M�|���F�kL+9�X*9��Dk�h�T����iӆ6v��Ċ�0�x$�������Wŀ�f,�	���.�*�tUA�k��ZnRg���n��ޏ����1Bb�Ǜo=6�e
�&3 Z�^4[EH�)�4�M����C�|@�,-�@�,O��@c��̺�	�I1�@D|O��A\�k��v����!Qז��{�(�+��e(�}O]��rZL��<-HS<��)�B�٘"� Vc_*�
˅��=ϲO�W?�L�E�r�ZHۙ�+D)A0q"(/��V���= ��u�{�񢔃�q�jMXS̅�p9�dr���_�r���#r��������;Mv���T(���Î!�w3H��PZ�|����M)�]��"vHX}@�[5$�g�b���M�ƇL�QЃ=�w�%=��J[+zCܳ�{�����ɏW� �}��O,�5̘��ɏUp��bq���7F�`d2�J�h�J�d���gI��{�~�|�㴔D$j��D��Sv�˨��u88����@ȫ/�Wb�����_4���{W9S�B1�`膨W�_��_/���;�;�Ò��Т��'�bwH�
�P|
Y��i�DV(��t'�lj��JȻ�������q就Y*�BNŸ>i��j�ːy�-�~Ğ$�'s@it��Y2́�=���'3�|LG�Lz_M�`-r'����V�(-CD2
m�|���/NXd�
	�y�#����&��b��}0|�c�a�!�P24)���¤���+An!:�,X�"hS��-p�y"�v��@��p�Hh.*�����$���S��r��2���m)���z���:qu�屈m�u��Yv5���2�Ǯ`FE����A*<�t��Z���h�o�vex~�q�T�Ck�Ļvu�į�e���*
	c����Q��ß���v�1�`��@p#�A�Y�ߑm��%�U�9���XAJ腓j��G��N�;�;ZƎ��Q��)2�H�q/45'ӈ(�k�݊i�B���N�c�Ҟ�I�r�a��{3
ڭ|��
��s�P���qx�P#	�/OW
%4�}�+��R�q�J��:�>3}�W�̒PW���Y��Jc�t@U��ʜ�>�ʖ�{�s?R�$$�X ./,:ai����פ6GF�2���cT��W�����:��	�>�)ː����3�3���Ǿ�D�d���	� ޑ����	
��l���Q]k�8�Vf�$<B	;����&B}I��J�*�R����8�)S�dl-�e������S<�'!�������+d�>�М�����ڀ�:��NP4n*
���k���g����Gp���
���خ�ɖ��W�M"���!
��3��^�*�F�V%ؖw���S�K<��V5t_���5EƞY��ڗ����7Aq�o�<�֏�f@a�\���)k�	pSB�z}���?`
�$
M皎l�2�ci�*�b�7l��9�ŭ)��r\:~Y�2>�	��V�Y�K����$�b=b�?�yw��G�M|wLa��O�vY�����/t�}yK��k�s��1��+Y���A�շd�ͭ<V(l|�5����vɈ=����
ɹ��	]�U���r!�t#��3��B6�
Q�?8<�P�����ߐs����=�6�5�`h��u&1+!>	%)
���V��F�h�u#��p�� ���4�H�1<kI��;Qid|�I�|N5c0��щI��U&8�G�'��پmZӘ#�yv����w*h�����z#6������<K��}�s:
���cte,�'Ҩ�U�D]?Ba�&�1e��G.���^0���z!�c��}��ÕEֻ�kZEs(�8�4�c��*���~��3�~?��O�X09�T�Yñ�7�i�~Dž'���x	`i|)T�� m��[/���.��f�q��{O�"��r�O4mn\#H�9(B)�G�m"z���E�@��˅uVK�EeiK����}�*�A��Rߖ$�7T)�{�/�W����g����JP���i2�E��=�ᮼgL��y3��],f�66�oΰ����!a=�a�uޒ��S��&��'��Jd8�s?���D�0�LC�J�*���R
.~��WbD�5C�fL�5���c*�8�7�yV�ib�F#I�_�+��f�|��r���~�j�����eŚ��غ�yˑu�N�#ju��틁x�t�!#ͺ��MhKN�ҏ�JE����'�$�q��:#����֖u٠_G���%zL
��t�^ʽ��2	]���Lx�J��)(�H�gqi$uD|,i�1���=\[[c�r�0˞E�d*7]qd��vTe�؈�ڡU�H#�Q�isI
Ѵ,���-NM��kq��J����M}�*5y]�w]���Ihc"��s�������̣12�%��J7���;Ʉ�հ2V7��U;�sӊw8��'_k�^��0�B�a"��	�R]>�� �I�����7�M4�8��d��4ъ9�h>o<�l���`�uT�1��i��`5�Z�c]`α�ss����C+��� 
�ڿ�t�D�@,�5�0���y�[#�s5�#�.m<Ȼ帪Z��^��.]��́�X�3�Zn���nn��dg��b���B�D$����cyfN�]kQE�e+��l�>��x�5�Q���SvT\e��ܝ�<�`��@X`~is�_G�T�����l=DZ���J����ݫ��~�^�k���(<]'�|Yr:
�µlY�|����H�~�|�=T	7D���o��O_�=�R2ĒN����=��\SY�gl२�Y������--{f�ZJ�C�_�Gu��
���˫	�a�������S�&����v�:.U�W�u��{mxޓ<���SS�w͕5�zlp��:_��Cݏ��50[:h�n�q�H{r	C�uW,� ���q�������|�R��&�N{���s�@��'<�>��
$C��:{�[w ]W�o%W�����ĸ��w�6;�y�8>��Ƃ�m"�GY�G����h �t?~B(E�l.��Y��
f����IˍA@1Z�wvl	�9f�5�u&ɩFl��!�0�I,hhM�|��R�K�
I�r4-^4����zU��v���EW���v]�Ȏ#�aS4@�t.�G���4mt��[ZfQ	}��H��Z͜I�tJ�̘[���u�d�\��ɦ��i�Z�j߁R���u�)�l�&�9lhk��{�����ɏ�W{c��*�{��p�i��DT����_?���������Oݲ�A+4,T0 �O��X\?!�ڕ�~Ze�?�����C�=?}�l<��%*���ȟ�����\�H,�0Dн�G�4y>N�͋��q��;����4
��g�G�����%|�J�
�
~�&�g�z����a�3���H
�8���leC���{��@��1v�r)�F�on��
�6SU@���n�¾�ۭޫng�>����e�^ˀ���=����z������}�K=���5�65�9\�r��F��	��dyrX�̬sJ�����m�N�НX�K�w@�v4����t��Y2�U�u<�yϐ��*
���r �#P*���:w4��)�ü��E
�(f
��"�u�r����7y0��>Uw�{c���T�߂��q R|�7��)���j�7Lq؝��G7 G�Wƭ1�R�KH��9��ۚ9v"2���x�|���i�ãU
����iЅy�$�e���_P�_Yc��U�v�h�N)�e2�i��|��/���T￴�/��ܐ���j���L!a%B�<Z�+��Q�=��{F�b596�`${��0p)D�I�/�q�|+���?s�DK�V�V7�b.�*?wy}�<�e�|��-�׵۹9y�f�$���~�'
L�l�2���m��#�m�Q}����4e��sq{�x���*o��~2�lY�_'K>���]rR5���R�w���$�c?dEAI��7��Sq@|߅��FL��9�e��`@��~���LީOb�Y��1�9�ɗ�V㽼	g'�7�ʵ���ҷr���JPF;&installation/framework/input/input.phpz-���Y�o��l�s�Q��yl[��z���r��&�<�>��AK��FU�r.-���%Q��x��_ �,g��y3�k����޵���/.�a�$S���OAF�e
�\��DOyDDK��"A��1�^`�D���Hc��'eh1�2\%
�Fͻ�g/�-�
N��^�?:8�����{�fђ'D�/C8C�^$�x�p	�^ƕ�
��E4����\Д
���|�p��TH}���#%x������YJ�^w:��d��k���^���_2	s�PX��x�K�3�0|
!���UB~�/�a[
�G<�aOs;:8<����S."
��B}�.�h���{ȈP��R���Ҥ1l��[Sx��ɘ#J(��-��d��̣��̨03��LC#��p;�C����2�rK���t�҈���+��<�K]P�ZPu��^'%+:��N�D�_�Տ;�?����Nh��Ӯ�g��S�8G�S@�|܉����uz��W��E�3�8I���ԧ��9�I��������?���_�Z�L� �ФM���{al}�����h�$�a\�������1���v�>����,��{�3��?h�@q�1n����w���%A�'	f����eE�;ͻ�%3I�y�/뻙^���-����y�U@7�0}�����9|�2��n2]6H2���H�����������y�F��L����-"��mu&ý�X!�>�4!I#��[-6��S�3�#+��3s�������/M$m�u����d�r���F�0�L�	������K��>�NP�^�ƴ[0�W�H�;�)߂�3��!�rT����b&�[{h�"C�X��7Y`��L���kU���h΄�r��TC<��1�d�ܺa��,� �J���U�9־��ϰ���S����y���S�耹��9_�M�͏;�i1��a7�+w��]��Lg�9`�3��d���
iݤa���T�8���ÇH?�|(�n�[#�%���$9�6`A7=�ا�rz�wA�i$j��@P�$z,V$��fT�D��
��e9�6�cQ��C�ɩ�J� �^��n��e.�Ey�,Î
���t�V�,�=}}�d�ILJ���-1_D|Y�\������1##XF{#��l�+d��X��R�>���c��.Kwpi��|�BkIp?c)y�l�\�|�/�t���,+5;�����
� Uۂ�u4��F�{�[8w����UߜnPQ�Z<q�CTkž�c%8�,:8t�`���ue���z��T+l)ْ8�B����B��z��u���Fb�o�)�EY�.�b�*dg�x���#�#RuŃ@���ZDU4��^�Mz�G��S�λ C���S�ܞ�-
|��X��"�Y�^�|X�9��Ďr�a^�0�<��2�����	^��3�?�)f�Ղt�@5��
�������.Y�`�rֈ��������A��rQ6b;#��doK�o�U ���@05r�L-��B��ƪ"�ar�h{��y\�09��q�Ol������$�\��E�3���}F#{<k�]@���-�Z�p\	c�B08,��
ͱ�3�=Ģ����X���f���Y��Q������&��\qT��w�_�o']7VN?��tsֵ�ǟ�n�;x���]6�WC��\�q@�b���ڭ	����߇+Nb :��+|�&H0N3�H�O�[���t���_M7���UqQ�x4Hyj�ɍ@]��z��}M�Y�
�U`3�V��+�z�2
��H�>����j�f�������NU�����(�h�Z�����v���Q�.aRՀ
��I���q�2���ۥ���f���މN�]��ՅL��Y̢�ӵ���^x�
dh:7V�y|�;2����`�x���)�5�����F�8�d������j�gB�R�AG/R���;����P�-޳��P�RWc�`�rW<f�r��Vpp����N�4zrM�?7j%@�{�12B��bF���q�s���J��Tf	-���a��6�	Bns��6t#�W���?�Bf_�� �q����#�_K���*��܆ʈa���J���jC>CkwH�]�al�r�o��Qɫ��JPF;&installation/framework/timer/timer.php�I���Xmo�6�l����R淶(�5K�t	ҠiZ4)�Q�%��B�)����ݑ�-���C�$��{y��_���;><��!�^_\��n��8n3mX&��f0��,��S`&^�{n!6�e<��N�8�2x]P��<��R�Rs��-�s$���b����/2�c�W�O'�߆O'O����Z2oGp���Nu.��q��*K�,)b�,ɿ��\q�$|ȧxW��=7��z6tI����n�gB�$�G�o��_��C"H£n7F,�ފ%���P�:��Pb�/�?�8wːą�f<%Gwr���ø�I��G�p�*"�ȱ�u�p���C�t&�^1sH�Vl�jb5M*����|�[��g�xd���T�[�;�"�V63y��5(��B�uGl��Og�~b&�\n�7�8iN�K���6�)3l	޸F`�䩍��=h�3���h*�V>�G��2P>�\�NPm���`7c�
5�����r�^�Q�z9�z����t1@-��o߼?�"A?���`<�;�	�㰏����x�Jd�I�7�%Q�s�-���e�8[��h'q&5˂RZy��a�aà1<�L�*썐�c��_+�����!B�&��&��e��@�Kp��
V�x�#`�s�7�+���A��G>m�$_y3~<lU_.x�B��)u�,�ZJ,H>�0�?��X��Q����`�
w���A5�t��Yn�Ӹ�W	a��(�-�Z:,���A	5'���8..Yjq�XA�V�C
4a�͈{�͐Ǒ\;a���H��F�O����5�[]n�j�6�
�?:	��87�+_ |����b�Yd�[f��{�&o��&G��#�;Ċ���	z�l���uj�,��б�/P>���g�
�F�-�"n=a�����P��Y�O��b�`\���h��+8�3�ˌR���G��Rp�߈��H�u��h�#D���rK�+����I�Z��h�R#Q��E�X1A�K�֥2;4>EI��/��'
�#-�}8��.T�����H�nyQٕx�ĭR�fM��1�L�b�d��o>�-,��kZ|y}	.М6�NG� 8)-Fb,2�1���h��dǔ��$�+���r*��1�z!|����J�L��r��qB�w��O�5<�L\z�GP
X��0
)v�M(�+�e�BISL`M8�V\J�dJ�!�NK�F�6XY��&�����47��܎�TZ����2�
���ߊS�εR�A��0�QK���F�i�N��c�sS��n��SF�6�����#a#B�qP�d�*խ�ER��w�0�?0J��5_��-�Q��&�e=k�Tֈ��q�l�����B����p�R��j#n��kRA������F4�窟y���(�s�A?�vhD�jp�c�j>�����"r�x�UX8lt���7��z��w�ީ���Q�0PTm�_��[�Y!u4���pM��@P�=�B�tG��X���mv �t��ai���N�8v}vXe��t7����q����Jg[,Ą9�ٯMP����I�5�l��P�n��¢�:
c�����(zz��?'��;Eq��w~�­j7�ՀT�6�|O,v���c�9�mK�,Z��e������w�S:�n%�!�^��b�_�0<���X�[\a
�b&�8w|&4�ʦ�mj	��Q���U�����l���+�#���#�DU����rl�/
p�y���ͿU�l���f�㯤_��JPFE0installation/framework/dispatcher/dispatcher.phpd�����R�H����Z*�SƤ�}YX�(��x��yɦ\m�m�"�5�-�5ſ�鋤�Ŭ���
��>��i���l���޼����>M�p�5�A��(�S��`��%0'�}��=P	��D�̷�S:'��A����'yh62�%+
�+F�Y̳�`�����S޽}���wo߽�+�yB$�4�h+y��K8*x\�������R�t�>є
��M>��t�TH��!�J	* ���_�%K�"f�O���(h�����ۗ�FI�/�)ٰ�F�
Ũ�ڏQ:	��Q����i��h-A��\�d�����'hBd��W4ֆ<�
�Z,����DHH�S@�e��%��HPc�i�;$V�M�,W`��k_���T��+4h
|	
?�I� MF��P6�VOyJ�I��ْ!m�ɺ��(�,ؠ�����g?Q��1�w�6���$��il��z$��s0D>X��'��y��F|{��*
�$ܧ�Q��\U4�%٠��1�g�!�,1�d�w���v h;i�ic�6��	��hDӂ(RG���?t����BAU.P��n��W�	�<�
��+��t�,��B�'�ЩW~k��Փ�"�=̤�c鹫���5�k<s�G���aK_��=����J�~�� #��[�Lδa%�0u�ky�>>F3�F���Rq��O�Y�ޫ�b��$%M���t�5M�l]y�I�z=C��E��	���b"��D�ʮf`{��'��+�b��(Lor:����fH��ߔ���c߀����~������p���)�K�[�vg��{O�����)�40m'(
��g�~qP_���д]M�[�^��X:#0��M$�L�@KZ4��$h�%�)Xe��,�.,k�
+���?��w�aK�I]�c�j���O�:�7-b�pxf��"Q�c���"ń�椫…�l�8�W�g/���K�.p#���
�i�j��U����ӬC}liۖ� hCs1��u!���l�dj��T��lǴ��3�����	��D�qT�+��f�����g�9Nq7D�>�D�g���ityM'�W'�cI�W
U�h��(8
Fm� 8�p�%c�AUG�jR�@~��:��+c���Km�vճ�C�u��zc1�T����Y�+���d�y�K��:>�}g��3'A%�W�	_aMΓM�_s&���\z�3Ϊ0��װ�N�ƅM�O`/ܻ��d�;�}�AT˕*�� d�o�R�[c�꘹���O	G�<�Ż=T�t�PչZ��k�yf6+�,��Z��!j���V[U�����M���]�L��3�9AW�	*��]ұ�:�O�^K5���D��Ͽ��d��x6�,��&�g���
�й��=y��$9nSVp:��7��-�k"��J�m�\K���B�]���|	��޾���G2&T�ֈ��چ�^��+�{�}�_?P!�B�Z�M=ƺ{Ae���4�)E�)�_7��`]�+�qc�'��u����ͽ��.�,�Oۡ|��p͇�9�a?
:�-߷; ��e\�b�peW�V��L��Ծ3��o�Wk�L&,S�w���&L�Z��	��T��~���SL��;�v��C��b<���G��^�0�C0r���?�_h]e��G&���߾�����~��`@S��6Z�-Džf҅����������@���8�$
��C�57\f��
�0П8=�E�1ٸ0'�/2�3ԭ���T��GƔ�K���)}R�dz0������rrn�����v�����f?\�~�\\���(àYtu=�O4�U�`��۵�U�_����ʷW��{K+������.Y3\��"�_4�S�oS<p��K�_H�R;Y,K�6U�T]���w/^�N�çv̰߾ᱮ9��=)��h��3N���I4�q��$8+���k�^I�������e�5�2�)%���X�W&�B�*�����+����"�p��W~Q*_JC'Q���{$�h�M���W����,��]+��kXC�b����
	pk��5E2�B���o��#��}sEihC���ʘ�U/n�����!o���;JPF:%installation/framework/utils/hash.php�
���U�O�0���7��-+�0�~0eT����r�kc�ڑ�D�����R&��/�Ǿ{w�s�� MҰ���:t�'g=؀��I�`�yc��F���L=�����n���9C�E�1�Q�[đ��2B��ΕQ�jp�ÔN�(`"�؋L�[9I<|{X5��v��yc���}%F	�pL
�Τ&S�A��q��K��c���
��F+��Ey8C�ׇ%E,%��0Ʊ�7���y�wԭ79 ��VB
N�is�=̍��dē�q�e4���nA�괻��LG���,5( ��bD�xV�`�6�-~�R!�]"��ɸ�.~C'�$!Y!}銣H9u\s����w.��P.c��	H�<����"���J�V�.�\w���
2��T�l��m���2O�
S�="�P�E3D�0��*rW�56��2Ӕ�o$��t(c�
�j�	���4��yC�:5����E/���~�� M�ɽŒ�Ŏ?�B����:��b@"B�/�;���g�\+��=(]��q�0H�H�{zT��ל��zj#���a�B9l��
ʔ�Kh�c*�MP�Δ�
9����� 
��Iv���Q����gmmu�P��q�'I=֊�Q'��<�4��=ʺ�-���e~��e���e�t�ʠ͟�
����+*SWY�R�{T�D��"-�k����g�^{�U��R�zN����?ۛ���7��n+�����JPFB-installation/framework/utils/phptokenizer.php�	�"���Z�S�6�i���&����q��2���<$#�e��Z��d���#i���6�������;_2o��i�����<����#x	�SZ�k�*fD*A�Jd&�����0O��+��h	�����w�q��*MR�Al�e���8n�ܾ��l����a�WG�������ޫ��L��4a�؁_Q��N�t��vs�fli%"�R��p�%W,�����/>p�I����*%���û[�1.�Ҩ�p�h����
��F�؀�������4����D�3���4�,&����O���/�W��h8���h��l���� g�	�c�왁�Hq��%�/��ҮL���i�`+�f?Q�C�7N%ҝ��b������A�������"8sO�2����-�����<I"��)L϶�
������džW�,�n�(�Q�����+�e]�����:���j���M�� D�RX2�����:MU�jD��Zn��Yf�U,bV�4`��s�37�����:�2FQ7U�fD��>҉��2�L	6J8Ю&��������t3&�����)�1��]�aH�G�NH��;\ȔbKzu"�3�{��&�%��+C_v��'1����13,?��Ԣ0q*�mֲ��u������*V�$�����X(��c�

#�.�҈?�󌘆�E�1'$A�35#��A?��U6�1�X	�AA�����.�r �7@ak�p1�D���D(m�ej�K���S:B�w��N�D���2��4��}/�bB�s�^����bC�3ر���)����9�$Y�e!R���p�ۄ��$Vt��֮|&b���ۈ?k��~��:�����,�C0�+Wdٶ����}��߇�~�0j&&)a�rAq2���
�~}�'�o+�H��M��k�!�J��ؐ�he�)X���Y�bN��9�YO4�S�"=���H�wɎ�|c�l��ی�/�6U�X,���Ij$W�۞=���_
2� �'A�� �{�\ɐ���Xb�%QZZ]	�7�5�%4�h�
E�k<D��ڷo�ވ�W"�}�J	��8^J��ŤD_=%����]d���Vm�z�|�(���h����!���,K���;�)��w����3�3��Ebf8�s4]ȩ�	r�l�/PJ�䨺!`�H���0K%i������5,�2��I�3�|�'b^��3���0&+K��b�"�0a�Fw�zd��wz��+�����[�$a5cW�O�8�p0�&�Ls@�ƉzrՖ�@�܉COі噜fu�����˓���WG���//�ή#Ra;�L���u���|	?�k���<UQ� 7�S����j�Aq�j
V�`ڛ�+�c��u��&��ڎ�~gY9I?vspS�L�Р�M�(�o|�}㈎�+�A̋�q����|��fU��W�"_w)	��e1R���z?�E�|����.��(�n�b�&�kpN�Vݵ�����l�3�a��|��A0ߓ�mH��f��4�yo�L;z�	k�W7��Xh��񦁇nf����۲��!wɄ���.?�M��I��m�Z�d\9��l�U��n{�\�*�/8P���{,���)������s�ٶ2}Vlݲ����?�~�#-n�V�Q%��|�žH�U�#̶s����Q�8�U�U�М���1Fv��0��G��g�}P�q�ӂ���4��[ǹ��Νԭ>�f��fm��fD=lICՇ���]��σ��b��Z3�;��c�!uH�k=jF{t��s��F|6�;O��������++��2$����[�r�mV̨d���2^��a�����B��ݭ^K��l��l[���֧T�߮`�wZa��YB��}���Ҩ�v�7m6����	]�E�3�/�@[���h廉�"!1n'�ta�L�s[АWo�"X�20n.���PD:�q�	���
���Vˉm�p%,z�h��
޸R���^�(�o�'�rn�7�n�4r*�`�*x)���"n'��)�mf��ʥm���
�K�	UXͺ+a7�u�q�#���Y�y���o���J��4���[�Go��Ns�-����V�B��ig���f�(��ZI��p�r�M�&�.�+�B#n��]O��@}k�9Ĝ,W�;��H=����h�]޵���߻��(���k�yu‽�:�Ĺ+��7�݂�:����CcV��ϿR{�:G����o�je����L9�:��
fҼ<������tsj�r���z[�
�RWl��NG�����ra�0��R�t�
�o�ҹ��
�k�R^_,��%�'�5QV�
x�]F�}�-�����q�5�G[\Y1$*���xc�k�F���8��ɉ\�~j���Hvn�?	 E���/7���My�.�rY�J���d�a���ƞƒh�"����b�)��G�h+������쏳���ǭǭ�JPF:%installation/framework/utils/utf8.phpt�	���T]o�6}��E �RK����H�ei�+R�����D�ZH*���;��X�"P��{/���Y���`�	�p~su}	m��RPLS�Ti!c�D�H�k�		IL�bIR�HIcM��,��$�_��lZ[n�ah~���)��6"�d�T����/
�^;
�#�a$<V�o����(�P����bqFh�����hFe�aT$�w��Je�::l�c��f=�4�1���MkJ�g�	͈�Rۛ��R�?+2b�AG����G6�B�u�RH��U?>�����@�H �B<���&k��D�cPp>֌�1��5yٝ�x[dU�zŮ��fCln���EK�bœ?.?�^��9�V��^��GN[��Ć���[�t=5+�zB�0���Z$H�!�.SX6�F���H?�8lwM׷����^���$��X��/J*�I�}���q��J���l��L�����5�>+�f���0��(
��K���b���+8�b���wF�/g�s��&��Y�sc��L�E��M�L�*���:
.���G�~��ɬ��
��(�O�xS��i�p���Sy�h6e�C�
x�:��=��@�����a��{����4I�ղ���Abԧ��t�'Lܻ��
���p��0X�|v�3.62�!�����)���/�6��H*�;!�n���{y����T_q��HpZv�B3��5ɖ�%���#%�k��d�h^�|�dw�4�gɞ��k���}�t��l����L��ކz+	n�ֆ�CLuk**A:C99�c���k�+����1 Xh���Ji:ă>��ZU`%ˍB��Bx��Rě���Uל��8��S�vpՏ��r�g�۟��JPFF1installation/framework/utils/servertechnology.phpr	���T�O�0���7	)-*m(ڤ�U%@���4�Ľ$��[4���S�Nh���w��=ߛi��hsӆM���"C�� (�F��p)@3��TG�, R,�w��)��!���E�#��"�|��\R�:�^A�Q��rl��,*����Ͽ�]�����@�Y&�H���P�e!�\j-r��y��s�B�G�%�@�p^�tg��*]��I�I���#۞c��{΍w��ӯ�wm�
ޥ�Q�A�	ҖV�w۪i�CIe1TFV*�€n�A�E!��af"Ɛ����֗��{
M��K�N9���G�O�EU�u�ȶ�V�6�I)X�%��;�MEB��EԬшJaV6Fq���F�6k���M��>��ON��	߿���f���@�@W�)�Z�_j~�SB�'��/��zK��fcuBǻ���@=rF�S �Q4�����2��2����a2�;��}���3)�Sb����^Gp��}r.��ѡ ���L�\�J@OT&���9��C{�zu�_�N���I���^��[�N��9��s�z�[m����V�+[�����>>}�0>h��cCd�7�����d�"k���JPF:%installation/framework/utils/path.phpAN���V�o�6~���+:�v�X^�=�]ЪI�e͒ N�u0h���H�@Rn������$K�u[��u?���?�)�r���3:��L�S^���+�2�\bU�im,�Dr_�$l���t�X)�Li���^ʕ�w��Nۓ��
|���%���$��F�p��rg�&�t�'�����ã��K�RIfr��N�Ι�T�q�>.}l�*�ڱ��t.��"��j�t�|�J�8��SBJ9�P���T����x��ߟ���GH�O^[��2�h�rI��Qb�J�7���� 1���3��q�VL�F��{:dkG��躔0U6����M�B'��3�^]�Q)�'�~�ˌ
^xM�U[To+铱��I.��w�ޣd��UIƿ���`i�V14�q��rV�&V�[�x8�Q������
����T�$����C�T�	�F�G�	���(`zA���@MYQo��	B���	699={B<uxu�L@�9�"x��)�&=wV���N���@���h8(k4��	����8�8
������������&���8ϠN�નu�j��Z�X��5��{��&��yy{}}��_�'s'[�(�[Y�?5�M��dhX&7q�FKxy�� ~$�~@�����e.9=�=��|�|@�R�����)����I&�8���)���N��ꇖp;�eA��u��t��aW�m�=iځ��Ê���˚���~�T�%�Q�Ƶ������1�h2�������v<
��g�|"-?���D�TǴ\�\Ƌ�rI3�z�.��ʼ6�͑6�gJ�0�R�h�ս����V�^	"���6"�������'u��,�E�~���Tؿ���P�����&w��9U�T��:��:�3���Čuͣ�ml�Z,$D�;��k��'x�sᑧ/����sh�p��&�T��'��'��za7҇aᮌ��L$�khUSg�(�m�w��q_�,�&������J��6
P�&�)�e�
g\D�f�y��M�/�&�z� M)�kc�iM��Α�ﭩ6�08���q3���(0�E�`�3��^�(��z��_E����uPl�
 ��KK�x�Y{<	pl�J;���h$�fX�|O��r
Ǘ���!;:m��7��т2E:a�W|���:6�>�v{���:�+\\[u���H���Ѣ̙�������[�H&�A���NS��3A���F�>��W'1�E�H�o߭�|�Y[Kq����%�`��pJ�)2���w<lj�ΥV�������>��͇&�ud][�,�s�Ya�}�JPFD/installation/framework/utils/parser/include.php	���U[o�6~��YaTJ��n2�h�lq�438F.ݰn0(���"�I�1���R�%Yۡ؃e����;��~ѥ��ww;�������D��!�N愒`��A�d����᥸C� s�C���-b��M�����JU�lx3ǚn�0θ�K#f����[�w��W{���^��Yx��[rhi�Vu�,�W6�]tU���^���P�aL�.༽�Cc}\=��*
��p��"㝨��U`��&mC�.���R�Lho�se���E��jss��B;+��G^-s�3 	�t�)D��qe�BVKX(sk��'�N�e 6���xVUj�=�9s�H.�`��Gc��A� pH��zO*�1�|ᱥ^�/�A�qlx�De':jc�{�2|7̬3���?������u�<��Y8W
G��L--1d�K�h?�c�R*�k>�����6�!���]�9����ug�Ʊ�%��[�WrMe��ԅ���V�4%;��B����U�\��oL����˫�Ÿ��t�bz�aLrQ�yKʛP����K�	K���0��$;�;��7���t}��r3��i�ufIO���pړ��P��|��< W��Q�A��ē��R��N
�qL>��8��q�Ճ�A>��?�����3�͘����*Ǥ12͖mr����)��_��I�o��D�M�]�6�3t�v)�3!��=1�"%�]S魷�8��w$�3t��YCk��6p��<%�郎zОo��ެ5}��g�g��:����|N�y�����6,~q΅�դ��y���������~��\��{��ku~��(L`[��A"�%���d
���\���c�{$�}��6��J�dn��tb����o�$�P�
��Z�<�=/���i<Aa�	�'���)�0e%�mzi4$�@��+ʬW��aҲ�J�Rn,MibR0*��@?�1p,��Vc3K&�>�pN��X���*ɓ��D���m��T��5�\{N�t��YO�w�i���a��-���e�Ƽ�_����	��9�2��y�JPFB-installation/framework/utils/parser/token.php�����V�n�6�m?�iaTR��n�m(�tq���\иݏ40h���Ȥ@Rv�6��ao�'��!%ˮ�V�������ߊ�h�x��
ϡ��va�q0�r��X��J�I�(,L��1K���N31�R͙�/��������j�Z�
m83G�;��;�i-U�R����q���^��jw���"�T����	:�4�Pe���l�t�"��8�o/>�[.�f9\�c܀A؜sm\\�;�!��F��O��8�ߝ���G�L?hWD]�qCuϥ�����g��?9��i�u7�:̄���Q6E:A��
pd�L9����|�!��"7W���\N�w�cc5Km�s��<k�E�A��]�aY�B�
j�5�r6������v�(�͙Fބ����^��*��<u�ݩ��O���*<��R��4�\�E�4���B?[��RK��DB[�mr���8�:Q������	�;���Qlƃ�*��9�������J"y��j�ѱ�:�9��j6s˱Ì���$�6
H�H�"g)��~ҟ��xJ�k�������h�C����چwB:8B�,7���9K3�ڰ�:9�!kD[��P�j1��	���'A�C{8-�H����@�O���!�x�6!�W�>i�Z�s!)�y$Ӎh|�Ԗ�e��7�vR��q^�*�$~N*;�#Sߗ�o�<�ܬ�v_�� :�\y}�M�=������+7����Ƒ����:â�1������g�H�/�R#|�m�4<Ʌ�q�/1y�,/y�H��0r��;��G�xT�R	i
��*+�K��>��j��0>7M���3I���O����b�Au"�����#��A����0��sR7
�3�\��ASH�z���UV���lY� |�����	�����}M��N[��i�]�[�ד�%���>��Ft�弭olOJ�t������ ��[5z�*�L�9�<�4�`�j5�1�f�ˋ�H���Si�0�8^CSќ\�1���j��ρJq5Q,��j��7
t}��~����pt|y~~z1�%�\��7������u�9~Cˍcz
Zs���|��V�ZW��ں*����p3�^k�ξ0#�K6f��k�7�[*��K�s���=xxgSJ�����uCH`��i^-8��U�J[���n.�K/�!�B`�el�k�x�ٵ
Q��\\dx������q��M�����Kw���?���b}��VJ}�[�>�~:`p��]E]����x�1���Hz�24�t��oU�@%��JPFE0installation/framework/utils/parser/abstract.phph?���T[O�J~�ŠF��K�SՆ
�Q!�N[�B�f=�W8���HO��gvm���؞�7�\�(�2!`�������FX��*ͬPע��)
+�o��湸C\#���j�[����B���*�piNJҲ5��Z��qUn�X�>�b�?�L��&Go`!x�
f����֨RU�20ns\���*Gi\���w�@����V���Fy�ڸ���TP���a�b&$�q�̾���΢�3H�C�2V3n���VfɴA=kUbS�Ai��sI2�1�7�AuL�Z(-��Sksa��#�T��AV��t%
dB;r���i�RH[�!9������	�aPje����vy����L�o����Cs�� ���a���ĺjf����h+-�B5��RY%���F[��0 J�>�J	�{�p�lG��6jt��;��g*"u���%����T@���i�41��vN}�Ir6�'I����������/�_/d�9�a��dB�+R)?�R�!!gj�����
��9S6�ï_p�}�w-kD����A%�Hq0��5ry��u�
Y�++xtH�fp�6H�uqNI(IG#�W�_�[�󊰻�;{g4�RYPAe�J�HS�N��GȄiͶ�.�!�z�;�糧
�΋	I|n=��(s<gn"�h_p*�+?�SZ#\'~h���5�����y
�k�4������zrC�O;��i7ǟW�G�~DOW$�T�/s�^Kz�V�����w�)z� �a���t/�?h.��zʯ-�W͆t�����ݯUY*M��_U�mV��ǰ��/��^����{ʉ}��"5$~|r�N�̑�)�����.W}�ڈ�4��+�#?�JPFF1installation/framework/utils/parser/interface.php�d���R�n1}�~�y�D�IH�*�h�EDQ���:��V7���V�g�$
PU�O��̜���b2a�^,�q�o
�M$�肊�Y��GT.�T���PA7fOHEڢ�1"*>+�<��	G����j��6�1�|L�D|z�
��l:���Mg7XݸV1���Y���Zǘ�8Vq�g�F��4�~Ă,�bӕ���qO����+��Vi���r��&��iy�SX���g1Hy
�iAQ���
�ら�+�]ƒ�v�!t�	ǩ17��]��6V'շ��i�&�TW��j��#���]qֲ�?��;�]H�"=�1A��K�ҹ���
?�&�M�S��v�2uw\�ʴt������lMP=\�O�^��Y��P;�1�F\�Q8�yɮ�d?�=Gډ�� ��A.�{��qZV��j'3�x�)���
�2�,��P2�!^��KF�~JPFC.installation/framework/utils/parser/legacy.phpU����WkS7�l����ct:�!41��4��%�| �ޕm���F�B<M�{ϕ��5���,����b^���=k�3���=�-z?d����J�V��L�eai�4M�dQ�d.o��D�؊�&+/���t$�:�T��v�m<D�I��U����-�������h�bkw��G�2��,6�nHGheT��Lڮ|�����d"r�O�?Щȅ�3�('���py+����2$����n�b*s�F������q�����Pgb'+��LPk#�0�v�P�\Y�SzAw��Q&��6�Ja���4�*�V�qn	9��X8�ɜ�q�q�+����A/�;Ñ1����+3s�܇��W+�t�j<1Vljm��nq�-X�Z*-��u�rT!:SwBS^.'���� Qm�U����X��2��c3��*��"���~hg�?(c�𽘖y�K�˲(��F�o�jK[Ꜭ.Q��G�\�'�A��N��
s/E�х���z�OY���Q+kp:�M�9�
"b�!�D��&���}��2�WN�%�E�z��?P��Yzrp@�>=}J�EP���x/���U��������_�WQ��������?��3��~���7����6o�������ަ�E�R�w��_7�����Z�;_J캑���Vbe����b
e�ѯ'F��Ja�7�w,s��O�^$}��̸�f�ب�+L����c�\�Q�7���nH�e����)���so��ڭ��ɋ���rsX(��<Fj�&"��V0����M�*D0��E���Ϛs�ꌢ�0s@�L�}��"�^�)Ҝ�*�.���n#��#FQ�{�N�8סR�@N�7-k�W=�T��|���U!F���y�����2��(�C3p���n�T�J�z����M��T)=��`܏�%��|8���>ǐ�k����,��^EP�&��P�r�ɲ�q�������r�U:|�ɏ��57���]�e��]�Id�2���ֽ&�Ve�z���̗
�$�K	"0�$��֯�
%^.���A�=Neb��-��H�W�\?0󬞖	���|%�I��9���T��-�V"9��'�ό�yG���J��0��[r�Y߇���[%t���&&q޳����'��#G�H�Ib?Z�b���
�0]
L���^bS��0���ܛ�z6�p�,6�����A��<��Bx����o�����aY]��z��F����s�̽Nm���t*4w���
��#?Y����"Vw�<�A���a�"F���;DqϙA%�ֈ��ɫ'P64_���X�0	<�ժ�^\#����sX�7���YռZ�c��~�jo���\@
�+j	-���x��Y:�&����#"'��8GmV����޽�Z�hl}��֔QwËVO�
c�����7m��O@���͢;�-���Ĭ��S�	ָ~�ܻz&���>�uOx��U�(_�(5l������jUع����Ь�5����շ�K0�6��.먭�Ѣ�p]u�z$�	?��58�>w���O�&�*����c�\����O
�����h�I�i*����JPF8#installation/framework/utils/ip.php��6���[{s�6�[��L��z�ò�&r]G��4�=�s�L���HH�"X���k��ow��C���]�2	�.���]��4X�����lxu9>gmv��L�g!����>�v����M-�!����Kf�܊�æk6|�|j�z��$O�'��y�[k��s��3[�Н/"6J?5�V��{���ʵ³$����@����=!Y7��1r����ܗH�����>-���Sx�>ꗏ<����=K�`!L�����>w�������F8.?�'��0�q@;�-��L|����q�Y��"��l�#���`��ѯ���lX�d�O���qP��^C5 �V0�v�܏أ!�����,���[��������f�݀�0?�<XK��nztؘY�'V$$('tXK�E<_���"\Y����y.���P��JO����F���8�N��(�yN<�Ƚ�`�p�=��-u�%y�I�7��8����cT�+��5��s�k�^��Dt�z�+�C�4~1���G��>�-���`` i���F�sf���<������,KXT����m�}\��Y�p#%�t�1�&I"�JS��] �\\_0��HWT���^�8���>[- j�b`�H��0]
��2�m��dqQە6S^_1x�Y�՚���+���;c�-/�*v��ط&Uؾ�&j�Z��TM��Rt5��^�b���/D[C�	��%{��I�p|�Ԓ�3�t�c����nЂ��b��W�-T�"�/�h�Ĺ��~`�NX�ס�
z��1�O`w�l�E� ���~$�I�D��^�A'�}:��*aH5��ɉ�g��'����X�����~O�F��~�*<�W�~��퀟��)+2�ʒ�٣p�g]RK�=/���$�Xb��<�n��`�
i�!"1M��N�-��C<00��?m
�t1��a�jд �Ç{�1hd��`&aA�R�����~�(��QT[������"lB��o�Z&�j������R�Q���k�	��F	�4�0�֟
�Ǭ��m�gM�B�_Zm�q:*"CtS�VʕYsX����B$�\m3��;��?�(e�t�
m�n�]	B�osNYc�F4a	
�x��S|!b%�'&Vv��Y��0�/��IC{hl��?h%y���L��+j'�L_�'����FcU�z��'ބ��V4�È�di�D���O-�wZ����/E2_��&n��,4��@�3��3m�5H�c����9�~%�,�<K���]�"eʬ�г/ST�`��$Ȉ��NmJ}{rʹ�K��!���w%Yʷ��b��f>;1�k����&I���@�[P���f����'�ܟ=5��r.�K?	vå��XAP��a�}����~�~���#����(
�u�v.�{*Xӂ�G�eU[�9��+	H+X���+ɵ��Ϭ��r���L(	o�����uC�~�G�d�0���JJ:D)F�!3�6��LGU2n�Ia59�U'a�����ie�CO���N��k��Qnm�B"��PcӅ�.�Ӻ�-��O�P�ʡ�	3���iL�a0��c�B�Ӊ��]z��	���(�%i��sţ�%����b6|�N�I�|Klt�E���pm��d�HL�. ]R�q�a��G䂩[L�X*�P�?��&4L�km�DyY)j���!�y�F2p
�h���e��4�z٫�Ifř�:~#b�L�ݴ��r���O�Q���;��t�E:���s\�n�<UhƂ>~�j_���l@���M�j���0�����sT��/FdZ�l?��O9Aĕ0�!�P��	���B�G�{�Ԭʖt�%����B�WJ�*P"�P�:u\W�r�����8e�SH�oـ��+G�$��f�����D@��vr3<�܎/�74Dݗ�t�2>udB��H��d( )Y'�0mmO���c=ՎS�UO�f�8g�%����8F&��]��G��q�.-�^2F
�3�-n]�� ��n��'yL��(K��ǚ!��w����2����<F��.�B'���E)f_�#Wm�����̾����r����Ľ�ٳ�ߢv��(,�D�s'�$
�!o��i!oCM��"[�J�=;��v5��<�vx���ަĹ���/���:wX`��Ih��@�%9hg{�1.�TM�q�>	���4FLA;)wŠ���f��glg@L����@J�I�/3Y�RϻT��bv,'��ǵ�3�/��f�tV9WBbuj�m�Φ��eh{�L]�)Y�v��%�]�֬]���]���]{Q�Ι��^L�3�R؈����c/���{l���#3�]&Q�@99�"㹍��Xb�V��������
�Gz�
��
���M^��":�Ks��)�GFƁ���68�:݅3$��srw~���[�X��ȍ��W�L7�vb��x��.�/&�l�(�g�'�6A	�l䷐�üH�~~�(���[�WqT8�W7�"�@�cvu��?����}�qQq���p��LI���|�pS��ʑ\��Ӱaᄿ�2˟�V���:�0���/�~I�V��v�0���k�l�8n��=����4	S4Iҩ��Ј�K�(��]�P{���h9ljy�o�;Wg�r@�Q�A��Z�:]P��b��mF�r��ߎ@������A��D�v��E�sن��q�͟l�Vې��`n�YDK/����l�$�4�<������tW�uە�ӫ68�r�6*����ې=@�^G�‹ɮQ݀�Y*�Y*���|�pH���ч"[��5c��w�0ܼ�*{�+.����sਏeը:�8��J�c�{�r�����<��MI�h[I!EŠ���Q����$�h����#��V7e�ا�"b:��.�NSWb�3�-\<⫭.tø,�?>K���Ej���2ɲf���EU߮wങҥU'��8`�'��M��Mh���$[c��5�Wơ\s����h5�ز�ֈ�j�>���v �Ե$�[Yk��ؿy(Z��0FdZę�;����U�2���$/��`�]�����5�A鱱����E�";p�y`L029�Fd�"
�D�>�}�"��5"I����Ѣ���\u��Q���a��4Ԏ��@�~D���� �#M��t�5e}#�ePG�������	nu�p�	�]p��M$��,�ej+?��=�gi�1�_gcMޙw���o���P��r���&�O.�oޞ���b��A$QqZkU�SWfz-C�EXD8�RI�΅g���]LF�WW���%��NҖ&o7,�]���Y�O11�1!�u�XB�c�[��i��v<}�_�︤��šLw�5.�y�։B˗�������R��l�tVa
����ԕ$ݜ���y7y*��H�V�+�!,�\����b%mիVU�<)����]�}�ʓ.5���直��$]�)����!4����#�ier�s���n�Z��Q�/�=�1.����#��.��dI/����^��|�S�c�hS�-p��D%�.Ryx�L_�9�)�ϩ�4~��K���J��F��8S�oޭU�#YFxrц�K��8B�@�QUpV@�V���j_Z�t�
{� ��v������k�H}e�Q/�+�Ko���D�o��o�����c:��J����O���l��Kv���s��œz�H@wr���#M�Qv�,����nO�<��co[��/���iB�h�]��,�I�Hw���SO���:�ϣ��/_�-���cT�φ�^����z����A�?��P�_���ލ�7�тl21�0t��/S?�d��$tWK�k@�
��T�I$�jv{(�x?1��܈&|8)ݔ0�(*��*�M��ҙAo/b�0vR>-�ϵH�I�qT@N���JPF>)installation/framework/utils/password.php�"����{�g�W-�B$@�q�+&6�6�ܘ�m��]�f������93��_�T`�̙�~�ӟ�i����ނm���8�r�Bq��Ta̔�^,"�0�y�I,���K�b�����%�.�"�+oC?�;�5�#�e��i��E,&S�ٷ���u��wz��}8�4�_�p�-d��J�w�������\�!x�|8M\܀c�y�c����d�Gb<��ʄ���"���P��xs��p���:�4�|1�~Ĥ����LNE0���Q;�%j����3���"�f���稈v�ճS��'��|>o���I��vO:BvfB������<�"�^w�G�5�0�����ڪ�14�X|�pNo߾sv0<x�����i6	�f 
/��~==wZ���[��"hA��#��z���B�B3q&RS/Ɉ{F�̟�1jbF��I����@�X�׳��Ed*$��N�@��@]#�_�Dv�>��*F(��3���X�d����~�3���H�3���H����L�U��/c�#	���`ld�f��*�ۄ������#�M���t��&�[�ڌؾYh��k5c)�!�$��
Nj�rR�:��xH44���]�%R����֌�hь��������˳�����!R�f%$�O�IB�|r�֑S�@�	����Ȭ���4��r��B[V�Z8�iY���I�B&\�E�-b\��_'�:��W�<�`��C��$�1o
)jC��Њ*��vj����Ԕ)�OH���3�o��(B�9%'O�0�œ�(&*�P0�1o�u/D
�aאf�*��(Rk�
�|̤����X��<������n���%���
k9
��/���P�����~�ڡ��k��BQ�����u�(-�d���P2S�J.f��"��ࣽU�c�W"bT�(�0J��3����,"�,Һ�]xƴ�3a�{����ި�q^�˜:^���SA�D%*#�d��R�p�и4�ђ�X��T��e΃�T�EbԠ�k��Z
�v4�����Gu߄�4\T-[+Xvꙙ�4��\;O�+n���EG	�G!�D��f�`�f�Bf�	UzR.��b
�?Q�&�;��4�����p�BKA��7��DF�M�Jie^QOTP��e��X��dM'a�c�
����B>r�ƀ�`h�BJ3��?w�O���:ӡ5HMj�Κ	lSw8��+��2��]�!H��d�q5{�6�����Mݨv
=Eˠ�>D1�1�xӆ������oݝ����{��P��Ҍ�.���l.���n2���8{ŕ���E&-UB3�bC�
ŕӄ~�B�jp���˳�Q�VO7Ja��)c�nxq689x�YH�O!1V��Y�6�[ڝ2,Ҽ�u���˅�a$y2
��Bq�����|�s<<�ze9b�Ngį:�!�H�X�US[� ;)qu�o1}K�=4�
�sV�i��*��F24d�>n�a�'Y�&jl{�*c6��N��o��Ԁ��fT�P���U�]4ꂊ\��
j�v�^�I��\��k�u�Q���_�7�3E��趠��a3�N)��|�ey���b��o�mYG�6e�2Z�i�L3
]������\1,
�)N�iw)@��HÑ��^��xg�b���WG���ɛ���=�x������\��d*���gA�K�\�?->ww{�<|���dq�nZ����1��[��`q��WR�����ĬQ>�����W���]�Y�o0��EԆ�tW&S�	���D�5y��C��L}]�	���D���K`wt�(��q٤���k��.�z��4�9DRE`���
�b;Pi��pFX�������#��s&�	�XS3�0�񏣻��`�U^<�B�6�l��oKSڮ �}�S	i7?մ_�[kZ���*��9�I��32�l��bV�F^4�J%�Z�LV���1�
cIF�u{K?��61jr�%�s���H(�f�:��6�r�M]>:�1utEp�=>���L����Ae.��F�i��&�����D4u8��)��w�ιY����c:�о��層�Q���T��Ѹe�l�p>�v�s;Ud��4n
�L:�l�P0�#��r�YN7��Z�ƺ�*��tp[��f�t�c1^�, T������T[7�r˓\
�J��,.�n����fO�6���U�p<ێ�9�gI��p����@�F���N���֏3'#���u�/�-S��k����n͐~Z���x�`��j��%/�g%W>*(�S�����,X�:6a"@"��Fc������w�_��G�E�C̛�7��Knt��W\YZ
�7��Q����2K7��c���',_�?��
J��_m�h6D����o���h[*��X���r_���҇�#�4���ߚ	Zh�(�Rz��"�;\WR�/'_C+����"A2s1�`�B=�N_i�Ȩ�=̨�ၞb�Vg�kڗ6�U��=���=����hQsL
�mls�3׺�T<�/�m��(0�:��r�s�� xۛ2]��XnS�1��s�V�E=�N۲6�
��>��ߌИOX����Wi|$ko�!�����'��Ӿ�Ȉ���yJ�V1\�z���-�ؐw��ˌ]����H��Fv\?�B��v�t��{d{�Y�&e��
ΔB"�1l+�Br�1�v�V��X��0o#�(�pQ�Dzl5\������t�Z�*�l�|��6#�(�JPF<'installation/framework/utils/buffer.php�����Yms9�l~E'�����ݻ�%�d��ľ8��ʇ��3T�)Ic���_��fx3޽:>���ӏZ�-���t��Z�_��5�]}��!܎9ha8(��T����H
��'Y
L�c�5��3�#��l���/�D�JO�}��?R|�FF�۵P�s%Fc�O������Ó��_�J�c3
_��	͵LeK
��ǥ���X�<�d���|�	W,��l���?|�JS\�4C�1�ʭZ-�C���Ͼv:΂�[ˉ�
Cs��9�21L$�q*9��K�i�^�
�FҦ�j`��������	\�<A�2r����
�H�f��_]�Bʔ9\�I/�F\�ܻ3�&v3˜3����!�:��re
�J>"F{dp3�MǙ������p����Ę�=ǖ݂�7R;_&����5�@��,��L#`#�2��7J�ޚ�D�mG=���
3a�6*mSܜ�X.�\>���5L��H�H��;#b����G��yט�1}�k<0��J�9��j{���|���{L��܅a �Z�n/��H ����biJ������\a⾯zH�'��x�{$���~w�	�����6�+�9V��Ir����A	*+�Db�Y�����Q���xbh7�l�n��8Zx���T��[����4(k䙕���Z��YZl�<�j{�{h�.t���;�v{
�(n�!Ck�6(�a~csI��RJ>,F�a�DtJ��a��#�m�.�<�>D=]�R�+�s{����y>=*\��C�8���^��D�;Z��
�Z�%�xTq��1y��(�tς��pIJ���[ p,	VA�C�@M���/NO�`�l�S����u��sv����{�m���)*Y_K�HD�ʳ�%����M��EtɓV�S�UM�5Q�D�����$��EY6A4X@�`	®n�� x��
s�̹ŒӉ*�fc��y*Q	��}FM���g��u�TaHv��c�Vz8�`Ŷ�	��N��9��Z$���<ЃD"�&b:�O���O�P�z�'��mhO(:�5Έ���y�L�X�K�\�������H�8�x��[����ض�8��V�B�=�t��#�ͭZ*5o'�OS3�/T�
��*d���8�ՃFЀ������y��,-�&��xU����].-��I������'#)���%������v�h���Rg��ljjp̷U�㈈�����V���r윕��)���+�֢�.,iN�9�u�����ˡ��"����q�3��P�[�G�O���b�xwJ�X���Oe?�(�3�[X�4�[j��Lŋxp �ƛ�I�a?�ŋ�Sw�K���Ѱ�C��6�)O�Q��ѷ|���R��9���)?c�)�;ʻ�瘍���W52svsѰw�0z���ٷ�]�C-����/����\wo��n���[۫}ˣ���3L`?�L���5�P�}��ic^�f9m���$�v+nS��j(>��1+�Ĝ�UM�>.;;�r���K�ޏ�����4Jj��n�Ԃ{�6��q������"kh{ƯΤ�ĊI�Zo|
*�.g�ʉ_��Pf��\�0�L�9V!J����E��R�<����ر� 𨹚
S���O^ՠ����>ZHy(��l:@l����cu�b��$�#�׮(����aY�;��
��؛pEٿ��B��殹����Pҩ;hy:#��t����'Tc��6|�k�MQ�ɛ�y	��ܺ�%�k�h�+�d)n�#+H*���}9���|`�HŶ���֋���vnRqFnr$����0�l�~��^�2���N�k�׹m�O�
��ֹ��CCvtD�f>�<7[H��k�<���X��1�����L/�k�R��uh��s��G��J5�ŧZ���I~�|b,�`�gk
n�j4OB{���	����o�C�3M��C��9�b�^{?�����6}��뾯c&35:vo'Չ���6�C���+��C�*�;8j�%���%�0�|響��V�X$��"��hYw���r��%-�,<�qN
%��
a���.
�s�K���':������.�kǀ
7��$�~eܥ�4~n$�헎���۞�i2�G�};p,$�5�|\q���퐭���l��b>#�|X*�]'�����
�%�J�-&�������l��w�l�@S}���<�,=X[Y2{���|��p��?ʗ/�m`�e`��%T�7�e�Z�-�.�s������n���������K�e^�-�D�?k�JPFD/installation/framework/utils/handlerextract.php�w
���WmO#7����!��&
�>�])
p*M��A�f�d-v��=�{g�}�^�}+J��3�<���L����v�Ѕ���!��8b��a��6R�K:T<50�
&A��H!Pağ��P���)L�p���$�_21�Wnd,1��9��`��`Ι]e�T|8-�ya�`o�݃���0�a$�@�o>��	-�L�"��y�K3��b2����Ι`*��j1�
��6��҄�}R��7��A��B��-p�Ũ 4\����
"�3\�f�h�)��� ��1���L�:�5<��x�O�A�z�`!��B)f|�P�PA-�O��q�5$�Dr
z��R�<�fa!�kG%�a&_��2|ԥ��
dRE�a'�V�(fJ�&_�"�F����1B�jbJ�f#uu�Q�0[�Т�h��8�f�i�0"�c�7���e"���Y<;<��E�Sqx�t�.�|�e�්-9�Ψ�v�~�=0aD�0�(:�`.z��֤݃vi۾�Nq�X@��El��3NsC�<ӃZ�
>o��"��<uȵ���j����K�~� ���s�`]ī�f�*6H����4���3���l'�]���,˙R�~�~�C�jf�y�(�4�"��FK���d�2��<s��V�A�(���l��o��Zo�	���r�]jd�|)iXHCwC�%����j	FbVX�+�2AJ�&���>[�>�| q����)H�)&mf����������$J����U]���*�W���?��n��ӓ����ՖsY�T��B��̍T��@�V�耦=�G�a�if�V���%�^L�|ޚ���=�H卙�V=v:�L�W#��jl$�o�����fJ�*�f�va���8�D[[��9�1’�,+��[�F��Y�}Ic9%l	�:��jQ�W��2qp~����_U*XbyR�M`%�(^�N��-�ஞ��b���j��
n�U\+C�Ô�b��r�o�q���Ķt��H�s�O�վ���\G|frw�f���k`Ó`e� 	�6��;��`�{t�篪�U'�x��i.�j�sh�w��KdZ�͟����~��R�ld_�xg�{v
� �v��O�Ԁ2zP,���t����z:z���.�k�[�x��n(��2OX��Ơ\���~�:C�^��{>܆{�dLoUD؁w��b��~�ho�-�{�����2�cP�m��@mϾ4_�JPF<'installation/framework/utils/pwhash.phpM	7���X�w��9�+��m�8��I˫$�r�����we��j+im��~��v���4��s�43�<4�IO~ȓ|s���M�OO߼:{I;�>�d��d�u��tF62*w�׆z"9	%j,-EF
'c�M��Pʞ�g�,���ө�
6�c�U1�D�0P��t>5j�8z>��5;�;�v�ި(ѩ��s�^���\���[�x�b�+U��,������4"��z].�����6���nn�ݼK��8�K%���S.��hS"l����I|���_KM��!c�A|���a��Q�Ɍ�� C/�U`�'��t.��H�V�G'�2���}�c��"B|Uu�Ƀ+�	���.�XY��lѳ�Bf��R�M֩4�"�!�F�-�pN�S�4M�1"s��J�H术�}�]��<��#޻�8�?�ݝL&�EvQa��nl*��C�F��T�1b�ݪ��L���T$c�J�
���"�$�3��4�P�KK���q����r���2'"�M��Y�"�6ʍ�]F�2xG
�GYx=��4}6D��z�� �[e�ȟ�֢��FTE	j=@��5��;��5�.���(O�9��Ι�fa#QF#1��K����aE��8�	���(�
s����<�i���0��
7<��y�N�Sd5�b���!s�ŷ��҅M��2 �!Q��%��_)V�*N#=��@�A�*�=��8V"�q�d:�͐�>�F�t#ΖQDH���P�|��ksc�sUSN���dz_2��n���uS=�Tkyy޻!��g��Z�l=����Y�a:2�L��Z��+:����Q��htL��n{��px���O�=��W�g����/oޞ�����~��o��(��A�~��L�������:�@���'t@_��w�Nh�k��1=|��y�k;��Y��|�ḇu�"���F�q�g���������@��4Wq�|Y��uL�.���b6e�%{t�fy��R�TӅ�W�U�GC��6�����78i�`q�K�jjk��C�)���*��Q��~���
yD�f�\1p٤'OB����(�ON訹����/Y�a��!���[���|+�[i}���+MKn�j3�&�R�Պ�"��u�N]��Zh��&��6RY��a��F���ˋwgo�pl�&�@�������V���6�Gf�/�]M(�	+��X
�v�:��>�iT{���M����qe���I�
��b�\'��Ox��–c	�,ɟ=��/�NkwJf�I͓ۏ�Y_���2[t��?����՘0��&T������c�i�r֝jK������}�IX��T��M�"��@��c)��9���^>�Μ�g-�DL��!d�ɬ���š'�lW\���t��}��y���)�<�
�����P�����BK"f�82�������s�C�J�d'�IKk�R^�������T�4�%��(����4��1~����C�P�s��t���L�X��/�
�j �E�=�����'���dy"r\�S�x�h��P���/�K��:Y�}�����,v��A[w�Ђ��u�Il�U먚f�+�,wM�K���Q&el�*sR��E]��"�HIȃ���}�Q%(�E�,M_j3��@5[U�G�9{2l�@���r���O=[��*��R�Qm�C=�,*�����hf\��2���^��KثCi幂6E���H�WA�H*�8[�G.<�*���{l��P%W���m����1�=�ל��e��Sl��W���:>+|�Q�7�������)����E�O��֯Tk��+�5JL�9G��	�E7��]�k_��o���z�T+�/ڻ��X���t�	�
���Q��C0غFaе���`����`�S��9�������
�V	⊡uk���n�6m��y�1焦�1�P��r�>���}���:{w������_h���U�7>6��{缫�<�>�D����T5��z��{�#�Q��(�R�N%R�+9j�8�]�[�����\v`�k�z��K�$������X�_'����/>�/�7~<��6�/KX�
�t���o�j�IQU�o�ݞvI�3ǁ%�ш�4��mR6Kށ;��Î�w,��0B�U���Pk^��~�"�'2��$�I���}]UZ���I²茋�O�Khi.ҋ�"\V1��矰��cb���zgY9�16�R�~8������i9�!Ok$�My�D�1���'���=�@��@��b�a��*�m;LB�8�OG�d���w&��}�L~��N���ٛ�,/36γ7��ݢ<��0���+��W����\~�?JPFC.installation/framework/utils/tokens/tokens.php�����T�O�0���
A�(���l+�C�(ڇi�\��XM��vZ�����ㄟ�Ф}��;��{��|��H�����.o�/GЃI���AP��T����J�`��,�)��%j�
���k��N��<�#�2�TÖ�ZP�������Z�Yj�y�`0��;�̘��}�F��Z�̤�~]���+sm��o��sT,�q9�\�������$e$@��~Ę��p'^�F�Ý�=<
N4���D�	���V�ĒP`˸�nUɤ̹�0��̵Q%7��1v��l���$`|1��D�PP�3�����;��T�h�&bYv�mK��P�8�
���,
�Rj��`uS;�T����)��Q"2r%�t�P,��BS*
�����&����9���D?..'����l�=��.���0e*^:%�'z�,B{��kc��;3<
�V)3�ت:	���R"��W��K��
oUP��!	5|�v�:9O�"7�P7�Zk>�4��e7��c��\����w]���p�o������
�7�m�J�L��
r\��cak��2��}=*�{P�����;g����D�`�n��+�
�߈�M\5K�{�����=ۍ����5&�^5ֽ+�e��k`�^���mw����E�i�6���^�:/����#�g���S��OG��/JPFC.installation/framework/utils/tokens/parser.php�
���UmO�H�l����T���OW�D�*
��V'�"Ǚ�U6^kwM�N���ݵ�8GI�~�6�y�yfv�Ͽ��𻇇>B<��҇6�2�4�D��L49�T�B�LH�$�, �iƞQA*1�8��
�9�$���#�֖��0��}M���Z[*��dO����)L�㣣?��Gǟ`��L�D��\���(�PЭ1�����,�\��W�{��e�ᶜ���>>�T��O-��8 )��S����q���?�?D�a���S"� �׌���S��D*���
ɞ)(M��ppq3��h��� /9>��9��I��ɜ�i�pE62��	x��@[Xkr��<��S�+J��p����##�#�ށΘj�91\�^�H]�*c)�UX��DIݥȣS�F��ڔj�6��hlR���4G��.�':}�+��&Zf�#�F����A�:���(���s��]�$�h>5�5i1����c}ޡ���M(���@'�5��RiPH(���"��ùX���bϘ���q���ǡUq��Y�M>9�c��`׮�G�y�k��� � �A�}�\ķ��x8��2��!A�I�;xw����+�}U^�ܟ_�M�7?Nd�l�0��懮ҳI��v�.DQc�e���� h��+�(8n�۔�}�J�z#����M�Ǐn��Z$�R�Pex�>W��}��[��+ߊl�l��m\*�&�P�W3T�*'�?t����Z��EѺ�=<�`5)�m�W���F�:qk�Z�v�s�a��<(��gm�[���ai�pL8���
]\�����lއ�{R�����b7SgR,�b/���ba�e�/��7��:��h�$y�62�|�����������%d(�6��y-z�{{��F�<�+�	���]�Zw�v�W�=��v޿k sAB�J��;�5�CY�#p�$�c�I�Af*�*���qY����?͂X�_��JPFA,installation/framework/document/document.php�����Y[o�~�~����Yq����i��H�&NP��k�.%��%$eE(��;Cr/�����!88zъ�����~����ϟw�9/ޟ��\M9a9hn���
%�$Z�JÈ%w��N��H4g��0Z�����@!�r�Re
u���
�e���Z��������S���sptx�
.D2U3��ޡA�
5˔���6u�2�piH�����=�\�>�F���=׆�z�t)C42��vS>��qt;����t�� <���
c�q��%-�Sc�r��_��3�>�p[�_v
з�I;:|y�
.1L3�p��,�c�p.�AC~_|���ij�Qˀ�["M����{s��\:��3�-�l}�)�x�Y2��k'���^PhLPA�9�Q����,S�w�r�|�
�Y��M����,����w*��\����*�<�����c4������gp�i�.��8�ź��.2��v� ,(���O�Lß÷>�h�(0"��$�*��8�=����'�"c�B�L֭�dk�\�r�`~~�v���_�W�+�$-�����f9��r�o��1��xj��_Fɞs���D����ʵ7�Q

X�����d�$L�=��N�2+��?9˲^��U�����_��/�X����l||\���<����
���N[ei�%c���2\&H	��K���z�mb&o^�W'nծk���s��ĵ�=bw�!�-2��/��*η�(b��x{\��o_��sK;Msz�~MI�%q�F�7�;x�?++��K	�|'�"<�i�l�uyX.9�	��9q ���Gri������D]�Yʃ$�	��{�邺d�A�-G�kڹ��|nOkD��NW�v_7r�H|�W`�\,Q�L�>��7���5����1LS����qJ-����I؟���p��>���	��eΑR��$%�c�D��UAƳ��S�!R8��@6C "���
�	�͏mFy�\M�}<�x�1t���Vh��)�ʡ�� ��ȸg1�/�b����[����5I���r��pű�n���<�~���L��aܐg�씳�YvkG�<�.��ED�sa1���f٤���휭�Y��l5扸��U����jY�M�A���ќN��#�����u��F���3�2�B:��(Q�H�:~���}rY�n�R�֦�?jA�;>l����T�uq~�2���E��1ɸ$!�����˂�K�XYIΨ�h���y:��J�t��XxO�Z�2`��j�Ǭ�W�VȢ$�c���NP�ӎH���GU�e˽T%su�1��w�탔�f�$��dIٶ�?Ҙf��դ��`U%�o�]no�p��h�������%���_��1<w0"J�_8$���@��b��F��i57H;3�%��ЪЂn�6�Uw5�Nz�˗5�_L����~��7�o�̜�	�P�4Y�r�K� P��*��	�3#�3��>k>_|�������V��[��R��0�iFESGV��R�bZ�|r�S������>�d�$gz��E�@���Բi��A
X/֕��d|l����ZO��"�S|�����C��|�)�YL�z�F�03��0V����� M#%Q�'�L\RN`���ʼnK��@��j�
h1]��/E�RG?�Lg��>��k��M���Mu?G:����9y�+�����*l��#B���(�;n-�K����dp?����\w?�zZ-����8���Kx|�r��<�?����N�9�j��v¤��gr��S�xv��{��T8{��#�w��O{��c����!%���mo��؞I3�jǵ_�o�z���@�Iry�j��P�o#_�l�����Ħ��eY��
�փ׊)���n���JPF=(installation/framework/document/json.php)���MP]K�@|���
M���}����R���\.��h�;�.�"��^b+�-3�3;{{o*�ƃ��n���o�IO�伶�K��ƣ��ƀ[Q�9K�S��tO�q,�
�_�W]���<��� (%u���heYy<�M�H����p:�ΰ���5w،��6�������y�UKAʵ���;֤��/Ml�䁬k{ͮ*ա�
�c�r*��<����j���V�K��9c"��.�h>I�g,�˓���E�7�h��g��l��(��2�z�>F��tû���)�V������N�JPF<'installation/framework/document/raw.php ���M�_O�0ş�OqL؈�'�Q�b ���t���0ۦ�Pb��vƷ�N�����ڲa���G�Y>.0�kM�*�`�hx�
(�C�Ŷ��N�jG�$Q�o�
��ɡ�Yy1��]̽��WDC��	c�NUu���+�x4��G�	6JԦ��+��B{o�i�1<g��<�j� �������Om�'�#�^�K�JM,���!c�J�I���|�X��^���4�2&�
�܈��tx柠�@Z��7K�oZ�jq<��riƒ�]pj���Y[�����JPF=(installation/framework/document/html.php`��UR�n�0=7_�R���i��VF!��(MMQ�(Ih���2@�Y~�=[yy3�a�v�A�3�V������r/�'�46�B�Ŷ2��(���c��-b�axa���Y�R�Gm�n�9!�x�	m�V慇�[��~���{�G�KQ�;�v`D��N]��A��1��yV)*W�Ͽa�
-/aQ����Ѻz����JZ����X��0k��x�$�8lՄLb��̘������]	x𨲻.�a����TJ��g����b�A���Ԧ�

_H
�V�����Ҟ�
r��Np�]p_�>^ī�z5I��%t ���H�9�e�!���\I��!�>��JPFE0installation/framework/controller/controller.php�Uf���=ksǑ��_1Ω����|�;���M�*�r�N桖����b�J,�=��G��.Y�+wu���ؙ��~wO��ۮ�G�_~y��T��'��}u�֪Jk�J]�E��i��jQ��Z��R]'�w�V%�b���J-J��z������׉zlF�K���"+`
\��[���h�`�M��E��+ӛu�����<���|�Gu�.�E�T�z
�UŶh��R�v��zI��t��
�8{�^�\�I�^5��A������p_�(�R(a����R��\/G��g�φc�L�h����?�8˲�ӻ<٤�We��e����h�Uj�����2����I;����	��M�*V��~(��q�MJ@\Uu��7����`[�^ ��!�WP	`o����I�@'�z�Ԁ_��Y\	�B/�����*`��̄]�T}��x��0IB��er�Delt�.�Uj�ú���_}�z�����`an����H�n��D��5B�4��b
%%�+hqє�kt��ոM�M��tysqj@�"�@�3�N��4e��5lST5�;/�$҇/RG��-�.�"�2�L�n���=���L�r�o�l	M�N�m[��ot\A'U�a�0��F��4
VI��LW�N{i�(>ɫ:�,�	��ؓ@��0���N,^�$�R�G�h���*����vSÆ���қ��F�G���j8�[yQl�E�V���Xd	�Z�*,�7Y�.,�X����ˀK)�&��_U�
.8�u�diՁM�X3`@����s����V��4��?�xΤ�j�l�m���!���^�<\���P7`A��#i	i��m�.0��^!<�R��E�<����m�A�"iIac���u�0Oo!0a��y~����^"I^��\��һ�]W`4���Jg�.�qI��d���С"YC	T�3}������4�jU�P^�c�����P��!YI�?�U=1�(���dC�6"�%I��$Er�E�{Ŗ�1��(���7SDm�*���Mf
�siD�LN��`Ơo��#M����	�t*E�)g
i�b��w���B���;]�І�&�@��F׳�z���K�ęw�Wlb&=x�����A좪�D
j
�ٵ���#�r2������9	I5�	p+�RJ�']��j�X�<^0N�	g(�'�����Ç@'G��o�O�s܇���j��{K_�Jg+u���47�vT�l�Hk�~i�y�A����W�Y��`�:�㪼�a�3���֠�ն@}@�+�c����4�C5y�6��&�{)!�aH���,��kh��|F=TgB}�t]!�>>Fa%S�1���A��b��߸��=E�m��6d�B5&a�(cp�D�l7���h�D6�хW1���v��Wސ���)��iSL��rd?/=�c,��Ձ�X��^<���0���b����P�礵��:8�14�[�X�eU�Z������G]+R Y5+ȣu5��{Q.A!5��&�f���mf8�٢D%��������鳋�����0Sl<� #�64\�c+�'6�gY��$#L�L⟪�+Dݖ�P�^�.����.O��yN��E�5d�a��&�k�5$#j#��=��� aX�n�ygf��H"i�s��&C\�k�E�3Q�:J(�e4�6{`��rƦ�7���@;��Z��[
��h����c�"o,@��/n�w�n1�A�;��T]��I\���,���‰<�H89^�C�Z��l��FQ9��N:@<���L�o��z�H˛"a�J��6�(�a?	���Vp7��ΝI�M^��ʢ����D�G,Bl	s��K1�!Vm�⽩ӬB���.�(��I]d�{ppV;����z;iwi��Պ&�9EAT(}B��$��Εl-�-'��2������ R�����k^�M��F��j� ����5�B�:�J�B���u��O^��BU@t6�K��ı�����̡<�����xg�#�\(�!Dr~ʨ;�������$���n[a)-��M���s�	I�W��5aso��c_��������C�r�	2,8�qJϰun�%�o|�b+;�<]AI3ňm�v$P[�oC5��(aaY�en;ޅ���w�?,�f)JH(q
W����80ge6�G�z�05f�u5���Բԫ���F!/��Op�6��+=>�J�
���O�_><y=�����F�Ged��m��3_���8[.�y�,3
�b�
�p!��Og��\ҹ�M'�9��sc�<V?��,
 V�0�yL
�
�[�|I
��~�/�
�d]"Ad?	��6H�c蟏�/�՝��1glA�ҥ��f�v�ؒ����+(���h��Ʌ&��b�J(�꡺�p�Ө�jC0�}�3̤B�`�����]>��|+�8���1���y���j�I�����2Z�f����V��_!�hw����D���.��vf��
Lۀ�����1:J��(�g\ق�YN��-&�ZK-`���o1 �Ca�_`�v�ւ41%�S��?�q�*2AY��1���b�����lAo����R�?�q�ީ۝@��o*�\�YƯ�
��!y��
�������eJ��kزׯc{�
mrH�����W2�C�S	��X������������QG�ȽN���(�Iӯ�fZb'��Ͽ����	��ûQ��
zҒ%�kƱs��Ծ��~�`e�K���Ȱ�Gs��{��;�޿�K;t�Q���w�mөg��Pnqg6��BIu�?�I�����"��9�b������<��@Ե�����D`���fZ+�3g0�AC�s�)��,�W�w1{0r]��f��	�l`�JҬ)u<�^#L��M��|�H
2#B�B�ؑ�մ
�_d0IӬ�K+t�Q�IC�˹���A80ֲN���V��,�w�$��/�b�٥�P?|Xm���
��'�٫W��P0vqq~1����~~v~9~���)�1��D���f�޻�	J�V��R��L����DI0bۥ���m�al]8w1��	�
%��V��j�f�k�%+�t���0�+�e!�m�o	u�E�U�dQ7I��֨��\Cvnu߁����/�Ŀ%:d��}%�����R�S�Nլ�қm6�x�.Hm3��z�%� ���iOA���Ț3�ŞAG.G�wt��y�p]o2���kyk�	MƊ߅�t�$>Ʋ&x�y��j��a"�&�V'9
b�WM���+A�Q�Ljw-P���LT]6�3��\�H�<��|�Pfzc�7����C~t%-kG��^�sFrb�b^0
�T4%�.H� ddB%UU,R����LB���ֹ�k���m:����d�u�
%�U��u���s�ed����!�=��.� �7��}��ϳ�.�pV��g��A�c�O�M�v̦�ZN�w(�oS��-���&Ԃ�S��fa&������?����"V萙�	�:��,��ɔo�l��r�nD{,z.I��Id$�v�V�� B�C��X��$�
-�S�ٞJ�?4�F���������k
��w�)�p���v}�Nڸ[��e.U�z�;����wA�̓3��˜r?�0!��o����#5<~�_�����_�W�)RW���:�o��82�l`l�8�({:������dc|a�QT�ĵ�kgD0�*��J�͆D�Zw��ђrՓZo���V����z���P�E���h��p0��[P���H��w�XQ�'�힆��VӅ���Af)6t��"N>H�[��nu�G"��S�ks�	���=G�,�Ʋ/C�'���cC��Z]��QF qu�V����x4�r�4�w<�l�V9�����CO�2�4#�]�Ɲ�v�����*�����S#��Ai�=N2�\Ofx�b3%IN�UCZE�0�ꡐ-߲8�4}_J���DA���+�>��m�mg�;�jq�с �j�4�(8�u���z�֦C���G��C�uN�6&0Ko�S�����@lW�';�|����\6M�m�7��w�>8�K�
�y�j�������7,T;:H�S��4[�l1�W�>
��e,��DGg���
3��ΰ-��aj@��?�ն��j�heN�pĶG�F��?%�3	eՎ�@
�OC��7��`��O�#��q<}]��Z$|��nE���h�1I�G��+
�~�з�#g�>��E��f�xh��:�_ݠ�vkx��ph���q�1����dZÁ�����`�+����BY�:���P��џJR���1}נ�5�Y�,�g�A�Yt�F��I��lj�9&�[��w�F(�~{�QWa��G{
���j�%cQ�q#؏;��U��֎�=�-��q_G
6%k�~Lg�����4�}<c�uD��#�v8'��D7�iO6?ԕR����S4��"͹&�}���z���-��b�i+3��F�68�ȵ2q��};aP�Ok���9ذn�Q�з ��	,������;��#�u���<Ȍ���zn_d�=iJ�$��W$�;�$ �L�D���:w�B��Z��IL�����E�
����??:`����+����={���v�H�g˥{uE�q�	*S4O��Www��Q��r��C�GZ���g�Z�8tOX��^��9	��8whwt9�;�穾Iw��0��(!�\�낅�F�i�\R�W{
��(�Gm�֓yKd6�phD�߇�
Xю�I%-r�j|��6�<-��³��f��`����t�>b�	d�y��)K���ԯ�	�-@�z��J`�f$���Z/ލ��Í1�7�AW����5��?�D���m�6&�w�)C[ֹ�*�&��R��C�
-��]�==y~η`;d�Hn��Xr镴�Fܤ|���Pc��r��S?Dl�p �`RđXCL+Ү@�u�@;h���%4�#7�T�^٩��ͤ��]�C�$�ә��9B�T�靹���",v��������b�CO�D4��	��ۍ�bf\��Kw�➒���n�K�PN��.oM@ť*�M���d��{�Ÿ�E��S{��/V��L�;�����:_Zr��y��Û�9`�����O|be���k��Ww��/�u�3�%3�\tp���}Z�!�����
fng�JY���
9��|r~vyqNo�xvI�����]R�_����P��O�j�$��z�d,8����'��T����>�o�S�>�z��Zo�4�Q蚗�@n�۱c �q���	!n��k��Fѽ"��;0&��X��>Y6͈+�t�Nc)0)�uT{���m���X���~�ZB��;����`v��x~˓�~gFWZ�Z�E��E��&*�G
aٚ���}$p��P� �h߼�b^+��_�ê#T�m�����oX���8!�Ņ����輳�C�б�NR`��?]��U�	��?�<�G��n��e{Ph�#.q�,�}#��_�(����*�\̷r�K���w�:�(�]�D4ͽa���_�����W=��N��8��%%������&��ҥ��K{
!�l��C�+��@��I!�zQ��{>!�w6v2�ȷ���un�,�[�D�&�[��]�~�Կ�֎��{�\dSv �I��\;����qaT�#s�ID.{m�����J�r�O���N����߀�\���띻w���I���,]��)#�K�4됢Û�ݣ5�d�DZ1`��/4y����M�qQ�M�����F��?t�m��s-�'z��>q�ti�%)!
��]7��O�9d{)�Z�	�k;"�W��M���{�?�1+�y��E�=�Z�_G���8�qϕ���El^�w��лx�a^^��(
G�V
�}��,J��Gs;�kO��M�����l9�W�c��^}`��&TzA��'ċ�yv���"]�W��]��J06[��.�_�K�AesO1����j(Yr�O�����U��EI7*��u�U&��zGu?]�bBSf�'N�E�T�����d�����#B��V�q}'R3z�8��>��+�j	M5šD9����Y�_��:��ʰ…����&�{���7"�0�=9��\�Dӿ��Mq�:��8C=5L�����QX)���a5B�kx��}�ֵ�!gP��6H��[E"��z$'x�	��k���ẋ�I=1��&A���~9�'JPFC.installation/framework/container/container.php�����T[k�0~��8�@��4�}Z��{Y)-�Ў=��$��HB������#�I��]
���t��w���u���A��]]_�3+�A�aN(	��L��	�O�fx&h�dS�,!yB�08�<d��yP����MV6Cr�	w\�����+�㣣�����<S9�ps���*��\Y�kܺ4��Gi}���op�
�ᾘ�n+����N�@�r`(xE)N��4��˳������w���@�2Eɗ �/�"��ck�K��]���Q�[Xg���KO�iM��ǒͱ��c	'5G�5���H
�$[��KԮ�i	�uLrlM~!�f�ghv�O[-�ɷ^�鯥.\+=�����r5�Q7!xg5�Cj���>�'Ա����e�$	�'qZH��"��H#tu/�:��o��u)�i!Kь�$D�]̌aK�,X^�Z}�p�{�X��̠t�Q=�
�]�
i1jDX4�GY=b
�;a-R�˄�ѭ��YUmV��8S�@q���5b���c�F6�:��=�u%����vW��&���E�}_�Q`�=����J8�9�������Tno&C��J��k�]��жK�_��߶�l���>��͘_9]1�AX��~x��h/���"��.߫�JPF=(installation/framework/pimple/pimple.php�	L#���Y�s�H�l��>��)b����r���e[�ͥ��k����tɘ������v�>.�
����3�習e�:~�/��{.��钃���,NY&����$�y�Œ�7y,���K�S�2�l�
�3'�"
�I�(�ļKp�-8,W�8٤b�̠W|k��ׯ^����W�����q�$�?�STh#�$��X±����+>�$�?\�9�x�B�3\��Y��$�����)n>n�3���1{��"䀟	K3��0�$�G��f:��p�f�G0�3�B�H�#���T �%O9:���Ѕy�9q��,]�.d1zz	���Y�D$�0 �?$�H?ϳ5K�
�2��
m���G��Y!��!N�'f�~G�	8������*�E���LA*>��"��ib�C�FmWΐ�Y�M!����1�O��K(�rم@�Y��C��M��5�e�C�2h�2��Q������wIz�^ƫ�=�<�P*�~A��Sr�~FOh�<�xM6�q2M���:z�r(��j�U��$e�͒\�0�7��蠳Yݲ�Ԑ�A �8Ur�-���^�0�M?8c�	����S���	����oz1��R����#ϰ\|�����Gcw2�ᘸy�����co��_�z�s8�����ޥ7E�ӡ�i�y�]����tN��7��%^g�t@�φcp`䌧^��at5
'.*q����l���Kw0=B����L.�~���
w�f�IQ�
G���.��S����s�w�4���w��.�:�ι�v
��2�(����¥�$����Ȟ�p0��.�;��?x��؛�g���Ke)y7
�:p5#�|=@HB��&n�N]���0Z��R����a��\D<h^;�]��9���۝7-�SzqDՆ����K�w,ǬOwո㖢GHP�S���IS�q|�K�ok/I�-�(8�ea����ڜ���05��%�X�j�i�*��K�ֻn��&��G��Q2D&��RϷ�8�eE2T�B��Ǿ�V{vUQ�'�N��/T
��Ya��ɰ��;�AkR���L�3�<�2վc4D��y�����v�Uad�����;�"��m�_$��>O�P�5�	b�0QP���q���_B�JG/���F���o<�K�Mx�&���!ɿ��� ��)��4C��eF�0�'=z8z���Jb�z굣�F0��r�>�]k.�<�t���@_ �J*L�1�P3l��Т��u��c[�\��;��*6������F�Pڼ�	�6�t�8OKpv��j����H�'Ǵ�b�9�ì��fU>+q��ӁQ��7Ә��j�|��E�/c�yf�4^cI�Ɗ�w>O�+F)��T�ޚ��%	@O�ܖ<��Fj���O��<�^>�$�����R���x��@��1�6�iZ3�d}B�/�R�����m��$������ϗ��u������.�c�]��%3K�WI;�Rvs���9q�u9�L�мR�y��G�5v��{n�c
d	��T�‰��k��8����/������t-��R��Tn�䓐�
ت����π
^k���`�D5E����P���,��ZU��u��Et��C�db�ޤ�YUO(�bv��_ͥ�1�,-�boˇ�K;m= n�R�j��ܿ��r����mSp���5�/2°�''�����Yx�`�Rq+�NxM�@���<�Z�w���!^�]��&��g�ŃZ��K��P���7s��fc[W#�Ş��S�9U턾�i���l�6\p����o���K&��LBB�*�Z�6������z�(pR,Î�jk%����Loav��Tٶ��Y6-P_�eY�#zE�Za�O�r~�!\C�qe��}��	�!o@H]���b���^∄#,��>ѽ%��e��|��t'�5�j�)��v�	�Ǝ?�=�'�(�O�߮QW�����(������m�`c��
��ޝ��]-�?4�V���e<
de+�����=e��ݵ�w�KWq���{-��pa���x�dٯ�a�(�
��ء��b!">\��)K��U���I��kFS�{���7��݅��Ç�]��s�s��\kn.�vg���}De�z7����[�z�q�@I�[�B3:4�@��k�Y��A9v�Z�7�|ͮ��q,�Z�ov�ٮi
��,��Q֒mG}�����N����Q�F�d�~�6W!�'y)����}�j��>Z���FW�e����kKs4��Ȭ{4����{���DB���K�ݗ�
��.�~���J��~��5|'齨_s6�������[B욖�����;�O�T��l����r��-x����B������]������W#��Xŀ��\.}:ĥ�/���9���ɞ���bYP���:�k�2���^������7^�O���(���w�_����޺�o�^����JPFO:installation/framework/pimple/ServiceProviderInterface.php����]R�j�@>[O1�Bl���9�%q�I0&I/����h5X�YvWS��ɒ9I��7�?�|�t����z��kx�"%��1qP��Aԁ|��J�*芎AT	
'�����~™���5�F+s參,Ȁ%�j��)��<^��z�\,n����
lIW\��3xC�Ȟ��#���d:��4��򯷿a���a�Ҁ��y��\7�A"� x�eKrh�W��j��_M�C8��ȆU���k����|$��	O�4Ά��I������:M�֥��r��?l��l�Ť,jc��]���,��7C#���Z�#ayEKQ�q0A'9���9�쒒�a֎w���"P|��,��>A��D�q�U�m~����*(�Z��48N`1]�?��;�:���7w�
zk�Ҙ��`�v#>c�8ݽ��g!�c��JPF=(installation/framework/object/object.phpM6���V]o�H}ƿ�V��P�>t��K�(J?�h�>u+4��0���fƤ�6�}��������"���y�=w�ۛt�����av{}s�B�� (�F*f��C�S�T�`�:K��pŷ�!T�F��a�F\0x[X����L$�i~O�-���o�Lsŗ+�o��?�_]LƓp�ÕL���CxO�Z�2K��Q�㓉\���(��}��Q�b	�g:�O����}���P��GAa�F���������Y�D{�ˠ��a�5�<AذB)ゞB��A�A�Mž�='�6�fk@���m2~>������33T�����d�{��)2n�2�o�5�P�-��Ex�j��d*ɩl�&�O��+�LQ����[n��E
�8�C��/��%�||���ὒ�p�߃�X�0�[���	�:�}�<mTҤD��QDP�j�1a�A=��Ι�H�
q��?��UJ�+Nu+�0�ɐ{�RĖ���@'��+����7�P��+��U����z��L�NC�y�l�^�D�$��C�tx��g��؝w�����5�C��Ac��<�
��Y�ز$�Vb �%���ŗ4��S�\,w��V�e��ɰ�薉�g���
M�D�vC����
���)X-Ѵ99���;T�.������s��V�4f�������]��*��O[����
c_p[��<�������cϵ��3Qy��oRU�^�&�vB\R��Q+)i
K��n#��T^
ڮ6o���%o���
�㩫b7��.2������y�c�k.]��*�yw��k�a���5r��l~��t��Q�Y���ܱUv2���Y��.���}X�*��k�e�c��im�_?�X����H�ޭ�(����2/���)��㢻W��2�'t�2	��jo�e�)�9���bX���E���,�\�9��X��LS��sPG^�ӫ{���������OH�[_asWE�~�$�-6N��s�F�N�^�5�F�^d-��*�qE�ʄ�}�\`�j����ڎ���Sm��]r�'Θ%�O�?JPFA,installation/framework/download/download.phpC�,����oG�g�"���q�>�W��5$U'@�dw�l����G�������}8@�'���3�=�?>N�ҭ����}1;}q|$v�ŕyT(���Й,"��<Ȣ���K\���Ypݨ\���
��Z̮����ّ���kk��d���*�Jذ�=t�΢�U!��o�`�t����ӯ��Qp�c���'�Z�:�e�s�ki�!ኣ@%9�q�F�P��d,��KX'f�Fe9��X�H1����V�Q���`>�����l0�
a������X���P�&�������V����2�+�r��fP�"�+Q�V_�9�W*�*�Y%K^{��A	�ɍ�@3Y&�{w��f�
�'�)b���?�*-4��Z�5qű�T���H�UFCAD�X�	@/d��X�")�ا?�4m�,�d�hM��������
�QxR|�F��IoЌ1|����-^X�I@.>��=��VL����#�b���A,���fÖ����Br/>\��9������W󹘈����-���@^Y�FX��~��`��XgQ�
S��G+�<j��T�>�q�c�u�,4�G��X��dfx���:Cy+�궆�퀼*��'^z�B��=;���u���C���f����
e�}t�����p���[ڰ���쩯����=�y��@�d{�B�<UA��q`���rh
D�I��A��ZA��������a�p�\�8�
��Ԏ�$Ҿ���E�A���97hZ�m�g��T���A�i��e�����Y��TZ9���q�|�/S�v�H|�U�I"��I�@��t��y�C5��*�,�[��,SI�v����l����e����	dD�<�����ņ@  0��ͭ����[p9�:�2�Ơ��Cڂ��������J{��ܸ�+ڤ񅉌
1FIS���j5Сz���Csq�L 
~2�L��Qڐ�饪1v�f7����\}�Ba�-a�o�
�ߘ�*$Kl�f��V#���Z�3\j�O׺���+Y�n�K��c\E�	ч�K�q�
��N cޟ�D���<�–-���<5���O��(۸�����9lrٕX߬�*����շ��ϰ�݆xGұ�'BYH�"%���t�8�Slj�H4�+/uY�p�?���}����BL'�C��J��tsh>���锠r.���d���)Q���cqzv����N���C��b�m0�Id�*J=@
�̠�i��(�F!u�ZQ@�Y�
ƾ�!7�F'	j��Q��,6���Ġ��F!�@�?}�O<ujD8V�������\I�uG䡼��O6���MG�>���=��Q���i۠G�c�{�v@]j�i����xpOj���]��9P�Q�c�9��1�W�3迏����&yt�%ȁa�Z�0�8��^��.���?Ž��ζ���b�dv����ݱ�M��Yr��u@���Ȧ�@��ώ>��\�;��%�s���N���%(7������"i
ìJ��J�q�ȱ�!��fԬ7K��&�lȢ��^Áxpv>d�l�^�)٬R?���B���^s����]����G�X2�i{����
hu�.����o/t!����j�v��P

H'�]�@V���\DФv�x��ee��F`�. o��7P��eq�Y�{�o������E��b=����_k�ʗ2��L�jꄠMu�M��H�;8��GH��s���f�l����*Zӱ��=�%�}�F8]���������������Ǝ�/�$���}�n��4�`x�3U}�7��D��Ln�������&�<��qL�U��l[��0�S�p~�O) x���6-���j�v���O^LL�I��8���dÔ��C����ad���8E��r��A���uR2-o�K��.��p9:N�T��QӲЩJ�#q��4����sh��2�8��Ќ�̫y���A&k�SB�V�=2��vmTt�v��~)���3�����8��K%f��=A(��*�X�Ѿ˾�ٵ-g�CQ�W$��L�k�\�:��J=g`��<�����J\�RM�A��r��c�I�U��!�@¥���<�Àx�wO9��U6�|���&�vR���I%��]�I̩K�b�3l��"J�@Z$�ai�3OؙZY��e#y�Ȫ_� ֹ�=�`�I{����W�8huV��2`X���#j0��޴��W�D7C����2�I�B	t�%pˈNm0��0��M����k�J���_d�V�ϣ��2���?�c0½�Uu���
��]���y
�{�Z.D��l;�Ҥ�_�7�Ty.�te�x�ם�
��P�~�wq�M�1@0���E�}b��a��x��I�:�AoC=`٭�wfc�f{�P�u�s&��S���O�P��((+�/ #�W�A#�Ξc�*6˹U�����qZE�,���z%z�V�(QN��o«������$�c�}`sW��`���UF�A�MBW>'��v6=ؐM)�`�BL�		���,*�D���م�P<|��0������ӓ����ի���7'��g��:�8:9{6;y~|r��͢�)w��J����kUë��V��fH�̼�Gn�0��C�3=�4��"4��ҶMAS06��gU�W,�%E�4�B�%�s�[>�B�chw7T���e�n�옣<��UJu|�fatM�m���X���+�$����2�ʶa����;z7��[�2N��ش\*��:�x�]Š��M��j���qD��M�<ý)B"�A=1��c�R5��4>��m�2'<J}�t��S�g��7�6[H;���E{:u����r�kkZ�
�j!��{M�mR�����o�z��L����x��t��M��Ѝs�At�$��Q��Y�9�Zi�������n~���.���A&oc.��FJa�oI�)����x���)���x���!��7L�7)�_x�z�-e꣓����"��:��;�Ql���T�c%�<�qH/���J�qh�X�ks��<�}>�f����"�i�N(TR�o�tq�YS±���뢌c��W�9×!ddnI\���DŽ�����ս�с��
����>�W������}l���so�4�IKn՞�����ᶫz&drh�����ȸS���8K]8���D��C;�[G$?��_�a&�U��ߙC��pno�
St�M��k[���?x���w��6��I�x@X���C��{�V���t8��30鍝W��q(��Eu�J������u$�������K��ɝUI�*ԍ��R�2���A�P�""/�&��A������vQ�<��z���	���B�9���	ʉ��&�L|gw��.���
c�˞��f���=��u5�<��uP4��q�
@g��T�?�� ��~�oc�o��;AG$U���/�߾I�
†zG<�$�~&(��xS�ŏ��
5�~:��Dk�?�l��X�M=���:5�� :R�ǭ�JPFB-installation/framework/download/interface.phpn
���VMo�8=׿bl����-Z�A� [4�aOZ[DdR ���b���!%�Nl��I�f�̼y3ԟ���&�7��tL�%S0��s�Ϋh��PxSG�9OSU<75)_���UdM�M����.Z���W9Đ0�j�Us&���p:+\��f^F��F�������to��U*З�BB��j�T.�i�.�U��m���'�a�^U��ݵ/_�����
x8��gƲ
��|�����b�
��:�n-�g�`�rK[9������i�t�Ro��yN�����u�w�N������4�tkFJ����v>�/����T�7������`$�O�c�-�Թ�}�df�r��H-0k�<��3e��Y�}��.�D˯�
���c��&7#�Ӆ�䤎�I~�<�hF���Y��P�g@<a����U���P-��l#�h5�}1���~���k�ۮ��c�C��v%7S��7Λ��lۗ��Ѣ��������u鈦҂8J��2-M,ӟ������4UE���'{i�S؜��l��o�=��V-�/���+�~8D!�{���V���A�/��x���owi��ĪK,k����v�p��AF������숶�8�9UU,�@^�REZ���\H��g�"
�Rӂ)���/�J`�Z*؃���
}����ڥR~�7h�J%d}kl;�j���.n�6&�w/���@3Ua�K�P�:@cBh�WY�3�� �C_��[q�I�yS�Ɉ:������1u��>���v�/Ĵ��X�u�뎥�!���v'�Rl����F]���
��M�
h���C���1�]���/�o	o�Uޫ�D;H'�h���i���%�;^���]�����=��}c�2��97Ѽt�|fJ4�[�lt/��-%���ג`/�`��E�Q��T�\��6��=K�Kzbu^?#��Q����ԕ�9s��!��w�蝷�l�)���3ks�0���#�������˗R��gt�x�����a���/VI��7�JPFI4installation/framework/download/adapter/abstract.php�R
���WMo�8=ǿbl�lO�n�u�40d�&9�iA�c��LjIʎw��3�$K�T49��pޛ7_���4���w�[8���)�`�yc�WF�K��=,���H^��MR�F�E�Q�|�Ĺ���B��ͣ�a0̧���%��Rax��|k�2��~'�w����x�T��L8�z7Dh�Ln��88�0��2��v����P�|+���ˏk���zRFX:|>H\(�r<�{����z:���T8�|��y+	qq0�1�!�R��bZ}V�<�j߰�i�X��
�X�#b{O@���*c������ZX�=�:�1�aeWpyqA�job�`�)�"R����C%��7�Pj���o��{@lH �Qc�Ƚr�W乱\Jर�_1)B�^+k4��qwnL����c��
"s�Ŀ1�����/tH���_+Ϻ�|����`�)�nI��ŕ��a�G���_|�/tꑎ��
�H���0�	�4����#��d���;�T���wQ�$�.��TZT�G%ΐ9���i�ap��m!Ҩ���,.�h��+�6̣���R:��7^%��1WV{����3���Z�Z=��l�8<Z��š'�Fcv�0؋�}��]f�j�3�-`Ud^��<H?�Y~v���Ipl4�F�4�Hi��ڡmT��<�(ʳ^�zB_��V:���ڋ�{0r����sW�S���!���ܤ�#���tX�
��v�jcjڭ8i5���K�lUq�mRxq���3C��IRL^Z=G#{�R•��֕�2d.T��Txؒ[|�1�\ӏњ�,��%�����:��T�=)����������H&�4�
!�ޑ4��ȷ��n��*jVf8tҨ{F�Z�8��K�:C�q�Xa	.b�֬B2�$.%���,kPKml���T�C��f�OK�2�>D7daJ`����u��k��u^�o�;�g
ED�o�ѨJSy�.�7�7\�׺uX���r��#_Ҿd҆֊-�
�0�Rq#�ݨ|D�tR�)]ty3�%��f���Ri��ܸ�9�T�k��6���F�|j͆�ݾ&�3��p`JMO7Z�X}���S�5�g^T��Z��4�8)}$>	Y���f�`W�x�8|A���+�Gݜ���5Vko����������q�~z�94��)�����gzmN/�?�JPFE0installation/framework/download/adapter/curl.php�0���X�OI�l���ʌs��8������U��a�m�ό�{x�-��UU�ۄ�Vg>`w����d�4�߾m�[荾\`�KZJh+��8�+���
n<�6M�S�R�	
���<B�V�>gQ��\�a�:Hͧo��@��|��ɣ����~��ۇ������;I���_��
z�q�a�a?�14�
�/"M򿌮ወ��B�Lo����P��z�t)D2�7����H�3�:|�9m"�p���"T�Q{x�� 3�ZF0;�z2���K��?��z�k�r�S�x0"
�/{7�(�7 WI(V"2����/��m6�SZK���`���Ȇ7�<�|N�l��
O}㶛
��h��Ի%c%�#�t���&I��>����$@~����L����u��[���N�\�++@Sn�L<Hm���3I�9y�f˽x�K�>֌p0$�'�
峁	-l� ���ry�E'��x��
C�]�l%L�"�ʋA,,�_�㏭D.i����/��,��b黕���b�fQz兡 [���<�X��T��x(9,!�9�b@���ˆu�!�QR�^@5�x�`|V���ZlUʢ�����y�#�@�{�s_�D�=�\�����͵bc�a�إ�W/��11Z�+�)G�Ȃ�\�+NF���ú2T��2�F�\D�Ai�'&Bh��'�Z�~�r&8�@f���
�.g���pqLPz����]'�D���J��I��(O�6(
6)�����h�-0�b�P!�u\k/�>H���a����D�@R� pfG�<�^��G@� �U�e̳N)bΑ��mT�u,�;�Ɇ�2��J��R�d��	Xb�1N��1�D�?�R�"9��h�݋�	[�R]t�D�p�;�����Y�js(��xV������hB�L���0�>=#5q��-����ki����K>�9�*d:>Iq*������	^���1����u�u�v+Y�z:�����/�N���h:鍮�J�O[������ߛ^�G?u5��6�\�}��š��||5��Oɑ��
�L��]����������q�+�����2�w:��]���i[u<>;�(��L�<��89hUs�e�R+?��+�g{�pEt`��w�i�]��_2i�)J�8� �A0��-�|��Y�
�n�v���)��½4
M�_4��z��.���~d��n���X�.�q+��Ҙd�XmR�3,���<.cB���O�����tP��������/s-�)�|GG����]�t��h8���Ɍdӗ��A��	���"�h�K�ܚ�O�]�˩�_|����Bj��se��7'�}^U�L��r;U�T���(�*#XF�N�k�d7�x��q>�c�%P�;~}�)�E�+"��"/�e὜w�B��
�˵f噮��<�VAFQ]`�S�+��.�{>����Av
ں�y��=+ ��$�{r^�[�D���*�e&c��O<���Z�|�����Y��̤����h�y|���\�Q�*�磹dy�x~���OF�6/���BWG��؋��f�ha�rv�#�^ܱQ`��s\�q���i�@�*%��g��;��!���}�[�����k��L!���X����R��
��I�����fm}���9"u�����(�%���Q��۟+���EM.a8��n��3Xb?��pjU�Z�_m�+��ᙏ���f]lEju��xcI2�gs�_U6$�$�!(��8kx_�s|�
�f��R���O�"���B��=ʋS�|DM��˪�tɒ�M�T�=%��6���ne����P�%�8C��*0{bE[U�\(�l�a��a4y��;��
��
lp3�6�pm����W��J��ʽہ��TJ��*F%��8GN{þ��?�1ӆ�6c/L�ӻ�0���CP+W��v�o���8���ŵP˰��j<5���JPFF1installation/framework/download/adapter/fopen.php����Wmo�6�l��k`Tr缶�Ц��6N4H�4�0��@K�ED����
��{��l%q�b�:A$����_ʴ�n�xѥ4<;>�&]������4��ª"'kUZ���"��J:NՍ4k)�Lh��ᵔcA�D�4+_���
6�Į�J"L�tkqQ.����>.�¸����zsog�%��8-2a��¡�)ʢ�
Cۍ�S�8�L�27�|vE�2�Zd�c�N��Ԇ�z9 ��!
��n7���$�����0�@�d���.SEI1ϳB$$QB�*��)]]�"G���>͵(K�q�1|74<�j�Z�EI�Z�'�w�cc��-�Y�ə�mK�$��DIJ�W�S�M�<vՊ��ȡ\�6�w;���l���R�B+������_m��,m͑������DdF���V�u��`�-��k.���lo�Ʉ�����D�sp�H�$��h*�S��®�IʠF�3�OM(|�D�[e�	������H�/��2i�u�?���J{�0p.G�΢:����C�L�x�L��RhPԱh�9�+A�	6��h.�DK[��I5���+��rYs�7u��T��N�p���5$jE$\�8(3���}9�Af�����9J�7 ���Ș0a	CrVڅc�T@�j%9�0�Ka,�ΏV*���'�:(��hF/MX�$C� �2%f���	G:��
h=)�\���ήI1.�.o.���.f�=$���\��*�hzN�B�d��{'�	��؉z ���U•��4���Z��
�a���Pry�e�4�3>�m�B�+��S�j����:E��oAcw9�87:JסPX������Z�[�C4L�-���\қ��$�sC"I��?�p�Ͱʹ�e�]�ż��9�U%o����R����Tsvot˒\�}�'��l69b��^�օf���s�qz�'�ېy1�>p)��4�uΚ���a�m�s���Է�ܩ��}Q[�'`�H����>��I����~۠�s+�Q�w?
�g�|��ϩ���bᒻ
�;Ajmt޵;2-��.��_O1|�v�L�7��Xi��MX����N�_+�d=4��&���@��ͧZc&����E�'QD[l�8���*�l闇K@�4p
?�[~���G�f3l�
-q��w����E~!�o`K�~]1\ߪ��߳�|��
�FxZ��3���c�O��_{J����_��DٯӬcz�8�pJ�ʨ�}��e�)�r�d�	�Dj���q�wx	g޼�������a4�����?����u�x�\���԰�Ы�Wk����l�XcG0�y���z�CQ�v:k�x�}'�_�	_n��s����k�yU���5k8���؞�ڤ�}��;��x��l;�A�)����%�0����7~����*�:rާ�;\����X��€�S�A�K����ng�_��w�,��e���^�nG��Z$��< �P�k@+4��q��ݽ�i��5�$�׻�/��~��k��i�Qq�JPFG2installation/framework/download/adapter/cacert.pemd�}���G��Ⱥ.
�1�0�����&4�cV
hAB�<�ւ5�B
�&P�k5�W����T��[�,#c��A���������D�ڸN>u�'��dt��N��H�(�������]ӧ8��O��5��n/�:�����z%��W���	D���D?� �~���Xy1~:�>��n�����n�n�_a]D?ܘ|My7S�1�O��&��ߘ�ɧ%�O�k�`::��)Y�!��$�a������c��?��1���n}O�_�:�y�EA�)L>��1�OE�iʓO͗��k��OӐ$����|���?��<�ח.���<$u��x��_�C���&�8I�W=��$z����8��"<GU���WQOE;����*3��)8>��O��U����8�����}헩�ŐD���^��̖b�?��?�?#��[������S7|ty_\N�A�'�����q�����0'���LS��E�N�7���Ut��WK��c�i���w8}{��W��w��;>���Uٵ�M��>�]�|tS�G��������/����)�0���'��$�8N�8� �FX_����PpGP�B
���	��y����,����!����~��x�P,/��hְDN�I��hQdb�����E��L�)����)X0}atO�u��ϑJ�,G���쬬�OB6K�
�W�
���2ա��b�xa)9��a4[o�`veR��g�(H�CD�t�]���?�+[�#��vC�1M1�E&�bCb
îJI.
CG�v��ξ�)\��;)���H�"+�S�j��qt�Ʈ��Dṫ��K�1X~��b��}��%6C�EV�c\x�c����ҏsߦ�d[($���7�at�"u�$Q�b��g�3�����O�t��U��ԋ™}�r㰺I\���6q��{���x;�j��5����憏`	��Y�MQ@���(��M��b^+}A�Zm^{�@y�Q-Z{T�~��r�r�*�)+4\�k��!p���L4[�9W�� ��6�S�<n�`��ockk�+�əB�A��-�(�iڼI�z"ȴX��˒��+Sކ��k��kV�dø��#�b`la����:TlC+�#8�Y��O ۭ�LTHՌ�%F�W���i��8Ws'3�X�|��NJ"�B����΋ξ:U����!G���Rɐ�pV(�s�Lw)���~l]����ٝ�;a�Je�3�
�X������c��P{oS��A>_�g1�wcnPz_ǖ^�Yo�B�gg�rX�7m�J(�b��C��L	x�^�a�6�/ڝ��(���tgnyz�=�����X䪦�j;�aP�bı�;LJ8���@y���m�<0����|Kȹ$�Ґ�)�\��N��3�KܧX/�U�P�ޯ��.�MJF�o˜@ޠϕ��:��³���5;��ۓƤ�@4C�%�uv��Q��±��>���4'4�@�n�a*���i/R3g2�m"�#�_?�3[J7��Q�+��*�{4�vL�W�L��C���`���j�����?��+�do����+|�ON�����İ�B��I�l��������3��<�*#��akl�C��c�`lNM�5���F���<�K�DA���n���[��F���rQ��E�_qW���f��fU��:�X���_a�H���k}W_��\���bݪ�;�u���ԥ�0��i��Ol��D:�FGo_1�b7��W@��E��Ϡm�ȻQeXH��@yP1�;`�2q?L��H�A??�UG��	!J�02Tu�~�]��[D���VY�8�\�]���1�]<��犯��րB+_�Y�n�ҋ{�ʾK�Kʬ��>�����>ϟ�f����u�j~�=�E�����*�1�2Y,#J,^2,�N��{�Qtv������q��׳����ygc^���]�Lj���B�w�:Y7J^B੾&|]ٽ��Ǔ(==�/0��L�Y�Q���Ɋ���1M7{A�P�$�!Ղ\Ac@n�|�JM����l/�2��v�#�����3�^֕wiTN�q�)�o_6�$P�q����@+��~�d�������o ��P���Q�#L�FsN�[dzLb�7<��g����9��en�CEdE�LbR ���Z��Ћ��|m��≮���B)��x��@qѿ�A�~��8��0o�`��[RP�}H�C��d��3�r�n;�I�O�S!��T�&���}=�.�D���P$�
K(-��NJ'A��D���B�盂����\��p�s�:4Q{�댮M�ku��k@f�杆`�k��F؋�m�E�R��a���f$�2%���\��g��[���z��)��:�e�S ��A�+�h��S%�n���K|%�gX��LF������g���,@�Fv��Z�a��'��g�3�e��5��x��Pt(�o�t9��.��t*����=E��$�U9��B��.���Q&��'af"���)��+Źf�m����
��9��O�&��d��%�_�M�O�$�����D8h�>.���v<�H�a��AH>08D��7�x`�t`F����_�i�$�TG�gxTLqɏώ�P�3����t�.@����D}pZ�B��&�A�YTe�EảM�>���jU��-���i�+[��W�O�[)��o��B�_����37�ɯ��g|��!R0�q6�3�����,�����P_�͝|�w���),��nC�@���fp0�n4)�L�zi��	|��?�x��o�kon�7�`�v&����#�n�`�/1&���&��+� k���-5v�/J�����ꭳ��gw�����^�t��q�Mv;�O8Kt��%ߨ��q{l���r|�\g���2�<�ףaN���βSPEC\�5���{V�+<B�Of��_�M�X�3Xz^S���8"�B��'Ǘ�ԛ�Z��Z54QX�ϑ�_�����ԈQ*��aID��[R��3�j�.-_V�t$��UA�@A��<����=4H*��މ��|�?!�E�/\'f?3oQ!�7�oL��c�3������j@}ћH���0�0V�:�X[T�F�E�q����s�9��
�X���W�x�tz��^5r �A�s;��S�nf�+�Ƀy�=��M=i����yB�l-�����b���y��\�0 3�8�|�/�+IO��T����jM�����-ҁ���ׄ��d��$�/b/ay3�0�����@���}f�v�/�%�l1߫Q.�5P�R��(�!I��	vR��>��R���p��g�,�7`���x�d� DT��S/[��Хܝ��P��+?������6��i|��m�K�%���V����;�˯i��G���\I�_�jg1�7J8��A7t}sͯX��Z+Y�8��Xy ��;��S}��<�LJƥ_4�7ƹ��x���]\2��+���#|���W�ŸR�/H
+�V��&�J���UK��%�ov��G�X��-��RC�Qj���Fyʁ���E���A����#�u��:�����7���`<�c��^��W�'z?l��P7>N�ߩ�^�l��Wӏ������>��8D��{0w�M�D��b��M
&�ߗ)�T��i�[C�f(�q�Q�X��2���*q��w�e�F����Cc�\qTڳ�;@/$CƓ(�%���-EQ�&#+�60�����x�#4��U�4X��R_zƑg�{���,]�ޡ��CY1�!�n"��!�A�!�'�r"G�za+M΁^����V�By����!į@��f]����T��8��-�Gxpf����Ǔ��#0�ԆzAU���	���2������}&P*0�/���O	�_A;�9��Z�]�����E>h�ޤ����>e����/$+1d��׏��jW�'�|�%:#	ʮonI��
�3��QFWiF��|+S�Z+��Dh����*n66ʼMAuMcY?����"��z�	
dI�:�{>�N���<$���,���"�,��hQ���`ީ0�Ξ����i�&s�	�=AF���Bߓmݮ}���4��h*�w����Q�C�w��^JK��ы��ƾ���Fv2n� �+8�uF��c�T�x�#]6�
���~�t鯉���h��<\`FoU����~kGt���9�۠����sL�E�x.-p��q^[��J���U\=_��4��Ι�X��t�`��\_��ZM=aԉ��I[�[��0y�BO����^�&<�̀p���y���sg��9vv��g��.�>�$�a�)������_J&���*�(��T����
곔:��	�}#fTP�w��?Еw��,�C��AݛC�T��-��f(�	ah����Ȝ�!>r�̨��;C�$Pj�{��
��E��
�.��'��G������ҜnI~2�����>S�*>p����Y.wȅ�"�b��_̮,�,�
s����
�}~&�b�01��1�
�ߛ�D��~+�
�V���oR��o8p���OJ���mj���uU
���*�%�s8��I�H��Sl���诀�.�e�4!ۻnp��<@C��.�"���V.���ԙ��c�f��T)�=A��AQJƽ�˭�(>�S!����#P@���.������	X�變�fJ&��T�*v{��nų&Qb�Z��g����h��ӱAw� b/Վk�}*�#a|cК�2���wؖ����jd|�[���&����X�G}���J��]�&���$�#��f�(�wX��jF�s��k�M,��Bێbo����UOG�y�/�N	r�e�t������%�,CR��%*I�c�L�-0b�Y��-���]��3߿��{g�h�s�-! ڰ2j�E}�`�t��X qy#��o��P�0��ף���������ͅWr�C�~�N�Y+�ܫ3���*[3
�m������HfDuЁ+��ʄ�2��1gP���C)X�.ɥ0�P���@�W���а��8�/�\��V���`����C�{
}��p�ء�4�Ajt��uzNu�Y�1�+�u��_|��ԢBs5	�#nr�E�.!�! ^<�L�m�YJ!���X�0�VB�[`_���Y�\h���[N�|�:
�a��۞��C�x�2��'26I9hX6O����0-���>�������s���zK?��)7���fޡ)����԰~����.R��8(�������_h�]�ߜ����f�E-�nP}4�E�}��n<�veW���	*I��j�.�z��G���o������N��o�'��?ry�g����y��U���AƯ��ġZ���.->����e���׍�T�3v�Қq[�Ǣ�+�;� _�9æ[����Bg�tN��e�qezD�x���
�&���('��9:��q�Z&�=?����ƓÅ���X��U��O�h�W3�����:�.�f���P3���R|-_O��������]P�H����p8h�'�د���
�xhsp1N{lDd1)Z�o@�ۓޥ(�t���YH�4dObu�5�"(1#�#IIu�2�߉V&B�i8%�mp��p��=`����Ž�^V�pb
���54.j�E���Z���]|��i�]� )^[`ѱ���>��v��1FS�Ѐ��`�����
	��[�b4a�#�4�ỹ���k9�\��3�r7��=]����]p�ʳ,o����m�Ȟ��%�����B�E�%���rf�m�ގ�� N�l���1�l����,�S�U��M�)u^��\.;/DŮ" �o�^�$���@Ӌ�	�Icw��@�E������,���$�W2�ٗ�9���K�e_&&��oL������T��I_ �9�g������.��K���vP����UpJկnh�X^��O�exwV=��B{
:��!�$Ȓ��������"�ս�΅z&�	Un�-�#�q_��&�-�ѧ�>�0�Ĕ�y�rA�R�:�-�c٦ܹ��;��g�:9�*8���4g���
�җ1�@���г�]�`f^�n�{����BA4Ϝ��{K��x{R����]���+؜3�n�IbL};��
<����K��Q��w�xJ%��r
���w��^��/�;�����;"��b�G�<!����a�n�wE�q�C����3����vO3q�b��LU]�����a٢��sp��<�������	�@���+� _v��/���>��?=D�iB��s��+�:Y��` �c�N|��|���͌�u_"4l�g˩�L�y��|��U��]�<|?i�t�`QО����1�s���w�o�>�
�%D�M�;IS
Z`�����%·>v��,���\��Iҡz��㋘N�\m�'���'�=()oY;'9?viVh%_�� �j��MS��5���?P��gj�A���E�*c���?��•�H{Өw�J.�K���^���q]��W�^
��@��aa#P�`�B�b�q�vO�U��L��*�}~B���睈ī��ɉ$G���j�|��Ķ�pG+J�f9]Orw�_���'�����	�2�Miw��F������4\��#���FY�\��R�v�7�a��|vH�I^�C.��U\�9�����;���p�g7g�:�ne�W)��� �F]3�\e�|�9Fl�2��5\X�6���e��1���_�K3�ES��\���q�>	��q�h�erQ��C'���r�R�ŋV�'���
ܴjJ�հC��GN��R�^��UKt���EwL���K�@k]o���,���˞ԁ�Xs�q/��׃�I�v�K٣�\N�
��a��_�3.��%��H����,��~��l���~r�M)��"]ܬ��@d8;w�G���'
y�1~�Oc&�
�V'm
�����2��}�w��݅:;���✋�`@�:�k�DY�d���O����+���(�w��\/����L��7j R���#@S�g�YF�����{���CE��e��H��.��d����ȿUFj�s�|�3��a���I�~A��8�f�>l=�!~[t��]��PJ���g�,��,��Z�=�φ�;K��k�!U&o���s���
5Z�T�=��u^1�.����	%rE��Z��d��7\<�s<�gU���h'�/V�od�#���x��A�r�D���P_�v�W�?����*���A_�ݹ����)�'mY��P��p-�>F�|�V�>蛕 W��ӷ��C�?p�[���(����Ù��r�f��)�����@3p���\a�� (���hΞ(C;�Ԇ�K��Z	]��Q�IC
]N#����iWj��&ҏ����Kc[��
���ٔ3U�O�/�e�NA*�����k/R"f��t��z��g�q$q=v˵8�k�m6c��v���hnh�y�k�¨q�dXH�}��0�E���n�A�x��!��#��	C�U�M�J�Tbx^�^F��&fso�n�������ת��9�a�a�,&|e<�]��%�q�B��
_�������چ?ۗpΆ�<oa�U�R4��Q�Ku�ͭ�{D
V!M���YQ��;���̡�`�H�
��x�M�	��H�������+�{ȥ���z���
�3�g���K	�h`f��IDw�w4Kw�@]�}o�Ԉ�z`�%a�(�v��4�(�� ����qJ���	�^���ɣQ���HuVsK�C][C�|������
��c��6kv?n�S�T�q�q�/�e*M�����+��O���ǔ�=;ujmyA����5��V�����
�k�V�d��|B��B~:��)~i�����૳X(��{���F���"A�=�����[)�x���<|��
;%?>G��o�������^����m�g����l��2*�.,p�HU}U�O�hUt�m��|�1���,�?N�������wL����~|<�INp��#�D���ȩ�^|�I//@4��P��ざ�K��Mx0	�%��h��cxx���<O4��S�����(���2Q�xfiG��,t4򉑻΃��*�4��Eg�cbqj�\pլ�[έ`���rl�
SM=cӮ0�;��gc�,`x&��>YA�K��&��w���M�g�n%�	�Ny���|�������s<�J�w#�̛���F4Y�6�U�
���e;8BOTk���\�|j��c2<%?+���Nq��
XM�:�F�5>>��Xv9��CRS$O��N�@B�C��Ov
��e���C\��߻�=T�Z+0�Kg��6T���E��XN8����=���}W�>��_L�.{�B�Ş%��M��p(Z`�8֚�:��P<�^�|��
��WkX�$Ni!T���|-`ް!η���*��1�������386�v+S>�&���:j�v�PO}j=<S��y�b�����@[�J�k�g����j5�p���J��Z-��Y�[.��3}(Z�XD�P�G�ѵڕ1SG���2ͦK73|��5L'��à#�b��0 ���
�Y1��I~�z�E�Z�\e��J�U��U��2|
�Z���:����v����%d��ָf��C����O��}b�8�>�u0���?�Ki���{��c%D�?�9���h�Λ��,��A�
�}g�s�ʋ�5*f˅p;ޢkT�I1!���Fn_�7��;��CG�v�*?�-�w+�=�u���<gt�~$B�P�[�Ѫ�ׯaI�_3���?�\��<B�L)�NH
�Kw�ۅ�~��f�c.U<�N��g��=͏X�p��'k����bw>{����O��ɻ�yޒ�<�
�g���b���E�	9�U9%�1 /vl��v�����_�8�$��T�ɷ��<������^5�ϰ��sWf4��1t�d���Q�Oђ�B��yH���qsj�4��M������Co�����Ҷ�vC�x���$^�)�0���o�+�A���`�I� ��D�x�,����Ù��l�t'���W�
6T�����=_��Ol�=<�����,:b~�V�[U�	���j��p~��.���Pկ���;�I�F�Q�}A�&�o��v�������G��XR�B.; f�-�$_/���Wvr
ݴ'w*7��B=b)�w��KH��zL��
V��i�E!(�z�3���
��Zq2�w͛^��Р|H��p��6"R�eX�Vt�[J���,*nϸ�7V�5�n�At�O��e��O��$E����=�%��;�ͽ����D^��+�^�I��\1|c��	fv?~��~�R<�ʹ�W�a�����y$��H22��\�,�va�b���Г�x}��hA���`�+5Om�"����ɤ.�Yx�C`�����i���_��_�9���_�y��ZK�ɕ�#���ݵ9�������um��]�!��a��{��Ȉ��;_I���"v��q�3I����:ெ�OF���j�Ƕɾ�o�w\� ���(cy�|C���P\�\1�r�^��]0_� �*ZO���dU�����J��q�k	����;'�%�͌8@�܎G�JR�^�*��� Ѣ�kB�S���$ð�-��Շ,j)��b�yH�'�33����"��11�'��_ǰ�����7f���i��}�AΑ�^K�B��S�ΈP�P���%�{u_���x��j� �]B��k���{Xc���JB���1���Z��&6����HH�%~IH0қx����~.�ۑ�@f,ӡw���1��<?.��O`��F�{��<�i��*SR";�^Ⱦ����{�[�_L���Y‡�N:��3�4e|�†C8���[��;���fys~��O1��m�'��1U��#��ݍ�!�E�%}����4#�ELv%N��`r�\�
���;y1���"��~�v���ZۧVV�������1�3��*ֳ
r��gm<���2�sK�\<,�3>������ˇ��#6�k�l�T����j���8I��Y��Sw�[�X��ǣLj���D���E�;�L���˯wL�#�]%�9�@����}M=�Zzj��k�́%�iV��n��laPt;�~������ܦ��&����79�1���`��ה��?�x�Ȋw�'r_C�?)4������E�茖f��zƌ�1���gr�~eY��,u��*_���^��g{�o�,�]�Ķ���}>�͊�/��}�fMl����!o��90�f���,���G��g	���������l�t�?0�LL��0V#2/\/qvz�f\"�7��lia�؍�3�^��rjK�ﲻ�D�VL[Jg�!�{u�nR[�go�wo��=A���^��	`7B�n�C8�V8E=_M:c.�A3���br�G&;D�̔�݇�w.���'4�/C�r�\�\7Q��t�s��2mp�e���j䣎e]t�C}�Dz6̡E�d��:+˅I3����
�����v:��虴<+rl����*�3��!���o�	~�gY��\�~�y_��[y� ��*O�)O��	8�o3!�rV����~��+�����x�O�~l5�W��]�_��<]5u�����35���:vo��4oP�&Π����7�Rk���Z=LU��9W���xvƩw׶�)o3�Jӥ��Z�JŒ����y���'�p���fȕk3Tc�"J���\�)���5����æ�JI�
�;X/��Wy
ѐ?5F���Tk=���0Ap
1ݿ���W5��t��Og���5��f�-b�j��˅�c��-��W�1��JRo%�ԁ��Bc�����+�T�Π�6@���2��#�%�����=e='��r��X�o�ŀ�����;|`�7���̜9=E��� �� ��rO1�
�߃����+�������9��K�ю�;��K�³�H���6�/)P�7{k��E_�+�oX���}t�0;�"C&_�/8�`8p�aI���-���>��UR��9�h9��CN�7���i�nwI�-�՗\��z�h�QH_k���3+_���)�ⓤ�J������ִ/�Lj��:��渓&8b���C�Z0�`�g�әJ����1�"=z�f.�c�J�^H��'���J�Ϥ��{�S��!^=�����=	��Ol%z�l?.:�˧�Y�Jj�2D�q�g��X�h�Ԃq>�b�T*�[�(�y.�`Ѡl?/1�L�ig4��K�d�ɘ;�,�H�VJE�17��5
/'����q*+E��/�y����UV���r�I�U'4,yj���^�����{�/����#�sߟ9.��*�H�{�]��i�ؖ%��n�Cv0�G%v���q8����y�(*�GIk�
�>��F绽���)Ճ)��5�{�?�w#�ĵs��5v5��es��xN|�zM�.F��8S�#f:���Q�x��(q����"�� >�t��>�Qa2h����hP*�m���6T�}��M���*Y�G�d=H7��YF�����9��a��^V>�=73Q�Q��ʻ�%�=��)�+�>�҅�	�Bp3Dc��>4$kQ!�}>P�vW��]'�ѿ��B�埉n�F�'�;T����>������IM'������6T^���Ё��&��5~k��D��C�C�흨���a�o	��W��%�HY�o|��_%�Z��U�d���P3w��E]������G�����rP��k��G��>R3��&����*��ޜ��(�0�<��E`m��z�N�mw�ɑ#����UN�����VS�5X�d�wu��q�Y����;�	h�e?(�Z=�rz���U��{~�j�tR#�&��5-��J甓β�UN�
�j�U�\v��M�/u�F��?ہI�����Y8�ޚC�+������I�MB��������S���U �u�x ��ƭ �_�xJ|�E�r)�6&��l3�i^Q�9w����1z�ۓ)�����ڳ�δ ���>Tgv\��ʶU��kΨZ�)@�/S���9e���s�	��MO�;�|�O���u,.��P�s_�οH��IF|<5��}���ME;EoM��l�+R�+�"�Ϻ�����̺P;Պb��*�Y�D��(�bQ֢�Sp�
MaTD�7��G��T��f��m�7�bP���/P��&�)� S�ܴ��
o�.׀�I��I��G��/���!�{��:�j��{{A7���@��#Б��d��t<�r�ɷ�Lw��,D�+|6�㊧`f�m��mŘ@��*��{�úc���ʧ�,RQ���<%3n;&�IQ"5�OZ�bq�)��Ǐ�|�63�������������ʼn+�%��D
�zT��Wo������&��w,Ҋ�z���E
u;�.� ���u��
����fZJ�������a?`�W�]a�৶�w�i��(l$��>#d9A1��r�0R&>v�IN"m��.?|���U�?�jƒ��z��X1��z:��U���h�8�$�ݞW�Ih�����f?�	>_/��$��e�Uwג=y�Kҥq������E��tv�M^�(e�f�p�~���)�� `4P�$۪�ߟ,��uA�x��ye�-Ry�NX�!n�o��/*��l�)�C�z��;ihgw<S��g��P���F�#|.�61J�*X�䩂|q�c�T�O6i�k0���iO��=�R�b(O����l�S�'qHo��]�~vZ_9{��u�b�y>�Cj��6L,��[�mun�ɴ����L8��Ys6���q��X'yP�O
�跗�p��p����vS�f>��SN?��/�B#�\��㕴1P�%ҁ𦌻��:]���-�1��zG�8�O=,y�f��-�pQm�;D�Ut�!y1������0m�IEN_�H�
R��Cd=+f'u4i�Y5�d{��Y�,bQ6�b�܋C� ��zE��V�l&9<{�����@�:y��U�E%~ Ja$��a[�*�H 	L�t��U�0C�9<�_�SBl��z�<ks�7�G�[b����������͹�O��8��w2�g�����S�����Ⱦ3��n�*����S�ξ5��Ց#�e��`��#�4��k�+ #R��<�c�W������ߛ���dL?��Np3���B"y�X\:�c.�ټ��%�)��`�k����0�S�/4v��l�v��f�j�'�&z7�_dE5+nB�"�%��^�%��
u,#E\��p� �4H9/�i9��W��[�%����
�(��P��,	���Mi~Z��4.�;��E��@�S��4�R3���߮��"�<��<�x�/�%�����u�f�n?��r)qB
�mfc�¤��I�9}L"���Ws:nK�F~��6�I�!��sp�ܖ�tBJ���%H
1+���z/�;�\)pnǩ�	�8}?���yZ5�t̶I0�0*Ŏ�
�?a���l������^4�y֚izN�`�AV̋!�g�7��)�G�[5�)p���Tط��X��?��Ѝ\jP;�x[Q�ؾ�c�NԈ��gb���%f�>�E���6(���#�B�k9B�;ј[�{�_a����:��1�����l�e��XT5Bp�;�r..?K`.�y���<���S�)�&=�RcҠ������UU��f��z���~*�=�`���F�U���-�G��ٖ!�"�㯳y~.�f�M��~���]hš�x�����ڇ��.�>�-��En�E��.��G ��?���[���.?X�~��RBE�4B��d[���䪞b-
{�ĝ]p�WsCGQ@���tS��U8t�+�2�,4�*�@��kf���bgҔ��/,�P�/|:�{	Ϻ�*4���'�'	�8ongꐕ8Gg�	�*�ND���$�Ji�qŭ"�ڐ<�:9�tH�\�zIpQ
l���=!B��&�~��*�xf��m�FNZ�ܭ9a�����cj}��+���~
�)�ϼ�$�vOျ]�f�.O�)�V�7:��~e�J@��P�{tsw=�ܢ�&�r�I�����^�<^�������h`�̲k�\J��<��z1㎟z��I�)�B��Q6F38-����W,�HJGe#v'o���t*�vc�|�^^�;���.%�7̰
��v�->}O�!5��*�\��<%{�!f�S��A��]��|��Fxh�u�r��䭴T��H!9�^E�o" Jj�=c�X��;ܗ�X�����?t��8`C+�����J��&T�a���!��pA3o���w��#QP��<������GfCg�%1���C�:�
�����:�]���U�?�̟�� ��.w>K�,�;�J���=�B���>h*�	�a�8��	op(W�~6�ޕ��2X�iu�L=kO�xBp@�~:Lh4�<�
Z�:��۲]��Y�9#i�0+=��Jl�3:S��K{����#S �2�<��)BX%.!�1
R
�F�����
A��A����3�<i�3��^����Hi*�n��<S+p~�m���d���1a_�K=h�b>�v/�˲Lƺ����8��,�A�0B���-.��F},@�T/�yM��v.6dO�%�J��?�S$Q�]�y��؅�P��zBc~$
�?��H�������(1;,Ӄs}`=�=/��_R�R^�p�v׊u#&��9��Mu�=����k�m��*���'��b0lj�0^�ʝ(|�=�=kJ�`I�P��'>թ��%Aw3��PHFl�a��ݔ�S�~�tN��!�jy�:�щ�o"��$���sV5��\�K���+��-��钪{R�C
�m�̼��n]�QA~1�y�U�@�$��}��/�]�������l�i�}�M�O��ȅ;�=����N�� ���:~M��7;D�W%��%�Z�T����;�f�v�j]`�ϩ�cl����Ov�� �D�>�v�>�vb1�!!���fٷ�i�����zPs�|V�h�Z���|�1�ae�~��ס˹Ξ>`N���u�Ѷp\i�P�uZ���9��6{�&��R�0��k���3���eң<�W%߮01wx�[8Zk`�O�!��<��Q�?D�H��6�^m��KCp��C�fφ1g��.�����w1�����jޛR���\^�s)���(Ԍx����a��U������½��&��GzYh��D%�u��5/�J�C���A��mF(��N<dPlڠ_e^�Y��U���@�����U���	����Q�юsx
���k-�j�7��s�G���GY�UH�)���bN/o07w�9�b�s+::~]T�s�&�籓}G'��y%�S��/9�ך�y6d�i���iImf��!w�ތ��?՚�ۚ�G� FD(��ɂ�ܓ���ͦ��Q��zdF�qbh%?���¤�2Ԑ�x��3���G�D�$RnM?ug��d�^�k����,�@�W���+��C���Z5V�h���n{o�N*�m�YRK�"�*jI��'�F��.���Sh��ɹ���I(����؅`��9�6���!�PCd��*���/��S��\K��m�`� ���A��wO���y�X�I�{���T13��!�f
�/埗�r�·Ŀ�)b���ʚ�P�����~��*A���|������Q��H�.�/hR�-���/Q�
TJ��ݖժ�o'X)%�����"�+�#�: �:CϪy�~u�_1C�v��%7��
xL!����>1jEB�r_%b���\m�n)!%;$�N�җ��'syM��*�+1gN�%ԁ�i\%��c�ڼ�B�t�V��`
lj��H+Uj��+s�H*[��96D��'JZ((�*����@<T��s�3=�(�v=~�OsWV+
�������Q�gD�y���/Ez 0<y�;/�J����dP�A�L�n]Fn��۽xPH��%���v|�b�<Ht��l_K2��g�*�qoK�g~˪���7d���R$*�
Q�|�c.�>6�Կfս�
/��l��vy�1�.��޾��p�±�^"���?�SY9���zF$��2�䳜L#����KX@�6<���M��.,��������O��u��ЅR�E���0��U�>*�qj���?�W�̆_�Ka�����>o�{j���h��Cy�<�vG�'�F�����Ƨq����7�`U���N�{C�	��3���Ig@hO�q_���+T�Y4�O"��b�T��O��Z�~�T=����H�p�ط���*8���5ҫ�4�:Xy@�H���[��s>h��3���n�5|W����z�ے��Hn����Z���1��.�_�eo3����[{�@�Qr<d
�UX�(��)�ʡ֯*���p��p(�4
$T4�?W�v��
�B��-�`�_��`9�-��[x�Q���`���5�����V�>���U�W�[�WJ1�N�Wv�u�Wv�����~���]��V����G�IU�_�/�}(��r��eë��C��7�%	n�ວ�K�3���+_�tqsh��K�$K�q�@teC�ي0��cм�n�9��u1�s�bX����3�[���*Hx)IȖ��������{I^�$� ^lx6:���^c��2��KЄ��A�k�W�k�	�'=i�Zn�+�א��.���8��VH�̿8�)4��?���OI���|���
�`�B���i�c�:�ہ��ש�Y�.7��V
Q��|vY+´�� �
�[9�XC򏡼��j��V������^1A�{� ��,J�7|e?�>c��~#�̻X0���o��0��D������5G�n�|�������gL�"i�^��6!�Kx��k�j}8K���pR�3��u��	W*D3�Lf�,^�+}4��N�#9�4A���{�Fa�j�A�f�|�KCއr����/\�I�׼����3 @sW�3`��ҍ"_g����)*'�^�+U��׸�x�Q.)�f\P�9����.9fu���D�ȧ�end����;j�N�vX�;�����02�����cqL���F��C5L�n�j)V*p�F\��{f�\U*)ׂ���߸��y�U�ڡc�����,M�7�i�+�@o�����!��lD2�(�"���5�V�7*ˎ�Qj�7u�i*�����_��Cy��9���7y���w+qd����)G�ŢՇ4������3�]S|��wM�=C���S|�/oK���2]'�L-ȃ��8�QCs��'��M��-t�E�1�Nջ�N���؁R��Y�X�	In��f<v���+�'(E�q#�\��(��9��3��a����tr��r�-��|>��Ǽ(
�Cj����v�֊!�Xk%�?;��Dž���;ň,Y�a�ܐ��	-������n�'Ӥz��]B�|����[��M�C�]��O�$mBR�f�IO��E-bZC<�LB���	5eh��a�_��Xf:�!S��?���]���w7/�|��=�[��{����(F�)�7���ע�d�9I\��Z��V�E�鯤��/vI�K�H��Eu>��S�d�4j�dt�a!b���_�w�{�k�n6��V1����`7Y~'���\N��H)�i��$,�{�ʼ[�W/t�5�� �B-Nu��}��I��n���ݾ	��[�GwM"}pN�`m�B��k��[�|�[�-\7�:l����c���k��T::��t�ӣ�K���a�2����_Eti6�oH��s��[
�-J�!��!��\�W���`�Ȟ,ئ����36$y���n%�ykT�w���Ƭ2�6iq)�5�����>|VRFsKq(ץ�z���.��4�pY���[���9�V�m�s&l��pD�a�.>��mƁ����:��/�k7�����W�?]�YV��i?V�Y���E��G�4o�<b�k�#���~�%�
�u%�Z֟缃�?�I�|�!��`Sp
�H4��a�k:xm7w���6�3��m�����	�9�L�	"�XL.T+�{�:Vc�S��k�4
0�����
�p߳��%Oq�,����*��~wʄ�
	5�/�X����Kps���iO�yb�m*{$b��p�<�=�˜$��R�W�PV�5|�_&Po:$�^�rvԢ�	�.���ÂN	kjS!�g�wr
:'��ng}�M��>Ε0'���ӣ�hץ��>^'˗�"�*�����VK2�1��t�o��p)$w���vmm�L~6e��Ζǚ�zzF��96q�l����o"O1ᆎA}]6�}IlRD��M�]��K$������3�[��ìY�)W߷�F�CJ�2��,������	���9���~���N%�X2K0*�d��B!<˒�%���<#�İ��}�tHX0*�_�I@@�j�ܮ�V�ma�L�9G
�懴����c�M�¥��P5ė�b�)�<"S�
��dP��9��'c�zp�Ė̋R�jĔhCl�r��[���nr�oT�>=���>[0'��۴+���N٬ ��,��̼�ơ8FG�ۉ��Rk���������=(��Ma-��Km*�#�DȢ��O�a��RE4(���:=U�vyBK��q�|<���,o��`�*ĖXJ��yz��Y�q��Ze�)��RN~�}VH�3"�c�������4%���Z'����Ѣiv�v�|�hn7wp�%�#��Lʉ�#�;w�Q�����v�����s\�T��K��[b$r��=��#F�_��^�I�5���bw�“��I_6[���Iޕ6�Z�Z6\��BY���Oxd6;����j�k��
�j���B�<`m<ݽ<��vc0���m;�L���eB-�ӣZ<�h}
R��W�׭�M�Mv�T����C�#��4[�)��y��A%S8*d4�VH��b��ê>>��$L�
q���7�w�G���6:�ճ~UfA�=��:�`��bQ���\��̟����GF<�FOr!�w���@��RO(#=0��S}�{�srl3:���{�H���^s
��I0��UI���[3��`��s}�ZBɝA��*�Gۄ��=}ܫ��;�)_<m
� :����i�o�p�]��։�㲃U��>f�J�+�ܥ��ق���NG�^�Y���(F��K��0����T�^�@��F��F�9�S/��Z��Y��`;��M�J�`=�*qٰw@}�¡�{�2�&�?_�:�l��S�x���W�w�/��ip�����OG�)�ҝ=�4���ث��5}c`�^��'�R3���q�¯~&B�q�\!q��&]�7��@��˦:XAD{e�U���`�[��_��'�N"�簵�*�Ev�T^��K��j�KOò��+����D+�)^�V9(V\��r���{5�!���Z���w�8�;�C��4ޛ	�T��#1Z!�?��Vn�x�M!�n����s�NcsBc�%�L��KD�P��A��X�����}A���&iH�Z��͊>�lY���^w���~�b����'��*�	(�y��	�ȫ�M��K�M�M�o*/�ǵ$�k�k�I�Jw�\_����$��`-X�w��WE�H[�T��̮���C�[�׏�#>gQ��gw���|��u'G��q�~jS�o����L����:�� �����v��נ�i*=�
�=����܄�|�f<I��6S/�Pz��0@ϒԚӫ
_.�^d*�3+�K��F��H��u%w��$Hq�؈=t�\GAoE�|��,q	��C)�^��)f�+7��z�)vg&Pᕮ��S	�a�O=uZ��}j�G���\�����tț֣ۂ�r�ĥ8��|��N�їZ�d�.�y83�j��j{�D�@b�����^ބ�&H�y~ �e��э*����c�l(�������_Zi��6����;V7�&��S��܄#O`�Ir����G��D]܍�R��%�J�~b'DYN��H覩[�����*�8�� �nI�)�~L���l�ŪP�r���j�q�3.J
�3#b����	F`.\��.>�f뉫��x�5�
3�{]� Agc��x�C�fi��q j�I�M�����P.mJ4Y�@1�4�	�j��̔U2h��i�Ii��"�0��%���R����~��-n6PlU�~YzVS�&�!���<y\�����	<�s�e�	|�!�Z%^��V�dp��o̟ᖚLrU��!h�O��s��wR���?���_��������?ۿw����r�
?�y�M���t��<��:�or����ٸ|9DP�[��T!���
�bu̮���	��HPf�#^Ͻ�K�/8��a[��U�ED:�EI��s��MS����4Z�Z<QE����$���5?g�U�1�F��]?��=�}��X��1�9l${�Q�Ƚ/�%M�?��q":A	�yn�ߧ�Wނ!W
�����ة�5P�I�(�F�]lB�s1,���W�=�޳��f��w�-nժZ��D"���fv�Z�����{�
����w�'�ɕ6��ɡa1]j�5F���H(��O�J=o�[��^��P�`��5H�i�w��)ܮ�4+�O�9;g�����,�Y����K��h,�x�I�,�,�<�\����կ�\J
|?q=s>����C������3�  6#�PǕsPMj�.#�!���sA�秗ҼY��z���+���yM���E9^�t�S}��%���c��B�p�Ӆ7\��7�7�3�o�>�_C�#a�)iN6��%�}
m�$ޫ'�n�mH�o�䭐;�^�4��;D�K%�����T���n��mu��J�u��3��ĉ��ߝ�#ԻM�K����_��NgŊT��Xs�
�X73��7�6�8�Ӛ-�'(���	�ա�ʄ+����UY��5�7i�Y9��֑,���m�qRdEc+E
��p'nq�'/LGn�|�3�g�:�"2����i <�:������@�}���HF�f�\�v��2�:��Sv���g>y��t�a�%��<��-m@=��u&g�v�uG\Y��o,����JFlYm��X��ڽ�Gɣ�yJ�VH
:HV�S�P����-X�'o]���G��-�7.'R�#w�m�I��Tdf�7��n�'�z?!��f~��@�J�
�x.�+��w�f�P��h�kݗ�&x��t�6���>�0g��ذ��_�;~lS��'�G�_*��n��Ay��"�߉�o5��e�9R?����|ɮz�e3j|��@�J�S�,�t����j+*4(5E���3����'+L��\2�
��@[*.��µ1��"^s��^�Ym���R�)��o�S�M&�H�΄�U�
3�6޷�Vl9gi�$�_^���~:#��2<�_��NWL�;�.��?�:��6�g��`$V��
�(�,�_�fv��#���7��v!צt^�8��B�^}{[U�#��-�Jĸ�1�W�*ýiX�_#�e�YI�8�Zk�Qy/&{��]a	w[��#�_�S����Eg~J�=�u�����̕������l�����������G��ŗ�*V+k`�_n�9�1^x!F�-=�^qgf�y4�6�0S�1M[,���a�4t��(7�q0�qf�������:��p)�\`v4��A�zD]��N�88��:��0�����H�e�f' �,��&9�0i;��;�d��R�	����z-!�zq��%���� l����u��<G��|�'-�{���!Ⴧ��ו����Rp��q͝P�T1!a*��:K���%ը�K�vI�Be�x��)�ɳ��0��ЍI�)�s?����|X$��at���]�	E:鋡��*L��
_����%i��6�{�4�C<�z���S����Gޞl�{��Ζ=��M�N��F'���.ք U�CX�a����}GE+�b�߅ϑ��hz_g�;��uv��cz���,Z��g��'��L��{�4~���H[�$���>Y��.9���^߂��r1�uL[S^ ��~��(l��ҥݹ�Wx{
��I�Y�{E�O#:g��	�����,�[5?P;�C�2��&h'R�s�G+?g��
x���)=-��������iuM�,�x�ڋ�v�ʵ�MQe�|��@�|b��k�y��A���C�8VXH��
	k<L!�V{M�q4U�|DIԜ��yں��DسO�%�_�(�`�y�6�yyA������,C��]�>zĒ�k�zuW��2i-��gsF0`~������
?Se�8�43���Ɵ�c�E_֝��Я6��iFu,���2"�i;/�y;��v���hPc�^����5o�݆8�'^}a�w��~�����3����{���~�$�f� �5�����U�2���1�[�{D~û\�ѝ�6�9O'����v;���b:�C���E��r��ic��x�NK��$&�nR���Qa�H{��P��j]�����lbP�AM"�/|o`�za`��M�b�KBޑ�M�RC��Z�p/�)����b��r�&AQ!�BT��/u�l�X򣷟ä�Tg-��aӟ3S�����_������]Z��VZS�ȏJtr������{��.+eU�΀�p�W���M��RYٝ4����՗z��GA��>S�}|��͡Z��E������c[I)_Jr���0Y-�7��Ce�2���w���~{O����>;���π��'�ז:�)�j���dv	ح(���l�N@!�W#���r����J�2O\�Uan?G%��%T�
LfH�)����?��g�9����ʄD�5N��/�HN��!h��V�RStڢ�quhGF�MO���^2��V5����9�7�00�yN���ۈ`e���
�N}��6Aj"�
��cV_vn���6҇�.�cu��R�p'�_�i|Y�gqxK�����Kd��`�����4�c(B&���H�>7�|a�=s��}� |5L�p0�6
��я���`�7�x^L��ɕ]
��-(�K���C%��Lڷᴑ�L`�m���r��۱,�FF���M,78Iï�,"V}�#8�eטM�2`l�/K3"M��ކ+���h_�	�����X����{���n�˵���%Xj�K�H���6y�ލ�f!��$S�	b�0�����p�
��Q�A1E�
�G�=��G�i�]JY/&:_��
��ޫ
�W�a��\.�+�3�;�%b�F��P�|�$Ѩ�W\�f^1��m|�Թ���e���8��̖��`A�@X�
�>a��٧�\�H���=i��_Q����o�+eqs_|\�vvehm��w:�x�$�sgשּׂP�� �tzΪ)N�c��f
]ո�b���ܯ/X��;�f��%�n��x�򛺋�[�x翺1�Ww��c�:��d�1+>�h�q��#14,i���r�2��:�{�o��Y	$9Ř�GO}4�"���֞B+I�
��@[㬩5�75v�O�-��1A�B�^R�H�
q�e��G��
&mN�zq.ȍgx�j�}������P�_
�0��H4*9>�`e��\�?$=p�W���>j���6ק��4�yao8�U��W�[�������!
Y�ϣ�l�s]L��uIv�ּ-3��},�:$�fE�EU�SBJ������a�x�~�۹��5��r�ʼn5�\��ÉԳUh"��"�l:�ܥ��.ve���f;��APe�"���b_�g�N�0��5K}?�^}�Ԩ\4�6��
��������3��M�J5�G]1n�Ջk�L������'�؅&ƀ9͉h\�x�Qi�&�ɗ}��<��>Q�Y/`ܳ/&��=�8A�H�h�<��m(���|�5l���DZH��SM“���P'�d�݉�{t�:{�$uә���p{*,�3������IPr��撼(;��A�^ؤ��>��"
���-_�mdK��/�!����a{�H﬑��*�>tNPX���:?m�7_`L���񕻂(��Ȏ����k���a\.;0�Γ꓂�b�&�j)f����NQ��g�h�<���3�,T�|
_�.W[�Ž��k��w�e��ȏ�3��܏B(��v��#�G�����k��xF��w�¹L�p�d~�ȉ����w|	����=��@n^\�/;Oy�z��F=��!z�Za�_-ݰ�V�c�lWQ�.���9t�lC"
���m���p�Ej��ޘ>���ea���<ע}l�*�\K�՜�t5��.Ґ���I�+��y�>��:��M��t��}�$�r��x.�wrv
;�<]^BIM4"`��(�s6X�WzA'ƅ���y��^Ṋ��T�H_�a�A+�'�ZslNj�X������}K�*����ls��w�Tn�I��<
�"<�<�dߓ9ǰ\�ӈ���_ݭpbpI�Wr��*�5	����ƨb@��t<w	�G<�S�y,T�?g�n��L�0
�Rh���ӫt-�RS!�/6�/�������P�Y�nD
�t�m1���z�[��ٻS��FZ��o���H\J,x�|4�ja
��:Xl�i�%-1RȬ��<��r���U3���U����T۾��t�VS�i]�F
ܳ���;h��ף�y�ꪙ�l�}x�j��t�'�,
9�Dc9qo��Lǚ��p�o�;�R��P�����SU�_�6~�F�_�e>��wf@��v��6�w, ��Z-C�#��C�^���*a��_�V��A��s���U(k�C��w��Ā��VM�]�
�\�E�U��4�[2O��y�MEv����q |�b���_3P{�:ju�y�OY�B~>��s��GL_T��3&�1���b���(��4@�σ�����TW ���#[{�ycX<�DV�4�Z�Α�-}D��֓a���]�F�>Q��E��U��J���#��]qc����\�,�Y�/=��d��58=o�"w�)�����9�q��e�%�b��Ƥ
��X���v��˩5�k61%Nv�O��b U���B6n��t2$��ʤJz�u�+|�3��n�=�:� iK�E����	!���8�Rj@%%WZxcb��V�1)��䈖�0R'O�ϲ{�K������v0-.
�`G��4@���J~H@��z��[_�P6�ކ�s��* ���Ai�p݈�R�-��p.�������ղ�	&t_�{,����4�9�)A�;\�q�"�4a��`�fL��4D����L�ٽ�Zh�U�4�J�#j?Z�%��*��>k�Dmh2q��Ы�T����֤	��;��Ζ���{�=P�|�1��b��[�,�����9Sp������^Ɯ}u���oI	]���BWK���3w
�1j�dLR��K�{��s�"s�s�>�9%Ĥ�c�H��=�f_o�SD_;�;��;��m�w��_�4����K�.�]8�Wg۽����l;������������
���ۦ��_�2�Պyn;FY������!O��t~�L[⢐_�T��E��_���82-��Jt�+9p3�b���>o	s.���4���e�
��tn"]�
%keC�w�wf��{,���q^�.��Y����0�(�f'�O���J�A���;��C�柱we�s�DuD�i���v�L�`��8��j���"��h+�h�y訊�<��2�����+FJy+�&���z�� ��&:4�����\D�����)�[�_b5��$�Qp�
���x�ν#��8���8���F�[F�RKw����Ԟ@�m@�t��Z�!Q
�.�崤՝�GA�_q��������*@]ߺ.�
�?�q�pӮ�E�k��4��έ֎3�~�خ��⡷tw+�Q�ѫe<��L��k���ț����^�.rq�q�&z���Hƴv�1�A�,�-�Ş���,�Ԇ5�:�GeW��T�p�OXg/�u�J1uO�k{���p�s��V�s�)

��I9�n�x ��튦k��p����R|H��
��^/�m�:�F���1P��V��!���V����|]�Y�]�����ҘHj��+[�y�)�`S[#��3����4�Ci{i�ۂ��d��(�}������}Lvx�gho~Qn�wЮ�$�ڕ�����+�5��������)��C�������#̿ì���:ʯs�w���0_�mԟ'�R!���I+8m���c׌��^�{j�H�x�GT�w+�G���,������u4lc2��ɣ���:���P4����;/8��a?�
�>q��^A9y�X�|�;I]l�����$����nݬ�`*kn��##e2/�D��Ό�iH{�����)h'�:��; K|ӿ�$7r)�}���>^*�㌮�
A��J��7W�m�Wz�	�f���C�.S�7Jk;�I��Q1��B�H��\t���QlS�ڴc5r��
�����Қ���I��r������y�w�D�Q��m��M�o�.X��0�pgO��&J���� 80.�¼��w�`��~�q1cak���'����7�]�=O�����t�,��kR��?�s?kc�|dkV�/���Oh.�0�����mﰣ���\J\����u�Cv:��ޯe$^�/a�QU,sOm/>��.g���S9ֳ�102���C�r3����
��wds�Yѯ�}�:����{x����Q�u9!�m��̿��&�=|�g��Z��SfM��Y��d�C�vm�4�A�_<���ܬ�VeQ���?�L?>�_���_�Ő�׊�

r�-[��b�g�0�O���G
�BlC����P�!��;fTo�R�~�R��k��������A &�HL"��
��B����PD�ʐdy�$�r�QC��z_��fs)�>�`^����<$�Ʉ��ߖ�S�&�c��6:��
j�{}�m\)��)���ϫ7��]���S���ދ����Ez��(�v�듒-����>]j$�
v|'e%�y����<�*�T#�r��D�Ϊ�YRψ`�.�����j܄6��`�v�w":�hc�j�Y�lȽ-���67���zԔ��}M�E����%��Y��FBR�8!7��f�R"$�)���Fb�����)ٞ�s��w�o����Z��3��\1����������}l
���ﲭ9Q b=���þ�@^���"6�}��a��� ��=Ƹ�/���AR��{^����ⵋ�Q���s���G�*��-S��K��Rl��HR��Ɍ���>���c�"=d|1O���6[������ͫv�9H3.����v�^�"�~Y�
�WTK��T��:�����+�f�Tm�#{o�[�U�v-v^�d�Zm�՟�n�^�C��9���{
�d�E8�%�偏��z=R����MM�����]���WV3v�1�_��o�7�������_�[������oǷG�O�m-EY�W�csf���ilKs�Lje�5X�#Z��
Z3��5Y@(�͓��76O�q_��\��D��fP�w���qf*瞁7�gyć���r��T�:4C"�)B}�����2 ��{�`3�"<�RP��`��Fg_�����hӐu�\y"͗�A"9y�'0
�a�{4��#$ZLx5
aR�Sǡ�H\�~"&#{|_�+e�N��4��'s<ћ�8x�	�N�*ɡ|���Gu�!Vm}���M@`B�Qꌄ���YQ��?�7j=��K��l.IM�`������Hs�~���?���&lv�ne}�X����8?�Ӊ����j#�����`a��"V�,r�2�M��$B?�g�{ς�]\��󝾨���{�
�"���9�`��V�x��#cޒ��y��R}b�k��E?�B�r֝C�4�Ė&~=(�q6,�.��������8-{R��0Kń����&���/bXK��5�ZT��yM2j�	�\㐪pp��ss6�'Z����]�
��z|2���B�~��Rv���Aə�@yp����L31�m|�IS���J�
g�U�
��l����7����+odӭ�l�e�0��3��Q�L�Ù���ԀT��3k�C��m8�_
�G��E��K�?HD���9�����醞��V�v�*x�(OD3G��X��/AS���4����K<�D����3�ʚ^����4�W���D�I��L�>�]����]
_݂g�`�_�k�qd����}#�Zk�;�Њ !�~��2���8���m�/�����d���"ܗO1�4��?}�^�ڡwVB��C�!������j�@��
�uB�;8��}L����J�C��/�����^JCE��E��'�$=�����-T�kZ9~@Hp�.j֮�~��5���t}�p�f=W��3@0�q,{��aerv;?�Xt-*�d��+H#fw��y@|�*ևh%T�Y��I���7wI�ީ�`p�<߆[���8��Y��n�R���CR�z>r�|+!-��ִjT�1��8�*�"��X8�3�V�YO6h�e��s!��w�Ϻ�%/{�F�^�	PY�|O�@8��|�h��%�m��C)�ؙ>����P5������P��w�ڥ�(0)a�\���L���RKȏ3��4�x�R�$8:��LCJ

�s�"|:����x�kՒ2�5'��y%n �~�P?�y蕥�}����;z}��&-mOz�4^��]l'{�s�&����e��*��C��~W�J@�Mi��ċwD����a�QJ���*]RK
�D��_U��\'757:�ej�^Z	���"�[+>���4�i1iD�	j.8�g���,|�<n�X��k�s��©��#8���+�p�H8Ѣĭ�y����ׇ�~�+�2;��҈�����\���]��w d<��]�Ƥ�xFr;�e%�}�aN�󡗃�Qb�	5���E�[	�!�^E�RjL�^S]���6X
�0�ї�X�
/
^���֊�B��a=�}c������Џ[W���a���h���z	�c��n]��+���ڙ;���M�^X�C�u�����]��Γh�u�6����ۋ/i��t'�B��=5Í&����ww��k���X
�4U�sE�7��\?yJ�yJd��u��7��֜_�����0�H�U^�q��.'�e7��kb؋��$��w���U/����^�;]M	kp0oJ7��%
�7|�B`s�s%g���^��E�?$��	��w���G����gW��[�YSF��
t�]m���?���R�/�%�g��[�W��/;�v�Yi�����识@?��K��o���ƕ?l�_���n?�,P�ZA�'���h[�r��:߬g��Y�n-�k<��1�B
��'n�2~T���s`�'}��o�x��cy!XB2Զ�'�bs��a������5nVZU��\����ry�H�^�VӅ������K���,д�LG�o4�Nlk�&�;'D`��X/�(��]��
�VO7���Zv�X��*d
C�y:��4�bx��䕖;�Aw�r�Ϳ	���5xe���y܉JxH�
܅r\A��e m�̸TtOr�I&�y���[9�� ���{4�b�K$��h@�O^��\k��
ɳR����}5�~�����9��S�#�N�_e)�&}�K���Ӿ�/M���p!՚"�'F��3Wm�Us��l��c�;���_�ʁv��8��F�?���A��|�e�	~+}�]�����?��)��u�׉�~j���|�����-���K�(����>���?β�e��q����W�����?5��d�����㗥kw�O{���M1:��=춐/T��+�R'o`����q�a�F��2Ó��4�JE9���5t�&L���.Kz���|<���4�*]�9���?W574�~7w8��w��t��Ɠ�8���cN�Y�@A��3��D/�8�<�}��?�u���4<V�	Rc۩�d�#U��ۙ����a-�-�Cp�[�t�ZOF]&�[�� 7V1,���ڸ�ދ�;����0T]�9�"YB��@�6�Ӝъ[x*V��ê���Q<9�����Rl��pw~�G�k�~}~�E@�fT�o���C�HW�c(��0�gA,j��=yM�jP.ҿnd
quV�c+#��
�5�Ǚ����f�q����2��e����<1����`=H,T����{�~��Xh��U�1�^���B�b�	��4uV9�%}Hl���Ȥ-6�K��=w�վ՞�S-V7��$n��r��>�/��4@�������~>
Gi����SCn0�w����n���/Z㮴�yfw�z+5� u��] 4�����T&�y�kї�@5�8{��
��$�x���Ō��������{̹C�/�Ҳ�W,���(����g���@��e�����]1�m���_5�)�c����?�c�g{�?��h���Y�އ�^����-�g������h��q�{��l��֌[�N�S�=���r³9?dH�pZWN�d��E&r���
$j�XU,V�Vg���ۭ�}ڬ���˕�������B,�Y��(�
���K&dҙ5#�3��F�^�-\1���~�*��S��S�]?��d���C��b����ER�[�75
�"�&F]�]�wo�L#)�l\����9���r�d�����e�������B�0�w�H7x�3�ԉrz�J0��
v'.z�L����"޶�����o��/,����������p5��f�9�`7
������3�6u5���ӷ�R��Դ�N�f��]���o/KcB��/7��|��*���%��i���@ʐ�����o�x�IQQ�q�`�3��:Ot��a�Z������qI�&��k�_�]����ٜ�;)V�}�$�
=��Y���*��<ψ�]1��RZ�8�G�2�<�}󴘷.� ߃B�NY��I�gH�K<>uԍkꀘ$���Y�����'���;B�n0�3��pQ��y�{��Q�ϱ���y
�?��.�Ӷ݆���,Ad���q�M����+���\|~D�_l��)��f�0;�����O�:��~0�'�A>�B?�ץ���W.qe��]����}K��rIC�9`:ߍ�~JJ򾌍�K�������m��8���*����E�i���/��^\Ԃ�ū=�w��+~oJ|��/k���O��[��W���FJ�|�Wvo2������8���}F:2�c�x��a�E�O�	$̷j��L;0A]��؊h��ޞpXj὎�,oX
+��-K�50���q���T��͗���Y�d9������S�j�����;|}6S2���P��0_�V����
��w��'��H�����K�_+�+�~��V�"��	�
_iq�x5Cѩ�&孾�T��)ioa즓��I�y�{�7�I�b�U�O�S�n�;Ζl��9Ѓ_`Ng�IV���H��w�ut\=K1���
�S�4�B�:�=?�i�[�&���g�s�2�Z���It�̨o9D&��<=�=��K��frN�k����co��?g�A�~�ut�C�1H�r�WHn��W[�-���XO�hhZ��5l��A:<k�'��8B)q2oW�5�	ΞQ_=���/��T�^�M�M�k���Lz����7�����M4�O�o�9`���Y�IߦB�?n�4�{���y��Zw��tz��8P^"$��P�(Q��7�l�kՑ��2�Fy��{JF��LM/��ͯ�t���O���s��[��oJ����e�����R9���9E�=.��J~�\�<������_��U`�d.i\?6���Kd��C�� 
Pۖ[�E�>^��g��;�����r�u)���f��H��#��t�/����w�������,�磰�r�ӷ�q�-�<E�x�Z0�$[[b�,=_tuk�@\=���K�j��e��X'f�L;R�?�TB,9��오�u��'@z�([�vu��oV��b{�!'�3�P�5����T��T���P��q׼b��묒aw{��1p�,h��*�6�*mW֢��Ԙ.~H��y����u�g��t�Y�lȟڶ6�&Ҍ�K�"��}��ث٘G�N�+����N��K��2�Dqs���;��qs�z��37�^{y}j�u�-�͎�́��vS��n���a�pu�v�͕���!��y�y�N��\>��J(�rEB��֝�
<�����<ϛ`~���V�\b&qu �\���r��p�����T�����d�V��qo�M�f�Z���014��x�1��k���3�
x
l��3����W�^�X�L��n��y�	�A�.��bs:�{���y����\��S�ƃT��?֋�rw��q�9Vv5o��BP.o��T��G��Z4�������"X�C,^������z�R�Ym��2T�Zid�}o&D���<o�%c��\H�.t��$F_��!�
�)5Gq���	�(�ͧsu��¶W����݈R���pB��-�PM@ͬ7j�����)�R�ٕ�r��ނC�9���6��A�H�`�����3�����	p�6�	
�Y����M���D9ʏwO���%�KÙH��0�� �OJ���l�uATR��y�I��Rd:ZC�@ ���+	-Ԓ͸uc�J��BKu�)��	�qjf]�(i����I�R��]݋t~c����C�T����A�(Y9�080+X�Ķ�>&�Θ�	��B
���Cn����#v�����؟*��'`�nad�R�����y[=m���n�X��巂�*a+XH�H#�%7͗�����X�dz{P�F�2���*�&��X�攄6q;�J/fNY&|��ݍ�,팏��#XV���a���j��xg~K��ĉ��ɲ��-�s��L�y�Uƛ�ۡ��V��Af��=i$�ل�%��瞠.^o�'�:�︮�ӆ�Θ�c�8xi����ȑ�JXu��/�^���9��p�8�?���,~SU��[�T��Ue�'�i�c_����?��BG����W�8��K,��0u�!�C��U��n��|�gq�C�������Hg�ݵ�m~��n��E�O���<��a��w��p�Ss�S;%��zLv�/��ߝ�Y��"�o����~QK�Z�>C��OAӤ���V��'��j������󭾮��z鋽⽚�Bi|C�$�g�l_	����/ӛ��˼P'
�s�ćWVT����U�@��2Ƨ��G�8�4����ƎKa���7-_hxh�u���(0$+��<W�-�
��)p�^�JV��3�-��_�l������D��CC৸ke��
�Fs�t�p�-�x�>�'/�=�y���mV��t��7A���Y��[}V�CQ��p^�n�I� wq� ����0|�'U΋H�)e!�u�uy���Z:���zM��5c���L��f�U�b�,�f5�AW|����*Iy�RN���n�f��_��R���j9[��h��oΉ}���wz٦B�����'1){���=t;����T;1A�”{�q�;���=�|�f
��_hD�t��6k�Pَys�Ӕ����������PN���c��"Oc-
���\裿���t�J�u|�X'����ѣ �X�
$�4�WMh�C��Y��DI��K�`n���߲�k�1�W��z*�}��=��ɴ�ı�B��Kd~�)��GE���褻*+[J�2�JbK�n$�Q�oy�^�/@��KM|�7Q�8�߉i71�
�֧�v�X^�qIݠ���]���JUj�Xs��"'5��s���e�&���.F�n�~�0����,�b��.�6Iy�c��'2�N�0��D�q��������s��:r>a�C8�t��,�p��}���JY���}�ٗ�V��eR�u�ڸ �gL�r?�Y�*�:�_���j���w�F��v{NoY���=�|�I�a%�Q%z����k��`�ͫ������\�����As��X@-d�CNd��f��H~G٥O�90Ti�%��7A��'
��#-kTz��(���s�B��P-	\�`G�o��	�YHb�X@�����C��Q����LuX:�HI���t��/�#�؂��Nq}ro+�M\ޜ=�}��䰹�SO���m��vod��|��!A�:)�J�/`�={>0/&�Bʌ�}���HI_n߇�&Br���<#N����;ھ��b&��9����Lr�o�?�i�#8}�N7%l���_��o���i%L��<���e�%&�w��id��d�K��"0ˏ(V���L~��V�n�Jb�����h���`\�	�Ϲ8��/�xH

:���F>QNG�/׮m�>�|��jH��K�׫�)��ƺ��̟��{S��|憴/��>�:��9�lɋ�9.��`�WQ�٧�E���^Y2����}/��r�|��B�˧B�b�7�3k
g�urr�aQ�c�fl1�"�ჯ�֛�Q�O�lإ�ˌT�H�R.e�;��C�>H����j/�ۼ��O7�Շk��?�0��r�7mݼG�Ts֦~����'Á0i�z^)���7�x� ɈI<D[|)�ܒ���k>H�R���#��ݏ�s��u,�T�OCơ���&�J�s|3"�K�+5
�c��؛�Lj
����Cf;���
����Bv*ݽР-��QXxȞ�$eQl�5>�ԛ3����
#����T�� t�<Ѻ��R�V~�O{���JS!��N5I�	����.Z��7��ƒN��5?�lR�X�5�W�K�o
73����9Ns9v�.	l^��[w9eS���1NS���\�k5��z��5���5��*�'���mK�q����Y*Y�tI�ڛ���O�:ȹT�ec�˂	���j�=��)�˜��0�룉^��wM>�Q�CG���H����JT�~n��?��P�NɈ�A������/�F��q�����ݭE��o�k��>	P"$fɉlgq���`T}g'�ѱ<S0�µ8'��Aŀ�����qu�Vۄ������b�V�;�RIY��!�v�֍��h��i�����Oߞp"�$���ϱ7H8
�;�艃(N
#K�=�:Q|�z]�[�k�_�V���ƍ���(��~�f��ە�0��W�SA�:�t��rm*�:́�B���`�;��`��o6����R��L=}�-�&�D�ˆ��O�җ�Q���t-/vբJ'���
%W���^�	*̚�I��2�T��/���d�느�l�o�5�ȍSz��&,8�
,�B����H��K��rHZ�ح$Vr�a�=�����%�]q�����z
0�M\��ߧz�_n��N���_���_@�o7��r��l��M��wc�?`��y�rR�l��Z~:����}$����D�%�������W�)����{��@p��P��Q�D�Z��0��.:���? ���p|�~A��z�/!ڷ�W�Ѿg����x:���~<��x� ��i=Cf��E����̇d.R4��[n����Gk����̵ �|���d�K�P
<�ԟM�w�"ci��a�G�n7[=\3&w��χ*e�4l�	�����#k9��Ȋ9IPc5|d���Ȑ�9��!R�yk�����P���O�v�f�=Q,�ڄ�
DqwaO�_�qN;V��&(j>���%��@Y���=A�Y���/�_P�Na�h�@i)g��]�Hz@�qwt���Y�
h�x�_�}�C���D�`�[P�3���>L�2�@&�����@�����g�{�ߠ�K���`�����D��Tؼ����J�KO�̮a߾�|��@� ?���-혽�1,8�s�?�e凗��3s�l�D&���p�G�t��9��<�/�W�>t�n
�e�!L�oA>V��b�b���&�f�^�|(�G�bq�9"[�
0!Q�7
���%a,�q�w2����)%EIj�����`h��ls��'�jO,oㅧ��w@N�'�iJ���vDa�6��
Z������]���"L��R\���s��dG��� a�/���~���z���t�Ѿ"�kDf��<����������B�~�
��~��4��{�o6^�7�E��	��(T�Ҵ�����5m�ݵ�נ���Q�'�Vt�pf�A�7�[�q���ӥ�? �ѯ�A�gC�GՅ덯�
N������{a3+
z:\e��T�]���r����"RQ��,���gG4O��|4{��M��
�p��$ƥG^0�j�x,9�vw���HE�}8��j�Ů��*|%���5�4 a�E�"�O���i�QGϻ0k��;}���x���W6�mc�F���� -�K�n�1�]�¹:�x���G�|�X�S��Ȃ�kJ�;�hrS�+�=��P��t��o�	�9��c�Ǿ3FqLѡ�f
�>��@����%Q�$Q�$�z�xYH1�
v�d_��g�7_��@��[5�e������u�kU8��{�+m��1�оc��5�w�۟�sv�����uI}��F��av��l�D~�u�I���>>Qk�p�~��0�
�D
W���30�-��qh}"T�U�Z�_����(y�����^7O��Z	��ܛg��i�$h�`�����γpx�Y��屦?�,zR�Q����@D\�T��K{?���%�[0�D��W�BnB�i*b.�^7C\t��:z-���`<9B��y(�D�{�$�<ˎ}��p����7��X��)�������M�� ��)'�u��"��̅�ۡ³�qLvi%m]F֛��n9	��<�>$���l��0��O�W����Z�Q�ac�w��GI��T����>�
�wAY�TM���H~U�������Xi7	���KO������8���T�;ha}���;���-x�?G��^?��s\����eF����:x���@�_��?=����R�����QMa�O�q'q�?�@�c�y9]S�dE�#�}tE5�b����6cI}؍�~�.{���y����&�i�p��p��HT���UD��,%�jV�D�
\������f�C���o��|/6]qE�t"��;���nU�'l�{��uCC��Hg�Ľ_/\�PQ�Ċ7Ш��	�{�!`���se?�"�s��Bf��C,-PT؈},N��⵸]̌>��#+�@��۸N9z`XD��'م���必����^s�+V��<_#�/y"��W��Z�����s������r�j�lԏ�:���]��^kM{R!�0�g�������k�7� ��j�||C?���!�]���(���o�y�.T�]����^(wYO~ˇ�r㞓�
O.)m�f��~eo�vv�ǿ�я�!ž��=����OT/4����8B�G�S�JW[� �o�;!"\Sq��x$\�*a��i�r�I�������L>�'���g5�t'�|b-!���z�ޅ�M��Fբ��s���g���4"W=�q�������;^���c~k)a��L����sG�$��>�G��
Yx!҅�/+�ć�.֥D"Q�$Yfּ�����0��#i�Ѝ�]bH�1 Ie�&��9̐׌t&�iAo&���|��&q]G����P�����kt��.���uշ����W�l�Q���tWy�o=�/�`�.��_���H&�
x��[SIY�B���3�9PH����/�I�'�I����5!/���C�\���c|Е:|��m7����N��C�=�8�������ծ�JJw���L��6��44��L@[��Z3�k�g�3�j��u��ſ>l\*�Q��}w��o/�^��5��·�_�b��x�͔6�����!N��"xK\����=`��nρ�Cj(��8��O���R�ĺ3��.����A����&���Q5���h���v�+%f5�돩�R�7{!̨�#G?e�>���=��@6?[k*1>z$Tҷ�-S��x�2	]hB�&)��[B�����n�B|���8`�a�z悾���/�Q���V�"��c�
�8B�j�V�"-琜��^��t�Q�p�~{o����������Gֶ�ɰܟ���)��a�Q`^�(�	�VG��s+�I-�bL��C��N��AˆHRq��3�����Q��Uu�;gI�CLV��`?��֦�Y#H�u���O2��"�������b�qnFN��
sl}\.���WS��-���Rz[��!���}�@�dg��ӆY��K:~Z[��*�j�������wV�
ÞU3L;���r�?�\O@��e�x
�խ��ςM�����%�0K��H)}5]xz���n�D8[���O
G��l�w-��x�&���\+���=/-X�X�}�%W8b��K�C�R��@?q����]ư��oqW4#Rz^�r�^�8�j�ܧ#���&<��N|FF����W�"�7���U>��]����@\�f[�ߖ�d|�d��&+٩��'���n���Aj#;�i�ɪ��p���R�j�KB���
�]R��2U�� �3�<b®}�%�������y稯��ޟ����a]�x�b<�������_���4�}�ɍ�Ӥs�e?�r��%F�u%��Z�%��
��r��ъ)E|ng��(r�0���9��E,��l&��:c���n�]����5�v���	�0������g�lD��9��l@����ˆG�ҵ���>�-B9�I���5`��@��N½4���פm����oY����S�B�hy��SwH�f@ɑ	EDo"j̧ftx�P�q鶎��(oW5?ʪ&�WϪVM"3�e6�E�i�'T�cW�c�&�c1�C	�b�t3�]�ۇQ�[ �x�����3��~��S�?տ��`ߦ��m|�K���N
g[�6ԟzZ�o��Զɖ��~'Ӏ�?ԪZ����m̯;p��klz��ĥ`�@�r�>�/�v~�����I;�͵�{<���U�X^�\`j}��m"�q�/�l8]�~���_������-�����{����8B!���5/�����O�����X|7��}�m��>J
h�W$.���Ƀ�J$4�f��I�X(����u�Zp���lE�襆> �&��a_�cG�K@ꗙ7`�z�*E�YiT������TN/���;vƂxpnֆ]w�>R�hNgyMʠ�R^~�I��i�[�|YyF_m�E^x�S-�A�{%���j��Ǧ�_��z�c?d<�X=ݡd�����oXNh��Y��
-�hޏ�f��
R$��5�i�7�6�{�H"5=�)y=�{G�Y��tr��+�S��s���~���P��C��F>K*�$�V��{�NV�6�g��EǛi&�ǡ!�
#0��h�����n[m����$�]MT$�5m�!vǵb����7��=q�Ԍ
lஎ�Zu T�zP
��LV^ν�Т�/�b��{]�(�o֚\�KÎ�3�d�PB�t�q�2��Vj�V��8�hQ�V�5�� Fj������rL���Vo-
ܗi����Tc6���J�XK5>���`�5���ݛ��V$)[�ek"^�{��G�6�a��by<}R��>�7��7�q�}⾛�oӠ[U�?"�/�x�?oJ��y�Gtx�M�p+p�>H�?�]U���2E���ܫ����?4�v*�m��9�p�����nz�c�q��v���H���q1�B?r�/���> 6 �e�e׷��?'_ՠAt��K��H�~���)6�����8C�X
S�E�}PBK��SR�EI�R�������,i���/�F	Yӹ����b(�+�
���K���Al}�ܮ^?9����F|tݐz}�=��0*ھn�����O ����R��P“"�kd�;��n�]op��yb��H��W_#K,����u#�k������_$߆�Q*�ˍ��=¥�)6�4��)��t�B(Σ�����X�Q�u��;��n�Q�F�/Jݨ%>�l�Q���F���
�>��;N9��b=�/ j�D��o����͎�擊�]�tg*�M�k�h��E㿕�e�f�J؜7-��D��
~�9�6��{w��>	���K`�ˤ[��>�On�p�
T��p�6@��6��Ju�.��P���Z¯��_���R\��S�m�y��\�wgz��W�0���z�fJ!0rgK��}�ǘ�,q�M� Bx#2~����p�=4ل��zݨD�MW�G���{]�%
2E�8PJ<JO>�1�ܛ����u ����^DN�����#����ɋ�,��撻4�)^m,�SڍSB�� �xFT�"��FD	�D�YIƫ�`S����OX�,�lȤ�G�ヲ��|�@��(f�d�ܝ~�M�|u�������<d%*VU]?/���I�I���>-ƌQ�Z�71�JW[>���f�`�T
̌�)x�"u y��"]����̧ᷭ7Z�l�귒��7���УH��2��;���$�G�Ѹ��R���l�i� �sΑI�b~Xm�d����D^J���'�IU�N!��pX�3�*��'����wd]�P��[���(��8���w>C�H���Й�� �^�>]Me�5�L�Xq�D�{�<_����Y.�Z��G��=�U�i�ɂV�C����Zv��"t�"�Yh�u.z�XD����/IH�-���R�R`�_E3��/����~�?���ϟ���.�M;~F�5�Z?3�]˷����c���k+�{n��`��9pC!Z���F´S�����u���<���2�Z.��j?�u�ŇC���~jb3�4��'�.�T��6l�׵�F�O���(��B�?؊���#��ɥ�WE`w}9E��䂇�ݰ�A�8�{�24�|QYJH|%!t�3H���9>~�Q�xvWe��vqN�Rό}`B"�i1b�ִ�׼^�"�9�VA
�#��s-�D��5Q�P
�����O�f���Pl��n��D�uA��#�1����DHQsԡ`���xJ,N�L�49����3<1QQ'�d��+a�x�鎒\J\ӣ`����:}b��j�W��<_�g׫�>�^|�;�=�-�K�#�m��(,�b�0�'7Ͻ]�C�k$�̱�����;V�+^g��6}mHlxni�)��qߺ��$	�2�1Sw�j�䛹oOH��qe��⽡�⽪xh��a�LP��b��G{#kT���id�������M��i�!���|7�VzrL�P�4&��uסk_~�	c�S�^,�=��v\�&��m�lS��U�S�7�i�t��a����@�����M��;r�
�D?�5���Z��r&祬��&�����U1����A6^Z�sy�p�'�}�Ň�I�_ٔ�����gH����` fa�d-aV�%x	<ya �]ԟ�
%Y�b�nt��4e.�vF�"���Z�Z&��3�I��5��1h��z;�{��29X���+����4�=֧�3� }7>QI�4.�I���
K���/�S7��bb��s��;��OQ|���=,�
)�e��<�yz���u��l	qI��^,�)T�!�E�\��Mq��V#�gf�lj�hL��Zb~�����8�]N>x;�o9R���������|W��c��x���X2%�����b<����VR��.
V=�������Z����-��S�H��<&@���4�=A�h
���J]!,x{�|%�ފ�}0��A-i(�E?o�-^�%�{x+˽��17fu*��H�9��|j�|1�3Ի{���D#d页D8����� >e�|�ʽ���(�pb-���yвWN��쪆S�r�7Q��9�︕e~a�Jypęg"��]'��ˣ���.(p�#|���ES���jn�2��L���:#�n�P�Ԣ5����N�אO�������{�_��`~��M�ȿYTm�=?{�y�g���6��<��~��`�M�JZM�M�����O���wv�
]�!�ŗ��W���.�=W��_�}3���%WW�v��k
���z֋�}�|�����\�]�~��F�[	�C#�2��
GS@?�MJ�3qb�Q޷;4я��n)�h��7�q�
��Yia���8�5`j�.-�P��	f�Ç ��)��O\���Ə�
矢c<�i"���Pa�޷��O"‚0~�c�6/�h���A�
�:ib�ދo��,��[��(��0ۢ�nx�%;xkŠ�i��2��ir�W��G�W�zt����,���j�@"Ҳ�1՞�i���X[�	�v7B���ʹ8����uwx�3\�J�H+xOɃXT|�!�(�HP�>[���S���7~x.DR$�_")  a������x��\St�]�ނ��<�b�(���6�vnr�_g���Mj���-�^�fy%X���A^AS{���WH�I91�6EDƳڻ�Z�5]d��5�c}�ٻÖ��-+G����\I�WqˌJ/Bh)�r7;g#fE��?E�~�-�+����*��0/�@��׻B��qNkc��W�O����O�X���l�>ѣk������7A��			�J&�KE��X�S�+g}�+=��O6O����L5�<5����(�R��w�z8�����%5�x?���BeK^��vc]Vh�jk���X�Wpl4�8�L�lwl������n�H�ۊp�fI_�����B$!"wo
*r)�_�W	~�@�2�*�®������"x$���pX����}b��tw��Ų�"q�<{v��g��U��ޤ>�,;=�o�k�h��:�򖽨�:�cS��i۳M��,B�<O֍�;�O�E�������1;u�Ɗ�)t��Q<�K�p�*J�c4�cO���K�Ec�����űךIem鐘	�>s�'���%������U�–��,�yK�ѱv��
��Κ�D�l���	�^:-�)�K|��s3�z���ň5����)ё�pA;�{�u�
�e�
ML�Pk���6�r=Y�	obm�g�!��΃�����jJ9���z�{��]�Q�t���j�elا��XbۭQ�ͪ���=Q�q~g�@���c���OUW��l�w��ڨ�ޥ��'^�����e\��(�/U\�ٓ��;�?fQ�Ҷt��b}�Ҏt��Q�ԏ��8����8ҿT�|�C�_�����
�wH����6��t�{[t��#�˨�s��p���GW��Tc���5�n�dnջ|�DJ?'!Cb
qy��'X��ڪ��_.O��wVS殾z�l�U�ʗA�x�f��	�;u�{m{��٪�n'�\Õ��s�Z��d�&�}�1����&>/�G`>;I@�\��1�tt���R�y7�R���k��S����VJ��ώ��7Ş�k�]��k���>"Ţi�]a�,��)�?��m ��'����p�:h��alA�,�\��T�-p(�����S��e
�j+�M�fud�EfT�>��!9X+/,����Z�ŀ	��������bh�3�`Yu	R��&Ŭ������OOθ�q��0;�s5U�i�}
�:�>�����l�7�������Ľ�X���=��|��,���Ģe^����Gϋy���t�{H�����%�Mk�3��i`e���%�N��ª���ֻ�4j[��]���- ���?�u�y�)ϝ�@ьwl�5�qL�m�z�4��@t���sO��VW��Τ���p~��_�Z^��"1����S�V)dM5��b>�E|ۑ4Y.�������X�G��������<x����5�_��/���%�N��]��ﻎ춋
��UYy<���o��ߕ��|��_�ہ_ܿ�z�K���ܨ�2�tA�/Bm>��F��)����e�܄�6��x|�l�W>��,a0���MR���Q��x���;6i>�[�R����`��+��~��>����8���������P�'��N�WW����$�*QJ�Y��ٔ��1�2��q)ʏg��K���l�DT�Z���O8Tz+"My��" �]��똊��mG�2�������>���R�#rE�H��Nj��|�.<8��XkS*6p�)��w?�wLJ�M-8ux�~W�Ń����
�&����JvU��� l��m�6B�T�wOY���'nˮ$!��+ܰs��\it�ˋ)˾B�׬W��FS¦Zw�?	�¿X���J���W_O�TZ��eS}�,�����Ђ͝�� �G�3���q+��9�z�I��T
c�>D�E �Q�k$�Q<�a�t����W�ޔ�i%g$ϙ����^LS��/���&��LN�i�a�[w�i���g��<��}�|�yVs�m�<���ۆ�K��#!~>����Z�UDf��
F��Y�
��4�-��yh��f�>_�8�d��t����m-tӖՏ2=#�k!g7����#=nek�&#�x��]X�֗�d�����_�_ԇ���P��?¢��U���Y�>��=a��^K�zq��i�t�w&~��~ʸ}���)W�7+��i�A�~�n.��+�?6�����篇5_�S����:�����i\��"Y~�NV���������NՒ���fc������_Mf$�+���ĥ?Д����u8R��h��WWTPm����^�풔Wh�T���H���[p��l&�E޼�_��\���Bl�K4���O���z�ԝ�*�D�5�M�„[O�V����d�[ln���g�ݟƵ��:�b0��uß���VbUP�9�FOڮ��n�s�@7�tO2�dh(��H�ZZΫ����̤/�(;��C<������%�h�}���N!4&�f��^wo�$�n�p��Yb�,59��+$��@�V�=Zh䱵�ZR�u@�SC�c�V�ǥ��_I�a=����H?PT���öu㥽?���z���6�����\�m�}�����=�9���������0]$*x�T����+s�����-G�Yb�6cX�|i�5
2~��}4��ة��l�BE�Ϣ��x/^����G�nۀQj2r���*�xO�=Kk��
7���0?f��ʄ>{)
"Ay��8WD��AH]8�O\1�jA�hi�~x4�^����T[
z�w�Y{�kNJ���l�S�~��&�L����?o2�v�7W�u/��ל!Lv�x� J~��_)~��<@Ç9Lb�����9;Gg0��Wa
a���l�<����]�X�����Hg>;��4�;�'<��x}���� J/S���E(�C�u���j��y��{[ys�u�W�5?Ӽ�2O��N��`�]�c<kis#fs1�N%���w83�2X�P/����[}�����z9�AZ0?K�1���h}w�#�lf;��)�iJ��e��d��+�9أ���j:5Ї��m�Dw|�l���}R$�#':K�����=�:�Ǧ{����,�6�Ic��
@�*�d�Ⱥ�Lh)yG�wj��Dվ^�A��8�-k�3��dAσ%X�[�'T$���Zݡ�G�^�J`�R�e���+-�`X^3���(ޅ���D�NN���e?Y*o��K��XP�$(�w�@�R�%���&(����n�MS(��'��
4��$��7�ᄸ_��IT��i�wc�p��G7�q]N�` ���S���d���e���l�"�f�K�CItx�J��ۮ7.j���io�,=������9���'$�+]�3��qL9�t<�-s�_`u��Xm��];��a��k��X-��X]MupPv���f�������u���|�~?/�WaO=5�����OX�!��}T�'^�r�~A�X��r8Z}҂��#[#��H۟/�sG�:���L(�`��>$E“	!�r�-^�̠z_A��w�������cyS+����6�s����%\ZGc��
+�$|��3@1'�A�xc0F� Na�aO�����`Y#��z���6�����/�A�F���"oz)_ٜ��)ܜ���hߝ�Y|u_�'�
��W�|�4���pp��%�2�����ɋ�/��͆�W
��KU]��U�T4l�
O�z�y���*�/@��|֕���{�61�=��BR�c�	���t%���"�Ǹ���=s�F�*XI���-��`�c�����C�\�aI��K��i����ו��†���TMͫ���z���)-���~=�b�H�L(
^M��5�{�@�9Ri�ʴ�H�@;"��.�_ŭ�I����ۙ~$�^��Z8��Ӊq�wOa�A�f�[J�FO���SXM��'�޷����t�� �;�
V�bQ�$˄�}�ӌ�&�LJ�aЩ�C=�]��z}�ք��$<h!�T2�&ZݒBU.��ݎ뫈>�J˪,����bmPw��P�W%�oST����U7\݋7����o�>G�=��j�H۷F��-5J0h* }��fAB��X��܏JԳ,�^�RI`�#�`���Z_6>u�K����|���^�MB�6t��٥����w�Hw�L�c�M�B`!9��	d��i����Go�x�j%���9����ھS"���%׍5����x5W1>�6�J��}<��tPe����VD�9'{!t�v���Wiw�:�ϧ�4��z#hI$�:�է�%�O�'�7�ח,K%=�r�w�;����pۈ3դ�}5t����ȋ%]��q�����"1j3��QУ�ü#��N�
O��aw	��p%�=�M���#�yZ�l�Q���q\<���FT��EE��G��Ŋ\�{<(I,�>^2�>��2.w���=�`�3!�	�5���\�	ɓ���u����m�xv�<�Yt����9�V?AJ����
N��� ��=����¹�m�>����O�?`�����Z����<6̊1�����/�o�[�<p@���w��IG�p�}��SL�rq]�Ə�x7�Q��g!gw�����XVmR�i���*����"�X[tș�4���'��y��=���Ci�6���c'�M"	�'�e���_�LxF{�R}��s�4ݸ�S�I��������3�����v��t���$D]��u2M�P@��#���W��@�?Ӄ�l۰y��,@)g��lX���;�J�.5u��[P��~��ډ�Ԩ�B��"�?�����ȸq)U�w�}TJ��"��hH��3,���PA����۹�Ǻ_��At����d[cr͔o��ɛe��
��J���=J��G1��f�]Zv�i��2��ѯz��ȕ�V�
�eʬ�3m�e�K���C��`w-���?˜�W��}����K?М𸕧<�YP2f���4�C3#dM�,
�Gj��s�K�@��+t��h&��ӛ�gM~��"򛼋��h!R�M�Ȍ'L(�/����X�W��Q�7՚��%q謖�_��o���+�O`�n�4��+n��i/�@}�Ŭ�Z&Wb�F}��HjwA�P��|����i�nA��*F{�}�w�@^� *�{��)�4�50Oz������ੜ�ܨ�]�ƇF|���Q�@H�W-�d���6Tp*z��{>�����ֈ�B�%�p2�ܰ�q|"I	�Ł����]�Jۯ>j�a�_�7:�"䓹��y���t�1�YY��J�+�>�#��F��qo&�y��М_�	Q�m#/]�N�)W}wM�ΰw�]��o�
;��m�_B�R����h��7**P�uW?�{�����6
�bh��Q�V+#�n5��l+���4s͇Cz�̴{h��͔*�V��N�2��{���
>�[yYp���'��RS#A'EEܚ䓁l�� �H������D4z�ʺ"�N���~���P��~�i E��^���d��{��q��9"�݉{����5[��X�#]��F��`�E��_n�5Y���!�ϵI�e�$��Ё���r�
������t�.�~��E��:��I�/�-�m��?v_�g��-�=�%���E{�;�'��Td���o�N���_�=j��
ɷ&�V���'U/��"��5��׬B.�cm�T��ЅK�l��w���'����y�H����)���22�r*��y��|��z������Q7�t��@���E�y��Ԟ�	
�\�뻃=+�E�'/X)5m�
�:�Iٜ�ܦ���i��鑢>֮s&&.��ֲ�A�ɴ������g4��n�c3ˣG*���:CJ��B�T�0�G�l(J�����3�Bz�0�x��3��~�^H]|H�S�Bj�g�����tG��hV��;r{vc�t�l�6�^n�H�(�0�H-��-|Ց�����Qc\�QFm~J�!k���F���%���v3��jNحG7c8�f@�
����D�O(4�
z�{?<V})?x~.���d��&��x�~c���G�`.<�&�Ј
9[fI��m߮�p���zu\3<WG��ȩ����W#r��.�88��,�%;(r�TB*�o.h��K�|H����8���0�b�Pw�f�Bd���˓���e�=�ꔏ3PP�~���d��	�i\?�N������;O�)f��=٧�|�(ě�L(2Ͷ�v��5�1�=Cм����d������b��5�ߨ{�C���m�]|��@|�82@�B�P�M39�9-sX���� �L�GOF��Of�O��y���)c��'�[<�������tY�g���,�	q}�W���
J��T,�
J�������_�����_��gb��e
et����^4��Ҳ(��k���;q̶.�s�g?m�B�0���� urXq϶��V=��eFD-���CNu�9/�q+�n�E�Ae��� u��PfF|1���G���O�
�Ȉ�%x	����O\����8�6}���%����qPre��쑅j^H�v���{��r�O=���`]$71	�T5-���ݨ�ȍ1��ֈ�tqqyY��mhb����x~~�1n����ѣ��\�?�_�5��GF�|c4ɯ+�:���~��!��Ek����JW�K�������<��X�)j�]��¾eZ5Ե�~I��eEz;��&��V�w����7��^�iOev91�V�qo�^��7)�܋��!{������lN�J����ː�G�U�_:�Z�:���:�^x�����;�<�#���›=�*��u�X%�f���-���,�ǜ��mk�c�D��+�mX���S�g��������;�_�4+���:N�"��34A����wZ/��ģux�L��"�4%��H$��'��X��c�H���*�&&�в/?No�V]N3�n����[��܃sa�`�a&�<z�bšm��=�BR�34h��U{h���X� ������dt�l��?y�"�!e�ݑ%�֧��Y%�_���`�X}W�_��R��Զ[˞D�}�W�^�ppjŠ��l�ډW�E�?l[5�)fx���`J�Z�:�m�,��$����*��?X/�y�������,p��8��W�`z�#H���m��3�d~JR
�
�G;dKu��
��Uby�D�I���.����o���嗼���^��k0���av�'�"`�|(x��˞��T�"�c�>�?������29�����yXڦ�����n�A)���
��l'n>�]ӡ���:���v�S?�ь}Fh�C�G��2�D���w\��?u��m���?���`����_I�����I�~�1�|d=x�-N��{�����/���+n��7�9�O�ͯ�	���S���z�kA�
��W0����J�C�"^�7���K��C��M;C��Ѝ)���!��N�!�*�y��eq��-$�l#� ��̅���
 bʌ��[$�!�1�C�}�҈���FLn�mӞ/P��Lfr�F����^�VԸ@����#ژ2��?;��]%�Ŗ�S
�x�ۍk'[u�)��ys$�uH2��k	��$k�;�O\�&j�՛hpL`��^��A�n����ݺ"��$겓�턏�J�z�Ɨ��[~��ڔ�b�o(��_����U�=U����R8���I�uz�ֿ��xN�;nH�ZVN�h��9��Y�%��]��W���������[��j���T��'#�q���/��H���%�<��,UkB{�2\�a2z�Ko+7�	Go4I��N����h��5�t��$Lmd���8*ѹ�vw���b1qp����:��&_@���<�p��6�6�]�2�-��E��X�֖5��EG��랻s�_]<}��qN�s|��|>�Y��bǞ
�Q�އ���k�eV�ş��1,�����?��|��r
�&�(D�%�
m�e�%xE�YM�x:n�t�Hd$���oo8`,W
��>���p�\�yL��f>��K���$��=K��e�フI����r��2$�i�Ȗ|�c-&Q�q��H��a���`j�Zz�\m�ݛiM=�8ƈ�(Ŷ�Wӷq�g7ف��_�ӌ��Ti��
��7�J�rpiN�M�����yρٲ�d��/f��Ӯ����Eo�V�*1J��Q[%����e.5<��x�T��Ԙβ;>�n>�q6�}:�g'�*�*���|��w���
V��gNV��n����e�&�W��y��~zZȬ�
�H��9��	�h�E�����>�`��
�d�������o�/�R!!2�*�2�n�Pk
.��?��4��[<*}:�lk,k�5��8>�E �rq57������Š�-�YE����a��A7�axu+�G:��I���3�h���ڃ�	8x�=V�����7u�z%�ט:Ä7h�ς+�[�B�ꛭC���6�"���o�u�I��(��܅w��e+�=���D^^����2�!�
X�9	�%�3�J++�Apc�N*���Х����tI���M�2S5mt8�*�n��͸O�hm�J�u��%ۣ���;Y��aZ��]��`����Ҋ���6�s�
�A���`}�{r�CRX���U�;�3*UݑR���R[
ܼLa3r�� �2@2n�

���(/ae�\;}����4&������ir�{΄^u��/����Z�c�	�כ�B$�j��1�,،���t����>�_��꼘�#�1ܲ�i�x���Թ�5Rp��Z���X0}���8v#����x��w�JGX��M����4��kXnhk���P����uQ?��~
�E�K������L�/�ո�r��~,K��TAB��w
a�����[m����
�v��_�h�rmv���T�af�}�V=��ܥ�/w)���<b�K�Jh��l[���m~�a�)�r��[�t=�k��A\i�dQ��1Hk��B�����w�@}q��=�o�~����`��O=�/f�C�����Ϗ��y�8�8P9s�鲎�'d@&���뤄R�3�]���J�����	)���F�Mv�G8�h3SvE|v�(�����?���uq�^��j�/IƳ7��z1�,�G	�!iS�k�,��m)���$K�A�/���Ks/�����=�כ^���"�>ES���R�4����G��/*�aiNЊuw>2����#�Q�H��?��K�8�K��n�p�P�V��u^k�B2�Y��P�5ᨕ���E���(��d/6R���d�N��/.?�co��|�>�8������.0���!�@%��y�V�ۊ����gxY���Y���#駪k�*�h+�ix��] �'��Ba֑��h��r1j�[Q`.���E�W���#�X5�!j}��/1+Y�0�������k�l��V�m?��@�|6� 09'8t�l��l��
O҂�%b�Z|,�U�^���ݖ[�@G,z�D(������G��;dQ)�oG2��紇�"�C:[7�֬<\D�y�*xz�{����C�į���+N�T���{T�Mz{h������;��fhگiԴ�^�-��^��p=Σd]�O��b���H:�0�Ik0�I=�1���N�\�cN���JO�d�#'���=���^�fBS�e=�Gi����U���፛
�"����[�=��ʤ�#M�>��}�Lm�[��Q�	[J�O���R��m�g�����&}�-"
�9$�^�w�����rf18�y��q�&}�g��l�=��Ԗk�����m0��Y+W���Ym�Y{�?�D<����m�� f�	�&�X�bl�'�����B������D�*�K�0�@*^�q9{�(Ԅ]4[Iz�_o$�h�n���+LJMq��͏��6oږ\AO^s�tb��_2���k���&�>w�H�+�(�K�?��H��Ձ��VR�%��F���V:_pc)�o �ws$�}�2��u�3}��,	R˨.9�g�4P�!�7�8��Ј�(�U�B^5jN���->ȼ�������A��)���S��}f�R����p����j�����/`_�p>		P��.��}���1p�`�o�<<���H5���~�<~:()�E3��脗@�-�x��l�f��6|_��)I�2��Z�C���v�ٯ���L��R�z�;4������I�v�K��}���_�I�u31�<�yWj�Ųﻶ/��8~��2����`�/~��uP\�zI��V���!������V�����uƁ[&;�:�if2��|�QSt�,&��H�g�0�
�,b�"HA��2�j�����-�j�c��+��h�E/�ݴ�5��Q2R��� wcD^� w4`ö������<��p�vS���#}|�Гd���ɹ�'��ђ��*�k熪G���#X��d��tTUS��Q�K��ܵzʞ���*�!AӀ�Ѷ��6v�S/ɻzgn=!��f�%�
��H�������J{Z{.v.�C
b���4HhMa���O��i[�׍��Ļx�ƂU8 �y�P���M��B!t���78�����*J�T]�n���x~t��p���F�5K��]ր.<�dưJ�S�l-�ṧ�k3�7���D}N�ϨNF�.����nK����8��f�$��l������ܣ��WJ�ڍJ�Vl
}�I����bR�
8M�.|��1�� ���#����sN,�^�z]��w�w�E�kfvFZ
>��|��C�>�B ��u?�XxhޯəI�4Ĩ0�c��x��-��z(�W��?��BMr�I+�:V4yo������H�V�;v�<x�wL&;�Y��E브�x��$'��X_[u�y����>��p��
���N��� 	��3m�c������֯�������[{��_09�2��2;��$�;���[��!��ƶ�G��p%y*�c�m�f~�6��?@Z:>�B�C�W6(f�m�D<t�yߛ<�ӝ�ǁ����JTҀG�k�xr�~�:��C�xb���ܰ�a���r���n�ޥJ�u����љ;��P��{�FVؑ)���g�ʘ�Ε�������y��>.��V�e��1�c)wM?^���j��y��^S� �~����L�}�F��0�����j]++Γ�
�
ɷ
z���5͇|2�f�v� F��W�ű��MrEH�6퇇X��J�M��T)A��Y�����Z$c�@ф
��O9p'+��Cz؆Z��M�-�\S<�x/
��O�^�j0-��nZm�0K߹w
/b�X .�Y!4��&2��4�t�>��*
���a�wY�|1V�1�����3y�B�#V����P�M��OA��M(�-H��8�C���̾���eV2�v�.��f8���'bM� ���3!�A���@ӗԲ+�y_DݰZ8�O���P�0T�Š���g[��T�_�w�;��4nj�����+���m��{�ﲪ,�z��6�H�ujP���M�b����7f��z}eHS
��80��vM���X��繾��o_2��d�N��خ9X�q�gIލ��=��,vTz^lxYq�K��i`��
����~M�z8���Y(��m������0�ʿ�"�%y������Q
���}�?ʅ�F����1Ѻ��r�Yt
"l<	�<�k5F��w�?l�=��Y�m:��xK-�;������M�l��w,�/��ѧ����O��-��p�Ss�M�_�O��L�!��>���ȸ
���8J8����!H���q�Ē>�e>�;z�9����c�RDt��s;_� s�c�V<ڼ2>��P��e߼�'��g�����_\�f�����y4:��M?^�ͽz�'r�eZ�u����ܭ�#ځ��h,.0�$�9G?b�Ti'P��/��=s˕�QJ�k������Ϸ�ה^Y,����c<�Q^�!��7�v<F�œ��I����wJ���<e^}�O�����W���n�C��Ŕ<SR���������s�V�,Y�׾kL��N`wi�K]�yn���D�.ǂ��b�f]h@.i��}�j~r
�x��!�[�Z#����5�я�#�1 �kD��<z��{K¬.<Ҏ"�x{�c]Tz��B�b��o�J�~VHm��]Z����2�I��'N&zA���u���z2�yz]�<S��Q���R/�xJ[��a���Pj�u$���a�-6���LY/kF}��94�`f�(�	��q��fu�������^�Br�ՄX8�_��AG�>3FKC��Z:����t��V�^N.Ɯ��Ev��6��W�m
�v�=a���
)^��< {����Ġa)�;����0�.�wob�OTܛcn�ZG���Ğ-�@�����;�`{�W�ȸ�H�4<usrڂv����c5�?�$�=�"���67���
Vl?QM��۹sJ��@`��<�UӍ(V2�� *´�W!�Ȝ�#�_�\+QL�bc/�X��vo���T�&�էˍ�'~>ovѶ��T	�B6?��ͻ�*����$6DE.���z� f�ux>i�E�OGn���Hc���,;�Hp&�W\[��I9��3x������	����3^�]��]��-��c�g��o��ѯ�:�+h����jC��)����eUdl��-Fqdc����o<���I�َ0~���Q|�Q�;��t������)u�b,Ɲ�kA��hr/W�bP��I�&k�A�6Ħ��c��h��Nf|,����ap��+]�?��q5
.ɰ ��=<��x� �!^>�U$:P�kK�Q�+>�29�/�{X��� �#��gp#��s]Q�L��k�z�=�"�8�t���H_�c�&>���$V6}�@Ԝ�Q[��&��'�I�-�#[	��S[g�+����NN-����UP��ގt�����"_�
�C���/a����$����O@��K����*��k�p#��.���yF���<��M/�u�w���`�8�=���$.X�x4��T�A��cX:��~M�[�oH6�VVDZ-z9���� ���*Җ�����}�od�ӌ*΋���sz9)`�x�^g�J@!�g��^�s{12?8����vb4%�P<ޟ�U��M�-;�-]�=O��+�%�#�͝0����*����;Ω��Z���13�H3�WJ�!w����o>�	�9)X�b�}���*�G\2�>H�\����{?�*�Xi<��?v�ć�h]�ЇPH����oW� ���c:�[DBU~d��\������O~<��C��ǿ+���%���U}�t�/|9"u6�q�r咁Y�&��x`�-}��f�Z1�z�C/&H�<�A��(�-n�R���=��&H�;&�&A׉�bA�1��JP*�(�w�f�ԤV^����VF}8c�4ƓVYQ2�{�ޅa	�"�O_�xv%��>	�����҈�=�`�x&`������Y�3B`�D��Iz���Y/���!C�%	�']�N>.��	���Vk1j��U��(�����8.JWE�~��p9��p���o-���0lpx	�i]V�і3����R!��K�c��`@+��DsXd�܊�}h��ZV��
�uձ����)�F��x������Rfa���r0:�[�1�$�}`L�GF��'�u�m�u�E~���ܭ��"T,�y���:bo��iO�G8{4���G��yk;D^�Oc���(X���LzE�w�%���	ƶu`7Ð��Gܩ���y&�ɋ.�=�'
�Qɧ���Lw�J�|�mh��)/i�vw�w���H(c��+=������ė��Z�ڗc��5S�S�!?T�cX��W���������ʅ68pB�'Ä�0{�+�}(7��\%*v�.�7	6J`B�D�_��4Oj2��jTYF�ž
�)���*�8���:�]x���&;��*��Y�I��E-���O�څ�L�:T�z��|��TA��VPO9#�'��o���[��K��	&���&�#Ϳ��ܻ�)���]��d�	�ZkN	�\|u����$��q�j>a�w~�륹�$�r�`���^�a��W��+�����ɪ��׃�8���׹����%ы���mt��REͮ6��0���1�����B�f8�"&�V���*xV�+-]��#S�-�V�-op0�p����s�!��N�:7��Ꜭ��]��q/��R�P�oR���+3�J��,٨����'k�&��g
�p�mPs�6�el~#�߯�����4��R���|�8��B�N'�I�t���O2��2sG,&q���c��Z2�þ�Iᗿ��}k�ȔM������׳�^E>_�:��i"Z�J���|9�L_��_|����FD��v�����:gUwM�]����O�!>�^���/�Ǐ��s��3�W��m����("�W�����á�[|}c��ܮ��D��	e��Qc�R�>r�.q���L��:B��D��S ���U!C�1�d�;���3c���';�ǧ���h��.B��q2��
��y�U?(�D�n;��i|Fi|99V����27�u�>��_ݯ���W��#�CY�k��;�c�T«M���P��9�H��=Q}��ɤyF�t�I��m:XO�][��.h�5Ƴe(�s�Q{5�@X'��[$[�6%��x�k�D��’�nj���W���Qi���;
w��R6I�v[���p��~�ș��z����Ɗ���3��m���=#f�|���+]�=�S���D0ͅƒ>��v`0~,����n�����Fh�ku�6�%���7mW�(&�=\�nj���Oo	ޛ���~���)C��s~��*F�`�����Ex �����tE'��כ����?��OC"��'��DQ/;_5.��.�(|���I��Ki�����6�h�u�y�P]���3Ў�<�����y�L�0��W���Mf��m���q�$�zRwC��b�u���A������(�ԏ
T���-�{r`��~��	oo�@��v=�IQh�5#����|��C�$	�]��~�el90���w�9qa"�1��z��"ܟ��3�ֈ勸qU&�C@��v����FL{��wL(�持v*)	:��?���0�݁��x�ot�Cw�1��#�c�€��~폲p�R�jj��pDGgL�}�t9~o(�_[��P���l(���J�ז�4��� �
e�o
%��R:�Kf���Z�z�%��~?v:�ϱ5��?h	
V���?]��l�/�U��:kV����4�='K�;��?.{�j�:
��7����>�����j���u�IA��C>W�)V�N��'b��k��|&D	���O�j���U�?>�G.�ӄ��>��x�3�^�~�J-j�o��l�0vz?�����ښ���"Q�d�{�\#��a�a\a�f�S~�Q�U"v+��K�j�^�#��`� |8=ѻѴO��8���;��6�vC@�y��M�����z�sM�/�{d��R��q�$Eg�Ӿ���a���;M��I�oCR�o��<��ӽ���ȲF��|���>�S�`0�����eL�����pE4�Mp �.%��OKIN�y�B-�mJ.��}�\���Vi�����X��Q��cS@���ð�����)&�E�ל�p�C���l�hI�1�/�Sx���Ǽ�m�ߘ����G5'p���zE,��a�[5�1*�1���j.3�s�{��?���6�ڮY��X���p�'��Æ�7�J��RLc�Jܾ������8V��
��K�_����_��ǣX��c��6'�w t4nc��w�D�oh��8^��\�Qv�r�=䵬k��L��6hD1�$�2�jK��4܊[:�\��U�HO�w}�Jy����v^�E<nʹ4�=Pi�����߮Dk�dZ���(��̃���`�os���� ����ӿ'�7��N��*k�B�	𵐠L��R-�u�G����(�kر	�5��s܄����u�%ֺY��w���nĒT�/� �p�kZ�R��܀���2���P��lʷUQ+8=�AP�%܄���Q�ߥgT=ᲄ�i�
������C��t'6����$Q�}Ỷn������f�������=*|g��f���'h���o����
�y�5քhp����^uf^��)��y֛R�E_�"�)s-L�z��R[r����n��dv�Oj+ OT14����4F�k9�t��A���$,sۊ��Ǘ�PSEh��h1���A'
�0�z����g?Y@�YCB�cɧ��f���l�9�t����:�|C�Wx����'���W|}��['����vy)PR�)u�z��ݷ'4����EH�?��.�]�s�S�r���`���wU�?bW��\/CN,iV���Es2s�Hkz�}Y�b?�t���L:-�_�#;-/��=� { �GJ��Z,O7��ڄ�}N!��S�^��{Φ�����195�wT���=���^�'�1N_O�h������{O�~50;�}����rV�R��� �9-��!q1���P�4����B@��P/��P�Щs�/�����Ƞ�8���o.��;��
�Ѐaslܛ���Lz���VI0[EX��XXu�$�}�kx�`m���
�׻~{�3���j�0}dՀ9�'�t�O�L��vk���xe:�]$ ���Ea]5�a�d6�h����e�t���rU��=Q�|,C�W\�zB�,��~�G�D��/�3ˌ+�]�GhK�%�ڥ���OQ:�k*x������ϔ]�C�įj@�#"���z�����?���Ie'���Z�3��B��2�A^��[/�Td����+�oWU�V߼\�uV1@�%ěh����~��t%F�V�7�[��0��8��W�K��Ǔ�TI��*��a��c+�5}|��T]����k$�x�Ja����V��s���_Z,�\]��Cg�N0_h��b*���.������1ɟ?/��E d�Vc����~�����QЬ�mܠ��,�r�n�^��{w�����S���uI3���/`?�-W�,��5KZ%�͉���cZ���
����gr���+Vp~$���?N��~��0��֯���NH�ST�ڷ2=}`��B\&�~2��Ú�擔��J*�
{�n=?撵,t}y��y��åT* o�.�����~ɡV����?B�&�76�rzS��Ż@�������<l\n]�0�P$�F�.<��7ͳV��	�/�� ���K��7�|�=������!�̧��%;LZ�,��!j,9�����ė�Ң�m�6�?H�Q�!÷��
�MO��/��$���!�3,���<V6.R��ez_2@p�y��AC��}��#�v¥�	�[�R�%`��^�?ν?'_t�"鵞b�c��c�t�Y�Ч�O�6���7���ԡ����`��@,��@0^ʏ*UA~SE��,���=1�W��O�A�8,BZ�X ������5;�3��)����^�U�3��	�H6*2E����.�(�q��mr�m&й��=���rA�U�����<i8�P�n!{�<dRB��!��}Y�;��5qs�a���<��z>�ʎ���$�YW�*��H�fv`]����y"lEd�ɵ� �,k��@�zkO�u��	�yL}�C7b�?�k�.��nU5<�h7µ����X!�K�Ru���e�־Ɵ<@���h��7*Uw���ox6�p$�c�4���_�hZM��=�%A���"�N{�v��ӯ��w���͆�m��-��i,�?
'����l�t��oƒ_L�'<q�i�?���Y�/�Z���SX�T�↓���'����'����|�L�\����Y3���.��R��y�r��y��SGk�~(�_ļх�
���P�{���>X9�����
�[�E�|\k'5�G�hpU����}BO��͓�>,�u`e�X/�j1�]���
�s�2��,N�+��J��#A�9~D�ͨ�gMqƇᒍ�b?9ɠl�t�~}{��[�(j*����� (��pE_L��
d�~L�~�j��Nd��5|9f�Em���J]�����$�ik�+�^j?&n��P�`Y�T[P �75�.Q�ZE��~B3��{��oQ�����+�b�7J���6f��A���ពC+�K���c�m���x��`���#ק֧8Վo�� ���4�Fs���gh&���HXG�+�0i.]/&]���ɦ���U=���<��Q��H.PTh�_JO�x}��.�e=cņ�^/�A�a�^o@�X��%rA���#~,�bN4�q�IXj�Ѿwl�/����C6��?���:�kp�d�,1�\����$�u+GL�%;Hž@8��+���>!_�Ǵ��^�m��S<(��Z7�5�%�ޭ�
�]��9�+S�c����#�/�˯b
2q֏�o��?a��u�����/���������hHId���b���_�������c�K�4��/�cO�}�n�id��J"s�|
�r�����w�(���~	�@d�M����ї�ѝ$�k���kJr���.�����=��K�K!����A�<;�~
�?�46Xst�����2���ε�=-�iK�h����ۨ&�I��ײ���j�5����+�N������/���2����o�*�
��S�E�~��G�����B|��6M�n�)�C>�m�C�����9B��6�U��3e�)g0c��USG�1���� �v/�����-�d���}�$k��L��j;��F�7+<}�<�K	�/,�.,�y��t೭9e�!�5C�s�EMw�x������{*e��Ҹ(�f{߬w�T�_��������C�ݍ��"0�f� g-��e���d�brVޔpH��G�}�o�;'7!��0�s�-#{+7c�� e��F!@�_�=�|ӪC�q-�:y�j~�۽���B��[j�Jp��p��CH1n7������B�|��P'�x�Ki�K�X�����`H0�dK���e�;~���=to�D?�M߷)!���J9��Kf��.��Mո��.���ǏBt�y�n������&��^^O5!7׷ȣR��elN���_���^�'V��L���S�3��������<
��%�{��P�F�9{����9�h�ՠ���	��k��:�s/=�-����C�8z���ܹ�7GZ���=���<������~���(K*v���q�O�F�	^�������AF��lU�ņ�)B���szUax�H=��;S�3Ӝ�V8-��c�����q�CcUkU�"�yc�G�F���fed�)@�q-�L�a�ox�hq�8Q���b}���G��Ipa̻�3������Fw��W�u�:��Z �j�'-,�8��:)=UQ��}�o��Hr����b�Nl�['´�s�Z��ډ�����6i@��/^y�g�iͻr��|�" ����WK��ͼ���'���r=.�R���RFT+7��D�����RЪ�Bȣ�3B��4��ک����~��ZΫ�z�y���2���Z#2��Xе�|����l��ߗ�稸����|��d{00H�4�bJݞ�H�ل@��I��j��R�є���k�=v��4�����J�A��)}JUT=_���4Dߔ8�p����;O
�c:7��ͭ�ͫ�kbA]��p�8#d;�/V:d��_q%��aޞ�ۦ���{�X֤~܀�
�n�
v���(Ĵ�Ҧ��C��$���M�3����q*���U�:��5 �/{��@��|Z�/��H����X����Ȅ���R����'`�9>xN��f	�	ڣ��A�C��>?��$�b���Zo?)���v�����hD�m��v!�{�W��*�*:��T��Sg�?�|���/��>���I(7:t�x�Kح�֫�u~�N�o�6�[-����l��ii-��s�r�����On�O��?��?���v���/r{��/�v�|1�ᴁ͛�a"�M[fMy�-ݞ����+J�!Ga{9O��y�!�[��ի�79�+l�A�c�Ls��m�0*DgǬO���!����R>tӤh��n��;|'8�6udsbF
���j��e�)aؼ��ձ{�3r���'�r��*�
���`j4gj-����S������ЕiK?����d⥯qRf^�6�5>�`�&%�9:jwahWA�)a\"q�_%)*]�9ħ--��^������S#2^�5��4��z��:����ˤ+G��;��+�w\B�D(щ�`l�M'/��9�xu_}ZD#��G�f��fl����k���b����ň�K��'�+�f�4˨��PA��|		�7_T�B)3>���s�K+��ZT�m��8�߯����by��S~_�h�&
(��z
ݿ�w]�w��$��=vuѭ������J��d�>�$ZD0�9Ä�Y#������}]��=��?��f�~��'r6&&x�W��j
L�u��֌!�5N76�f�Jy��$��_��@��K��7&z��5�>����`�=��$r�]Jɉ���׽c�P��d�YZɐ�΃1�
�r�;�.[Sk��I&�n~�yTlh��Jc��a=��ٽ�k$Y�-�H.
�~��v�����H�"��ef(e���;��՘O��՞�t��J�1�rz��TVH���	��!�z��7,H[^u��F���-l�� 44(��B��nZ�P
��H�LϒFat�@Z�#���������MT��%^�f��̹���u�׷zL��'���ߐ���o�VF@�\�W���s6���K��>;)
������2*E�*LV��O��
H)���x��䴼R��	��lXdJ��D���'��h�E��˜����,�M"�,��/�zڧkI3�;2�1%1���(�t�$Wúx�!b�j��@��I����[KBEj�H���JwRޕ�Cx�t�FT�[! ��m,�V��қ�_؎������R��c�����O�㇞-5����ڇ(ț�J��`�w����xìx��
o~[.x���r�m>��3_n�4����+�ȗ���~�{7�a>�_+a��O���ƛ��"�`>e���ᗃk�lJ&�f|)@��FDn
�D8W�h�Zu��U��|U��) ���c�1I������t���Ѓ??�Q���s����>��yb(��O���m1�e.rIm��U��Jf����.[��k!�0Y��İe(�����x�PzK�@��\μ�����ʐ�e�����YT._l��I>H"Y}���5E;�Zp��x�ľߑx��[���=�!��^�|�x�(L	e��7��f��q���OU�/U�;_KX�o��L�ʲ)@(�X+�{�>}a4��y�[RrMH�cX��7�.�J�<L%x{��׉&e�BG�
���l�$�Зr�øj�aueN51�{�x�b�e��e
_�|��U�=?�AxLJ����Z�]M��ȝ)V��R�����M5���N�R\���N�k��o�A�G������[7فe��G��GJ
�ln�Y
���ӱ�~�Q_l�1�w[���ir>%̙��e�19��2&*/�Om�M����r��������(����
�<��BG�2_`������x�u�86���O$���m~�C�K4��Ⲹ}����ZB�Y�4���4��xAs��;B�F��P�y����-\^��h���tuX�W����,:p��3�VX��ա�m�	Q;Y�6�	�0m���/%��v��BOW}��	&J�[pF��<!�gʝs&�X��rsQu��2��*Vdt���s���~����p���,(�`�D�]�q
PqTgy��Ƃ*��jLf�d��w]��P��1�;��(y-�
t/zV�8(�U!Ix������LP�J���F
R���>W:��%�G�%΢��u%�S�;ApQ�
���ҧ_��)oFH+6>&�&-[�ɳ�6J��yyͽ�g /���+
����B��g.��eB-t$�)�����rי68tcߨu�ݔ9A3:����!�x�$~�&L��JHR_�{�jOf����Oѣ_\��ڋ�p�����*>��@���C
�e�����m{��",w�Q
F��t8�H�s��`�n�8��6�A������ނO|S����)���-��]�sE65�(�$�>��g
V�~g���}��AI����%�P�(?8���0qh��j
�����;��/�`�f8f:�F�̧�� �=��5�NOl7��t�ٖuno������,_�nԊ���`��}_.����X�f|//���S�<Vzw-|>p�fG���;��W�]���.覾D����%�я�$2�b�w�:$L�2(������Z��x�o��O(�a��d������q�9���=k�����{��]�SD�;*�<X�pT(i^��6���8�Ak���N�k”����í#��d�q�l�o�7��W��xE-
�I܄�(��1N�)@>ܸ���Ц����_ �ĩ�	pd�#ί�.V'p
�WK�%�?�w��e��z���@�.�H��ҏv5-�$!|
YI�2�v�]Ƚ���'6�	��6��qD:n<n�dڨ�����Fe��|L�o��ߣ�*��V�qǺQ����c��`k.��aE�8z+�5�%�e�߾A�_�2�v����iR���2�J�`����/�&�����>��{��uM�����%N.�ş�T��P;������}�R�J
*�7쨮��6j�7����h���e����/e"�R.�'|t��,?u"�g��}�P��Y����Á����~B�m��iy�_vZ�V-$E���}դ�h�o��T,E���
o�{�+�nw�#��������^4�=�E��|�v��_�ݤB�|v�K"�^cy�q�G]���6�J�A������h2hG��W�)-Ԙ�̋Fu��5�r[P�7�4~���	ٿ�!��O4�?��r�Gc?�(7z�әfx	q�8��m8�*?��n89���>~�`�ްRw���pzQm�Oъ�h`�ig"��&�fn=e��J���� ���.���5��ZzeҜ�|8t�'D�3�&�K%�����z�ѯ[�C'�3��]YG���}X�PC�p���(�[�i
t뉛X^�gXO.6�	�AQ���jl`��U��U�#I땊t�2�J;Ww�o��*���l�@���ݣ�R����b]ji� �ms{��Y b�r
v7T��ƽ�ݎ��J���p�MupEp�v&��!���.F�$�:0�1@U!%
5��4��7p?и1Nml�]�B�3�A�{��NN/ɸ
&־�{���
�-G��	P��Y��Y��<_;�	��P�*�Q�X
i"١�b w��~��I��j��jk�����>|И�ɇF�2�>�
��-���z/��ks�יka�O	�W�N�cA?�?kX==:��
z�J{aY���ע=�L��ś�K&���TDp��Kj�BR��z���D�T�V����a��*�Q�:�K�zty�$v�E^��U�;�^�M����i�:M���Muu�E���D¸[����2P�<ڿz�r����|�Z�\1\�޺��}'���&U�Q�ά�۫�y�C�ծ�ꪫ�
�媵m?� 5{WozQ`j� �ܩ��8��!ܥQ43�`�W)5��L�J����S`�` �0�d-E'0^֯ob�T�t(�ܕ���K�",�4�p����P��y��<M��6��=��H[�]ۛi;O����s��~G�KRV��v�.�X=����R+�^�#��jOۮ]� 
�LʵD�=w��y����*X�j�-$zд����Ԁ�b��D��8�%~8~=r���(��_�R��7�/�`�3��G4�NH�ﻙ�O���o�礁���Y{0�%f�Ƣ&�%���ZС3�1���q��y�X)�,�L�	h:�Pp�[P}~W�{X��:
]J�ϣm�B�o�C�/�����?ӿ^^_J�ű�Ɗ����0���¿�]�W‰���vÃ�&�_
Od���#��~J~~%S��SNk����EP�_�Q׻����_b��'V8~�Q��G??�����K�s�?���W�n|�觝Se,E%�2~�b���˻0�#�\�$���r�[J���'{�Ї�^Z4��L�#��U���/x2���R<)�$��K+!]�*�l�ث!':�&zׇ���~�xJ��2U���e4��ᰙ�*�xwKl��t�F����4��҉�5����=��}.W\�-��@i��>4����a,{�����-�b�(eAQs�{��7[�*��_7�k�0�Q(CEa��TO��8N�Σ��X|%2�}9=.�V�u�Ys$R�A��X(��6��N�[��KFpHtSW������x�I���ү�)��~�w)�	�2y!��i��,��>�A;_�Rp�c�pYߥ3C�"��mi�J���)˚�����B�_�#���� ���!gnyI0]�ӥhz&�:�f�uE��I1����y�͝Q��;����ս8=�Ka�ɫ���Ur(�CB��^�!���ڄ1=D3'�!y?,�y��bq���a�Q��k&�rMuǮ�|{������(��f`�*�\��*��y+WѰ�I�.�F��:� a��Mk$��
��}�}��Fӆ����g?�+a�"�†����oY���ֆA]����'���|�fE�4X��6l��	���؆����(���Hl�7%�+�NweG��1��A�E��2�BND9[aD�I:1�*D��`=rR�Z�o1
=�rR�L�ߨv�N����='�`F2�<��mQ�B��jN�����d�gp���y��6ʫ��ʁ�����B�C]�-�}7F��FR��Cw�e;XH�hfP7��+�@L((V���<��<A�9
�p7�=�"9��λo�xܔ+���8�=_�WB���R^қ(�W�>�ђ/���ڍ@�����׳M�Hr�����r�-���̘��;�I�a"Z�	ܞح��
x/��^��8T���,�����[7	u�"
�a�v���8�q�5ٹ�o��ӂ���u2����aY;{·E�̫ēc(9�~���4�JŔ��H��d�]�<��;���B���i��GBe�c�E�ͥ�� i�Kc9��@WY�*{����OI��QK�8�������ݡ6�*�/���Iv�����$- 5��K��%2&�Z}��ȵsNj%h��WM\��=��w��+�K�h:���_
���
��`��=ޣd�b?*�'�����P��<�}A��kA��5�&�z[
�n��vw��KD��=���E$���o��^L��c�?�@���j�� ��D�9��ŏ�xv�����O-	\$�+EkT��r��	�6׆u��8N��&�IQU�޸>t`�����Քl
�̗���4��'�G(ż(�u�DndG����sؾV�yӴ&��m�,ʶd E)���hau�뷻��[��Ga6csIi�hk��d߳:��j��0����^��l]�J0��z��N�R�#Ճ3W�MU�!E&ܻ�"V�`�J��{%<����חvls��-���6��{l���E�4"�!��RO�����Y/Q�Ӫ�w{乫E�6t�5F��~zClC@�
��T|A�+I�.�W��a�h�Kx�,����X��1�m�FE|
���KK�]�N�u��p���ݹ�U�|iC噴�0φ���}�		V���غj!��%	8�oY���]L�ҭ:]S���9ޮY�NmOA�d�*t��y��%�����Ӷ74�o��(�o���/=}<�;���¾��up���I�4���� �����4����$ԝ�_9ݏ����t�����<�WE
�(����i�E���h��R��@vn.-uz]���z���p�ף����)��MH�ϒ�xU�Z@8]�[C8L�����4ί~pB�c����.S,-Pwbe�j�-_��=��O����z�1�
Lm�
?k#LL͗����Z�a+l*��Q?�Z�����+q�ߴ��b���%>�_���%�ǭ���E��N4���?�������Sg�+CQ&���t�2
J
�m
�jp���"d�7)���p'��u#�j�Ӗ���{=���(�������I3�@�veU�!׹-r��aP� ���ƣ�^ť�}���,����c܎���#G���]���=���Jh��hl�J/�|���ɠ*.����
��~��R��;�lr1ա��E�����H�>a'v����:Q��g"���aE\̦fM�+��D&���cH^����!����;�vhɦ��(�z����Dz�(�?�W
��s,��c��\�}��w����_�~���<�Y��~��~)Og��
���(�YY�;>m���K�+����Т���u��WC#�;�7��-Ϻ>�M{����)�h5�e8򝏼��:��r��YЪ|�9;�@��$�[��p�>�٠��}>ս>?#s�)��ȁ��S��
CuM�xW����)�h�>�:�����og�>����]Ӿ�Ch�i���Y	�m�����O!د�i�h��L�������[����U#D���^i������9o���J����#�(�a%�f�D�t$�Oo�_�?�qq
����q㞗����7զ��݊�y��/���L��<�%
=<?5����>}��Y�}�\�e,פ�������Ͽ��|.g�퉑��2�/��'X�72��u*�Y�k{��P�%4��"�)!��OFe_��
x�����Ң�>��&l'��T��n
j��u�<��@��c�DJ�oU�MwD1��q!&l|C
���Xh��J,郎�1�.W�7�vh����H�4:�Zv���X��;Yڥn���qxQH*�2��ؘy�x��$9��Dޘ�A�+e���Y��"
LT���7�hO�d��e^
K:T��3R���#V�mw�,m�g�M��zK{�ଶ�fNJWC!��%���:0������c�}�6:D��
��09�&�D����>
�ZK��g�����5���Hb��P�tܘ�.
nmQ���8U㜆����������$�p5w����|r��G�K�$E�[A���
d���I�a��k�/��1P������mB����b
�;,��{�Z�$�!�ah�~0�߆~ƪce��c�����4�J�R�FI��� �3��:�ÕzG/�fK�a6؝[L�����L{�1��h��E�7�y�����@��5��7v�g��/������yU-���j�֩�1>�vD�ңM�:� �'��҉M9�����߿����CWTz�Yh�ڴX���`�4��wA
�I\�E�3�;>a�k�\%X�N4|�u,��N�J��P�Ms9�n�d�H�ѡh~zp�uT�%��t�X�7�)���5X��Mw�8[:���w�]�?[�e��-�oW,I���a1�/�>���q��<BˡDL�ۡ�5����̼ܰ~�s���*j#@U��n
����a���|��.���g�M�Y��DN�\)̢�O�vӜ�f&�
و7�(W��cL�a<�dyIA�@P��C��7�W�<�zP�/�#��6���<c�����2n��^Sh���
���7�j�C�͙Q^��>�O���ƛ�z�^��<�1��o�
_u!��m2@2;��A?�i��8�8]�9��qYxz&����f���ݰ�H`�$��&nj�7|�c�<$DΞ��
0΂� ��
�Ҭb�ڑ���M\�";�D�[���&
��~k��W\1q��Ǒ�`^_�c(�W�F��Ϋ���)x�*���S�&�<5B_��'�U_Ϫ��[E!+S[��Ԃ�`�����.is�!%$���BY=�U(Kk
ڰ� �L)����7����9!sj��Ā�D���([R��!�Y]ז!t�U�rI�qm)Ѡ�j���x��Jơ�[���ʴ��p�+�d��٭t����	Z�Y�M��7��ʆL��\<[�x�%YmV-=�����ڦO��i��舭�Ԗf�1<d
o�R�t�Q��٤�ڗ��Lr#���j{�"�5k&$O;|_H@���n�ڪ���m�/������9��<���ߝxuG��.8s��DBa�T�;�R!��
��P�h�FZރ�^��z�B�2�H,�#�u��]E�
��S�{p���x|,t��V���ˇ*&�X��H\��5��sNx�O�D�xɞ��{h����ԕ��O�e>f�\�AT��+1����.��S�Do�p�?p\��^�>RI�H���
�f�S��v�گP3g[
���G
�<y�wh�	�Eb�*�E
H�y�ԑ���\�̬L�&6"�A�1��ך��t�;{�kE�K�v!rRoc��.)l����D1�d���A��|0�Pӌ��;�m޻ئQ�����q��z�}^��QK°.�'r�{��� �(j�%WR@��'�r*�ؖ�nCo�!5���R�5�O9W���g�
<�T';}��KRԁ�7��\t{�b�����\a�x�E\^����U
��V� "�����\��k�q�[���O"��!,��AۓҺ�"�����Ҿ!�L�]�J�����x��s��G"�?D��V�_H���{,��7M�O�1�|c���e�@�PԚ��{A�U����������c%M��,ʻF�_�1���b�g0�m2�I�5ʗ��,���~D�>t��Py=�~U:,O��_y�i�X��֗��C x�6���]���@���CY�M�y5�c�ܼr��-��Zbn|">�}��~	�_2.�w���Wɘ�a�ݝ/;B�`q
Ƶ��L�F�d6��|�f/���|r��FD�pI9A��/�"��i�`��B��~���rD�_��#��d�I��RH��Һt���>I(j�b�d)Ц�
���DŽ~����5��ھ������׵�Mi���Mw�J�"�����1�m5�g��7q3�䗊������2մ���
|��$�7+C�+c�:4‹=I�`�l��E��EC�E���7'[�ظP����n�Q�}s=9%�89��Z��u<����y�F�d}�������;+��4��"�V�@�dj�T˝IT�e�G������O�;+(񌋲�T�L���ep�OO8�_��{��	?`yL^-f)�鉪���gܙ�E�r�+�.���
��	yڛ��\��ˢ���>�E#�Fz#cl��rנ�m5
ڗ�lr�^/��R�#V��k�I@�pҪ��lI6=捍@�]�K�L����Ӎ�q��D�턦�b��ח+�\�����d�=�kG��0��ɁF?)^�N��ߑN�Y�Ο�U��0�����/Z$�tnLs�;(s|
^�w�&+p�����шR�y/ �<���=��jƷ�,�y�6�CG�W��O"�:�I���^���z�F���1g�ńr`輀����*�6�/w�x>�[�g�#k󝁎A��Y�������U�/+y^�0��r���0�w���)G��ۼ����&�2��|
}��o�z���W�� \�tq�mys��|K̲���*e4�����¥z�8Xb~eõ�2Y�~�H�՟��y%�����
D���C��xl������?�2�')2T��IB1WW�F����"z���J��n���,�#��	:�4\ݜ)2?�X�(�!*�`UE��4~�B�`�>d��<~�V�l���S�_XX��GD��"s���'T �mW/��/�_l�ܨ���̘�?^9�j
/�|�뒸�7᪸�La�uV�.����^�&B�����A1�N��;�@r�/fJ:�!l�@�hy�����&��sQ�H�w7X�o�)��)FV��o�A�o��h������ϰ�eA�cJ����k'�ֻf?���w|���������w| �w����[���ؿ����p�v�w|:���N`4�ν�r]�!ME���Q�B~?��Z��|�Zֵ|�8MObU���-�$=Κ+v�y��v�σ���1:�ԧ.$D��m����ܛ6]z�1iM��.BW���;�ےD�r_[��� c��Ru��Ii���̐������KK9�$��6�a�@�)zo(6��	_����Ks��f'�� b< !����ڌ���ŒA����`��x��Ɣ���3io���U~LD�.ӽ������x�ߺ�S�=vb%2B>A��E��{]�O�g�����bl�?#"�O���љ,�C��E�����i~?��V�����X�uB?���}�ӯ��@~�?;�G^����gg��a����_����U��c�՘���M���1ѿ���Z� s�O�P:��������4�8t�K�r�r��q�O/�����~u�����:�?���,��2�gǬ?�+��^�^����Wt%U]�́���9����=k0���{N��sߠ���7T9���>�s�5To�|ɶi�^�J�n+��nG\%���E@^v�:5��oO,:�����ĸ��6*���4��վ�����8޾�
��x��p�C“��Z��h,�2ܯ�G��4���v`�����|�5���>!Ȅ��GX� @_������<�U6�;����Ÿ�,�8
>��o�~jkA�@�F�Ѻ\7b*"�`h����Fz�;~�	'+��=G�h���K��� &��X|F�~����*�L
�0�^�)��T�\�\��#��!ݥ���O��1ӆ��8/3��+ī��L-`��=���j���q�p�D��Ƌ�j����]�VԶV�ciĈb��ǐ~
��ɨhU5 �k戯����2;L Ҵ?Y�þ��Ա���wE��3[9Ȯh���\���Š.�u��~��w�
��s��z�Av�p�.�4y��u~q4R)�JD~�����`��C	�-Uu�#2�m~RR=����xb���gr P�����(��Me:X.���-�c�	sA���}J���'��cM% �O�I�'v��yq������;o��+�l	�O���zݚ
.y���o})��؏��R-��Ie�Jq[�B�JBj�+��eɳ���C�Y{���Ib��fV���t���=9ڍ�o��-]�w	�;�T�KF>���ɠNg9u��]}��"���/�ȱp��%�B	sĸ�&JM�����{	*�M�00�AY:�����xg��ڟ��������)��s|�q2ev��zÞ���h���P���Cn�٧ݚI�Zy�L������|.ƽ�*�p�7�U�}x�z�-#�y'��2֢���d�8�(��5��u���_/�5�pc����=�&0��-��,RE=�U�M/�c�"�h2p�z�yad�5�N�w]�y�U@C�D��8(��t�y�i�Ό�"
.�ٹ-��;��쫦�D�RT~�D)@t�:�V9��NS�����85�����Q��V+����x�A:Sm_��VPVY�{��*�I�0��ԡ�Sl�o��?�(�'���PP�T���-���=E�����s�r����BQL7�?��%���Ӕϱ����"<
i�����n��S�G���Z&��eYV����'m��$:E^*"y�Bi�x}k�+E�ż�(5���c:�4CX;x��$�<�ȗ����w�x�%T3X���5��P�u�(Yy���|�)�ş�j��xfS��+Q��JU<�K<�Y�s�{~;Q�����?K��LU2�K]��(���\:�|��d��>�D��ar��5��l���B4�g��`4E�=�m-�r\���&��0x�q��v��^�	4/��"���_K��n���\�ױHV��R����c^�l��[����>o�+�V�=��.�3Ѿ�"�ܹ���j�>`"��� U����_���`��^Gʼ���@ ^��s����5�W/~۲Ʀ�߅���$�O���Lj(M�No��^��Tբ-��l�澗*���_�Fm��|}�_�!ɎxJ�4#�u��#֙���8�`J�<E����Dn�;B/�2�(��=e(~�;;��߸!�A���lۭK��;�f+�:@��c/����G*h�N�F�ȵ
!xj库���2�p��h)�`j���O=&��u��Ռ��Y���Y�K�_�}9� �պqn��H.om�k/��I^�fi�~��Ga���Q1vA�{�� v�5��P����i-A�RU�<���<�ZaF��0t�{5j�&�cz?�pSl��C�����{+@�y<�{j�2�D�;����oQ:.e�G��ܿ��!��H�X�ݭ���\�xnu���]x5��/�Db
ˏ�E�P����×{��%��ImV��D��ؾM�w���f��0�5����^�(�w�Q�<̪~4�kx�Y|���"AO.HN�ַu�$M�����z��k<gC��҃O/G$�@��K�E<���z�6�3U���U�k��3���㏗w�Z�Ƙ~5�m������R�Ḱo�nٌ
ɼ�9�SdTK:�S���rĩ��^��ۗ��#�(E��䐬9���=�v~�vxۅ��Bb���8�_��O{��ݟ��HV���Hq��wxu�~�����r	�^лs���j��U^����ط�5F.&i�H4�wS~B@�ި��)���U9�7E9z��P8Yަ/�8\�0����n��wM��Rʖ�k���t��P���O}�����=!�c9�&&�\��j��7)���$�c曎�w�ژ��S����ߛ{	[��.y�,��e��sF����`��q1�'�{��V�WBivzP{��MѰ7����hŤ�Nx��TI\�@س��ɯQ�N���ԥp�K�DOec��1��<
Pv�=��:9�u��Y+[|-^��	����{�EѾ����#Z��)�H����Ǝ"5�u��٦J��

�T��`�ut�~U)�kV*D\���D�Dn�"��I�3v�4�t��4)@Ґ�L��{�T�InD���r�^h��wnn�scp٫��ě=TUG��P�Uw�	����m���K5���r����Zǎ�S
�+y<ϻ-Ꙗ�ִ��T��S�v������1��Ad���C
�2��:i�r{��D�1�t���)�?������C�_@�5�`�}�w�8q���Ը��:�A��4�"�����~5��2�L>���o|�#j�+�Q�����k�#�y��k���K�O�e��y]z�z��u,b�eL��D��C|'�<�����bԽ�J>�u�݀�����ca*t;~<�(FL]狼��O�;����)�d�����K���`�����K)қ=;���N�5�>��IQkhH�<&݉�]��6;�k��H�`�җ�μ����g�S-Ǵ�հ�x4Ɓ2hݧ7@��F�e�~���-S^!�H���l/����)�T�ܛо�JqK��(��"U�ɿڽ�	ǿ�Y��m�`�-&7?�r��أ~nH՗7���B�n�H}�ϿTj����K}"��g����F�&T��Us�I#���C���د���<���;���>�G�_��n��@s��r��9�?�E���~-�����M�}cTN��;����C���9M�qo���ԫ���"�`މ�}ң�9{�|��R��(��
�MA��9kW�x��=^#�d}7y�}r$�-u�71�[�(�{R(�:;��<��]��v��Y����G���w�ޥ�Dχ9��|V�.;J���A��5C�)E�$]m�e��o3å�x^����7�C&���f�PQ
�%����^}�:�������}��.�͉���\�Rf>|�D���Un�$"9�n��91����,�r�AԒit�{�W�22�/.D����W뮑ݴ���x���0ݷ��5`�IA��
���q���#���	������-���[ʺy������E"�/%~��d&[���t^�a�¼/�(��v�G�z^��z#L�42���啭�'���������N9V���t���@���u7�����f٥%����Ur-�b�5�&k��k�7�n����z<B�K��}XJ'�*�=�%X
dgʩ֣yܯTD	����iW!
�5I3%�
 ���S3wk�Z4��_�I�J�q��t�z���
w���I�iԜ�CK��b�N�^nU�㝲X�K/m��äO��L���*.��m�WS�aۉ�z�2�X�L���2�3�w�S�E� וh�ɾ��P|����L��J,��A��A�6��j/܋B�c~�^�7:��H-A��Pfw�l�-⋳�N;vi7^���<ҏ|�2)�w�u"��ȉ�!�s|b.V̳�H;-���9k���v��y��L�]���`�m�0�}L�)���g�Q��,L��
�)�ܢ2�F�| �n�~՜�\�8�hwN-��|�U�ф���Y64��ը]3�ў�Sk[���|q��[����*���BE0'�P�����#��[�5=�	�"yVэG���~��w�.>�-�o ���)�TK��M(ӥ|�<<]2��lU�si`�L>N�2�e��1r���ť��|H���L܀=SdZڧ�3�g28T�u/]/�y��+[�@&�[�ZJDNC�3��i��]�m�'���
`�����voNjN��z�]z�`�)JJ��-�>}�4��k�y�Bӻ�q��6�M�&�q�U�|����;���8fH��mR�����=�3�tt8A\�W�Y�p�o�Y�8f>��͟q�^�m�w$
|�k��?���@Y��Pi�U�L��_�߮�b>���8 �c���V��^�y�p�o�c�,��-8k%#Pxm�b?��{a�
�~�.B��>���G&7�ֺ�-:y�$H͑GI��X�Cx ����!�@	���>O��=���
��ha���ur��p!ܣl'�mwG�+(��g0z�
(c�@�xM|�_��b6LR_����E��y�/�$��=���)�ߖ���c�2����a��j&����28r��m𾠚��R��:w1t~�Z�P$�:�$��e��Ru���A_�k�D���������U.u��y�43��¢��4ãf!���9���Νב���)V;�[m1���6�z(sX �y�d�N�g�$Ґ&���;�T�N����PZ���D%Q=���u�Ş��!A�����v��^Wֲb��cPX��Mo$��~s�nl�y��+�Է�`�0n�����^V��r�ġ9Q֡2�'��aPh�E8y����CZ+f�SF���:|MM���Z=������C�B�4�o-���q�c�^	���/�Q߸�'g_��"��C"���L�˛���~Lb�zEl������G�!w;U�’���-�|ӟ~G��7m.�+���Ƞ�
�b�1�gGT��u�Q�w���#�����&�P�;QjK療h���;�]��HEʸY�K�!��H��t$�K���n㛷�液�`�q8`���G(��Շ��ױ��FJe��|��v��&$�t�.v�w��B�[��}`nđ�L�+�2��t�컅�z�
��
+Rl���|Q�$QNbު���a��}��*��`�	w'���'��^х�Q�`�jHז�;�B���.�b��\V8}��G�N�-a�Y"��)G;wgkb�i�¬�aX1l*��vw�F�A�@�hL����u1Xk�w_���;��=�u8�����}�.�V@�`V�I��ĈWH�pG��w֣f3I�I�1 h�^c�h��螦����Ƽ�n����t��N{p�g���Roq=uǮp"=�R�UIy8j8L��Mt-:,Ź/E2�k[)�:��^8��i�Ry'O�+�6�+yW��dh1�s��9����F.Y���z+�w0�j‡6t�l���-�Ql���T֗�:��i‡YIWR��-��Ӄ��#Ypp�F��U��/N���;�A�����r������ׂ�&�:��@�/*
�GG��j�V&_��:h���yBK3�O�����PM�GB��Y�M@A�șJ��~�p����,�6���Q���f��/�˹��O�u��*���kmxl��1��|�w+��;��#�^
��3�{�x�vt2O+
�S}�d�;��"~o�a.u�˝�LX���5�ҹ)����]q�S�S���I����HT}?E�l�ݩw����÷��0�tv(�c�+�d�D��Q���d��Ȓ��Uxh�ݤ�	@�9�Wu�a�.�&�w��8��t�j[���Q�^m�^rym�"eqo�z��LA�B�6����4�`W^�Fz��ʤ��ɥcA�qŁ"�*n�н����Л坠���;X��c�G�0=P�7�JL�[�a%BC�ĉeJ���۽NB���%}�"��-������S48�6-��S�h�֓��9�D�ؓ���@�^n�t}8���kX�u�Q����x�l�����!U�����@��{�~��Ƶ���z���<{O�X�p|aSW���-�������:zN0d{�����^��N$�<"\M|�}�48BW3�[����s��d6��*,�-�}n�����ps��
Ғ�\lzB�#�"�w�D�o�it�#��o
��BtgzN@���=��<�E�c��zs���7s�w�rRc�n�P-�R���Bo?����.*��-���8�7�3����}�_�C=��k��’~k��Ojȋ���~��U���A�:�U��ǎ	z~�i��U�����M(fϕ �q��,})�������i�Q��}a���
��[��n��]��{[%O6[KR�U�є<�)~D\{�M�5�6�"*�_�;����Y��Ovd�`�v���O�f��`� ����MjLBԋ׺H�����l�n��RW�ɞS�$v:���*���T��S=���Wӂ�|�z�\�`�đڻЋ:���lj��� ��U*K�	ש+��H	�&�+
��&X;��l�XdE`�5.B���)��~�K'�](�-������Eb]������y��a�)���=��	@�fẑ��[�s���„8�沿�챽
ܮ�m[��+�=T�
�_�v=�N���%���dY��Yf��93OW������qA�b`�j��Oڭ�M��^�X��k��^V\�P)�d�M��3�jF��닔�:�-��Y[�H�<˷_�w��.Q��O��/�Q/L��=|�R,����}�b7��ӶU}�,S}�Tx����tO�]FT��j�1W�M�<kuɫz�I
p�N�[�hK6�
�Z���k�>�y�J`W�P�J�B�H��x�?T�[e�/�z��(���6zw�C�8�����b�MSB�V4C���U�}O�šá���,��jr���}-�ϭ���
�_��g��/'�M��]���R_��N��Kz�z�~�$�?a�ꮙ���j~֮��/uC��M�f9��-��m[e��x�r�Q�
����W�TU���	�>���*�H�鿜�Y�I��/�P�_	C�Z�
ͷ��Tn��eC�`�΂q�{���
��mo�[԰=�'*����Ñ������}m�AJy��މV�6<���u��i�5dQ��[��w�a���q����U~��Ȗ �On��AqctGBUZB���qIFD� z�^X���w�<���o����2tl�M����}�ګ+|�D�
��;���t���A�F�bnW���<�p�y�
�ֹ���$�Nt�<bE��Ő��R���a'0n����6s�:$	K��ƟSq$��t�;M^4�l���\���.�\���s��K�YJ�FX¹�%�
z��:I�S��_�#�G�Gwl��wr�B�<텟rgi_x��̽�
C
@���G����*�7�F�s���X��N4���n5������|��~Q���V{���%zԆ�bרyk��Dp�rm�@�������#Sr��㦕z@ӥwp::�*�����䍕i��Ҵ^�1`�݇*&��uwK
����>�I�yM�.��.,��s��G��6�Я׃�׻�'��X�o��ʲ��,����T�@�|e�^��(=�����|�+�y���^`y�O��	5��{}�y|�l�>E����=��L�y�q�������d��t��g�����Mgΐurւ���3vL5UXyܷqB*W���^���vˁ<��5����,f�iW�����3x5�S�I|U��)��x�6o��Z�U@���a�}���@�z&!�M~���?�?�
G�Z���DZZ�ӆ����(Q3���kջ�Gןb�e��c�ѳ\��Q[��C�ƞ��}�t�!�h�y-���[\G�d�/�s�&�܋R)�;:�M@�"������rJqB�aA9��k�y�&�	r9���^�+��PL�)D�~"��r{P������~�ܸ?+���b��ģ���� ٰ��Ƴ���q��3۸�>�R�?r����}x��҉V�����O�~���c�+��h�X�k`r�s�/!��#P^!P
r��^f鉶]Z�.^�q�p�1_���/����-{W*�3>�J�CK��:�S�1pծ��yF���z�ͽ�=2��#>���P���o'�xn3�;����{�d,����N���hA3��b�S�PM/,��ø]����(<4
�#O�fN��z��?�wD?s	��Dž�t:J���Ҝ���/r���{��fi=y�y��N��A=tZ%P�zZO��Cm�*m�z*mn����A3��?�j5�tǀ�E��ҋYLc=uT��V@��>͋ގ��X�30�::H�wi]��vn*�`jl���}�{<n��+
y��f@
������\����u�Au�S
���7�-Y��.L�2zyG�Fz[싪[tػ�\�!��i[c����F����6��$,���c�8�rW{�1ggî,�!–H�B�s}u����ÆY�2T�:XI`e�U,�Ʊ�"�7�[�r���M��B�8��|����\�qb�_8wk�)�JT�R�Vwl��е�i�ZJ�z���R`Ywe/]�ò����>\Y֑�=0a��D���H�LRT@��q���&�?�Y�K`�h��ꯀ���v>���a�x�������o���B�Y����c�f�tt�'��j#�@?�*x�79��Ņ�`)	���'�	V��z��.��e(�m�����,��;��Nn����2�$�TC����
�����mbX�D�^��$�b��.��{�G����i�%q����@#�0���b?~����?U%7a�x!�-(W��\T�a�btz�(�|x�T,��ݻ{��1��^^]0;�A��M�j5�QYӫy��HZY�}��E^�$^!-A_�+M���0�C�1����]�!�M�>���ю�_̓����gs����a��U���F��b_���O/{��#��| 7�*st�('_�:��u��Y�'�l	�#?�7����<����9�D����P��3~>�{���"~F#���K�Ά�o��ȯ�i�SD%W�U�����a7-�y)�������4�nK�/��L_^w�S�F��
̯h��¿J9�T@r������í�3}�U
�V���y�b�SKZO^〛ec�q�����ߓ��;�R]m/ĵ5ơ���v�n�F��lcv9�_9TO���)�(8��f��ܖ�Aw^s���j�F,"9�[�5�W�L:ܶ�N-������7�x��	�I�6�zN��C��+�x�>�Ӓ<Ե��\����be��Μ��y��dySju,;��KU�ܨ��g$�_t�ŷі��̜?�B��䘹ZPm�[	���.�\�
�"h�`�좨�4x&o�n���>�W���u

ㄆhO��^9��6:�!�cG�T��<&B&4�ܛr�����~��%.�Mo�hCɡ����,���a6g�%�4\me�7Q�0:�w3[����D����
i�J�	s�|_!��!�ښ�`t~!w5�Zr�
�R*U��+�����(6w��qE��sQ|€tn�.fa����P��Q67�	A���H����P�MK:�T�s�x	1���o<H�K�E��Ku3 ����
}һ���CD[�ό��/�S�4N~C)P������V6��J=�<�o`'����.�ݚV>�V�^#�����8�K��4�|�ȭ�2�2�1���z�]�ن��5�&�
7Z&ݕ����1;̉8�\��i\��ɣ�Hn�,3@�lVk5x����P�0�[�|���-tAQm��a����xS[T��^�;Q���8�[�9gC�P�=20��ץ��i��t8��h/c�@�w��<."J��?3�`/����g������^�[�|�M.�(��Vur���F�MG��+�	�Ȯ����4��_O��t6Ҟ��j�fV_\o\0�Fp��`�7B������H����.��{߀��4��*��� ��d�p�'gM]�x_�g�����o�\�^�"��P���I�NX��m5f�V�:�O>fkQj�T|��7�Yu�6�Wʐ�T��u�����Ǯ�,ZJ����˅��/�x�X
�>�r��x��X�v��cn�+o��ojܓS5��X�N_�4����2��!}G��*{\��y��l�[PЛ��#DE:���8O*'�i{F���E�I�;��9�ݞ�~p��!JךX�
����B��H�7N���+�5�GQ��%���ߏ���?[�b}�S�_է�5l�������û��f��a?\4�C�W��|�|�b��R�[ocM�(��ST�c� @Mx�=z�'�y�K*SJI�4U�}�QG>~��"bF��9e�t_��I���C�)~S�v߆���sy�s���	!v�}�I�۩��~�09�>8��
v�"��&��]���7�w��G��E���1_G?�ؒ����������Y�2F�G���?���[�rM�R����>�y}>.���<!�y}����?��|?23e�d,h	��d7y����Z~��w:v�-��+BH�0�Mn����,�m�_�Z�2�=�B�i�
A�'sQ�M�z���y��})xx#��F�r��d��s����^rY�V��n��=?���Z��)���W:#7w��Y��-g�Oʿu1�>����|5}���?���CGnD��)��P���	�s��?L�d��z����#�Т�!ˌr�W��Mײ{k��mz��}�]��y7}s�0b�el_[��s�"��i��.<1V�pM�ZW�-VVܻlx^�65���8ܲ��%��ģ�B|`W��੍�b�)l��G��;�N�_�o�D�#N���n�0U�/b�}
�ʂ7rG��ut�U�.m��ݰ�jJ�y�y4#ڴ�U!�R��Y��<?xQy�G��_�) ��U�X�"�c@��#�(�J5Ҿ(�|j4h�˔�k@��Ӥ`D�UL�6��!�G
��ĭQ����_���+ؔÙ@�3M9��t��?�`A>3'�Kת�+sCR/���Z����|3�ozu~H�E�٠�ή�yvz���h�c���O�����X�#�<�fr�8,!SD�1/oX�a!�V}Q�׵1�`�b3
���d�0N���Y�:^��lcl��(>Ba��;%_ٖ=A�o�Ӑ�����T��'�)l0�M����㦹�1�.#6rꍗhi}A��9�(����C}���jg�#�*����r�x
�;���ٔ�%�#\��г
��s�=�D�b3���sY��>�^V���f��8}FR4R���[��z%�C���7����Y Ě�;��&�oׄ�I%��E���k�|S�3u��x�!3��*������Xy��
Q@-.8$��X��Vb�틐<���:��+�X�/5�S9=r<s�Y��di/7�$�d�h��9u1&ZT���[��FE�V$0�5��6��σ̏j�w4��UoM��o��;2��s�>��G`�{!��R#�.v���9�0��F����&�r�/�&�(��a��'��\j�e�%论P='�-�	u����]w5Z&���q��_&����2�����*�ߐQ�.������Fy�����p�γe�w��+O�.u6?�:�d2�ܾ� \�k����
�?�@�J�^P9nj�G|Wװ3��
�_A:?R/��a�����X��M���og��F&0���h�y՟������e�'���}�j;+
RF�̡�a��#�FCI�ܜz,u&�Zmh��v!��g�W���=΢`=�UK�k��&|u�e}gۄ�,�'ca�kA��H���sf`��;$�)T�Ҥ��,����G޸i���E��0}�u�IϪ��b�#Y0w�`�ܘ�y�mjO�dCTd���֛�����ͫ��F��OWƤ��ő'Q�b�QQ}•,91Ha�k�0#�^_��Z�BF&���SqY.�N�>��c!��Q,���c㜥
�@�}�{��?��[�M+f�O�M\��gͧ�k0�*���a�pv�����Fk~�x��b�o4��]Kn���%S���B�Y2�w�OW
��Z)-y��Q��Մ�G�<܄d�̵P�q�V�@�����Eވ����r��1D��~�� ڌ|\�s�k9���ÔVb�S��AGfpF�T��Y��5 �6�d�4�A��d�D���n��5d��<đ�v��Ɩ�����77=V�*��Y�
���$���s[pv�
�rq+"�o���G�cx��`V"ok�pҚOW|:>���z�,VL���:���f5�v�ʾ�s���E�O�ћ��˄�
������%�St���W�
՟k)�`��>���&F?��{�Yf9?��)h�+]Ue��m�� 2-@�����+}>��T�X��k����'sǭ/�8+�w1}^V��B!��yQ�!��XT"7��F1_«�=_\N3\ZT���>����h섯c�%���e�t�6ܙ񝛾s��I��܉8�i����Մ5�nH`�NH�n�R�>�ԬHk�-��3MR8l�`=�dڑ0Ӧ�0�*<$��a�z�|�D�*�ő�C
d'
n��+]���k��_q��Wd$ߗ?�ds8:�ސ����:��\$��d$� ���,*���)&��jлz�|6.�~)��U�pC��!�';*}w@�tB����x�����iw��ں;�c7�*Uצta�	%�G�S��'��@*A�{ 7<˟�e�#�s4[����$'@V7Zshrc�_!I4D�-
&���Zb�R�h�^����6lj"��2
��ٻ�tO�s����'ĕ
9�ɗ��{/�—�TD�X��(Ԫl�P=�!g�)��g
��_80�S���+h���8�޽�)Ż0>W�_Nz�{��ڣ:��e�-����O=�\|�\�f�X{��u��c�m�3��Y�<�!�+b�ӓ�L��_͵�:+�0L�����"l�W]\'��y��G%��5t�=ϓ5���2�%�zq�
��~Id�݉�Ћ'c�B^��x�X�ڲ�=I�%�挾dʢ�o��&�3q��C�c�QX�;㠋:��
$���Y��-����e�KkFU_�O���ҴRY�%ƻ��%�SB	�.o�y�=(*I�(G��D��yc6��,w�(
I��pu�{u���m���β�o�
��0�}��R�ЃǛ[��!�����ϧ��7��8���'�z�3���q�Xl��0zJa�f-W���Η�Ʀ�5�X�3P�M�
P���������9�s}�Q�R?e�?S?��S��+t��eꇋ����������R������v�<
)Ɖ�����^fS�v`����"FO�[�<���+�Ci�����1�^T�x��g��;vt�,���i#���� Ϙ�F#��>b(w��
�'�d>
����оP�7�䈒!�����r�3~>k�qS����D��N�5�^'*�j,�`��+6�˒�
~����j7/l�w
���9�dy��CKC����߬�����?sF}Z�oI��8�(�� �J���UQWLc�o�h3m���
o��m��
��o��*� \85��_Z�H��۾�(t�i�pWN�k���Zů�g��{�V��V�fS�5s�S!�����Ղ|�+K��]5m{���Dž���{�����APAa[z��˫T�E��'�3w�A�:u<�cBc��+!��#7���Mhra��6��(&��>�}qv�/˕A��k�&[�dk��q8���l�R��ԟ��b%�پ�ql�e��f��~>�IP&]�s�"H
4�pA40#�\�p�4%��*���ޟ���'-]��yi�\}���M>�
v��
�Σ���.* �LJ���4Dhsn�E"��|�%ǟaVQ
�~}#k�Ʉ~3�'�r4�Q}�ą�{���#u�r��=ґP·7�Z[|e�hʔ�!O�ҋ��0��#�C�P%a8�%KO����nw<��Z�p�k�cM�����Vz�\r�{������۲�1@/{�w�^sG��tzAY
Jo�z�
�u��P�
��W��<��:�!���U8��!'�C�tP@��a3pb�����ր6���+oG� a<gg~Z�W��|�N��2�,$S��כ����v�P�+3����锁�����-�_+��[�ߝ+%X�v��&��71�̢k̅���ﳘ�
G��� �ĩ���Ef��#��ڿr�
�9C�4P!�-����3�'S�W��Dz��ߑ	��:Vx�;?P�V�W1i�Cɤ!mslc��ºV��/��+��o
�I���x�s�1�J�����/$	\����5�;�C@!���dn$LP\�D��u �B@�]��GnQ�3)b1�"r���z��=��ӔFM���P\��Rq}͇[]���9����*;4W����xǭ��TU!YJ�s8ʕ�dDK-tdQ�V0��fN���w����OARi�㘥/�ks��qʧ�����^�D��L�r _8����������ʠ�!�q���)�_���՗7$��M�t) y�Rt���֞W��co����4��ͪ7�S�闶@���� ��;ևb4"%��k}�#��@U:�G�Sb��u,��jh5M��*�e�ؙ�fw�z�A�cnrc���6���\WY�����a���u7�<@��`*pq5�2��3�f7T�
�Lŷ��(�߆�/�[�f_���/�O��D(���B��_"7y�_m�~U���G�}7�ۧ�C��w�?�	�SD
!l��pӇ݂�]�=�X-R���~ߠK��K�����o�P�=���;��?L?0���he�lTl1�F�q�ӏ�P��M�XOY�3dm5!xg��a���)�7>Q�m�9V\�����~���M۲R!z�U��A����z.e	��D�i�N��Oy^T���ҫ{�d�.�g��/�����T���ŋ )KU׵����7Ɔ��f�뉗Mă� ,�'v�&�)��M����́&vɎ�%2�*�/����I�|16z+�W��xx*���a�v�"L�6I�:Xk�W�.�9�2�Q�<�5�L�f�����^�����g]��MB�+��JK-�A;�hl�9��U�&�z�qkj:�T�L�(�Bc�s>���J;���u�:���=�v��od�A�A88�~0�>�ʶ��v��@7�'��G�q�kQZ���M`a�`�޷L�R]�.%�tȊ��S�o`�nׯ^}7�b��aگ�k]Z��n�d�N\�*}��j���{�CBz~.�gBZ���<ea�/�L,f��w���k�"�-��Ug��2�E�>��w��>\�P�z�5�;���ŚwLrn{�"�L����+^���ػ��5�gh�{|a��Ȗb<�_����϶���#m�v7��ı����#�s-[\{o�ݹ�^vq어K���R؛f$w��� f�(���aW?ϖ����o�iuث��L,�;G=	J�oRD�<�t˯��G�{�ܑ~+d�8w����^���<�s�ũ@�1�5�eF�C��E���ϯ&�bFj5����q���(�Q��a�L�ý7B+�Ew�M����� ������
z�+������k3��:�LFz���v�.�S�&�8п��,k�C�q�	�����C/��b�Q�R�<H��>=��i0��6��2��m8���@ t���qh���4!#�XOk�\�sɲ��`�ÿ�Z�y���ز���u9A��u˿+f��YM�F�?�2��=����{
{$]w�*�jd'�ô�<T����	\���%7��1<�磢��T@F?�+FHDY���0bb�7����Xc�����C4�-�M$<i0p��.$VţM�|[PK+�)�X��m@Lj�ES�pj5���;E] �}�-uZ�%>��4����������_��ĤoS
�r��z�F�&��?���ї	|Ϥ�W����l�?��Ex~(t�u~`����~M���Z���_JB������|���#�?��������G�U@Ec-�����*�FK��R9��kV"�vEXϻ9(��NSz��d�*�!��~Z���$Bk���8e-�?��8I�/3��Lա�-ሟ�&{��F����4r%Fl�)�dkM~�%�͍�LB(�K�@ţ�Fia����5J�f]�bު0x�oœ�J���x��&ޜp�\k��<�'ѱ��D�t$�o���U�1���Uw_��7�i��!���P|XD�$�ϓ`�_�jV��ܪ!�V��/��`�
���mg��H#�F���(��2B����C.�Z��Uy��QZD�t�煘�D��q+��n��#�?C������|�#p��7�`�k,��"��gY�@�5cx��t)x�Ϧ�TZa�R>T�M
�둮����̵��%�s�f%��"LU����s�N4Yq'B�7M����e4�G2�AGThfD��ɗp$�y��I��p��#~��?N�='J��FF��+@�		���Q�xY��<֝�pԔU��ſ��᧊�f+����w*�2�:/�c񏥇�Sz���;:Dhl� ��g�c�
��/jh��K��y��&�bE�_sm�AH�v-�y��h���WA*�kr�MTk�!�6����pC�)�����;���M�����_J���t�n"��:��-�
��wM�u�몢]�6��a3�cָe[�I8��H��Z-x�5뙮P|��+i�'!�d�����.Q�H�d8��Ԣ	��{���+�u�
-V��_�?�^�6Þ���< �O:��X:<�0���<��f<��F�S2��E�ϒ��M�e��ŏ��N�Y��O�/���{���.�r�!�nIıH<�Kq�P�WGx|m�g��'�wi���%�ٴ�0:kwL�/��i3G_���O|Ygl����O	N	ܫ=���u9t�T�#Ha�}:��-U<�`Nm���a�C��X��q���GZk���C���V
S�;�N{p�9E�V3��%"X�u_W۫�D5ք[rvF
��8�b���'���=�z�$JXU�A���۴DX��~B�/��}V��^�݉ �t��$�0a���]�;	��h�W>Ĵt2����8��re�s�S���T&��q�`6]:}O�^Y$l�a��jC�aTI�n��Ugp?�g���t1�ǔz�A�F����r֜��.ڳЕ��h/�0��Nή�XV@��[g=���j�z�$����l�q�;D��:;�ʯT0�q�/ȹ��x|]���We�>�D�r��<��90�_�`�ѢC�R!k���=�-y�j�b���8@�C�ա�.|��n���;Ψ;Mo5G^c��;|z$b/�|+n6;�Ӈ	"��*Y�ɺ��{	6���N���!��z����z8������v��d�s�����I\{��Y��7v뻼����0�F��8a��3��>�-�a���>2V�0����#�b,�Yg�j��������je2�E���-Y^v0w���F�\*����'AL��)���vw4�Ыf�AsO�V�Ŝ#�;���bn�"ey����4v��+F��m��^�'Ϲ�]�|�#����G�sC�]µzA�?�e��u�j3�P�{�U#���B{�������b_i1�u%��zj���i�¦5ә$�2i_i�y$//��..+���;ka���S.��e���8��Z�ߦ��e�!�')������>�*{�B��g��s�p}r}��ޮq�.��D��ՙ�f�HZ���*1�ʎ�.�ޫ�t_dq6���0Y���ש�Bd��v��,
�Y���߁���wN��*��1)�,�sh�d��?9�Ԭ�1w�"p��V4�58b�g`�p��v�#m�?u��?��E�b�Q]�f �M$��OM:j��������
X縙JU�Me�]���kӋ���w�r�⻛�Zn�0o��i�N�:5n%)>���(gr*��YJ~��e��R懤�Y�/��L�ٮ1N���o��q4�~ K��L������������;	6�6%��6�wf�RI�C�f8M$�~���Ve�&M��7�<ﺢ��	@��ý��1��Z�,!�|�W�n%'%�'���.��!�&�� P�Xxp�����5�Lc`�M��2I���9{�0�N�Ǘ���T*�[/9�̓�|�8����zF�����AX`��0�ǘ��l�k�AgR
|:�kQFCQA&����{
+��/��"�D�tU�ؒ�fbڤщ���NO'�"�oB?�v/J^?ѱ���$L�!�Z'�%�+�	vE����"�/����g�?K9��/e��SU�I%tI���߆TN�\�r�g�I�6����Zd���o��_��"�ɾ���-��׼�/��Ne@�[�|x�W������f���f��vz�kr�0?Gq�	�|�u����k~�!,��#U�i��!�
��q髯U3�_�,ռ�� �L�f����K�t��uR��-�3؄^��S�6e�Z䥝����i�g0.�rK<�+0>��@#�F�%Vd�E(�u�fȨ�"���Ɣ�W�7V1����Ƶ���ۃ��h6��K�Pe4q�m@�7��l}Ц��"Nqo`<GWm�s�r�������o+|(�d#�Z=��th��@�Ed�g w{>N�.u��3�P�os����
g��T�0�pвn�H��� u������_+@¨�b�we��ܲ౧_��2SQ!�������.rD͹���vX���Y�(�摀��g4ܠFgπ�$�ȓ��%P ����Qƾ�#��iS��86
�f�o;�ET<�D���()U���$]��,�3w�6���\�����\�����#��������z��9��WŨ���d�	�"��ZX{��2Po��c�Bs��P;��0&aJ�]=D�Ep�w�{�K� ų�	�/����������J����?8���&
��q�ע2ۧ���������^��U�Y9ƶ�X�3�C���aT(Y��%�o��s���p�y���5�:Ն�A{�E�ĢË��k8Z�n�K/Z}[���Uo&��6�7<������{�Q�;7W�d�}i�/s	��ݺ�ܜyP�c��a![��<���8�@9��&�3fr�,49�aP�,ԯ�{�ӣD06dC,��%�_8P�m���;���Pܬ3�Sғ�M�}�2sw�e�g)��u�w�tR]c6BWO�uﯰ4'�eQ�VDW���鍢4��<��ӽ��x��4�m{px�0���ߚ�u����}1��ۃ���FɅU������RY]��(�0��4h��c�9�W��V�jq� �A���nG�,� ���@�/����]/r|��BD��	�jjsp��Ŷ�ݦ�����?�n�*���$�����6ےsqM�s옇��e�.k��>�@��r�C�g	�C5}����4#�Η4��7��S1cg�@�4(�*�cc��t�}���F�n.�l�t,��䱛�Uw�`�=�^��$>3���LQ��ܐ�1>W�r��V�*���՞�*�����x,��b�'4����G����~b�W`���VB��>_���қ��9d�򬏙�^"68�vZԓ桾ݚmߘ�~g�s5���2��x�[O�˴J���x)rv VG����4*
���L�A�7\��@�U�5=�w�Q��z�}��`m�@!{%��L��J����2��Ѡẖ�A��M�Ə�+O�B8�{g��X�wLE!��[�f����Ѯ�����b�NŠ����;!��CyM �ؑ9��5�l���T�7��w��������%˘���l�K�jя����:ի�;����S�Q�96�$�r
0���"�O��
+�����겟�rxF~f-�+�Чx[}G��+�� ��:V|F~�Q|����v詷�u
�s��=�Y�GW�y�M�	P�?֦��i6��у��*�|����h���N���l�ϩ�;���:�����O�?"�|�Y������4�w32�ͤ]_�H��}e���Į�_nʪMpw����͠��ӷ�(�_�é�k��[��*������<���q�� hS�����yj.�ž���y���?
�>��eL��jk8=����Ww+`ߊ�"� fO�{*���[�1^!/�֙���9v!�@�Dvd�;^{���p���b�*�JV��\�������	���-����>�5��j���X.[a��\��8[��L��L���8w���$�	���(����L����9߰e��:�����f]���k$�婑�E;���Tw�ܴ���dh{έM/ݮ�!\�(�wg�-1���{�
M2=�ө4N-o=h�#��u�T�^�E�z��Þ�a-�;%��)H��h��"���*5w12�%>9ʛ�����P2���h))3�
!�ɡ%!^݂��;Y����4�P)F2	�3�D��ύy���H���ߩ�����kY��V���!�C`�t��5D��!>����׍g�i��F�ގ��芫|u��T�!�G�3��g;A��DV�.����ˈ���N➃�^��b/-�$Hy>��W��O#N���S�P��(�J��=I��	�=������9��@�)*Z_6�#�fg8e�v�5�w]���+�s68�}7Ҏ��:y{��4�?D�[�ȇ. �wƲ���˚t�;P=^3�S2���
��$��Uㆸ4B����^$CΕ'��X7��3�J@�f_L���<=)}��ᜤ�G,��y`Kκ�j�=��^�c)��~�X?���ӡ:ĕ��,�K�
I�W��U�QS/�JW��2/�T5��z��ECCm�b��\e��{j`��΅�
����Ŋ��<�A�]��º�
�X�k�&�뼨u��a���("�@�s�`$�g�ױ1� :FW�r8������G����T������8"��Uf���Oz�l
ܖbii�}��'|nf�/B�؎��y-���:8r��at�,�
��U�9
�b�J�&�8�[
X۫�ɽ�'e���M�.���";�T�J���f~�ͥͨ��]�¼�1�����3׼g�P}��6L�;0"�A�ޠ`F]�-��Y�eonD�DG]DB�T���R�k��}[T����sN����P��Kc�.I����䀑	X���TC��|�_'�g��Xk6�|��O�
_=�a�r��yoΔs�� m�f�J޿��l��S��7��O�:,�^��]��[$k�ѿI5~9��or��~Sy_�v���ݓ��ݓ��Q�^�f$�1ro��-R��^��1jk��q�u�?�6�C��!n&����Bݡv�X4�@ʃ�JW���(I%�nsCAP�2�ͯo�����w�t6���6?0�o��x�R�oel��Ψ�j<�<�n�ϭư��<����R�v-�E��kW��n����9	�YR¯���`�'nД�9�����+ͪ��0z+�>֮[�9��.����_Q4�
r�A(zm��I��0X��Ys�J3�߷��n�_���ҁ��J|�Pˌx�ȕXЩB���:�j������gj������i��վ����6��t��
���U�?���[�d,�6�ݵ��G��������G�U_��4�<������|دͯ%Ci��A�#�@�;3�Z&:����U���Ȉ��v$�"��2%f��S4�p� �S׈���0��!����$͹�N�n�@�a��`ȝ����.W:��1��"�ʰ�JE���b���*�B_��ql�p�P;��\l�9u�]���d1��D�N��Zp]Q�VEa�2�������^M�e�wq��hV:x�f���S�w�pO����DO�E5�6�_N��NϼZ�����]$t*�>-`Hy�7��ڡ|g�q�h��p%����;�}�ˮ�7�D�0�Pk�a��|���km9�����"rm��m�@�U%:wp7ʚ&�H3��$� q�f�'tIW���=f�0R�$�}��N]�BG��2Я"�<_��Nd~��x/`�i��54P�<+�9��s��/��2Y3�/k��V��@L۰����Q?^��5 ��~�΂�h��à��,�	X�СO�'ްU���Z�7���<�fݞ8��mW�ll��K��
X	��@x��	Xk�eU����������Δ�� d@���J���"߼F\{�:����N�:�Ry����p_��T���%1H�lWWg�bNl�S�F@,d���p��p��`a���EP��ڟ��E�w���	�3xh���KUr���'d���<�U�L93�3 �JgX5�V��r-4A����q��|��4p0����/������?hq>��ygK$�.�I���\��۫����왃B�,_���"���E�a�d:I�mɊbꉮ�����Q���;�ɫ�Bn�|@ĭC�����,�J;�N��]��g�"v]E��D���rQjh˼�Բ���^	l����,Χ���|���b띶��1=O/ӭ�]�ٸ"��D�m�{��#d��#G���
�_�E��A�P�j��阛�})h�.ڞ���4�ظ�E;��Z<�n	�yNl��A�r{zs�-��y�
;~��{>.+:&���4ž&5�v(,�)U"��psb���Y��Y�!o/���!�;%j�>LW�A���]��/�t�w��9�g�O���f�]�&���8�M�Y^Щ�.WR�e�0����e�OG��k��\�]�!Uh�Z�B�1�L�{�dcέ���ö�S�Qe�}�����c��j�JPzqSO�
�����a�cW�]����6��Mܪ�� ���e���T��?7Ժ�2�I�/���B�	�W�wywq"�6~�u�UuO�U�z�$����݂b!������4�G\�'�78X>�̟�Җ�����v��w.�N�T9�I�4�[��I�
��6y�!տ�b
�@\��{�n���h�j�".vM.e����s'u/R�`��%8��a��.݊�uR>p��P@͊\D�!%�P��Sw�{��KtƷ@	��71��Am��_i��awb���܇0���)PT<&%��{;OklS^e#�dF�iʲDut(m��^Z
G&;RӺ�ޛ(��#���G�����g�9#a��]H|.���"��}y�xZ��5�{`�6<�g.&"��Xد:�zL���p��5$����1|��|���7�J�z�m�v�¢BhC&�J�L�*��5L���j����vS�'x}/�<��fG��R~"\[�c!�q}���H��3vˮ���B����	���A�P	����\D��:Uȝn�h���
xv�WKb�	Ʋ9AC��Pg��*�L��Sn�D�'+��.�[��N>t��\�,�n�Q��ս�r��5����- \z_
g=}y~*Q���T��z<�[8h<�"%����P��<��_ν�!�ζ�Ki�+��� ��յD����?�W
�֕o�NJ�#������,S��e�Nm��Ƹs��<��뙜�>�7��[�w� Z�a<|�l�?�iv����xeP���|	�+���;I?fi��x�txA������u���yMG;�N�	����>W�����f�߈��v3��1�V�����-we8��c��N�K�[T�?��-����_�&Moc�S=\�3᜶��@v`'�.e���<
��
�,��^�[�"ȸR��p�����$��o�v)�B�&1�3��	xp��E�o������t}�:��	�YqIp�E�;
�;����;r�V �D�=G�©
�pqZ�t��j��4	�#g��S˔ō]��A��V�&]֣?�+\��ŷ��4IP�O t���]ֆ��<�0��C���E	��
�0���X]�h�xĽm�����a~Z��� �i+�;t#NEY�*���̵*��UvM#��;A�����/O�18�_�l}�e�ᤃ~)/�������ob醖6��4�EZ��`(�p�+C����Z��H���kq���o������I��߁�|r�&��gҪ��4Kr7�9����;3�S�V(�kȍ-��,�s�h*�AZΗC�o�{�GS��Oi@�5��T�9��ۗ��]To��|
z~�8�&�7hh*�a��3 ����%�H+��&��*��Yt*�����|�E�(�B|�N�|ȸ�U�P�?���–!��vs�f�U@�;�nm�\���&��汽���P������1�ߦ��Va���u}���u��lf�Aj�����39�aWHV�J�	��Z���°�z��x���g��R��lY�hnY�چ"(��@ۈF�_���� �h0*�a�k�8��C�Ux���E����FE^5���[+�r\%a�p���*���8�]7�yѨ�����*^&Zx�eF��h�z�4��5z��;��x`?;fҶ�j��jb��ƵK��,ID�	D�Q�]���U����~z#f�������zLbqL�5h%�sR�N��t{#�^�:��/�^)$�"�]�:}j�1k�@��,��ݍ5���J/�6��r��O��U�U�-0:���Ӛ��y��6�-��Zp����
���(��C���/�z�xcc�1�0.L��AU%�w%��&N�!�Q@�Iˤ<�̥4j}����:��Ԍ���9G�!J՛YD�����~��\7�B롚�S�
Z�̖�K���_����(H�e�=��������	��[O��/�i %k�m��n%,b��]�k��C��+�Z2��^}l�7�!Dh5D�����2���R�{�b�8T\uՍp��=GB�Q\��u�A՞�u�0g�ll�j!���u�ad5v]Om�!<e���m�VW�w���z��Cx�)ǭ
����;
/+t�<cy�Y�r�O��,�]<eq�p����/=Uջ$�xP��m�����]���91�
Â=S�k�dx��3gA~�4�������⓮p�?�!��]�G�4�X�n1{#	�К+�F!*�O�9t�N
��4��$r��P��4��[WQ�C^��.=�x���W���t�٣Ӳ�μ��I#��ߜ�X����f��t*�+�Y�}�$'�]��}j�%$��&oQ��g��:�~U��������خg�����^����L�hl��~C��y����+��\�ͭ���ޞ;�.o�k[�&z,���
�H�	rFe����:��=i{�L1�!�U]�����}���������ڜ����A����SP@��8��qޭ���@����\�L��?Ȝ?�����؟Dx>g����|.n���<�o��j����F��B��\���c�pQ�iVT��=io
@0��ԏHl��B_I���7�1E&@�2w�RoP�Mg���]X6��R0Az�S3�`(g�b^_$鉄��s��y�_�Q��՚�T�k�˽�0��_L8rܟ�yB�J!�l\����mȉ�44�H��Rm2��fE�����ܠm���O��K[�f���ֳ�Q,�0q56�
7�(�n� ��J�_y���$,�oQ9�a�5�m�2�c�F�����g��-�6z�}�M�~���������ϙ/��Us�Ǫ�ߠ�����^���u�c��K����[Xs��N`��{'>^\��=?�>���p�@�R���f5������G���Y���zE|��]S؟y�][q�
0~�������"_��<b(|���j�����G@�H9�M���`*���v�k�b�p�߱Zhp����@4��8FƆ��x�$�`�|�<7[g��aґ�_�f�W�D8�W:�CI�	w|XH�H�|���nVKm�91M��2�����J?�X����qa��ۃ�^�
�PE��VPu��u�^o03#���4^�����&qސR%��cG
���vJZv�v���.B
��`9'�#�a1�+���8�LP�O���@B�=nPr�[� ���^
��y����jh<j��[}Z���Z7��8}°��k��&�±��(��^j�X�Ya;��d�%��C���j�Ky\�<��S��C���9��H�HR��>ZaR	����R�w�XEL<���)s���%$^���E��<��\D���ݎ��˯��/�Ֆl���>��YY�W��#��E���r�S��v��	8�[�p֒m+�Pk��먁��H��˥�r�S8�z6�MDȹ�x��y5޺dW���j!J�}j��s�U���\Da[�������%�ٜ�2�&L��
�̼���W-����-�Q���~�)R��7*r�)Bx�pڊ��H�I�ӡ�m�_T&���'@O0則|��,a���nI�����)�T@�kp'O���bz��Ò�@),����{q�ldCp��5�9��������z\�O��	 �H� A'���}���>��ya`A�����	�!~�E��S�8��C:��x��t��ҳ��n,��@�����j��'�h^�%-�Q��V���ܦ�:�O��(�l��"\�4'�/����C|o轘�0���wʮI�Q�מ����WA���I�G���#\՟D�	FB�ܙ�"��������SJ��>	�[?ٿ������`Dws�.�2c��0L1�:�;vZ?��3W{�!RJH^�"m�
}����2���\�IU�w���2�cRUu�HD�a��=�T��z�}i�#=���\H�so�3	ԥk�j1�s_��x?&NA
�v�cG<��Q�Y����C�
h�Z�jh��U�,�x�i�$�	3x�R:&)_/�,\yΉDz�f�{}`�@TZ�&�Ǽ{-�K���!:(�m�w�CC��z(�!�,�5��\ k�{��\����4f�r�f��m7�U�(Б�4j�u��?]��Q���#�\��ʹ��8��U��`���5<C[����w�����;#�I��d����V
3ZKc��s)�B	hU���#�WFLW�t�lK"�_���;��~�)��=�_�1٠��}��p�v���e��	�=i��6�?��)�y�Q�/S8 �݀Mo3P�ǀ���e� [dfC
޹��u���	�K���hT�_�:9����
�����x��|����o��
`����3�tE��6$J���`B���~G�������zm��s\|�Բ�UJ�ϩ�ovI�����|��;�z7N:ں�s9��]��n���(:���1��C����ʒn�1k��3�`2�`�l�1�g6���[�S׹<*=;�7�����Wy���䧯��ގ84��E�<|ia�%���rҹ��.�؀!�x��A��8%3�zܲ3�q�wh">sE�T�&���xB�U��ĭx��X�-!8������	��^-��D[�gC����c��̳��ob\ԝ���3�t�������q~�����H؊p�nK��.M��^���['-q�ک�õ����v��z�{���_��k���,u����/��a�]Q�/�ʟ��?��3����9�^H�	
����&��'�ך��\���'���DG��EA��KZm}�6%�\.�^)�mxtE�d-���\H�E˫��<j�8UG�
�-�����[��I�}���]uR�.�:��K��ó��օu+N�5m#7:���3�C��*������Io�a��tI��it2��g�KxQ���v$�dt6ķZ��_�]Yi�&��Ozi����zC�XI�����:��VnT"6�J�p��0aG��z=�H�Z�]��P_\1
tT�"�9�!,�5�����j���{I�Vwo���އ���{j\�i_�Ȧl��\
�cn�t8&��•h�j"r���.�k�rJϚ�े�޴:��R�/n]�+nj��p��R9�B�Ü�||؍��c /�a�}G��ւ��OI3�a8��[\�]y�p3�³���2R�4~t5�j�ۋ�#�{�V�M�y���o-6�Q�Uj-�;۫r��n�<�Ҵ�[b
J�\�F�2k7�J`R��?T�dt���0�c�[��fP�;S��*���?8M>�s),+�fLg��b��xy��;v����^5^�Fg�썯lu��eKԛ\���|,�Ą0O/����|S�5���{�bm3u��H�e�^�-"@�}�iLʝ�vP�ˤ�2^����)��7�T����ܚ���Ńބ��j���)�T�!d@Dc'�*R4"���nKS�l�}���,J�(��c������ �IXS���i�2P��
�@;�T�RF��ȳ�t��R�pӊ�?��;`�y
:�F4�B���l�;�<ed��ܐ"2~�|:�d��$��{�T�ۜ���07מ�A`�ʄa����'.�]>
X)�Մk~�4W~�
~<�6�m��� .��<�I��B�D�Ϯ/�s�������N�ނ��{��5l�T�;��h�
��R@�����ï(}nx+!����./��<�;�wƢ����nB�۔�}�[�kL�N���c*g;�`aG-��n� �2��-�]F�2	`��$��\8��
]��3J)A�)�w��O��5�&r�݉=T�0xZq���٦�^<�4��o+�er��c�G}�����Fߦ�
#�RЗtȈ�ڧ�u�?��_	8�*�ڦ��BC_\B���~��?M��瞡�T�]\S��Y���Ee���N���wũߨ�h㷉u�w"��5;��/��0�O�S�
(�B�:S|J�f��H��ӻ
�Y?���w�~Kh��)���U-���_���׿��g��L�Բ�֪xV�$�=���F� ���]n�̪k�x4�Ԟ���(�?z��,dO�&��@V�L)jі�s�hX\�)~�b ůۥ�'-�oG[�4y��*��	�I�rF���-z}�k�U
>���P�/�kvo4�����x�9�@��A���5�-7

ٷ=U��F愕z�w��L��Z�}�kyu�ja4�q.�v��F���ҷki(�Lu�x�0i�b(��1����4��$|;_�?���|��l���o�L��B��J~#��C�մ}e��^��^�=�J���GK����6�S�)\�K���!��9�I��zN���af�&�#��;��eJ%l���w�T�����?�@4��X�&_�S_tĻK=�o@���>_�r�*l+d��\˲�ư���;�z�B�܏�0�~��_�5
��Y7N"W�S����TA�ǍmP���K�~w�p51��}m�*Fo�ҹ��<�K�q���~�M{��<o��w}��&'§I�ė��G=]
h�:��7l�X�An8n����>��f]MX���jވ�.ƫ�A��|�cO$̌�:>/#���R�Ŧ$zb�����NcBî8Ή�P<	\Rq���sE��C�X�n���P�+�M7Ź�Z7FJ�l�PA�>eASX-$/Ѹ�^���h����)�JLa�Ƥ�|cN�BIl�y`��Fσ�:�r�Tv�ưm��j2�_����J|۾k�rg��*�Vi�}��,:����wfS��1ЕK�z����y#ǃ���K�_Wtث{�Po�]�A��E�<zXӚ?��z�d����������7$d����o�c��y{5��-�W��^'1�P3��p����5SW8�a��F/�cӋ���m��`�|�Z�P�k�1m�b� � 2��Zv
+���$��y�H��{���Օ9�:��.�#��k������S��ܭ��с��8P�Gb&�K�K��k�'��/S��t�(�E��?�H}�p�|�n�v�	�^�45�~Z��Q��~�o��=7���i'�@OUz����x�����9x!�����(�#�Tk�gl�w=:X�Dn��ܱ�� >��;�k�YLNO��,��ڲ�Ff��4Ymz�fn8��˜xVu�U1=�3
'|�����k��oI9kX`��71�~�]*�Jhs�t�����o$n'��a��M���T1Z����&}��!�i@�D�$&��Q��4X�A��$��Ϣ|�एB"ӮX�3�]��[XHo���_��Y/LKv=)�
 [Ğ�=���g/#�>.�ċ�G��	9�`�#��r��xبYr{��Yn��(�Sp�^'P�#�f��{�q}?Jy��Y��Lr�<���E�3�{{�Tj��Z?��ƺv�t���*����	��"ѥ@ax.MM,V�_��/o
����dd�!�]��)�i�/�>;���1p43��YG5�gf������~��Z���;�p����t�žM�|Y���C���_�C��ؕz���A�Y���b�C;p�?h�~G��}{��/������O1%�F�����s��PCY���V����Fe��p0�H�~�s
��Jܙ�0�������~�|���/J	�b3�WҦ�hd�M����s�|������ܿ�9)$��AX��uvm
��ͬ�a-�� �V{��j��p�䤋!ԥӞ8�R�%	Y\�
���p����uP-���W���3~�L���pM^/�k���y�G�TC����F���yz��$�#]2�n0�,��0��p`�ͳ��І�R�S�{����r 
J����QU��1��m©��}o[�3��S���I�������}"U�䖒�������R�o�@��.4�Ik�~�q��
�$>5�]1�?h�މ�l�>���ɩ�Y�8����Oת?������|��7��!��=SdB������}���u���_#ȯk�� �2��w�b�Д�77	�������������+,����$�W��эj�1�b؛O;��\Tm2�� ��u91*�?n�%Z
�Ƕ�[H+W�
2�t��Nd*�KiAG�3;o�/@�T���%t.���6�ϋ�|��	mYd9��7Q]���"Ѵ�^y���6E{���ȝ���tnk�7ޏ�o��o�NlĜ�y�O�;����a��������F�1[�)Z���N^5�_"��}�HR��p؏Xp�ɋ_��m�_���,g>Ҹ�Z�z+x����l�x#�8�v��I��R�������9���Cw?�!Q����;$�Ky�W��tͺP7H�43��M��t��4Ju7{R�`(	��d�?Z��8�-�BF��2�p��r槁����h}��x"᾽����8�Y�A�ym��9���T��Ǥ#3fh�vGխN��<��Ys�d���k��(���#�ţ����ڳ$DP�n���
��lc�;�Џ���_"�wL$�E��p+,�j;��_2�_�&��`�qL!��?���/}�Jѫ|ӌo�E��k��'R{B!��m��p��6���!3`��I�	~���&�M�*=B}ˠ��<�<WV��b7R�BB;���.��3���x>r��k�%@5�K(�]�e�g_���I����ϢvDY�癛�+������1���iUYE{���h޸X��n[����\���}s��C�t������'��3��xY*܍F�t���6�$%�.���Ғ	|/�Ь�$�K⍯�"O��I�c>az1S�Y
��{@:�3'8��7D�i3@V�ёly�8��1s݃�C��o�����[��&D�1{H��;)U��I�)xׄ�t���y�ڏ'��!���5��ݯW-�!E����M.�5;��:[FZ�/y2�ae�.��;��.����"9}�jǗ�hzo(ߴx5�m����K&�ʙ�g��Vp?Ȱ�fA��嬦�I����`���ڷ�k���#�w�g��w�����U#��q�?�U7�8�֬ow5�VwՎ[��r�����X��c�8y*I9Ѱ3��:��X,��)�I,���
���9��۟ma�ݝ��ǸԍF������+@�������q�<������v�Ӷp��2��Q��ty!�ˊ��~o!������4@v��H�"O���༌=:
��zy��$8!��l��b�_I'%�zPW}�f2�,�Z̋�l�hИ:���lB=Ml�v�a֦�U��Z:fhm�����C��$�s�)���D�6eG���l���ika�K����,�/��_��+r�|�7V��-얪I<�����rs�����F�����f��[+��宓.ħޘǃ��Dq��H���
w$;OG��\��1BgfJ�3�E7	�>��	�2�iKѓƘ�R;'k�e�T�|��|��{i-+{Bl'��xy?T;bzˆ�u�I!*e��mmΞ;��A��r����~�iǝ55
/P6����l)�л�4Ks$S�����8�X.9���8.�w���V�+����\^:g��M�y7��'Z���������-ݢ�&���J��#��ĵz������U�J-��N�.b�Wܓ2�.�W��:p9d�
�o~K�C��8ұ^2JW?���"��}�X�k�e�1����`+>7Þυf�����߽����'��%� }��0� �g�ִ���Zd.ʝ>(�w4����sх[
��_�L���"�p��r��M�h�`���)�=��Em-��zN� i3G�5��ue�J^�ŏ"%�>��Q���m�G
�8Y�F4|��a'���x��>\b��}�nT�qd�6�`�!��?څED��V���:�pI]fy�9^�(q�3,vG��`�f6��2�p=�ĸ,�W?�[��e���0��̇�׽�C	
�̦n�XH���V��P���GO�#�i1@մ�&�K����{I�v"���_����λ��!�d0���@�¨��R�f�!�*D�����6e$a�[Ew����8n�Pm��Q��W�t����c��S��h4.�����g.�6kh���	]M��D�b�z���5���/)��`QBM��ֵ�e>	�yx�G�+�]σg¼��dG�N�(��A,��X|�Ob1E}�k���x�QЋF�@<��q�/#���J�J�?�ß����|��������S㕣����,yz'>�(���ݟ7ED��ذG|��۠K�s�~���*����z|.��%֍�u�Z|e��a-�4���)A?�����!�ʾ4��}�n�Ĭ��S��x�2����9��Yp�b��T_��1Mԕ4�}rקR
14����)*�pE���d�]�iq��z�8%���M��ť٧Fߓ<�Q��4�<G�����tu���拁���f��%1e��2�wF���b�5؇$y��/z��6
�UFa��Q�g�k]$��Xk��x��ϲ����5dF��[�,ncM��5k���O�(
>@�$�M�D2�NM��tG����oH�l�̆����:>�J���]ZM>��Fa�����꠻�~�}rL4��o�����i\�,�?t�خ�5��he~L
���0bi�6Hr3�@(?)HF�/��`��U��F��Q"GWI׽C�J]����B�"w��<NN����Z��4��PeM};0X����*��ܾ
�鄗�"_�E�G���~u�0��"�����}S$e?��"��C�/���D�OM�;E��g����hU���T��5����+�y!��d%J�#��
���ow�!w���а���מmxp�F&�g�����L��e��͓܍�� �2+��_��C�=vm�-�6/%Zk����R�T���-�4��D�^�M=��*	>�{�'�j{��%S�͸��T�/���)�o�ԁ�'���
�e�x�� ���E�ٌ���Hj�z.����0��"{pڪ����{:e��t6�Ā��k�Ԭ���
0H�x�AWg���zm/dS�?���r��w%2%n{�>�O�4�i��6�^���񕁶����$��r��pר�?�Mn�K4��ҫtJ�GL<MM���%qaϽ��"�Q�K)���Í�I����)�ћ����R���,�@�>I�A�CD�qr�ͱ��f�j�}4�5�g��,�䆇g�@�^�>M����VqE�7�j>��)j���'���ʣ�=ra^r�w�~cs	���-�q
(q�Мs>�t��
�1^'"�G�J_˩x'��K�}��‹	�F���r���ǚd�q�d�&��ʪdžsJ�y��<j�[&x^�ZE����������pe ?u��f�Y�b��I(�M���Z���M�W�I��s��ޑ����W-��ڛc�w؝l��'�2#ڭhOg�JE�!2Lys9���� ������\�M�{q����-'+�Y�^<o�+鐿#BA��y�?���:��ͮ����Ǎ�Xr}}���>\tbBZ�2�,�@|Q�;�7mٵ3dž�|����ٓ�e2��0�mW1�\�f�����Y���4;:�P���X��e���[K�S��}v�l[\
{'��s�G2�D�n�['�%�|\���"GA����%	7r����L���V��%�{�B�	�Twt���*{
�-��_�R93��]��ߒ<!f���o�x����^���� ���c͙�]�!�i���eiV�ɡs����>�O� 0���5`�s8饰>ӆ���l�������N�D���5	
B9�	�e�-�N���n�\�O���ڏ��K[,'�S�<e�H9�cJ�ɹ����19��&X���}�s
�U/I�����j�k��X}!'^	2b�Ѓ�������4�V�{!E��ئc�E��B��C
���g(�S�m�o�{���l��K����6���O �L��&�7�TL�_2E�'
�%l�U��ad״\WuF�~�K��Pha���a���`�J;��6)�?��Ã�3��|�"�D3���фn�~Px���i\ۥH�)���'l�#l���A���l���X�~м���6���-��Af��v�U�R=���9#����j�;����^�6��۹.+�@�.�}9�vS?{�d|��p%$^�0���3��(��ڿQ���H�Ս�('��KJ"�a��_bg������\�C�Q��xV�>��M+�5����s���q�2�1�i��-��!�������{lr�~M"=�i�1g>�_e�|%�k�Ï�B��H����vu�����1�y;�/���������o��u@w�m�ϋ�lT�Q�?���G��Wјk��L�>hDbf��3�>������ө������|d��m�$����Ic��3I(��}|I����il����E�?,'{�W��K▼�
-S�$咽�����C�{�6�7��q���%�&������F��ë�
�Yg�ӗ�޿�%�wp���_�%����%:#߃���5��������S'Ïت0/�i/�V��oK]�+�9�:V��\��tϩ�\�u��(i+.���6��o�լq<�R|��W�4�Y|G��ԗ`awm0z��ᗞO�)Aץ��o��ԗ�n�G_rT''u>�wn������Zx�]l��Dj_�}��P�qm�ɜ=ܗ�+ٙ_|I�'[}b�Y�K%PS\	�ةf�N�"�롱���F��	���.`h������,��7�*��T�ŭp����XΧ~;/6�����CI���M&�~��?��W�žB~��L�颏{gLZ��w�"�~�c�B��B|����J}5���g~�gq�5Z8���'qI�Uv�=	�k�!]���m�U�
6񛷻1������P�3L_�d����?1L),�'��ܨ�^j�j�*�LA��{~On���@Ë��E;4�v���a��ޖtQ��q��U��D�9�0�{�S�<�|��֭unA/�7W������š�K�Év2;>J�H�r��w#�?���0U
�|aĨ������ơ!/��~�z/�d�r��d����j�~bC.�gfDf��m�߫�i�_�^�B+�z�?{��dYv��4�Jq5[����q0�tE!�T�|g�����A����_��?���/�?�Io�L����3�ݩ��n�P�q��I�D�ͤ�?�ԇ�|��������b>-�����ԏ��R)7�v��qU���Ū>��_տ犢NP�����k��E�+���T`.Q�i�w�ነ�]W�@K�z�=��—�V���.'���5[wn�w/B{�;B�Ed��B�ka���Ì_W3��;�63���]l�������VL�����zdՕ�&`><'_�%j�
�_�S��c���B�'G�z2T^d?l�V/��R�y�Wz����S[����#y�����'�aؙ�F(�k��M�JN]���(v#5��>�}9�|�s:�˜$ˡ]7ݏO��������������f�G�^�V�a>�mź����F�4�M;��~���cv?���jG�&�_�
G�'��Xv�l���+t���pyDx��a/I*�����wcj^ų�-�a6�/@ؤ-<��-~�p���_X)����՗��z��5�}O��+���g�n�P�_��c�E������齑9Ԫ�򊚅l�O5�{W?��V�FĞ�๚Zo���'5���%	gG����fڴ���j]�����Aa���CT�ؒ����Y8���vH�{n�����j��e���5d�YAA�F��Q�x�sD��!1*���ۤ�"w�wZT\���)>�
�|!뱖�o���*5�䂙����Y�S���ֿ.�
���3W�4ET�����"խ�
4���
n�伢)�$0؋_�G�?��c��O��I7849��ˬ/�6��7�%
s�;W�L�E����
�.��c�0yT�����7Lf��ӬP�CH�Q�Ȱ ���2z�}8��#����7>]���Tb5#���@�y��l����P
"��9�Z�N��'��3N�r�����^u�C�L�[A��2h6���i�+�za���z8�xuQT�,A�1Ax0]^�yE�kn+�p� ���r ��O�K�S��`����N�
�*կo��ͦ8�5U	`�Y�[����Y����E�!�	�m��h yI�4l|���v��lwٞ)��^q�܈�10�J�8��U�Fb��@�kG��������(�_���x�8Ył/{ʐ���ao3bf�e���)+JG�rՔ��O�pQ�5ǯ�e�6?���
����NJ�g��l���|!~�
��?(}���ѽM}����s���L������Q�}��|? �	�'.��e?���U��S�o���,���ۻ|w�����Xx�����h�"Q~Y&�y��Z�q�^����uS�쇯s�*���@=�%p4��\��\.�x�D1�u0{[��u�SR3Gi�<DL�տ����۾=Kt��±�n���h��Y���Z����s���he7z%~�]K�UB���%��/�W7p���7
�����er��S�uk:V��`��� �o�6�ܚ�!W�E.�H�̄Rd�ET��*<��Nsolγ�2D�''��oTc�V]��i�R>�j5dNRIȝ�o���Ŀ#��s��44�����|?9т�Z$�y(��6]C�D������Y�	Dr_v�~���X�ȳ�-kД�#�32e/���/�ĵ�wҚ��抏�t��0��+����{N��t�M<���;VR���97�R�&4Ո$ ����!�7C\$��Y�"�x�yo������l�!�����G�A�J6�9b�\��:9_��n�U�`_��W�,9�eK��_�s�}�jIh�g @�� !��1RWVf��m���VA�}/�%|A�L��8z7�q���w�������h<
�VI6��Ұ��PHN=��.�8y�H��"G�ӛrQ����_6/|�u��gM�uʼy�H�<���bw���j�K�l
o��ӳ�쌝O@vL?�{an9�}e	z��ÐG)�cwi��(��/5����� -6=��D����N?۰��f�|�56t�B��b��
�$�wxf��l��b��G;}���eM���&fJ�l����d9�0�R�$�w%k��!G��g0�w�j����n�fN��R�R�lkX��q4�p�3o*�C��dq(�Xg.`8��cz�LQ-Y�p�*CD�<L&?�V���^���(�̈́�@�9C���X�zZ�[�̓'D>UWv�!�����X��	���S�HJD�m�RTn%��qNǮcX�4�)��g���Օ�=k^�%�?��B�QUw�-�.���W\P���.p�^۱u�_�:>���>w?}S�}��ǛN����/N \�XW����w�~�v6��;L�o8)wgԷ�����a"wKwm�A�C�`�Qe���r������}}���a����y@��Ԧ����mG!!��p���+'<҉':�/��f��Z��\��6]��v7�!X�?�`���1�M.sUp�A.G��	��W����s�~��E𓾘W�?���f���'��U>,Z>{�l�7���(��x_�\��w/X�}?�Խ�x�������O�Ǜ֣J��\��\U�i��yQ|'	���|�x��=H�u�d|l
����[��(�`��"ٜQԙb�b}sѿ�x��$,}�Ɂ�U5ODW)���(e�������/VT	�h+� 7�aV�M+����օ�|g���4�$������h*q…�R�܅z�Q�Χn�������u�ڪW1�;�������	���c�ܿw�שZ���C׭}�gˇ]�(x�_�%������QYK�MX`.�{��d���!�t6��1�`���D���C|ݷ�s�-6�4BN��-�j7B�q��5ďFpE�����k�>�XWp|�95�d7O�a�����?�}�����L�ŷp�mg�Ă�ar(��^��[��o�|���A��lN��S��X<������û�V��3�ȴ`kA����B���T��
��>/��Q��O|'�V���kه��	)��{ͱ(��SN�>bﱺ�%���"j'�N���g4�/oN���6��i�	5t�
â�GA�|Ȋ��]i/9 �Nȸ����8v�ŌeEL8c�%6�Hp�m��o�K�0�-�7pܱH'�(|�R˔�B`�	�&��zI,h%QE�b$������Xkʅ�W��"�Ŋ��_M[�����
.P(e{ 3������x���a\<�O�|j�%b=|�//�(����Z��H&F��d��ԌHmK�Afr���Dn�D��\���ر�v	m
)I�<.�d��pe8���B�9�>�4X��%s	ꢔ�<�tYk/���T�
��Hzz�x��x���A��X����'m���PA�.b��B��3�y����F���Py�E�a�zZw�šp�.�"�yTW��*Ŀ��T�C
br|����UV�avt���6xlp��r�rL
8��p�D	
�.��0��C�M@�䏬��\@����YM������
��#�le(�J?�97����h��g��I'�-�y��'���4NݡY�@��sh��:��̈��6+��@“���{�g=|��Vo�v���P�!�1f�p+<Q�ѥ�޾q���E{h��a3M��� c�*G}�`�>Cd�Q�/6�P;�}�j��%H������O���Wd_�
�ڦI�أ����a#����"vIB�_�}7��Ak`/S�D#�"���̷���o�䃬�jIõ#[��|��'*;�C�,Q����	W'4|i�
yT���CVI־n�n�Gx�-BX�>���ov\�赵X�u�^����_��F���;�旆ID�͗�f�A#��Va���8$B�HO/#��J�����h($w9���7q}��A����7�^
a��PO�'mW�����>�u��{�1n�*2@��[q_�-�L�<v�K���{��&j�@�>��̮9<H�J�)�Z�!ϔ�m��p{�H�W���V8�|N�pSZϲ�/T�0�&p�k��.BB�A fZ�u��7���G��Z��>�,W�{�7Ӣ�L���粛q�y���h�|��W�~޴��P��?�=�h�����z�/^ߟ����?8>/4�
�%|{�iA2�c��k��E�/K������cn{�?v}2X\�ߑq��2b�Jk�	�%�K|X�]#��6�y�2�mI�g�"�a�����׌���˗ax���"�3vX�jAD��@r�e�.Ji
�+"���h��E]��_�$8����f!�(��_}�n\,������%-6�ی�����8λ��ԳP���8�|=��p����
Ἠ?MX�[�5Ɓ�@�ue�u7�����E����㛶��V��Q��so^*������l]op2,�Oz�5t�20λ{�򹨳�q�l�7�5�N���!�PO(��L��T���g����U�Fݿ��pˆi�7�h.Õ/�ٞ�Y���b�s��Ñ�3x竫b�4��5oa��n�^��,��qa�*>������^�:+J��mU.�����:�[��b��pg�|�|3��D>uA����o϶P��q��ދ��U�Q��������c#X(���'2�������n�sy�1�7I�l��`��op~\Ѽ���>��,M,/�+G���y*뿃��ʇc�prK��D���Cxj���	��<
��8<`���zb��%?�+��Ɏ�����!>=�{X��P�#d�r�-���^�M;�ƚ�R�C���G�E�ׂ�!�:�f�������U���@8��F�t���\�T�*�{:�+���1��ry�7ŋ��q�R�%�����X���-��i�zK�{gW��˜WF�����A�ȭ��ڮ]UG��u'���]c�I��1?;P�ܗ�]�pG�Ʉ)��j���W�� ��c�����V�w�l�T��r�Gx�&�a�tY��/}p���ѣ
�r
��$ą�IA��k�
X>�KO#�����\Ko%R��d�D~��:�:��8k
���Q+�S�D��ڸ3�����E�"�Ώt�U@�u�r$�r�?R��M�}9���}��Q{��8c����.��V0/l��1��
Óa^w)vFe�E���d҅d�EF�R~�Z�����'���A
��_	b������_���b���������G
%�_؈�f+w0h��;w�*�����QE�]s��,q qyr�/I܇wHn�G�	����>�p�(�aG��Qo�!I����PEC1���Mߜ7S�
Fs䒻l���'�/p���z�:$�Z�"���d:�\)�XAT@���h�R�g�
l(
���t�̼(�ͤ%�K��M.	���)it��tL�ҏ$�K���0b�O4s��L��x�s}~����["���/3�����1�az#��2�Y���fU�U��{w�y~qO���A�o3p��
Y�{ȅ5�h��o��iD�]��v��!��q��.NK�%�4J���p�j!�5���\�I�غzV�,g|J�H�@��ŷ�ހS���0�J\�ΘHox���iQ\�Q�=����}��e����O�}�
X��p
�mA���ȵ��K5�j1D(���g"��yc:>`_"����:6�`�X���m�r��0%��L�]�<���0���ê�ʟ�i���w�<��i��h�t���|k��8�(Jn�1A���:���<+��ز�y��+7u.W���lR�H��\��s�b��J
��?8�~�9�����?=�����翍��m����Q��R��zC֐�Y�n�e8�� T���<��K�A�����:���f1�E��⼋�ӛ��ro�j ،�*���ۅS4��a�~�T|
��x+l��P�~��0Um%�{	n�`�kN%�+����d��Q#�bc�~F�Is���X��;�	;L�yc��m�2��~hyX���L��ٳ`�}4��5^����B�@[Φ=�I!H��3�;��X��K���U�7�EZ+z����A���V�.U9��^�m��ǻM���v]�~�1!�'�ݦ̿������/�@�h��]�ȇQ�硄��v�:��\�5�k��^�zpF!cq	�Oy寥����oEZ��C�n����XE�4VJ'�����&��{>:*�uA�����y~�s$�����O���O�+�
|7pQp��k%,`Q`��SOsf��Hԅ
y�4��Q�t�i��-��Ո&���-���e�h5��3�ݪk��=B�E@����7W�gl���[<N��H������R��,��%=>�9b����c�)��8����f�oּ%kO=���_(2�ŭ�},[Xn��R�q5����n|�î��
��T�����
q�{���21�0o4�ѣ{XX�:���R�:yv�'7���߉F�P$<���3t��ktJ�:���c*���M �^��m��$r?�#����3�������)B�/�����P�ɧ����f��h�T�R޻�2�(�B$('�߂`����m�?I��/׈��(v�d�����!�}`�og"};sQ������-?s�{�hm��E��[IN��J
��~��/����5��;m}�f�W����B��L��C\�b�0l@���d�k�����#+����h��"B�rޛ^pĥ�a�<���s�KLW�W�3��H���Q?}K�ԁ``�j�Խ�
c��N'�>n�&c�d����s�BR4]@{w��"�p�H6��,������|9l��]$X�ޚ#�t��w9�
��\�|�QxǔV�]3K�S!ؼJY�&?���3��FtJ)���'-�����4|��N,��p[��W0-pM�W�ǻ�H��>�k|�����I��l��r�?�v �3Z�j���B�Y��ۆt/��bIӛv�
rOqZ��!��~\C������Q�2wP�36�	�+�%��ڨ�fp��0��ۣ_�dE��	�Z��U����/�o�?Y\�f��1�<��X
b�w��@	����e�Ɂ���4Z����}#$(LKr�b�3Ui9^b.G���Zq,O���U
�e�$Z��G��d�w(�F$T�3� Q0�)s4�$�^'�
ׁ}�p��)m|h)$̃�t�H�k>����R���)�HK��"ʫ�@O�$-g'#e��RVz1��}�,Wk��Fq���~�)J�!�T��[}'�a69��b�<�w[hjNM����������Ϋ+�l �k�jTV�(���>�}�4.�iW�����4�£~�ㄉ��4f�h_�=���`��׮�ko�)0�PI�U	���V~�q�s������z���D�@9�Q=x�����)[$�x����L]�,��(䞐���
�'�x�xq���J&J��J.8�F/zz��m�A��OX��$��4וd'�a�4CE�BW@��N
L���
���툢!u�a�sQ��@,w�8%��l�U�0�2�ͬ�C�&�V�*«��}�
ɢ�D
iآڳ1��8�Ԯ�]�*,��[΢{f\�;s�j�Q�й������|� }�Fa��B��G�A�}��W�ʦ�bxQ�*�x�_���_M }���f�i|�W�{���j��z�g������V�|����J����/b�#����Q::��h���o����/����^�F_Ű����~�+�n�>�y��I�m�?>��}Zm�i�/h�:��}��'����6����7/�����V��}[ǖ��qZp١��wՄD�J��I���̫ZB�|�+r
T�J�;�xw�J��	:�Q�za� O��?��5��bXt�*�Y�RV���>xL�;W+��p��IP��&��[acJ9y���^��k�\�	���\)�g��y>l��V�Y��:f���ICr�\���
�"W���p�	�0��2Rn
+͈[~*W-�,�a(����(ۂ�	O�qQ��K�ĹS�E�=�$��������^�_�E�3��P�y&��F{m�m�U	�3��xؾ
AU,׾Cj{�jC��"�;Em�Dp���S;���'��q"K4U��J�nw����lәt:���cϩM0:�=0������g��K#�vo�:��6:X}�����9W(���d�-�ʶ�o5�xI�<��ڱ��J�5f��k¡<�8��ʘ�d箺-DԳl%#V������Y���đ6��*� i���^[.�)�����G�{��)%����{���S�<��P/�9>x��\�K�4x�Y��o��I��Q�%ު|=�����`\љ�?&c�yL�����3��W��P����ߦ��]��$t��������)�m݌��CI�Š�k��.O����9��L\K�5�rD���o5=
?Wk��|�'Rflv���Eo�������ȝϽ�.^�O�*~ӽd�?�"���88�&��g./��K-�π-���aI
{E�`K3n��S�Xu�&�&�;�6g��`���9�
�ܨQƗ��%u���"3�|K��Y�D��/Z�w����'	�:�9;2u��?@4�m��C/$��G;�bM��n=��8�,����hd1���^�2�`Х�5��LLJ,�����>��S7h >w�����/g"��'�T�KE
�ޜ�]��A�c���!�э-����rSGa��!��A��edc��`�ۓ�.9~ђ�A�ROw�f�9��K.X�*��B�q
��xKL=/���>6�:ljħx�[�K���s�gp�+�1�(Gb��fTD��L�^"�^W�?�{g;E-қ�',(��1퀡�m�I��ߒ�;��p�o�ku��-f褭�O��Y�"��[���%��Dbm�	�X�l������_�����;��
ؒ~���Q�����8%�=�M�[F3��m*���2�3iF�j��,y?:��tYΟm�Mæœ�"&�Ԯ�������s'�9����FM��δ|��ns��n.�/ �g�i��6(3�6����q�?i0�?~=%��$����R�d��2�Ů9��`��5��4pnp��h��{z�zo�y�ۇ^~���Zi�#B(M$ҷVPl�U��΅�Zo���o�|گ�<^��a@�[
HѪ�~��o�Vʚ"Q?����ȻX�p�5xi� ����!q�rTc-v���dI�M���>6�/��s���>���7�I��?A���P�{A�NKj����ؽJc��_��h��_RQr�)��&�4r�,��n��L���vÁ7�K��������8���a���N5j���3?��L�q����V��@���Q����6�:��ݠ��Cq��W�Z��տ"�?���>���w��&��k)×X夗��pN~oA�����6/qF
��_��.d�^�RC��bΟB��Ʀ�;��Eh~{-;��Dq��Z����+�{���[�}�z�o[�8�߸��u����0��_���6"��&�}��jfLJ4�H �
�9��n�tqRM������g�!,+�O��Lʇ1Mw3����1U��xM��a��uI����27'�/F�kN�����O��д�[`I����kLl���y�\=٪g5�:��p�;p}����~����B�Kp�p}��\��~�7����e`�]�.ٙY�
�S��8���7e0��^nL���3w$�'�؉[���h-����sM���`	����𺽂�ICw1�9��>[��V
���+d�D��s^~��cͥ�Mw���ߋ�f�7�}��N�ڙ}Ir}��g)�K�Ĵ�#�D3�VsI�/	��
G�"�=�i�D��9�+Ï��\ƞ��S�4��Ҥ�/�tފ��F'�>��r�˖����e+"=�G?/���
�6�����8u�[�>?�:B�a��pkzZJ�H�b�O<@-]�G�3��Q*�ޝ��s��j�~{>��ֹW���*�|t����6v�c8����o��)�!J5h�#���y�7.�	S��F�U�l�|���u��Q�	�@OF����6�|��/�.͚��tٗ��T��4���(Ez���ܒ���N9�h�ܶ^��]��ڿ�)��� �W�}qw�!mC���MJBpπ��Nj�9� ���f�/�Uյ4H?2G�Uƍ1.��0W�e�O�o�2%�wsh�إ��o�C*r\��8�X���V��(�[;��$�@>şH�^�0������C�ƻV*���H�s����\-�Ԋ������qT��/*
�\�Vg-c��emڦ���:ͅ.z
zd�=p�T�L1����MK�L$xƸ*�Pp�����^yT$��rs�O��(�v�7���(~l��/�G��I=z`7�OӰ2j4�Ə�G������޺�ܯ�i�.:P�,�o7*g.�C�2�����\�B"U~?kfKc�ޯM�v$�6{ʃ���Q17AB��K���;�?�嶡a��=�9�?��E����qnفR���]�*�d�J4���%���lj�M��İ[g�>�j���G��a"�S��	��v���\
\��-�2cO��K1���-���ʊR$�&���@�ev5�t`�@4�mK������\��G���x,�i�S�%p��v�z���B�oEzZk��_mj��XD�� ��'`8	�d�D2��.>|��zCsq�+��
�	����|8W;y���nR�~l61���__����3q?�f��p��U�S��.9���{� ��Y� �+��G����zJ}ݫ�co�_ց���UW��C�ϝ�?�7�k��W���nN%��װ\�?�Ч-�I��ve�ב�p�>[g�_g��o>=>���H�i� ���h�<��)��%���z�q�E�Օ�a��[i/��_��i��B��Z3�H�G�z�e�i��ܳ�-D�Ȟ�U�
k��kw��a/�����~m*Ki�<Or��I��}����A�mg�"$��5(qH̖iYp��h���	&��/��
%�ҷ���b�Y[��[)���e��ڝ{���y�c���E�Cx�઻;h�s�S2�#�+>6I�L\b��Q�ukl����S�|�ҳi���ԃP��dwZ��7�}~���UݎVG�(8�sb㦚�vV�#�c���JY
�&��D� �R�>�;�TH��IG���p�q߉����`"�>Uc~��]&��oJ��}7�·c��
�y�]
����//�|.Y.w�)�����hLt��RW����]��4��P�y�
k�Z���)���H9)��Wb�݅,�w��%%u5g^{D���)���J��M�/�Q�8��T�����D�\���R����vwˀn�e��o��Ď��
���lS�#��]m�L�z�
z�N/��}�<�"'r�@�ʂ�_��"�9���oY~]1�[���-�E�!l����f����0�F��I����
H��į!��F���M����6��W'Tp�L�6����*���>�If�A����9 �]+03ŗ����*_�|G�ivi4v�;��'��;m�cwHO4�L)DF	a�T?�:�ufB>p��^�+f>��>�Y��7��<�z�.����v���m�xe���P��z&�Ճ��7k�X
�����,�#��!)��&k�o�B�lV���u�LA���-�?��l@�o(�K��u��΄1�[�A�>ϷP[2ԑ؆��Q��/H2;��W#{p�V�pɂ�(��1�[F�rrO��Ǯ�8ʚ���#,-p4F�H��S�>�z��n�u����z6F��k+�#����Rĵ��cʴ�!�� lT,�py�b�a!1�7�"��r��C
*����ɒ
7���d �^O�ZrtLQ�<q�+-�/m���Y_��u�g��K]��E?Ӷh�d��އG�P����z@�<6�/�o
Ou[ׇ����k]�%(��|�:j�N�).5��о���M
�z����J�&2�E������q�.Ѭn\�4��F
���)(����?�fU�8!�K��\�P��;!��?�I��?6I����GL���V�D>��+B��G��:~4�,��3
�
D���u�n��{X��?�K��0�>q��F\�����cz��u�D
t�"5�>-ɨ��7_
g��5��ǭ�
^���9�B��u��V(E~�D�+E��Eki��0�1
2.�N;f?���H-���˚�FA�����d�����7�O��0�n�:_N��{�e���g�txC�r��y݌Q_�H�^{���%Wǁ�Eސ�DhS�Dj�yc���(���B
]ǐ��b@���?�J�&
�/9T?����c�ˇ���\�����2�,���a�x��3���x�~�1Ff�A��U�g���g�]?A�-����՜꓇Gh�W�kh���:ӷ�S�Û�$\���=b
�w#�o�>2�O�蟜|��4���+�߆�?n
���{�SD�E�7����a�WXdYW7�n
r�ͻScnU������͆�{{/��˯z�U�G�����8Rz���U1����o�R�/�e���*>�K����E:���
�Y�a�Y����c�15��O�k�,���.'a���Z�qh��e�Xd{��;7�~�

�c���kB���3t�|��Q��jx%j�a��cb�
�i����
��\���V�����$7�
́x����0�EX���]�rjW�����
/���l4�p�K�<.%�����������^��W��pC��d�Z�\�e��.�-ā�}@��ޱ	d�$c��[��lk��2�5Ӫ�&��:�b�z�5d��u��S��
����W�%*�WZZ_lB��EQ^�5���"��
���#�|�ym\uJ0�o�#�n�7���A
�㭥h	i���-&}�uy�P_��È��a�
..7�!�#�̶,
��t����s�lER��̤g�9ʆ]snD���#\��Ha]�����^_�"�7�K�~�����.M����.����=e�vф��~�_@������V;ȟ ���!�ʋ�
1��֗&ɯ�_8�o�%��qBZ�>�$M�m��2�K|E�F�4dCæ��ha���d7j_�U�v�`_��-�9��y�G8H`
D��:D�C���."�y	�'��r�ib������{#�#8k�I�G�l��#EXS�	T�Ӝ��Z6	�l���0웸��,X���[6f�Q%�$|'/�=]�+ߐ�
Ih3�w'�x��	_Q�_檞���V��������Ũ�(2�A;!?8�ͮA���K�qOvGB8q�@9�V#�r�E
H�"pJS���=�W:���%�YQpFɳ:�\?�^Hb��χ���p�mhOj�t�=TB��ͯt� Z�2�#e�ѷY@n��W�؇�(�RcƳ�e�q,h�ׇHˍ�6{j��E�H}a��}Ca^�x��bQ��}��5��WH�䒱��IG����6�T�]���b�6�娍�c�[m9���ÈxAyJ�X6���0b@���>��`#�K!Bb�do��CR�!_s��V	#�I�.�ܦN�E�T�����u�〃
�nt����%A��w�m։�LBQ�p���v���j��1��d(���V����X��7�c����OEV��5譂�Z�6ӝo�%f��O;Պ0x=6�I��z'R(�1��:}����U5�㱂<���
���_��zp�َ��ݶ�c���(��u��n�Ҳ|5&
�xƻy��R��n�"~��GS�낳)#�c��&�E�K�@_��Z�=lhA�zm��c����-A���0t�-���܎ÓYy���h��LZ1�>,��Z	o�S����V��&����o�|�"�<�~�5}�#����O���\nt�t�=�(gO���B8f�I���|�%�s��2�10H�����P�9��#�t���W�A�����o%d{B�6�~c	�[#L�,���o]��xV!�H�-�ڐ��3=�P9ݬk��Qƅ��%ﳬ,E��f��}ro��	
5�fgL�b��)\J�x�-��sE������P�,��{8�=�C^�qż2;Msq��Хrsj���Z
���]�*R�7�\��w��)#��K~n�UT�_��?E��� É6��p6�c��ΐ\���D�E�Ϻ���jTW�ET`IQ�z���������m��CӖ��N��ڽ�$���¨����?�񮿠
��Bo��`L�9f��n)�W��m�Y8��Y�}
�N�V5%�Ԣ�_y�!�S`O��EA�׌�y�3�M�c-�Q�nĵ����4dC���t�JN[Ԛ��Lz����7�����D��IJ�`�Ӵ�V�4���1�fb�"�-��G�ߵ<-��v��
��.Mw��Hz��,��*�i�*��A����I�JL#`0����~�'�>ⳕ�=��=*�\��ܸP�/����6L,Z)�[�
�m�q�#,�ȍr3�AK�J>\�T}�z)��[2�)b�j2�<@�m@
�Ȃ�gԙ�:� �T�d�q�	��O�[��������j�h8�s�Y��H�2�_y��sO@<e_V��2l3o�v������#�У��y�l�z�%?�ʜ�^Sx�?3�N��U{𱄾������M�ܡbxZ�K��0���_��'B�Y�L5o
.XZ)�':	&Y��B����4h�����$sD�T���_Eu���ӡ�;4,ʙ��@�Ư$~Օt��E�����P�Σ�l�
�k�>�H�ӗk^r��?�f�Ч�x�U�
����Zf�k�$�U|"
�f�J�ldfk���P=��x%�-���W�ō���R��"kɇ��º�BY�Ef��h�n1y��n����=ڛ��2w�}��q.�Ã�J
���3��u��ԥ�'�*j����9��K����!ރ����t�,�m�V|��^�w��i� 0��6I^1�����w�c*]��"�E���cK��lPSA��8��CW"�yC-@� ���E�o5I�3c\$���x�&>OL��3u�+�Y��B���j.)���ׂf$�k����vR��RP[��r w�|��$5�
Lp�*s^�df�0%�f�vмߢE۴�kP���Is����x�jb��+��a0g���c����,4����$��Y��Ʈ,�8�M���*�xS(�u�.���]e[�5ص�����h���xG(k*���[y1��?�@"sm��M~��i��S}���T̅�l�"�n�/ך������Ya�a�����T��i�����i�Ϩ6�|nkf�b���/z��_-:�f
͗�ͽ�H!�uv	>�,s-3?�]~Y���S�� ���>�9�`4���C���a�Ǵ����,,D>?SN������6T�d�o5��m��=���n{�����=��/[R�@N~�4���ԏI���IjoË����g�t�LcRX7k/��3l��d7���-@����^��f���}��XLѓ�KS@ue�˨ܜf�a��;]�<}�gjFT��/��r���㼐a�vb2�)�O�"ς��]�
�k���5.oȡWQ�F��{�<�Wg�;�Nx�ɭ�n_̘�@5 !�
�[���\T�N�˥����H�˘L`E�ё	t���cȋB8��}�_����^@�A�t{��j����o�o<���?b��	=�}�AI�@��(%K�PÆqq{�����y�X�_a��o0����&OC0K�,<����[��3�<�o�o1	|���b�V����Y�w��zق�!��4�hY��A�"LՕ�#�,]��z`!h����2c��]�c�J��8��]D����Ɠf�h>���w��|$]г�\^)&}q�j��6���
�M��	v�#G�H~Վ0�n�s9l@
�\K�O5�,:�R�#����A� 7�uF�$��q�[� s�xy����(�qL��{]��˲�|u��1��L�߸��1��~���D����	�Zr�9p����d��뾆щ�c�q�݋
<���a��0g�r��/�$��\�yбpC�e6۪���%��
�ƚ{����f��X�q��-�_3��(L��������F6������CxR������M�s`�������j�%��&��:��,�vJ`R�Sjڼߓ�44t{�J7�ܤ����3K� �2|z���+λ�\���7�A�8:�Iu�tת�qM����#��5���Hz�ب�.��y<����uCJ��nX��B����QdA��
f�f����>R�,��=�
K�q���J���-.k~�/�VA�C�5��tDF��۱'uPN����q�þ�����#?��1N�?Bg�����Iw}�9���I�k[�������@�m0�G�,�tպ�M������1��0K'�rC~T[ZT����a���L�CBx�z�Z���1b��)d�}�l
S+
?�'i�S,x��x�E{�_W|����4�Nb��q�A(�\xh7)��r�h<�1t���>��x���B7����,jO��q�K�����$�r�@��\/]8�8�J��C�C�w�amT򴗌�,$`Ց�|�9qS:ǜ�r��,���B���[�߻��:�Ty,t�W��J
�5M�?r/��y�[w
@��ʓnd���,�grw_�H�ߔdv��/]9�x�tp�C���~�=���?��3M�w���%��2���xF�}�	p�N�n,~t����k�8K�X��HѠn�m���"�=�"�te1��*��X��5����fmP�%���2�swh��j�X�ŝ~���ݰ�����,po# *��G�a�����4z5=G�q��Dz{_-����
EN+�Fb��\
Ӌ���adIq
���%�S,��?�lY������D��Y��>�{�} �H�����ӈ��)�ןK�'��Dߥ�����T�w�G�I[�1]�O-J|�X���}p9q6;l��-��O��6�~\h#p��U�	9χ�Rnm��>��?���#��x\��2�c#j�f�
|v!�7�4�����_�|�~��
�_}��)��8�b�b�1=i&��Cl“*#���p��&6�x�Qq>{��$.�r����<��4��q�"��x��8k���weB�-�y�tJ��+�����o���\�]`�kk@Id��"y�(}�(m��+^���j?��m�T��(�v<3zf�+��-'�iSa���R�"(R� �ܵ�S�G�����ꓤ�Rr���Q�a7�O�#+cX����_�9�!��خ6gp}r_�B��OU�~ړ��)����`Lbi���=�ŗ��JPF;&installation/framework/model/model.php�
�.���Z�s�6�,�H�IYv۹Q�I��M�:��V�f�ih
�x�H@��d����͇�v���<L�]�㷋�����g���]�=;e�l��L&%g��2Q����H���r��cU�Hě�K�|��vl����GdK��:Os�l~(�k���u��]�;��7%{mƣ�~w���"�7yI�넽�v2/�*�%;2<��%�J��g鿽����(e��|`��=�����RX���G������/�������g�X&|8z����Y��ovY�M�K�\�	�����l�._��C��@��
ȹdgLF�����-H&K`˒�^J.Q�W4���)>�{��K��HF�<a��8Heÿ́⠾�$�K�DQ
6Z�II��	,`80YE�4\Ԥ�f��@O�͊�$�ok��_س��J'��@
�V"��Bp��Ӕ�1**�?R�q�@���έ�>���s�nn}�sc6��$10(u?�I�n��uj��Y^�"0L���"�xVNMYS8aY���m�U���GӣIH��=�?�Dr�B_�rc�;�b�ǩ�D�ŰE=3���]%�p����Y����U���}��25��ا�W�{z�dņ�\�3���̛ybM�k^��(�!�_£e���}���M"_�dB'����n���:
��ֳ̾��l��Ix�U��T�n��
�lc�<���`D�Q5d��G�[�O�,�7A�:P��M}�1�o�BH�t�����f���Z�j��%
�C]��
��ۡD�!E����87�n���?h��`�\(�@�z1�P�	��0��k����B
���6h(iC�(�mQ�4�ur��h����2Q�T9@��O�m��\F���W��������Z%QPL�O���B�"� �B� )�(o�B�	�ۄl�~E_[Ki��P�Q5�:X��pJ��'n�.ɠ��"- �F�#�!�0��9K�Y.0�% �e3�� ��u=�&5B��=hp�XV��Uy@��!�/+�AU@+Q�TYe�>d��%ˡ>ɒgX�
i&EC..�C�R�\v���*�-�h��yQ�Tޔ�c�.�O�9F�3�c�]��|.�l�����$S�9
�㧪�J��f
wl�3���R��(Ȼ�a98������LJ��Mo���M��a~���@,�.����ӫ����)����G�u@.��

�9�^�R�6Z��4���
_EUZ,RW�A�E�M��$��jɡ�,7��R,�ZI	���T0�<���֠��V�d��ķ���v�
$���/&c�H7�˙z�F�×�3;�!s��6�῟3�E�龙$2Ηr�O�a�~���f�:ҩ����ޕ�\e���G�f,��v�5E5��P'*:��+��"�&�G�
u$�� ����Q&+�.�A�O"�s��i-}Ѕ��R�U>=;y��0Z`�(7�7���6a?�����=`�;'�W-%�z{D�`������.�w�4Ӧ��UB�5�ׇ���LRy	��*ɖC�?T�J�t5��N�O@�q�6�c��Wv�2��-)��m�����J�=�[)?4g���+�c�;�c3d��Ϟ>e]�G].��q���B��.F�e�_��=��)��l�g��W%:u�|o���p	�Y2X��L����0��`���O.a@۔)6��H�v`8�U���C��<�h��
5���"��_��k
Y2-�ؽ���y%b�mZSA�	^�hF�3�v“v+C`(�٬����hI$B"e�I��F��ǵ����Z�4$ۘ�
�jx%!.l��G�3
~S��T?�����nRl
��4pe���А��]���U���,�&�D;����@�A��ޑAmBiw��M���DJ��\"`��V{�'F���6�J�O�XW[�zٰ�պ�^��Hx�1F�߂Uq%7�

7�kk�*T�x��
�������j,l7�
B:�TV��Ǡt�O�sԺ��Z�4�������a?5��_~ҵ�c4��-8�s��f:j���W}ȓ�S���e�e6��Yś�]�bA��<��K+ �X�2���d��<W�@�x�Cq;f���JrMU?�#ڱ��NK?h��@2�:/�O��m,�{��O�<�Y�)�	�������>h����ϑ�ۚj�x�йEmt��mTu�U���ϮoS�6���pzˀ��h)&Do�͋z�q��C�!et'\��'�ǝ�
�+�p���%�}�8�����ݎ��ptTo����=g8m���@+|��Y�9�Лv�a�\�T�/�.3h��7c��zK�F:�Z�{�_N���t*�Dr�}���c(C�S9�.��>-f���g�iW�8��z�x��������Q���oa]"\��-�DXp���?����h:j�����U<:��T	��u\L�}��D�0h�H8c�*2����6�9Uq'�o����H�S"���dAI%G�|5:0���
���JuC�ژ�i�D�H�N�Q7b�z_�FZ���6�YU�g��GtNt5�נ�T���NT�M
���tW�zEs�8��,���[
�aI�H6���n]>� c�L:�_�魕z�M5BF�!Z�B�ڜ&�$�d)M��u��D���r5��l�&��*9����y��l�O���ң%{�߿%�ȊgY9���ϟ?��۟}�>zm��ۉ2���?x�u��F�H�H�tWF�B�-��D������p�;m��մl�����L
�k!�2�tt�J�f�@Ρ�4�mߞ�/,%�[=N�W�`6uwp���o�:��GX��Z�z�Gq��!�y�r�EU�F�R�:������6w��Ώi]wQ4��(�7J�2����|�W�r��S��]��c2��
�cz�h[���<�>��&�G����ө�����k?�)���X�e3��f�WL�M��j��ԁ��;�u�,`�.�_t�K��V[b�����*�1Q�üuQk��}�`7:�4+O��z�:*SS'�V �W?��3�O,4[B�D�Gw��N�:�x*�B�Ƙ�u�g����#��dw@��bA��� ��ɉ�j��RdC)n�Wd�zŝ�y뵺k�9	Ҝ#�����vX箍ҍ��PKF.�B�'dR�vf�s��`B ����F�� I&�LU��֟���B��Τ.W<ƕ���Ә�V���*���%���ʘ-�}�'K�����5��hİ�����L��'��
6նC�-j�fJ�^��כ����{��.�yAi*L�ltm��>l8��)x���{�Ez��o��t[4}+��e�cYt�ؼ��I��
�Kk��ߖ(5�ك�KY�;�)�xt:n(yA�֫��o�Mk���VYk���K��JPF9$installation/framework/view/view.php��C���;ks�Ƶ��_�n8%�R��3�9El�э,id��^G�K5�b=���<��)�i���L{��ٳ�}�~W�����_��"8}s<��r%�J*)J����$τ�ʤ��2/�u}���*��JD�+��|��:�k�,6o��ik�2)`4��B�M"�]�er���K�kM�=}��gO��^�&�*OC%~��W@Ѓʋ�Ns%�'UL��$��B�oNߋ72�e���ĉ����}�~*`K)l����A,�I&��h�8��&'r<y>|�� M�WY�N��2/dY%R]
"�N�௉��k��L�r��+E����q���܆�@F�e��o{E�W2B�4�Hdu���煼IP0�<���!�J�-5(��7EX�����2�nz���s�ݥ�o�:���ֽ�(���8��I���qxw2S�t$F�����@R@F>�P�f����@q�\��z�^�ڜU��S�����y	rI���eOc���4	�$0�&������F��b��)����#X�
10�S'�I*gM�62�r��4`XQWfA�Y�]��H�+"
����2�Dy�Lnj��;1�glV�$�=\���c�b��`+@�E,x�gU�
�C��}�pF��rh&�?��k5ʘ~eF�����X �ǀ n2Ià��&q�����������������'��ķ`b�eN�*wo�d�<U/��d�E���b���:�m�5U�%MY�"�v[�*�!��Y�F������ƣ|]�bX�i,��nDZ࿖�^�u�@�ݍ��8 j�rr7j��4��K
ÑaQ[�`0u�#/x���f���y��D���;�򓧞N���ժ��|
��%A�;�vP���`��R�XX]w�T��
x�
/��8Q|=v���w���xV���FV�@P�Ei�GKZF����V�D��Q�6�Ʀ��:3�(�߽����px��,ŘX��(��05�я����C{�_~0`W8��{C�‘�>G�O�o@��a���I���3��f��\�G#r?����o���37!�}y��1��_f?�:G�I�ݡ�i|����5�	�Q����x�?��c��6]>��Y��}��k��.D��#d�������ś��6����n�0Y��<�?���W�e�
�J^?xY�ZS���&����&n�758.X���NN���S1���۫��Z�Ѝl9 G"6�Ϲ�
{��+�R:5EϪ�
�!#�^x�	n;�&N�"jq��~����`MN���`�h��A�1����"��$�c��yG�8$}l�qD��9RظM�"�@�����9[iw�l���s�����LZ;���W��mk�XQʛE)Ax���>�O���O����i�u����zn���2LS,*	���?����9ՃI�L��lU�S���;�%���_�q�sC�x
��[�Xwޗ��!>�!h"���K��(�q�aZs����$÷�':٧����=H.x>LӼ��gޜRVu��9�01/=3Ya+�c�1�A�ޞƄ\T���0�Va�d*槗��go�<���_��Ө�鷲Z�1R	lqX�bY�k�n�*Pa	-�j�Cm�0�s#���v�NM`��o�J���L݆%�9�Y����R.a��f��_m="���4v�Ḁ���Q��d]��bȴ&��ĝ�[o#�c�u=�ٙ���=�Ko��L~Vy��A�n�&�5�dH�WtX�?b�]J�ӥ��Z*,b�A��bc���^����>�s�"��H��:Ic_`t������\��it,g3��_��Z�;��Kc�	��c����RK8Iu±����UX�ђ,�J�X������u�un���۪螮�JS�"�ڠ[.eި9���2��BP�W,�A�	�>�ZvM�E�
S����B���4~��6�t�S��ߠv�M�m�b[i���n���u��O�B�zT��|J�B(�$ƪ��)��Z!��$b9���������8���@ x�N��M[����9�k��1�c6r�C'����a��ל��]����=30]N��\�a��Ǚ6�t��
b�AG 
RҳR�	����m��Ky_.�#�.���㗔,-�g����x3�\�o��T���Ӊ38�D�S(U_�S�^K���&Ϋ��Lg�������F�fB�
�PQ�%f�k��H�����$�:z{��F!�[%���VZXJ#P���i�f|�<,iH�Oas�u��DZ׵l�r��)Q]��]Wxu��ׅ�L��)lTּX#1‚�2�&��b�Fn�Σ���M�1�&�h�b�~���!|��Q5��0�D��k�[�n�j�g5X���5����K�/6ې�R�&y��؝A�"la�l�l�ј��3GQ�G]'A�5f�߂M8M���`�m��v,���<���I%D�$���)�8������/R8�1,?5��	묌S>|se�2̪�vG�=��2��Wb����c>'KRn�!Lٝ'J�^�����y���6�n�Sn�Ǘ����
�Ѥ�S��(���	Y��W:�j�f�ni��3�·Ƕo�*����Ri*)T����g���,���Ty�י��˦	�	gϙ��I�Y�� �h}s�J�+L�3~ jg���y�@��5�O�0F��Mr+a���1#
�
%ʄvl�
�#�����s�(���(�Fa��*`�*T+��۰L�kg)@�ː���4�����41m��Y�n��&�Ʀ_
\����*���N�=#ߒn)��t��{l��͵S�.8���6v}`���%=j�n����l i�w�¼���ȆB�eC����X�=�]sM�hh3~�KA/�3ąP�5���]�~GJ~=̟���!���D"#~�ٖ
X���ok��b-��Nj*|���*�w�x��/ a�'y��Bό���I,�\jj�D������O�Zچ{*��пX�P�dYi�9�u�L��P�"����
F屢C4�U�L�Xu��1h6��=�p�5����U�*|8<Dt��n	�J����F�U���t��3þ�B�i��}�9�?2?��6�����C!� 
#$yd��T	�J��J`�t�^���_t-./@�>���D�<<'�/�}es��ƻ
�RDaN]��`"�k��4�^(e�O%�,J�ݍ+C7\/�|]7��)��KR,@˨���e��MMf�M�N�Px��L*�~r��Ro	k�zj���^3?��wa�T-�
Ɍ�ۇ�2�853��Vi�1��|��:�
8�j��Ft���������r���+�ѧ���L�V���{�!`���
��@56�u$�C.���݂#�dN����#!�*�Ni�!LH"�-:������"�ԲN�"��]�p�U�Y\~�jƌ��F�����TN6Z$,q~�����A�m6��E�I�����_3���[Qm����8��,+������K6��J76ѭ�0C|KD�j����^����2Z�q?�<�K'#}�O�[淠B����
�X'��䞶�ABg��;(Jt�����n��il:��-~���LE2�3A~�@)
�"��>j:O�(�c���,j=�Z�m�̈́C������ :vERF�!$��Y��v�5�̘���o��7��N[�콑-�7P~r���>p'��0�	���ީ��Z�N��۷�4Yxl.����M;����[𹷤<����r~[
�)�;%������BH�rQ3�Y&��l�tPDZ��d��)X<��|`���xƈ�CpQ^g:��I��Ex���!k�H�z�Lܤ��L@"���?>Xt�4ѹ�1����N�h��&ɻ���p�J���4�?����*_�U�W�:(ih-�YL�j�t
��I�xK�o�}�ޘ�k�MyIf�JoH���L�^���C�M���/���F.�K�W.G�1��+>�/��;�6ٓ��%m}�n���B'
���z�W}A'�l�1R��#bܼ�>�Ӷ�u��F>^�Q�r+y�=`?^j�z�͞nnhc���?O�b����z�FWtzl��h;��$�k���� 9��R�J�w딿���	�c�-̰z(t�~�J9,�	O��@g9f�uxS�'��Ɔ�m�.�t�^��_E8Eҥ��O]R��U��,�;�Nj��3�Y̕Xb�I��O7 �e�$/���W^!�$$W��%�	��&6Rc|E�]�O��	_�5:z(M'�A�E�ޅ�"�wO�o&����H�ʺ j���6"o� w؇ƽ�ۿa
�rƚ־K������ݹ}��:�h��vw���-���/���|���5��{�Fߕ�p��b�`1e[���4�W�F�W�*���<
��z�#��y7/�����}g��:&Yb��7<��' �b����Sd*8�	�0��8�����2LR�)��������\��S�������˳��/��σ�~6o`�2���T�z�5k����S�t4ִo�
v��M��jG�_RPb#'r�\�7B���[�:4�wx]�q��frl.��'�(�[���n˙�-ڭ(�I����f�1�l����C�撎��j ���gƚ�%�m��:�l���4t6}0�(��4�7JPF7"installation/framework/log/log.phph����TaK�@����Q
I��U9�ԓ3j�K*�r��f�.�d��xW��7���WO���{of���E.�����a�d0��>LZ��T�Q����@V)�3�PK`�/�j�
���K��.D��D��̹�,���@�\*�/\��B�z����#H_T�pӁ+jh�+Y�E���RcdR�U������X�b��sJ��I>��v��6�H
����3Qb���߿���R�at�"w�g�l4���xD���V��ٶ��H�?�o�$&��$O��}���b�|�����Ž+q`Ͼ���(�
p�UC<�rۿ\�
-�CV���,l��EZ��5-0�=�{"�MO�iM12�sO��UI}y��ң
�Ј�q��n��;�	�e������.8��{���f6�nq_�~��r{�ܬ�=���ӭ�=����LVfKde�?*k7[F�R�eN�1�{����n
�'�D�&�VAK"!g����nç6@wށ�}u`S��@QW��
e�8��?�]��8	���i�V&��yVI,�6�^�;w�x4���qb��E�w�/C"[ui�2w�� c��u�g�6�&�����)��G�65�<Ӂ�JPFE0installation/framework/vendor-fixed/autoload.php1���=P]o�@|�W̛��Z�&�jcL�i��L�c�����&��}�������*����Xw�7L�]��M�Jͬ�5�BY��F��Q`���B\��#�b}&�^{F��6_��Σ�Y)���G(u;.�U����ܧ!�A0��A�(x)+f�>��=t5Rɦ����`�N��j���?�QM�U�h2�ЃҦ�=�E�\�}��}�ƺwY>u�`Ut}���U�8����RYsB�n��i�)>�)�M&u�T��`ٞ�F�w�u�!��]����D3
�,�(ȣ$LN�#��I��x�(�:�p��JPFYDinstallation/framework/vendor-fixed/composer/autoload_namespaces.php���mPMk�@��x��T#z*m�V�HE
m�a��&�1��n���ND[(=�03�k�|�U:*��o�k��Q����h]�`��/�ȵ9��MeO`�t�y�Ł(�x�\4�u��j'����V��Ai�<3�wl�*b��
L2�L�F��t��5��u��+1��][���ř������o���PC�k���,��,Oġ�5��D�%8U*M��(vu�5�HA�R˟0/�\��Kw�.Lݜ�)�,���6��,{���Y�$��&ׁ�?�[9���*��r#_g�
�L�JPFI4installation/framework/vendor-fixed/composer/LICENSEr.��]QKo�0��W�zj��{ߛIL�n�#ǔ�hC�
1�͢���	�ݮ)�<��dE8�M��'xl�@�!��t��Ï0u�x�vYVc����>B�&���d���s�v:�R;���M�>Y?��Z$�p2��!]��p�ch�E<�B{9�1�D|?���w���7�f���!�#P�W��pI0��&�F~l�KG�ۃ?�;��I�A/��N�������|�s�<A�/	����i}|D7"x�={�T7ϐ�3��E�\�p��#:\�)]Gʺ��͌�]�h��a•��a�<9�߳�`���7{�z	��$�ΟW��bo����ؙ�>&<����4�o��W�4[�9�j�^E�Kx`
�r�
�R8��4;PK`r?�,s�j͛��ĺ�ǚ�E�)�|��Ie�ka�( �;��
���.V�dQ	�˳�0�0�J��i#�M�4�]��#}��RȥF���<#+ր����*����k���wZ���TUr,.8*c��ߨ�TQ1�Ρdk���-�(:���:خ8����W�$�(�4�9���cu+�Ӣ�@�Z����
5���7��\G�i� ��U���_·�JPFQ<installation/framework/vendor-fixed/composer/ClassLoader.php�&9���ko�8�{~��vϱ��|��/7u��e� I�8$�!K���,
��4����%�����_N@QG����/^'�dg���yJ�'�&d�\,)�aFIJy�R/YL���IF�,%3Ͽ��2����)�2��=�P:��[�"�s1��y���ނ!�|�ܧ�b����S��=����l��wr�Ky��s@�A��%,�'C��q\Q�Ә#�'���ԋ�i>�/ȱ�����O��Hx��@$ː�yQ�'^�6�V	�4(N�@�+�d�t��1y{���a�W���4�[�X�^�ȋf��N�`0Kq�{�)5��("�lP���0]��r�$���L��N�NN�'�J0�-��܁�gi8�Q_wa��o�A��ԧ�Q@R
���E���yJ�m0��~̐S����3N<rz~�7���>�\y��@"�ňϮ|E^�X�jlze�����!���h�,RpV��k����9�_���^1P�������t:�����
P�S��S���7�:�����`oBQ��OX_u�U�݌{3P+��	��~������@x��d|F���^cN�ϜI�S��fiN�-�bi!�
�pN�YN�����<��0T4ĊhJs��-zS��GAJc�m����3`�c�y�O{}�Vh�d�<�b7�H��׈�5�!"	”����B�L⁅1�{Q�QYn��9,�P1��lBf6�dޢB�D��%JDh�`��=Q.�*2#���֮Ѽ����{o��,��k/���.���W&�fQ��2�2��|8���@r؛��KÄ��oo4�f��P�ɾ���v<I�+���8`�0���\D��$����1�ْ��t⅗�޽��J��~�V�m�I�Ȇ�"�hSܣ�be^q@X����	�!Ե�^0j3H
�ԁlrVz��������F2G�>��1�R��ɤL��<�E1����c��[j�>�����!�ƞ�W�2�Rp�w�6��*%u�X�q�O�*��jS������fy�9M�S�d*���+�.($�׭尭�A�Zn�P�A;_h�
����ڸ��7�qݶ�A��u�lC�L����
:Q��#*S�R��	�
���LF��Q0C+IP$հٌ�F^�k_k�VW�[�F(�V��U	I)�eu�<�=�!0��@Y$��
fb3"$,i�}B�6�jG���&,�%�/�x!]s|}��C�ĝdz��8P�jJ�2�(N�R"Z�٦�o
h+(N�r�U�Xf�V�1�rU|�?�yEf�+d���iE�]-6IS�D��5�)��!	[S���p%.ep
0|���4��5􍵆��c��$�j���[+�;�?e���]Y����y9�>���4�l��R�^_*��F�����v�F�I�˒6�y����i�=*@��"zMV�XW�ax���N�1#q��xhP%O%R/��su��*T�����[�L�'WG1�{a0N9L��4����E��zm
�#3"��Ue��>5��԰�ȩa#r������b����ש�Gh=�F�yA��gm�5J�2t=��e��4����f#�#��)���I��=�})U�`�C.D��3��8E��8jh�qHpY��J0W���4aɘM�����w7t!!J�ܓ��v갍��K�
�[%U`x��������ֶHÃ}D���͋�	Ü�
�v�O�8r�=��xVMlۤ1wY��6�������A��cVW����TE���*�q����x��&V�t��t��#pδ�!�$O��D�_;So3����8�n��\nj���fgg����7hJ��V�'�
h����E���V��֟�nƦ��|^S�2d�s�z�TiTb|��n)N�@vR��Aͱ��o�%k-�:'i3�CP׎�a.����K��K�GA1Ggx�b�\�#�,R�b�<M!���y�P	�]��o���0�ơF�h잏X�j��I��}��Я��$T
ǃ������W����]ʡ�r��
˔~���~;���_vz��_1��!Oo�����Q�(>:�>yt|19�~�_L�o?}:��Oz��y.�hN�A�B#!��/B K�5��+�9ũTmȳ-�2\o7����KY��
I� �EE�F���#��pֿ���F�O��F=-���O��'�N8*/d�'C=�����XԆ��3f{,٢a�4�?�f�ۜϾ�d��ߜ�-�Wc����oy]�u6$Jd���8����&��坞uFa�s����l`? _�|V�*�ݑ�F\��sw�bt.2�%F�l^�t[���\���2��n�.��z!K�ŭ�Yqy���<����FC4�k�(�K�g��n��{�MJU~�*RtR��� Z�-Io!;»�d��Db@8"�e��P�u��$>���*m*ڨ�����R�j��+��9�ZW�}�F*4ToѴQb��i�[w_�Ĩ���&�*�ۀHR i,��ҳ�ז�T��p]W�6����.Ⱥ�����>��(�?b �:$�b+�x�DZI����/TD&-Od>ATR�	�\<�29;?�t�i�F��{�����l�8�01�w�K
�^5��C�]��}G蘲��a�������ڭ�TW���4[���>�hG�ʡo�j
P�*o��Z�%|�y��R&$�Qt���.�m�H�Z3KҳztU��}5I]�kD<�i�B�b	��w#�}/Ž[�hAh���N��:}���lrx���_����l{d v:�8��y �,3��A�j7���3}"Wc���NY�pX��2.yI�X���4v���8������'!e�yY��}�KR(O�$����0OND`�	�i��dJ��,L���I#-�ay�4K�:�迺;-b#���(6�|�ǝ:������k�)�<D*�5u5N��i��]�u%Z��er�7�m���BI�}�``��	_T}����𲆥��%�P�T|r۹r����>`ğ7�E�
݆�j�tm^�M�v�z맡��V��a�l�Ѫm��D1옺ף
�ǖD �G��D�^u���"��ad�3#�]Z� ��*6le@)����j
��n�T��&bL�F���fi�F�w��/;�Y�?�c��}��[� j�[�{3ߧ��f����l�!�b^�`y����ՏR����w�JPFS>installation/framework/vendor-fixed/composer/autoload_psr4.php���mP�N�@��W�J��b�"!&��n�vC���nI��n��x�ɛy�͛�'W9��F#,��c|T����2hk�k�e�T��A���<�T o�<�ϗ
S\�w[����,\�ʒ��Pj�1e]˺�V?�P%���n<���8hU�Zz�N����:���#�z�C�k�Z���-bY������L�\��Hu���
���M���"s�o'�CX���%�ʞ��� g2��f<��l䉆ךe/��&˒�^r����W3I:�`

��o��PD�JPFWBinstallation/framework/vendor-fixed/composer/autoload_classmap.phpB��mPMk�@��xA#jDO��ժ�H�'!l6�d1f�ݍ�ߍM��4�̛�17w:�,��X�7�5�x�V:�!��N�V�ޕA�ű��Fd�L�w� ��8��
�H�ɳʕרe��oyJ���.3�ted�9,���&���p2�L��"S9�xa�
UViU��"l5v.�p�RPak��*��Oe��5�3[��#�>���!ca^:o�'����	����$]��V��Ι�D��4�!���'�5���u�5�������3�Sfȕ��?7��1
���-��yN��g(���?|��
E���t��2JPFT?installation/framework/vendor-fixed/composer/platform_check.phpH����Smo�0��_q�&9 R�}�:��6�h�I�4E�9��N�Z��w!��iZ$9�=�����4w��mL���a�"Q h4��Q!�õ�X+
��o�"�Sq��ƨ�V[lW|�;d��,T�h�]s��i� 5$�W�V�$-���V�����)LOU�����2S���"��2�Q�?��%�(�Y��ׇ����u����M�]��v!�Wz���#��"�`j���6W�&��˜�<�:ں�s�kp_���Y�%�/F�I8��}x�~�?����4˾�R7�c�2F�!k�+�F���G�;�#h��S\��B&�������<v�<��{�4*�)F1�e�6�;q�r9�x'$‡�$�$y�@M� �ZiF������"L�pY,y�
�6�l��l���}2�g���<�R
�*a���Y��tU���*��;�L,�ۼꄄ��uyM�t���T�y�bt�J�^tׂ+р���xi�[��$M�C����e/��(zr�>�e�2-�o��ޥ�6-�����{"��N�7�`R��yUl٫�JPFU@installation/framework/vendor-fixed/composer/autoload_static.php����RQo�0~ϯ��J@$�[�2Z!4��u�R�8�`�đ�TbU��.&DH�4�/������c�/��ځkXlW���=�%�FS*�J�r0\���Ti��
`���3�Y�	�GX<!�>5yr�<*�hF=殠,��
2�6�Uq�"ۗ�l_]�{�l0���
�W��<�{"t4�P�T��M��^Rp�M���+�Q3	_���i�ϨM�k��$I�&��8��*�.K"S�x>$��.�}�Ku(�!���R�ml�h���c�fm�uN��t�#�x�/���0
�B�Kf���}(N�O4�*��!�5;Bזԧ�}�OB楌��i0�(I$a�7�E��oQC��a���,S�@WӲ�!�D���q��w�vT����j�Š������9�K��ϓ�毜x�q����KZ�����f�������)nh%��Jڻg�/-U�e�s�-�KU��X�I�m��AE��{F_ �ӄ�f�������F^}^��WR��B�|n�[�:�JPFS>installation/framework/vendor-fixed/composer/autoload_real.php����V]S�8}����xj�	�%�.�
[
2�Rv�R:žI48�+��[��^�q�4��V�cݏs�����{:K���p|y><��s��D� Qe��HbP�i�D˜wy
\3q�
�<��8�Csx]F�a��&��ۼJi�O��ͻ IRLg��On���Ξ�׃K̒�+�cN	�B%i�G��n�c���V$���~��c��Gp��iF��=J�y�:@�"" )�kY�.�<#�<dD,�%����T)Y�$�4Q�`�Hտ��<�Ø��?������{�����d���o�kDH��� 2:�(��*&���$��:�D��`�68<�RT�CL�u*l���F�����F�?�B"0v:�fv��6R�,��:�*>͗���\�p�G���y�Y�v�8�"�"2
���a��ל���&k�5��LB���`��]ɿ�Pi���
E�r��|�\�����Zi��@&s,>�%�J0��S�?��{
�9.{3�1�y�M�y�Iz
a�\��¨�tuq�ޟ]��]��)
���}x��B��C׹�x���r<=�nUfb�AW���`�DD�0�CJ����5�6e�k����TKY8�m(=E�JSwB�[��M�{?<�����g}x�{�A�0�3r��=�)����6���U�|Ѓ6�`Fj�Jt��u8��Ny6kgw�f�2�Sf�6y��:U���J?�9��>����5؍'�:A��z��h\��P���a�8�����RD��8�֛U=��V�Z��k�U���Ԟщ��LL�>���Wm���u�1�n�n�������z+�݃E������h��<��}>z��xt�a��V5u>~hU��5ũV�5���U��Y�t��JPFP;installation/framework/vendor-fixed/composer/installed.json�����VMo�@��W�8�iY0
qm�U{��*��[Ekv0���vvqB��.vcĕ�,�|��̼��g�]�f�=ۀ��9���]�#Y��|�m� �\w�*4���C�-�J�ۈ�t��
��'�s�Kþ�Q%&5�CX��V���}��ļ�f�j�畕k�`#\�H"�dû��L�E�X�Dl�yz�z>�fI��9|���"�.f�A��-.��&�$�	���1�����\�<N���j1��d̔E�e�R�J�#�3]g��9�n�NJ�s؎&+��A����cz>�ؔ���v£�s�6����
�Vp0��Bq@I��U���،Y�0I�@J���
H@f���$�ʂ�Ӑ�hE�Q���'�����r�?���Hf��sP�a�7i��f�p��$�)Ҫ\1>\�T�us`v��f�֭=r��d-��HZ�=�����
7W�i�>�\$ 
B�~^]b�=&�)&x\�t�_W�hɕ=�(l�|�P�0�d�
j��`��i )Q���^��B�T�m��
�<�{�$�:A�m��W?V$��D��r-�I���߻f~߽'���HH�Rt�]G�M���A!����r3�cv��(�j.��2�ڔZ+S��5B��Ͷx˜���?���.hh��Ezz�����9fu^J���MW6�ݼ)�
�=�?!~-��÷g/gJPFT?installation/framework/vendor-fixed/composer/autoload_files.php_��m��N�0��s�Ԧ���4t�aa9T��B���=M,��;���L��E������/��ʫt0P0����N�"60���u
��GX9��G�YWvC4F2Pl��@���h�!��j'�͍�*� 
��]N;�e[V�^}��G�?'��xK�+Wc��!��B��kk =x,��i�VS:�����c
/m!X���qM�A�j`N�JS�6ʺh�)�DpS�d��wn�]�	u���8��W`,7���!����!ϓ�B���f�t��)����}%\Л��S=��h��>�γ��";5+���eg=���o�
��zd,]c)e��ε``Lk[�3j�X��'JPFWBinstallation/framework/vendor-fixed/composer/InstalledVersions.php�����X[O�H~ϯ8[!�A�	4ea�@��E��}�(�������F��=3�L��**���}�~�#�ō��f6���w�
�����$�"
Aq)b
~$a�u�|&��Kd=�,��5�Q�z��OQ���3&)�")L�w<��RLg��O.o�:��v��ہs�gQ��Ղrh��8J�HA;�8Ӟ�
�P����C�,��dB8˄��ĵ�R@H2n7!��"�<�ǑB��h$�|�z�h��yW��:�OFgE�Ή��sJr�d
c������,��tT�F,ł�hruC�:p@ٖl	n��Q�88z(_� ���r��fU�s&Bg+�Y+d�`
U�
�̔$�(1�b�vq��'��c�=6��A�v{w�������7�d�lm�VjrUm�+6�n>.d�.�i���e$�k�$�F�������cN���\�Z�VwxO����A���>��
x�
=���d������`�7��j����9OQF�Y���b���N>n3��F��vf�'!�;k����t�(�I���� UŢՆ��U�o�T��&��)]iB�r����q��R���|#O�n�GI���i@�����]��’~��W��ƴ�؄>N#rR�9�)MTx2W��N�2���Fפ21�Pߓ��*���-4Ζ�Y�p�uA(zT����lb��̊a�'N�hIk@�.)�™���{\����}�-�g���6E�2�>�F#7W2m��h�t�p�4��m�o'c=�����J>k�EmlH�Gu��*��w`J��ڹ|�qϸR���Ee+�֣���K�N���}�e���z�$��[���\A�λ���wq�����8�<t���S)j��g2��o��C�?�G��=����̒�d_'�ʁ���G�mO#
�N9�N���
�����$��~"�˕��.�_��+�jR�_�4�3�W��*R{�b�54�M���j]���p̅vT9Ĭ!e7'L3K���T%����:L��*�nx�j@o��3���ߑ��GR�[W�����t��{!��W�C�S����,�׵����7ǎϔ��s����|�
�Xƻ��*�Bc~�$���5>�\7+[�ܗ���6;��3*zm��	HW��m싀vH!n9m��]���Y�<���$������R�Z��Jm�JPFO:installation/framework/vendor-fixed/composer/installed.php�����R�n�0}�+�-M�p�uo݋�j��Rw����*�-ۉ�߯C��Vٛ�/�9g�0o��F�!�3���,���޾�����ߓ�Bɞ2�h?ߢ���#� �-�Q���t �ɐ�t��b*�,)y�#�1M1K�f��iY-)f	ET^��C'>������a�О�������?l��.^�L����q�
n�����s���)�-�E\�"�\D�2I��5b�UUg)�y�ba��E��"eI�4�L/�p6`W���÷��
G�ЭS�,7B;�����N��x#�d�BG�\�����ʝj��8�|��
����q�;#6��O�og|ʢ����-a-x�Z��u��@�UZ�Ze!=n\�k������;�H��nw��f���s�Z���0�JPFePinstallation/framework/vendor-fixed/paragonie/random_compat/phpunit-autoload.phpe8��u�KO�0��é�&j��R�J���Kc5���*�g5ܰ|�vgg���E�Q��F��j6�ޠ�EJ`�88��u9�x[�9��6����&��0�t��;\m���{E�j*�.s��ZsYHW�	��R]3��y�NF�����7HC̬I]��}��C;v�+3Lj�aU{e�PΕ�d��	��u�y�����\q
!H�x����{i=)��R��R���mO�AP�/��N�&�Ejrs2Ĭ��Nj~7�m��'��p��c	7
u�>�ߨ��>�L�o��"Q�7O��Ǚ��k�d�$|G��Jr`*3E_��[���:�Xu�Eu!%a)6=N�v�[M�D?�/JPFXCinstallation/framework/vendor-fixed/paragonie/random_compat/LICENSE�J��]RK��0��W�8�J�!�қ	f���1K9��W!F�)�ߙ��Ќ�{�����@�Z;FX<2���kp�>�C�_?�U��A�.�&��Ęl8md����bt8�"�6�+C�3]�`-��}�6��_�lCD�ߧƍn<B-J3�L=�DH�&Xѷ(m;�|{9�1���`#<$�2����$��f`nz{{��K��$6��Z����p������N�@�i�!�%b��w�@�v�u���:G�K�f��ތr|���}OY?�M3d�LM�E�\{�7���p	#J�	�y\٤�˶�:4~�����c�(Q�Θ��f���G�����q�S�a���/uq��_q�DŽ�w�g&��c>��J@��f˵YC�Ջ\��x��,��4+�1���fj	��Y.2?+-��fr]R`O�y�Y���+~�?i$5
H�N%EMdk���|.iv[JS�Ri�Pqmd�)��j�+U�_ m)˥F��yBU�x��/
�b|��5��\U;-�WV�Xl�:��Bܤ0T^p��`��YL(�,����lW�Z����J����h,3L��;t+k�ײ��,�Zg�։5� �7Z5�s�zS�wBX^ WM`��6��JPFoZinstallation/framework/vendor-fixed/paragonie/random_compat/dist/random_compat.phar.pubkey����eι�0@ў���0F�2	�@�QFCddq���]Zoy���(�\V1
8� �O�BO>�H�o��1#�t|�0n˂(�d��}�Mj,�*�*��;t�G���]����6ܱL�{{Yq�b��*;J������i�LӺ��f��
�*g��[w��T��M�l��@�\�AA��7JPFs^installation/framework/vendor-fixed/paragonie/random_compat/dist/random_compat.phar.pubkey.asc����mѹ��@�ᜧ0�-��C�j��nP�����9���wg��?����Y΂ ��,�?p�^2a�5Ү��@�D�b?�Q\��Ӷ�V�o��G���H?��=*:�)q~b<f���u+\2���n4+�=s|�7X[K\�pٵ�ϭi�t3W�޷��V�Uu�!m�m�[��P��^_2�θ�}
��L��b3�3���̚Vxl
)��Md����I�Bc���EN_y!Y㘺ǹ����*�\{��.����	u�4;H^ź�C�+T4���
7�71`�K�{�sg��H]��������"��,r�$�
}���K���CN�Vv!cc$L
��}0h`jH`�8<�*l��J�O�Pܬ����=O��ɠJ�̧��k��:�$�JPFoZinstallation/framework/vendor-fixed/paragonie/random_compat/lib/random_bytes_libsodium.php�����VmO�H�ί�CHI�@x�уr�I���	�*Ukgo�x��5it������ܝ�<;��3��w��$�����>8�[υ�$�0�F*f��@GJ�b� dѷ"��D<q
����)�Kp�q2��4�i-�T�r�!�S6��0��"�/��%����Q�������S�(�)���zHh�e.�Tj��>fj�R�L���ny�KaT�x���+Mq��CJ1�Ɲ�:���/��g�c
B�
�D�P1�$
JE�E6�	�F����-��"��7�+��GFWI��y��&���������,
���Sl���L���8�2ϕм�q5�'4$\q,�Ya��+�A�%L�x��*,!�t��
�ĀJBx�lD�26��-+�ZF–}*�b�3S�I,Rl�&�dw\Y춬�)g)bN�>��0�,�m7%"�i�R�SbR�b.*'dn3D1t�1"܆����~��/���
SA�aaP�m/P��M+�yj�!��l�/�9�)��J�&�"���x�e*C�ܚM%���& 	Y�2M�b�d6��XkJ���hd� �
U$�tu����*}�\d�F�:2E4��~8�T��vć5��c�f��.xc�'��`��n�I߿�j�p��
��G��
{mp��x~@h��h�(���}���5�}�Gq'��Y�y���ܠ��W��x��6a�x�!!�80r��׽8�?v�D����&@G�;��c���	_`�w�f��=�Q����O��z.
�]��\��F�8�]zΝs�Z+�l��Y҄��KR���_w�C���'��1�`��~��n��Sfn��FJ�E#����-�(�Bz��+L����d\�Z��+O��E�/�	S�lT;,\�������j�YvK*B��V����
�T�xRɦ�p��͇
87eG�‘h�j4-ӂ�����~��zQ�~P<�Ę\_t:3�Ex�y�+�Y��qg�|�B��q<��0��M��B��=�9q�>W��-�lVI;�7.2��`=���K�j��Ǩ��[�d������~*�U��*�P_P�����˕�3D�D	4'˜�J����[[nm��+�Rkn(г���E�d^hC[�eĚϸjlخ��Y�KmW����	�Ff���IV�g���i��~�g>�m�|.��ľ�E�l��:kZ��i.q�[/'�g�goO;;�2\�!_ǜ�ۇ��dF��IFl�٭^g�SY�����L1�E�����?�Hl�К]A�q�!��KsO���%�ﻪ����+8>:?=?;~{r�
hA34���I9����O��RXu^��9�z�B{Yk��y��y�����$�e�U�?��fw�N-=4	���NR�aTNyV*���U�����Z?��j�v:n�]��� ��N�v3��Mq]��;׆�ދ�����|J��1Z�xوc	� V��̰�����JPFlWinstallation/framework/vendor-fixed/paragonie/random_compat/lib/random_bytes_mcrypt.php�:���VmS�8�ί��:%aBBhI)W���O�8�8��E�uu,�$C3w��+�!�י�@lI��<���>}.��w||�`��N�O(�H�����"I^hH��0���2J�SIhC��ca7�D73�	�An�\
VV���H�W�����NO?�������R�
�ta��6J�̄�^�c�cc+��ٿ�-��L��`R/>1��׻ �	HT�4���X?#�u�!y��C�
IP(J��hXΞa>�����5w��f�
)�b�Vu(�S���i�]��E�N��|]�<��
A;9�A=1�sD^H�Xcz��+C�+H�d����u ���H J�b����Á
"�ωS����:EKJ$�9�̤5PJDܤ=Q�f���$��D�Br��5��Ô�bLh�Y�g�SQjSn�Gd��BQVƄ�Y���NH�D�8��R!܁��yBof��v�v �d=,5N*S���a&�84‘�!��ш������p)�yN�z�7��R���X`��_*�!�Dd�x&���cN���N����l�\hD]A��/���Td��:�9Y�ن�$Jc=p�����k���؆�{��[�
����#8�8>��ݥ(�Y3��[l%�ř�:`�9���\��9�ıqڙ
'ˑ3��T����h�w��ښc/����cZ7���:d���gd�������p9�<�/�����-Ϝ٭���=����X��Ʉ���D��;𜻱cw2�q��F|��Į�!���r�YS��6Z.2$I��	�c�fɫ�C�qg�g��|���[�{gaw��E��s��)E�\cUgve�"�� ��raom�ȶ&hnA�5�F�[�<�@�7�#,� �X��a�F3u�n���M��Fz�Ɉgl,��ﺷ��O�VU�A�-)�lc��Zm׺��k�H�.�e��+�ų��3M��������j��-�a7�����Q/�D؋�I�����g�A?�.�88ˆ]��,|�^�ׯn�����׈�k�Rޘм^ש�
��+hW��XcWjD����wR��n�[��*pU
��r�3��O�܁�n�z��>تS�埂����V�D��Rh����Rb�xþ�_�5t�ɷk�	гWK��e�d]*M�*�	5[1y�����`�I�Z��_��	�W:�:_���45���6�U�	�ȐR��܌�ສ����ȟZ-�ڮ#Ёj4zs�qd}\b�����wn��s���b�����^�Q1cy�WWW{UL�.�}�x-�*X���T��_�	h4�H�}���~%֛�P�*�/�xd$�LW�ˑ�w̅*ś`�]#Ƴ��#��k����ʣ!^ c::a��Gc��#��]��,��8�q�/JPFq\installation/framework/vendor-fixed/paragonie/random_compat/lib/random_bytes_dev_urandom.php�	B���XmO�H�ί�E�K�$��hg�aw1`mH"'�������pl��&����S�6ys������]�UOU��_�Yz�z����R�e[tL� f��PY"�,LbR�ӌ�D����\����ȓ�̈́O�%u�t^�re�D	d���R|u�����k^�.e8�et������?��O~�~�͒�U�{��Ph��4ɣDQ����|�+
=+�տ�+�F4�'�@��バ��A0)�ĭ���O�wo��<�&afK�O�+���]��0�R��bA��!�D���m
c���R��[x�*��>���O���rT|����߽GP��
]�N���Y�Y14Oe�D�z(�<TڸP�LH��L�bԠ@
AI@�̕SѠ,A��� H&��l�K����TdW
VW��u����"�L�a����KG�ᑖ�7b��	.��"�fI��t���l��E�Ϛ���pB�\{�mfֹ�)�p���_h�R��5���$ϰ�t.��lM�T"�ʁI��+�6��s��]�W�d�iO��
rC��d~�i����AEɂm���4u�� �$A�W�'�6�pD�U��Oj�FMD�>c�ƫ�e��P�!DY���r�-n�z\[4\��t��
��g�ku�3�a�����혰����_ip	(�J��n��k4�����a϶�l�/z�]�E� �P	6J|�-��f[#�wc9�x��={����.�q�9_�а���^ǡ�3�,(��ݿt Ⱥ���&c���x��u��ci�na�Ê��`�ձ���t=�u-,�[Яs޳�4Xw���7
�vn:W����6�w5�˵ū,�����=�=���k�:�'�/��jPDZG�Kgp�-e�h����oF��a�ߎ�'�Ե:=�1qak��i /���/�0~��w7w��ag|�X������Ԏ���*���mmЇ����P�yHAw�����2j�R�.?o�6� !S�M\%�P2����a(vj�⡕V��%m���	J����D�n��Aq(ԯt=.UЕ�_fKZ�]	\r(1�h�J�ߤh�e�:m��E�$O��h͗�L����8�C
��}���Ț���&Q2m��j��N޷��hy<�M,dž�qaߖ��ҝ���h�n�f2Y(��{"��.E�"�xZ��� �50�z��Fʑ�a�X��~{p%z�K8�M����Q�^=C����:��o
��<������}J�h�'s�+Dum�Jg���ٲ!O9V)��Fǵ��E�g��>]�~CԿ�F�Fr�~\q�����Qx/8�y�ī���\5�ch��e�ܕ�St\�$#�6�����l�51��;fPW(Vx�K�0\��DO��}�x�͐�����17'7C��֠�68�K�5�i<<�5��03h÷V:��F:�i��(:��1� %8���^�l�!9l�<�v�mOk�3�k;@́�nd�Ot���Ψ֪m�FI��7��nqm'��GW��
�^����?�2�yr"tq�:����O�F���ܘ�}��J2`�����NY�Kc�"� ���A�4�PY�Ǻ�=�`����Q�J�ML��$I�m`)�,g�V�T��
op�u�
�J���ŕ�]~2h�k��A%W(���'(����*Q*R���0Ȋڿ�o�~�S����m���ժ�x8�gD�yȍ��޴��+�;e����1�P��;Ժ���IB֏4��B�1�3�]_��`Q��fl*p�5�I5e����l7�Am�1`�U�Ǭ�,i>����t�Jj(C%�}���ԧ�W�Ym<�f���ЪrӊzU��XM\��ٞh �
�;�
b�S͛��
�#^��j�_Ul������v�S���j���Yq{`�����ai��#2&C����TXR�߽߷M_u��m�]ec������s�[���"���ڬl�(>ѻ�
�S�'�)&�R�T�I�*bj�F��!0f�	�?e����;5m=���� Ώ���u�v�–��&|X�I�G#��o���/+���L����"��1��)j���g�uN)_�E	��y�%x��j�t}vBz#ż��9+���8�Χ;�� �]��̷6�ěC�$��{�IƷ'�Č!Q_a����yS?y}+z޲
��B�M�
�5�ܺg���ȩk����>��U��h�Z �+).�&��P��}L��x��$��vI�ө�X�r�]�F�0��e��}{�&�;�L*�|��ۮ�����/���&,
�>��IQ��VK�7"̦&����/�ӂ�<3)�)�L��k��B���V �a<�2Bw�__��
�+�jkE��5��*���`W��?_O��Ѷ��N1ʕS�O�Tw*��5�l�B3�"�X3��\�A,}�guy�tZ3#�	'�N����V��H�ǃǃ�JPFjUinstallation/framework/vendor-fixed/paragonie/random_compat/lib/byte_safe_strings.php�����Xmo�6��_q
���8MQ,�֭�-'Bmː�f�d�2m��E���]���(�/��:t݆Al�ǻ��I��[6Ͷ껻[�v��u`�)�5ɔ2�\��b�3
c!a�_�D2��� �,�l�+��06�ภHG�J �2Ḣw�	@�	gf-ٕ䓩���[5���;<8|
]OE)��Mt�D&�PP/e����Jx�RE�O�}8a)�Q��7�]l^0�H���J	* �p}�4����l��xf�`����PF�(�s��	h4X�.�wڃ�@�'�<5�o ��3��*�@6��N'�bo�px��
:?���h��ݔk��.8)"�$W�d�crƕQ�+�2��1D�>�`,1�x�	�@��dh< �:�)����!��"'%�2�̸5RJ�ܸ}$��:��1O0$�d��8�]3rF,J�!ڄ��]��z*�ڄ��1���(N�#BRn'|�!t�X�t&�s��`fb���Ɍ~�]M-q�>�k\T&��iSGO*�pȄ�F�%FCF�22�.̥h�r*f�p�j<�)
f��H���\
Z�c�$�t�E:⤚z� �P�����
��s(�l��bKM�$�!+̇�yJ�h��L�18�E&��{S�ǩ��
�l�7���}r�N��������)|�����Rr�n���x>qs;�����m��M�{�x��a&���7�̂��į�S���n�
�-��r�.qny>�г��m�۶����A4�s��|�t�n���q
�O8���n�I��~}T�'���z�{r©�n:�x� >�����P�F�v;4�}�S22Je�NZ%�6�5B��>
��8�P]?\�>s��w�L�:FS�.�<�urFd�u!	����	M�n#�����y��c���jc�DÄU+y��K��&ai�V��[XT���#6�)U+����۞�!Y�R[P���YL�U���&L��VfC��t�?����^&"����װ)��0%����"�Y��ei��Hcr�
CɵN%�f�+�VY��ve���`)/��ѐ�
!�\Sk�\�¢���r�ߐ��MpSO��ak�$�޼�{e��x���hV��9�A>�눞Jq� �ʘ#��w�I����Rze���N�2f�+�u$�8��e��T7�nP�0�M�]���"��)��C쏌�0v�\H�����kK�[k��&y�.]���*�`�U��X���G����#S�1��y��Ě�!�Vgk��Y���_?\̩�˼��7���V�k5/����/rRo�3���%�DO�*2�[�ܙ�OL�[���ܲ7�e��������"�A�K,��\2��c�sD���v�R�Q5���+6"7��M�|������~-�˱��`����n��^�̢�)B�f���l�&v�m��ox�1���$*���@�\��
L꛰Ç�H�V��@���G�%|�羾a^s%X����		,y�0?�|����Q�C���fi|e��䣊B��ZT�&cK�������&+�R������1��$hc'/UX�-��E�6�i�E�k7����<�֖﹫~��\^V�e������h���V��V����$E�S�o7�
�oП���xWa�QA��h��-�+�A��;�C��U��.��뭿JPFvainstallation/framework/vendor-fixed/paragonie/random_compat/lib/random_bytes_libsodium_legacy.php�y���VmO�H�ί�CHI�@x�уr�I���1�*Ukgo�x��5iT��ofm�$-��YHawg��y�e��<�w:��;����s�„�����HŌ��X��T*�X��ȁ�8O\C�83|��o�G�+�lR�e*�����)�q�	n�b�/��%����q����σ���S�8�)���z��R�\��Щm��b�"�&���=��+�¨����W��:m��b
�;;5:/�_�џy�D"f��bjIDE�E6��e|���A՚�����7�+���FW$�w^�r���Vu��������[1�f贗	#Щ'n���Jh^C���m��8&f�^a��0U���B�05�m0����@&2�����P�$����,��6�Lk�����9�LY'S�bI4���q��۲v&�����q}
aY[nJ��F�8-&�I}�������e�b&�Bc(�p�r"���m|9�]'m�B�
����ߦh:�I�S�����#C9�k*�4�,9ߌGX�����0�j��Y�T�CS��rA1�2�
M_��$f��52i����H����H',M!�}h\d�F�ud����A`[�RY���~�]�7���a����ۃ]g���6<xa߿%g>����>z�^ܿG�;��w7x.n{����
o�U�>v��-���omVh�;&�;7�q�\{/|l֍	������^�~�0�F��E'z�<�7r��ax��q�O��q�Ț�~�F@�B�=�m?��?蹸y���-�at݁�ݵ���9�����I�����wi��:��
=H�t�a಍��J���mpoL�������E%����-���������
z�3@�1)W�����Sh�&�˄E)o6�-
׍V~��4;��D�Ւ�Hc�s;�w�=U
�T�	
�o`�a��MY�4p$��M˴�*<�6��^S���O?(>�Ę\_t:3�Et�y�+�^��qgC}o!����l���&Qr����|�>W�`�
�lV�v�����u�����(�Ϩ�ڪ"><1��Z
\��UuI�7��bim�r��13q�p�sW){�{kˬ
��b+��}u�l]Ԟ�mh갌��3�������������̀g3�����3D�"�q�_X�3}���/��E�Io4._��<���������-����&1�5�xOXGO����ޞ�qv%x7D|sf0��r��'��~��s�׿��	��KsO`DG����l���Wp|t~z~v���l[Ӓ��bm�UZh�E�'y��������g����+(I���f����B�Z���k��SD?v��Om�Y�8>��i^k�2��VNyV
���Um�TW��D_
�Պ�A���<�	@o����	>���j�̿�����G�f6������f��9�+�S|V�&̰�N��{�y�JPF_Jinstallation/framework/vendor-fixed/paragonie/random_compat/lib/random.php� ���Y{s�V�ߟ�$�Ȃ��&n�
1��)�G��MG#�������Ř����\I v:mf��W�>�?�͢��W����W�I
�%R	�E���Q2(qc)��1M��""'vg�^$���Q£ɊZ���8�>���d�!x0�w�:w�wR�37�V���)�X?U��q��c�y|B]��B�I�W��h��Q��Äs�iZ�tE�0��D bǧ�b���^ދ8a�N��|(�� 7�‡s��G0�D�R��>��x�l�E"�;R0X �Կ��)�9f�o���k���w�$7B��hG��(�p�y�C�y�h~�A�Gn��Z�*�Բw%{1�k��)P߉�;���$T�d�3�e"rA�"��D�"��X��w���4��)�3'�uR!|���!�(Gl�؁L�jJI8UK':�$	]�����\*����@U6��a��x�� ,ȯ󷴔j.��X�L� �_x,I�ڗs�1atm!֙I/���izrʿ��/� Ifu�$S�,9l�:ks�'����Z鍌�El\��+��,����Z��"�Xh4/��4_>a�i��ut���Z� �$��7��
R���G����W���}���|`.�Ƨ�f1��(ăDEa��nkl�r\�4�]�n[��!��V�l���ߟ���]��#Ġ�}��%
�G����d��?0�C�
��u��X&���Egܶ�W���2�B
�yf�,s��n��5���[k�δ.�Q�)_�Ԣ~k0�.Ɲր��A�74!D��V�rF��`�32?�
�[�sӵr5,(]�����{����&�k��)7hw�iY7uj�nZW��ꁐV�!S1���S��¿����>��h��u�;��o��Y���e.��)[H=M�]3%Ė/; �}<4�4�m�: 7d�L��H��R��'�2^���f���`[�J�F�=@��C%��������2Pq�-\iia4��S��"��i�ͫ�9�L��U+��w�J��C䇞�V|)P��iR�pU�̟m!��7������>jⳆ��~ȣ�kpǿi�L�G4
�v�e݁t7Awh�F1��8=`��4]�U�vOY_���NY��7�P(7�Wٙȡv�ƾ���[#{`�����9�5���u���i��l[���8p�jۗVǴm���;P�vp��"�7�hM�m�8�LVJ؉3�T��.10�T�*9�I��BA���8c;
�����J�,�Emu&>��	�FRH�=^�ӣ�~��xc< �V��<��P*Ҩ�2��Vp��4�od�Ҙi�O�,�3m0YDqn:�ƞ�srY
�Fo�B"��q09JUI0̥��0R����ؒ���{et�(S�&o��DG�!�/'	z�bN0�s�Hm6cwl�t�x�z�p��N�@�3��n%�.�چ��As7^E�NX[�W_hS���b�?���{�Fz�{e��Z����c�x6D�4�*���WB����!�)�	���1���G�p#!lԉ��u�v�l݅�ĩ�22��7�xP�J`d��xm�MDeŘy��^�r��!�W��͓f�^��R�4�>}J	�t'�i�yZq�R.=.p�lM�	?,�\`��l��-�VR�P�*�_�l_�9�j[̃��v���y�F{�
Q�(.�������*��-�?�Y1�C×������4�$���H*�>�AϊyB�{�8��sR�B���'���PX#�;��ԗ ?�p{(F�31�Ԫ��ȎO�PE��%���]olU�y�f��ˢ?"F	�v��HU���B��<x$/j	bW���±�#e���L��+��(�����fLg��K�[%2O5��˗_5�S���Bʧl�5�v� �Mn��u"4�tI��V��"��;=j@cJ<�`�D[E3Z`E�֪%�ފ'��"ϖ3-nQ1oex�M�/��H:*���M7]�xP(4c�z�{�p�X�ҥ�{��W)�Y���#�q�q"2�Av�AN�w\��9�'�?~4]�钭g�`m��Gv�R/�Ϸ(�[�wjr&���{'��oT�)R�G���M���]�d����#ag*�3م:Œ���>H[8p��-����u"	*+�}���E�
������D�C��t�����@��΍�0�5�F%��l_{���]L�6oI�I0�4{_l����@6.�4x�%w�x��Zۨ4�����%ّz�=B�i�P��w;2j	���(]���q����Gʞe��MK��N3��2�xL�nf�x�^��‚_���Ӟqb�rA�X.�8z4&��T�����>Z�^{�i�0��3�O5�B1GF�
ӻ��2�X�8�,���A4rȬ}�e�ZM�f>yjj�?���c��c�c�AV�]]�ypc��-���r[KyV$��w�T��=<�,�-���/����������F�R�|"e�T	t{���5|��1���Z�>3�. i�f��
�Ev���z�![�K,�|���{���cp�F¥Qϵ��?������-�9%�Klj�o���w��
����$Y/GX\�x���<��8��Q��ye����n9+�}J^��Jy�,���?����:�	��_v6�B?��OK��`��:�Q�����X>�ٟ���9��h����<�Ͼ��xtg.�?�"_�l*�z~)�|A7T��mPD���ا�����#�6#�Q&�g�j �7����v2\��/���\���37v%�)�ۼ���Z���<�Ϋ���&!��/�R0no̘��fY$�3��.4q���p��L|n�J��`~��
^c	���ؙ�\>�^�N���ǟ7�5�����7�ƣU$J�Z�d�~������›��4�fRmJB9�<����t���>χw= Լ�o�@���®�w��H_����T��Ű?�^��0#X�%^�J��W��3�T�\}y���&�^�o���_Ч�?JPFp[installation/framework/vendor-fixed/paragonie/random_compat/lib/random_bytes_com_dotnet.php����V�n�8}�W�l���۽�M����Bmɐ�f��mq+�I�5����P�/�vQ!�Cr.�̍��W�'�gg'pnp�{�
f)-ŵ��!sб���T�`�ײ��T<p
����[p�r�`pUK�I�3��D��S��l�P`%�݋e�Ub���kǝ�^��W��7�8����ղ�e&5�7>F&��2�\���`7<�e0)x���+M��8��2$�P���	F�����Bd�lQ}��ڒ���"_����|��~�h�Dnw�B��?<6��x��vpڸ��gG���뷘��&L���sa�z��刼PB��􄫵Ж�Аr�11+D�9r`�8��8ej�0���Á
ra�ȉJ	�Ca��%-�f��ieZ�Xش'2.�<7U�,E�%Ѧ��Nk�ӎ��p��A�	7��&����DLf��2!$�q&֢vB�6BęL��`�2K��_Aiש� ��শ�@�w��9fR�̂C#	X�{�V�\S�K��&��c>¢Z�*G�ܪ%�g�R�i,e��
q�e����[H��5riu�2R�3]�e,x>t.r�F�
3E0��z��T��S���Ѓix=�u#�)L��?�p�Nq}�?���D��;�q��g?8�=���ˆ�����p�����n�
U�;��@������ޔ썽�?ĥ{�ٝC���Y@���\������Gn�y4	�����#t䍽`�EǸ�\�t�F��N�9҈(���]��g0G7�<��^�����\��7��
ѐ%I�L�z�K^]���0 >�0�E�t�n4�i�S�7���([�]T
�T
��E�8A(B���ل���ܔ�k��|�yb	�_����L�"��V=�[�u�Ӂ�'8M��F���V��F������pK
�R��k���mO7������}�мLd�������"!㦥���>�~8��'�W��dٴC—"<���Z���+G�5������s�*b�}�yq�uw���v� ���=��
;8Y��ұU��3j{��#
��:��n�z��������X�y��x���8��l[pO)��[�[K�^Y;���}GE��k��Kmhİ�P�W�#�C4'���j��̈�+,��ʾ9*"���؅�J"�)�����Kh��a�%�G��������޾~=�l"�;u���?��!&���^�qiݢB�Z�;7�T�Q�}�:��e��>v�A�������-G8���Lʂ;�L8?���˷�!#�H���%6-�䡝jPe� a�94�]+���#}��J�=0����'M���^�ST��~�Ox,�nW��T�y�񆛪i��s��9�H���B���/���gq��Q!K�pƷ��;��K�^d�C��^=uv����%�,P��Q@������}��M����Wȇ���I]��!J��~"��Ҍ��i!�,�%�bX*�`YV���Ѿ�j֏'�'�JPFdOinstallation/framework/vendor-fixed/paragonie/random_compat/lib/cast_to_int.php�����Vks�F�ί��dbp�G�����A6���H"�?1�X`�UwW&L�޻�08v[Mf�>������Ųh��������%-ŵ��!sЩ���T0e闲�ҥ��Rř�3�n�����e}#�5;��$� 7x�/,�{�,6J,��ۿ�i�������t)3���	��F�B���p�����D�sM���y��`TN���W�t�u%e(@��i�	F���jr�|V�`*2a6h>ULm����"_����|
��~�X�#Dnw�A��<5��x�'[:m\t곽��ُ�0)��3��b$���$u�ˑy���
􈫕�V�а�cb�
s�\qr钩w�H��
ȩa"'M(%��������5Sܦ�i-Sa�>�i�⹩�d.2,�6�� �-:�ό��1&tܜ�Z��,�-7%R�q�R��3b�gb%j'dn#D�	��(�;��31�_n��v�t`&}Z�Զ(��9�Lj�Yr"P����^#G���Ҵ�^�վaY�K��cn�f�g�R�Y�e��5iLe>$M��S l*1Ï��K��+*���1��^�,�)�Ç�ENh��(SDC��mQHe�>U|���{�Wɍy��0��O~����?���F��-�W8Jn���>�"/�!������~��{~p
�h��	>��&��Y��^LxC/�q�^�?�u��OB�
#pa�F���F�h���!r�W:�^���c��. y��o�2""
�pt������楇��ˁWyCu݁��C�ڳV!Y�t��	7}�vɫ������I�K�F��Ə=�ȏ)2WQ8�J)�hZ4
�
�"�� �B�q�m1����ɸ���?�F��C��Г˄M3�>��_5�&"7w,;�t�[��S숤gӦ�!Xi�/�D[c�2lb��<ۜԗYB�3���3�L3pP����������΢��s��:�8q� �Z`�k��Wd�����Ƴ0��_%���
ܮCj�=�((�m�D�ӏnG�	\�

>Lg>O���LW���v���ؼ_W
+��,MŌ�h���W��,�,˧���T�Ѹ���:/WS�~4B��_c�S,p��۷�'PS)3��I����jh���/�'Bk��_S^�yJMq�3��q��H�lu���5��X^b��ɦ�O��+
t�)%U}pj�en�x�f�u4�]-��L󪎾UX�Q`�acՁ�{�+`��ٱ�Q�C�����=y�>jL���a{�饠�T&X1��p��C�c� ��ռ���ڽS�޼y�ȇ����V��9��"|���M�Y��,>����%�m�^=��S����|ۚl���Z�c�o�Μ8ܻ�y.w5�=f���?JPFcNinstallation/framework/vendor-fixed/paragonie/random_compat/lib/random_int.php�
I���Yks�H��_q7��@�1�vƏ8b˶jl�O���M	�@��Z�n����o�s['����Tau�w��h�ߣi�������V��uh�SAZA��FŞ�*$��224V1
=�>�ȋ��|��XxF�h��ֽC��e�(_�@A�yaכ"L��k����L
�/����f�q��l4��-��
<M?��-��T(M���3���P����]�P�^@�d�
��6D�ٯ����`��ڒc��E�O��0��1�P�O24ϫU���
?/��n�����@�
c/^�T�D�pB�
Ŝ��]��✻�uI�v���X�.|�3�\�̭;X:Q�K�D�?j6�q��9��{���H� �	�wK-ʪ�"�ImC$5ME,p�X����8�Ԙ��OD���Y.(BP���Ɠ!��l.f
iZ��܋������H��L�&E�XW����q<�Z]#��PČIr
�K3U���Ϣj �d��ہ��L�ۈ�1`��[l|�fj$�+��IOk4��a�,j�.>�{����"X	A��<�jIYY�6Y�4�̧j��\Z7N��:R��π��� Ps��W�H���d���
�/T^�f�IE�(ȶ�yAC��F�0��;��1��
�"�|���
���6];��\޷z�}�:���=k��F��u�n@��ڃ�ԹD�H?��9�=�ߧN/���vo\[n�����m_�;��;�"�ك�՛It�>˼uz��xm�so��Z.���Y�e�G-�z���գ�]���;0���n��eέ�ԡk��_�nnXc.�u�zl4�w�{����;7�9�����I5���{[���m�ʱ\[:�ԩ������¿��i�o����\�
�޻}�F����(]�:�K�9�`�XY`o;�0>��C	����\�pZ7�g��9O�Ri�Ɵ���EH3b"Ѐ�����Xݞy�S@jT���ob�2[�����2���j��싈�[ޏ�A2��z�'�-9��4*��f���:� +�f�_Di��0��&hX�T���@V�pǴP	E(����8P��k"l@�������m>ݶ>Ԩ�R-���*�Y�u����P�=mx͖z���k��&�ynTQdQ1W�;�a%�=�
p��D�)��})z����E��e]������R(XfM3[�<ߗ#�(<`uY��B���ɴ^Xːȏ�����>x�Q���];��t(`<x��B�tI�Qe�W��8F����jI����d�~
SJ�z��0KpdCQ����%�c�ښ�X{�c����1l��<.�a��i4��(��N%1��{2��[�^h#f4t'H�3CAqm��s�#L:�iv㯐S��{1�D4Ƒ�ShbgW��Rg�23=�(;j�Fؤ_s�'��o�j�dԟ��-��Y2cG�6�4�9��:`}��G�9��B�ٙ�p�Ƭ�V�|��ȁ�o ����b�S��OT��"1�0	_��Iqs{�0`G���|2�#a��zT�;�_�V�f��g�G��P�ZIo��j���eE!��/G
�	��j�S8���� 72p���7�+7����rl��8-F�P��
�W)���w��'��1O�L,���L6�*0��}�p��P�����)�hq&�$EqH�]d^�������;��W����ζ��Z��wm�as�R�N�լ���O�5�ƗTJ���cO?+�z�d��@�kk�����{��x�"{Y.7�բ�!Ф3�8�1?�8�0�F+F��<�X=�Q����	��SO��U��+q�`z�ԘH���Np��O�xɰ��nQ�nc|,����H
���w��zp*����#`;�p�@��=��/��;��?c���2�=h4�_Qe���L�>������������!Q��&�Rs�C���|��HY+$+*4
��J"@���� ~b<�-��`2���fh�Q�˯��� ��O&�����0@gy��vď�Y��ґm\�y�2
����׵���u�ܚ7g�������2�o_��=�R��j�j7�V�]-�+���˭��c�2�\��&O:|ղqj(&ȫ��(i�ƨ)	QgH���iz�rc]�E�f;:�P�1���(	G��+E��Z����/��O6��j�e�
�-��(�F�0��p�P1tGPX��JfoQI,Vdah�sg�yd�Gs�sV��W�Ľ��!H����_�ׇF~
��	+g'�����z�".㡟�I������X�Lbo�}p�qp�6�'�oW����&���{�\I��z��V�v](��
��wz��A�6�y��K����o͠�Rel��L\/��r6�Qf?����8F�T<��{DY��ٛE�8�_�/C1��Յ��oڟ�o�}�LD{���?7����Q��+�������F�f����a�������V�
?�
�mi�u��N���M8�����8�ۯ��*�P����֐Z|�X��V��q��!wmM?]�z�2җ�
��|_k�e{�q*0�uV�x�d�1;˳Z��^��5�
���I���S�z���H(�5B{����ēay\|��׼�/�T�Q�/_R�[��5e_k����OФ����u�JPFgRinstallation/framework/vendor-fixed/paragonie/random_compat/lib/error_polyfill.php����T�n�6}�WL��q�Ţh����%A�7�S@˴�F��c,��;CK6R�@�Ù3���?۪onpa<�>AQI��I0�:m�S�[�:XkKQ��Z��ԫ�P)�\��ዔK�G��-��5�4�x+66Jz[�ۃQ����몼�������g�UY�ZX�zc$t��ջZ[�9"��X�*ec	/`*iD
�n�u���X�9�T����A_�����
�ٶX����;`��s *�Ϊf��=��~�G�)�x�h��G��vE��yq�s�����]���/���
�0b��y��BR�X��[���Si��zq�B%���l��(�����J���i��Z,��!M�%��ήB$��n/��m��R���t�����d�j�+*�E�E\\�<+)jĚ�u{�*�s~܌*	&@��ޭ�I]���P��i&�E)D8��^�5}���R�m�J�r��h�,P�R3�NZY{r�P�}���(QK�u]�,Y��޾ף<���4�X�����4d����k�'��nV����)��������h�C�G.Ԓ�����V��a)��av�Y{i�xX���]����|{"2c�'��1���,���la��y1K�G��$\&O�����i�����<�8C3�G�b��)<`h��[��H|������,��>�OaMx�$� �4�
>ZDa�"K��!�1"�<�d���Y\�bb����gaQ6��(##�0Jҧ�Og̒h����_��c6T7�B>`��)�Q	y��y�	�3FV��ߨ�ILzFI\dxPnV��y�3�Se&Y2�J���x���*��A�B�E�N�0fa�p9wZ{����Sk����j��>{uɌ���������\�0�#N�h.��T@�j���x9�BK��$�({+e��k�o=���s�g_��}�???>�VZ�1=rV����g!���'�3���N^�#-�$f�?8�}��B�JPF^Iinstallation/framework/vendor-fixed/paragonie/random_compat/composer.json�����RM��0��WX9-+�E��J����z�V(�zڈ6v2l��'�猀�T��{�s��(�{T��|�i�
Y�k[S����i�^&p�R�qސMU~*a�?��n8���#1̼��Qn^@����>&�L=$��I�;?�k��_�q�J�f��h"/��j�CM3&�G��The5�>�L���v�����o��A��u�$UN��5�hoN�Gvle+�צ�HJ�
l��n7�VK��R�N7��j�wr_��y^�Ip��G�4�2"e�q�h�o�!	�x�b&�ۼ:��#�ud%��o�|�G�?�a���%�������?�w5���!���&����o7���A��>����B�	}��L'o4�T#[�xp��C��j��p@�5�|�Y{��Ϸ��#]o=��vP��+ɓ�1���s�JPF9$installation/framework/text/text.php.C���W�o�H��4B�ISU��K�4ǥ�9.i�}�"k��blw�N���7�? ��NEJ��y��=����*k��aF���z0[q�#�A�\���(M D�IX��,�)2`"XE�<�@p&y�{�p>g��P$���2�S�Aj�exʖ	�W�iv/��J�I�����᯽�ã�0��U�>��t��YZ�iVǙ��8
x�����g8�	,��b�pfo��ɮ�]@�b4@ �A��E���u������ ���
�m6ϥ`����0��;��ne"�E�KtW���e��&��/t5��e�������1����<� S(#~e"
8*B�I�P�l��z�+�_��eM�D�Z�=�L}S"�!JO`2���H��J�n����
��Gk>v�g�+#��%�0ܖ%�,Dp�F���il[I���"&;f��zO�~Ӓ����|su��)9��g�� ?�9NYx��\`R���NҐ����c����hEp�ܧ_*�i�Ε�x1�\�@�b1J�ah�d_9���,<t aI��0�YK�
oWR���2����T��^D�E��M�VK�E$ri`W�^�Ó��{�n���%�c��ؘ�k���&kG��2�����F���S�s��w4�2t��4,��$�/	����:E��)k�� �Q#��ZQ���|�q�&�`���s	}����`qΕ%:.ە�@���ݲPu)��ѳ�<אb�B���Ȍ��bb����]�˼2���ϣ�q�&+H�z�_��:D�#΀_\����{t�-��P�� '���X���$W���L޻O�#^�K�@�y�����!p`�?%���r��5�����~�^!rg��%;����a{G��+J��	��.��0�9���Q�i�zґB�G���q?��z۴[OL���ngb͹t;���?�OW·�������g��g�G�c����$A�����\�P
�{H��5��tm�p`!z�j���{QȺ������Ћzz�&4�c�$\�!���� �E.D�.��4��_*���t���j$���<�.�������ۦ%��g�����q(���n7dJ�3�B��Y
Q�*1QBQbA���"�z����´��U��|�:�Yge}�<�����I�s2�`g3J�Yw��G9�$��+�۪�i��Zi�-�t�K��<KvϜ�:��@�mOw�zF����J��5�aUP�\�&x��QݑH��&I���J�9=����G2O'���qg����Њ��#�^w�<�]w7��G\��ͷ1��C(WQnRMEsM�TGu�*�S=�M��-�J��ĭm�ӟ��
k��n㮗�*�ݨ.9�2խ��ѵ��j�g���V�$����r�Ec���uW�c؉�r+�Y��ܩx[���K}����ѩ<�W�lސ� -VNM�2���hQg��t6�/p��R4��j�ט��DM����Z2o��g8�a�����
��]�Y��eD���U���B��խt�+�v1�Tz�wv��1�y|E<�Ze�E�9��cEw�^���X*v�ȕ(�
f"}�Qt�TR�`R��5��4ĝ-�n�3�:��9��94��Xk�HU�Te�j�Ŗa��|��.�nO{/���do�uZ{٬m�-��?�	�]�MJt?����~|z�1�ꡍ�T�0�Z=��[8�.D`&���_�����b�t���z6�\�in�i�ة��p�i��!"�eQ9���2�-u)��ee�B�oY\pS�6Gk����_\�{�}��R�rH6ieT��io�JPFG2installation/framework/application/application.php��)���ks�6��+�SR=Y��˝%Qm7�5I}�;�9ۧ�I�bC,Z�t��ow� E��ә�d"���_��W�p���C�5��sv���J0�h�
��,�Nd�TT$�fKY�[},sƋh��ŢBp-bv�e�B�r����b��B�x ��9��;��%��E2���J���[��=y�gO�=g�h%S��vm��e�JŦ��[�4�D�����?�7"O�yy�]���}=3�R
(y:�b�d"�����o��D�������<M��d��u�2�N���[�i�����yI{�_�P2{}�PIvGZ��<M�.K����P�A^H-"��>��XV�)�RS�E��D0��2�+��ؙ���5�l�̖��U(PP�d�m�u0��p0��<?���q��	UO�"4%�k���g���9ɔ�YFk�L�Q2x��4���n-�ϲ�Ԥ����۟A&�
/9(}�>B��
BC�
�h_EI�g����A��U���_JQ�)��/Zhkb8I�/�A�a�x��^�ѡoW����{L�ڥ#�Y���f���-,�,��Ţ�1ܱ��bǪF��C�6�W�:x���֤�/Y��Z z�F�*��n�Ll<�P��s�k:eB�5��'���6�Ž
<��7��Uh/�h�#�����A��ȡ<@ml�z+y� �h�i���}���f-�"�cv[}D��h�J��D1PcC�B�-Pe;+�sR_Fz�;�TǸ�пH�ڹ��R�9	�$��\�����G�]�O��*��=v
v0�x����rtU����-�%�d���r%�D�c��iJ�Ӓ��<��T[ᬑ��ă�X���ť7B+(*�0ߑ�v��m�T�H�_;�N.e�������nz�؜��Tte�D��L>U4�OK��=b7����j���P]k�8�^������"���5�M�U�78�G�U�x�,W
��`��c�q�adth쫯�#�@�tyxXg��[�b]G����U��|���J"U�"k���ʇ {<��R�~!�+W�q��
(�r�7�[)�R���\���{X��Q�����w�z�!g�VeQ�D�lS����AA��c��O��t�g��Iͪ-�{]�ߚ���"�_;+�/��~w���LRq�
������`:��`B�S�&�+5�}h�!����x���XH҉#=jT�,t���}ۡ�1���'�k���
4ƙ�£��0_�����(�,�De�L
1��U�YO�m��V�5���)~(�0�]V�X~�>��@;5MW�R5��J?�P���NP�`X?�V���p���w��Z��6ڪ�v�{����hջ�6��"�6����Pf�M9���M��S+��T.z���L�!��"J�L�2*��m�'�������r�MY���f�Ad1N)�L	A;t�?x�`Z��	��1��Q*�h3��V�I�ŝ
ñ0�2�f�3��<=r�,��OF!��e�O�X�wB#pv��B��*A�b���Ӟ��g��P&���̴�дl�e�wl֥���¤�{ucP�0�&=8z��<���Z1I����c���;C1D�Ɩ$�X@�-�]Tҙ�����E1SN�t�k;�p�(Nl��(�`�]O�E���q����4�;�	�
F��!��!�"���LmЦz�
�x��Y��H>���%e!�`����g��;�U��T�W7Ue����d�8<N�
��e*7)��m�o�iɬ�˦a٪��!����z�I�y�����TV�w��̟>Ok�q`pJag����A{�Y���	
��YEu��9�V��Π:s<�RIh_'?���Fp��&������T |�dZ3��xΡb�J��ok��&�t��9�6ζ;����U[�$8�T��|+(�V&>�d��(��T�X�#V*L"Ռ��3��K���*��7��[!�G�Ll(��$<��LN�Dlq����S��1f�X����Ѥ�j�j�\B!@"J��
�n��K8�S0�xK�U+ l�A�����l��K`E�@�f�e�����%�X��<a�<�� EQ�J�|��)�#i�?����_	سeT�N=UѺ�ʠ��_!($�'5�[)��Drtd��l.1�wքgꝦ9�1#3�@k�م�2|���jƸ�(a��4��U���I�-��a�X�֯Fe!��hU0������m�j]&:^��#
���{A�j��M��r�:��Ś*���(�?]c'���^�AK����Erxx˱��>��S�8ٜ�RC��"+�n��
CK�x�����W��uv3ݳ�j0��zbj�)h�Z�%�@��@7�&����R&�8[ o�uq)/d$�0��O8�)Ә�C[�5x�ĸ��s9��݄-u>MYޭ��"���Sfi�Q!��NIX�,��7������K���m<N�t���?��Į��b5LA���Z^�����R�í1�%4����e��J*M�A�`��V��p�i�%s��s���u=�2�	h�D8�;�ee64RR
]�Ůmɤ+;�ٞ�@5�m�K���Ln�pp,�c��q���A�����C�ѸGU(3(��u`ƭ��CC�>���d(Y�������bk���W+�z�.��������܀t�C6l4nc��8ê<5�rk�:N_R��Wt����"x�[��N��a�Ri'�V�����D<�J�x��T㲴%a���a�dx���ϐx�[.U��30��ʱP4g�t$��d{/�K7��T�
�c�a��:�k1�F+^(�C�����������
�Ц�W,����|�t�u3;l���G��Vܖ`���	��v��8��I�!�P�MqG�� ��˃����c�QE��3���¨ɶf�S
��k;)y�&<�}[=2���j��~�1�u��M"޵X���x�d��kA'�
�>�J�fh'�;�/�t��S?p%9�R3�Xn�����q��o?q�z طR��
��R�uL��kϨ�
^�Rgq%��ϼF�seW�A���R�13�����J�P�F��k5�F�`A�����m�W/f�כQx5?���Ȑ��~ZSj���Q��X�5.kaڞ��ͫ��w��[r��&��_��w��׷��y�Φ��>��+���w���Nߝ^��QƓ�o��6ЛC���d~��ͥ_�ΗV�Q��TU�F�a��*ؠ����!�~ۧ���}�<�/JPF<'installation/framework/filter/input.phpQ���<�W�H�?���q�X@���X���r@n��l�m
���d>f������eɘ�쾻�.��RwuUu}w���i����\c��w�����r�Y䜥<���σ8b�0
����
���<a~:��<c�9����q>��{9"�;q���_x�O8c0`p�7���4�Lsv���a�����o�����i��k�}���8��a���Z�$�0�(C�O���<����<`'��=O3��-$�@@
��kk#>">�������{�&�k�]S���!g3��
�(��>G���x�r`���,���gl��\��'�Fho�wް��G��y:�쓟>Y�GÎ�|��]��Os�KW� ����x����s��w�Ð�i�h�w�e��l>��g�S���}���������!lM�zGA�G�<_�m��ܪ��)�NS`�)���(P�4DY�GC�upjw��|�rXyCa{$mM�j��r��Y�#�ܟ�Ex��C��=K��<�p�I%� ��>�|��!��.�q
Jxd9��G{l�y ��<̛-�>Er��P$஺���wA����Y2���4b<�x�~�[��b�0���ªr��F�ҏY����!�I���%R�^
��V�5�$	y�h� =ɫIϣ���g�\�A�7>p9d��3n]f
�r�4���*�wkV�O<�e݉+�,_\EzR<�tL\S/��PɃ\]>�k�eJ�V䮭5���{�y6c�	d�,�y�NUl�����n����6{0z��t(�?�u��C0�y:�$��D	k���u�莃���g`}S��<H��,��6��Ł/'�[�m�H�&k�R5�����β�T۫u��Q����_����}�#v{;T��Y�]K��}�b!a�/n(`��Z
b���~��<�YLo����|Ā�ARa�"R�3?�@7�<�����A�)d������z0)"�˲`�n��J�x�B�'�
’�����@��:4���0Cl���V�_�f��9��)5 !��Q��
�Q ���A4a"c��,j������G�����M:-Ki�LR.���]�&2��&���2��ۄ
P1���a���Mwwg�?y���W.����^�Y�YϬ�l
}���,ɟv8��5���p���Z�s@2�?�'X������֕/ZPc�K`���"�����A:+S����C��\��..0" Y‡�8����Ҷ���0�6I�D������D�A�
��J��<%��Jc��+� �ʸ2��ž�G'g=�xvv�؏g�@5NN�|�|�\��]�Z����#<;?�g�}�]�Ђl��\�IZ�Pb��a�6�8�P��i��dMK���҆�v?P G��k!���y��}z��	�SA2��.�5��]�k�c�\��EȔ���eD,�lν�9�KR>�z�n��j��盭.�?�t�rh�HDAAS�a���<����oĐx�;R2����?A�yH6+�$�Y������R��a,|d�2���]w�E��弦�V�6�M��I���
�8ԋWC[��'*S��G�@��O����t �t�Is_��*x�/~{�iW-`��d���0�Ө�A��q���[�Na�אA!,3��х��#���+
a�j�.?�����*@�U��KE����P�0B���
����l�lY�ps7��k�׽Y>����m�-�Z�ʫ?o+/�秽O��e�q{�}��st��/G��׍?���>';���[r�,M@�����ډ�S���LI="�T����>�0&�۸��
��b�����kVt�DӀ��!��<v�L���'�K�|M�q�H�PF�L��r��5���#��)3ns�E�|��q�d�_�u��L-�d�L�P^N����bM���i�k���AM��d<Ees"͑7-�\�-�n0,'��#A�9�ef�b��u�%��#�*K�zB�ye�J���|p�s�ٽ�b)�;5�Fưd�RN�W�w�t�]�sBK���8B��^`)�w=��ga��%s,
0֔2<��)��[;K��܈I��9O�0,k�a��h���=6��U�t�G!1�P���WV	�g��iˮ��W��y>����i�k��g�Y<��/X%ģ��u��>�з��*�`p��� ��� �c��T��[�H�ʲ��l͕d5}�I�΂�b;;]mԧW��,�4AR��8N�9�K�w,���dn�l�u�\���0�a�����H�^�{K&N�^6������ւ�����E�&�M ;�<��SU�������y��J-\ډ�C���q�!����Ü�9��ֲ�da�κ�?�W~µ��i핔�|D�7��X�w�j����-p�x�>�
X�� ��r ��
k?�0$�*�Nmc8OS��Oؒ�+d�,�r��p_�
��*n:�eY�k44E�B�J����r� �a�R�\Fs��ё5t<a��
��R�,��SJ�N0L�
 �\fϰ`�G�BK�A2E����X���4I��h����Xڢ���T>��)��
�R�/��#���<YL�&g�%g��Ct�$�3?l#��=���R�2�&�D�d��i�?渆�]I�#�d��IcEM	�Pd��خA�z�zE�m?{�l����7�4�B�E��\�G��|�����@��[��.
�<��D�Lê��f���c����m>�9�zCL��>�(��"���݁-"��9S�v�����ե�w��Cl�q��&�Uʠ�U�T���j�z�",@B�(�,���y���L�� �VqƏ��8��[�)(g�W�y��J<��q9�@��
��-�	�&�,�d�,{�8�L<�����
I��,�*�9�y �a�ņa�q-�(r�0�ѵ#R�nõU�4_�q�)��`Q���Q�v
q@b�℘$�-����j1Z�ю�2[�Lk|���cl������C������h�R>��~ZGFb��Ɂg�`��MsO�m�c��$X{��HXv�fG�y]m֭�Y��ӕ���J�n67�A]!Q&2�uC)~��H֛�LU
i1sj��_����'���׳T`��y�}P{{Aq}.7��b�
�@t�����w1y��@�N�4�h�Hs��H0T;���]�P�0��Gjw�=Rѝ���e.�
?k*���l�^hs�mf1��2�`�u)4��װG�=��h%�}��Y6����	)��ͣ���@	M��Dn��d!6�Խ�q�n�DV�D�B�z�'�PI-�/b�`ũ�I�샥W����]�_�-�(Pu��|�x{vttѿ�=�}��r�w��6�VyZW�K	��聫�w�7�Q��*���ULَ|d�]A�V�^o8�䶄6��B��J�*.+q��5��	�C�S�j�!Lخt�%���u�d(=�_��"������d�׭�@�Z�C���!p�/�XJ]�o�6jV�V[R�v�Xx.L�dy,��Z�D�ђG�(	�Ű��?�C<}�j����v��2chﵙl�w��M�L�5,�c�xA�T,� ���$�)mD@r��~'
L1�%5X|�b�hvK�0<�,�<g�8d�p2��Y����Z�Ғ�/�8%	�-�\�3U	�HX�ҹ�afo4/Ê�?�[�٩�r���>E�%�6���+>��?	
.�0�j�xTM���ć�S���gHS����h$ʱP�E�`FM[o4*�B��+&���j4>�ƪǗqdI�^�ᗪ�Ǒ��dS��]p����Ԭ��8Ӊ��v�s�Vs�� ��3���fVw�edX�c� �|H�)Z�䊵����q�D���bS��ʝ1닃ܼ��bQ�Z���>�Ђer��ѣ�	���N<�½�]u(�M�`�X���LD>G/�m�Qq��~��O���c���UTAkF�(��n��iϹ�Xf�e����E��Mǂ��0�o<k
m������JO�6�h�&;���⛣2�k�� �"�V䗧s�W{���o�>h�����
1��ou�"�8jY��Y��NBf��[Ԑ�#$1d�#$����eǁ�a7�t���
%)�/:�ϒ$ј�Z�K`|�9R��ʣ��?JLx�JA��C��x>����,}��ju�q�u�A�Gw2�V�s��m��W/�0�E�5����L)�jE��>{���3{�ȶ���N�f���P�/e��{Q���)��(6-�r͑ދ*�EE�7�����O�w�ĉW��!�ܜv��+��.`���~p:�-0���ʤr��-V$���*P�B�șJ}�B{j��,�2V�4
�ވ&�T8��r9�F�B �V�
D�Q��b���/a+!Z��d��{�P/�����P�#��A���l�)la�R9,`��k{�Ju�-"�2�^��P�����q�Z衋�S�3�|�Q<��t�[�@I�9��qtf[�|��Q����E��xNLKg̻p�f�x��8{@&9��YlȚ:�*����t^o�e�V>г�cC��M�b����2�� ��ݒL�t�f����Z+�ϗ�Z�o�/�ԥ!c2zccI�ֳ�,��bW�8liΰ���9d���-+A0����"3g��(��|�yVH��$ɮi?�m���+���@���A�W7������m��d�
�v���\�4`�}M�F
i��V�m{��Z'D���~G|�5�n�]?��B�y�>�eJ{�F&ķW X_�W�~JC\봳�C鳂+�8�,t���Q�f�*P�fZ�)շ�>()�W��C�G�m>�����-�)�ͱc��d�zy|yܿ6ƴF�-z���@�j�F��%������呠��.�3mQ����*��b`�f��\�3����&�,��+�z��|۝�S�t��o��
��4��Wo�o�Y=����`�U�t.�1����X�^�ߏpc���Y�5��`���O=j�Q�S\U�np�]uH�fU*�-�S��]`��:�H�txD
�vk�N��^	5-�(i_��b���a�G�ưG\M�S�|Ǒ��%qJT�/�u��#ᐣ���N���v����}�����ѣCo�b���S}�2���eM�:�s�~ڿ�<�#�����|�a$�y��I��aG��2�JV�$�U.?��
�{�3�	�s(h���`�/�cs���v�)�P�)��8���xX�T�A?����[ڨ:���m8�)�R�Xa��A-�Vm3�y���k���'"Xla���_�R|l���W
���0�a�4��i����^q7EI�d#��ɖ��'��͕��u��������nl[j��Ī���4~��7M��[�9���ȼ��*k��9$������>I�M/o�9�X�J����!�G�j�U(q��6��}��E�U�(�N)�ۮx���Z�#���j[�*F
ީ�M]/��B[h~�g��/��֫B�S��,��@�5�m�'.�|�c�TJ�Uv4�'g<��WY�N��G;�g|q�,��E2,�Գ�x
��W ��	nvQ�7�����W�������u�՗�	�Z�8M��8R�t2Ʊ￱k�&d�4|�l�i"u���La6$����4-
Ȣ���'D�U��	�
��h��|uL��
�e�����^c�X�B|
ܴfa��G�:�5:0um1$�-�����7�;��7��r�p�y!U��:�y�?I����7JPF7"installation/framework/uri/uri.php�>@���;�w�6�?[����ʔ�n�n�MS7u�&��v�o��O�� ��dIʉ߶�������./���`�03�y�����ŋ�x!N�ߜ��}q����J)rY�i�Q��"̣��4� ���D����N"�ePʅ�ߋ�OR���,t�U�0N�C��J
V���0���h�.�k�k���xp��8��u���/~��4K�qZ����]� \qʤ@�o�?�72�y���:�;�y'���v"���x:,�2J�b��N�zz��7��H���-��uT�eK�	�E�&e%�^H��Ӎ(A�����8x".9�m3qY��wb�|s(>d21m�P��A	�q���5��?\�,�K�.[g��Y�C2��@{wR|N�O��0�A��'Pȳ؆k|g2'�Y��E(�BM����BPM!N>���_�=����.�EQ�Q���(؏�g8|&)e�F3��x)�m�x+H����N�4�C�Zn��oӢ����,JJ�I��4o�e��?��B�ITչ����	���YQ����&����m+�:ܯ��x��b���<Xmd�%� �a��I�-
�`��h���1Ъk�s��_`�'hWQR�A�¯��vh<�=`��sd&�p���ql�X�휉����iZ>j�L���+
{��_��.�m�ݧ��[�?dg-Q��2%�V�%Qq,��d-a*#t�\|l,��UN���
�A�C�+�mR��#�K�{�a���=��6Q�:�goX���h($wC���_��}���6�\��j%KbVQ�������r�Hs:����k.�m����)�+0R(0El�{��yI�
�~�s��IE��9E`$;*	��b��"�J�н��KT��RN2!	�B�h�!f��V�F�6�ATl��*�(+5B��5�5�]�^�rz�YZ����2^Uk�n+#�BZ�C���cp&{VĘ
N�W81�Τ
��IBL�@n�B@�\���H��g
x���;1z{}}q5�	
�FE!��g��ƣ~�v,�?O����襄����F<:���#�5�{�uYf���h:����wzʸ��C���z�:�".J1a���V}�4nR�YF9�'�r�сQ-l1*�6!�`d��&rqvv�w�o/��w?Ssy����W�3�]#Ej�Ҵ�#0P�u����
x:0�j(C�ۥ,���U��u*��(^�x��(g�E(Z_�ed��i%9�[P=S�R���s�J�Њ��J�/�r�c9�����[��a��`�U8f�HEah@�i!BR)����d�r@��?��r	9m%0BY�L��}#v���˳����S�7����̮�/��߀�<M(E1���i �VGlrt:�Q1*�z��$�`��Ҙ��ڲBm�8c��ٛ����!�gde��mnʖ\�����������<�	�mN=���8࿣�5�`G�p�2p��x`�X{��<B~��4R�:A�A��'2S�G1����m�i
{0���S�F�-`w���L�Ъ��G��6���	�e��i�G:��Ć̶\��q�"�%Ϡ1��ʘ���8�o�8�J�8�s��I{̍"YF_�[�_+W���c9yXO )|�� 0��@R��,-F�:�A�p�6��pyKwd�,̀�v�0��B�{���f!��~s���#���u��Ӂe��:��L�ȷ�q��fY�+�8�~;;;��XC�7&���+�
��2�SEom<��(�<1fo8�Ď��Ro��������Q͋�W�R<ȆԞ��7�ED;둍U8��?=������[��W�ݮ|�6��=q����hS���4S|U���~�a2G���M�1q�M��y
���-�ƎA�?�A�\�l�G��M���Y_��m���d�5��
q���k�x��n�������G�떤*����	L�iT���F���F��ڮ+��[��{Sx��K)�~�����9�P-�#.�=+%�)H�fK�k]�wi�x`��Z��dǮ)9���^�j���՘���$p�ȡsa��Q	��L�XF2^�.YTe+�\4Ec|�u����m,5�feՐ�^);L�6�	�.���;0U(�=NL���yx�s��h�1"Pq�`Z�Q�d�O��b)&�x�Tx�q�!f	��6�uq�0˜��A�ȟh�^�� B�l�����d�<h�����vEu�x�&�C%j�.Xd4�g-jw2W�3E�H
G8���3�Y�JG� ��K��[E�
��5�J�\�wQ`���[����c��-Mpu�႓�r���ev>٨͎�͝�Zڈ��^'��K<Ś�>r�0�Wu61XG.���Z
��ڤ���jl��{�n}x���Qy�u��%�Y�{X����9�Z�Ձ���u���i�&�?�E�A��;s>��)��iL�y
�`�]�,5�a�3����dM��RzEu�����H�"=�5���Se�t�>K2�S[�?S�����6|.��%y�A<�AI��o;�n0%�N����#M�/l���p>��a���4��顙�M3�wь��Od*
��u�i�j����.R�=$c�=\�1���W�h��=����e;�����~u�N1w�h?Q�X/yN�������x.
��
S_�E�x!����FC8p�{�!S%~�W+��t��;J{��u���x* ��3r0Q�(�2,7Y5*o}C�o-�Z���RM���)Z�U�� 3������z-�O��H�����"�Seu��6w���Aa��w��}��3�`t��ɶ��0���@�G��C2Xu�BVL�Ť׊��d &a�M�W�����%��_�8W��j����IZ��g�|i�����M����D�Rn��@�œ��3�]m��RN3>j�kD��q�w��6��
\_n]��+�q��V�����*�D��B��,���<ȟyyߔ��/�(�s�^�X�M��ΤT���* ���u��{�!����V��A�V���[%u�����Bm��j�֬��=_��#�ޒo���[@��M*[�ぅ���+��Q��x�ߜKV�vl���W��3�1Ң4����A�RF��!�@��z��-n�����I��G%_��o�X�hX��
.#Mr�.̂���G�D�rp�����{ES�|�����l
^�F-
E�QU�k�V���v���E�g��,��pa�>L�	����@�\�o�x!����g4��WV#�u�k{\O
�$L�Tމ��%�1߂�>]����,�,C*D�	��ٳ�h@�u.>n8�?Lm#\*dmd���3
�ɍ��ذ�-������8%�tӚn��-�Դ�*u�P.�
أ��ۮ�P������vJv�7�G�\Y�*ⰅJ-�L�n�Yw���w���3^Zn�3w���!g�]�S����>B��Y�[�-Tj9�ի���>�����]��=B�;��BF�!��N��]�Kt"W��=l��q�7�K��d��_
Ʈ��j.�����0���w���\e��FՒ^Y��ȟtҹ��#���f�\ҧ��
sE��G�m���B���
tz]�Y?1-�/1tѴ�J����DC�5��fkl�U������θ�)d��x�g%�e�>�y���#�$����*��B���;Q�|��^��wэaJ�u5d������絤cs����n$�nT?����]X`-�GkW��9�]s���ikG񏮎�^�6Ы@�e-y���n��ߙ�������LYפ:/^�*��{�ʅ���߸٣�U���pnQ��G��g[c�P��BA��X1�N'��T����W�ǩ*���f7��ՑQ�t���y�O�����l���n����N�{��V�x��V�����^c?~�H\�t��8�QS�1M�+sSN[~�b�OzS��ѭ�Bѳ����34���n��k�x�0��@Go��E/��/Ў?���X=p��/�r�=��o���۟� Lh�P�V��7�oR�����ݵ/?T�Rc�[�Z(\��O�hD���&�g�@�LJ�{��o��	�"�f�/�ى~��R���er��m,�p�y�ss�����ҽ�4���\��B���W;Ĝ?m@�|����u����կO�����/~��^��/��n�����^��g����w���^�2�����-�ڡO+ʞ���x�=��c|��?��9>^�c��	>��x�����7��}����N��LPT��~e�t���0�B!~'�D�E�uC��q.�})�w	�=gA�}蹙�U@?�[b6ʙG"�_1[�栺Ib1aMq�.KX�UM�����'�^d�U}0]Z��4T
;16rg�m�P�٧*�2���s˪��šO���JPFB-installation/framework/exception/dispatch.php	~��MP�j1��+��Ժ詴ЮuYD�B�d���IH����{����cfޛ����Z��yΐ�\׋
#|���"�S���A^����h����:Q���#I4�=Q�1�*��!�V���ۼ���I�St��u�W�6�>eb8)��Ѥ�L�V���,1O��`�=j0�y�����J�	��z�AM�<�86���J�ȇ�����N|Z3&i��l�].�jV��@*ʆό�!��΂\���
�GтΑ��3P��@&��/L2�rA��eJPFB-installation/framework/exception/download.php���MP�j1��+��Ժ詴ЮuYD�B�s�&O7��D�R��͊Jo��yof�ӳk�9C�r]/*�����O!Zϣ�Ax�"6֣�bwp�^��H��$�t(wD
��0�[m�Go��˷$�V��u^mۈ�۔��(F�b2�Z��j���<�u��m��걊�|K+A&��'j2��ۡIV�H>�wH�t*���1IeHf��rYU�r0�RQ6|dL�eu���іK�)��7j�4�����IF.��0��JPF=(installation/framework/exception/app.phpy��MP�j1��+�.��詴Ю�"�,���M�����$�K�7+*�=f潙yOϮs,�2�e�\U��#	�B��Ge
���El�G�����ԑ�'I��Q���Ea����&����%��$A��	�z��.�6��dV�Y1��V��������:{�6 �zl�<��J�	�e��%�\��$�y$�^�;�J:�i9gL�V���w���E9��h<ydL�eu䆿�΁N����P{�iO&���L��rA��eJPFC.installation/framework/exception/interface.php�R��=P�j�@��W�-*Ms*-��		Rh{.��S���쮡R��]C��cf�̛��lz��$aHP�ծ��=�)O�伶�+=�	��G�-.����W'r��'�fFq$j8��(�ȇt�Xb^L`yG@t�Θ�f���=^oS$�<��y�oP+��;�����6z�Cz�8xy����-�U���F�|����y"�^�;�JC(`�rʘ�V�$��w�/�m���Tŏ��1[.E�#�,��ݰ�?�JPF:%installation/framework/autoloader.php�����U]o�6}�~�
P@R��n�=iкm�e͂���yh��"B�I�C��)�kׇ����{�_���:��FtJ����k:�_J&+�a�N�lnd�h�
�D���$L^�[�
�Ͷ4yb�	z�ETE��Q+
O�ƩX0�÷\�[#������<��8�_��{��Z	K�����V׺Q�Ҩ�sE�R2��z���_�+6B�C3��u�+6��ꌐ�B�GQT�\V\$q6�t}�n�>�����Qd��F�t�3eه��YFC�G+�
m��r��H4��bwcܚ7U\�|��u��I^���f�Xr�
���9�h#�+銪F)�F#�Y<�<�ar�S�L%|M�6P��r�Xgjm��(�ٜ\]�8��v��h.���h��R<�#���מ����d��S�����@h�F0�<�Ψ=�g^�U�A/��`�̠�X�˴�}	`/E/)Ǒ¯E�%<\�7���R�$+���@������⳸Dh�PC�"3\+�s���M���-�O!R��uG��o^_M��4�cr���i2�����V���#���Gi�#g���,d�@n���Q:d^�s�����R`l��lp�S��
�÷k�_��HG{R�w���۹T��FZ�^���,�\5E7�p_a,�*��5.���à!_�o�Pi����c����#z1�����<σ0���g���}��Ɩ�W8��	�Ǹp��>��^���n(�l
s��m6����N���b[�ݢAk�7�š�OZ/�8!i�����%���w��.��â�`����|sߡ��0��NC�+P��W_���A08���|k�ѤcY���}�u=�3�k��4zM��z�s�'q�����u�H0�†V
��}Hݫ`CY�0���o�
�Ex�Y����x�D�JPFE0installation/framework/Autoloader/Autoloader.php�M���Xmo�F�l�
�`��[�Ò����.h�i�ah�,��[�;�$95���wz9�r��>-ȋ#�<�!���//�y�<��	߽9;�.|�s�E�A�8Q�%BI�}-��JÄ��iL�s��1����0Y���	�י��+�U��:�U�wٌ��Lps�W�J��<���S�o�?ww�{�N�s���Z�*Ri�b��g�'��
�˘�y��p�5a�N��g7�\��^0�Ш�<�<F9ɻ�a���,��>���x�����&�@�V��8��E���n���!�"�f�P��
-�CM1�E�b��1��Ѳfr�|�#�J��L*͍���U��@o�Ւi�Yk�8�r��c:�zi��3�M4"-��ث���6��aH���������,}��S�]*�œXwo�"$q��
���`� $~��[� �d�٩�����u�A9�<I�t�nXe[a��4���O�23����4�Z�D<V��0�V���~qX��Xc�F+��s0��hܢs�̩5
�}�~a�b�g��H�U´WCtce5�ǖ����e��p%qB���'��:u��)��6ܚ�3ؓأ1O�VqˑH���OBA��1�,����=X���U��եPi��Ad�Tda�WF1�X�q��L����
���n�H4!)5//�Ukǯ�͝�Q�g�(�2=2�Z.J��R!g���c�	�6/����jJMb��	��2�\�t��L.Y(����.���>�L��D�5���R&,~cQ+C���)�8�b��ӯ�lћ9��"���]��i�u�~�(��¢0�RI�J�;�c
5j*:����o�F�\jgq�k5����{l�R���V��	c�_�~Wk�:5A<���C�fČ
�� �OY��ߔGf���ĝ�Ut
�?Eu������)W���+'�)�	[ۢ�xR�._Dɪ2�a�"�R��`��HE��Fﱓ�
�b���ipuU@��el�U0D�J6֒�z�(��
���C�A�n�6�X�!�'5X�-�
xe~�)�j��.
'�a��]��	Ώ	~mb���c����,os��V�晓hq�������4;~s.ш�2B�,vFHuϸ�An�0��!�)&W�h�>��E�^<�]/��/��?n@hj6�4����V",�w���^2�^<;��pe�4�Y��B�m�5�֘�J�d{���\N�Ŕ[[7�(�'�a��k��b�{�-A��&Ly�����G���o#-��wnou}�Uz�a���}���v�w�֎�6C�i�y����1�_e���$�C�s�鍠F���e�vYc*Bz^�;^����#�i@o5𮝣��Ӛ�>EK�<#��O����e�4���Tk6�
y�R���;�!�������qp�-3�F���yo�7@&���0O�(��'�,�E�'y�>�i��p!Q�1�<Sү�i4�` ��xZ�6����B����#�U3�p�rv4��]�}�������b��Ѓ&��4I��j�Nǩ�|H��
U-�y�̟Ý���Őz��3��"d��T\D��#�	o9���t"�q��+�s�0�3�@\��"<5���<5�"�̳�bM��c��zΟ���Mo>�i��%v=�ʈv���܈�N66"�͛=v0MW�Z�v&�]�*#�+_��W�>v_d/>���6��f�>�Nn���xL�����7�WN��JPF?*installation/framework/session/session.php�*���Z�r���-=��ј���;�r��h��BiD��'�p����� �m�}��H���9���"ۙV�)����Y��y<���=��Dw��Gb4W"ѩF%idd��P$��q*����f��ƛ�;��(�*_LV�{��D�"��'�(���؜�x+gJ̴�g^����S���Z�ON�|���1��<
d"�v�9Z%QeA����E�3�@{*L���x�Bed ��	^���N����-p�0@>���T��o6�ݷ�ދn�E�V����o~�G7��*��]�(V&�*���A�Dt�*!���߃^�ٝ4"I�g��\%R���U��L�Љ -C˹
E��T��� �^l�d��!@���4F���L�-�1�wy��S�F�@kԜ�Ur�4�j
���g"̂�Q�߃1�s�}�B9�����Ȓ��2���s�e�w��t����c.uo��[����.t��>�$;�?I�:�FM3�;��ɠ�0ՁXE�/�;�<��)Y����5ʉ��h���"�C�A%+p\āZ��b@��7��ۡ�w���s�C���uN��Dy��T�y�*M���F
i���R�D���T�j	�Y�)�8���I�+�����~$�0��a
������@���ī�^�m�Z����7"�!�7KYDl�ߪT��'x�A�؜�g�9��[�^������&�����m�כ�H�{,�5�Uw8|wy}.^�����^�h�b9���R����4�1E�[�U�X��`�T���q����x�{ys����Ne����B�@���۔�Ph��(�E��h/!8OL���l�p��>X��C���C2�a�{^��=X�y��q4�^�,`Z�e��ZPV��DD�l$^|��%��d�N�ئ�B����ar����Kx%�Z� ���#�ؔ��
���7�DZҏOOa�[i�,�W�$�Tbe>�1�R�Ae7 п�D�N��ht5��,!�y�Ɖ�T�R�U�ƽ�[��C7>��sш�ӆ8/+L�;"��S���G��I�qq��{1_7�銵l����%�
�k��=���b}5�*Z��V�F��v�ˎ��4�h��������UW�U���{��\�<��ɷOWr	�>䊜�T�Rs:G�f�2#_%ѓޜ?3�B.�pt�	�"d��	9���vM����<�%J%wD�#e{��V�/�����br����\;i�G��t��SҴ DT��z����-4�y�rh@�9Ш_3d&���%�{�G�����q��i1X-4q�J2ȴ����T�:�q�9�s6/nF�d�� T69��uQ�C�'Dt�E]�vP
U<�n!����&���ee������nV��	Y��tز��r|׿��Q��Z��\-�G�[g�³�I��[�j�e�h$�g"�8�;���z)1	dx�v��:��T� �v�bۖ�6x�4��.�P�z��	��5�sk�F�n��'*�,��:D�����A�?�V}Mhc�����I;�Z��o��`)W�-"�Mb��)

i�#ރibۿ��:��Bϖj+4A�VB�\m�ls�cό�!�cM��+jh
sR�F^�X.�u4F�YL��	����Bh["�B8�-��C���n��plk�!��я��w�ߤ��Y+5c�5Hǯ{�Q�������d��f���:�rO[=��d~z�G��d�_1q_6~uy��w��9��DŽ����(WDk���Ŵ�{4~uGNO�T�2�[���u�!w@'m�XY\�U'&a��;z��{q��4�8Fc�)�5H?"��׍�Q*<�mi�������Ra�� �C��|�d�Y�F�'*��Rɽ0;x�K��&,��J�X��G�ĊBq43T�P~(�QS�Pz;�
\���[��¼�Y��:��<@L��tN�04�fU=�̀�k�؞�U��Ts��9:%Kj�DY�i�++���я:y���-�Cа��0SO�ߟ�K��<J��0?0y��G��"�B.��_����/�1�&H�%��8��E�e���!�u�~�����;ϣ(N�:�\L�Տ���� �	~�������}�ŷ�[t�F-��D��C?E�}%M�hU�Ѿ�����vA)}���� j�^���A5�c��Y��٠�U� �r\�."ST�U/�{p	]p�ʫ2%�z�<�\�:ȱ�Į�%Y�����ɴ��홋��`�n�{��n���GK���fQ��u��:�ށ�	��6f,��'����M�L�n��ֈ��M5�����m���+��m��
7/~�/O��5z=sv<T���`8�}	y�S8��W7�cipD���Q@�ShyIItIaP/FV�ԑ���y�����#���znGtv[I��$��n)Yl����ߵ������cEf;���$ӯ\�c�J\����dhyh`�`'����{V�)M�gSp
��Ec�W���̭{*�u'r[���Ϧ^%�Ь���)�e��,j}s��19
)|;�����5X1�:�
5���l�gvѻ��
W�Ķ5�6��@P���F��4eo��^��!I��N�Si�}����Z{��J�;���NQ�p��'B.P�Rj�'�S׹���x��J��ƒ@��_�>4r9fA���<���ծ�9�
��T�{a[�ԭav�t��H��-�8�Г�0~�
��Ua(���4�}�ܗ�i��Kp�C�*��Ie.+����ݓ���&\�K���R9v�Bn_�L��ݬϦ\!�m�X�c=��g���(�Yl�T���yJ�����+�W���nErh��d�5K~
�Q��k�]&C"nmGb3!�L�?�)`#�6�Xޕ}��_sL�%�v�b�?�M�(�C����<�1e�ڠ�Ǻ�;
k<��]�J]�,�U@��7������o�z�b�q�P=�ziQZ�yq�U�=۔^U8��[��8��m[������s��8������T��nm�1�t�����oJ�n�d/����)vχx��ݪ#Ď�K��PMYώ:-�V�IE=;J�@��\��$���F�HLx��>/��%-���gM�/��y{��pXװّ��!�L�Dw��'؎�I�F���iϹ��ѝV�g�!�c�z��+sڷ�7�[��Z�O�sي�7�7ϣu��R��=���r��ڢ1���]νU�5�ml��w7�X���Sɹ�KЪS��*:dlЗ����-ё�Z)��h�%�+P�YzB�e�;nK��s�dI��(�[X��6]����>�4�qm�N�<���[�kn{{\�Ls?6��U?A�(�p�@W,њ����>}���_3mv�]N>�n�b�,x��!s�r����.�y���]�����m�Q[��6����ƷZh<}Jr�\A����}��@�*_�(Ĭ5UNL�v���rŕ'����R�{�]0��m��}�����߿��`�d|�3u'(�ƩyXi�w��E!��t�e��W�m�����>��
 �nQ���bk��N�<���B|��>���;��c�	9��E�#wH�H� P質GK{]PDt�Fa�!GKh�Vt����4�J��B������{�C�i�M��~����e��tqB-�=S
����E��}>_����O�."����j͖�$}}FMNH����ra�֎��_ni�8�W��ǵ��,C��4��m�������������J��o=�N��!��O��JPF9$installation/template/flat/index.php6���V]o9}�_�ή
T�L���[Ȓ�FQR5�J��Bf|�{�@ت�}�=��h�6yH����{�OzTL�z��Y�<#����Pr5b�b�:m�Z�Q8�kC�,�.�L6s�$3�p2^��5�����	��+�Zj���Y�.�!x`" �e�X1�:��f�:<8xM_��ȦZ2K���-���ХԖ$kg�[Rd���2�����$�P�q���6�`����s�)IL���^������F����N�~ԫ�S7��0ޫ�R'����/=|7໬��$���lH�`�i����bH�ziRC�3p�(6�n4�(�qɴr�\7Z�]s̆���D(���fLB�E|�ڙ:WP���n�A��lXtp��Y�dSf,�n�r��"*�%2�=�8��4\O<1����9IW�����W6g�j�[�"f���LX�+�l��8_�v��xN�Re�&��-�s�C�@�ǥs���ln86�~�{��&�[��uK�֧�-G?Ԋ�M^�(�w<6�iRU5k�$���F�ij@q0`(��<������K/�b�>-�u�K�|��"�Bi��0'�~�(�CO��p��:�2l.��O��(]�<�~t�B"&
l�']����
ai���ZhH0�Vl>f&�Jʳ��݄̌Z�Hs	77�-̖�h�&�guo��M�K���v�ilHy7�e�f�ASw���A�7@�"�����!��h^f��a�����]ͱdc���SjgLʨ7������v�	ۋ��R���q��u���sP��v�x���$�G�n4�c!�-\��D���E�m�y��n����Œ�\��_G�'�������h��]}��~�r���]��R��=��4�Vő���{{�'U�u7:���~���
\(����;��փhj�Y�gDW�@�Xq�-?�	�G9k�
��̽q'�W76��q�瞐���6UKs��qy��)��筶�rT����_��W۸*D��)��5�?\�=�r'i�z��AU�y�8���&�UH��w�
Q���$�^����ӱI��Y�r’w�W����E�V�猸)�&�5�x��N��bOTk3IV"�&�B�Q�zAm}8��V~��xbjY)�ۭ‹1;�D#�@�]��w�^�m���%�8���L��jW�����*ɕ����pٍ�ε�z�vq7�%S���]��ִ�/��{za���1
�%��*�m���5���H���-�qO˄�L�x9�Q���JPF?*installation/template/flat/php_version.php�$���Z�n�F�?�D-j�0)�NSԑ�uR'1��A��(
cDʼnI�3��],��}�>�~g��(���n�[(9<��wf8y����w�������9��X0-�`��F�H�127,R����/�X�
͂Bp#B6[��!f�=�Vda}�J%
<��_r��s�̥���/9�
{�|�������٥b�p�~��h�U��Di6�y�1����@d�迺��^�L<a��n�7��[Qh��A�
xx���Hf"�۽>�����>-������e�kÃ@h
�E2��Nj��BYd<{��//ޜ__�3����?���6����t��!ZϦ;�ؤ	�#x8�!�&F�DLk�V6�w�2�u�E�
O����`�M(.��ə��_�����=�NƎ��
��z:��b��ŒX�2#2s:Z��ħ����=���L�O<����QE*��
b.9i�L���-��A�@�q���Ņ��w����>��8���̧E�?�bȋ�?���E*6hV��"��rwu�ttI}��Td�%�Q����S�M2���ax~'�A�Q<��~����qoE�{��2(4�-�g�d�Ƒo�
�����
�g�.�k��J�����d��%��t��
������
����������I(o��R|]���2���;.�Eѡ���/�jQ���c%�%�Zm���@��ٜ���&-��5W���J^���C9�Vt�q/Iy'��֛�}�S�{^���G�è�[��WT�j�+���'|&�=O�(!�魿�l�ke�ܛ���E��D z3��Gc�"�Q��Qy�*�3A�vE�e�t�^��`��Vέy�#-Y&cB��MѲZ�/�����ϊ�K
Q��p��*f=K`5�B�F*�v���,Q���nu�5~4����P�a�^��{$�|�V�y��1���t�~xZ�C����Tl֘��ʂiQ�.J(�
�L�
[�pB�RS��V$�g+�|�Z-P���
���R�>�%�#]z6m<3�-�z[��"y��6�x�H��,�Y�\@E)�a��"H�K�Ƶ�4[��d��%GU1B���2
���Ej��r���.�"� ���p
���*OX
1c�xd���������ǰ��	3*��}4UVl%TWOE^"#�B����'߱�����O��8�XJEk,I�r(R���+}�7LS�l���`dp��/��"t�l{/_˦�4!<�m
�;>��<���>�����M"άY]���-�2	������f��T@>fW�LOKm���4Z$Q�~� �-�kA�.Y��!ˑ��J�-
L�f,�С�O�� r^�,�,໳:��K=-#���!K
	=7�k�,�\�!;q�f(�%�4��Q���'�c)_���B�㧖@�KH��e��T�d�����62�M��>��+k�P�0�̗''���We��fo�=<e��'�^��z�ܚ�૨�9왠�CU��t�)׏H�x���P��Pd�<�1%5F%?0{�""�R�`��jZDhO�ڴs&c�{�%
<��]*����84f|g��̖@I���Dd�n\[���0�$/0E��kDM���0G�V?�󨚆�}{Ĩ�ڏo��c�y�ŀ�lDg4���}Ov����?j�s1
�����T��E"C�fm!V�;Ұ�!a�ɡ��5��Ր0|܎~Ws�h�)��m�b�	�jO�5����]����
�4/�J*�K-�
4HUMesw�P)�8@1R,��1\@،���g7
hYc�J.k�8�+l���]�,��1�tMWO��p�l?�Q�)�g%��i� ^�3�_��fd��<`ZM��b@g�8����b�i�'��k�l%�0"=��0�E�@���!)���Dd�<�m�u;a�aoxo�O7��t<ֶ}���}���:�����A��-Sf���U�DK�i��t`�B�sȲ���ֹ��|�����,.�H��66�*�
���;U��$��:T��z�ԏ�'����1Fi���<��LWl&W��������Q��Ĵ�[��vR�^m|��Td6��
�G?.�O�Q
{ k;\������]�
O�H��$AR�6k�b\�e�L5�U�Ib���sS��Ȩ���Jy`M(
8��r���]��zZ���h��KZ���j���Y���;Z^(��T7���X�I�f�mi6M���m�F8�ЖGՐ�yIi�$a�;���5�d�5λ{���.��O@Z�s{&qM{��
	U��R�ˋŢ��ϖ����Ĕg�����9H�.`�{m�n,��n�Ե�I�J�j�����[bT��
��s��D6�`��̋D �>����m��E?�@�
m�tg��QC�֝�ʂZ�:\�}�bk���dS�mVe�ggΖ�ke�&�H-���2w�rQi<�9m��F�-��?�A�H���U}�9\�8Ɍ���S��6�nb��7����i�&���a�9C�M�`�}H�J������oy�[�����/�WC��b���+�{�Rf���N2Р�	Km��h܋�=_*��f��К��J.���htUG.Ĭ�I�&�gl|�"�7���
U��<�������:�ܖZ��XE
z������C{��v��Y����J�j#�Ž&�� ������d�g��������D�M[|�2Y�c��m���S�Ǯ���\Q%�p$�oF��hwю�.2dOt�s�=�W��N}"!����r�if��g����xG�t�6�?{���������w6��9�>ivU[�
�ɽݟw��Tf�ֆ������4���dV��=�A]|Y =���B����(6��a�?�J_�q�����ħ�2���H޽Y�3BG"ʨl������K:^ma��z9�6�orauEg�O0��83��)�uҕRա<fđ{i!Sn��CsaNG׳�g7��U�)�8a_!��)�Tu~m�=��=gi��{�`w]��ܘ�]
P��_
�w������{�j߇�/JPFA,installation/template/flat/css/theme.min.cssT����Ymo�6��_�&(��"ٱkK(��Ű��v�)��S�@�q\!�}G��%Yv���'C�cQ��{y��ܼ}��z�>}��ۯ��)E�i�$UZH��ȑ�$+4J�D!�����*IJ4�Q�A�����dg�q3���0�|,�)YP�`‚�j,�F�E����_W��u�x����R��B�;�Ph�D!V\(t��q��Jg͕����7��T��\���ه�T*�k�$$,��9��l�[\�JN����@]H��c�_��\AA��ߙ�y�q���˱��Lh�B�TV�^��,F�ad���>�(�վ������yl'�Ph-����;![,�ge"r��N}o�{ͲBHMrp�S�Rcc4r�t4�����X�MN�U�[�Oj��-�9Jo8�s���P�<v���L�l|�-wy�ޛ�H�mk��k߅�ǁ���
'ᬷL$wbv� K;q6�y����9���p,`�����\���}g.g~$��J��"%W�1��0u��S�9�i�}7�߱�d���%ӝ�L�"[�
�}��4
Lº���:�7��Y�>�=vQE7�Mi�e�$���e).X�$k߅�����u���jВ�� 
��Ac���a�4���<!��HF�;Er��,i'l7����WIb�R����C���C��h�x)dX��L�l�0eqL� �H�H��
�JKkd�����ĻM&�����J�ǹad�MK�S䋋k�,i�X�h��T��`9�U�PM�E�<�?0V�4D�UJm�ձ:�L\��Sc�rp�ޞ��a����y�As]v�Ԅ����ȸ���
��,s�{���Ϲ�Ga�����K:��2U��sf�c;��H7s�-	7�ܩ�KC\6(�O{��i��l���A�ߺn���I��Ƅ�E�]�F�_u�P
Z��
h	�#�z�S
�2!Ai�_{`S񗝑����*^)Z/�8zǼ�LB_S�YKR��$l������F9+|�7@��%���G{���M�Ǟ�f��m���e;
Yk��q��� ��vB��mk�&�Z�=���V7�"0�ޮL��*׬���Ԑ��P3��{l¢�g�������Y�t4�O��Z1YS�ֈpS�"M��a��S5�I��6�2��.�ڽkH��	 �t�C���Ɠ��~�M�MƖ�H��D��n��Y,l#fҴ�U��z��-���Z�{���Y��Sc����P�����},��1x'k���D��x׊Ov�U�xn��g�oA>"u�	 _���A��rςl-X�~ڜ�
�A�<��N��Qxi%��ۦ,��3|�I��)�=�6�rXP�6\V�!���;�]G��b�������K�c��%�C���A��;�2��̹�m�%͹����i�6�$��!�aP�1��\�g?�p�N�]��(����%Og��A�*��t6%��wO��?YB렫{�u���x��k������8���;��W����Ǎ�K�����x�NG/��4�]33rUH��
w�9�g>x�#NI���T����%�3�?������l���������mr��X�DJ9)����JPFB-installation/template/flat/css/chosen.min.css	x(���Zm�۸��_���u`ٲl�z%�6ȡr��.헢�Qm�+�:�ڵc�d�P��MѢP���ș�p�y�go��۱�$��/$"�@b{�>��$ mG��kN��$D�8LR2���O�E��?c�L21A;!Rw6���CӀţ��	�(K�|��>�~ȣe,�AX�5�\D�.��Y�x��,Pb�ޱ��v'�]0F�=�[�c]2�����#
H���>3?b�,ƙ |��û�?��~���;��
���[��	(��-����F�2�0�HH�0NG�?~��ϟ���g���e)�(�xK2��L�OH��4T�a
'u6�+$H�F�e���9��M��V��)�uLYF���f�g�4���&̰`����L���,�m��4#�m����/ĝ/ҽg���
+��2e&n��Y1�bͺ���;"��ǒ���=M���x+a�3��#!g�I��X��	��s��b�$${�0�������y��h��`��QK��=O[��$t�l6�!���ŵ���?��Ξ�g:_��A����pv�(�q�D��R�R0hq'�F�^e~��S9�8P�j*N	�s�|Չ6?���g��)�	 ��Qep�����ij%8&��`�,xX�O>Ƙoib)��J=���@�;�h�x/;����8  �ǩ'�^X�<�v2�i�!��m{�`o������U��K�r*I�������0�VD6�u`_-��5v���iw�#hW�VY�lT>k`��Zq�^σ��晫�UH�
MJ���SZ�V.�}�I�Dn|_���,�(V�w���D��Wj�^>�q�	!�`wo6K���u14ד�4rN�"
$���I���Q��<5鴌��F�V�b�p:��T��Z�b��B���R�yH�J��®*(�'�lW!��<�s1�"�<�ϛ_��=�R`�{*F�KZ���z����yO����6��xy����5��yt7��T�0+���a�&�1��Na	�8I	�~�ܝ��f�զ4#kn�!YYͰA��Y�I��G�v�v۬�S�H��!�F�2��������;���"���72mѠ�ҡK'"c�M�Q,�������� Q��6*���Ʊ57��t���}d^e�*��P�>�d_����
�itp3�d�'N7
��0���Q&N���gͫHYQA:�[�bc+n\�W{�JXq6}�Q�?�ZV2p�A�Ȏ�xԵ�ҥ[��W#m �	���٘�N�6c�?�/�V�TԲ�3��J�<�]�E�B���,��]��f`b�=�nMse�`�Ę�s��,���P�cAZ���lSH#��J@D�
οk4K�}��>� JE&0���IŐ����U�P�����DM��>�q�;~�Hv�tP���^�,��v�j�����*�Qa�ds}]N�]��y���
�Y�T��بP����H|�^�����T6O�<	�7���(�b*3�BsZ��ș�t)�k�W�켬U�c���b��J����S���J�@oX�Rʶ��JY�n(��cW��ҐA��%v���$�74N8uB���0�u��?�j�z"���4��ULj����#��Z��ECe�2�+^�C��N#�|a���~eg_T�ݶ�-C[`-�������wNt������vC�?P�?�?6���VW���&�}M���������ϋE3���z)��J]��2��I6?�*azO������H���=�\b�Y��U�5g�
2U4@)��{������]��ٱ��K�������2����K�5�&P������t���w�t�~|�}��}G����^a��^z��u׵�k�����K	�ڻ�
�vn����M��=ZK|�c�),yg�ެh�:,4�*��5��WG�N�����k�kY�W
��̻@��8���"_Y?w����=��i(�N/E� ���":�"U߾)�_�E���g�Q�Vt��	�gV�#�fnq��d'Oe�":C��]�"ָ�0#��O��^s~��i��Bu��~�6����-�.�C���"m�����M�[��[k3��ny��.5x�������ż��u��sQ�tXmb���Z�jW_�Z�u�E�8pߊg��*�8��cR�XPp�5#����2��T^�>K�L�D��Ĺ��j<�,���jd%�̗�0��MWa���Nj�¢aoA�J��2}�¼$O{��� j�5�rl�“�<Y���t��~8|��"��0]���M-ҽyN�ΨU�~�7JPF@+installation/template/flat/css/dark.min.cssfH��͚�n�8��lrcg"�4�I��i��r1(���([YH��a�݇�R/�,Rv`�I�?���I�ׯ��}�؀��c���^��w�M�H���'8�[a	"ފڳ9
��Nw��:�>�`�9q �Z�o��+F<�;��vb쑛��c#�@���W�]L�>����q�L���K@(p �T4n���Ú^�-lg59�M|���gg�a-�+O\BЉJ�8�=)bM|�4�Z�vP��0�iGrZ"�Z�:k+h�g��HѴ��Մ-��+ӽwo�k#�V�,h۱Pi�ZQ�4*�SL51��fB��6���(�]SC�!tr�5��u�
[�'GL(!|>�14�<	��خ��˜8��!�P�{p]�Q�����*��`��˷G����W�U��7$�!��3~ƒ.v�[�q�BE�I�	ql������8HAO�C�l�k�,�hV���؂�ç�x�j��#�
�&��۵Ȯϒ�%�V���JK��yW�NO�N?�
�u�B
'w�n.�A�����麡2y�--!umw�f��@s�����P�T7�|�0ciƮ���clh�;=��1ڎOq�1���}�e�
u�n�1�k��϶fs�4o�HT���NJ |9~�+��06��m-�aw`
;ɻb<6�^)��r2'?�SDɀ�
k�L��F�)�5	��C:����E��TD�P�\$e�D2M��A~jlM���b��AS5��36:����
��Q���x�W�{�\.(T(�ta��B��$�hש<�f䁒:eB�8lh�$h�(�瀒:���6lh�0@���yZ�H�!���(/R�T2K�N�z���ŋ9�F�8���ؘ��s��
W��V�\�b	�9��b��$U�L(tgX���*ݦ�T�s��J���[���{���/�� Εc�v)%�
����p���S��r�Fp���.���54���s��6o��e�?GˑJ�)y�xcUld�
#��卉(�QϚ۷k�-"��?5��14t��G0�B�c�qe�i���a�nǔ���p]�L�����HE��X9��B{y�hݾ�������_�>}���������C��V��Y�+?���=y%��ݬi�`���	=S#��BR&���H^��R��a��S������0�i�q��׊�s�0��zӛ�][�E���PJalf�j�Oƨ�T��.}��ŤSJ�Q;\���	r���܍�D��[�S���/(��n�$B��q��~^��y!H�<Jľ����n��q�}V�b�
��K��w�8pC���pSʃ�*��}��M��Tڟ$U&%�I��xg�ǵr���۴説�K.�z��e��ꎆ�*�U$����������<�#:����#�C�W2PD�d��A
ύٚ��
�l3�bњʖ�˘=x���
�i[@�6�i��69C�!*5�`CՁ!	T�&H�N<H5T#�R�I@��<��9�s$��Hpϑ���@��#�|G�Q��H�:��t�h�4��q�� ��S�9=v#�ȣ6��͑�)�F���i^4F#�Ȣ3'�f�eԂ25"2J���@L�(�J�6�E)�r:�vإ��ȀK�h�)���p�c�,�CX��+
��ڀe�J͐��"
�H")�0�
�X�b��(��p�S�&GGLN��+y	@�	P���T�GT�#�ȈZX�,&R rR�Bp9$�L
H�t6�ݦ
���V����zd�I@���bv����DB���1��l-l��k-�.گ�JPF?*installation/template/flat/css/fef.min.css�I�	���}{s�ƕ�)�r�j�E�k�ک8�8Im�lś{�Vjj$�$"�PY�*'�ؒf4zL?�W��q�~�I�)_�v$�@��ģ�QvU��c�׿�}�u���?>����K����ۄt���c���ѿ#��۵kO|���P_�mc@j�Z��/]g�皃�_�ֽ^ӛ�F]o�ڷ��б��+�7{�s���r�Zc&�[~/���.�=F��o��ubװj�9��jߚ��C\���Z�5ǭY�O\���v���:��ֽH�b������4�%n�f��3w����aÿ�E|�7���E~3�L��oC
��#�[�U��}�>�?w,������b�]�����/]��Fȏ��K<ɣ9��X��N��G,�>�:�gs�d˴��lwz�O#-�l����F�����iyX;�|��FTFsl��ޤ���H�δc 
0Aj��
a�a�A��C��o��a#F�q�c��Yd@�јѱ�vn�}:b"�:No��O�T�!1�� z���w��p}����L��v
{�@:Ŵ}c]�CкL�1��&U0S"�֘����u����m���L���Mw�A��G�5�d�d42\��ؤG�l#�:���i}#?�6-���1�7:ЌL��>10�vskL��пu�&��Sæ�ϐ��l~i����Vݠ����L�P�m�b�äu�e��TfX�M{H\10�^i]��d��r4�i�����2�ځi�p����~�׶�,�hwr&f�Q-=-�x:LJ-Pf7����_�����}Qӳ�U�5s��o-�c����[��'t�s�Ȉ��Ű�_�Q�=��%uZGF
�#���b�k؃�=b��t>I�5V�Ũ���j��mE�/����En�O�2}E_##�݅|����Ճ�m?����`4�]�?��ȴ��KS��?]�7���t��,B��K-���n(��s�mZ�kK++
���3���3��]��_�Q�m)�ϐq6�x����|ӱM�?��lJ�0�D���.u��G��ئI�:�}�θ��ڳa�f�t'���X$$�L|߱e���#����	*C�BF�~g�ne�IP�[��}:���어�R�]õ�Z����N��ޤۥk&�`.*��8C�<�Qc��W�|�7�
�[X��9;�o��7&�v0���'�0��=��fκO��א,��M���wbv��E�;�&�` vD-�a��}�Z��b�m�G�3uǻ��\cϣW`�L�Ljp�t17�q���5]j?��Ҧ=��թ�+��ᦼ"��n��e�_�`�N^��_�t�5w���=���[g�?F�lxr�3>Q�$o.H�k�
IJ��!��� 	.�-I� |����4��w$I'c �����_ �(�`@��w����K�Fw@���p�8����W"�1���:���������[dž>����9������%���(TM��p�� �P�	$��s`�өӰ�j�#�ę�lj}�0*�C8)ktx�DLd�cڪ��z�'�xм���@���x�?E�P�F�x�EL�g�i'�}(�g@�s����M�0����a⻢�~tA��E�K�p�=ꋂ�/���;�P~_��zNw2��*�#���@`���ږc@v�h��\�T5q�@���&T�;pL�
@��}c�qM���`���
վi��h��EuE��fN�;cXo�E��?�mh�W�,�잀�:�+X8�HO�x� �Hg�S��̑1�FW�ʲ�1���D��A�ꃚ�]�������B��h��4�a���P��Gf�ڮCpظ+ZV����$A��5�a�4�f
��-~)�y�6v�X/����v|�O�Y�
�D�0��nã�8�ē�]UdPg��@�DŽ9_Tt���(�Ba���t�-�Zj=�k��܈�c���Y\�.D/nlB�DZ1�	�	q aA!ϧ0����5�;��Zt]�g:�0��ѹ�'��*zy.��<���璑�5Ds?��V�}qL���k�
D�:�c��Y4�=Ns��Q�ִ�U)Z���[�hhHj{A
G\�-Ȅ�e����!�
и�#�����w
Աh6{�ήM����QSB���۳�P�(Y��A��
�u/(z�l�����)&62�mq2���bk�q,� 1��b���GpR��B��7�C��kX�BDq��b�G��}�9��Y�q���	ԁ�q��=�\�y������@P�����N��JGo$��C^8�{�&�	��&��1ocxL�;�A<�������aO�G?O����_@���xFcz�eD�!E�HHS� R���R���Kw!���6�(�DT���P=�����QAg��ʖ~�&h0�BDI��sh`�<8�~�i�X��m9������>0\��<��Ur�h9�.�);�؆"*G��   �,;ք�l�y����|��]�8�5�M��4��u\��1>������1PO�;��4��aȚ�K^�8x��@��W8�n܋T� �e��ג�:;�7�W�X�}��Q�NrW�`�?��79�t�@�#�$�o�`�}����߰y�s�F���Dt𮀄Lƃh�����ǡ@��w<�Q���@b�U����h�ģ����h�`�@�B�1���~G�M����'��9��s�QW�o8�m�{��*4�1���=��*���h��;�b�a�gz�-���r��P�Ȩ��\p���.4U��Cx�R�i䔐8;��2��D�Sv��f�	�>�0����K��zyFĥ3Ԉ��[�Y]�wPp�O#���~����͈\�>-4�E�uߴ���c2���\>�p.�kv��i�j�˿�(�q�UM���p�� j����4��P_g�a�gy���cB���E�ƀv6б�<�����&����
��4R?� �ٽ���K8�);@����y�GAG�(��ƪ�
-O���Q��s1�P£��Q��|'���;Q7������GQ�"��w^�#���N`�I�k
��9�p�"F�9�&�y��]�{"��p(�$-��3�B���L�z��`v
������{�I	�؆��]��a���$�B������f��E���s
��1�OX�M��O0<��1<���b	 ��%
_�!ɯ& ��JJ�X�d?}�Ix_K�=d�?}J��L�M�.����OB@�w����8�����/����	0�w�K$a|7v��}$a|O3�>���_%R!���$1�_DZx4��w0R��D
g�#��x�(a�},�,zv�!���1�	��~%�%����?0	�'	8d��YI?������_a���/b
$�w�7*a�,�
ٝ~#��������)S�9���x�,�F��~��!���D����I44�=/�0��X\��$
���Q�0�١.��@2}���gwQ�D±�R��۰�vvK2r%��qS�����$F�w���m�O��S~�,��H��˟��J�_L$A"�g/C8	�K	<���U'�}%��Bg� 	c�<�þgo�zx��[Œ��-(�j�"��gq#	���\DIǭl0�y�n#���}��Q��=+�[�X���WL�~H�~-�$�q�����VI�C�ߋ(	��h(�~�G%�C��#�GL�������'L�q�ř�>��e=`�~uj���㳿�SHd�Y��C�}&O!�7�Y�}.�$�q�{���C��N�7�Y���c`���`$�}7�����s"J��
��?/�0���@����1+??@ƸYk�}�\��@	�Q2����O0,���h
<GqG�dax	:no��1dC��0	k�
.��B�_�H	�y"��~�$a����i)�
%�/&lCN��L�R���
 Ԕ<�[�#҃1���$}-�߆V���`ָ�;�V���Nb���7E*��ֆ��BD���ˏ��#ڱ���/!����l�� 	�{	0�+�$a��У��<��_�`���,~*�w P��EmO|�D�?�H	wܴ���C%��FΛ��@%�%�������9�����H3��Q�Oh�`�k#ዛ����0	�g	8|��L�9OM^<#%�q�y<t|�?`8�E�tC{�/�	�s	�
L^� 	cܲ
o�@�H�B�Iw6%]�P\x�Z�f��Pl��J��	����H	��d
�^�C8	o�FO,h�x �P��"n��|P��(�$ٌ��.�:n�[]�#%�?M����.^@Ɨ`�L���m寂h<�%�p���g	{[���oJ�̼�H�kBQ�����5nݺ`x���H��n����0�G�"���;_�q���
'�>47_�
��q���6�l����5
���!Qr_��J��g;�P���ޘ�n="�+�E�ޅ��_|$���\���`�X��'0R�7�{��S%�[�>�����/0R��D
�6=8L}����mi8a���[,�%���Q�DBܴ���_|!�$��@�|�#�s>�[Ծ3@cу���5nN��)@~pGDI8�4�`���0>�cQ�G P�7���
�0	k�V�B���@	s��^_04P�݇����q���$X���0R��
����"J���`p�nC���e�@�`��<�l�m��Ǒ�m�} Ǒ5���B48Y���>�;���82�%a��W8t��hT�?`���@b^�C�H���qP7�?� Xl��/x�6�:�1m��'104�>�Au���7b�z��79T���026��� �<p�62z�c�w(�<����� .t��^T,�4�Ãh/�wb��>�!���Q�V9|!�DN�F�����]��U#�?<�0PH��.×'Q���H�0�9X�����IZ����G�!mĻ�!��HI��.u�O�ލ��=�U��Ǒ�&�ۜD4cô��;��	�?�p,�n�Xt��sq(;���C��}ͦXP��A_lwx��Q��OoF�-������CAc�/���;=~�=�4�D�3��ß�PCx�����1U
<m�;�p���r z~b=/���bN��䀃�$�pp��q�c�� ��5��A��02�����	�V�O���i�
�a��I4'�oS�h�o"�5v����(�F;�̿�A�`���flA-�8��02|\��q��N� ��hR�'<9����Q���9���hÂ~�or,�wE���
��H��02�X�����߉#�X�0��<�k����[�����/�$��͂^A��|YH2!���|�2��c�]�~�@q���	Ih���˯`�$��'St�.���k0R��W�Ԇ��}�3*a]H�y-�&�����gR�ĵ�.4�Z��h�!�sQ��Ű�߈i�1x~�Ph�S��]h�d�B�!�D�o%���5����1�$��8�{z�''8�qrOH����K;�����N /�#))�B
�C�'�0RR�p
�B�GK�q2Ր�bhg�GKd�IH召5�u
�_�4�w�	L���Գ���s,���d�;��u�1��	sr���B��Y����/V��?'Ӱs��M��֦3���0�;]���0�4͏�ag���¸3��`�N��]���:�FJ�W!�>��IX���{pL]���=�^��/@�$��~3�0/ΰ� ��dԱ�[�~#q�g��}���5�����"%���t`��C���8�d�A~�&�.��sD�HP\��W��wi��hN��3�Dۂ�Ŷ�8�tv���y!	u��tt���P�D�a2ў1t ����c!�,&͞�Q��=��<�)T�g��cwC�>@��r6�8�BY�;���N�yBp��1�?p{��}�{�
�ǜ��Wx����xY����#�P�pt��`s��,�n:�/l��ɋ	z��I�
^���	��P���"�\������Ir���	��{�i"i���B]bYP�:��&��gB�O^�`�z�Q�Ql��QT=Z��,���p_�Q�)鞃���8p����I;8Ų�^��SGQL����e@{N<ms���^��.�����u��ף(����W�#��6��G���Qh�8�,O@�z?>`��j����.�C>m�)�qNjś��0
{���>�-"G=��
!����4���޷,�����r�#Do���[,�<�q�AǸ�\�\%�I�BE����Zg3��0v�5 ��|���A/Y�5m�����W�I�+�mvc�@����g�Fԕt˫�@�=�hˊVP�iJEf�d��P�`��NDǴFm!1o_0��@��w/�}��n3�F���#5yIН!m}���F�kr�dOD�Ӯ&�R˷����p��t�P�	�3�W�n�k���W��S�#����wN���Y��T��EH�d;�<�W���ҿ���-��L�]v�v�ۣ��8�~�S�:48�f�f��ˣ�@�@؜C!��c�]��>?ɬ6��feum+�M�Z���Ǿ3&v�)jP.-��Ԟ4�OE}����+Taᗗ�"�Ծ�ͥ��:�w���A�žh\~ܥ�i٣Tu���o9�>c�)��r��8�`Aj�Q�@I��Έf�EF��5���旐<�����g��f2���7��j�UZDZ0���h�3+L�Z];Ԗ��:�|_E��!�ק����JF�fmemVq�"�^\Oz�r���A��5|Zc���tkOf�R�2�j��
��o�J�6��&1������дz��R`Z)0�)0k)0�<f�;�w��
���OXK�?a��������t\���j�Z��.���Z�e��VqY���5\�.k
����Z�e���qY��T��.����*6:����o��V��S�f��
܁O��#�0�w����O�NP��5j#���̣HL`k��Vk���iy�1��J�����
��+�
I���'J�]�֛���S��v���f��t�*5��,��	�/��GH������]o�����̬֤?1�ʹͶ�M������*\�z���A`Zl�F',�*�U��#G^��œ�_ZH��r��
��D��%��l7g�Y��s��mE�4���F����`h[�5Xo2����ףZ��7�%��6��#�
Ξ1�v�,c-�r�qd��5{��V���ښޤ�f*i֌��D��ٌ|��7f�4��yհ�Ŷ�;ƨJ����5k2��u`i�_�����isy��(r��R��5�K���H��Y�иF�NuAK���o_��-}�I���C��F�4!�>�.��z|�1x�~�о�!�7۶?��k������Еr�%r�8n��RʽZ"���j �uN��78��ƚR���op�k�u�}�Y&9�3�j�2�����TK�Z&9�;77Ԓ��I��

�j�o���Q���4��P�\���54�s�^.=�W5�������r�����9O_/����jCS;�����z�ZCS;�����;�ޔ?4��ۧd��q	̒,����k��V��4��x�2��V�}�ʼ�[��p+�>ne������yP�Q�}@�ʼ�\���+�> re�D���ȕy�:�bW�}@�
�hR�}@�
��^���+�> z��D����z�B��W�}@�
�ȾS�}�j�@@j�cc}�:�ݫ]�V��O�z�>�~�\�$W>ɕOr�\�$W>ɕOr�\�$W>�|��������h=��Xӌ��}�ʼ�[��p+�>ne������y�2��V�}@}G���+�> re�D���ȕy�2�"W�}@���]���+�>�YH���+�> z��D����z�B��W�}@�
��^���+�> �N��Ы�>{E$�O�_�$W>ɕOr�\�$W>ɕOr�\�$W>ɕO��')�"�j����cM3Z��p+�>ne������y�2��V�}�ʼ�[���e�D���ȕy�2�"W�}@�ʼ�\�����> vu�Į��f!��D����z�B��W�}@�
��^���+�> z��D����;��@��<��\>��έ_�$W>ɕOr�\�$W>ɕOr�\�$W>ɕOR�O�[�:�2�x��P���,�~��4��U7 �n��9j�2G
�V���5�[��p+s�ne����Q��9jP�Q�A��5�\���+s� re�D��Q�ȕ9j�:G
bW�A�
5hR�A�
5�^���+t� z��D��Q��:j�BG
�W�A�
5Ⱦ��V^r�$����ʠ�2��+��ʠ�2��+��ʠ�2�y�����"�������r���Y�5��Ԕ��L��4J���u�^Uҷ���ĕS�)�h��V��:�>C×>GZ��W=�g��M�uF}���ERM5-�e�\�ʚ�^��(�6}E=�.[�׌=b�۞��;LE�h��ʕ#�g� �'�O�dL�{ҹ�7-ky֏�&�]�z��q��SG�Y�Ů{��.S/X���b7�H�#S��3���ۯA��_�y"�c�&֭}���~��?o�k�k�:{`�	<����!7����C�Y�r��7�${�=�c��r���)xh�>��Sֵu������s]�l�_�h
�
�j�6�Ws^���T���P��h�0������mUMk�jE3��P/N�*N���O�>�İne�����5��=:r	#�j���I�2��di6��cj��Dͭ��y��25C�!K+��4BD���B ɣ
���O-�
��
�%�<��g�j�݉�V7gN���4CPg
s��3�!�:_���i;1�<)����Ԑ�ȣ�$G�N丌/O?�5\��mPG�7��*ɟ���4�ԙ�\)�L�s'�ז�y�R�#O�Jh�8C>u$y�!��\�;O��&�.�<�o�hu[��V����4�Ե�L)�ZE3K��%%(�E�n��vq�|�H�C )حz�oL,?O�2��~�O��䯧 K���(�T��`�i�$Ir�?I�<��Wy��Tgȣ�8üC�Z�b��ƭD����'�M���B��g��c�]b���n

�Ԋ��B�v����ۯ{�3��A�(oF���O�X�f��d���3Z4Z�c��q�o���6��gŝ��K��s��5����8�B�}q����I��
�-����y�28q��FS�
��W{�$X6P���%�`����iM\��,Rԙ��,�-)(���"'F���L�΄	vj�GG��`�O��{cB�32�[@�z�i[�MBMD���ld�l��De�Ug;6��N\��;&ե���~�G��F�r}cdZ{�]{ʰ���Мm?it���|2z�����xl��|Y^z�R��7������a�YZ��v�ov���]Ӱ�=JU��k"m��٧�yd��4���L�4U�Ɖ�uVrP�Zt�KX��WL"��\�H|��7�':�J��x�����T�&�b����W���ӎJjU��rN��"u�Jː�[�OU������U&��]��z���t���LI�C��ԝ/]��]/7{���;���Nծ2�g�r��Uk<Gw�ͮ�-J�Z*�4u��Ö�*����(����+��e斕��*TnM��ڊd��ڃn�I;w��nk�TR�-0��^�h
�.h�v�k\�z)>iRH�Nj��^V��y�/E4tT �ܚB��
d�k�Y�۔�U��[J�u��qU���.sU�*�=�Y���I`z�9e�.�D��eݹ��]��|M��#^���kME'.�g��O١U;祗MYgW�?���R�aH���U�c���^%��`?Qz��=tv��e��"K���$���v�Z7�7�ݐ`Y�Nw�e-�$VVI�te
��nu��uoh�ݶi{į5kl�
��;�ך���Jsc�r����J7aɶP�NB��D!O�`�<q����Dq�C&ӹ�4z�շ����a�B�sц�Tj��M���U[�n>�n=:�+}�~e��U�4�>C"ኄ���JUu�P\�μ�"��ud�n_�
<S��_�\Cv�X�~��"��T�a��X��k��n�}����)I�I�OL�k�j�)��CLN]���MY������Fk��Qh�
��tم�T���rl��r�׼�Pr��B�J�.Ч$e&M<�ρ�B]v!C�)��j�9�.�e2�R��.˱=t#��c�R���'̤FF]�!�Y����
��E)t���Fd1�u7���*�U�"=uA5����\�4o���P���4*M�I�S�x�%�Ȃ.`r�P�M��N�n�/��^�Њu�y���#��T����|]9g���B�8\ I�(P I�-DA�H����b}K-�H�;ua)�.�Ǎ�zk�P�r�I-�H�.e1)�RW��[�9�^)��]�Q%kDO"��̚�4HL�,���v�B�C�gW d;�'�Ƽ�*�w-
-L�C[�	j+߼��%�a���4*K�I�!���Vx�Vt�a�F�-�O#�6��E�v�Z$)3i%�����ׂ۩E�l�QO��^M��r4a��(M���>�VL%��j�]4=�c� �\�K��%�T鶘�8�^���_Cntn�`~
�T=�v�I�HU�ř�mz{���R��Kzy�k�R��VxS�i����c���g���@4��!�~36���92�'ʤ#�����dPƠ�~�l*�H
((U��JǗQy�H3�2e����s���ƷӍcF�]�������P<��pS����뾂�Cl�������ȫ�?�ζ��?�R�z�%�v;�g�}�T�ԗ
�K���~���.OJ͞�ʧ�Թ�f����*5�j�+��*5�¶����]:z���G�b��m�L���3xS�xT�� eS<<d�U��f���fT��,��hX�*�ܚR7�d�UZk�#�X�AA��P�>|Iѐ��4����U�k��������6)�ZK�ǚ���j��H#�J�?�ۯ�`�=e�h���JlRSѐS+�;SKR�i$�:�%�\[�����k���3}�@��9T�Z�(�>����*i�I'IE;N'�p�L'FE_�$)l҉)���3�$�Ծ�F�ğ{P��]t�Tn���6iD)
��"��F���,J}�&��Rk���MQe5sq����
�T��Ɓ���ۭ��E���f+Z��2�i�����滁�Ʒk��J����q^��.�u�ǯԱE�|�4p�/N8܏�˧'�Tǟ
�)�ul�Lv�q|A�hX�L��K�u�w}�'��h�I���qz{7}�m���k���v���j�_eԈ�ꭳ?y�Y[euƺF�o��W�V��c��qL�~��e���tx��	�B
�4���r�юJq!�����#���2�nj����G�E�Q�����
�a�
����%�����w����gԚlo{i2�>Э����m«�31�;�֩���왤$�<�/Y�D�q�w�r�M���Mm4�_��A�?�=ȓŋ�?.��/zt1���ų�3�A���9��6��9X�3�*j��|���e��;}x�"~�"6<�;�R��G��8����v�!�nw�۷�g{ь��º����U9π?�`�4{�DA�'$���|�k_y���|�����w����|��~�߷����	0����ǺGB�*�l)�|��5{[�u����0�j2���ȴG��k���w����1�!&�e�m'��79�EC.*�.M�\j.����B]g��5��Ri��S����r�+w�<���'�Fy�b�ÒV^�|e��j nh�S����r�+w����Z~�S����r�+w����ĥQ�z1�a�+/G�2pG�[o �}�(o���䅕�#_�#�m4�`L�mS����r�+w���r'l��(�<,ya���W�Hy7-Ke%7��X��vr��e!�,�fC�
����Q��Q��s6��V���
�h��*,���x�C�Z�C+�u�鋫�L�C����
�ZA�M_\�ez�~h�
����@���UX���N����
�!ZA?M_\�ez"�h�
��3��F���UX�?������
�$ZA�M_\�ez%�h�
��c��L���UX�o��Ήv���N��	��x �L�D����x'zA�M_\�ez':��ZC/�Rt5���R�Cb"zC/���4}q���w��z�D/蝠鋫�L�D�}���N��	���
��Nt�;��z�D/蝠鋫�L�D�}���N��	���
��Nt�;�7z�D/蝠鋫�L�D�}���N��	���
��Nt�;�o4Z��VA�M_X�yr������l�2z'�`�^��I��RX�|,�Dn��dlo!�gW��ذ� ����m��\��63�5�X��ٳ%K��:��$)�3tD�ez��3t��^G�K��M,�[�A��1@
.uL&�����f�hv��p��1Y�&�'��f#!��sr�T�4+r��%�O��5�[��ȴ��K����a{K�_�L{�I��Ԟ�����E�^�ey�)2pH�{�\Z^���q|gi���!��5�wM�Z�(��}�ic+:�c�6��͑1 �u����N��	�0�a��V���c��m�ӳN��y����R�C�;���cO.��ĹD��#��iR�؅#�L���yE��w��y3�X�)R���#�̢��R~S���/�6��S3�����iZC�d1 �9���j�Kx9C��*B2-k
���V�$KyRT_����!O=͒�QgO�䬫�Da���,�3DY˶��D�i��R�d���ܱ��`t������	��k&����5��)s��m@5��Me�;C��``����:q�}�Ҁ�EnjN������fp�@!]�
zA,�iq���l�-��[j@�C��x-q柄�%$]��=�U�e��%��3�4���ٙeE��q�WZԘ9�UE!,Cyx�Df9���oNY#�	�9�1�~�2Ǵt}v.z���3��6��7�6uÛ[���.���������5�=�˼g�]&~�ѓw
���BwIƗU��s����d���r���w��>SW3��#u�s3��\Nv�Ү7��UK2vuU
�X�|�&{T�x���K�
j
�VRg�ٓ��W_�4e�Wx�NiU&�WRk������k�J#��"rU#�jz!���Lv�e.��1�&��Wm(B�Z|>R_��<?��x��^b��:(�%)�~/\������r�B�
^��XŒo��ʀR���Uep�8��?�+�<�~����g=�v��̃���=[S(#�/�w�~�L�R���=E���e��e�a�b*	���1.^�dR���\��6���%ւ
5q8[���KA07|k,(�\��G�E�|j�}Z�t��Y���bn�W
�f<`���ֹ=U��|�T��5��`!H�����k#v���Մ�
��~X*�
���S|?|
��B������l�>�@F8�e|����^h)Gt�[���U�s��S����X����]UEO�H�-3�Nj���J����&=U�-Mz��Pz���4�6�CVi�+�t~�E=2��P���=�ܝhF�<�ۿ��	\`q�%CAE����I�E�<!�ZNC�H��&���u��(�Q2��!OT�>~����sm8�]��~I�R�b5�]�(E���b�N�wʵ��3O|䥪�;&��q<&�!��i�^�؊��W�m:o&-����+prfOX��V��z_�V�
$.X�����<殚��h	!��')ܒ�p�����[�J��b��i��Й��E�z��ZZ�]���b2��X;�VB
	˄����k.��b5Z��ԅM��x���E˘Z��@t��̥�@�c��f!���Ϡ6��g��CjI�*y�d��xM����qS�8ǖ��`�W|��|���
�Ԏ��
s俘��
�n��ɦX�R^%j78��Jw4rr�?
�5��{����?����6�
U�O���:n)52�ʑ��YZ!�n�^���.�v�𫽇P��D���6����2�\4��rJ.I��26���R�":��K���S��?�
M�S~���V�����J+sfNtt��|�p���f�2�6����4�>5�ALI6ќ�4�h.�<�(��I(�6Jh*a�0 ��ʚ�R�1*�J*�4%[I�Qzqʶ�2�5���/P�R�F�t ��Pŭ����J��RK6;f���(܌{V��΍V��6���,�9}i��\By�S)�P���T�r�u_LVݕ59'��ch�TziJ��
���m9ehkJ�_�
,�L�N�@TE��[N��}̐�J-��RfǗG�gע;2j����U�Ce)+�[&4�B;��;#~�z���`��NNW5�O��M��7�u�c�淪7� �3M�����Տ�-CY���%~w��1�^��sΖ��>

�]VTJe��j�7)�TvE,m9و�t��������*�Te%�(m9���>n ���ܪ��1IUVr�Җ�
�u��j镼ZY%c�����-'Q%�6�J���*�Te%�(m9و*y���"����+�dLR������d#���F镼QY%c�����-'Q%o4���Jެ��1IUVr�Җ����77J���U2&��J�Q�r�U�VN�*�7��`�*���(oI��""͆��_�F�.E��ľb�/����Ҫ���*��K���7��H*���(*�Ҫ�$q0��i��V~(L�.�����/I4L��a�jC+? �UCEUZ՗$&��A1m���Ӫ����*��K�И�����i�E�PQ�V�%��i|�L�hh�ȴ�bd��J���D�4>L�m6��eZu�2TT�U}Ibe,�n4��ezu�2TT�U���%��Z�l��G���e��J���D�t>Z�k
���b��Ѳ<�-)\U�
�)h�U]]�UiU_�h��G��VC/?Z�W-CEUZ՗$Z���2}���-ӫ����*��K-�h����ˏ���E�PQ�V�%���|�L_o��G���e��J���D�t>Z�o4��ezu�2TT�U}I�e:-�7z��2��h*�Ҫ�$�2����7��e��e��*�:OyK�wF��h�-���T�O/�^*�TlzyeW������T�����|��d�QSY��I/cz����?<%SmX%�{�4�BeNS�ʙ_�)��-�o��[��]���?r�2��XT���E$z�\��������
LJ�µ��ZI�(,�"��JT�_p�mǶ��N��^�+�.S�)�b����L���2*w����]�W|R!��VQ���*���BY��bv�P8Xk�q��0t\��
�������+��ˎ]h���u��Ȯ�Э����=�0,s`�M��������CJ�3[���"W2)�TA�Pr��l��*��g���e�\�i�UP��\ZN
N�y� ��qe����^�WN~��=�g�0ҏ�k������xe��Xtj�_|25�6��q�Zs������?[�?�ae�M�
�������d�ع�5��K�\A[R��j���5�|���-E�.V���ۆT��Xa&�稴R�߭$��q�[a&�#��R��$��q�k\a&�_��R�Q�$��q��\a&ޛ��R슮$��q�O]a&��Y�RW��#���ײf��6�":�<���Y��V�BVͣP���H,"@��(�,8����i����@0rc}s|��a�j�K1ZSo]�(���(���U��*Dy��\-�*Dy��
Q^�(�B�W!ʫ�U��*D�?3DyQ���(���qz���rq4���.1/�O��졒��
�go��& |���a��	\�F��-JK���"]�΄®S��sv]�Y��a�f�K�
�+��.Ż��]��VQ�jtPz�Ԟ�/RU7�ᡴ��yh�H��J.��X+nZA����u����qnߺ�}� �siY+�h��VcJ�U���C푥f�����*�RXˊ�V�H����۲�=�ip���vQ�3�/��J���og����h����IJ³ɷn>ZQL5��
��4Pv�*�BU��M��1�}{
����bQ��^���2e��֧*G%��*�DA�,7;�i���N��@�Ie�@�˽�JX�HpY���e���P�`py���>s �bm���cB
#d:����q��#A.!�TN4��[P΂"�i?��I��R����J7���i�Zw�q�:�x�~��n3Av���?[ǥ	��v�s,�W{��e��$`��hvY��^&	���n�f�pbK@������~0�|��x��`�q�[1������lj��2|s�̑,_R���iYӫ/m�&[�V�������Th`��VV�1<%�g?z�n�~����nkU��]õM{�o=�b��iM\C��z��RL�k��۟׿ѡ�r⓭(��M4A�
.,�k��i���&��ua��Ț��_kn�i����,�6�hv��#�����ǡ���>�6��
q���Vt/k��A&�F�"��+O�[��=�m��8�)MI3�5��'��6��g}Z�~��B��p�焰ę���D)�`�3����R�<�gޠ��آ�%μ�'J��K���8�(��zyt
K��U3�(��FyK���-�(��fy�
K��-(�(�ō��\׋�6�*�0qW��!�Nhzu#hl�Z�ATS6�j�0��
-�@�)I5~(�Z
-�`�)M5~8�VZ�US6�j����5�����lT�aU[ohVM�Ȫ�C����2����U�Wm���`�\�,�w'�G���cRW����#�#��k/=I��#�k�K�K���tkOR{x2���8���oҿ~�Z�kп}���Sԡ\Z��?�4�	������=�>���3�I�gu��#
�O���]���|��#�{c�.��EV�ʦ/
^Dt�C�¹
ibNko��I��0��5�Et�Pt�6�W{`��y��Y���Ų�:�F�WQ��,��􏃟/͞���X��������w�"��|l%/���4~ќ�Zo�b��o9���v�N��oa0�&�A+T�ѹ0���9mWة���
gʳDܦ�z�ӏ��s^���c���/�2fHYI��ZJ0���F;_���wL��X~\/
���}��>_h�O%`6���%�s*9�_ME�/X�fWR!�V�dL�ڍ�^�}�
�%�Ã=�T��YV�5Gc�
۟ p�R�1���~����x6�cyޚ��A�4-��k��ac�����d�*ɻ6��j�S��2f�D�`���3����
�����gÃ�![A�gٍ�P?j_�h��:㞳;3��­�oo��J�L7���lEü��RF�&�]R|�3����n/�X����H,{��pl�Y��ף2I[q��.6�A���LݤIn�ך�&`O�dV
�[��+��<�G"5=�T�5��.�H��dw�1�5���
_���uohP�t�]�0���QL�l�wIg����e�۳��b�d��ӧ�N6=֌�
�H�q�@{�F��qP�S5���V�20u�#�X�ʹҝ����n8�uk�ہ�;�]���,%x���5���^oltI�C�]B����|k��P�,Jmc�c���e긁� a�"�ތ���I�E�H(��Ϻ���Ea&�2s����P>��
<xIw	�Y�X{��l�y�:�I�g��uC���Z����}�I�)��w׻7J�Fu�Ы׽�aY�Q������ ����)M[�= K��%{�e��{R�Z;|_�Y���&��^2q��)
��|�
�@	Ҳ��+8i7r;�}�:<o�#�L?� �ocKJ� Ҽ�Y�\7r�)�:a�*V5��d}fǷ�ic�C�8VrƊm.�g�y�S��O��Ҍ�؏�K�h�~���k�"d���K�f62��.h�,��������nA�UΘw��'x���ᐹ�����wX��U䔛^��37�. �-i0�Ԡ��˻�I���6�V����2��� ����bjK�W\�f�f�7��A��8�؋��i��}>:�K��iTr�����o�C�us���2�@�˸��J�
-��ڣO�E^ʭ�L֬�O�|��i3�7�p*C~��f�-���#�j�vɓ�xL��w��o�LS�gX�]�Pr��y+#c\{��/�JPF@+installation/template/flat/js/system.min.js��3���Zms�6��_!#S�<Q�tmo:�Y�c���8���ι�H�-��IȎ���]�_e;mһ��`�x��b����*�D�Ľ�b���7�yG��R�G�s�<�{Þj#ӌ�U����MGU�ޮ�n��$��;<�"�fz(,�-z���œ��Q�K�\d+�vgW���4KD��f��Ɏ�U�~�YŌ�a�YI�.8w�yC��ӹ�N�w�߮s����Q+� �Valj�У�.�nN�S�/D����O�V��͋	!�R��e��_������P�q$ϲ$;�Q�Ro1��]&�Fϰ�s��gV��ū(*��P٭�����x�����m�1dCR>
i�gPQ)��^�`�F��o-Ud4�#)�ܹ��:�;ȋ�h�W�2?$��)�rA�;���lX⭖<���g�<�]yBt@za���E�p�4_|uIL;��X�,tW��F+N��u�\f��a6�\�U$��TmW�8t��T�͆�a��'���%���2�~��t�bK!j�P�]g����g�ϧ�3ft��]���:E�l�δ���g�߽|�P��-˻��K������"Ϻ
��O�w�/ɶ%�*E���C��غ���FRS7�3�:�u��v��a��w_�r׼�S�q$�ls�o3H���E�����u�m�iD�����7a�a
3��cF�y̅C�����2�����Я�Ԝ��3�s�j�6�:`(��4�⯱'�b�s��j��G,���:s�B)��領�ۂN����6�>���}c���Z��
+��&l]Z归��|�0ng|���y�A5�	�u��BA><�i1��	�?�����	�3����q�Y��C�b���<��Ǩ����M2�@�G��L����ʃ}׼o/j���9��f�q'�#�����ce�CG�0]��!1;��8�	ʪt�9�"[K��ߞ�B^rn��B���+��Ra�������f�{tCLVb��ωx�x07���O8�b �;��Ωw��VW�Xo��)��Tɔ��uF
�|'淽S�+�D�o��c��8r;�G���S��G�9��E?��󒊹�%}0�O�?L5\Q�@�;��*rs���	y}����Y�-�_-IcXi��B��Ą[ysj���
�0e�-���x_����9�C���].�
�ڢ{��V����H͉��-�3��	l -��Gsݕ}��^�eJ�\�s�l9��2׶��l�?FT̀/��<�c�Ix[���dc�4������u��Y+�#�C�@dI|/�`=���âI.���w�{o�\��,�
'�,�b�7n
QCL�^����a��<�{�)[��\M����o�m/��
�zz-�>x�>B�Y��㶀rm�lV
�x�7�;���A]<�Xvڹ��G���� &��b`T����1�j�	�&�	m#����֩�l/t�W@�4cPH� �׈^W��U�ē�v::��%}z�#bS^��^�*����W��O���x�J�� l�2$�Z~,b�6KĜ~����֢�-���Ǎ�����ƀӇ�"��~�-�;�oȵ�B�+F��Qm6W��0�ip��ɚ.�K��h�U�_0m���dv�v�㚛́�����*�,h�#���x`�-LQt�E+�k�Ζ10K�`�Uƭ�n~R�~D�ױ7#E([�1f���$a�3���`0iW���#u#~�1��ڂ�n��x{��\��i�����njf���&l^PK*����N��&bIe�\"����)��6PaVg��62N,��6�e�L����2����nYq��T6yo7��t��IDd+Lt�����y�Л��4�r�,צi�c���N�?�6kOY�N���愨�{D("��oBO�mjsyF@��ĸO���IS��)�(�a�J���}�S�<k|E!�Ka�
����%VPM�*4(�vBc�GE����g7����@�/XV�-`Y�&��dP�k�aI*7mK�`π����E�ȩJ���ܪ��A���%�ɴ��k�/��<�9�	�Oy��^s=!�
�[P�MS;�+綞�k\^ ����`�n狀r��j��ѥ)��S1ラ酝g^����e��!���K�"��h9ĕX�`<wr+�dv�[���-�i�J�<����(�[��3�*+�k�J"`iZ�v�L�ϓۧ�s��RcZ�R���l뒟$�
Ke�q#b�޼]��Ά/�O�_�F.��q}O�_�<{A.���<_��[gX#�֥��ZH�Z�ZA�0��U��"�߰�s#V��L@��
�e%ֲ&�D�p�0�]<�Y��X��<\Wx�<\PL�P܃a�b��l^�٤͋�.�h/���UɎ��'�B�.
ǯ;/�T���vw���D�n��4��㊉�/I��vD,�}���F7��g6��y��5T�ԙ��*��
�v��n���������o�EI�}�^cQ֭�Y@au^����MH��G�R��s��_�4�<��v�q#f�:f��'�>��ܳ��i�=1P�Un)Y	��Me7�V��CN8�r]T�]��r����U�4\�''�:���U\�K�WSpeW^�S��S�; P{�>Y�"Y�B�w4y��V2;O��%���*L?$;E�J����
&
�И9��^���}���A�ĴE�"���	EN�b�P����F�wn�ZM<f�-���ʳ2�r��L��KAU�Pe�GP�2jȼV���KyJ/�CH,˝O�ԩV=��1m�y��`��3Z��������]�}Dq��x�<�qSi[{:n�����M���n2C?��#���+�A��⃱�Ф�h����6&CO�t�Ǥ��	�r�����osL��t��rB�
�0C��nF{a�$
S�hKj�v��Z�G��
�	�f�d0o�/W��u��4P`j��Ve�����6�����g��zs�;�,�W�h?MXU�~�B�'���/�=M�xK
�R����v&�Ӥ�}��o�
�u�(I�`;��u]�ŵ��Z̫�
��Li�0S"v:���F����#a뤖|���m����ph��t-8#P3��Q
2����<C��:<���Sz�uܲ�/���
�Ԃ,Y�x�qpY�>�%�=#ȡ\Gy�	�*=�fyJ *q7���������7Ծl�����w�O,��m-0{���*�w>e,c�b��ۛ��������27�ƚ-�<d$�&n��(�o"���� ��/OT�P@���!�U��=J=��=��]�Cݔ���l��ip|ۦ�R�k1�ib<���ڶvx�<D�Q�ʧ���&�vUE�Az���--pQ
n��|̚et�~ɳ�æ�hF��<�Ƴ�4?�����L�!Җj�m�x�
$U��LW��4�
���>�8҂돕}���4:䢛>�x����Mhy�B@;<\���x
฻�j��$�Ν�7j���.��7_���}&}����iy��Lz*���^Gꇌ��
,�KZ�^��+ǹ:�ly��Z��ZT������E�w[�oEX�~�9ȇ�O�0x���|���"���kʏ�)���L���s��
pW<-š�L��6a��==-�v)۶���/���d�y�%�`9pDw�"�Un/i�oJPF>)installation/template/flat/js/menu.min.jsyQ��e��N1��)�FB��x�pQ��TM�h�**`֞M�8��?IW�w�7���Ϝ�w�S_p�!j��HV�ni���|Q�(7�"��Z�~�z��P�
fӏ�&���
�\;��r�����]2.@���*��d��x�{�����f���|����`$_\��^[��<q���/�I����c�_ɷd�����KVƜZbu�]ɒU�iK�	!�Г� �Qb���+w2B&���ɔK��L�dHF����f�!���*������Q��tY$G�滌X���L�l��{[[Z!y�>��
O�I|�,�Xմ<�W:��:C�X���qm�1/U>
����o�ڮ��b�;�b_�JPFG2installation/template/flat/js/chosen.jquery.min.js�q���=iw�F���WP�y2`A���x)A|���m|�سy�$�$["b��d���U}�O�̱��&�guuu��:y9x��RN�W�׃� �E�������߫��i�}V?�������?�3R6$,�vٌNN��v��&��d�Z.�u2���L�jzr�5-�O~�|����7�|���oW�ͫ2���z��$n�<.SYE�sM�U]�ּ�M��x
I��w�{R�M��ĵ�_�4е]�
@Z6m���U���۪q�|��U�,+
4��*�I	���Y�dYWm�>-IJ��q����q�@�dҬ���L�J�7�"k�?��
��Oga�*k�P��j���I^�ɗt��eV7d�^�l$R�DI6�O�jN���?|�����C��i�`�w�=I��K�u֐0�)p���Z-��H�0h�h�S��ڜ�^��qWg�r�*H
R޵uM�r�,�笮�'����У�a\dSR�ڄ~�m�~��1�=T�|0�g���פ
�y�dӂ̡��ϊ�iHE�ba�]��
`9OC �����Dֳ�<?k���2�����b�*c$
1[$ɨ���zh�7�s~�n]�׷W���۴�K;�UQ�ɸ���IJ���h����Ȧ�!+V��3FXF�x����eI��?����R�YK�K|��`�hƍ���<&��0z���*]�7���~&���K�D����_�O��
��v�f{��1i+�X�a�ZMRN�L��G�%�g�VHϵd!�T[�mb�aV�d)�0��6'�<%�jR�
�w�VCw`2+����"+��C_�J�u���d�˪
	#A�Pz�*�|Yp^�A��
6}C�ɜ�fP8A:
#��s��CT��!q�����(�&�| ��Y���d�'�F϶�L��J1����$��Ö�D���a����&�X�+���;��G���W����Z5dh��f9pYU��������(�,�E�`�)M�b��HU�K�`�>��2��~^OO(B�2�0E�8i`���ΰ��IX�����Pi���;]
o�׽
(�<<�>��3�IC�z���@֢|k�Z��C�0}��k�-�d��"o'�U=��>���z�S36!��I�zc`�J�b�
N��>�Q٭��4�}�����Q�����5@�5��%�2�p"8����b��,o�2o��!)��rA�Ş>��M�t �e�΂Ʊt_[c�f��B��2���3��L`iV��d�� �%t&�zwx��
�o��S���w��)���?^~���7��w�|w��l�(�0M�䔻�N��I�"����.��[Hu7�^S����H�M��tՒ0�gmv{7#ȷHpsEt�!F���S,U�MkeG%f;����.�)�9!�{L����=��f�-	Sh�&�.�˪�^��.�.+I8V��*ܲ����r=�v�\��.�Y��Di0Χ��������Epd#J��'Sl�p�?t���CJP}��ҍ��Q
�=�}F9�G��媅m��
`|�G�X�\$�#���ߓjՆ}Z��4J�&�9���S�2�6�hȠwV]ke�g�Z�X��
<X�Wt�׉SS}]Y��ph��2��m2**�צ;�]Ӄ8���>������	�xA��a\S�Rex楁Y��`�5`^�7q���"�S���U�s;5|Jw�4{�����ys�G;n�ۼnZ�H�FMҝs��+�9�f>�(̤�B
HuD��k���ԭD�Y	ַ���k{&N7�ܛ3Ij\]����B��s?�����>^�(~*�~|�ׄ�;v(��H�o�!D�n��h�*�E�ҙ��x�:1��=�o�뱲�,��&�DOr2#�%o
�� �a��3S��\�e��bT�:�R^�][2Ȏ)����R�����S^*s�s���~Ky�p:��t$�Qx����괅�
�?ᠵ�Qr�~1���"mS���c+�l�̈́�o��f�Z�%�Ue��>w�v>4>
�-0���ۉo�֝/$4�3�όB�P��գ�
�| Rw��0�|�*!J�Y@:�4lz�C{N�Z`�(�ڛH2�q��"�% ~�/�^��ҽ#mup��#+c���P�@��O�r��'��s���>�ޣ_P�E<�'q��y��o��m�R�Ʉ���`c."J�@E�������yF/��I|}���v�ܜ�����άAjrG��p�r�.z"?/�r$�:-0�1�2��[���:�al}5�5caݷn���f�P��%x��\9��D�3{�����af�pB#9�ѐ�
P�5t�,#�}`����p��N�WyǦo�Y���`HY�0v�\��`.�vC�y�JX�Jo�4`�9��sϏD�(^v�J��,�_�}F�]f\CM7w�2۟��N-C���6̍5Ќ�H^�/&秇�+cv޹`�g�*M.:~.`5�����V0	Uo>��0��������6����|&7�S�����v܎��u����Qp���y�;��T��� ؂��#�Xx���o�(ȃ�q'wo��i���Y��g�5*+$	�Bf����А�]��h��*�	s�Sp�We��g��(�
�_�:jڅ�E'��՘�)v|�RPwP쇞�q�g[A� �s�Q
����n��4nx�24�Ȕ�>��>�'4�'��}6��H4�92o}���q���>��6�-B��+@�_���p>�$�鳋Q�
Ƈ�b��D�x��aW�l�,%�MK�����)���@�a�G:�#��H����_:�b�4��ϯ���g��O_�D�B	�m�X����j�?��P��6���+��oo�E(��	gBa���$��F�μ���Ǡrm�pQ�:���l�C�x˿�ϯ�竑 A���u�F�ȹL���-�U����I�W�`R��Cm����tk>��v���x�tl�,Qr{���uG��K`��=ƃ��;���+���D
�Poo�%���<�Д!��Y��٢i������Ʉ�A�z�i1e�����?ׄ�Ճ+��9�i�|�v����;#�L�Jb�F��ɤ��搝�U6+�G��y��C�t�D��ӱP4��u��{����4�xq��HU��\X��řApq]��2+/^Yѽ��'��� /Χ�:?���I���c�y]-����UC�q69�U����4F8�Vm5��4�"��N�D&��*�����pc�6R�Q�����%��"�|�R~�uR-(
��a����:{w旣I��j;du�(�nd��/��]Uj�sj����ٕ�go]�6��USݶ�K�����UT5�1�
/�I�=�wY[�xѓ<�V|y*�E�jt���j�^��I��Ǭq�����A�h�>�|��٭����|�v��m�η���vl���p��ߔ@7�<y��
(�rf5��G*��{2x�dY;�d۬�-Նz@�����Xh�l����"�S�ܖ`!��d�gF�#I+�JēK�����KIxD�_�Y��e�|!��ѫ��9A5�	�N���5�f�T��f�.���+�Б�<�Fz���:ᣁ����͆z�T����*F�%���:A�SnO�4_L[��R��QC�$���J��J�/�Vu����w.�ES�j7&"��tJ(*�^I^-��@��̚���R����F� ������!�Z�]�n�.t�%�b<+}h�`��Y�12-�i�Jq>��0���
:�O׏ԑ?	�O$E�9���6'Aܘ�L���V�/#!�r�t��{_SEG��h�0�*�n[R��(0��L��n��Q�-Q%kĒ
�l��v�D����hv\�!�{�6�Z�Iq�E�(��]ݮP��Dۀ��h��<�i*�D��wF�<��Z�p-oɽ{�DӯHz"�Ȼ�l��j!���ԪFϞ���m��8o���5g#�5��ܣRØ����o �UAgS&B�m�$�N��XDN��Oݖ)$,��B80��:h6��()�;
O���v����/C�DŽ�y.tf��٨;d��V{�7d����;��۷X�����uPЁw�㗰	��j����&�v�o/��^ٳ��'㌜�d��ҕ���|"����ދ`ۦ���f�޹f|�16�-Hռ����]r�CӸ��cka�݆�O������b�v�a��>L���a�%�j�;LdE��͖���=�V��ꌦ)�?�3�I�p������fH���E�1gg}̋����J47p/��A���	{Ko\f�K��c�A�iowK��	+N����]H�v"�_���F��|z����J�=�2C�M�K�v�'��#���$,��5�.�OC��Mp<��C���e�j&�u���V���,h�GBa���aj�1���\���g�z�Y�OO�P�4�vf���ᡢ�aUc$�h4M�7��)	P€��;v�5|k�e��1�Y s��n�|�c{�rel��4��J/L�@H��,���:t,H[9#�Ε��r���`9s;�4����4=�Ǔ?�O4F�>��ͷ���&��5�&����ؖ�#PJӣ�:��ˬx�[��I�2�E��s�>6+��a-^90;��I�r����qkA`^������T�ay�_o��N�Pτ�z�ܚ�tY>k�/����8�A"]��hv>�緸e��i�,3�/|A�����e����Sh\��?F�"��|��:`D&�.uj�Ly6�d=�6<�Ҥ*ӥմ�+���D�4Xd�/���ص��6"�ܪ>����/�H���J��BԄ�T�0���xY�4t�a�ʃH��c�=(��;��&Қx�\��Խ�1��:��ߗœ��N�� ?.+�C7���O�;�){��?����������I|�.ʝ̫.��c��J~�Af1���޺˕-U�Hx���t)i���Ͼ|O�G�ז��N]MY�i~T�ڥ/�e���x��LXn�1h͑{$�pE�u����I�R��q~1��a�Xo���:�gl�N�u����8�-K; �~�wٚ�g"Q�؃���z@�s#)w찏�('����o���c�.x�ՙy��\�N_�9�d^�s���b9HB6Wl577�M$ɳ��{�j֎7������nk�����ɳѧvF���z�ChJ���� ���C!�>ǧ6Uin!b���y]ׂM�	�惡-q���
�3S�4Ϥ|�+�I_�<m����M�p��N8��ڮ8�]�x��4:}�yw�f�0�p`�cu����p��I�N�s$X5�	��n����12�θ4�p+�FN?Kb��Bi�ui��.~K��l��B��E��kv�UU�PU����G~�ؚ��jqz�o�<�bc8Ε{�a�ٿ�	&s�t�`����
�|_��%��/]��r�t_c�{�aN�������Lj�/p�$�?�ҽl�ǣ"�"/?�1�����0P:�;%�A�EM��Mzr8@�A��ק�����x�C�M��^���2�Y!��O�{a�
�"�Lc/�f�m2��徢��f:�	�wgӈ�LL��G=�.e��[�U_��J�/�V�j����p�p�h3��w�׾t��GL�Ml�?/ّ*Ƚ>�tZ+���ȟWt]�4·�̆�jVǽL�G�/v��<#��c���L����oD9ޛ�"{p����σ��u��?������-J]�x���y_�L�I��'y¯��.�+hG�R�5��[�r۪��F������a,�v�jgX���?�G�Ϟ����w�
*"���x�.ya*��q����h"�.{G<W��u2��r�HCFw�-�*<uU����.�Cg��2*�A��1�����$~��X6��bR�Ы0ި>g��,�V��"[�k7�ݎ��� P�s��h���T?���M/.-�N��][���m4%t8v���9v7n�j���	6�x�$W���H��%b�ڨO�#���|O3#1�oq��*;S�������穠�Vc����/ՠBERS2��nF*&�?�_�0	Ӡ���x^�"��g�ou"��J�ij�U9n�J@ɍP������z�����40�]Er���'8����!�=Bϊǔ@����rܳ�#5YL���$^z�kE�UY���KD�³D�{Vh�����.��'��š�0L?���bƚ-���BVX׿��ć�%2R�D�0n�L1��U�k����
6�,�¾?/���:^�u�r��d$?�T�S@4��	K�Ê4�u�mc�l����.=� ����*���ܶ��t8.���j�����|"!��|\f32
�p�Gɪ�*�2���z3�W|j���E�ُ�f�y�?������:+��XIZ�_p.�^�6��Y}Uҿuc'=��R�������:�b�_e��㴚?�"�)ᗉ�����Dw�u��&#?�x������۬]$�yiƘi��lP�}�p&M�Yf�߫{�a�.��6��cc3c�I��OR���JPF>)installation/template/flat/js/tabs.min.jsy������n�0E��
�1Vh7Yt�h�:AQ'
ddAQ�,�
I�0l�{G��ݢ�B䐺s��h|��9@*ɕ5ڃ�p!X;'�����J5�Br�q�L��UQz)F�'����O�/�m�JSKG~r�2z�Lk��82~I3�
ԕ�z���r
���]����`����Kj���`Y��,��ć�f��&"��Ad�G;�A^iȨ¯Z09y��sc�R�aE�i�.���[c<ZHKR1Iү�נ_&�h�4W��#��|L�ؖ%�;E/S�N���<:����~+�4�{?g�M�Ȍ�О?w`W�P���^bjɒ�	~��
�CC��a0�3��h-S�)��xZeq-(��W���1�w�Rz�o�=j���MD� �NGs��#h�Zgl��VZ4:����%Qa�q�e��g��@ET�4�����;[��e�z�K�m�v"��(K��=���=�!N�(���w���p�֕�(A�>%`��L�1)I�I�r^J�k��i��UT�0�3-�'���	K�a5��#o�G�n1�ǜ S�������q�b�Gɞ��~�$`�e��ݭ��v�(/*F�а$7�s0��,�xV�VzU��$=NB��Oę�*��m[���L��JPFB-installation/template/flat/js/dropdown.min.js����T�o�6~�_�p@ ֪��r[�&E�4�6�)�E�~�4�QT����,�r��w<~w��w������\�+�l�� ���$W�Wl2!�7R-d	��
��kV�.�@�����//���M�*gdK��Ȼ��U��ג�>�u�`j����o���`�KC>w9^����|� �uJ�'F���Cm�{�d�'���f��G��<}�����"�p9�d��RU���s�(:���T���џ�KO@�9���[���t�T��1�N!��pǶ�O��; �%��{�Ḁ��Ql]���{~n;cЁ���a�qޘAh��%�f%�K���N$c)u�WP�$�m��*���b��F����Ձ_�#3�B5q08zDy���=D��m�C�<�'+���8}hRy@=
q�e\g���Ωҳ9�����FU�.4��	�F��k�;��a���lh4%��Zy?������(�����w�-�H2>�s�Cx&M��c�4��ρhޅ��0��FH5@��N��v~�!�Z�m_�m�[�P�+���v�b!�^��U�f
?�
~E��15L(KsA���R�rH�ʬ���[VI��>��S�w�!�|�ӈ��(�%�bwL��&�(J�nj�!rn��Cl��3!�k�^�d���9�+�tG����	Η#>�[/~�����g�H�h��7��i�ֹ(NR�WE�MO����,J�Aک꼏[��������i�z�g��#룴��>e=�c����o�&m9��~"�뼂��i�����k�߇l)��JPFA,installation/template/flat/js/tooltip.min.js�����Vmo�6��_�r�a�4�n߬0Ydh�����%�$ڲ(H�[���Qo�l9v�}�xw�=��;��7J'��:���!�{ƒ�"�8�,L����e��rTք��h�y��t<����0e Fg7��uX�V�b;�
�<�N0_'�ɖ>bsΛ��7K3m�5���M�t�V#�L$*��$��pV"�>�f�Wd_���J��Q���=b�/�?��.p�9����{~N�q�qz:��K���7��f8��%<����g*����h��M���◮�,�$4���cgc�������c�G>ݖ5�VJ�׹�Rޯ 1�=$�aLd��9�dd?	:y��{+l2t
*��cPV�P��$�+�tgϝ� W�4����vŚ	��֘LyK#b���8e>=.X�PL}N2F���R1)LL�s��\w�v���IO~�b��6�2��#�k�K.��\`ޘ��0p��C_j�:���X'{�
�0���	�T2�`�}y|�L�)$�.R�Id�2X�fl{P�vVň�ԗ[�	fW#b�p\��a�T�87���rO��S����D��lx�e~��YYW����v�^���Nƞ�b������dw=�L��<m�^
\�<��|@���O4螼y��.	{�$�)�'BȔ<��Y��Bob͉k{][hc���b$9d�V΄�o�
` �*!�\!�Aaȿ�e��7r}u���U�G'���F���lٵ[9jպ��4�}��xҙ��D�ߐ��%��n�K��O�����|�'3�P}��LJͭ��2wh���r��۵n�[��R��c�X�	r�8H����ك����EC�p�T��g&Ww����0u���J+9h,&n��[/�s����U
.L�S��E��l�VN��޼���!D8�6s������Hv��y-R\�%M�l�շ_�*	���>q�D�ͥ�WƏ����C��S����s��3�&�A�i�[I�����>F�G���t��v��A��㈶0�#g��n2RTi��b�y:G�KpҐ�g�X�fZ�,W]_
�� m��s񫻭r)�\�P���N�q�\�4���[ɫv/�ڎ��H�ў�u��d�[��p����M��i@�j[ec�� �:��?g�x�zuq�� ��̇G�ؑ}z��=`��Y�"�JPF<'installation/template/flat/js/jquery.js��i��ͽk{�F�.�}�
�f���d�(�c'�$v&vV���<	J�I�@K���ު�F�"dz�>�9��4�~��{�<���]R��;�e|z�?��ѓ��SEO���_�lWi����bL�
_�yqu�NIV&�qr��|W,�o��6ͮ����H�ۤ�xo����eT�'�
����Q�
�F��6�WG�ʣd���M�U*��u���Kz�.�=>R���l����c��*Z�yFEԒ���Z]�C�~�+zJ�er�j�6�z\寫������:._�d��6)�;�.ڎ��F]F�NI�+��,�9��2�0��E�A�F'��h>�������x\,��2K���z�'c�*u�\��\�7��ϳ��b��_~yq�t2�[�(�K�F����.n.~�σ�����?��0�?�G'�e�̿��㋓�9�������|��o���
��1�|�P����^�8����k��z�]\\�\�����w��e<Z=}9���P��щ7�y��l���W�.ٯ�u���z��|��+�+�j�)G'�yt2����%U_�/@E����Y\&>�WN������r��;�G_�e�dI��{�<^zQD�;��f�N����H����*�����ԥ��5�[�@p߭~ꣂM�.i$���W�<˳
i�|���Wjp��?;�Q�!չL�xq͙|/ϸ?%����������V.]	u��4�o���1��^�e�UtTJZ�E���N����)g4U�Y��I`�:-��|~h������4�>�Y�.O+B��7��d'�Uu=:�o��<�x:ÖQ�:��×��6YЂ�A�����8�d�}6��L}z)��8e�ű3>?���sT�}͢�(�\q��.��l23Z�MR\%>ꡗm\��Wo���K��:1��e��=��
-�s
��Sa�� P��URV�����8-�]�i����dQQ���,�e�~if���y0�~�7U\UTHqʙ�"NL��*��X'���w/���y������ԗ�o{<N��(����N7%��*��d��A�o��VQ���e�N0�Q��&kRϔ�(f*���Ƃ�1I0-�Q�ť�cڢ�WtV[��G�h01O8؛�m�(�;_��7�B�Sҝp���$t��;�頪����v=^��5w���	]0en��t8x�Ǻ.?'�4s�&s��d~P��^-�6*ř�"w7N��z�\H��~�PUcy̋�*!����1��YA�A�R��TT��M
�M�}���Q|S?4�v����Ќ��4W|�x	WiQVU��F����{�L���3��:�,&C��(�4����<�W�ٔ�p����lN�B�7=��إ[��\��fO��"XM���'��ٳ+��w�#�@6T�Te^T��1~U��5�+?�E|�[i�
u��̳��"_,*U��	��kG�`�'�nG`]'��^D��3@K�2��I�՗NI����NMeO��<h��~�.e��%���E;����0킳���l7�-h���;-��σ$�VE��$�T�{��i_�圖r� �&���p��ʥ��T0�*�Mw��4d4�u�4�Tih�$��]˃]3�>�	OY�;��r��#��K�O;:���%��kI����;4>�?Ą`���|�����
�5��Lz�	-FB�&�h����3
��eN�f�I�&�$���u�����T2@��ǍJ�'X'z/�䇴��s5��r	��Jh�/�<0o��Ž`��V�%8`Z����I�t�h*}D!�`����}O1�tJ�@�z~���3��J���p>�xI�H=E�1~�3N�%a��|��и�E4���.z^�9�@ɝ	0��l#�.	�`�b����:g��QI]f���~_�&d�0=�ӳ�����B�����t/p��j8�<�yy���D��%j�;�\�
@���.hy	��N��_l��]�����TR��~�нQ������"�9�鼢n����kj`PS΢�u7Rn��0����=�x
۞����&/�8U��r��<�Z��]�^~Y�W���$�bJ��p�������hF��h*��1��^�zYԽq���BL���&��ivH��}���R�P	0�u�H����^`_�*o�~�<����<�7o��5d���ٻx�.�x0���3���Bf�I�ZfB/�Y̬��8ɘ(�oQѾi��M�0����=a����K��n�hۂ�<�.�K~�ט�M�(�2_Uc�!���@qy�-"�y
����Ϡ���i��ʐtB�G�ۄ�f��F��wo⫗�&�=�h��F�,Bk1e2��Aey�u1-���e����6��
w5�v����zQ��ʁߔ��PYs���X�uq
&G8M�Uy�r�l�����\&d?�.�߱��3Q _�7��P�L�!T���	s�4�G�h�[E�������u��|�	Ӌ��4��S���КY+���%����χ���R:�U����n{'e���*Y�.v�I<��{.E�ߙ{��ۓ2��A��~��B���fV+l
H���[2��H���ݙ<
��^J'3"$	��Փu۴;k_��_re����h�-]�M'D0}W�D��UT��Yq��e���٪��cɂ'
F�4I����e���ުy4a�V��\&E=��{j;?˩mZ��pe�|~�{�n�/z��|��/w�6A
Z���H�a~s� "�Y��P�-5�rZ�2R&9�"
i��ãJd�>=��C+d�?n-�<S�Faq�>=XLww��5�sKv�B�n�T�.�{���hwЎJT��,%�v�=c�9�(G��Nt�2���4���oW�H��:M����!!$auP�b��e��jc�Vh]��"�g�F�
FKt�}N�o��.��׾Sѫ���`�d��C�m�Q*�1�`j�E#���O�Te��ȩ_���dz�R�{+�Bi��%���U�r�������S�SP��9ѹ���G���4A�0ƅw�e������ �b��7ܨȽ�bHC�<Y2�,	s|Xf�G\�9���n�dݼ�����L��YA+��h�[�<��k�xUN`����Wi,%H�]�����ͣؗ�tHsn��$��3�\}�9S�O'��!�9蟁����Is$�υ�8z�0�H�#������q�]r���HnB!{<fx�ڼO�3o&��jH����%��f��f!&�=g	Q��$�tp����Шt�M����3�\�C�7Ұs��</rt*��@��ͼ�D����_ֽ��n||k2z���N7
Մ�|!II���r��N�e�x[6�sIԃ\�����5O�f�lT;\��h��	'�	4�gWP
�&�{�
d$��r���
�{oW/�����,�b7����R��dtxrY��Wٗ F@uЁ̑�v���k]����_���tU�`�M�p;^�e|IXӛ�=nh�fL7Ϭ�(�
Ͻ��3�w!W
~RA��.K�ќm!���N��Xk��� #>`7�$�E��i=�a!\S��5�J�����Öށ4�a�oo-��b�^��4�n���i��|i�P�LO��9���F*�x4
F�@��u�ڡ�p�v�����X,}#������fZG�n�v�`wP����*�T���
�kC�޺�,a�e?
X	Y��hFɢ*1տ>!*�/�$�DЬ����ۥ
��1���nǦ��)���Az�Pg�2��M�#���<͢�������n�	H �n�.��BAF������CV].˫tuG��!��,[eM�9�PoKC��"����)#z*^��weϷT�e�,��]�3��u���Y�R-jpn���Zr�$�r��2j�ҳ
�m���������^��R�3-Ψ�3wy0MڒBˆ�c�;�#��!�Ǫ@��C�ϛs�kLA�v����8G�CPg>(��.��nT0e��~�,��t�D�������>&"R�4����|zp�8���ӟ�9�3ЁR��;��53,޼���/�J�E�j���_���33F$�SO�
{P7��i�G��R
B�7�~���R���O��I�����\u!l�G��-�<?�v���(*���t먁p4J���!5B:m	�`�4\q~ʤP��6�qmϊ���DG�5~$�Sྙ#S�kP]rb��>%�����v�Q�ʝ�T��n�j�y+�fc�Z��Z�%�KMֽ�L�y�\�	�Z�q���[��7�`+QjI�3�=@D���g�4{{r�Y�}~�ى�����d}ttv��f�]u�"�����e~��ɹG˶|��<���^&�����P̀��^v�)�%֔� A�g:�������[��.�a�wk:ce�J���l�\��,�Ƌ�Ж���T�kg�C��b˱�qE$����*)�p~!�Vp�-M�*�W�C]�ϖ�����uR��J5��au�R<��v"�f�;��3`ΰ�/�b�/C�$f����h-̼��<jk����E�2N�3ל�x?���S<~�w��4�kFµ�BJ�*����`�Yo:�����5��,��w���k�)L���!���|L��E;�J]�޳ׯO��m\	�b����{B5�nc�;C"����g��ӌ#�W��$Y�_�w����5o(�--EпNq�!�fߥW�\	u�u�;���w$n��d�m^�,��3�)M��֪���3��F����w�-�s�����!�=Z�jkv�5A�qc��;����玺لU��8��:g�<Lt��w���ש���*��\?���ϵ�^k��ӡ�e�kV�ҝ�QY�S��O1z`�*�y��׈���4ޠq%��^�l(�ٖm�Ԃ���r��!8��B�Y*Ed5�@WGiv_�.7)oBa��iE�.S�s�Cкx��
W2U�l5�>�]�)	�Y0�d���J�r���'�s}.5�!pU@���:�F�B�{#:^���E<L5�G�y��0t�+�.ۈ����!�d89��Y��˼X&�D�ёٮ��皒oG%���i�l��=��&�|�V}�x��~�0�nO��@T־�l�o�%�6Ϯ�>���1�%!3�fg�$'g���_�?�/eF���Z� ��>bА��K�#K~����E��s�WK\z{l�V��@�#_�hgų�֪`[�DV�S��;R�.�I��++���q�����,��	��=�~�n!站�:z*��ܸ�>i��>�!��m���>V2�P}]�I�2�^x�-�-�"�����S);]�M�U	�w/5��u�q����w3�K�Pg��M���2�<#���+���T�1��^|�O[�3��[����L�y�z_�>j�^s��h����p'�i,)a)��~��
���y�.-��L����̾d�5���}�Q�qg�,�猷�ׂ#4�)�_|�1k�ʬ����Aw�w��b��Nt��F��x���8AX"YTF�Ì9MU_��؄��6K��/���y����s�0{:�'LzjA÷F��,oU�W1T��-�Ks�ҽѕ��$�h=�$2��0Q+zKf�<ğ�c6[Yog+z����xIM���)k�	V��/�#��m��r�pEWT@�����~��
�+�:4�hz���c֫�AK(�r}���7�lo��8	��#�J�����o�#y�m��3L�V1�U�8^�$a�9cʈ�V��ȴB��]������Rw��(vVlGo�̮�<��X;V�F�"�ΦH�G��Q���iUFP�u�3�?kD�oV�CV�+VJ��\9�@��2���Ѥ��Y1���gլ��2�Ϧ�ؤ�Q��ܨ�X��rv4ꑌڵ>���<��l$�1���&�ߗ���ڼS�^�*}����.�q��=Q��vȪ8�7�Z[�����'WL�d9:�'�K"|	���z�u�.��O�׳�����/��|tz�X�����k��'�|��ǟ~2���B��2��ON���kf�~�Q���`@����.�u6����w#�z��ks~'�y��N���/T�䭏LG���p��x�|�̹V�<B����f"Z>i��*J	[��Y؀��ÿoϋ��c��@�ʤZ�_t.�(^�I���N�uQ�yp9�E�)��\��%tc�8wȴ��pq��7* �Y�RcL�U8�6`A�b����ZL��"��@o'x1����Q꨻��K2MY̩��
�O���m`AW�|��%�L��X9����}�W{���o��
2z6tː��Z]��U���X���%�OM
m��e�*ӟ+o����{�y�Qk��AW���)��e��C�B4��4�0���:,�����f B�Ҫ��,�*�=�2LQ�rՖ�{�Q��>
G�(�:��֍"�8X�
��9Ȍ��_��v�.i��Y���0G�Y�����E<���
�tZa��3My6�0J�
��|�M���%(Q�N�/� T-��M)Qf�-�P��<[J�8j����p��FB0����]��(��!٦�x�I6��@L�uF��m#��n~5��4�X��eh�2:}����K�9�Z�[/V���{QZxX��&Ra�R�f�盟D)�stw��Ly��׬�E��EM̪�uq�BS���I�1�CB�x�9l]P􎛻CC$�ה��$�5���l�����?����;j��ٽ������XU
ز=b�k�U
rt�*hUe�)��<F���������GH���[��rW��fC:+�������Z�05Vt���>��*��uǴQI��`)&�N%�Š��3	0*���dWk��|W��]�!�P�����E�1G�>:�(�Ł��"��$&.�xO�S�g{A�hN�O�=��;��MR4}oL�xW����'0�l3�׵���~���Y���z�5Ѧ��n]��{��-%A�2��w����9�rA��_���o��jb�%cTw�����h�C��.�s�l�{���<m�ah	
�Ǒ�u�a]B�?�ҷ��'�Lo�H��N7�F�Qe.���
�y�=��yWǢV/gԻyԺ�����1�Й�K(%�����o�ٳ�v�`��TdYՑ1��!�4tK��Z)3�!�u5��̡��nꝂ@:	C�S�Q�5d"�6��g��`r^��U�rqj�F�|��m,��^���J0{���j{��;�����K�
�k�*�G�~Q�u�@�5�\�*��Zw����({�Z]mn�ڤgI�zI�6K���H,l�q��S�(�<T�g�)�c�L�(��G�Y�9�Q�%���SD�����3g�=gys��|�.kgf"�i6�Tꗚ��~��~����[�Q��&�^a��Z޽��fT=�h�3�UF�V���L�Y#4�q������$�w��-\����!�N�aM�8>�jV��U� ��iKS3�`�}��ș�;�ju��ɪPlO4�8������>�xuu�\�pes�s7:Ǝ�u��7j�5q����w������y�{���gK��v�Pֲ[EM#7`0�n��~�=���ۃ+�:,�����s�YȢo���<�5��{p`��y��"����L��{Q.��n���f�I�.+��"]�ɒ6
ip���Mz�^�v4��Z�ť�_`��N�q#���l��d.̖��N��iX8��y:-CJ��n��[K$�n� �-���!u�h^�^������}��Rp?V�Բ%��*��9�����e&%��t�@N��۫:������bLr�{�l���%���̪v���kV�t�+̝$��� ��Ȉ�Z�h��u���[n�1ܩyy�=>��rw|�_��?v�5�XV��Y���H�5?~r��˚G�iO�SNj��ߴ�`�{�"��j��i�7�#�R�W��
h���=�"����N�k9��20�0
��An��
���gn�
�Ȁ�A�0g�
���N8�;
+1�0��`��3Q(
s��z2�8>��}K�C�ҘOpq�y�̊9���%���gT2�������=�X�w�����^���T�����C7-��gY��GI��^%�G:o��?�T�Ɨ|�=��񔡷C��`��t�B�KzR"p�BW+z�j�+���GO-��*�P\���Z^�Qi�/��� ��"u �����גB��+�{�M���j��_>��e=�d���Shվ�	^��v<M���J�%J���r��{s�:�~-��s�]�s����Q�@!m�,C5�S/y�5����zf�x���^}���E�1�+�N�����>��~�A�M��$�m�ߣV�\�Bqaߥz����}�?>��Tf-
?<��Mhw~��*|t��S-��B08��Jȇ��W�YN/���Ũ��:6�Ņ��~߹�4�xp!�;� l
���z� �v������7���0|q|�B�r���_88�Xx���Qo�ҥ��f�f���	
���0.U=:g�
Yd�=]�U��V���P��V���˺��Ƞ��6e|�6�Q�E��#-+�{w�YVV�djC$m�6�7���zm��&�T�5�U���GKI�x�v�`.�#�W��Mc>&��l�AdQ��������;��L=�!��� �஫���[���S���q7�h�R�0Q��k�`���҆�#�t}DEfH����رu�n؁�$�����)ZSL֭*+�6�����ա��n͞���'�m�"�rF���؇���2�<cV�.6V���xx�������fLa�j��������h~�����_�w���qIŤA'�c�r��G�����o��UU�w����N�	��=[t��L�Ц"����\�
���i��x��]ُ��0��Z�$�j#�I�o�;ɧЏ`�{������Q[%\U�l��Q]�+�Q!`��LJ����q�\'�6����IQ1�h��з�A�<DF�4P�.z'�c���+x�+z�*�~�zv�F�.�a�,Y��0��6��r�#4�A!jc�՘:�ql�J�"8�ő����ð4�y=e��	Ǐ�l=�"W�]�jCt���\jӊ�c<��ݷ�L����׳+�|��t��Wt���c��L��W�u �P�W*���:�D�0Wz�B�Uɂծ�c�Ab�L�t�,�n����Q�̘��P1�܌�ӌGzPZ��hG����O\��=�w����hw[����x���\�"3�Ġ�����)!�M���!oH9��[�A;�M�l2�����fB$?�|�^���
�jBU���[24䕓��	Lδ�~��(e�ƹÕj^Zu���'A-��c�᳻1G�I~����{:�v�Wv�/�+�������ྻ[�f���{w�R873S���8>�=����8��z��������●�G�XF���r[��n�_
`�ha� [���.�O����{~l�<�
�3���Ǟ8����V���-�j�шv�l�y�YA{����=�R��bI=��gS�Zm����P�r h��9����&���[f9�sZ*9���:���jW�oщ��4����d��i
���,B꿾�Tm�?���
@UcSetD�ov�8�}s6o(��*Jq��
��ԑ-�W�}�i`x5��I�U-��ؗ�6ѕ�&uc5�6��,1p
���kؕE��8NT����꺑ǖW<-�Hט��' r`��N�9�����EG�PD{G�ƄU��~��1JY3R)D�
�K�̯o������9�G}�D��1ƀn5ʞ�b*˺�֕Y���Q6��5˼^�,�����l}�J�p���l���}�E�oe�ir�߯tU�1�x��ҪP�׳%X��~cvӽ��8���*�n���	���Ӆ\1�2��M���ۧͤؼ�K(�\��g�5�+UD�h
�U���:�Y�e�o���<P��eآ��|�~�UZ���JfK���,�;�lֻ9�f��qa3mj���AOe0S�����)��&�]��
Xk���H�xe�:|�2�+W�3Vu]+�S����bT�����Uz�w����8�Y��/��-�11{�k;����
7'���i�x��2?�}����|^�L�}i��V��Rk���d�$��j�8K�c���z�7͉N����f�,SSo�>�qA%;�w�uD%_���ݐp�+o�Q�﷧;���\H��~��>z���j��Ā]{��Q��)q��j�x0%,+�d������ޮf:2��wg�D}X�G��]^\��r�|j���}�U뀷^�/\�Ө�v��&D}󥐣��5;k����Xj19V��!4&��6(��/���J
AJ0#.E.��|�ё�J!8��"
�r��I������j��Vvn̖ȴ��,B�xw۱�r�#�J�UA=g0�4��T��p�Yi�U�Ĉ���5պg�\aT��6��R*x�J払���~��g�Wi��!Q�$���Q�K?b�S�R-�b��m��]},���n3[�~t��U���.��4�+�u���a"º2��uE��.���������G��X�S����(F�TpG�{Ug`4��ǣ*�$��x�=zGx	�/���f�:��T/��"���3�}�?<����y�Ƴ/7̳�O�媱�|Z?��X�B�J���K�_B�䇦m�Ҵ��:Z�F/鑘���:"���:*E�d?�ߟ��\����9�`0� ����Yk�i#Ņ�(��E�vO�L�wK�
2C,�.Q�κ����Jvu�u����m$L���2N.7a�����Ԩ�M�mT�ﭺ�<�;�p�8���x��;'1�9G�Ŭz�:��H{�Blw��q9=
��ߏ�O��p"�H_��=�P����`+`�1���'��K�%���<q��X�e��zM,B��.\��Q�mSۦ��x��%�'��^�؊�Q��T���m}`c��G�i��wP`d�o��3cPX�vT}��4O�\Bwp��d�]��k�"@@���N�o����d��J��f�nx9�7#c��U���s���u
bT���ᬘ��~�d�A�_��N�e/
�G?�����D�A^�m�kolW�L�B����i#��A�QUg���i�3���iƏB35²"C��`�4"�J��.<�:zgcL�N&05��	�����Mn�n!)Y�mc�rɷ�ř�U�W�.@��*���ʳ�t#�y�*�L���
.<�ytC�v�KS}ē���B��.�z��.��C��V*�P�]�%l.v�j�0�NO�#��Ҫ�8UoPK
�Y`^r#Dlw�\}s�vL���)FGD���h�q��=~�s[x�����]b�����-�~�7�p���T�S�z?�Zw0Gm���� 0�N�]��uDb:�>�B#�^�-髲�D�̬t�J�{��VO���w�{t�����&�Sl]����|M��J��na�k<ՙ;����@��mq9�u�`���FOBB��f�0��6C���˥��<���ѣ�;�����:k�@��lvW[�1ol�J�v���=3�VA�Hϔ���VK:仝1s��o2���&��P�
��`���~����BK�A�$Eu'�b⫸�cr2��D�Ly�����뮬tMKw5�s����^�ކN�f�
`��,B��=�!h$��T��sF�Gs��ʸ��U�k�|���m
Z�8��K��#�x��;��9��~��!�^ρ�y
X}�}��[�
R���7�M�:��!;Ȃ��ރ�'�6B��j^M���k.V!�,�#4�K����ϝ�΢U�B�&����}#C��5`z�
��v� ��
�F
�!��l�OF#��
�Tٌ�e���wcs�:��G�J��5Y,c?��kO�Z���:�5�
�r8O��X�����e�e�O�舳�!������u���ubS�ԍ�愌�%�2b�ofp�4�%eKk����E�������.����B	�e��CV
ڌ��t�
�9��ɦ��.��HfÑ�Lki��{C�SXR5C]�R���63��}D��4�F�A�D&̚&�rkh�����)y��� Į�����/���]�)�*�=f�5��DZ�̻�R��i�u��ij.��EUM�Ii<�_�3����pGm��S���k}z����h�w�-��Ra��>�����ӒT��W����N�����S7`i�8�|Iw�3��6�
��B~^��Sk}�~�N?���S�������ک��﮼V���c�7�@�q��$��L��м@�"N�Qs�#��/�7���}2���..�����b5�ԗ���hrP���|?��2N&#�O��S��/k7Pq�ݹ�S������O��S�F�����2����ˡ�i�
?7���o�u�?�w<���+*���Iq���"/ԕrA��zZ��8���v4�������1B���h�������m��Z*%�ь@7�Q��c%?��E���]����-�)�]�~tK��<���G�/��6�Of���맯_�_��c��|��� s�9չ�s�7O�Z���6x- ��͛�B������_|����H#y�Ջ��^�>{�a��{���g�5����|����W#&}d������Vg6�m���A��7�����3�����R(C�
����aY�a�\���:+]�{����YS�Ӣ?�f?S���4�ޙ�P>�Q�'�li��|xqQ>�J��~� ��]�m#:�4,���R�h�����;iGI'?__,�����ɕ�gtr�:��~�ȣ#�8h}��S)h���.��h�t��T����9ݏ�Z5����&�ް���O?�������V!B0K�L��=�r@?r� ��O���}v~~:Q����'�������tw���o‚�i��Ƽ�@b�j�V�k�Q��:@�@��\"�1��&�����r<��[�H�O�Y�F�����WK��4�?q��T|�~�	KfVmD-/
��Lgv˼�W�Ŵ����8��
e�V`?���+�mk�4�2�u��k�0D��vD�Ux%�-�\���~MJVs$���;��ԍJOŞ����֘�������F��$�5G)bs��_����r",|~����~��
nG�q
k��&ʪ"��
��>;0i�uۥ�q=P��o��7{bu@�2A��>9n�N��� ��q�|e�7��ݳ��N�������Nn���o%�?��Y����Ut�6Q��E�8D��:�Bn@������~�P�Z��u�ӫheoȿ+†{n�S^u�`oF�7�z͏�d��SٍF/������m"cN4��h���ZYWa#� v�s_���kM�=��wf&�^�����u��G~8%����2?(>p�Y>�QY�I|tF"�i�$h�fK�M=�(h��lZ��pSK��,_��Rx��8@������2��D>�����b���Wr ^���
�و g�%d#�������cP�x0:��44Y��i��4";>p�ى~�on�:������;�u`m�E��魸G�S����N�k��#qy�|��Pt�}d#T$�-��^b��N\(�aʢ���?�#9
�t�x���!]���G��0�%`pΡ1�V��sEEˤ�>g>7]�+'4G�2kvW�'ѿ5��1>��NLsHx�/G�x���}�����W_�:�y��~�^0!�$��U�K��W'8I���Sڔ���L�C0�?��x�+hmW��HN?��ح�9S?e����-����$�fX6XZ'��H�>:ꋂ�/fsĂ���vk��IE(�������ǖ�ck��؊�����B��ڥG�S���M��j���6+�a�kn���T��t��v������I�y�s�L�sܴ��z�td���V����~����PYf�>�ܷ5��q&�]Y�S�3f�/"���}h�"q�	!�n��(Qe�7ګ�<jt�_�P�rŦ����ro���
x�i#j��Cnxq�vd� |@*��O`��z3Shno��^�29�����眖����l�8[Ѽ�v��m����j����{��F�՘�QJ�?yh���������Gv�ޅ���;�d�!_������o_o-�UxK�LI5~z,�M�v�Ii�c�l��h%þl���M7U"�|�{�6e+;�5�o�Ը�i򱶜?��T �~��L4��J�oy�;,��u��=��+�z��a�+�Y��J���DMH�i��/
OrӤ�ȇ�[5����"ƿ>�.s(p[R3۫i�>A�����������1.���\N�r�EM�ѿ3K�;`�yv��'t��ꁡ�g��"
��!��A����q�R���Y��oTLG�aU'TTY8	?9.������}w�qB趍��k!����A�?O��l��-�2�K��2��e��Fwb�h��I;+]�H�U���n.xF9Q�F� ���'�)5�o���:�
��6��Ӄ�ᝪ�p.���@�,��`��L�c�;�
�-4%
|ja��]f����x�E,���>��a&����?�}��>X;`����J����1`6�@
�*�f��{�6����N%>�Ih�:2ܖ"�?��Q���K������@�޹��9���¨�b}�����
��&�L��y�4�-.1S5b�v*E@��u;X��m'7��s0n�tD��fV�&E�7uv��"�9����f�s��]V$��*���#xGI�j���-S��R��_��@�� l�
�G&����D��b���������u���x���ĸ��p^O�%`r��ib��<���:�UQ�b��S����`�+i�q�*�f�����J�{r[�N/�iv%�k�7����Ӛ��v�v��.>�~5�&ї�����͔#>X���;
�i���j���m���sV���6�ħ�bCFqP����a�8K�t���X�h���8��=���S��v��)	�th*�NO���_�oݴ��Ҳ#>��VsY=!i0\Β3���l�l� }�<�ci�n�V���	@��=(\�+Z�ᕕ�-*�{G�~:Q�\��Lv��
|
��+�}0pd��]�{�^x�L��RO�� �;�N�C�\$��|W�7��L�7�R
�Y���q���u��ňP���y�'�O�e�������+b��=�\����lb�2��?-�o/�>������[��1tR����:��˟̣!�y�.��ϔ�4�<��o�T��^�\��e?���kN��N�?���F?��P)n�'V����@K^P�x�7�~�i�d�[�w��͐q�My�jW/�g��
F�-#��GH�S��JmdN��H�[m�z���biFL��;~�B?鰴ZK�֜8��U��B���e���q$
;�� r|�0�pѮ`�34Mo���q"g;�8����P�Nd��k(��dk@�q*�PI�/g:��2$**_��0�rj��S�����5��uP�,��G��9a��#���z{IS��Y�X5�Z�n����5S��+z#/�">m�.�h�7�002\o]��<^���'�;�i��[�9�v7L�kp��u��N��P��r���z����M����MO4�޻X�u#:�z�i�L�F[xσ���tb�۞����2�w:H���1��=���u4���R7��k��E���"
~؉�:Z̴��%���/��t�GN“��F��㍣B3[��h�C�7��o�_E�h�t�27�;�[x8DxtF,�ыh���m5?��k������#�+�z���tBǤ�z{:
��ӿ�8���%+�:�V��
�
x V~0�R��v��?�8�@ȋCϥ���������J��r���\��.�D�HB�X><u}Z�<�!�O�u�b�\�m�U᫛�[mQ䷵(�.lU{�&$j��F�X���H�!���	s�!�CPP�U��Y�	�a?Lu��}T\�����М��@^j7�<&�SD���A!G�(W~��T��>������[i��ŃL6+�C4���B.ץr�T��-|p-~�Pa6��tn^�-	�4�����Q_�|?�Q�<oS�oa.��X�GL�.�m=���:D�h��M��j�˺��s�D��;��qMڽͭ��&[����x��,��(���5X6�8VL/L�Q����Y����p�ω�oۮ��M~�����m0�M�$	��vQy��+-闝	g�w��g"&���
��N�AXl�
��d�Y&��C}s��Ƀ��U��!���*?��#�����~�qC�!4v҃OXlAI/{�3�����e��u/��(���� 
Sܗ��=�����#�lH'��o{��w�z�M�8���a�߯:��E�!L�V#߃�l+�zF��N�@[���"O�[;GM�̠P�
�0�DbwkӦ�bkRq���I���D�)��*�!�ם��F?��R�hl���S4��z�����p�,�Q�ݳ�+ܽ�V<�
�@tH��&/�L����QPP#b�<Z�PW'���_$ps�ɿ������H�k�}ahg:�k�3��C]g�x��5�C�W2HHp�2xu��s0��`�RWS���G���ӊ�T/tE!�*^�*t A��V;���yі�J��%�u��bu�G�NN0��'�϶�߯镠2}�8�p>�G����z�@��e��۰�Sc��Ӛ��_�b�;�5�x�h�Ô�`���5���a�F3�WQWu]��:��Q����.�1��j�^%�FM3k{+AfQ��"��6`�K���Rz��颗ؓ��Vv+艹�D]�W
�}�ƏZ4��A��ݷQᎉ?v�9���7��E�|�bR*�f[HA�����I@��\5'jr�=�&�O�$H������s1�`�-n���V���O8���gk�MO6��<+i��N4	@���2�Ybk
�f��5	�}�s��*pXB��j�*	6Di���HC��!��������Q��U�����:2���Lum"�|r��*�T; ���]�Mw3�&�5-l0���8ܰ���f֠����?�CЀ�_+�.>,��u[�f�ѻْ�@�m��6`��\LG O���:b7�w���I}kY�
�QEg)#4w����ӮN����!�_!��1va�F7c���r�x�PG������:��zNu7b��]z��n�w��`�5V�L8/42�s��0m�\�	7�h֓�����l�˝`Q�o��5!Gun�emky}���Q�ƭF~Pﷅ	�i"������
���N�2�V��7V�X#��{�_.;'����s����(�)��h8�Q�Kǰ��v�n��DqwN�@�����,�v7:
��Y
�f��9w���%�rsm*_��-
h.Tn��-�
�������/�����RNt�t��*.#o�[pm)�FCåz��3�k��-7���ZU�۰��#���7��
m�o��z\�=�>�ǂ���aۉ����L��g��ypvIk�q������+�zC�
�`�a�!�3���u���h�e�Va�iᮂjlPM�hO��
�KE/�W�-V��ƿ���ՍR�-�$XrK]������k9I+�XV�
����wv��wC���,��5)��)��:z�ۃ٨9�ne��2*E�,m���'ӟ<z� ��"dbc.����(
5�hW� g�����<�Q���e���Z[�F�o��Һ1��>�ˆ��2G�m+�i�D�4��GV#ik4o�h$h�H�D���_<�H�Q��&^��@�����aI�"_$6��;�\�^��+ӪAU[:�0"6�� �f�<��'(Q�7�
!_�Y�&F���t`��B��CY�����.6�S�N�Ў�_�z���>6o,�٤mXU0Z�m��jI[��������d�u�pf~�w���uRFo\W��H�����TjAt����~J|���S7��l��f^�I��ma��'=j�yJƪ:�+���k
U�?0r��&B\E'�gU�~tB��wY�rI���=kΟ��g?���]��1�[W���HXjQE�,Ң:� ���`�13�=��jzyq������Ѵ�'k׋�����*^��uPuv��Y*h���A\��p{�NWj
F�,�pKj������:��#b��,d�����w���Ŵ��z^0$���>��K�
#�2G������w"�+���8���9��2�t��u��ua��j�>W�t]�?3c�Є�>rl��R^��{8tg,���m����B�Is�*0��W����U�3��x/;
z]���eញ�)+���Cx����`�����%h���2!�B��h
��.�)z��	%ș��P�}ɴg뀕z��W+��h���e��)(�P/_�s��m\E�U�^Ѣ�
X;���lnj��n�t��7B4��&��U��L7Iq%��8�x��6�2�|	��R<3�����;�9���X�v��Ӣ�"y���Kq�I�eN4#�mz��`А��|a�1�G4��P��[kl
�ʬ��o��	�S�2G/�%�.{H+��(����"]����4�2{0s[;F�N;�i㏊<�҇̀�Oj���k�x)/���
�1D8�Z\� ܱ��{�q�|(dP��B�p�1�9B�r4�M���6	�9��G{��
0�	Z,=���V���s;�����Q��:3��|��m�q������D��V���]dP���V��	m�E�]��)q���	G�Y%F�–�N�u���`}ݺ��M��L(�Y�c��;�&w���a%�h��&\�%Ex4ې�{K��%g���	�����{e�4��+|>mH��ΐ[0$�����X�c�����ׯ�6k(��4TFO��d%�S�]뛞
��ĺpg�S��}�4�Dj�Uֶ�vCo�
��T��d�k�h����ˡ�+�fnuQT�
������������d�
��LD}�^nf�U�_^�����:��eJ02�-�|�L��8{�{�3�?봬�����*�ZĬ���]��WyNӳ��U��M\��o|��w�|W���W0�ٗ	Ož�m(���������.<��@�.�C����z�q�ɕ�Ԫɟ�W��
.�..ʓ�&w����w�D��p��/�>�\&�u�O7W{q�Fdž���ڈ7�'n�|�~>�?.N�O�Ruɕ�/'������/��?M/n�g'ꍴ��"�V�����F+�}��c#�޳���=�OZ�墄����h����(��Q�[��hqB9~���d��i�>��U�K(�1u��L���0��={���Ӌ�~4
�H�_��|N9�?%J[m�٩2f�GƔ<��<}tN^���:�".�Vi�^�I%y�7�Uh1$]U��g_>�7(�j�A4�>�t��~��E8{b����,��d��Г�f�ϼ���}sۢ~|�)_��⼧QC3�}b_W���H�N8����7�1���x��ۙ�A�ZE@�x^E�V
�Ey����i5���G�L+K�<��!��!ƃ'?�9��u.qͯ˦�����Yj2�!�>��_�WZ�)У��Չ慎��+�
��D���җ�"޶�^�L8�]b��?�@¤�c]��8A+菞�������a�C0N~�'4�u�%>B<w�K�;^�L�b���ԑXM���s��c��b[��c�B\;#���{�1��?�K��;�a�7c3�•ʢ���%]��E5��E��J��%��CG��}��VȺ[�t��gA(M�fc-�/4���V�[W���|���Z�0sh���
Z�G�P��|�M���:|����3/��a��^{ړ��vR�����J&�(�������U3p4���h�NO�18�����n8^UF|����>�����ZkJ䪖w0/чއ�� ��q�OȤ�Y�K�I�q`�WP����_!4�e�x�
�T\Ty�
y�:Ѩ�����o��9V���[3S��Mq�"�P4��t�諺l��Nhd�oy�I�1�&-��J�{�$2���)؛���W��a�n���<�7
Иж�r�q��k��ϣ�j��ކ���"�<�c{�9���2x;>���e]R�?\���S8>�sr>�f�ee���3�悎}����w�������ɹv�������S�v�巏�8�LPD�XM���1'3
Z�V�^�ɹl��k�ID�J�����$F�eY(/-د�e������p��1��J�!3�l���2�uy	��L�:�$Zi�2xy" m�<���mq�"]����SuŦ�jӜ�+�(7�mtzm{�<W�u�,����x�����Zk"�6�gc	}�3�ʦ9���Ԋ�e�Q/��[V��_��YF��/./���h��j%�x��XQ>t��'�El�5�d����N,�P:�JP_���3���ق�X��Нt�~�
�˱��	aі�j̝K&P��Q�<�Ŝz��_WN� r�Iȵ�O��wQ93=���K�U��*(AN�b;��^��XZxn4-��:�D�x�/Ν�c��T�,D�ſƷ���X��"*��_�x�)T%�c�,����!-�Ԏ'�]��k��[��sb��4^�k����A;]����ܽ�\^��U��Ⱥ0��70Ӗ��O-p8y_9��>'�bdb�(�\�73�%'z'�P�Iu�������PD��թ��H�K�t�BK�n��T��������5��T1:�t�ə�_*� 괥�t	5�� v"�D��������f�Ԛqډ~���X��kÑ�2uL���h��S��2�<��C<FӬ���:*1]��)��-��3�@4J4WwP�,g����VN�Y����)�(C43��u"���Al��a�ԇ��2X<L����EKH���,�u��(��UY�Ԍ�>�g7"��Oy�m�sϰ����:���q>QO\�D/�
>1��=.jT~jd�U%zo<n~���(��.C�6����ژ
V�a��\�P ƹQ�M:�}�g�����'O�!���=�b8��hR�2q�1���,1�A�z�ض�IS-�Ed�r~s{w	���#�����-\�R�~�H�ܷ/5E(��5<��"������U@�"�)�.H�<����6�}����� #�J1mw�t�G,��b�'X�8��̧�U�ma"�#�d�"��0C�*�v�>��ul��l�{Z�C
�Df=��$�u�*ў����(��!̼�po�=����i����d��&ȰPH=��w=�\�
�ɝ�g�@���b��,g4���^1G��/��������8(غ�K�u�D�=���d_�>�
8���S��/u-lf·uRz�����Ą,��o>e���J���6���1�CAd
RVrf�-�4��=�>�B�	؈�?[�9W�%�_`v�R���,��y`�P�\����U!l��Z�B�R���(}BϟV��́���ٕ*3�b��R���1S���u��僘@�:R[hrg��B'aR%��#M�Ю���T:��8���4>N�2t��4��o:d�3>v�#7+-҉�^�0o�u��z#4�����0�f�.((�5>��D��e�������Yy|܌�P�ؐ�v�h5:gK�\}^|"�J��]kc��<:�otrU�%�}��5����)�1�{�X�b�4_�+�^z�3�j�V`KjfU?kY��OZ$U����	R�s&��:t
a};NI0� �31��bz$�ʣ��+0���scT�ql�u�)��L[�I����c\�E�Qs�oj;�oh�`Ymgd�����d�f�9�0�����w���R���$^o���s0|�����H����1�
�cp�~gm�*�����_�U�o��dU!�ݏ�9�U���F�YBW�J���&.���D�w���X���$9~<�j?�|����,�����77�ή3����?��'Bz��-�������6MX�e��i�<�.-��tM�=�P��	⻺�ߩ0�!�1E�U8Q+:L?$�����YE3�M��|�T���'������*�(��_ѿo������5�Z9��%ЮAyό���u\<� .������fXY?3p��Y��rW�M�L�[蚺gm������vQ��a�'�n�{='�^�̧F�}�j��[�nj�ތx�Řj�j6�[��P^�^ڞ(v=��c����E	�O<���r�-@��k} �G��oV�(O�<���I�Fq�� e\oet�C��™�xd @���TN+��x=�=3QM�ty��4t��b+\��ZX ��(��^�����w�ئ��I����YF }n7"�L�+y���m�C4ö<!QR[�q�0��u~�#���-�e=���:�WW�Gfb�wբ����P/3]/�PD��N�4 ����i4tki�_��2���>�ox���h������u�1�~x�^����}��\��@�&{��J�iY��m
p���i�|e��:[^���m�M��o��w���Oy�a��.���x��:�+�Ѭ�o_"m�'�ż��'�w�1>$4��-Z�Z���T�t���3�mԹXSv)w�	����Z���es���ݜC�[�("'�z)?#cV	Dΐ����5���hz�Z �\�o�����]r<Ήn㢔���3ÎC3^xv�/��U��|��zi��~�q�Ƒ	)���ETg�lLe�f-ʽ�.���{7�2^���Z��%�c���uR��`1Oe=O���<������k	�*r�䤼�z����^�b�p����uo-u\/����cq�E��f��Op���k����d��W�M��t7F�*�o48jD�U��q8��A�	�ihOu�{Î��n�*؜`��cTџ3xa�Ѻ��i�vr��ZO�0��
Iɒ7���q��5rjG��hDEn�)0Kr�rʓo|l2��~��…�s��s�{�n~/�D����Rc��vt0�����|d#��x�f?�K���V^�t'Ք�v��ä�n�\9�b�=��t�5�
p�_k�㻦�{;�n�s��W9;"�@�e���e;۝����y�NP
��ҍ@��r��`��f�9���|�p��NO̥�l�pǡd��m��?T3R�fL)��ԋwUNE]Vvۃ�k ]��/�����'0@ {��$�~A���%jX9��{�B�i�S�E�%����z�^0�$�x_�3j�~�
G��,�G9OH<-~��a��
���
pDP�Vm2�j2b/���L�n�_�?�x�z�\�魻Ntz���{�0���B�ȝ;:��:\Sh�Rɍ��e~K����ܛ%5/���h/��sq�^�u��ļ''�cr�|��` k圚Ԫ��g�=��.Y������U��jR�.I	�M��$P�m90�>2����9'�A�@�ͭU5qE}�����e���0�J��G������W�'�^���\<ϒ`!������F}�M)^:"
�Z/
ӿ���X+������P����������7@�M�����o+G��gV�G�uÜ���N�cb0[cN�ӡH�B�V��V�<�S�>9sդHO�&HHYX7�m��������" ��B���T�8+3�&ѕf���l�D�u[q|��Ժ��ߧ�A�N/+4�f�9�h��`�<S&�3]5��B�8�kںδť>���#sq����c�$�m��ΧVLz^w�jɂ��+T���UV�~`J�f\��t2y�.4��P*]#�e�<�,��N�_�G�Hbt\A����<5"�[LT8���HK�'Dp���ճ��D�j�G�9ZԜ]2jSצv�Y�B��o��c.bs�7��β;�������4�=2�*5K̵*��96��!�a�����Ow�\ϵ09����k"Ȅh�/��l�I?�J�V�,".iw7�8��v��r�����J�lA'E.�
�Y����W��4�6�Q��%m[�:�VG�%��e]���m)���%ʼnш
����*u�EJ�-�\�j8Ϻ?�Y�(�f��X��	n�F�<���PC9k1��������2SB���d�r�`f�"�N�����Jm��bv1t�Vx,�%/3����{q7�g7�{v���a����Y� Ttx�FAg�6��J��$mz*��x��r�֌���(�M,{3�!;�^îKx��F�^��UM�i�i�(����vb,�k�3*40�Ĕ� 'b���u�ͧ�]
��,sL��=��h��	����k�"�&�D[A|2	��򈧢��X�ca�cQ�u�V�_eʻ(.2D�ž�YVQE�+���T�.ee���Z
�t�QM�������Y�Y��(�4����
�.��
=�{�T���Dd�Ϣ��:�*8��t��V��W�u��L8Уx�a5���v��xy��iP��U��=#���-w�ZVf�WB3��=�I���ǎ��"C��q�w�f15��훃��'*`kw��Ht%���7��g�boX�܃���}hu��k'���S�Z�fR����F�W�r�+�8��R���#¹�G�L��"_�����:%
dy���M�+")2y�m���<�C���^~�]c��IL�qv��8�G4��q���A8�@ճ��#�I���s.�>��j��+��D�D�U�ءd��n��+8c�}]c`�m�ڑ���s,��#�L�e��L!���2=Q��1\��Г?�σ_������'�
���S��.�9D�pPL�\m�K}�ė4�{"��oTVy�h<�ER�;�^�F"���3]��_�����琷�E����ɉ��?�.n���0`�OЍ��B���><'��'�u&������w����yy�O<�B��HwYs�������:��1�.d�Q�(�~;l��LXVw�0-P�r�_����N���#�8⮺���f#��ſ���6�Y�`͹(��;�p�)Gp6uqq�p��<3�mH����7�_���o�0�q�_�z"J߸޶D&d��A�%�ȶ.��v��`$u�[�1���|O�Ng+$Ƙs���*�gp
s];�t2CS��g4=��{�18hމ��߸�V�z%&�0l;��3C�չx��}Z��4	v^�A�L�U���2�*PJ˨��;��^_��ǯ3�zmD�fY(�0�����k1�Q���Σ�a�i��֩��ݮNX��ڗ�}����\P̪��o_�~ٖ����$&�Bm��������,i��Σڠ�+�t
�;��6����	��O�>*S1tg���cB�&`�����3`b%!/%��ѽ� ���UK]�n!$�#yfr��t;���H�\;V:0oP����Լ8�$F~.���MyD�����p�l+�cuE���SZ�u�Fgݲz�rY�B�2�0mK�%&T8�4��K�.B0ݤ���������\s���:_���F�6B5XS.8W��4k�ѫ6���Xe�Ȥ��vtss3��Qs��.πe�����<%�w��?��癄bӈ�-OOl�%E�/B�5[ڬ�g��_K��r2 E瀿���xt�G�(}"�qK'R�>��l�/ٗ��L��|�龻�Z� ;�ȝ�y�G񸘩9������o�_����I��������Y��{������)Ah�M�G�!�7S�s���]x�tk?�N|S�Y		Չ-U�1�-JV`^g|	�,�v���n;���Ɨ)!�jY�R�+G�~�	p�VP�a]MuMu>OV��&K���Y�^C��.2…w&��2ن*/��ڕϴOAu�������&�1y�^F4��� W�CI��;�+����@Wt#=!T��a@i.L�7���`1����Q5{2?T��rhЕ��}z�^7�U��!�S�X{�,1�̲����;�e��w���X�!Qw�KT��/�e�M����F%��F�S�^���M����x�����V:,��q����JB�^�5���DB_���ߘ�H�9#L�Ji1UE���٤4?/�+3��1��f�G/������u�k��Y�|Dh���.C�&�o3徾���wrB�y+���x�T��t�����B9-�g8�u��7=�p{tKʮ��Y�tw���~c���0P��р5�����8>�h[�8~z���}Bo�m(��|�_��O>�vn�V��f�F��s;vn��I��M>=�a��+����7@H�%��e���
��lG#��I<�K�q8d|�U�
���Bv	姩�8��i<��d��I��F.>SzX��0�/�ھ�����(�����A3��D��`�d=␛�K~���l8�`����@���`4��wQV�9n�%�{��<��)fOuJ2E��*yI�o�
6�W�^��~��o>4�|�t9iAK���x�QK��
<e��sO(�H�j��r�2�>�e�
�R�SOy����;;�-��',�º�|AM��D�%��7U�3�m�1�T��i�W�D�T[�F�(GǞ
Y��7�gI���ȽA�OCM�5�5<=/�kk:��~��hp�r\_��)��uٶvF=C�1���u��
4���$��v�kOgf̦d <�[juM�GH��zh��3�
�ا�ѳ����T=3����{�YD�s��6yu�ީ�F=���'�������	�Y��t��k�Q����$��'��o�M�K�oiŠ���GO&4O&�tM|<�h�S�u��Ѕ�A�|�k:����{�w����8���>��������g���V�$������N���%xз��A�x��x�)*��w�3�z��U�}J�.�'�s~%�TC&�3��j1����߉����ݩg��<�شII���u����
Pת������3665z��+غ%N�����R�HԄ�>���� ��ň��=/}�d�Cv��
���K�R��v&�b��i)�K��!3te��,���l�씀��p�ق�
�7��%�ـc��Cbjq

N��gW7n�������5w|���I����Jj`����1�H���ʄ?6Xk�(5;+Y��}��F�o)�C�!�̲�S?Ș���@��ǮR�]k k�R�'�m��C�9�2�J�#7l[
��k�K�]��6Y�}����<��36<�S����MJ�x��Yg8�K�� 
���Xl�]�����
��Z��
2��=E�l��	��ȣv&"R9)V�wB�y9L��q23{�A���g�b��Z1^�X��$

���M����L �G+"y�h0U�y8�84�*
���^�^8��e	М���������1�/�vTi0�tk�.�[L���*���9n�𩅗�`��֭�[Cb��3��q�Z$�v$̽D�	/_��xBS|��:kW9cr�P�O�(u���̪6||�@�+C��ƙ�T�����>�!�g�a��:*�A�d�Ťa�%Ζ���A�5��	�&˽AO�IB,��^6X��%�_(@�I}B��=�)���h��)Z	�a�ý�)�R�V�y�A�>�D3
.��4:�?
�Ӌ��Y���$jy��b�Y�Y�Of�*��|�I6QrBȹ0}����m0t�����l̙��<3�D�(I��^-E%M�
�Q&�Ƭ���P��d���ï#��Je쬱AZ1�oLfS3��1���5�LQ���#(�!�?gb�.
���V�"�ͬ�[gge�g˹�Q.�#�jy�9"7�o��p�#l#��1՜�H�3�C�2(�SsY˥*5:'���L��ۓ[�&�M��a���Rƾx֦f���xB��H��g�L��ب���|��&�Ƚ��`�!�]�m|'J�Tָ�d�Ek���
a'?~�WU��Ĵ�}+��ق��lt��I��U�5�y����IJ��E{L��:ą�ȍ�����9�/0;mY/U�7,���� �!�Y�,i��x]���;]���Auqv�]f��j+r�x��j�ŤV�@���b��4�`��-8v���4ݍ�m�s�\�L_:|q�:C���ɋe���"E�ӻ�@�#'�^�2K2 Nn�yޗ��E�P�S�μGz�%�;c�����knL�e-w=
v]P�R�Cz߭i��ё5Xh�D�u���RDB�$���=�]�ƷH�v}74�}�-`����IG3��J�ɠYfӰ��X�
q���6��q�H�T��5d��b�z�t�Ӆmiz�Vyޡd�E�A�4�K:=}��c���9RO&�aICSB0�O&�tuK:4����D���8��B�wXE�M:m���ar0����2�M�[6�a�"V��5��y��:R�kB5~F�6*�c����k=)1���~�w�K��9�B�������-R;!��E{v���1U�W–2��A���z�#�#�j�?7I��p��̘�q���P���1",��9�1�h�k��"�p���9�D�DL ��E�<CXB[�v��Oϖ��V��SO�'�N�R���K�������!|�펏G#�)�~UK��DD�ZKG8�:-��h<3k�u�;���]D��K�c�w�V���i�7�z�VM�;DKk�Q
3���X��.���g��*��7ѫP�f7��$�gS�	&�5�)،�+��*��x��ou>�7�PO�D��#-$�h��s-K����]����'u
�Y��
����
��lL�N�1��2֯ut��]��;f&j�V�����yN�q��~��T"�\��h��V�y�Q�	�I�e�V�qe�*r�kGN_�%�U��?/ҫ4���:Mʰ��F!"Sv~B;�fR!6�JFQ�߰ok]���^��>��ӏ�FGy�	��y���hK9�h�hhX9��A��z�,Rב��>��:+γ��]�L���q�]&U��np��&U���n�V�VU���qg1��i�CA��(��2V�cP0l��[jp��bb�j)뮯nY�R�v���eq�nµb��������$3m�w��SY��
O `Q�Ɂg{�u��9��*zx���,�9s]I��@$(=�E�\���W٩�eL��r)�>jf$�� ��֢�j�|[u�ِ�P��v�j�sW3Sze���j�Q�$
�r�)�����=('����Qi�
W%��%-w��:��UL`+���<⿬3�O�]I����DQM	���I��Pl���}�-��(�.�)�5��+�`6
2GL�dw��.�a�FE`6�½/���~�	��徸�L]f���S��S�C�깷���B��N~4]��߻}`�D���t��3�T�E��0�P��H��:��fK�������J[<��(s���Gq4�ܥ��a4��迎�U�Һr�E��b���Km�.kob
�K�a�ɒ4;.뀮�@yMd���x˝ߤ]t����lgv�/��/O�&�6^�A8�]�Eѐ�?	v)�7��aO4�f�%`�BN#�-�v�ٕ�48����(E3�]�cI���|�������l"c)BJk�9�TlI���UK�!���g��~52�0m���xf�ʮ��wK���ᠧ4�XT���0s�hՠ
�'�r8	�(Q	�R�l�=�|��H�
�:֌����8����>cx�+����p��8̤���X�j����;h�(*
���i����vHQ��*H�����ב��AW�v�������lA�"�Hƪmo4-Ɣ�TBS�#�/y���j�꠨\��-c���i;i-W�ڗ��!���e����Af�U"���7&)9
>r��"�pF���z�:�J'��DM�i�� 4��hR}3��zʃ�հ~kVRV�V�r��X2B~��u\a8��I�]c���Q���~Sn}ѽ
�ٲP7Ǿ�@�p�g�,	��x7&��i�M39����F�ݜ5M�;��������e�l��`��ny:m�� -��=�s<��rM�c:1�='6����f��j����]�~b���xO2�%E������8�o��qOw<���BRr=��B�	�X����.6��l	�M]'���Ĕ1fԘ0|��RR��k���cr��cͅZt�e�a��ר&�ʓ(��U���n��!wn,��h�5fs."�	�R��hߙ00.���D)�l(-��2t���٥&x�6�	ۺK~v��d���L}�3SpQ�tŽ�z�QՙT�i�F�Eon��H���5��3.�6]��A"�C��{�pg5����7��`���t����W
/ĥG���p�:?>�_��q��ű]�Z)��3� .$�L���l4:ǞO�@o
3��ʟ��>9>'`�Ɋ�WVFg%S��R��=�.�*	@�a�d�t-��I���}��|��63��
�*�=o�v1-j6
όٔl��v��8�y��t��[}abj�DW2ѕL��p����v닏�ʝ_�;��-#�D��x����=�3�q^��oyh��i����&�au��R��F��'���2YQ̌߷!<r�jn�Z�����ސ
��0q�Oݗk����,L�U����﷜��Nz#nz���"��j��Rǁ�_�*��$tE�7]�Ꮓ�t��ە��� 
�7j�kéLz�F���p֫������٧Æ!�R�L	r	V�V�A��Ђ�-<.,1n��V�IX��5�hk��~�G�"�m0�r�<��\��I��l����RԷG�5�h#��P�<>U?��TӚe8��_���;K��܄�{0	���	��bֻ�Ɵ�X ��	�|̏߾N�0�#@	�&LM�A�
�t�}k'5P3a�jy����Ym	A�b���$�=3@����B�V�Ky����mܵT#�O:b�o��V?�eSxxx��ӏU����H~�A3�����	hV�Z������Y���[����G��:�	�<� (��\���hUg��z��0`�g��C��5sE	�u?�щ�I���5��U�^0~�_݊��]�h�B�g�ˍlX�to��(�'p��c�B�Ў�k\	�g+�mmC��&���-�/1(�����гuJy�#��8�A��!���(�h>��C:��*�Iƻܐ3�Je\���M<���?�K��q��e�f�C/ν��@[��S�/��<�Ӆ����?2�#�dC�Gs�S	ޖG�nַ"��J��K5���B�cjhF�*�M�����2W�bU΃���t`�
V��������W��nwC�tS
���M*����d�ȍ�|HX�v��j�+o#Y�m�ms��+�#���@y��s���i5�ZD�<ak!F�Ά$i��1���	��Ja���6�t�h�6�Vt�=��C�\���LO�k榲�VJ�]��:{?���|fnc����}�N�u��l<q=�����ɚp�#;@A9��5�.R};+�a�#���|�3[}��:�;�Gn�v�56*5�����Si�r{�:�.*�k��|m<e���k��tha��OF�98렧�s�<ۣV�u����O������P{dD��0�>e�`�/��t��I�8W�4��7@..�wzվҴ����q�����@2	�ҐYM�irN����D88���qa�h͙o/�)d8r�8o�c��ݡǡ)�)Ϭ)^��]�z5��a^YfE��u?�!r111Yh�6ۥ}`/�z�g�Y͠��{r#��<�u�{Q�ncV7&coŬ�L{�U�x3䐙ͷ�q�Hi$�c�1���	ᇊ�|B;�iX���+�L��?�{�6Σ�Ryfjj�6Q���;�7K�Y7I�'�,�!�qi|�6�����JPF?*installation/template/flat/js/modal.min.js����XQo�6~�p8�WYQ��v��
t@�]���@��D�=�r��;J�L�V�
�R�wO�;��cy꡺�,�%�(��f�d:!������v�w�1�z�4J�2�\�ޢ�b�J+�h�h�����Q��XDeR��A���`f�!����}vF����3�eT�B���S����D~�x�k;2y�_\�9��FȊ�	U$�H,�Ib�oR��<�劕7D�����+0v>��d��B1]�r�U.��%x��f�Ȳ��Bf�T�2�Y��	�&��K�IL>AZ��*+�
ށ6ɍ�]��D��o�����A�H��߿?��m�@�����4���ͯYa�g�q��Nԇ@�؀�	����~b`�����[���h+�n��N
ܝwuy�\�f֩n��b� �Ȕ��A��%S���n#�VPќ�O=رh���& ˀ��Ι��(U�`P�E��K*��h�PEP1�FCǵf�T���t�n��4�c}{�#ñT�)�����x��t}�$���\P����M���!�2ۓ\�:�?k�6�1�-���1�M�.�b:-�y�)��Q�_/���㽤�
+3�/�p�.��lI 
*��j��Z�s�bS=ȷ����]1ڪ��3�{��D�18̻�k^���k���#A���c'��X��>���x��m���j@r�~������hL��E�{�N��F=(��2�jj�v�6��%�g!{va���5�Y ��]��o9P($��Qx���]�_\������5I�y9zH�����!�3��ˢ�^֥&�U%U�
�+���pw��r���^06@������_��~��t�y�d��!�`_��r7>�d;���u�+sg�m0GM>C8X��	�5��p|��b+���e_O-�
�N<K����-K5t-I/����SMM{��k�U�^��u<������/V��
��,��?�<�^���@]w
e�v&cM��e�c�Jo�%�[C����)]84?��
�T(���͸�|�eY-��{��;�+�ЌYJ�-Qy)K�Nr���a�BWNjW �2?�r<�v�H#�_�{"SW�׳�j�6$�q���sG�ClDz!��k�D=��y��K�	�DI!�u��U�A��P��7�v5�Mے�ܶ5��}���,
�%�k#�E�@e]����}F�zs,��ёl���|�-HޘÎW@��#����:
��"�����5tB�R�#��
MJ��Z56O�a�w����r�r<.'70n�$ȥ�ߠ'�C)Lg����e~�jF/_5|��L�_f�L:eOT]��m�����G�;�@�j_��¼"g����o�����pB��,:��g��a�XKj簆��
�W7�w١ 7�D��zo��U`��Y��}�W{���+蟚���>�#���^cM��ҹZ�U���u���ء�A�\���|z��ŋ��&��U��/�Oo�&�A��`Y9��JPF?*installation/template/flat/php/buttons.php/����S]o�0}��]�� ���ES;Vi�i�*'�!��bS	M������\�{�99Ƕ?�Ӝ�;�.���*E�L!(�(�b���
�+HD!�6�h��
%DR�1�{�nC
����SdBk�/���5h��aًD�/�:Upw�ڑ;>{���,JEF%<������eBB��xTqɕ��4���3,�cA3xڅz���
i|�tA[ʴ�B/�	�1a��:}��gS�5��a��
wJ	.a-�2�kT��WX���\��
�uMjH�����[B,����/gWd΋��7��V�Fk:i����� U��b��<���|�z���i���>i#=plgD�?��L����#�f���J�jE��Lb6-��GWl�}��?�0*�3u�ME���W�F�E��5�W%
�d=*%M�H��,Ѝ�J^�=j�&S��E��p+(��J��L�('�)�������M(#ۧ�*�I𮩕&�f)['W���uo��~:�˜�K�G�<��P"�Y2���d��E)��w^J�ߧ�*ѫu�#�JPF<'installation/template/flat/php/head.php��
���V�N�0}n��Uj�A�e�!4�Ф��u���q�ؙ�E��u����mla�^�����s|�G'y�{����0��x;p� hnj#3\
С⹁X*�p6ρ�0ᷨ!T�F0.`0C38�"�+C�J��n�6�.� &�Z(�B�Ib�l�����^�����5�2
���Z�r�J
A�Ǖ�J���(�忸�(P�>��Ԁ��y�J[_��,�d@���"�����2�<??tzq�=���A�1�M�N�f�jP��a�2wr���m��)�!��C�i�n)4��c��!��,�^A
���[T0E�����	#�E	���s��q�9��w�hq��S�����Z�i!�
��9�.�)r<�w|�3Bߏ|�*�������-菄����M-�R��j��wL�-sUG��f��>���_稊ݩ^��ϘȠF�[f\4A���7�e�X7�)�G�h�O�j�9�25<ol2b�&2�����/�ѫr> Z
��ڗc屍�H�����R(�AaJOO�.�$o$j�H,��P��l8���lܔb�]�\F�N��Ϗ�v�g����B8���z��K����t�'�r1���)�p�DCs����b���l������|�C�+ƵO�:��/q�_3�7I15k���P�����~����o���D�?$���a�"7,���JPF@+installation/template/flat/php/messages.php���u�M��0��WL�H@�U��|��m�v�j{���1C�B�e�T�� Pr('���;�ΰ��\��d�`�~�����V:��)ÝT%Xa�v�).N�nD./hA�S8\!9!8<݈2�n��BQ���{MY~D��;����c��C
D8xx�揰�"W���>RCW���
ea��xui�UH����w���
/�Ku��ޒ4���8�T�C�g��������l�O��@*1����"��'�|4F�N���ˢ2�O��nJY���;�VB��-�w�,3Ui�&��>�h6cw�X�ǂFe���gҡi[XC�hM3h�X�=���R��(��s�~���O����d�<kw
z��V��B�=����x���n���t=�ͦ���nRѪ�i���M<�F�?��-�8Ew��v61�3/�QL}xm��ܿ������}�x(ޕ�ī�Hc��ԗC�Ռl�@�#JfK6��_JPFJ5installation/template/flat/image/chosen-sprite@2x.png�������PNG


IHDRhJ�q��IDATh�횿o�@�#�P	� �����?!d�ԅ�sft⇿'R�J0�#[���Ɉ��+��������P����{R�W%����ދ��1�e,�J4�h��'�Y�2�Ny�H%?��/�4��
L�j�[��	-�85H�q���H�����qȱ�s���6�C+�%0��`QW�X����O�5��
�]:ڿ��h���Ig���7�oi����
1n� ���f���Hn�'
�!-��
hjh؝l�n��zH���A��oj��Q�FEæ�����hH
'��wԲt�c �8�H۪�/�4��
L�j��`$�8�� q�iD�S %N��9 �J�1Sp̶�;X�k}\kN[�[�t���������k�%��s�F<Uk��}dvǢ�W���b��?�O/n&�
�0p)/��Pyf'��~�|��|+a�C�˒�bKq��SB>��p��3�K�X��R~����C�gY�Ƭ��,�9���A%w;8Q�h�H�,�]n�p��Y��>�$�c
��)�ƒ�K�hw~��S�ʼn�q��P�*�w�Ҷ�����X�y{$���u�%�&�Z����'������(�8�؜��֜�b��ҍ၊�5R6�emP�0�<�F�-F��
i��#�	��z�H�|��Y��JZ�\N��IEND�B`�JPFE0installation/template/flat/image/modal-close.png)�����s���b``��p	�� �$W�)WR�%�%��i%�E��)�I�
����A��)��'Sm��2C"J"|}���s�Aj�*r@�ƾ� 19;�D!)5=3�V��J
�)�Jᦾ�Ω�UE��U~!�U�ɖ)J�v
6V@rSK*rs�*l���Z� a}%���l[%��"|��RL��u�

���ML��u���
��L��������\
�����Y��A��l�2JJ
�����ʍ���
---Af�U�W�$V��+�LpI-N.�,(���S���KKl��`^�-��W
(`��W$�������T����W]RY���Z�_Z���4e$��k)��ʿ()�9.�ɥ��y%�.�J@���+SccScSccg7W#CCK'#3'#'C#'3���8��X8���Y��[XZ�X9������z��$�%���f�����k�\��X�_���K�%���
�f
�y)��Ś�聺4�(�,5ŭ(?W�V�X췰$�o��r�L�}���@��x�I��"`�X�z� 0[��tq��5%0�됁H����_��T�_�7/��l6�\��_�$��n��̎{��6���3���Z�!u�|�U‘�}�(x����x��X��Y-rN%|��:q��i�\{\x��Co��nnճ��K�<�wl���mZ�dn���ّ��\�
�W-5�ǂ)���s��$~�e��������σ���f����Z�
*<]�\�9%4JPFG2installation/template/flat/image/loading_small.gifP����[TS׺𕕕��d��"+�KB�T(b������`DmB��h��^�H�0"���t�-*�KDD�����m��<�j����1��rv��|�cΗ���}_f�.eI�?����-Z��/�|��w���/�>}�믿���O���N����zӦM�w�ޏ?�x��͍7&''���|��7�?>q���ӧ��绻�333_~��˗/���ܿ��񔗗�9s���5===??��?޾}������<�Ry�ܹ�����I�F���j0��������
� v��}���~�N���322�z��+W�=z��^���/O�:u�ڵ���[�n�ܹ������w߾}---���K�,�p�B{{��c������뇇�<x�k�.���'����ݾ}�����7�,))�q����ܳg�~�ᇇ�F�����>z�	��[��B26W�m��m��R%*[_��fcY��zë�3��~�9#_k\�A��
2��V��G��f�j�;����Ʀ�f��[�b�ŔX��kơIh�F��h�V<|6��0S�;p�	�D���47p����GX<��I�d=*�S���ۘ8�S�߸�Ch�mT3�P�wR#!�r�ݴc�,@eɌ�;��a~U�Z-�\X�W��^9�����9�z��	�{T�e"�P·�\�:��'�}��=!
��~h�����ŒpꯆI����=,F�tm���T�J�Q&��rv$l3�G!���K�r�h��(��0J^"O�Ș"i�ub�4�L���$U�n���ڠ�g@�Eڢb6��I�lZ���m�{�r��&���Eph�n=�s��	�(�x�V�]�S"���٤�
��eqG�hb��������<�k�D���g���#�_,���������ΖJsX�;��źѱ��~�
�z���*�AB��w��8a��R�x�Fw��� G���J�'���Vo�
*C�+�
oriF��lr'��G��]!_zRƢ|�)E6����u#����im+�L
�t޽�4B�e�^�R��4��ֲ=vEJ⇀�R�`�X7t$ F4�� ��z״��|�ԩ�f�4��o�ҝ
6����=��F�a������r��{ל<`\�3SQ�fe=|��*���uX�2�V��i�z�A'G�R���H����C��h��@�9ꍑS����2'M�(,'D�Q2Ŏ�N���!�֏r��y@��x"����f�?��i~�_���He�_X���4��j}6��`�I�9]N׮%X�{�`f.#I�{܂&Q��`9�"S������R��|
/%�	6���t%.[3�g�bJ#�C�	�8I*J[ι./�H��t���:�������G_�al<����Q����#o��4�F�k�b
�i`JG�i�筡b��z
�ةKT���3��z�-�3����xd��f���+��j��>'[���T
nP�&��Ǖv��&��Ƭ���Ic���h\q
P�6���l.'v�j;յ��.����c�`\mU�����Y:���������R�%��Tpo����%I�L�b�����%y�P96P_� ���#�Xk,�_���1�Z-�{�%Lq���@#{�Ī���D
�-�E.��~��L8#SfOQ�x�o�yVb�]�U"�C��o$av��'�Th�r��y��T�ʅ<�{a
*���`��BL����cĵl��N� �1x�š�,��
I���u�������~	g��F-c0� ���'T&
b!���::�@�dO�ԧ�
�+���S
�V��=��߸���cm�x��u_��[�e�pt����2���kN"n���+��������X�By@<���t%|�҂~I^�L�}�Ʌ��]�R�����xR=�
��"_�Gҭ���HdC}�7��h�p���N��m�^��zcgM����:�lJ�����YpL/jڠkPm-�Pe��%�Q>�����,���h&H3�y�ؚ@P�1�qeY-vZKf�)���P�aΉ���`�ɺ�+�$8�a���7Y��OI�/*�'4���`)
����St
La�¶��!��pz(v\�D�%��03��q�		&AׂT5D�NAq�B�_�*g�p$�4��Q��H:���-��/[x[�h��\�B�n��-a����X�i��Y�2r�$u��]�a�����0�d��p��Vw�9`�o�v�.c���y�
��'ͫbw�}GaM|��?�qʏ�Ud�L��D��@ݗϗ�R�8TV�fl;�i�q
7�:�7�&�#�4�%�f�I���)�"k��9��;M"�r
6�fH��@��u�ApI�Wo��'V�)}ob	f��
�gb\@u� =^�>�&�l�[6�c[d��#i���UI�����<;t�z��3#{������	��A�}ї��!z�i�w�h�~e~��Qh!S�O���?�¶�?p)�k�Y��v���+���^�KS-!w�Y�hLR��m�17����-��A�`n��Gu�*bԾ*By8C9�"������6���}���B�~@ �H�rp�/b���j="��Oc+��.JP%x�x�D
��|j�V7Q�͛}��R7>��ok�䈑�_�0�v��Y$H�ڹV4�#���Ŝ�=��*�݄Ѓ�U�Q��1T��Mr]�=ŕ�>
��m�C
L~ĕ�-R���,^��sCo��H����Aq}��u�*mP�m<��3Be#���Я ��tۜ@��Mċ���/�+��u��ptȬw{�W�bt�^m��בq��L3��xW�F�U��Rv���ڭ���rGxaRq��r�����ѳU{���� >R���z�vL^�##2�����2;M���2�UF��{�"�e�]�_7� .����X)�t�iBx�!Rv�ߜ�EH�|.���/�A�:}����c]�U%J�
�R�/�U�u�O ]&�p�"���D��8N�u�G��ׁ�0▓�U0�C�r���@I/�(�l�XQ�1QT�'\X�V���|��=@rY�6MuD���ʇ�6�+����Ƞ�bN��=;*Vv�%'�̳-=�3H�Cv�3��
�;�nc�hl�HK��-�Ufx��d�Iۼ�ҳvz��~��m{\���F�0٬	�M
a8����L����I\,�V���'�<x���������!ObE��#'���>1q詝5��r����pLt�K�#�9>`a�\e��v�#@�8j��WV���J���n���%�C]'��u<ĤRA�Q&��_'+�Vm(d+�T�>��:��dQ
שׁ��\ПM����
����tA�F�@�ǻ5Rq8�΢��ww��-xA��d^(H'�r�`���W*i@��`�A�;Y˗j�9a
�}��ﺐv�ы��������Ek��Z�S�B"���/f�DBDX��I43����KW�
L|�s��#�V�H�,���<=<���CR~m�2�Xe���#��L�L[-Ta�l>��aNb݅2oU��`�?�u�%kszz�{z��A	��I+�ܘ��)�o�(��2�z_�X�-�_�[_��&{`��eր�`�	.�
7�U����׀^�e��"ړ�����V���#�DZ׸�2Q��xiȗ��@R~����H��m�o��ʼn2筌S=��J&uE�)V��f��Ǯ}�g��@�~MMп	�I�fY������.�2���؇�j�f
�^�$�Z�n˷�@_��#�JPFG2installation/template/flat/image/chosen-sprite.png���PNG


IHDR4%��^�IDATH�헱kSQƯ
.-����=�$�b�o�$((T�Hw��*����"nupA�@ P�Apq�J$p!P��M1��.�����;���=��\D�.Y�n0��@}�DMF���>Fb��1���
�c�	!6�1r��b�%G���I��J(v��fFy�O����H4B c�1�}��^��4��5Fo��G�X�ٝv�U�n�(�R�s�p����v��*��8sP���*�c�O�TQWŬ���j1Q�H}����T��+���}��֕d�/���L�Lc�F�6�˔�7��,9ʼ1IkJ�(�dJj��Lc�^��z*"Hu�j)�׿���,?<��._1�a�������°x�	/b�}�T!�����i?O�u�	oc\������eN��c:�99�\@�s� uZ���q��|yp�k�a�����6��B|���1��G����gq�u����p�+���[�*y���IEND�B`�JPF9$installation/template/flat/error.phpm�
���VmO9�|���@��U�T؄�6 T�V�wҩ�����58��vrU�������@�C�b�{晗찮�^��e^�����b�\!X�Z�
sB+����B��fR3y%�h!7�r���q���P|�s��&��5IY��P
{���F�����_�����Λxog�\��ҒYx��;����ZO���.l�;���QY�z���B�$|��H�p��w��z��:��z��on\���6����u��zY���C�=��N�`�:x�Z�g�:&e����"�̇�^������.�>\�� K�����؏�g�6.�\+������s������L�6g���N�BI�n(��Y7�h+D�r��;ͭ��2X�����I�����"�x�� rfn~2��p�0[w@�ٔ5�X��B]�t�j��mD!i���`&׳�q>�R�΅u���>���	ܹf��6�{jl�W݂o����m����9ot�����upw�_�ఁ'귂��V�����[����,m��4�CN�e�QT
��lg�gq�Ep6�:ȸ�.��!�<�Bڢ�ǃ^�hV����Y�������6P�}Z\H�m��	�dž�P%x;K�C,�K]����Z�<_4q������c�rr��u�9�x*X�����>O�Z|��K_m?u��e�䣧��l�2���8�c*4�`�<]\ֿ*X#�R��GW�It�G����V�sT�T{�t��K,��۫W	ů}�e).�T��=>"��d)����\{��i����{r�lA��@C�gi��x�W4F��1Z�[ۡτ�К���iv~�
ѓ<���Q1q���x�*߃���(Ե�p�P�	����o�Bqf���9��͍�G:m8�#)!X?E��"O��Iכk:��pbiV(܌��k��#.&� ��|]P�r��O��l��T��)�v�iY�ė�h悏�wC��o�L͛Y`)���@�'�7dN.O��nM��dy��P{�N3)J�߸i]M�ɶ�jK��Q��.��zF���i�I�n����&/��s�����G6hsj)��e�Aa����r�eM
�,鶖 ��O��w���a�JPFQ<installation/template/flat/fonts/akeeba/Akeeba-Products.woffZ8l8��e{cp&Lml۶m���v�ضmll۶mnlml'w�n�?w��t�tO�T�̏vW���׸l�`��J��STad�'�A��՗L<Ll��������������O�b��ʏ
�ݶ032b��7��8�\�,����q� �*}��u�g�������_gco�o�?��?�k��y������0�����?[�?��@@ ы���.��_�V��LTC�mbhjd���?�?�<�X@(��`2>244464�À,�`�a�aOj
maaC��@e]B��Ŀ=
��HR��X>�D3c�����̚,b�h�)_?���{�	P�?��?�?�?�_� ��L�ц�1rT�u7;�r F¾������������¤ƨJ1}=QYI
����?���[�I�QP��@W���j���ɰ�W��VV����<��ɘ��jbm▪�vF�W�%C���2���J�?@�k���6�7D�����!��#��e�T
��u�磧m�溶��৪N;;m���̾����ř���3#k`���ܡ���}f����hL��~]w�
���]�B�ޏt����@�Kb�=u5K/ыK�9a(;n�U�X������������	P�E��k�khC
&*-�
��	��H�u�:
z�+	�k�M�9�@��y���z8xQ��Ԝ!k�v	W�2��Ⱦ��O�u�v�T�m�c��hxN��jq�fJ��^MY@�"�����
�W�i:��k������\u��*��
���J���&B'��s=�_LN��0�i���=�B�o�ļ6�N�2��.����#��|��M�u�M��\Qc����{yA��g��u$��i��dm�`�t�=���o��ɬ��#7kp���1Z�}TXщ:m�E����R�1+	�	b����ϭh~#�F�L����-<x	�[��KK���Ж{���H@�1	'c�[�t��@��G	D��
��*�d���m�hB�}�o����u�W�g�w��=:��:���;�������j�<��{!�־e��S�o�A��U?$@��=����^����t��M��<��`^�|�\g|�9u�^�4�
)*����O�nW���g#�s��	{I����c��:~maّp�f���:�
���%�?g��!}%!Z��+�S�sc�������`Kpw�S�/���ڐK�4����?3`ql�Gm×b���$���T�P�B���T�/�U1�S
���=p�����o&7%�����,u�����h���~g��V����B�-���;󥳐<{��O�`�J$���9�k�O}][Un�M��ϫ?N\�d)�s���,“	��"Ms��W�����}-����}�e(>�(z}_��K�(3�����.�j�ɯ�%���������x�nb�~D�U���Hx���e��Nx�>�,6L�����UH����>�������c�x�4j���V��sq�ݤ�J췊"��~y�-�*㐓�N�#�<1�JI��7�����C���2��$)��B�%�bYb
�x~�eC�|1��_ߌ;����hi�q9~��3K�č�Y\/�x�T��A=�L���+R
�%�]�����Hv�C���|�����m���.Iȭ�z_�=_�?E��Z��uB���8#�fg��#t�l�Z�۝;@���Y�i�yR��zE�A3��$ib��<So��)���|dPa2D��-�(��/b�A�~�r3�trױQ7�\&md!��F���V���xp���:.�IE������"��x�Og�d4�7�jK��U^g�=�1
n�{�����h���e����� W'`����7=��!�����#��|�O*C�i�Y�C��g�6�]v���=�<<�7��] ��%��EÈmĔ��H���u���G�%�/zn����oo.B�n@8Y�γ���]��-VZ�j��������5�8��u]���t��-�ǐ��6�}���zn`c[.г�1O�%>��'9Ba��FѼ9y��=&���$֏�靖��܍�>U@\f����fn:ve�oX� �a�w����8����VQ��Z.��RD�-0�W����Z(� �s�#lDXbI�?�;����S�]�������[�^G�׮�����w1�O�4�ǘat�@0_{Vã���"͛'�璇K��U?�:����E��E ����˔��p`8ui�q������9�
�[�}n���hc�d�S�V`�:d\X�bO�}��ח�1�k\}�N��xԱ
��	�oK��v�:���:�hY��ް�'7�l���`A��t�jBp�a��&��t6��I���R�����������)�ků�8=����Ve
a����PV�<�Pr�a�k�j|XL� �|x̷at��t��h��lK�X��� �sb�RC��N���wmsI�?f�*N�2h����<;M�@��'�<�s\��e�Rz̽(|FY�=�X�㮳
��mN�8���R�����̙�9�pБ�?B�v���c�3J�
.S�X�(�$ϴ�`�_��zK�Q��Ɇ��<�*���a%ס����=���^5m8@��{�H����d^�@��n��ҭw�Y}C��}�	OL����25R��m�̪��0xaw,��G8.<]��~K�7�G�ǀ�?	�9��0��ye�O⃮�J�iе��N�U���
q_r�����2����K3ǹZ�쉨e:�	�����O�RX��`���<���|>��+����k�����@��W8�p��2�N�ۆ����GT���g�Һ�Z���S��x����e�}&n��yb���?Vw���:xv:u�tJs�p��`t�tlSs��H_�c�,�ɗD'/:�\��'���O���H��ә]�
=Vx%d=Tr�W���.g7j
�[�N�Hma��ʫ\�O���bۼ{fٲ��*�7`�b1��:��1�=l���S�j�����mV�J�d�����a���i��u��A�|��hE�3�z�'�X��
����ߓ=g;�8� ���W�=�s
�rk^��K\�'+Ղ����׾�"��!�hb�6"[W
i3��/?�c��<ݳ�>dw�YX3�o�
��m�&���t�G=��Qg�U�����j0G2K�Ԑ_֔�n���tڞ�="O<��D\�	����%-澙Atgk��$ZA�ى�iީUu�U���Ar�HL��Z�m}Q$��Z7��h38
�"�{�h#iZ�/$�L��7c~8��% �w�m�����jkb����;$�e�1]s�﹉Y��|���0F��H�mo��=Dr�qva�L�@�������~ը���õ������m�T�굴�a�!e��M�s�a�����&}��m�l�E�_�q�����lJ�
�ǽ݂9E��%;������n�s��(���D���G�X�7�Qj1�A�&�����u@\��e�bN�ҦqY��~{P�@�H8���#�y6#�/�b����SC���t+�,�����U@��s`n��$��
m5���oK7�Ϙ?Q�K���!)��x� ��\q� :�.�N�Z����9�D�c��+,B������*�'��S��u��o�
�~f�G�쏊h�i+�3���"�='�C����A�ya��-!}�C��"�3�cS�#B	��V2􋤦����$�E�d#kS��$O�=nZSZ�Y�WQ�b*��y:�O�^���/*�*	X�X�˕L?DT�Gm~��K��Ҏ���G;'��ᾤ���b��5�&���a�Y/�I���옞w������ �3HcE��{oM~>��t��G��5��'N�b�F��I�3⿡E�U	W�Jz�HR��}����S��o9^�_�S%2܂k��Z-�e�ύ]E�f��R&W�f`������No��ff���f͜υ�.����4�e�`Zre�՘�
TPx�Q�Ŵ��P��t���$��0#.
J
f�25�}�r���&�J��8�Ǚ�
���98�?@�����ڣzI�0|oѥܟ��5���ȫAO�j�9���a$l�GA߹�n�!�D�HQ�Re��.hh‰�$f\GH�yT�Q���Ǩ�҂(��!S1�݈J��G������OD�S�v��+Bń�QUN��1oŔg�#G��I�,��s���@B�
~�^L��ݮ������I+z��X�]Ëa0RT��{4��FV�U��z���Ui����_ߝ5���B��E躈��q]���Bj�- u�x�O����!�
�gDh�����#�8�<%hX��F���
��#"
����d�^�N�D�G�T�ґ�.��ul���L~|d;B���eS|ٌф9��b�bh�_���h�F֓§�à"}�1�Ɖ��u�'�G�d	����Cj���6ĉQ�Gf�?�E#$�Ŕ�j��F#R��;n"���Q�`4$��Xq�ޥ�}n����Q�6��.;5}�u~_Naj�v(͖��%�/>���w��q�g�Z���Tx�#D�8i��!�;y�RSr����r�
�2G|�*T�B#k �Z���&F&E�n�NoA���鸬�-��G�K�A쎀��/�с����=;��5#:��c���U�8�{��)H�����?3�"���g�\xT;�x�[o*�N�7m+K�ў$pR�����Ub�X�\��tq�NHD�'\�(5�*=�;.=esj�Ş�-bP�� [�{��\�_w �{w#�0z��Z��;s���cpc�e\S�R��Yy�c�-5�z활��ܭ	�프����j{n�X>�9(4�-ŴV�.�.�L��[&�鬺R&�n)�d	�v�"����T�{�v�銩���y-�B��!�xj�*E4��R���`�D�0�*����#�-A΀;�Z�R� �3fXh.����-�⹿���\��|���s���2��p�E�� \.�Bwr�G���,��>kаm�}�:����_5�������`(x�'�&�l/m��)�n���D���p�:;1:z~�X�Fj��\�C�7��`N.�Bƙ�$�և��C�>�9��W�2a�D��x��d� �3�A>Vc��fC������*����FÒ�1�2���䜮,�+�v�B�8l��
�QƳq�#��<N����#�d�a���i9�Y)��!�G����U��I���-�$���o,��ٙ��8M���}�^�՝&*䃌N�͉�d�=޶�p��\��6�u����%p�+���YF��^�S��O�8TE�î�Ǐl؅K�xM��%YE�5d��z�F�c�K��”��>�K>=�	d��f�>Bt�q�X��VƂ
�2񁰦��&.PB����v�uy1d�՜���p���o��Q�V$z�e�,���JWյeOY��i��EC��������|J��N>9�����u�����}���P��i���T�.���N����N)����Vϵ-=����4�1-�	���Z���Lw��N�<$g{����L�j
$m�����Ƅ����،��tM*JE"_��?T�;�X���Ϫ�6���ͪrR���}o�)��y�B���靏�15ə�S�oc��@��ь"+s;�`��H+!�si�5�����O���ʇ�qw�eC��C����Y�[)���Y��u����H�
s`�����P����K!A�cg|
���R0,�����J�T|��Yv[[����J���.���;����8I{��w</o7����D�/P�תe��&=.n��N�X�J3q�ݣn3��#�GI�$�H9���"�8ճ�|���e��&���t`����qͨ�Dp�p�/���$��&���`�|>���kw�9�l�ctȉ�,Z%��"�Gt2��,w$�( UJ_j����}l8�B]�[��A�z~-�h��k�Q�2Hu͙�����27��;/e4�jIЯ�� 
L����QO�x<���DOݨu��2[QCT��wXemF�)-���̧����?��o�&�B*��������Zoz�К�3����l��&���7��*sݡSM�2R��D�XH2=�S��P
��Ca��T��[u+��N;f{Mw88j�H.�M�-�-�t��و���'�Y���g5e=jIu�Ɏ��B��P%5���QIH9�+�����%�u�y�z�_�+b���*�Ұ�E�Xv�k4����l҇����w�l�4�H*�}pֳ�:�8M���c,3�G4�X��iXCG�[/��6&5��g&�>(�^�Y�΁fbjw7��j��3��"2�z'��h���W�cFG�#[#\C͵�J�ըm[F5��m�#�LEӼ��x�r�vKIa�|�I����qO���W+�ί�a�ұ���cБc��:�9V���I�%�2�E>�6�ko_4�V�.[���*)���*'��c��s?�`��J�DݙIΪ!��*��1Tdf2F1��Xn��OX�2f�(��S����6j�)�X=4%h���ܫi��K���
pL������t��g�70+7͕j�[v�@�ԕ^��+�u��y�f�'��N,�����"�V��
�A=���1S`��&��4�� "b����ƴ�����G�i�v!WD�� [�f[r�A��݁�����"�_5L1ҁ���� ��u��q\d�B2��+7+����.��	Xdj؟�ד�"��SF�u���]<�榒3%�A��;
��g'�Z�a�4j1OG��GQ��$�l��y�@����
�w,`}L�����]�\C����N�j�P�`�H��֪)��j5�rK�q35c�\C�`>tȔ�æd
񹆆Sp�
�<���j�}��x��<z�E�B">i�!ڐc�U�L������%��,����&��/��g?�F��S� w`���j�QF�aC�!i��ī[�HP�wYV���{�EZ:v�R����ux
]���2	��xR �`Dv� �`��*�$矏L���б9߳�)�"�a���"������{�-3�"�"���M�|�R�x��"����%I`�f�ɵf�}a�J���	A�
a}�!o6N�C��+—�
?��A" �P�!���3jc�N��%��
�K�";�R6iw�C�Y֯qq�U���5m}fX��{t�&.��jh�:��<���Z|��%�4�����z>�q��U=<��Ǩ�|t��"�7ren�]�L8��.wL{��w�d��?��^�rU�\Ρ�^0���m��hk�JND�ESvt����}lN���ۖ�pE��2��Մ�.\���84�q��}sOڛ�8䐀�kf�3;��u�q�^"tr�&��i��`P�,a�|�3�]�wohs�Ff���o̅fZG���fZ�y�z��P����5@Q��;�%܃c�p%¤�c�\/5��k�!bu���Q3I��w��I(��z�R$�)ܟ�`i����BE,��;4��'h�[ؚ��n���:��hڭB�u�E�c��ZG�
���n��P�p`b��큫I!�߂���%����l�O>Q+��Mɽ�ë��ÏF{3�
���2>{K�JT��4����DA�kr�����-+6�i�N���J<"��0�n�q��>>�]�������~M��*��� �y�7TԢ�Ϻ��u����)(P��p���x�W�d��`����<�
��fz�+�k��ד�Χ����5}��dT����ߣR�Y�"u�g;3S<��V7 c
���p���p�����;�::+����b"�V(h`G2��J�O�*���[��0�T���)~r���A�4it��GK�.�#e�qö��(h����UP�G���0�&FU�[C
TBN�, V�U.+���F�A�S
��4�F#}񑭈��o$J�����pU��B6O��nc�y,ѩD���{1.�'w�d��F�L���+&��!`ળ|�I�&�����3rϠ�x�a�f�4����ƪt�j�]��}I��O�"%[����e/���!���g%�;�,^L�uD�m�A
�]m�8<6��*��~gz��z�ƷD��Ì����>ϋ����6��_d?/X�����
�e��
!N�����
ȒN��װW��o�T�6�?������d� ���~�X=���ŘI���3~���ϱ\�%)6r�����Ey�bKUz�^�&�E��~�AW���*��sv�8�C:%���Gϕ��� 7�
��!���b�L��Kґ_7��y��Qy�UYH��`rv�t�.{M(S֓���`�s$d�Ā$�7.���"�y�O���h_����gE���]�8�f3ENb0�juVp�uޣ��(&��~��nd6U�bBY_�l;�(��0���,k}���ܧTy�;�"]@�����;����D}���B�#c-O�����:�(��u���nja�u�EX��H��J�.�=Q�}G�k@qs�k%��Z ���;��鮳Š۪�j�ξ5����֮����4]���*�G�����'X��TZuN�Ҳq�~Kћ��q:����i��a�*n�x��{�C�n���xj[1�.�����!��;�=�?�?�?���=�f:�Kn��!�F�r�߳7�5"R��/��{l%��iD���J���c����2���jS8*���A҉���iQ����wN9��M�Mu2�tɾ�9ux
Wޑݷ97nh{O��j����mx8␖=!���鑍ؗ�ǒ�yBwouNwN�Q<�&��w$��ӊ���� ���Aٝ���?%��q�iぷ����Ne�l֌[�d ���S���6;�G�4�wދY�/�2{�����@��(��_��a�d|�v�u�n�XЂ^|�����̍�_��L�0h�wf�@�G�{+W'��[5��مq��]��
�`-��SP��v���C{�|��#�x���U�r�yS���v��Jï�J���^��0J0�C�g�aߧN�
(������]��g����Ǫ��Т�LZ��䓑H��o�V��N4q�.΄gXEB�ȱ
��9F��~*�*�"���u�^h�č�us�Z6����y�V7�{g�:�[@:c����%N�8�(C^R��l��g�Lb�b���&+�`b�k�jA����-�O��Q��U���Ⱶ�db��a
�yr`��\�u���
e.�5����qA��Yȿe��i)9���a����R�$Ξ"��U��am���2�׈'�C�=P��x��Y6�ٺ�iǝ�|��a�6��u�70(e���FÚZ�*�w^���Zs:}}���43��ׯ5D��q�Z�湳�.a�*�,��1w���f��ZD��Ϛ�=�Tz����w�^�9��97>�DY������2��|�T"���l�qy���f7�9���
qŶO.�Ҋ>���6�h�<.P�^��6���>�HT���+X�>�rX�
�fSZ:L��������}�h[���1K��6C�/g�ZW��'L\��W�m���@�f�-(Eq�!ZA�C�W}�k,;�eb;T����m�~61j-6�c���au��C�V�H��X3�?���پi�ͱ�4p#��a��՚�)�Gxꗩq��D��=�hA7��oj~����?�c������#���$���/�-^Y�j��_��\�}B޻����-w�aj�g�Y�-��&��%�u��W���b��-�O�b�/|AV-y����/�3�m��St��ˍ�P�9_�W��ˏ!�uvPF�:\W�Ԩ����.�=��A5_�F��Εm�5����\v�ۋ^����_@wZn��N�U�9�c���֎�k����̌�*+I�X|g�X
2�F��V�Iū����7��j�B��D$�pW&]HW_W W�N;›�
i��V��D��]�6q��ky{I3VTߟ/
�?�	c�(����EM����v~��1�q�3���s��z��徝)AH�Lm��L�M_ߛ�_pU:�V��<}�V�x�1����'ѓ�h��H�_BJ����=7ao����3I�o�9z��ĕ��sS�3�*�2k�k
�nj��#do��+'8�������)��.z�X�0m�T�t�O\(;'y��=��8���.�UC�
���ǡ���@l�tfH�H%t����Y0�w���rt|Ը�!��^)��1����m�8*�lg����㦮�GL�!^1x|��Uv��I�z~o�JI(�m�Y8�|���~�]���Y�@�5M���}����.��
��j�����j�h�~�&q���(O�*`��s�glg�jg�F����ñ��-`Ɒ��i�KRj
	�Z��9$�Q�M~��i����>���#T��H��	�A��DDp��o!I�c~<��a����#`8BE�BƂHU��a����K(}q� (���<���,�a�œ[G^"ؕ+��S�N�o��*�_��^|��M��Ѕ�GQ�[a��9O���Kp� j��ڔ��k���<����_�t���J�X�įG��ҏ˭�1V[��*��ݖ�y�9="u�Mf��T�Pڋtn�����\>�i��kO���V�=*{+#�-��7�ݹz�2zj#�t]���c�1�1�vuZwJQ7�L���M&��Lo$���bʆb��k3�r�n�Ѧl8Y�NB�HΏr�U�"�Ώ��#����h�Qi�Kk\��#���>��� 
�YB�q�,4��a/q����Y�q͠�.��}���}FN�ƇKN��n�2�o~����_�^�h1�(�O˾|q�,��M�]��TUL��h�%�܁#K����ЊX�+/��m�H��3����KGRe�fۦ�X^Ԫ�p�HXQ����u>r,ɹ]��]����˞�N���������
C�:P�9�{$�_��ɛLùL�#��{Q��y=go�Տd)\�
F�N�
�ߪ~=�5�j�Ia��pP�ڽ�v��ܛ6�X��8��?��tm�΄���4h���2dQ��D���D
���*�Jٕ���^G���p��ǁ!�����]��.2�-N��o~E8�[TS�M�a�@~W�W�t�ȼ��?��Di���ȼ&�r��cS|nC����RP��Q2�F!f$��:B���5�l ы�Jq#� ۭ	�%��0���!�V�;��_J[I�;pI>�:��޼�ֹ?�:B�y�@}����}��v��R���86x�NٽF]�}��B˔U�K*|^�}�`%ʆ��w� ���;�9�86����Y��Q;45��0oN���d�:vɏ�@���=LL2����n���s��]�졥yCT4�e���ͺ	���$!Nj�>tn��ޒ���2�i_׃-"���G�˲�޴�'�7���r��e��8��{�{:ݾ3��Drt�VTz��h�{��~8�� cT�X��1<�xQ
uԑJ�d���jh%
��8Hx8�K�X$T���7Ft�寻�.�%�3d!f�FI58}K����w�һ��}-�.�bO�$
�b_��_UΛt�ys�~zcZ���1�K4�EJ�H
�#hl	�`�:Vb���<���,�:RT��"����kPPWv՗[��Bˤ)a���R�u��@;[*�H�r$8s��y�x�{zl��:�}��j�Ch
6�;>�R]�Cd�$є�]�n���s�X�`}��]=���f=2*U$Ҕ�f$�4�#r��6�bX�s��fF�ˆ��R��'8m��$������uP�dЍ�[ֆiԃ~�� �V%4<0:B�M���ҭ�HcVA��>�&�L5���pcq���03n�@��p�K�Do;dz���	� ���J��o;������=�ܿ#W+��W��P�z�.�x
jh�F�9[���0�EW�,���Z
-��o�R��ݖh#&bC�d�mKRg�f�g�F�>�E��1��r|�������>)����uZ��K�����ɪ`'�uw/ݾM��4�j�bY4xj'>�<�C~������+�$1bʖ��
�P��2o�D/�3Wo}�����e'qA�?CAt�ȭ=��o�
�u��|����Vб��"��5s[D�ۙ/�H�;��$�.S"�~��)H1S��
Yx��r�ؾ�V`��9�����,�j�89O�É�Š�a����:���`��=�)��k�sD��Փ'up�!��؛xƭ���1��
ok�)�:����@f�oX�����+B�H�8�Y�vr?�_��}�̺�̽M��w}L�I۶m�{��B����I�)L�m��q�mך��meP(=*�\��T3�W4�1�����$��#}��>��F{r����P�-�>�s
N쭨KM�F�c)�ےU��a����lE�rom}\�
������5_;V���/ag������}�f�Su��ǿgm?��q��6P�W<?�~'|��A�f ~�.$g���!I+3XcXďr�,��q���D=r�̘�Lڴ
,;�ī~\`e�֑�f"%���Ԩ꯯�3��灡|*ydE���Vh��R�ߺ��ϓ�
k�zz�ŭ��!14���N�L��5wz���#�a�4�+�O��ʩZ�Ҷ�ٝϏ��)���8�����1�[=�2؊��A}��j����zyk	�Ľ2��S�D*��R|@�2�Zه�hC^�ٞ+Q���S""�D��3k8c&P������Nʀw���K��.j�,Pȝ�62N¥ޘ�6�bϒ��R�+�f��i@cрO/�A���)�7.�����3C�Y�\��.�Q
�5W?�L�ʝy3���JYsun�[ǜX�:����%�����Uvn^uU,�<unqWzNb}��0�6$�ڮzڼdM��Si�w�w?1���	BBhG��n����6
ɷ	n�Dr
W�g�_��E�3��R۬��R�^���ٽo�f�R�
\NXۯ-y�1��5j8�c�
�����"�n���}F1/���͊]ٕ���I����<�6�����
Eg�W���@j��
�N�^T��)���n�c¾f���˘����>�p�_�|Q��
8
{3���D�1�xX�}\E�|	}�o�/��u����#>@�y�_b먜FD��J1.Ռ��0�{"0�
�	�7���A6�lG_�]x��a��a�5�,���܍����͙z��ɂ�qЊg�)�A�z���q��_��!y6�z��1� ޸���c!T(�ŀ>�Ҹx�G�Ķ�^��t*4@\���m�USR
��*(1b�ܾMMPr�V���i3{���(�^f�G��N�2���2x����������O����]Kho9U���'\SL,@�"�
H���1
�Bz�nj�%���b�)�7fdhh�M�dt��"�-�+��Y�Lp(H�J(u�[�8�o�E�vx�Q�0DD>�$d�O�ATT�j�Vt!�i�OL,�0�kl*l�\<��6~/�Q:1�	�	)
����pd�dLAt"�c(
�l���	S
�Bha?��1Txi���0'DZ8xx^���ce��}rH�a�[��0�/ꦆ�Ei�ݷ�VVp�`E��\�]TX]VGh�6�R�F]bD:���?N��#N9+��vw��Ng���گ����ti�}��(�Sj[��S��
�<�ƭ�`�S����B<F�bY�'��-{�f�K�dy���B�֊��2ٹ��~��WE���V-�4�,q���ZR�F��(�w�1�Pzq�N����D��GX^pDm�d�����w�NqL�YP��2�5>��RoR�ijn���r�^�Y.=kj�ǹ�5줋�xN��,su��:�fீ���n�r���q�����j�"����y��ΐ���K�>�
�µB4="�ې��}��d��"L����~�����T~,�//�.�,q2��p0����7�_�յ�p��=#�)�2��1r�w��P@Z�JPFK6installation/template/flat/fonts/Ionicon/ionicons.woffS@	�t�c��\�4�m۶}m۶m۶m۶m۶m��~�:OR�d:�SY�2�79QQ@@H~��W��6QQe��t$�]b��H�W9%z��L��Œ�A_��`���D��x��{�6@��F���4�q �D@a3'����C��f��P����Dd�&��a�OC�؀��J�#���q�	�=H������5��������:�����Cr;媍�������,�5�1���_�?H!������3����D��d��ʦ������>�!�
Ǜ�oV̶4��A�
��2� AZ 
,��������C��C�w�PpN�_Q��=�>�#������������߃�����C���p%}L�L������񧙃���Ciw����F��(�Š���>(������ˆ`�P�x0�-�H(�LX�~t�/¯@g�ϔ
 �e��?�ZF�����[�+A�r�buN�S��&c��t�$N�v�n,�dK�ƳE����%]�(�,Չ�᪏��>ڢO�J��d݉g=�b���-��!c�{��L9r�+���D��=cڞ���W]HmtNn��K:�,�Ө��k���x��h���d-�'8���I�w7�.�%��j��<�n+y���!����i�v��g��xU���dc9��C/x��=m�<.qO>����:�0�;LŸ
��yL�N�i:d=���˨s��B�nGhv��E�����_�<�<�	�I��f֝�7c��Jds/٣�w�	��������:L~�c��{ӇҰ,sE�w���_G�����{�Ӳ�b���M��L��պ��`��;�N�^)�ƳFe��5��D�vY������%����u}�����&1�ޓ�VK�m�ܛ��&�;&P�s�����mu���Ozއ��`zQ
�^|�7�ԛ����w�mV�;ӆ�֜ٛ�g,���[|�܎�Y��W��m�{n��4�&`���M�@ۍ+˓����|���f���1�"�j0�������`�`�@nj�40������I���V���v�,�n>�����xu>���v��yy>�����yޟߘ��r��L�������Ħ�Z�w�~�Y^�x�s���|;�V�0��X����x�|O�.������������z�y�y�x�y�x}xuxiyayYyIyEx9x-x!yxyy�xew��%A`l`�caf�� w����,���׌��Y���oW0H(���a	��]skg_���^�q�ŁM���AH@A�A�A9A5A3A���4�����.�a�����!H !b!Z!!� C!;!�!��ԡ��f�������;�aH`la�aMa�aW��������%��'L*�)��/�������3��'�WQ�P�PQ*Q�QaPP#�p��Ѫ�&�.ё���U�s��1@0(01�1
1�1)05��2�񰵱�q�qZq�qeq{� ���\����	�J	�		�	}����I�%IpHZIqHIo���%��s)p(*))(m)s)�)'���򩮩�����ǩ�i`h4iRi�i�hUi�ii�ikii/��H���<�f��ohR�}�?��j�&�NY�Y�Y�YVY1X�Y��ؘؒٚ�����7ɮ��Q�qʉ���Y�y����Ε�5�M����̃Ó��ˇ×�ͯ�. ,�,/�+#�)�-�.L'�,�*�):(�+�(�,q.�#�$i(/9(y+!�".�(5/M&--.]+�.$'�)#)�(3*�-'�+�.'+7/O%�++_�`�H�8�D�d���4�̠��|���R�
�j�꫺���ƫ�6�Τޫ���q���Y���M�ݭ}�C��S����˦�����ۮ;���{���G�Ǡ'�穗�w�/���_��k@b`nPi0jpi�d�ihj�o8i�lDg$k�m�otl`L`,ilnn�i�m|k�b�i�m�mrj
`�izh�g�dfm�i�lb�gnm�l�m>k�`!jak�iqoIc�j�h�l�j�m�kEb%hei�hUi�n�b-m�l]n}n�ici�h�nsj�c+j�oko�h�kgGf'iWk`l_l��`���(������T�4�l��b��r���
��J�*������:�V��>�~�q�9�������c�����k���{��'��W���������P����8���T4t���|"��
�:��N>����Y9E�U5u���m������s��������4�����"�R�j���֘�n���>������њ�ɞi����E�Şe��
���#�Ә�K�����ۛ��ǐ矷��O���]Q Y�]YP[0Zp\Q�[hXX]�\�YDU$[dZ�Sl]\]�[�^UYr\
V�QFS�Y!Y�V�]%^�S�Z�Y�XVCS�X3]�_s[TKW+[�^�Y�^V'Y�]�^w_oZ�ڀ�`ސ��ܰ���Ԩ�x��٤�d݌�lߜ�<����r�*�j��Z���َ�>�~��A�����	שع�E��ߵ�M֭�}��3��ػ�G�gܗ�W�wޏ��߯۟���?0 =�?�4�<4=3�2\?�;0"9R;*;z8�2�;�:v9�5.8>?�0!:�8i=�?�=�=4K7+;;;�4�>�;O0?���������dc1�o�]$����'S���)��Β���T���\)��U\b#v�u Lh%3Qul� #%�Q0�q&�{�~�>�o#�!w�豤O���I�`���i��Vu[���D�8c?@��G�3�PN_|2 ��Xy��}�xB�_(r�QϏe��6e�=�Ι�=��cz~g9盿�p����a�p>�5N`�x�ڬ$�%ʵk��e�7��!�6)�����"mkW&,DS�B��%�������=ӏt�W����U6g<C�j�궬^=�W�}B��4�H�N�m{6l�f(�f<�VT�X�E��]_<��|\l^����;�bYp����`������z�Z����Lz�4�]�>20}1�Y��q/���'L���G?fff~X��@�_,oT�4,�� %��@�F��Ă������B��E�:P�a�E�v/�+D��t |����C�O�C��$g}�8D8ʊuL~�B���2ն�IOl܂L�!�.�Q<��:]3�U��:5�@}X(�4y��Q��rn��
Y��S׊L�H��(�S{w.w댵Mʴ��A_D��]�OU%�7;cV�$���u(�knb�E��d�
�L���SZ	k�J�hʉ��t��<���BGf	�����J�[��3j�_@��Ǻ��A��D��*=��һ�hJ%ϣ$R��"��E������`l����-w��ͷu�f
��&��X�za��sA��I؁��[;sݨ&�dw���C|/y�|���qr���aru��gP�ؘ���I���n�Z�BϕAu+N�
'��RM���5$
BLiN6��BE�L�`��19kWέ.�یē(�X�0)�p&�G\��G)9=��;�&e�5�O��)(ŦL��2U4�auVfV�;FffL�F�,�Q�&̢L�L�v�b��Mf�v�I2%(k4J�,�F��(�Z��#�k#z��m��D�z<l�PIyGԁZ��;y�����nsm&g���q�v�v@�d׮�ζ��Pp�C~	>](���̑�c�V�Y
��lE6�n7�t��.���ݗ�0��~,�WS�,��DUQY�Z�����hK�i[V���t]Vt�Z)�͹?�i���A�ܪ��l���d]037��LE}�͉DS4��{9tg����/�J����`	�˗N)�@2�D��6N��y#�M�#�x�x��CZ,z"�bLI X���㰥wK�%Ҥȧ�Ɋe��¸S�OI��!L�$��t�Xp�~�N&��!�9� Z9 T +=`I�Gƍ��a��9�����֓W�z$�l:��������C|��?,s�
�5���M���r���%���@n�����u̖�P&j��'ٵ�`�7Gg�Lj��v���r�s��$n��K9��ӽ������Y���Ã5=���E?�䊼� ���ߎ����e�7-�w�vXj��Od�j�WP�G#�X�X�Lܳh�DJ1�7XȒ���_zp�i�Ƕ�}�,�K��u�уl>��'y��<��rH�E޹�\7�J~n3��9J݃�vB֎�
GfW��n�Ɵ(b�����t�*%�~
4H�bҏ��f����[�Rm�_���Ѻ de��L��µ�痰����c��`37�$������MW �p�2�l>�L����'7��̈dI�}���,�y_��O�}i��'�3{�.���U'4m�'Q۵l�X��72+���E���Z�\f>{�5�e��J
��#
F�ޖ@q��I~�	��nB�
�];��I�+uhnZ�V�g��iSI�/���:�yc��D����"�S�_>[LE\`��p�N�/2v.��8!�OQPB���X��]�Y�g�J�m�48A���0�ڮD`A���YB�h,M<{���f
�smS֘����E��	�u����%M�-�+Nx`�d�	DWs��{<�#���L�5�|����`�؎i~���%zq�렃$Q�35 �!���ꚃ1��YF	1�^H*T���~�����F� �V�<�=Z�V�B//�أy �Q?E�.7��Od�0S���:EJ0�+�)<�>�Γ���O��@PAjd󦯠�탴鯛�bq�Q��,��h4�p�zl\�?��?�<��'r��Y
�>}���(��@@�	�P����2"�	H�n=��2���xŮ�44�3G8�DS�|����{�/gd�MA�n����i<ߛ��x	\Kk�s����G"y^+���?�CZ�}!�D�Wl���TŸq����-S�T\�@q1\L���y���ĝ�bF���Y�o#���F�4�1�Y�y�tHs��2L���&0�9�&�	y�Ȥ=�:���4H�Y-|oռQ�5-�>JX��M����w�)���L�%�"K�U����Q���i�0�}�}����ԉ���?��̹���j(66L�����hX�+3�Q��
�h�>7��T~Q~C M�{+,yl^|q���!�7���>�>&����FY~Z��Y��._�1���!{���v	�YE�P3J"�ڋ��
�$�g�%z���c�kk��A�(t��94>�ND\�����p�\�Q&�r�ģ9�pb~=��`Q�b=D��4�9*����|H%?~@�'�J��+����n�1<��I���X��[P�D��\��_�&�Y�by�i��ɞzy2:��`6ҩD��{���9Ϯ�.�M�R�|�`�I�<ٜ'NL�g���Na��o�/>��_Eh�/���RBA~�s�z�Q�$2�ڹ̲|���_S�c�z�=Z����fd�&��\��FS�Ye��)MS���-���,qys�.GN�[���迸y�m�#{�P�G:Y`0�aܒ��zK�%0�a����ܸM.�N��&��rؓ>��
k�f���oT�n���������|sOycN_u�	$��+,*͕�O�6���t9��<�gm��i�hu?I�܈g@�����剦
I�Y�g����f<�#�/�T�TT"!O�z��po.x��K�2x����s��
k�H�����`�k0��}��ۨ��V���<���~�ؿ�P�<��<��:{m��QN�	c_py�r��Y���U�\^D�ͯ���$��0K#�؉�]���t���h��Nʽ�#�=^��Bhi;�&Z����	��@ڊ�iĩ~-��Ŕ
K �ȯ�������L�Q��a�SZ��e��%��ٶ�>C�$�ĸ�p�� f
��@[K[�sŔ7�d�a��R�7��1�z4�zK����m���Nfο")tW���sax�?����9�EGh�����e���p���\�Kg 	�>ށʀG�H�چrC��M�������=0�hZlJiIg&I:!o�-M͍
��$]8!�<��i�6�X���Y���]�EJ�����y%��^|��9��2p�w��}�xW��3
��]̯�O3�>�8��"���fM�=ڎ�%zP}\�a�Sli��HQH'#�-��c3�b���>	<�p�f["�YgQ���T�~�qc�R�z�=��{�j�i���X �����z3�Z���F��].�9j3�|�&���_l��M;�ɂ"��j!�����2�3k1���'m�Y<�Ԝ�ٰ�Ch� j�л��E��5�DV���Z)	GSڂUƇ;W����+�#�:������}#�Y�q�
OZ�#V\1�	I�U�8t�Oq���\�#i�a�B��}�"+ɰ��ƾ�z9�F��X�曁�L�SZNd9C�0���|���Q�Y����b�4	J	
B�����HS��F�`�S*�L�8і��>֤R[����rs�s�r��Y�TQ�zz.�䀱�H3�Q�����.��Nn��Ы��v�Iq������Y�uU��Dz�p!F��y����ܦ̨@��Y�`����"�+�+��.ˏP���[z��f��I���^
>SfbPv�������1�2S��l��
�
�J����Ed1E�	o�[b�Y$���sqO�z�+� �ʓ��PS������%�E* �>��Wr��X��5.�̆܇��L(��H��76�%H��XЪ��fO�Bʮ1@d.$�翟���^D�@G��;4�H��!2*8���ܶ����a�WS|+��4L��m8I �='I�!��u�“R9�X�y�B��M��3��Y��)UR�پ��'�t��K�
QCi��lV�(z���9s|��#&@�₠��!1͑�^K�H*ņ�9	&B�$��ֿ��-�j�m���T�����bJe�?
ᄐy�<�î�q�v�Нp��#���Osh�!&�Ȭ�����z��fw_4�`N��ϩ�1�\�k�ҰP�F��0]`�T�Q� }4��:f�Av��,�u<q�u�Џ�x��-\C��Nes�z uXUS
�H4
)�Z<�z�`������U�/["x��z���z��ƾ����n��CĈk�@�
��m\�atԴ̶ejZҿ���;u2�bcP��9���KE���H��&����6��o�. �#����L�'�I�Ij,��X��௪�B����M�t��o���g���vj����@މ��wѤf�Ua�.���/����%$z���Ls�-��S�Y���ƯJ'_�I�W��_D$͘�L�O��5���+��䌊'�S��dwU���C}�x����G���� ?����\mp��M�nW��̝ �(�@�ߛ?i�PZ�r�����az�4�X>t5d�0���g����ن�-���wٜ3|o3g�Х���Ju�w ׌vl2u=�̆3\=�����#R�,l��
c�1�J�[ӊ������8�9���ҝ�v�QY�,��}`}��G��
����
��a�~��;h<��;��1R�
�>
Hߝ��������w=��? T�sOm7UI����,�;#X>m��X���O 1^̣U|�
?�?�.�;@�0?�E(���;�;t�<�Kv�#Ӓݼ����d�,B�P5��oc}�5�i{U_
�>�E�-�V�P�X��k�sЪ"���UN�6��*��ht�sn��ř��3����hЪ�]�����&BΛ�dt6�3�'k�y՞���J{��۶�\�H���\���*˹ݲ��9���X�z�r��9�tw@!�&�c��f�7�9��������g��r��K-�#�R#{ͬv���h�F�%�v0�\�	Cﵐ�w@�ٍ�
tcm�겱�I_��P��
�~;*K�00�,����r��r8�ͤ�`�l교�mV������73�S�G���z7�����L7�Z���S,*���t2Ŭ"��,�$�=��<�4�q"|hV�i�k+�c��5=��Wa�Fm����ϣ�SC����fv�B��l�s��i��J���z�2�|��~t�X�<#L��,
}����Œ��XSǺ|o)��п���ru��8��oYA_Ė!� ���4�/A�d�x}8��E:"h�a�bH��9UteLM���EA�q�>��w8�W�v�'�_ߺ�2<a�\����yޛq�9���Y��zu���64Q0>X�)바+��%9��ч���eg�*U��
!�Q5R}5��� ��j�(��. ��&_T�E���J�K��;�NK�c��Ҍr{���|��lI��]��!�[�]����5�8&��wc}��=Nܽi6h�`��|�Hb�.8����N{L}�x�[Йri\�}�2@~<s��j�BsDX	��6E�� [Ų{|�0��3�f����-�"^�7��j�^�1� 4�!*s�7�D:�(1�8�`:��p�_@�L��p�����p0�*
�����Ҋ�RK�ey5�O�2(|n
�gj*��p5�;�����A-�b"��A\$&����|K$"u�Ah����+�q�`�SF��P"#�%q��j���X�i+�ep��� _,!�r%�Z�lt��<Rłdim.e��[�;@�U���F�+)'�n۫��E枍����҉�����	C�1�Y/��7���8z��2�H�ȥ%�ɢ�(�mz�[��O���	"AX>��ɚ�x7�Ǡ��e���L����_��4�U���d��5�adoxt��D��2�9l�A�!b�gTv��'�fD3%��D��
�~�Ti?�1��P����i�Y��bR��=�ϲ��o�~ҏF�~I�&#�{����ݱ��r�R���>vA������z����Ĝ��r��W�DK}
=�l��/p^Xe�B9��%���s�˜�?\,[F�س��3�Dyx��ޅ��C3�x�M�V��,�8�[+̶�/
�B����_��?�絿�R�D\Йi��g�����W���Ys_S��(�;���~�i0m��c�c^�?}���s�-���~�O��:��Q�L:�F6a:����a#G�PU�	�S_ѹ7�S~�Vo�2v�|@|�
�e�^ޑ6Gѧ[��][j_�m��z���M�&Dl�Y����g`o��a����.�p?XM�L�Ȁq�4jE�裉�V��CJlZpoԨ�+�›g�s�(s��=�7K�3G��8e=Z�+?ѯ*�m�X���*(ރ	�� @
}�j�+uP�؇o���g�G]2/�����e��>qy��?�nK
�$ж$�/��*\�Eu�b��B�Ƙ�+&�3�8m*������W��{�7��:"B�]�u�@`�X`ơ��t �~%>1���B�V��+�"cl�-ۈ��"��>��!�(��蜢��X��
k>iJA�Oߊr[-mT��4�$��7�<;4Op�����i��f��_!��ƍ����v��ip0�l�U<���V	P�R����!�as=�P�C� Ha���=+����4!��T&�N�����w�KNT�i�Z�����L
w^9n*�l��Ҕ��4Z��˫]��N��7A��q�r�wEM7��)`�8�,K�#�V	�`Z�B��sS��C��?��*&��9� Ҳ�w�z�:{�}W7���҄�*'�p��P�AB��E�%4ħ��X�x���sS���t���FU��"�y�/��~�H7j �'��`�_q�ݼ�݋�@3W�,��S�͎�͍m�r��8ӱ9��G]��p���7�rF[��$$<�C�;��R���yj���cP��~L]o�\C�R�{�_�f�1��_���R̸]"�Ax���r�h���//�up��U��D\\��۝��%���L��*�9�0dז�=�+S�5v�Dt����nn
�>�M�a�<f�~<�\c7v��=�4s��5�@�������g2 �V�9Z��WZ�����o���:����k����<"�R�F�C��{�ye���#�.$��Jw�^����H,7;i�I�'0�qʰ1�i��W�����{Hf����8��#Gaޛ�����a�٪J���
��L��= */M��E���G̸D�	���x���B���rP��p�\��k�ϭ��9i�q�^!	@
"��錉Ymiq�UrS���~��'��Ð��J��+-�ꉈ��Ey_j�n�~l�
�:�~>D"\�#�hȲ�e�x��N���WҸWtTyC�^�8�
y�O���Pc�+;��~��f<�!lp�G�K�d�п��y���{�h�����ce�MA7�p��=IFF��93�tK�4�I,�B��IB�Y�X����unY�����4=7�1�H��#��\�W�|v��U5��Ǚr�2Bc�� Q��?qV֍Zty����t����z��R���\F0���>�̩�'�� c��̯`|����tP;�y桠�F:u8�@G�GW� �R�=�cŻ�>L�8�����,4_���Z��УZ������C��W�1_a5>~�s��l?�fʼl��+ҏ֮r��o���iͽVN��v0�`���o�/��֚>�f�=Ȩ���U�=/�־j�k+cIPK�����/
�4�$���~�}�D.�j:ٚUf��^Ps
�:!����\F�ժ��bp�v-_C�W�����C�d�Ɣ~�9��ĝ	��q#�p���o�r�q�Q�;�'����
JĮ�"��R�Ap7ٔ��N�����G&�:�Q+*�H~eK4�W���
���ΧW5�O�ƂP��8Q�3v	%�N����L��'��t��1t����T�ʣ&_��^��c#�<`�3�
6�ȷ}�x�6?l�H����d��y�P�ٳ.
P�1J���NJ�m��<�Z�T��<q��z�;S�(#܋^����X�1S@�TD�%3��"��ܪ�<��~�_���b��f�m� #:���t�P�i�E��*�M\s�*�2Ř�|z�)���#��pm� FJ<F����8[�sb��SiDž5���^-WϏ�^��7���Ic��)ʣH%8��<��a����#�J��W�6ø��*f���6������H}�]��B�jl"��A���%�X��*�����/sp|�D�s�1�'T�{Eȋ�~���=��1��]�����.�r�'|�{�Ϳ&]��xh&/�����0x.�w��X�1�'*?I��f`��;�|��F9�7ʱ���ԉ�A��=�7;��배��X;%�:ζǡ�OD��?����!5��ה��Җaz��0�F�"���M|���#8��@�
Q�8�O@1�&�a��-���LA�%�Š�.�C�o�L�?��nŌ�K]L����J��x�>����9B�y��8/�V��0���:nV%Z���&39;�O2��d6\uHI�~��"�=�L??q��_��b����X0��W���������=�t뽭��1���Y�Oe߰�VU�u�)�J`�n���b����'�b�%Ip��dP�@Y��7w�=��Yl�ףG��$��u���0�M|ԲTH�%��4]�I�W�������Ua�j/��C��\R�չm���fZ[�o=�'*W��B~��X���I!�xp,u�r�4uSPeB��l1)�Ί�X�A�tRE`���-��X��Ѡe�q�z��W�a�H�[.�sr�3�'uBls���~:�+��-r(�ɥEy���u����f��c��$;f�ߜ�F��xPo�d��'�dK�=7��M����Y��P�T�8�@���
�p�$����C*—C�6��2�PX]��_$m�f���p��Lry�v]����~m�������z�P��{�����"4��,��|y��{ur��@�����ol�f4ߊM!���2�g�Rv�!���Ԧ�{�O3y-=����z_����b���J�y�ʄ>U�	&��H��"SI����[f���0�+���Է�&��eddh��xm3Qh����V��g�zfe�TLT���F�T�]6X��^���/�6�~��v1a��.����Rޢ�=���D1!�=,WUT���$�b������:�w�.r�NQ�
A(�h���6������pJ�+o�J&��=�P�'H�ғ0�\����0�U@OfL��'7y��9�&ы֊F5a�eY,�O�)���(������4�?�Tk&��?��4�c�yzs#c���PV��c���s �Q�5j@�C"����t����qf:>�����$�)5fG>e4�8%�J] d9��kk��嵷��EV�?*l�
�;��m8������94�Hڼ��+��	w��8�R��DJ@!����u���.-������k�����~��y/\�����<�J�CI�s���z���7�;�#4�t�b���岬��j�^c�)���9�j�j��N{�����wq2���-A���@H纨��<W	��1�5�T���ɠYV�E�(9�zў�l�I�AQ��|�ڵî8�}���L�z<6�!��-u�+�
]~����*��ۂ��!*?6/�����Xm��"��ńL�P.�<+{�N����̳(Õi�WJ�{��p��V���H:YYysiڏ�ˢ�-�X�̓H!{�Pl=G8�'!�K�0c]�3v
}f~N�>-Ծԛ,�M��5���
8�OT*��*"{	ш[-?໭�K��}�pp�]
S��yO����|\�OS�Y������bny�:b�m���}��3��I�k�V߻��n�U ~`��F%��g�s�2쥲E���ݚ�hMj�y;��ɦ?�]�c�\g���x���1c��e�-�2��M��/o�I��c��c����)�X�u���Χ'aO�ĝ�����u�]�	 �2\,��W��h��I��u���Vq��i���b��>͠��|?;�lѾ��R��e�G�6AX�u$o�nk�����^�mf�B~�jf�"�s-�ޣ]O����tʳ�m�*�P.(\~"vM�藬��cX/@_�	L����{נ�;]���3c���`����sg�T�Ƒ͹�泲L�(���]"٭�F��)�A��^�� Ed���h	p��8��^[@�=��= .m���7$���Z[{� �t���:ab_�A��^����IW�`�K�X����"H��b��,�!���E�Rՙ�JjR��� ��[��i�������3�H/�u>C��V0\(C^��f(�ۏ�]�@�<Ή@���d!3�#�"c�ܥ�<F��v�74�=��hρ��n8����A�h�P�Siٻo��l�X2���P���rf��;�v�"�^�--hP$�C��#b�v��$3��k��)����"D��2T��v�����A�����>�V�\���E~�.D���$�=%R��NOG�'�mG(i��,d������M�pF��_��\m�yӞ�O�6�QR�vT�,��Y�O�K��C��"7��W��H�b8)^P}���]^��X�vIUlؓ���A�=Ceb����$���'���������8�[g���~4'��&��'$��i�9Q�W�Re�Ǖ����u;i7ϊ���K��(��6���RǼ+�)��ؔp���s�9��j�|!o� !rhe)v�ެfo�,5K���>V'0:oa^��J�¦��Ncjm�})Tk�ejO�!��w���s�잗��	��O8��e�
E|_�x�x�(iFX2�
@��V$&ufa��pryD(si��"��~�����-Y�y�M�D�X�iP	��vc�+��?��E��=�
��<�JY&+���'K�����aX�4J�Hi�Em
�GB��%�)P���"j�D3HsЩ	���n_�qt4����|i�0?�{�����������������9B?������d��Vf���
N'�;T��?KIo+� �C�Y��d3�r_�䡭,����ǎb����x��YP�������>�>'H�PpK��<���..�U���������o�WD�����..�Hʱ��
��o�b*)z��y$ ��q�
뷄�ouf�6�^+���̹U�65�hv1�����d���Kz�p�0�1��I�2�x1"��0T�3�x8���Ch^o(�
M�g���_�*��q�kC�y�e��d�#b��K/BU�!$���,b:��z��3e�M�ʃ����Km�f'��D� G�rv
�7xA�S���;�ʓ:@vг6���a�'j��P�P!�9�%�^�3��G]eR�3Ƀ��<po�c�l|�e��u5�r�����Q����][�T�݁�Ȱ���(��nc�u��K�Y����1q��J����u��yXZY��VLw��Xgʫ�J�p�GD�e ���c�8���Vw�����W/Q)*���Vb�+�D��|FD�;IE&1�J�T*u$�����͒B�r�2EaR��DƐ�rS���2j�[�Z�����V(���d� �"�{���FD� �,��l��,�i큈��\u&\����t�8�a��L�H/AT9�+<��>-
�us?��[h�&�O���v����8c!��H-`��?�?�� W��TWu)��&�<17��L�[�x���-��y�����r��-c�j ��@[���p:ߧ�W�C��<���w�=�/����4����r�'Zy������u����B��Yl��.m̟��`y�#����S� �L�#�!ͬ�^���i%�@����B/�b��a昫�;��+'P)���۔qt30�7���e�M��hi�ܹ��5�/�w�rl�� SD`���b�@�������gkC�a�%(�]���t�%E��U�ɂ��)# ��i�=�Y�eG*bJ���
�/�᳇6�|���5�\t�I�_�����]�!�*��Z��w�e)p`�ܨ���0x�M/��F.]�t��Š��K-�A+�C�S~jʞ_�&J�K����d	���!"z�DM�R����cx�Y�O�s���WRH��2W��(�99�l��c?��Fi�HA�p{"�m�+�]%U����Kz�M��1����^8^��E�ߛ&�7�䦽��k��A�[�	m~��A���".t\�K�6��V=w���YS�K?}��э���Qx�d��^50zlz�Qx�&�9$�M;u���R�j&>M�۲�E��5W
��W��s������|�e���=mU�DqJ+��>�)f��{v��%Q���xꆜG�驶��_�ɕB�����ͣ��QY��r0ht��Ny�g%����nΚ�k%��!޶v˄���==�̑�R�E	o��|��u���U�it��1	Aspd��]�)�j�{���~��*�;9��}˯��)�XZ����^-��*�X����9���zP�w��=ܑ���
�Lۻ�\��+U0�lO��t�rM��c�_�1�⥠k9Sل�[b�r����kh���=�j]�tmy:{�lϏ���)�[=|��ğl��w���y��������6��4R���o3�D
��80�����UH���)X�*�ҫ%��a���o"8l����L��FG+�e;���9��}2�
��F��i�u�&��S�G�$��9�
%$>&�U�}���p�U{]�O�o��*G'���?g[9�B��脢�k��3�5��z7�(��Bc6+�bH��
0�p�HM&��2����Z�[�E�?`�/�:�':��:u�H�N�?c�_\����#"p���DJtװ��|�*�.�]M�,�ok�y$�ӲSq]ݗ>Yi�nZ�~�]n]7�A���$�vd_�U��U<�9���U�m����;+�&M���*���c��"j���_�.p���,�z�W8��f0����HR	���ϝ��WN1��{X�Ԓ�؇
��U,���Y�S��s��r�,C��"�����*C=o&f�N$��_�~�hWk�|�|�3�����wP��sU�7�Y���:�;���r�;S���d���s��u�
�Q�����W~���B�'��Q�1�QB��p5J�S��'��|!�(.ɚ9[Ͻ]�R`��wE0bkpNʶ�4�m��\&�ӎ�Y���mٹ,�GF��w}�"5�L/��YL.��j�6��mf�<��`k�0j�p�o���@�?
�
��}��o��oOˈm�A7@n�)�v����&��T`�7�o7֎��C�����U������hC�<�ǔ�ݧ���F����a5=��e�Ǟ�>��@�fF_��F�K��V9�S;�(�ü	V�hyVV���>�J��ɽwu��H������\L����+��l��V�.�rc榨U�
(��˾���c	�k��_Ǧ�ʞ�
��o��|��[�#����[�U�����=��|1����L�]�`�5oѴ�F��V3��֟�:�P�`0Џ��rK\���
��{�
C3}Ӊ�G�q(�g����|&�: 	I0�b�*BE�k�w皺ݳ#�'�?�?1����!Yoc;��ڊpqJM��^���ԓ��Ůٺ��4pn��
�c�8p
��ޟsE��k�f�_)쮼nq��-�!��8O4c'ڸN�]`t#|�	�Q���,�S$�0[��M�.�u-��ɖ�ƣS�l<pޔ	^���y��o4�&��֢��\:n|7W�¦K���m?�T�zȹ�ctc]ݻ�_��4��—����p���!�{W;38�
h�����5)Ml���g�f_V��K�X�Ϲ��|���?��L�s����	]`@���U��*��d�G�ՂXs^���̬	B
=�����%g����W�uKg����Y�s�[�y���`Y������k���;�Ku�e�9KI��?�8�5�]{�6�-�S���Ӏ&�?�Rթ|Sɓ���b�?���p<��v�m�7�7�m|{=�w��0[[��}��>�2���jS�ܚ�r���F�ME1\@�MKm,Y���~�$���+x�I����y�A��E��V���ݍ�Dd�F5�{�R�k+F_"��N�5'\�s/��b���>
���D����_)�
8�
'��=�µ�dV�����$�ta *֌���D�m����(��g����J�E�I��O��k�SE_Z���#-Ӛ���[~Yo��c�d�jM�q���y�t��A̷�K�b�K�*�c���>P��;+��(A*�@'RI�XT��Gf�BAQ#c�4��5
�f��m��X��u��h�Z�er��i�o=��y8(��}�:Y�y��G�N
��J|J1�7�P�	�c6��~�#r�Y����x�u��E�%�v܇2��l�?�D=�`�^M��Ӂuh�O^�/X���`�X�mh4-��:Po�,��VŠ�#N�c�����.t�m0~
��J��lb����\�~�s���k#����i�������6s�u����X��l�6���1�"�3��G�o�ATʽ�&�#�]_�Y�T�
�T@du�^�˫'=8�ޞ�Ss�I*�;�{K�ʋ���x/
�\"Z��Pz�b�A*E�|��D����v�Q@��J�/2^8-���H B��PD
Vc�a�4�Be������#iە�rdH������$�<YCT!���*|e�ァ��N2r̸H��A�ҋ%*��g]���{}�@��{4�|{��{18⢌�yÿ����{3y���v5>G��k�d���e���E��z����?z"蘧��#����9�PfvP*ܫH�  �pۏ,zpiwꨮ�^�ߦ��F1��>��g��J��JLn}��QO�/�4X�No�������q��V�6X�7�O�D��A��`|<�"7��Nۿљ�n�f����u`�L�l}��k8��f�D#�\��]a�ģ5���w�/��*o:`Ի�p��_a��t�=3`����������"���;�~�Լ�j֏s:���s��Fx�����}4V���\0|(�ݬ���CG`��m��h��TO��CP�V~}�*}�
.���/��\)uý��F�QPҶa�����ɭ�MHr���O�ai�'��H�5�˟�lz�8_���࿮
b/a��˭lJ-~���\��-���0IP��MR�3o\W4��ډ�/솎p���{�H: 5���&7�Hc؉�:���~��@��,p(B���)�.$=؊K�*;4`�/J�0���l�_=EZF8П@&;���ӂ��.&W��L�P1��,`�!�!��#���l�O�D
D��S�1:�3�A�ݼ��;�tR�f›��(��ۜ�A9�t�C��**��{8Kꨯ[���$l���
Jn��8�`)�����辐�H�_L�y<�J<��!�	��+"���<�I
��	�2�
��T�S<�E��U6���t����1)A�?����+�ɿ�\����E9�7�y��r�J�aW-?&n�����.���_��+�;�laA���>��B���W��-�?7�ۡ�����N �O{�X�ݖ=��טk�Q��n#b�Зԧs]��6�8Ybk�\��A�s�VbT�ڮ��
�i�E�ݞ�e��:��(��+�x��#�L<�\*�e��)�T�a��"�>h(QZ��{�"*�V"���M����q~���lA��``<��z����5҅�z�
�Bx�`囂/ы�/t6��\ak�$"I�"�#�	aN۫��W.�=]	K����Di�gQNZ�`
Q[�|�rUKVA�f��y���t���$��D绚��[=	�ve��R��q�dtE9�ά;�C���R��[��Jz�Y
-ȃ��B���J���	|mmm���C�\���O�050o�rX:����?\�>?w�4h%�EZĸ
��x0�-`�Z�#ح]^���9�Z�o�q��KxwNU�ǰ��B��S4��T[�LHbm��#��C�r�X-4�x�f�'�I�n;%�)���؍�Q�{��^�/��6�����;�?T��T/\S�u�{��s�PE���j_��x7��L���и��ᙫ�ټ9烶j8zG?�a��k ��D���\rñLr�1@�ш�NPG�i��tZ96�N������8�b ��RLF�$�풩��I��c���אݷ�Z��4DܤM����
XjoZ�UR_Κ��^�.j��̚N��
��;BfP�����yB��}�;O���ng+��f��FK1�4T����b�޹�f�����YU��~;5U�Zn�ۿY�zEq��d�c+�g�wY�of���O�<8���WL�,Aa�[7�a(=�U�fo���H7�:��
"Ի�^�7#h~ʇZ�M�e(gƻ���������%�������H5օ��`s� ��Cn�o*�8��_p^�b̛�.^��"Sϗc�zIMQ]qvG[�n�	r�2N���{#b��}�_�7fCc�U2.#�.�-�K�t���d��B%��=P��z���\]����6�<�����u�첯����0w��Ӧ�:A����z��S��PK�}��ܷ�z���3-B+��q����n���,X5���$��
3�ox`:��	l[�#�����&h4�e�<"}�k�xi$I�n[�r��n(�
�c�K�ٓ�l��菙�w�j��n㼱?ې�1Xܙ��%�͍o��@Q��ecF@�p�6$;�DR�7�G�2:Za\jq@#�I��s�!�iHqnѮ[=T�p �O��oa\aVJQ̎��ÎhC���ʛK�W3�[)�W��L �(?1�[V�u#	�<YY����D^Y�I�&((�m��ҙF�<��bt�v���l�<}�״�����Cy+zw��l7���4
�`Z��k��D;	�6����ݳ����cC��y�U��VU���Ts���ی�5�a�v���R	�
�v'�g(9�%�ϋ����Ya.���ޞ��a<bܯ��,~���f��x"P�vLԹ#���6f���N��úC��2r�G$��	s�x��H��,Fk�׎��Q�F�k䅈���Ȯ�_��u������u]�A�B�W�<�f��rQ؄�G��ь�C�ap��
�y�Z���hn)u�����?�����E���Ae�w������`��ha�e��B0�N�w���1�^H�=`�{نڮ�LB�g���
%�O�ќbJs&˔�!l�}
kSw��������^wCo�cp�e���?a��n�z眳�ɼk��΅��E�f~�=�h@���׵o�.k<�ajl���]r�ۓ!<bw�WFaj�g-]r��B��j�
�8�^U�����em��|x�`}�&�s����Ӆ��832=��=:�@#���[L�4�ӵ��w�n�?�{�qs�6���^�&&�>�m��S9�&#z�`�l�\z�j�$rg�.vcn�����i�sb�^��Z���]*�U�<|bn�u�̠o�lkʺ���5JԜ�鳵vw�]^��j@FCg��Q3o
��	�e��A3#�b��s8}�e�z������
Oޘ�a�ӊۙ���!T,#_Y�~/��l�#�eQ�ft���$%+uV�)3F�� .�����"�h����~���u�3�O!�$!(������ߗ|1���0�xfm�ROb3�r,�w����^\��_�4 �n��]���Ϊ,��)r�i��2E��QY�K�jU3k#�|3��ftc~�P�{�|w�LhrzKC�� �6C}��v^�"��z���k0}�	���;�c�Al����K�d�R垦6�H�uVA�a�?k��2��p�q�� �P
�)����Ȱ�.���rj̹H�4�M�� f�+���÷�"_SٍX����l�ӑ�[Q�Q���q�J5�-�ՠ�H�y
��f�1�vE�B�f�'��' �x�4� yBB�H�!�����B��~b"!1�|�9ۭ�����M����x��X�;�ױ��K��ذ�v��M�0�3�+rL,�o�B�j��2ο�b�fn��Cb~�� �hf��V\<틚j����}'fԻ�G
e���.L�z�m�����4C
>�l���f��!��S��?B�c:?��8�"#
�B��MȔ��V!����^�ׯ!)�܁�O�H/-�(�m�um�9�C)����$�N)�щҿ�,R� �y��8"AI�]| ��Sg>��P�b!h�J�$�O⃰�Tpgu=D:!ćo!��A� ����_��#�I���j��.����,��E���GpZ�U�`���'��ͦ	��{�I�z�*�T<s���白����'�#4$�bE��n��J�GQ��P�J[����`]r�+
����>T
yB"B�	����c	�Bw�	U�%�R��*-�#�@�6֩@h3�~�9R�Ht��~���sY�ɇ��Pn3�tM��:���zEG���J�+��N{�k@v=7}Td�ˬ��l�=�-i2G�8q�~
�0���1۬���$>�b�A㲪F�H���K�%�	Y̙���fH�B�l�e��J�ѓ�;������q3��qAh$���>��@^~���B��ͫ�5mC�Hn��{�c*�:�oA{��,�f�9"��=�Mp9b^(���Q��Z��?C�Q��Sb���+�R�Se�ؖ9�YW�E̬�3R���Z<�%�U�,����SD�|����)(`Vv���`�s"g���J��T�p(c�u�6}t^*S�!�x���3;SY�D?S4�ddi!�w��@؉�{21B��qGT�7�y�C�Tvi%%j*����W	���3hxkq�+�G�`-P(�\f^������8�m{���L�	kp
Fk�q�s���XZ�l{Rњ8�R��V��s��hȖN_�K|�Ao�KMbZ�2c���28@l�aB�i�PX�Rr�e����9�$�"��e=���'s*���X�ЯTu
��K;Af��ofr�-O?�.�c�����w,�:��*P�
X��o?�f�i@��	F��!$�Чo[K[��l�j
m�y���G�x����}��A^�ωB���C�:kU�7�}���3�j��nC�A��v�B�פ�ݜ�&�癮��}o�+WRno%ʳ�T�+u]�F&�&w��m�\�C�����P���=��U~:����1�6��l�:*YK�?��EIʍ�H	L�߮laN����֍��u�Q�.]�@��`�
���uߔxPA��O��}M�M��Y��y[r-�N/�K6���7��o�c��U�/	�3G�Na<fU6�$�$5��l�G�|�����𧦑��.G��2����R���F.फ़��R���W����kL��	��D�3H9�RIL
B^Q3nj�����@��we�^�	S�I�A���^�~5 �t���!�
�ˮ0_�q�$E�
�&���D�:⃸v�w,{<��O��ìE�l�1�����(�G��r���u���e���:�&�L8�˱�]I��G�m"��8�E��,�g�PzC���Z�N����*���=����n�V��kG�)��ͭ�"F����*lltz�֓u��4�"Z	1�����n�{Q�g����)M�QF�!����
�y�(k�#��H:�.�8#/*��u���$�{\�)O?�~8��F�:܃���+흇=��=8N]ʠ���܏=g���Ξ��b�d4Ro9G��a��g3�@wb�>��\��S�o���$�h\(��;�9'c�I�X�w9c]mӓ;5�g�.\qxW����'mT0&�e���Ȟ��_hL{����"Hɾ0R�,�GG����~W4�HdW`ے�A�4b�f>Il"+m)R�X����)8�wue��IϹ��LB}� ��\����v`@��D7M�\E���[ױ���"2A�@�k? .�1R��Y���k&5z�4"TP˖l�&�Rߎ�8q�V{>#J$��f�r
S��IRI����"!�I�+���S�%c"��O����(j˻�Zv����A�#*G/��5e�ZO�=�K����'�4AI��W��[�&I���Nqb4��6m�EH_�i5�V��B�����!��T鬷e^Έ#ܪ�B$�q]��"V3��w���1Z�ޛ*�Ŀl�~`��c]Z	n�w��CU;��%m쭈������Jmp�bڭՑ��r�a(,K0�Y�a�Kͤ�AY���X���
O!� �	e�ea%���x�
�[����t�H��)�=��
���%IJ�#.3i{\��������Y���6�Y4>������N�֖�W�����ơ;N����Q!{[����Z?��,X������ :P��5���T��k��`�-ۯn"3��g�n�h��(�I��V_L����i��M���}�˨@�����Q�*��V����a�]_�ċU��w�A��'�{O�{�aR(��VUt��)�Z��y�S��G�P
Yg�9�[/�6���ǎ��J
N��i�eH���������!@}�#�\��"��r��S+$�g1	Bc�
;���<��|���wڍ�}�x �{��6�jb>2\�
�؊�jD�kú�X/�����eN�ƈ��{�{�*o��9=�J�:@dcDm�}���b(�7��� �$3~L5�Q�L}�Q�m3�V���y4Ti���s
f�B{��Ӵ�z8�"8��7$���u���f
��,�8_��v�ʰD
w�
�]�h�t�8����4d�,�T��l[Hq���:�:�8�8yt?8�#$$U�8���r�?��
�:q�@�)j@z�s+��y�
Q;�fk��5V�.B��\zr����$*�g��m��ؚ��ʳ���W��� �g޼q�)�
���p���D���5ƌմ���K�ix�j�k��J{��L�i�����t�8.���t��H��������?��ґ@���h�{��	 ��i�DP��ᆪ����&4#ǝk����s����,E��u��R�3QHOڑ-��Fj#����H��@M붠;6�SE���˘2_�=$GV5!�5-���g�p(^��J�����V��%QX�#����/~�BG)75�
�����{1DZ?����w��(՝&��dƈ�)%���4�M�@f
!p٭N�㠸�$U���B�qtYW��M��!}Y����=�Z�V@�]
�ȆA��1��c����!����$��6������\…P���a�>�gZZL���~賷����wk���N������"r�~̴E��D�o1�)�����(�P	Z�'�T�� H��	���Ifȵ���t�
Ufꕍ��������U�_�i"���W-s��N��@0���05��&�L���A�Aj��1���)J��nIf ���4p�j�h��n��k00�MB@Y�1�߁��q  	q��[�/8�tAf���-����b^��1K�;p9�s
XPh������ǘ��gB3.�r����p��ۜPE
2���(J�؇�!���3�B��EF[4��wYTSX���:3{��۱�0|�`��a��A.��r]J�=TPl���`�T�;����)��/��I_E;����9 8O��p�(�k�DIa`jg�,����*s,�]�J��ArDוr��-i8�k�o
�H�g�H
��>0���"�p�մv���!K0�����9����y����<����1������Y���ܲ!p�dM��>ٝ�j\
��\�E��ax�~>g�S����3GGR�B������3T7�/���1r?U:��wZ9�M)
��ӖRT�?F���ÜF�
�3��"�C
/����QAd�V�|�S@UCv�������0ʤ����R�I:�+Ro��â*�`x�&:��KS��3�Ljv�8�'P���&0����p��C�Ba��G�"$I�b>�+`���M�󧢰�w	���a,$)�@�bI�fM���Q~�ʥbF�<pG�C�	�s�zQpT1�:ˊ���uNG2��@�+Ò���k�n	�� ��#��A¿ h�R�[�HDʊPMV(�L�un��~YGU��n�p�1�o�Ӏ鍠34P�L�W�"nB�oLLD	����&5I()�����z(4
���c�Qǭ�5�k�	j1Gʂ8C=1��
c�K�A�{σ����0LjvJ|�Зs"�=t�f�EZ�å�<��+��E�2��C��"��)�����Y��"��S
���`��HnPkE�E=�����w(�S	�`�	�
2�w)�iQ�FS�޼򝕦&��|/
���|�	�R����蔔^��s�0?E
�ȩ���(?DAl~K�����8����m��u|���#FB[�j�P郼;�|��W5lZ�T^�Em	�m@WU͡��?�C�
�-���fޑ	��!3l�~Au���|��-5�:=Z�j,������z������.��p�x��J���Tu=�t��(|��ⵙA]�������xN��.ź��"�b���zT9	�i2N/�®\|ApG��AS�!'6�ͭ��\-:=��A_;-�b�YV��B�)LG��%�b'D����{'�I�'�	����EBa�H�ľN=��U���<t��������ya����ti;H�t��u$C�>n�9��iƀ�1�X�1G��
���B��R�s�<�Z�\r��;�0t}�������Y=q�ГK��y{�ޭ��<��I"C�^_=�J�9y���}�><�g|��uՉ&��v����d��fL��\�nc����uP�Ѿ������4��b|6,��06��������1����0�-�S��S>BQ�UF�gS��G���":�Ks�'��1
�F�������2O��W��.�=�\��h�h� FKYmA�h�'�� [}Z�u�s}��>���m��_9jo4���o@c���'E~�,U���v�X�r��Ǽ<���7�`z����W\1ܺ��|Z���Vj���ŚP-$�WĚS��G�\%Fާ�Z����6�y�8dxޱ�8u�)������o
��>�G���c�ux\u����|%��ykUdv>��;�D��\~���jG����&փ�$T30�Ẇ��rtl�����g�zT�!��MX-�1w����ʫ�����^��{s������x����x�3SR��#6]��] ��v�%��f�ik#^X��V�K��v�{�l��U���c�ҍ��&��L|�MN}�L���Ơwbw	��'"��\�<DP�)h�?�p[�;C�����-7��������+�mS���gM��L��~�a��č�ts�x&)���h')�n�6�?��6��,��q9VVQ-���]��\�N>v�tg�164w���mj���%�rx��߉�'vX�񐅐yK.�t/���2*JI)*I.
�J�պ�ZBg��*��!Ը����6W=;]=zD��4XIal�s4JW��Tꎛ��e�B��;jq\]�z�����>̎b�+6$�m[���9B�F�nQ5/�٠@�w�T�Y�g#0�ڌwS����#ҵ�/����)T�ok���g�)����o���l�C���f+��@�x
XZ-}x@*�3�`�*��4'7"��W]�Lj".�y�}{�Á^�����������&n�S����FA�D��a+a	b�:���j�S��v6�c�~��j��az�K�F/�!� ���L�fũ�|�F��Z ��v�SFe�pGSW�ˮ������*�3׹i�Ͻ�Eo::�bTc�l*������(lI2���J;�ۑ����ց�bd���3��,W��	Ma�#B�#�$�$(�=�O&�r^�g�f��y��8$��r�,�d�J�+Qv�^�T���7@��M�۩cn���R9�p
z3ӷ����?x�c
FUܹa�2��A�Hbz=��@s�<\5�Xݲ�6ٱ���P��Z�H*��!ѝ���q�Y{V��6�E�\I٬�pB��~�DK�U#}=�������F3cd�X{,C���MFR6ȣ;S�HX�.Uf{ATl����:Ó�3|�n�H3DSV
H�i�8Ac�iz��T̰���O�S���m�7=ed�9�2�y夰n��Lٷ9�����T*'��I>���3 ���옞���Ði���WVC_"|v��q�Q�.]N��A�~T����~R�P3vF����$�IVH^K`�8�n��5�_�-�*�L�r���t@J����lXS�I��,Yr���?��;OC5WE�(�˘$���0���S�)5�M��m@�����^!�2S���x`�.��}J�����|�A7�	
�m"�~O��4gHtI09xR`��h��{�XqP.o�C[��{�b[��l�Va�U�|��w�Ȗ�Z���[{��m��79�;.�s���Ø�j�d��l<�U�3b�(�k���J*�q��i#Y6>��ڔ7��6�V7��)���8T:m
o�����6�c9B6Ԭ؛H�.BW���,v��z:��R0�a�\:�"���T��",�K|a�g�D�5!�y�5cD0n��|�a��e����IR�2̔%�/��n�7�+���z�p�.-�� !2R��3��d�ԏ�R��q���@�Ur'�G*;�I6���4�l(!(�G@��6]x��#n'U��@���9:�P�)�C�#���t8�H8��4q��)8*�S)�Mʯ��Ft�1�
��5�v��=�����e��`k��:~rU�� l�3C���/��� �6�=Z�Q���-�6�bTxͧ��Bu�LRe���y_-<��WdZu��V�g�{�psE9�G$�$��#��#"�@���φ=:4�����<G��t�r(��Z�$�U�TP˩�j3��1�@�h�b�O�JO���H��+��"�n$��`F"��g�XtÐ˪��K�ѭ!jy���T�q����C��rb����(1N��r:�}Ba�9�k&���ʴ�y��0h��@2L:���?�i��4��a��>�N?Fb���3Y�U7hpw���z�,�K�ڟe5C�$3�WY����X#��>��G��Z��P�ae����0s�+��������\������XW��f֓�Po�0�p��eV��d��������81w&�μ���Xf�War�D�&�{�nЪ�-R��8k���q��qO�瘏�<&�A����f"�����!��ۿ,T�͜!;��C����3I�1u�� ?�XHN=O�c��
,R��s�?���dzIa�F_��ˈ��;� Pqk:�/���2����u��OeO���'z#�"�f�mb�/UΔS�������"3�Q���;���KI�49�:�ސT����bɗ0���b�Yv�Ȫ�7�6�3K��Z�,^�*P��ӂ��hpPN��Q���[��������<�G��%7Pv�,�qGz"λKyA�����l�ꅢ�̪����ո�vM�Z+(�{ZV�KHmKp%�˥�
�E��VmX�8��P�,h�Bc�$��,��g1�+��d��|$h
�>R��{, #Fn�W[O*�c�x�ؕt�	��Pj���ŢA?�x�<��-�46�l
+ӥ%dP�X�](��6Ȅ���1\�D��^"B�#ʙfQ,��
w�������97�}��Q@�w��G��H�T�@W%��P�ӥ0�k�tN���x��J�NI�Fcӱ�M��W�5�A�.g/!���
OSWܰI��UAL2��m�~ߠ�,�H�WV�38�����q�Ē�S���$�nnV64�����	M��܁�孪�� �X��x�
,�J`��x3$F?��aR�WR�qj��'8w�E��|'�r��\��+G��9�4�SB�Xz[�z���ܻ�K�}
H�:ʰr���ww�n|�:�X�zi%���p�9��g�����Αf�.��?0���#�9<8K��'�sq?u���}Z[�0��+�ɶ��fx��yP}�Uv�4ȮQW�,�VM��o%�=�|hT����f�v�eۆeph�()�����zKk9[�D�W� E8�0P����9jk�1���Ѱt-�z���c]|���*����R(��$ƚf�wm����)񖑞,���{U�즺���C�d�#�	cj-�X<ʚ;�(��Ǜ[0�h
�Lz&�`T�S��**�&S�<���>L	����-�h�%Q��6��,"�1e��!�>:g�mj��,���}���I��(O*U�N�3�<;	/��fw:��}:`��+	Yck�gc��on];�9h��E�}����78�cl�a'�������m�0����zA�p_H�<�+�G�=U�e�Ҳ.>���sY_�@�/mh�x��ϥҽ�Л��B������F�fh|���!ǀT�Ub��WAz�4��&a�<�gyܻq{����Dx˯�x,�@���{���7 ;�?�5�y���k�sI�ή����Lj�����Ch�eɎ]}3�����{�(��F�J�(��ZͶ�}CZ�¢�H�����)Y:U�<�yٲ����8���sL���3�*�b6�=j�Ǥ��,յU��CPB�1Oh�p�@��N�������q�GR�!l�]9|%<�zގ��Q��<m��2�c��rl1�]G�[]�lj���o4�˃L9F�����3�y�N��q��xy��ڏ�}R
D��hc�SZ����h�.�Uc�3�]�wR�}�Z�Gf��GZ�H��ja�_l�TT��/�
g&2��kH�3�9f��g2V����h�A�M}
�����2Ý��ue'���In����q_6<��oH9t=��u�_�/�*�a�e�6�sbG�Y��Zc'�<�@CF{���ك��鿶�Vܶ=
�9s�S���t]�a4�]��$}\&ROV���1(OY|%z�$��	-C�
dk���"�;��7�7��`?L���Q�XX"b�"
��^I���[�q"�?�H+_�V(�N�M�v�p&KONe��-��\{
~.95�P��̀�Y�!�ݰV!��+~�3��J�t���R�?-��Zz�
+~4mgT�U	JP�
�D� ����p
d�٤���{���+"�kY�R�qUW�E��MZ����R���R_�V)���G�Fo��px(�>U��DK�a/Q�bAr��}��G�Y�F0V�<�_��`��$�rF&�$���U�Blh�i9�&2{�O�Sy���{�W(*F���;�`V�ѧG��K�G��|7@�GwF���;���N��ݕ\Iv����ݙ3�x���ggt�W����=6��ҍ��Q�^���`�o}��g��p�*�A��cI֚=���%Z�rm9Zَ��5��꽺"4x�D~�˂��Ҏ�!��0��W�u�����?Z�h�2��2�x��g�hnE��~���k+[��
�<W��@wriQ�ͤ��*��3�:�;'�f��Q�A���_�X��.-t�Hchc�J��i���8�z���!�|P�K��B0_-��n�Qo}�?��*����叭h�kk�ݒ�wl�M�ǣ�[���3�<7d5��.�2	(l{
�`7@q��u���Se�0ʏ�j�{n���6�HQ�R�e�59��,�)zm��濁X���ۮ�j��jq�i��E�͕f���M}(��]��{k�=������*��B.�����#�`�d21Re�<�p���<W6�j�~a,��!��M�P�Kt�)Y{�tL3bB�DU$�pC����ܮ|¾⊜Z�#�����z�K��BB;o�q4�.�vS]��䜔+Tk��F�"ٕ𺭑{�:�a�lS�IUTu�宊P���>����|
	nC�>�e�@[��h��+p�<�X�2m|K�V�k�-�
ёE��XW^�)s頢j�|y���$5����4W��T��*�b,����������'S��i�.�{"�{�l����%�\8�O\����'�xƷ6'|lC,t�����,���pӃ�x� �[�(f
0�y�rlVJ1����y�挲�_�R��y!!;�Jяt�2�K��t�x=�kgK8���w�H��͹�B~�E�����{��C�s
2;�syI74�x]t�o�z�9�k�}���4}�%ڡ��z���$����9wA/�⟤�
N�����G�A�MՊdV��1^��t���XY��)!�fi-�����q)yg��T�ފ�l���:���SE��f<�s�=��͏f�(�������HS�5
�q��l�\���;	u_Fu��]�S��φ�S?b���tsøޛ�ǔᗄ�p��<I(���ez^ׂ9�_�Á�Aą�i^�ђ�4v�����d,�����A��	�I�����.�<��7�;��*��*ٸ��޶��I�]R\R�y��i<N̒b��V��w����i�4�Nf��O��!v���(5.8�
4y9��s�]�����׾^����4p��^
�ʼnpC�A�/�%N�)��3E���_;3�6ZS��lq�^T!G->���v�χ��T}
=�^\�	~Z^��;�_/�m���W��ҕ��]�������ۙ�]K����xL�I=�\�������>�y2�4]$�_���,M"�H�[ᖄ��!A���NW+\=�����x���e�����[�8Qj�o�yy��W��4O�M�Zv�Q_N��F�ma��8�5|���^�G$z�!-�0V�{ҠH�'F�����i��/��<n�>����@�ӟ�-����Y ���{ađ��Ma�J�pC���E����[7B����ߗ������Up��H7ǰDV8+b9�<�<�4�|���Kۉ!4��T�i���z-p�e��SR� �/�T����ߑQ�+���e�lʵ�ç����]�a����j��h�b�+q�|����8������#��=X�j����RgD\WMe�ʮ�
�,pW��&��oηX�Q�)�g�Z=;UU�m��Zmf�ZmL��O���k�o�7�x۲jgˏK���٩v;eߦ��%>>991A���ǯ�?/�c)�O,+=�*P����vMD�GV��E�V���9c%F��b�X&��A�5�B�P0�=T
]�⡂������]����ճ⩣O窹��Ts����ֳe���o�H|W}WZq���Q��t�wz�\X�����#�;����[2�*�!gi���E���0a����6�<ܛa�;М��r�6,��4/�&���1���U!~�na�~%]����W���x�5V�Uyz�%w����|���DkǮ�G�������ȡa|=��%�,m1kV�Qɞ�Q�Z�V&'��me����O��$�����6�B��ۍ���#���T����mP'�}��V�N�U� �H��ԙ�V�E��В�]2/��Q5�~����$�?2A��Ȗ8��/r˄&	��`��qO�Z��%=5�n^�AJNQ���v�3�]b�?H�.ڗ��.t����ߧ��2���)�/:��x��rY��K���}{�(������Am|�c��&��m�뽌O�K��h������W�o��ض5���p����bP��L�׳��D8�V��B<q孷>v�z����
�M� ��[��"�w�7�CcV�	�<צa�蛟��Se��h��=\�X����G��N�����?Lz��7�ϡ��‘=IB��n1�J3ȠΕ2�"��VR�t���T��(����6���}��&���o˽|*������tn[��˖���}�K��%��d�7Sݭk��Y�,7��Lo�2���5|��xI��Kɯ��4z��Q~�A����O����n������+Ɏ���֤�$ݾad���q�Ԣ�������&%,��6��+���;|׆tf�-�A?癦g��#�|�b�z�ԟ�x�G��۞9�̨�c?i��:�}����}�r����Ǐ����֏������(Q��XF6]����e�p���2`�d-s4W-�e�p�	I5�؀A(�X�r@
���������9�`K�|"e0�&	�*���P���&>6��Nw��"0��k��B���H\��s�G}�-��a��p,�M`h �ϭ����?�15� ��r�4#'aO��������[�o}}���rgTx O��?�����oܛ��|8fg�?N��y
n|໅��<Px�w�} �Mf4����p��d3�����O?��_�:9�����کS�4�b��'V��E�p�@-z��t���]�{��ЪIt���]x����Sd��)\.3�*TJrQP*W����EpH�n�݂Im�Q���N��g�(�
ۉt�&&�3]2�X����r�3{���B���%So�&2�Df�Ԩgn�d��/�e���tc�`;;�45��L�9Qn��b�<�,��$i[��F��7��٭G-��*3z
�b���T1��@��̎��؟�u�$�F�n��_����&�����ɕ��S��Ov�14�	J�1A{&Q*�\l���&�Mn���1K0�j��Ϫ�7��DyX#�7*р�*0�.���'���2)�|]h����!��co�Z��Ef�*��q��j�5_��:��{��?���iUk~ β��c�m`��`��F�c��(`Ae�
3�D:�N�8~�9���,k���;h6�Xfb~��e��&`X�#<Q��gȊb�	���xD$>�SQ�!0
Ϡ�P0ǵ1�h�q^'��K�h��3��3D�edSS8��t!��YKt��ON�g��/W��������Ƴ��Q�
�Y�1���"�:�,��^�D,�9tYX_�X[R\u����
�~b��DlG�����_w>y狏^}�//���y�iο���o���'����3�
�0��l���h>b���8�Pa�?�rU?N9N�0�^sxA���ҏ��z�;c��[+G�!�j�r���6n[|����V1G�zl4�x@�,b�V�	�F�À�`x��,�>zE�����h�\���(���5+��m��Va�i������N�Q�}��o��g��M��q�QI��&
+���8C����;�O5�۽{��	C1.TZ�J�.oω=�.$�Ĺ��1"�c鳭p@{�뿑������R�m<}T����v%����^�`�5��F_����ޝ���uL/���'Q�����t�?�5$��@��t�����ӛ��gb�ݓC�����_�9��U*A
���6�]�j�<[��Z��Y��A��ݽ{w)qK1K#��򩂮e�ߜx�[y�"o�5~6�6O�n�늉[\��	��B�Q������u�%�-I�;?S�d2P��y/��{ќ�gc;�4��	"ѯU��^xl�/�[z��D3i�=ȩ�|���0��h����1pï[��0����f�gn�ȳO����U�5���H�q���^�Z7ăs�o�'�DiJ���䩒���io��>�(%�a^\{��H���rlA�;��^=��	ߟx��ժ��?��b�;���jr*q��K@d�O�ֺ�_��v�w��R��/����?7n�O�ʳ�k�XI�&����Ws��{n�����C��}��0|Rw�f��߆G�VgX�[�7�-~1�>;$`�`��Ԑz��OoL�q�OO>��LNm����.zD�R�0��p�PD��L��.���1T:X��!����X^��76F΄y,��5��͟��<�L�L(�EaR�0`�3�8*��Nc�Z��%�ƒ@>�4��pG�"�k޴n8�w@��MU�N!��w:>,�[�#=)ڷ�4��I�\-r�ڕ>p�#@_0^a�Z��jO6����6�e�:;J��V!�����m@%��H�5�K.��d3a��Dwlr���G����Wq���DrN��t��frD*q�O#ד�K��6����&����o~���z]��k��"�8{���3b����+��3�����i�50^~M�i"��$ ��������k�W��Ͽ�Q��8C^q׈o�|��ce�oT�х�[���߼�BS�>~������u��Fe�� �p�|�6�"��
i�O��}�V�9�e��_�V"�1ܫd�j�����4�t�^�0q���n�{��w^}x~�}������{g��ۅ���=��n�6�?��=7��
�?8Կ��{>�_�`T�b����ɟ_)>�{��nhv*��䓽�^��2��x��'��}��XL����P�Q^���:���`Z�ο��}b�c~�$?���(g��	�^8�~��]������0l���:���Ā�Z�
�^7��VRI�=��U���]pj,%Hgl8h�V�T5�Q�3[y��Xh�bP�с̒�d��ɻ�-�!���x�}ȕ���D$�α��6#0N��;��^�<���.[lZr�M��k_�=�f4��2�\�¨l�g�C�;��Q��9ò�?�ѳ����ta2�E��5r��z.?����d��V�t9xpu��`����B+�h�e���y�D���Bx�py"����F�cw�l"�>�����7�3����*�WR�ȣ�3J��d�f#�^O���6�s��+@������Y�9�ц����Z.�o�w��/�f��f���k�ehK�1�4
W�'b���+���
��P��#g꟢�|=�OC>F�M铿E��H��o���{��o�O�Zk���H��[��^��:��OE�o9ã3b��q���?�*m
�����P.h�wJ%�_�.���$�s�:�K��$���%��'�U�h�����़�⤄�����X�nu�Z���q����S��M$�$���X�e�'�����6�=��RzѶ���h����n�4�q�S�k.��nw����(��Y�;�;�(O���H��f�-��:%ߘ <�Jd7['�i�Wv&
�o���.**ӊs�{�֕�7��^x�L�WbG�j9�Q�Xx��Z�{�N�f���V��/}��q��	c0�,:�s0*��Zhs�.�a���ګu�[�6�׎�_�ɮ6}�����������V%Ε�h�b� && e�Ӥ	�/~��h�����Fc�|�ga�]�y�b1P����
�x���n��p�g��8t�����W5ps�x�'��ǘ�J��n�������EӍ�M�����,C].Y{~bRT��'���9v6>�;.�� �E=�sM]!���WȄ���<muUwɄ�jV`�s�8��a��1��ƛ����󲲨�'ߕ���Lk}�
�3����B��#��>Tc��И�Sz6Q������.y�×���m�#�m{��x�c۪2�3�A~_n�F��`BX�.�����<�A��0�݇���aV�� ���R�Qt�\$��V�=�g��ay���^�v��E?_�R�l�Nk>�95��1��+��1�����%����$�`Eb��ψV�'�*���7�/-�^�uf)_�y��1!�D^�ؙ]��[�-�"��nlB.1"�������N���RB��|b�PN�(��6/<_h4�1�V���N��m�IHŻ[���-$�D7(w�VPF�Y-XVղ�����g>d�����Fams�y�����ŋ���d���_.�2��a66�Q�^iy��V�0�v8}B]m���x���Cp�I�z��H��rL+��6	�Mtjdu������Y�!q+`�/�$���FGWa9����d�����MY�d&!�՚q�eX�6��E�6������K�Df$NBrV <�-�2� f%A��	�+Ĺ�D`%ˆ��G
O,>!��0�INF�[�;�	T�5�GɃ@sg�h�xTK�}i5�d+�>�(�.\���x���F�<�q���O�<��g���Z�*�L�p*�1̙v�-�eX�i��2�eފ�|��(i;�2��+;����6�Sn��`�-Ŗc7�~�8\0�/�y�U�d'z��xS���#&	�#�Th�CC+@H��d�T]�i�y�����/���5���~�{q�K��	����x�����)2K`�	\3��_�;?�W/TϪy�赬v�x�
��S���Z����	l*#Ȉ1��B<�/���Gp3l�0O��>N�,
m���!��0�Z
��x��r�!:Gff�Y���
��V��P˜b�0,ZW�T��K)�#�R�^2D"'s�����aL�����m`#Q=��7Ao#���ص��4���F�"��D�L�CD�IK��ƃ.pS��*�*d�";��.�bh�[���cZy�С��66� w���Mvԉ���sj'��K3���Qw3r�͘$-�Q�d�s��*�����\17�L��M*�sK��@�-6�3��1ZF���C�,�hm=e|g9Kn��0RuBΜ��uP�XmE��J�؅ZU��V��.���+�V�W ���^s#X���*��v�F*��pr��J6�~�N�H�4��	\Ϳ�
/�j��ݛ[7�Fn�&׻����v��{s,��+�fX�>�	�B��q�ySc�9BPL�0e�����S뒣���1U�����<41�[�=��Oj�4�!��ݗѲ��'�U�<28J��o��hp��o�Ӷć���f]�{���]�"<�pV��?T,�2GC;Ġ��SW]��`(�a�/�>�m�eM�l���s��~�mG���'.��;�'�/�h�o��-9O�b����������5�IA�C��r��1��==).P����4��BSTћT�$u�Z^4,�X,W�v�R�c�+��`�J��x�'율���d]�E�Z%JCb8a�Ŝ%&�T
�;J
w�X�'�ǢY���7I��9i�)�*4IpXFb�hQ/CA�,S����i�l���x�����Ǩ���4�P��Q|g]��a�!���0�(�
yy��8by�fsT?�lk�l<|��h���yɹ�?��ɭ�O�61�.?
���
9�V��b�+ @��p�u7z��������D���E���N�ݍ
�m��?����kP��
��*������zl�5|���SOA	�����Gm�&t��� ���4:�!�@�3/ij��뻝/0�i��`�XG�a�E�a������_�u��rh��G
�I�	R��r�ݶ���X,ͷ��0��,�B�R�H��DKd��z�&��1���
�������}a�-��\����U���E[x�6�@g�{�\{M�}ԛcw9(��@=<}��3&c�p�q�<�kC<��-4��ܫ�y��:#y;/��ˊ�j�c���k���V:�O�K�$�J�#KzJRDA&Y-��)��SEo	N�EM�S�
�Id��i*��8��l�߬��9t� ���aJ"KxXJ�18E�ć���_m/�3�4Y�5a���LAQt��)��>�Hf\�U��"��cDN�5��͸���,�F�
v¶D��I�
����<͌#+�
�aI/��۲|�w$}w_��w?M�!9*G]�uY�����O�}�Λ$��5&�|a�U�aUMŬW���W<X:���	�SlE��AH 2�Y�o[@��5��r�*�/@��&]�0eYRM��ģ�!N���A��.1�$�X��"߯��(2���d�r.9�?-�&��H�D0�	�I�&Ø#qކ[XQ���0���i�f�q͕A���gz~"�&
]A�1gy�5�T�D�>��x����0eY.��c=�)z��P�o�7���X�]��|)4�����j��#�e��G�}�ڰ��.�O��/^����X@R��Fc�B����7�ܖ�����Ű�-��67a��OT�U�O��"�rI��J�}�<H��0�EP�5�[�����e nm(߀?Z�
�V�bu�@p��!��͔1Cu���
�i���
��t�՘�����x�Ւ��ڋPn�x�,oˮb�i���XL'�����Ϯ��c�|�`JO��Vm-n���"�"'��7�n�t_6%�7u#0\�55_��b�Oqd�&1.å���oB	�&O>��
��N�<�"���\}���{�Q=�
Q�4]�-S3
ՑmD�g=��</ZK�%��Y�#_^Ń�x6�0G�i�pi>)����v�k覝�,)�H�,1L��Eٕ���=�@4%eĞj�NƞpAs�	B!k��6�P��F�9��u�6�~6�v(�z]����~�QB�U��on�����[^y��k$O��-B�����j#�	���K�N�N�k�
��l���:�lv���i���+�@�9��F�H�I:��LhN�r��=�$�X��k�~`e�>��_E
��î1uZY���*�
�z
����b;���2cIЫ��U�qy�E'E[vT�ƅ5E�D��G�$�c�6�_�e_T!�Sߒ-[x������-��5��H:��f�s��t�A�]�ҥ�kUQ�M���b�uh��xd�~������Zk	�0���G��o>�hW���;;^�ZR��>+���~�L���1g׵���P��o�v=uwd_9�М�t�]�j���fgH]�3~�g�}����G����,wȜ��q��*�y�|�nR-&�j�u��JiK;m�׎���whd1��C��S(S�*�"j��R�E��y�{�*9�߭z݃���zy_�[��@_��М�7��+�w��%�r^��͈���0�ة��)jQ��{xIKMqp+�sk�)������t���k{׌�����8Y��K)Ff�j�(V��n�[<"�ڏ�0����	AqQ�TԚ'��0;$��Թ�,���:L���Y��y����K&�̙LŖ�W�1w1��
U�aM�u��x�~��`�U��S�ܕ� �:Z�oI:Nl�?�8��[1��{��<�#��m8M��k��=�=���Q��q�cA$]181C��v�&���@hz�`.�xQ��(c5e��$L̇�c �0�B��y�L�x��I����2�׼��
���+*S�F���@X��s��|kX
F��Y^VU�Y�g�R�D��Ih�/�8�령L�꓏+6Q��$��[�8�	g����.�qSD��s�������ܛ�IrT���o����K핵f�V]KWU���5�F�IS�F��QK��%0F 
�`���X�l��6�����m|�3��b�>����|�l3,��[�ʼn�Z�gF����o�+�Ȉ'"#O�9�w�T��t���x��c���<\h���o~,�yd
���<��&�����I�����e-,"bvq��[H��`���uWWuL-+�\H���N"	��9t� V��ɭ��N�3�b��Y)`�tfpS
M�Sx�Ğ(�M5���YE����T���끍2y��g!����O�߭<�}�h��5C�nPnW��[[�i3K4	�+X�Y��u�l��(5��+\����&_��8�_���2h�X��QbG�[l�4�i�T���L���Ҵ�D	��fg���l6�A�+~WC�\
�e����3�6��v��i���G����>�:���g���A�g�v�ee�Y�l�ߓO#��A?,J2o���DԂ�hd��@<ފ�*�&-������}�,�y]z�.	�#GIr��[��%�7N�.�7����?�QSC�P�'QK�uOh<���	��O��aa��e�/��/��yԹx5�
��ۇv��8]G�m���_r��k5���5a��������<��१]��a����(�2����y@*Z����X�
�7Њ�wu�9����x�H�}�Y��}�е�ٳ������C��_C|���K+x9��+�<ΐ�a!gY��Xw�~oW�&ȡU��#�M��1|&4	�,��B`?�g(k��jͭ��Ԍ��ُܮ!
��]L��׾3�kht���77��t����������4��ૄ�Xuʅ)Sc5�����zϝ$a(�%6Ν�}��A�02[bT	�/�\�7�y��g�;8!`ab�Ou���Ժ[�O�?#�DA�\��=T�#��g�Q����d���V�:�f�����<{��=õ�����L��{��`/l�?��n�� �k6��$�m��g����r�]p����E��}F�8�Z��O
�]��*�U���5R�Vg'����[[�=�EԨ"߆��ZmbW;{��$#b��?[[����Zێ��eW3��F�@ˏR6t�?��V�U<��a�”��z]�b�3�u�qϣ�`�ݽPx�_���hر-br@���2�|�|&�P�ɓ�����d�s��s�?䵌�]ϲ��Σ�P�I�2�mf����c��DB�w����%� Z�+�e�c>�7М`Hx����?=�lh��95�s�"��"7��Ѣf�L� /}�g�u�T��-,&K�A��ƁF�=S�x$r1�&�Y0^��?aU��J�ʓ�ܯD�@^��Y�#�����"��ۇrpA|�t��g��؂�ۏ���oV���ļu�N�9fjX���pB�ܹ�"33+�s��T��Y��A*L[�Gn�����{�tEО�3{fR�ٹ<�l��E>5�D�7\���e���վ[�ݢV3�V9��
�k:-}u
���P��H�z�7�͵յM�.mlt:��B�8�N������N��m�7�ݑ�%�S�A�#!�Z��`R�FrC����+��i��33�L[���"�黓Xn
yxN'���h�|��x�#?ԛqi��_�l٠F�#b3�w͎r�F���SK�r������+ �$m#Z܁�\�n�5D�)�~��W/V
���
&�<��8�C��.�o�������D��tL~jz�1,*o��#S�7�
O���}����Cm���I��n���3o5�0o,b>�3�'���%|�m`��&�y�:����|��?\̏����Ti���1\�N��O�񻑇�fs�������V����_FV�g�~O?2�)Q�}���8���ԣG�ȝ8�Ns��(��8T�rO�҃�?��'&3��85x�?�2��lÙ����	r���S�<�;q1�q�@O�~��C@�
�/A�|��!�E��A�e_e�R��G;zG=|�;��Z����T�K�wM��!^ó�8�$'9����R+�ds��j;����öz�S�_ӽvŜZ�$4<��#�̬�PJ�IV����F��!� ^&}����}����1�V�`�JH���;��'��^��)��%ɝ_/\�q�zJ�è��".��WN]�YH��Z!�h�116&�tY
��L-z���dG0�J���
�ڔ@���r�4j���FM��*���/ަv
ڴ�0��/(�+�]�F!_׽�id�!��+TbYܪ��<�Sq�����6ym�P�r�]�5���?I���l��ݽ���U$
O�	EOo-M��K��E�M3�/�|�����@ �(i��8h|�/�b	�9��Ι\�C|�^8�pš+�7h&�oH�##-*+���4��kkD��4/&��䇭l*ZE���b���);�۶PK�<��E�S��f)�⯮�鲒�d�Y��嶗�M�n�V�=^P�(R�Bn:fF+���X$��T��t��I��deկCa$������+�"�k2B�rE��d���j��
�e���1�v"
�������|@�*�2(i���-r���:7G6@��y~y۲u��0��W�>�6�=���b���^E~aT�G�Y��-�ෂ�Y4a�^L+�5�&����1�ن�x�]�4�r�xl���bS����1I5/F��̥-Mt���f��c5E
tZ-
Q�Uش�J���iN�����R	�u��F#\*��+
�k
\J�8����drF�K����E!��'R�u�E�Vu���&V��M��ܛS9�N}F�������Wq9&�	�:Y�R��d׎��PM��p22��BR�̉��ò2�G�<��	���e��Pʳ�4O�YW����WlK�R��{?�<�r�\9;SŤ�;����9@f�K'TԷu�Q��Z ����'|c�����T��;U�(�_k�&M��3W��~���KN��R����oJǼ��)j�Z��-Խ#�*��H޴a��t��Gfu^#��Dɇ?��A�%W��1�%<���a���j���
+:xW��޲�,�d8�9��[\C��!]7X���g��˙
��Q��������h����b%�A� �.W���{��ݲ�W���w�|qjeq��1>J�bKVqC͠jD5BU���yM_�9@%�5�E]�~���cN�@�N���bx������r������`����7�!��"�A�`��.��Q�p�
�Sl��g�bx�<��ψ�u��0�g �;fcO����&l�ͣ����1�ь賂�!(7p�ɜ��9NX���<R
����!�$���h}s~n	�M�(�c�6- ���}D�_�:_S�V�u�Wy��i5UI+���4�~��#���'*�?�T^k�w໧SJ�gS`��>�)���h t&J'�6;>Z2x�<EJ����3�Eo�ߎ.�
^ļ���Q�{�u��S�O�-��'b����d-�����Xc��O栚>�c��|J���9�S�fY�RT^�ݳ�9�}�-������n)G ��
"�q!�6�P@��Ϲ�0ʻ��Y���/n�s��;-��ż���)�V��n�>��X[؂����Y��4�c%��rf�3����b<m�x�13̽�];rY��hQS�t��S�D���D
�C�0U[�$�#�ˎl��6'Ф@�>�y� ��"��6�el��#��a�L�8L�i���e8	׀iTv�&ϗE´$貭�P��L�L�U�RMW�֌�Fk�.�	j�	����TljԯV�U�"��W�lt{�õs̍�=�F���}x��5ԫ
N�H������-17�/EV�#�Hԉ̠H�!�ܮ�q��õ��]�uQo��z8?����no�ڰD⼃ ����C0�0u��{�{�6裮��j�ܳgq���.�O'ȍ��ς]��2�Mp9�9�&0b�J�q����p�
]���ܵ��lZ9t�h9�MA�-����:4��Jp��|<�&B�?3�xV:��g�qxP�|��L0czQ�a�j�*��ï���o;���
�	�����	������Gj�#�W�a�k^������J�F�~~*��r-"��9=1��	��?<1|����SD&���kT�DC��jd��-C�d| �Lyb��l3��p�vi)�R-�z#R�wy��R�V?~|��Z۞^(\�%J��A���%���T�h"����y��b<����Z��OLųg���ٰ�0��A7f4��q�Q}���w�_��7�Y�"���m"�5�'�qh�Q�dN���j	G�rf��w���W'o;��{Q�
��ɳ<Y�6I��(GҬ�4��cS9�d����]���0¯����(f5��=s��Bsgk������ X�w�������ĝ�O�I�?�OP���m@+!���7�:�&șa	�����3g�:Ž.���V�Ez�z�:}�*�P�*�T�F�\�?Ҳ
�L��P��.t��`�����+��{g�Z�;�"u��:��z�T/J!w�Խ4mk�~�|�|}Ĕΐ�7n�4�)#S,4B�o��l��V~t=�v�����ӕB��{&Y�)H�2�Y�IJ���;�<�!�!F
	�~��hF����dIx;�Fz���B�ŋ�P)C�d�� f�n�	�2�L4���	�`��F&��4=���^�*r����ކb9W�W����j�z=����UE��*����z�#W���j�]UY��^��va{��5�3�W�x3S��MQt�ժ$��g2�I�}����bd�d�[��wP��zXɆ@�9(a��]�|���/g$&_����%�h��NbCΠ<Ġ�<Y�l��V���
�QB*̠ԭ��p}�-�N *��0��7���LϾP��'�I��&	��v,Y*��ؗߨOBWgظ��bұe݊|�}B���G�#��~�!MQUA�Ccu?.}�*S�D_�L��`M�S'�o�6T���r�Q��To��EA�^0TiߓOVn]_#��#�|��+�f��lAPUE{�ɪ��!ݏ�_��>�11��Z��^K=A���E��g�ߦ��u�;�_R�H��(�����:�^��B�o5;r��}7O&��i���Pw�^�Ė��� !}G���6�g�>Ļ��Ny�\!/�ao������x���!�w�Dc@Ho��<}�ž�^���mܩ��^CO5	�!�T���+(�L�1A��,�B��.m��5
a� �v�u_(Ch��M��i���Y����K�J�����QD�y���F������"M7�����%2�^�t&1�ltw�AӋ����f�B�f'�� Ql1�sׯϸ�S�Τ,;F{�#�g�����mwf>)B�݃���9�oW"�2{��
Gs|� ���]ؤ#
N 1
�/dz�A4JZp#B#��^e�U51"QN�!r�c�zL��;9C��y�Z����WK<L���#1YE��}:����p*3�
��+�$�զ�*�ՈsbUN��^'�"�_))�\x*EpW��Oh<�I,EٹD���𯑘c�K��k�=��m��[̲�n�������l�ȉ��Wb�zL(��=�!
��FC��f�	Eg��?�3Z����I���1@{[CD­%�a�h������8�����xEds}<��Ǹ">c9�(II�|Q=$�A�-�bUJI��^Ք�u�v*	QfǣJ�T��7$�2�f�ED��L�5^�G�۩WQ����~��87�R�,ިp�!�a8�Ɩ�o@A�L�v�^_x;�����_t��967�;�wn�����*/M�/K���(s��#H��mo��X|�������b�ǧp�%��%=��o���O�c:2�>黡�'�˶���%�&�"!|���#ʃ��AW}<o]�,�CEܛ~���e�kq��I��D�A���	?���&:�d�h��:�X������k8ĶP4.w>��B�"�B�`Mkʚ,84����i�~HN�'�D�A���S�U���zr�ԅCM�h�+���3�]h��<NM��Y�gBkD!zN�)�G��
z3$����<��!R.�ܣ5���-j�ڏߐ��~l�n�nv���7l��0�_�,�i9�`9͎S��ca�9i�X!|X9/0x���Wu��f�y���m���vG���_}�2��u4,�wv^�|�M�LT��i��0���5g��Gf�Ez��s��z��Cs(Y��~Ξ����o޹<�5*�u��swl�9�����sh�Yu��ty~�5T�
��� &�'Y,G^����kzԯ�'/X��+�HP�������J��FwSWR7�%�13���%T��XɦQ=y��1��6�{o������5\��9�%\�8��C�u�Q���l��+���T��B9�Z�{�T_��Y�Da����Jy�[��V�E8�[��N$���}�x�{|yo˯�X�u!�-� �����o���JD�%Ѕ7ا��J�� �<�cp]3"r�������V
RO��erU}�^7��+0AM��u�Q��Y�p�}�$����~qd��z��`�����+���!|h6��p���@t���p�3�����N��H���u�1�忞�x��Fy!>uw�v�љ8Y=⹡����S	�\��EVы	�I*,������"O�rH�m;�[�ض��d�ǤL�l�t�/��&���(11I�-0ҍ{��p��|�I_6�oEd8Y�qe��ei����9�O��J���L�OV��1�)�z�K�{��1�#�-���K���O�h����Q��He�'�aH�E�U�[ׇ�O�F���x�^����6�ʺx��'2 �����
=�x�Ra<5�?�<ޛ�#������}U�������'С+d�/c16MJX�!v��	������95bZ���T�eF��ܸ�,U�E���§qWݯ^�FNz�,&�1'����Q��X
�`�1,t�bHؖIQ��46(��bY���/n�	�~�x��d�иr��j<qc�4�?���������t�S��/�*��?�c�	�h��{J��$��'��W�#�?3�d���M�].$\Hݺ��U���K'�ssݹ�%ۯ�X�]5M,�㱼�K�JU͔ⱓs$	���džt�,/����c�m���A�[@���˸�a��kkkz__v}�4:�N���|Q
�8.�j�B��NA�z��"Eb4��pV�Lv}(_�Ɨ``�nj�D���Q`�EX���J�ad��Z����������;6-������VvG8��Ψ$�Æ_NjZr9n�$E�by�u��E����4�n1�P���/���ԟ�ɘ=��8a�q�NK�۱��r�)EI�ݡm���"�]�d�=���X�@S<���t�0�����aM�59��(��ј���D�Z����r"bV�^NǓ��0n�g��dKa�l��/$��D��jM��L<�u�a6��wi��*{����[�r����r �~��L���(�	��1)��>b�xzَ�#�rH˓�.�D>ʒ�^~��Z�@z4�[{��������j�O���
�s�Sݮ�^�l�v�׼�9���ޯ��=j|u.4�oKl-c"��([��z�Y�ՙa���P�	�C�@���S|l]2b//\BO�3/��َc��Lh���^_�W�u|׿�����T�1�v��mOkS��yW]7����K���0A�g��Lu.�3�1-��dM/�|}���g��
��_Er�X6�y�4�E�4��r&\�쬐�"�E��}��*��|b�k��T�œ�\&���Ly���+'
���V��}:>G ��||sdA�M"��e��D���jǘ|`�`R���0P%�
��p��M�@�چɃ{	���T�u�~�c{۲�Y	� ����D�b
#m>�L��a=�(�.�7��hG{��Y�T1DBSn�KŲW4���F��I��W.��O���*��}��F7w�⫨��3����M�"�8�g��a-��Wy�!��W��R��y)?'P���?�D�q���6��٬�Z�k?6���fKpl���f��,�ڶk�b5����K��*�*��vM>�T�����il'{��������!�w�f!�]�p�E����D	jR]��T�3:	b���k8���yd�u��ƭ�=��p?��񝀻C������d�O����
�o}~t
Wg�˜lP�w�JO��w�'��b�B̕(��o�!�L���%�n���E�+�5�m�/
�:�/Atx�!7�b�~Tt2׿��:���)V�xVg�n$�f#�n�:z��[]]Eѩ鲐m"T�i�驝�]�sb�X���z��������\�lD�@�=lOvT�q��Q-:i�
�׈9�V��] �
h>���~�ۇߘ��O� n�)���
1N���~�"�?�V5�����
�����5p�G�W��s��J�����G����B~!�Mj�I|1?��Hap��v�oK��D�_e6v��*�:/���:���s�p��bQ��^Klbf+�d%�Nj�=�#��qro�����4��eo�f��IH5H{(c���*�0����O�u������bKCL/}�Upl��۸=[^n�"$.6��-m�ij��`��)#��p'�<w��������fs�gϮZL�儘�q��`~��o:Y��c~Z�a�:�a�K\�PV�{p\�Wp�)i
.�òh
�e�~_%����
�p���X:��nG���JQ�N�(����������d�8q]�w�n�(˱�Е4f���|��&o�'���O��9����N��$�><?B��8�K(N�),����ВgT�#��U��Zr�s�QT��u���C���7��ieCi���)M@�vՃ�bm������\Ǡ�կB�	����">
~�2/��3����t`0�x�k��v���~_�}��!v��������������x��G1V�2/P���q���g��^�n�"�`��*k��{�H���ʛ�^��d�C�u���>`;��K��~���@x0���@.j�"J��|+�LRa	_3��{AJ|�џvo�r�[hY'e�ܟ$�O��<F0xd���/��@����}�a�DL���UB1�sx$���U֗��ٕ\��#����W4D��%l�q�s��ࣵ�kO�.�{�^��T>�p{�X1���F�7�UtuCs��
��ED��d����F�u��juo�]����E�5Rnڃ����R�J
6��W�dt�D>
2ɹX�%�������ʀ��y��*Ը�Y��|伹q�B~�9kr.����2�>��Tܰ�Ө������:{�=
F�B$�������g�栆6;\G��"5��7~6z��P��=�>�˹�M����6j��:ڤ?�1t���B�C`e�ɗY���������ҝ;TN���̮�eaj_�[RhZ��\�c�mC���v‡e]���3??O�#���Q�[�P�m3��3���_"�%�!7L���ɟ�p#�E���1I�y�ᇲ��$���|$� ��ğ�_5j=���M�G$�5+��H5�
�
Zt����D�4�	��І�h���h�"Q��$y��#`OX"�(Υ��q-�2'�muPWV��RTa�mce��6��p$�q�(�D��b��?��?èc�:���
e>��#zd�P[Ų�#p���}�7�{���|V7�����ՠ\��X��վGC4@?�Eнp��5b��:<�L[�EVU �хhXTi�0r�1F���!H���ʝz��4���`i�/a<���P�Sf�\�ժ�,��h�A���"��l.*G4�j���8N9�q
!L�$ +6�j/��  V`iD�x���EC@�^)rwG.�5f��o�!].U�b��Q���mEh�`��;��_I��"+�..P1ytEpq���x�S��ͫs�l�۳�1�#��"�P+h�4�t#NO�y�pw���;$��I�^�ҏo8)*�����}ΐ
̖��NH5�}K�*W\N�%�HK�"����I���J�N����0A0��C*7���b��n�Öɏ@B����K!��%��Y���;����=xBh��>�^����<�Mzÿ��'��&��[N`D9D��(��Q;wn���r�I��NfA�A�	�ǫķA��V�!��O�O����u�`��j��_W�5�34��*	�C�o}���?�կ6��$��ܭD�^��y44!�����B=����K��}�O��Ev��*��9�L�7���
�9�;/Տy�ݾO��Ǚ ��#%�$8��IO�9&��KS���,\���.X���sȗ�
�в|�l���o%�F���I�/P�۷�����"A��\�ۈ�b�D��	�+��s�!NG~Ԓ�V�wȃ�;i3r�#d�`˪�$�zܯuܣk�
u
u;��A�S�9���׉�[{�NN�J�&�X�e��<�)�����
tKġ�x�3��l
��+#
yc�'F߰f�J��v�	�0�xTn���:D�?N��n70ՠ���吘
�BD��tX�ê�g��N����?e9"����0/��x�	�̀�Z��,�a�!�7�g�ˆM�0-��C�!FI���H\�x���.���V4c���n�Q�1oC!#�4�H(�0��eB��hq��%�
����Dc�8���Y�T�ͱ����X4���L�(��iF5c�>TIc�ϔ���B�;�hQ/���=��i*ÈH�IX5-�y����Z�����#�bT�A�Pة�KX��S����4��騂P����~�ó� 6�y�������"���֞��௷7X��.�"���-\.M������8����
����vS��>M�!���qW�Sх-�<�ˎ_]'`K�U���:����
|y�o��cN�=�$�~����X�:��1��c�&R`��c�M$@�F&�M$&�3�]��r&ǝr;x�!����F�I;-ߐd�0���C�Â���ٻh�n��-30SټՊ��-){1,�,���
��h�9�×&@#|�_WUH~Yb��cB
KLx�qjN�"�c�e#�h��?�24/��+!]��x'�t�*�D%9����SE�ъH3\�*��Sq�l2�a�!�1�{q�E$r�V� @q,˰
����E*�ƓCL���SL�`�42D\
.�Ē��AB��v1*H��F��E�!d#6lG�&BvT����H�l	�H�e\QWR�w�ł�"(6~58UC��
�bI�Գ4	�Ǫ��`p�8�2x�E�!W������i�t�U�$X%���LSx�=���!��jUu�RC��:^�����M��nLn0C��ڪ���b��0mH�$�Ǽ7�h������X�^K}���»�5	���iܿq��K���lc��pɝ-����<��d�u��h�ԅ�����kf{����_�>�����p�Z��.���F�3����5
�=)<gx�w٤I�3��B�$\
�!@e�����u%�\�G��W�L��ZA���L�iåe���$��[ӂ�ş9�&��,�Fqr��>�Z_o�.��Bp��Z�UT��DЖ�D�ƽ��,+��p���03���<�d��TZ�C2��"D�d8�#�j��߿|�t]��S�誼α
��SS�i$�	\�#%�М�Ӽ�IDA����<#xGs�~��%U�R��U�=8V�qa)YV�.�1�4��pnC�Bh�����f@ʥ���W4X���R(�Z��;�w�
���_�ԍk��yPt<^��ہ�vڽ��^vo�ٙ�tn��/�om��!�q���)�4����z����K�4�;@k5�շ���"��>
����ޱ_���;�?2�5~(��Q�L�)�A���!p�o7R@*K�"]ka�n"J���F��W,,�Ë�W;u�����Z����ӿ�e�X���o��\4��K��B���ΩO����Ѻ�i_Qv������p�x`��5v�+��9̘�s��%��l5���W���J��!�� ��|�E:�)a�(��܀���@���0XL�t�P�
�n�O�J��/�ܗ8�f�S
�A$HX���%ѡ>��5o)[{BQE�����-�W�C�r6�l��K6���:W��g�u.²�^����r,#��/��\rM^�̟B��\���D#/��?y��)�
���X|A:
��@�^����ii���t2�裑������1@��wL��&�_9	�lU}|��z;LtR�1V�
� 䡑*�d;|o�1�Yb�b�8�<�
�c� 1�Z�ZG�鷝{ۯ�sVY_l�#���
ν
U�2z�
�[�D�H���0�&ζ84#��=��B�4cp�AV�&�S[b�
c�AGM�Ep��Kt�<|a���Ĺ��w���{��G�O��b��|����^]�{�uk����!���X�56�D���^3M��Q-l0�8����喝�FP)&��_q���Tb��$s�6ƺQ�r~��=���v�*�sװ4�#W�
��ڋT
�.�˧�M@�Y'��5�8�ՌG1�gC�1�ol���p�Y���-:Z-+��9\b�Dp�4��OS���pin�Ks��lȌj�cb$�>���;#x7p���}��L�r~>�����$�(	��\F��j�Ԧ��
FE!�9DŽ��\bU�^�cbL���?������**Z�-��6x���r����~o#V[ʝ(u��uߪbO;;h��٦�՛��#���
hB�;�<Ln�.��;/>�%��Yц��.�K-B�h`��K;����`�Bs<�}C�F���a5�OӠ	Vw��MU�f�eE�Z��7:^j�S-�,#�q;�1�\ɝ�i<��f->��H,�6U{=
M����y�{gY��+Y��C�ͣ�l	�Bt���c��N��JR�����:�u�_��˩�ˠ:X���>W#��q��"� ��C��GC�����D�o�v����v��t[O�~ܹ�ݾ�Ww�C$T�!b���cT�s|��/�r��B���u�ԁ�
$��,�{�'�����z\���6,��k>Q�"�k����f���)��!B��7�;��{
l��ȂA�Z$�A'��i,�7��Woܺ�Q����S{���^q�ﭹ�l���+{6�.�Ҳ������}��{����2�-~�m�WA����4�Ç[��s++7������..�㕹����}���q�ͦ�n��D��?���h��?��PO~�[�@�1�:�5�B��}�MB��j��P{�E
]\���ohK�Atq�Z�ל�]��hw|�������t`�Լ����{h׭�j޹�[C��^Kڿ_Z8t�����bm=�N�����Y�m����e��q
�SM�k�W`�i�1�du�F�1��V����){Yvj!�G�d„;����!k[���s�7��)a	O��a��7�Qܪ��F��v��l&K���XgB�Xx��[�I�2��*���d6�q1��G8��&��
�{�0���3������������N�����D��f����O�jDJ�Fí�5K�c��$u>�Op�P��D<�Cg�{n����x�u��N��-y�c��H=�X�:��Ƈ�'���%2���A�Z�V��~ֻ�}���X�biw���߷B��K�FD�h~xS*�@׼0��<�u�M\){m��%�]�ƅ9Ö��ڂ�H��󍼘8��#�y/�lg��!����<�����z,S5>�T�b���*�*r\�_��hqg��>|��d!6�t��4�)�&�٪c9!�
9V���v�3���\\XI�|L��|�
1�0���p*����F�2�
>���s�p����YZM�a����b�\v,�#x�;��3k�EC�t����&���~pX��z�&�qLn~�NzB�#'�#��>���ŭ�b�����ʍ7>~�‰N��^����o\�}S���[�
A4���3
�7�
G}���3�gގ�^o��8�n��S���{�;�P�gS�%0�ʁE�U�ba�zL,����u�����7���o`�����+���bn,���sm�`�)^�c�����'e�<q�z3��LS5<Dz�
�=ӿ�z��!������������m�kP��>�����opkd�2�ap�`��4�f��N�u�:��:<ˡ!�?�m��ŷH4��F���8K�m�S }f�5��c:XH�d�:��E?0/���ej%3��/*�2Ӆf�d�s;K���;�fo��w����|&�,Lg�9A��Dg�,Y���p�5b�ӹb36?zml�]���ܖ�6��Պ�a�Vl��c,�9\��O�1��j-�~�A/�[�CGj �P�ep�PQM����|�$�*�b��u��(����LuY>���ė���)n;���i�E��J����i��VD/�q���`[�����zA�X?w�Au�6����ͭ�����&��,�^��������|��y���w
6��u���S���������yxj��B�D!�����y�"�4�#�Q��\\<���Y�?�����S��Զ��y�'G��4xL�����-����x���*�?�,3�������m{�[AC"�FHt�ɢ�]p .$h-4�hOd��?�9Yy$	#������i��Qhʩ�.��ʇ�{@SbKZdX!-��"�={��H�wHHO<�p{�!^
���.~^���i�T�ݐ���'Q`#���D[+^�)P�L�D1G˜�)�E=*�۪a��E��ӊ�� ��� db�	~p��21��>����8A�9q���j��܏�g�r	���4H����E�%I�<�ϩ�Y��� A~k8m��(N=0�
$�'~�EZ�e�)�����*�E<��]���d��P2��M�^��gɤ������{��R
��+*�k�)ߤ��ĭG��H�t*���>���[-W�+�L�d��r����e2�����:������TE��xph�ʍxT��RǨ5�5a�	&�e����X�u��n�+��ǒ��@����3p�I�1�v�R�x��E�
�iܥa��M:��K��d�!:��HӬ���f��k����1%n�5�yXZ��i�6�;�N��N�c-���ݟ޽� #	�^y0����J8�M:;w:�l^ϻ���G����!\���n^��+�1e�
-���ݮ��v9��{���#;d�g2�=z����n�ia����������槞�T������x�;�y�w���w<{��_y���D���$����'��r�oCNm���������N~�ç����y�sr&�=5as^�;F���R|n�v�azE>�:��̎���5�Ս�?�p�3�H������IΔ-�<}�]/�o�b�K:��\q�C]}��D����r��ᢒ]q���x׉�\~�s��`�4�O�P
�^�`�BW�}o�ai4_��_���71��ɢ���C�ͭ>y�~ݏx:F������.~{(�/�q��`c���j�y5�ٜ��B^�����مZ�P+��'�wCb��}'r��YHysR~���)T]=��X,��l\�n��h��y+~�>�I��?a*on����|P���瑏d`�ML}U\�2�	k^{�0�Ϩ��-�'�RXWCL;mJHb�� M:D�J5̲i=��1���kHV�0�srҝ�ε����)j]��w/�R
��dm]`�(	�H���0����j�6Ba
_d�(*��;�*!Y��w͹nY�wt̓�͑�[�K%}��)�`D(���c�N!����B���Uƃ�6�q��y��]�AZ$>#4>״n)W@�7�aQ�{0�{o�	���
ן��7�n��T;����RW�`��Z�`7�~_�u/w�x�^�$5�za|�PӰv�r���1)u�(��:&��n�y@:By�
�|�-//|q�Nټ����W��P��N^�>}�ؐ���o��S�t�{��T�'��Cu�'9N����:Z�4���C���)t��0�]s����a|�`���nH/��`{,�`M̓-��LY+
1JvS���j�vg��Eҕ�K��m�#C��Bb��j�S�5�]HH$��i�n{���\�ndrQB?�r��T��θ��=�V���D���5m�ԇG:0a�y�_W��rwu�zI�;ĀZt�F�����{#س�W��C:,����2��Xf��貦��2��Ƈ7�`�?8��;�)�Z��
����<ǕX�)Kh�d3����!�G'��3�>>���ȩ�wο��a�(��EqE�XU�Fn�JSE§��;�R7b���D�0|c!��<���ow�aY'gxÍ��٪�����h9~�(m!my��Ϥ��q��"K\���x"��I�����(��&rR_bK���S(XZǩ�a~�'
.�6f�Ϻ�1#�t��y�J䯁�6���? ��.���Vl�W��u���{���Y�Nu��Em��yǫ2mE�f���:�|_`?����x���2i<��c�JF�%u��:�����B���-��@�I����#���~��mm��	�Ö��X�%�K��
\��f��="^7�pe��hƶ���a�9���������w7��`��D��x�6E�kGzk�W'�|�+^�T׃5T_��|\�}Uo���D���&��5A��>v�8Y��'|ͤRj���|Q���a�^�`�̉4����)�s����Oh���d^'h'�g.z'����`-�D�j�J�N֊�tÑ�~E�<�2PY�IO��:JnΒ�L��ҧf�P���_Ƙv�~>�{�U'�DV�	�Q�q��E^ගD�(�l�DG(t
N�Ãi,��S�Ĭ^D=�F�C;�=�|�	�3|��C��;MAGƒ祝��
a�&��
~��?O���|pH[�r+� ��5�W��Wߧf� ��]�VS�˵�&D|)(}��/	2G�HC��!�*U\�ര�ӗ͢7U�ظy��#�x��{�{�=7�ˈ"P������-��=��:�m9o�nk&[p�>��vo��@ M`����9������ �P�xAna��gG�F��+�ud���P)�������ݵ-%�F��]⣽-&�<
ⷎc�1}&bM��>?��D>�DL�f�GpH'!������kHT �N�EC]�]�ύ�U��Xƶ7���M��在�=Ͷ�(�o��;�Eݨ�Ͳ�&��/�$|AP�w�6Z���͍b���F�/��X�D�(u��Y^�3���?8�#z$�t����+��==�y^EK�5�ظ��<p`'��|j��%{�=y�>E����:x���bo��\xC����N���w�vzDO�'h�Ʒ�
�.���db���Mv	h�{�z+��p�]7!��%��g�8���`��BȖ~�AǕ��,�E;��Mʒ�O{��=I+�,:%�-9�l(��ݐ�K��b4�D�����wV!�\)\��S��,�K�WS�S7S�SwS�ZD�}�a�+zD+X5�`���O���
ۖ��$�S.����#m�,l��6c;ɁdrL-�'Fr�\r�\D/�bE/!�+o��t���|3S��%
��<��#9�)����B���(E��t�ԇ[e��Wi���g��Xj|v�WV˼F�N���u�/W[1Rۣ�B�U-4Γ�B�f�<Ab�Z��	������G�Hjm𝊚��]��`�0�qϱ
T��k��K�7P�ai�~���V.G��P�0���Zm_���Xe��E���XD��¿�œQ<2D߈���#���y�WE�T��A�=�vd�H�O$r��^n���;�]�B["Ub��b�����B��e붖2�o��#�i�>ʔRw����Q��q�t�i���7���L5��ۏn��p��U���!a�V�W͋ṝ�$O���x{e���^U�}�='*EtЪJr��՝sa��Ȉ�-��{�yA���,U���o���aD
�I�~ab
�3Dj7|�c������p�mS �)<(ʶo�pԄ�����j�zƒ��ĩnwm}
�`6�'�Xʼ��?q��ѓGL�8�E�U�x��C�saM7�g�PX�$��!���cclL$V‰�!1��)q$���X���o|�w�aY�؎z���V����ǁ�?9
�j(B("��.�����h�l�q!�ьJ��l��9Q2DU�MA1pzC8��W�Vz��&����>��?D���N�T	l�d;܍qKA�Fu��6���
j;�S;%���Uj����`�8-��������O�톸�*z��R�?�I;���|U�|}�6��Y_������Eѝ��?� :�Tl2a�w���o<�̃�{s�B!�G&lf
�|��#+�[���m��.���2*�f��P'��C��|t�e��A�!N��f��K<#�c���d˒�1���Ԑ�9{m
w��_�B+rњ��yNd��D��w·?f�*��fCR�`�>�x�Q���
��?�g�Y����]+��Iæ�j��຃]��E+�s�c8� ��kz�_�~��/lV�׫h1y*Y���Ө��z{Wopnu��Ql�w�7��z o����)ȶ��f��[���A�8�J��ƍ7ns�|'�E ؏��ǟk�̆���
=F`Gy�AB�<���ނ�6oh����A�:�[��oܰ�m(��ձ�8to�7��I�W�5;5U����K���{��7�*e��5�3�r�b�b 7���y�E�&���(K��ܱ�V�NLn�o1�x}�P���W�2~��W��1��-|�ru�!�w'��r�V�T5�t��n����^�<r*‚s�g�X9-��$�<�d���Z�v~}��2B�i#�0l{*�A=�dN�쐮�n���q������g�-�j���đm��XaD��v���$��?}۰V�	A4��Dɺ�E�K��N�bV����,g�\��;s#dE����t<X�L�c�[�+dD����_���Ǭx�ȅ���5N�I�ֆ�l�k#��9�N,OY68�Z��*�=-��c�z�a�)��$�U�	�&�=��&/���'�&K���T��	|�;
����G��2w<�0~�yD�x�8H���O�Ly�,Z�
���,g=���D�2�����Bc���a����ü:�����~��8;��x�eʷ2bf)�9֔͠�@��v�4k/7��I�W�E���
O��	�z���0����Y��C��0	?d���ţ���!<z}<<����J�\�A�;�jl�ͭ��c�c0�
�>�a���ϼ�������]����3����;��ޙ����5EZ�DC��ef!�6�����)	"�`	�0����
6��x�� �"�L9�,�^u��R}��3=5�o��u�{U�}��|~�q�|�Ń�om��僿<��ch��,�I��	��	���lDrB�K���t�i���y�#���Χ�Y}��\_C]�H�BЃ�%>\�AJ �2�z�d�AJ�]�A��Z�zCI"<+t4~��`��+n���2xa;�g�N������W�(�[��m�Vd[�����P��2�
-/�9���LkS�s�	�鴖�����z8���>s͏i��8�L�^��
��q�s����c
��λ�ÓzU�o�~r��{W}���*� �?0n�ᑾB��:bn.���z�3�S�x���]v�~�5e"�g
�}�~}���l^̀7*?��~XQ'EE��d�YIؓ~i��2���W�;RI�8ӯdϧ���ޢ�}s@�zF�,b������f�t�	�{��;�T~4j�ۈ��H�����Z�_nO<�c�R���z�ƜZ�v�w�Q�ڷ�0hu2��v��zAV�%���4�no{t�I�1��ݏg��So��d�G�qH�R=}�'?y��Uڣ�F<j&�c�^԰m�k���&�n4�ض���/��������Fnh�v7�\��mc����P����9��c�{����/�xW�)5���ʃY_��3�,�gW�r�]�~zr}�$�zn��:��&x(��ݽ�?���q�x�|���	B�����!��[x�!D> i�#��0'|�L�,���#���I�"��<��if<n?b����fL�
J3"�oP
�?��<�H�x��|u�2)�i�y"�Y�YI"�,o����+we*��&��B�$r��+D��Hj�0?���Q����o��‹+Ы˒g��>��9��$�@;	�i�Q{$����5���?:D1P*d�y�r_�g��p	/�#���`�Y'b������EZ�k���e�����������G=E3��^Cnr���sLM遪am�ƫkQ�״,K|z�a�����-�5�h���K�Si90�Z
?4-�4,ۃ�/��\OZ�拌Jh�d��V�~jh׳�tBQ<d�vv��� 1E*rwf-�
���Tn9�� 3�
�
�����P�G�iQ�4��,����8�����������<?3���=۹5�������}��zQ��0�*���$�do���6&�7D+�|�y� u�
1k[6Y-�V!�C�������طV���X��X��t5��z���>�=���Q�|Ax_�+��o	��5��6�wj�QU!B�NE�O�`��*����g���B�p��e�<O�b�x2%�L�d
���
����5a��J�cy��(e�T���:�1�A��T��.�pd(ao�jN
�M�*r�TJ̐��)�����V�P�ܖ���huݵO�;m�;ݽte4T�Ļ�6����J��5ո��)�N1��(�DJ5E����$�[-�:(����O�x��p��Q�}R���_T�O�2�1��d��j��ɒ4�ڠ�X�D,JN$�MEw���*DI�N�hy*S�8�r'�V�C����k�-�ِ`lђ͚��66�fW&�.׉.�*SsƦ�6c"�TL�k
�O�N5�-�V��
���}2���F#&���!�>���F��p_���0�l�p�
��&\�vHXb%��4��S#Nϖ���8��̊���Y�8[*'�����cRFέT��2O�a�C����a��R��a��z˨f=��z��zK<�3��CG��R&�qC�)�����97�dRF���&um�0�(DP��Жjf��dGj
��@��lȶK��_s�T��L]��z�w�!�%&��!թ��;r#et"�=`�ؚc�pw��l݆$Q�G���P��H���x&��P6|��D1�`��W��.���}Y%��b����'��_��˜�SM$PC�rCu�'<B���uKCp �H}}�	wk��X�s�PÂ��{6��}�MK�$�*��)a��b��R�&�����|�xlIh�ky�6>��r�AX��� �}A���ZC�,0��ܚ�=n�0]{#��ݖ=�7���4[�;���iV���Z{�q��/vh��b�Ɍ?�g_z��9���WA�C�`4��q��w�o��'��]Z�w'�' �?��X�ɝ{�K��8�CJ�V��QQ廾p.��;N�vE�h}+�c���{n��r�$Y�-p�\	�8WW!p�$9T*�k�EUFq�0�08Tfb���F�#It���He���O)��3��1�x�(��֭�E��j���ЅIzJx�$S��D�J�90�80���)K�V=R_(��v6-��@�I��s��o~��4�1;}�8�),�U�e��y����xt�lSBA+�����'G���`~�]��P[���k� =acH��pf�E�����6��a��>T�5��ģ0�yA���Pk↲�F�VK�M4�I$�pL_��Jh�����lY�C�[�	̑,�A�"6�\p��.��ٟ�\�t��)뵍7��x�:���M���� ��M:�d�u&�~�����<x𝚻9p�����R_���%���Q�UL���@���pW���+|����x���*�y�Q�����<SԾM�Wu�EU��3F"���^�\��E�p�.�6F�ηB~�څ�.��W{���a��[T�w�2�J��p6�v�x��Esa��g}�>c��^t{靗Ȥ�gQ��ώ+�t8L�������m��� ��(���݉��a�꠳�
�8^��(4�Bx�՝nH��a�hV�ų(̦���(�6�`�|�P�G�{>�U�,���e�;Զ�u����+�ȭ�Xlv���b'�ʘ��~u}ͫ�w��?�7A�A�1M�W</���A�I�&�G��8q�`�>Cl�װr&X�Zs���}x����.	�܇�8�p0t�
����*�fQ���r����X�7ϟ��?�uʊ+���{�6����]E��?�I��{�N�y��3^,�;��&��z��u�?Hd���!�����K�@��)�r1��%���EF�@�d���F6��Om:ʌ��Jz�ow�]to���Z�+ɣ��Ñ,u;s��;�7��&���G����xA�'�}��>��W�)g��x�}�=N�@��� !�#lA
o֖+wIPD����N,%vdo�����	8������ A�v�7�7�.�����t�n��>-7q�L-��u�,��s�,w����i�1�����Ao�8LJ�&n�e�E��r�y�ܡ��1
hD0<c,p�9$GؐF�G�֬W�=R��H���U,�d]���b��pmb�����W�d&�j�W��БѱX�0�6bTD�Z�>5+1�33ɋ��T��2f	դRe��L�L�A)n�XRȳ�6�H���)v|f:Nw��r��;�\x�s�)�џ��g.1p=����ޜ�Ji]�e��`��2�3��'�R��7�p�x�m�c���F��j��J���~׳Uoֶ�)S�HR۶�Զm�ƽ�3�n~���o��>cα�����{�A���?��nnac��6�
�qm<�&�	m"��&�Im2�ܦ�)m*�ڦ�im�Mg��6��d3�,6�
��lv��洹ln���l~[���l�U�X��j5�[Ú��-b��b��-aKZ��ֱ���oK�Ҷ�-k��򶂭h+�ʶ��j��궆�ik�ڶ��k����mh�ƶ�mj��涅mi[��6Զ�mm;�ކ����d;�.��
��lw����lo����l;����`;����p;Ž���ha#m�c��qv��`'�Iv��b��iv��ag�Yv��c��yv�]`�Ev�]b��ev�]aW�Uv�]c��uv��`7�Mv��b��mv��aw�]v��c��^����A{��G�Q{��'�I{ʞ�g�Y{Ξ��E{�^�W�U{�^�7�M{�޶w�]{�޷�C��>�O�S��>�/�K�ʾ�o�[�ξ��G��~�_�W��~�?�O������w��q|����>�O���>�O��>�O�S��>�O�|:��g�}&��g�Y}���>���s�<>������/�C�����U�y���}_���}	_�[��w��}_ʗ�e|Y_Η�|E_�W�U|U_�W�5|M_��u|]_��
|C��7�M|S��7�-|K�ʷ����o���>�w�}'��w�]}�������{�>����~���!~��~����{LSWp|���D,��N:��UZ�� 
1b�akt�B!0�T{7��W���|E�d�\�M��Pl�nд�P*"������B��0�{��Z�������s�;�|9'''�<�p��n�
C���n�����b���j B�f����q�
b1|3�$ހoԺ~$��}���w�\g� �W�
��KK��S�)̵����:�]�I
���7��\Ą�:��������M^�m"<�l�'.!�+O!Հ��E��\|+y@,��l�X�8�l��a\Dj=6�%��QTr�����k�0st�����R�?�ܖF۬���%-�G��|�BŰ��)��#��'N��0%W��
3�W��a?�^D1��_��>C����k|V�A��O�#jGラX֭�N0̏<��#��_��8.�Ï�T�
&�+���|F�rR@�P
����5�֤�(Y�t@ߤ��C���{�T	O��6�%�!G-vP��j d��uE�!�e��E�!�)�����k檯@!�a%�˩�y֚tp3���t�W#?����bD����)�9
+LE�5�K�(���K�n�&�@���YSf!MΘ<	��I��D����s�<v��q�	;He��;؁<�jt;�d��]@�1
�TTk\������%~=��Ŧ��VB[�x�3�Mh�C�P6ā�:�l�I�ȵϼ�������Ti;�=?�y���pnʣ_�u*�jŧu&�}��kj�L����v�5�����D���c��v��u�/�$E���s;��:�c)�hG��6+�ͺ�lf�s�[�>^��;�*F���=$it�b�R*����{&�3c�B�6h������-�Jq��6���"$�4N�)�9*���)8@J�a�S#���,3ӱ`�z�]]���ٝzS�>]�NBŏQ��F���(4�~Q�6/~�6Y���s�?�Q-�m���q'���}3��A�����;@ՏAKg�-e��{����	�l�?A��4*ekǵ��w�ԏ�:d�R9(ޱg���/��A�Q����0��7��_�^�3n�,^�]Ǻ«>\[YْuL�-��)��9\\���6T�,�n(I-I�c/�W�=���!�d̛��
O���9�+��?͞Yc��y���x|f��&�����spuSEחw)�
ƺ�>�����JPF-installation/version.php,���m�Qk�0��+�[���t��[�"N��n�M��j�]�Tp�~��� �~'�ܓ�U(/l�=���Y�.��K�d���
Y�p-��^jd�k�y!Nd�51K9�3�#Q�0�*��6��R��&�U9�8�A�eƥ:kq(,ƿ����Q4����,�����[�l��u)
�[����Rp�L�?M�1��4+��3���i��t�*���v�C�C����]Y-�PW������Ud��1^N{QQ����3���#���+�x�$�x�Z/���w��H֛�2m�c�w��do���?x�F��JPF7"installation/angie/application.php������Qo�0���_q&%�B}Z;ms;�P#����XMc�v������h�Ie�K,���s�O_LeX:2_�3A^!8�,:���J7�U�C�-l�|h
++���E᱀���F�M_�Ǔ{]k��|5��-P�Vaw&��Y��<ܞv�L'����dz	K%+]wc�F�vN���Az��c�Jb���slЊV��z�	��./�"���唱K�`��k~7���x

���1I��C��t��cS��搽����m#�:�(�D��XD�_)7����h��$.��nQ���iQ��^-��F�,�n�ŵn$_��z���y��|�s	c��ʘ^9&��D%� E]�'u�T%$��g�K�Ǝ�{�4x���1�L�j��um�Kv��YR7���d��JŢQ��|�[N��b�įJ|q�Ȉ��F�w�DƮ��?
k������G��i���D~��ُ�=�!��JPF6!installation/angie/dispatcher.phpE�
���VmO�F���9	�q�Z
w���H�퇪�6�$^e�k����ޙ������<���/_���vw#؅������.Gp�#Xt�X���R+3ca*�EY��i.W� �(<f0�`�@�
8k4t֞��2��|+H*�@
s��,5Ee�<�p�������ß������Ls����N	U��T�A��g���)j�/o~�K�h���rJ�n�+���T��,DQ�3�1K�������0�B&1�GQJ)8r�ߥ+�Os���uF��Y�O�+ꐳR���F�!5pc������O;>�n��l��'w�L�H���z}i5x[�1}?Doز4��gB�׭�lmlvk�ǐ��|�$��7R��?��?_RY�A��੎C���cA
�w�y��	�#hϹ@��=S�.G�N����z>����{�Q��\:�)7k��bA�>h�o�	Sc��V���O;ܢ�`��˵%�Ս����{��:����T��M�j줞kE�_{P�Mtn��Sk�T���ؽ�)�� ��q�?��IK4Hb�3�О��!NZ�OB�a�A��y��Ap�����f9����<:�h�	P��j�UZ7����Z���%���FN&���9n����kcQ�3�
o��	�����j2��
���w��n`�Ak�틏ۉc�	�K�]�q��B�T�N�N6���]�p��z����v8_
�W�#Z:���J�����CȰ�\'a�Sp]*Usc��;�2&��$�QM�e��A��_��B"�Ycƅ����mo��[��
����gx1FOB��QW>Q��~{�6�ʤS�cr��U��ܔ�T��f��2,���O�����ֿ���^
ۗ�����3�'���`�4�tJ���@#�i���f1�8��|�:�(i�BJd@o�B�T�ʒ�ܑ���c��J���!S�64�.7�C8�,����j䀼;�D�1�
����PV����,��_Q!��a�i�
��I�*ӱ������D/��"�2��������yMtO�7�J�4�֯�;O�W.���rY���[�^ð�zJT��9�*�e�3�=DѿJPF<'installation/angie/js/ftpbrowser.min.js����U��
�@E�~�Da��������8ք�7̌J��ޘ�h����U��)°r�h���$5X����=�e��9��ˈW�1����6�2R82o��$
*e�^j4�D�C�����$`l��0��Vf���Ϭ��8g����q�-�F�h��W����(d/��_JPF6!installation/angie/js/ajax.min.js�l���TMo�0��W8�X��$趃]�(��E�;(h��Hٓ�A��>�N��av�L�z|�621E%=xD��rϟ*)11��M^�Q%��R�zH�,cHy��b�z�"9HQ�����
>��%L0d)]�/�b0��M(����遍ξ@�?��'������%(/����`ЧA�fV,��h��,�5(�����)+�
�<PU#�o��$�-8��AT�$���x�b�ݽk�R��@
)�9���ȱ��Y����a�-�&L���JhV�	m3L7I�Z�;}��é��
�H0�DV�t�3-���'�~ߒ���	�Uhe'�ɵ\BYO��+�1��ز*�V��=~d����%&�C���ɎB;5tk��1˨e��3�Lܙ��R�����k�@W'2jն��߃v�>�M&���x,�Z���g��ۣVh2�#g�h��h��R��у(�s�a-�e���^�n���V��j6��Am�/v6چ�����vb�n����_�>�!�m�C;n�JB��}n<$����WS{1����=�@����)<y�����mR�q,�?�)���j�
�.nE�/7�#Kc���`��Ƈ����gNv�G��A��0>��N��A{U�4
�{j`y�f4�{�jT�7Pׅ�~L�q�\�(d0��?JPF6!installation/angie/js/main.min.js������MO1��
d��+YKZ����!@���%��j��l��ڳI�h�;�B�8Kc�����8�l��:��4 �e2�LAҚ$]���D�*����N$LۑT�CS=زT�X�y�����0aBz)�o”�;R��(� ͐��h��:��~��U	����_��\�"gQg��?�L aAst>d2>�N�l�î��`v��?+@����mEK�Z�󜿳M�-K!���E��f,K��w���;pB����Q^ll�A^
�s�C��)����X�2͙4�60�,XDi����1G��Nv��
�8!���?4����!�y�?C?��������u#Aٲ����QfCN��cs6�vnz��&�)h�����'(�I�������������؃�L���g;��tXS�i�=JPF;&installation/angie/js/polyfills.min.js��"����r�F�=_��*�.�{R��W�e2'�$[�UD�j���@VT2����Z����/�ϭϵ�mˤ˫ҹ��2������Ǣx�E-�ֹtԞ6��6,s3}�eL-9��7qU��n+�~މ�wU�0G��F�!yLlr��<9�'U�v�6܌�a}f�~�T]����` HG�{�&�n[��<���l8���O�H:X���3o���p�krx䍓022�y�Ӯ���@�ݞ�l$Ic���>��,/��[L��#�>�WۆDžfWt��?�b+��'��%K�p�p$L�t& �c���Hխ�t��9��9��o��j<���sGct��6oD�|	�_�$T��8�{��.]y�v	�W��f�݈�k�B��n=M���9y�U�ۺ��N�.��"i*���]==�?�E˒	2I#�p����U� �H@��I��y�#�F$"��^�c�]�{�޹k�\����`01P��[�
/�j���~[+W0l�q��/I��S��VD�T�.�
Dδ��6�RD�]��(y�P>hc��z6�<���h�d>����+�z��Jt�r�{�����mǢ�H��e�̯?'��,1���Sj��BǴh�J0Ik�b�/��@���᭳;v6`�bށ̉)���\�x4�*=1w����Z��D��b��E��}Qރ��s����@[Qd���]6���ZŰ��8P�b#t*�$0I�p���W����3C���$�#�RQ�ȐA�N�+�k�0Ό�cf�i�/��|��NV?/�L�����S�7s$�(����]���<�ēo ��*���	���X��^F��Ca<��ҘJ�g�=��3�-�9a��G�������S7�Y�"3��W�7#����I�A��8r��r[j�a*�Y�4H!s��2���#v����j�5��b�/�ӽ�l��-8e��\�VFL(��A#	�f��ϭ��ǻ̀����<�}׿g�ˆ���]C����YZq�(��P�X���$>�T�9�����Y`�'F���q���q!��t9�Z�|Z��������9HA[��b�o�:�4�5{����=�'��AOm��}R�8�ETmX���m�AN e�B��*�aUs@�G�up����D��ėR�	��%o�q�+��C���A*4� ~k,!Ļ�Cb���򍺫����4ƪ{�
���O��"�,b�9��A&�KK�Ԋbk�r]�u���Ah��� n�	m�\���M$�^p�J��4`n�� +OP��B�:��Am���HyZ]��d�?z5���<sD��<��OuCS{.��J~�Dm�;IFP����	(� ,Y�LR0+mώ3�|����WY|
\e���f�t���^V@[�ʿ�vv[�}���꽠2��ӓ�p�
��5���Fޟ2�c��(Z!Y�C�
;�!�ލw'Uvw�Z/�ٍ_�q��j�kp�~��}!ҟ	��a6�
r*fY
C�-��`SI�ԅ��b�P&-�Qdvt�)3����d찅�(�ԩSȩ'�@�PLy��Jmb�`��g9UG���r�rK�r�4� ���|
��UY?�cb��$6ĝI��1���]�l��,wf�[Pѷ�^�S_��}����t_:��S�����bI�
�X
V�Ѳ��U_��."�E��x�s��%߈��n�� �s�@�qO�݀|��,ٮ٦D�:�#����F��S��pp$�JS��Y'�Ӆ�T!7e��‹Hz�z_V�o��*ͳ\��fO{�(���c��9@����y���E�B7O�Buڪ
�d�E�Q��;��7�I��俷�w]��\��|~�b1�?���8CӋ}�M�3�y�����R����~�),k���ӹ�FK�-�D�R*&}���NJO��ꝭ1��w���}7��p|��֘:�S�hK
i��EX�_�ֳ�ä�ȃ|mB��ϕsA:H��e��p�;^'�٠��
�� �Q�`�?�K<j���-�|����3��)��X�=��p�=<~0ժRg�O
2�ݎ:2<���Zc�,�����4
u�	��GO��2��E��*cB�u8�d��K�gX�=>�Ql�����*�/ˤ��y%�f>nC����ך�jkEm\R}�kpO�4��0Y�8H
���@Ò�6a(��=���Rը�����&LqS;��}Eǟb�M�"y��?�8lx�W��"�%�3��I�{=9���zbyԠ�ը����J	�H`0����t0P�t'���#��y\���LYCby�p��y�lA7���iC[Y��l������]�!#�j�p��A��n	-e���
�͗��3�DŻ��#��6��u��ܸTW���S|M��9�,��>VO�_�Plc(�%T_�-��U��ؐd���ժO�G^�#Z̆�ڪ�$�з^C��}��`DH��!>=�JA��<Yۑ3FR��-��&�Y��=�����n�S�~^�!���Rdn�{TM�mT����[?�Ւ��\!�ݞ�4�mL|)c���V^�v�m��B�I��q4�X�=}��O�/��5�t>�5�����voI���Y<H/0MA�F�o��7h*h�����~E�|�F�q�$���#E�_��5ĢV���$�	�S:��	��Rz?�(��(�����F�
+Υߌҏ,�p���G���n��WA��{6N��dp�T9��D8���	R�O�;]�M�M�iL��C,��ν�?XؤP>^��.݋�ߪ�}(��A1h%�	%ø$eF�Iҁ��ߛ�K���[��*���.��m]�9
8(�%�:����{!�:��5���Clط�T�l��b�#����(�7.QL*�j����߀�ه6�X1�o5Uh��W���^X4�
Z���D�5/f�]|�ÙI�މ�*��XX��K�,��Ӻ\�\Q�*Kv��%�:}������/]r"�#�������"�<ش9D�ex�2��_�^�<�^����"�yB�e�u�"�L�v�gHg�O�c5������lOՐ�J��I�\�s��{���i��$g��s�uU�7�#>AD���ǰL�Kߏ�(���}_|���?���6��>����/��6���K���l�?JPF=(installation/angie/js/offsitedirs.min.js����Tak�0��_�����	�0<�XGa�6���8[��bY2��,��Y�-)
e�O�q���{��*s��H�%�����j��i�@�m���d��c6��"��Y�dQ�M	.f+��
����u�m�7)�G�,�%Bgu��񮸑�כ�"`}�{��@�Bހ�1:z��9��^t���'��H�kn���Kv�@��$l�J�OD�u�QZ�1�m�!@���i��
U�%�DC���CA ��E;b���$~1i�Ȣ��u�W�pۃ�[������|�H��,~cq��}��4�6�⫰}@���LsN�zsJDd*�0���������Ko�8���@C�R��:��D����e7�j�K�����F���� ���o:�;�����S\{l)��j2	�0�
>�^�{q��]O򽤬��Y�z�;�����fڍTu̖�����|dum2�AU�Z|��&��������@�JPF:%installation/angie/js/finalise.min.js'W�����j�0��}
�袻el�C���Βc��IIR])}��a7�C�]r��?'�E|��}��K+�MX Ț�*#����~�@��.�f*^
:�q<�0�x8>�SI����pj|��PFXB�VbK��~��r��R��8�(K7-k�]G]�6�E?-_�i�S�OIǑ$�ߦ��Te������m��^�y�����p�u�=�9
S`�`�e��7�4�%�(�F��sC_	�k�@ۜ${%�:%��+Ҳ�"����s�/��c�7
��VN��R��m1˺~^(�7�P~JPF:%installation/angie/js/database.min.js��#���Z�o�6�}E���"�:;����2tw�649� Pm��IM�{���=R_�Ȕ,��p�~Il��W���DH����d�1���%BJF��Ѩ�	��H�K��hJ��N%dB����y,W��w(�%��iΫ�b�e�(?�4��_���Qc��I�%�'ǒ
~"�t��;�b�YODb/`��
�H``��%#��0��H^3f[�'$D�p
%YJ�qq0����c�O���ggΚNl�7�ֵ���
�y�\1�E4�Z9��@`�L�{�ǂK�3��!�P�M��j��m��H
��Y���jca�^P�0��0Ѵ�r%J��V*Q"-40Grl}L��{��@�(�|"����������l�5%�����ȶ�y���eĭ��D*��Լ�/K�;�Լ�/���S�&>��S�&>�q���a�%V㱶�v�|~�,i
1m�,)vy�T	�r8�କ}KxF�=��\�L
�ہQ�2�$Lw&�LN���ޜ�����t{N�1���&fp6t'��A���0|�]j03��_�3���hI��h��׈v�N�y�����x��S(9OO�O�X�Ok�]�L!ȒNΜ��u��:zS�Zh?�������x��*"�~a��F-�*nt��Aø*�>����{A:Y����m�F��:��p�(iL0�P|W��7��Qps����%.�<�,�=�O.�!�^����F^��3��lnI�:k�F�0	vǟ�Hμ9�ą��q������W�o�uU!�O�s(<�߼��v}�n�{R��HHΧO�G��cB��mu��ZJ�ڊX�7h�+�:�cxr��/���c��菗�W�պ��?=m�q%�i\%���\gպ-�˨��H{U�6�1���0Dʕ���FrX��Y큨
�(Q=�9I���8,dâR�C �D���D�'�*�t�(�����F]��� I"���ϴ!�݈w)"� �n�)�<@��VG�(bbj�ژ��7�
�*ƃ����A��l�����h�a�ؿ�~��k-e��+�}#�~�rv�]�w��6���%Po�p�R��>�W��c"�:�i�:�V[;�`gc;d#C.��	N��z�F��b���>r�ht��ҙx��*קּ†:�3�U�W��9y���<�>O�|`kE^9|L6E�{zJ��'�;0z��e�ȸT�d����Pp���1Gʫ���,nJ�����t%Il�/�'8�����4������Av���5��md��!��e�U����m|S�E=m3���2���xi(2zj�m�gg.h�H��8�7(�;z�?.u��aP#�y�Q�q��/�����G�&f"%���P��C
J9�oc(N)t�Y�7phu��c&m5����d���W{:���m�w3W�`o�g��]�x#�U~6o�o���׼�H!���1K�d�DŽ�1�V����<�O������]�`%fR�z�)�����-�j�-�65dP�3���$��F�ү��*��2G)U�V$���bdme0���(x1�M�� u%^	<h8<J0Uy~G�$�G�;�����0ؙ�q�ɋ�7��W�M鵾(�v����&��VE�}O�FW�-�<���J[ �3�V�_�ֳ�3�"}1�>0)�����2�����xA��%��Y�/W�����\_E�}�[L"��6�x6�pP1���A��!)��\~��⟈�<=�5
�X�`�OߞL�t���#�,�U�$|�D�J���X��R&5����3�f�o��B�.�-��fRjՁ�] <�F��C��#O98D����
��^5j��(�o�eL���� hL���q�����иZ�js���AѪ�a�_Nɳ�0�f�����O�;Av������NR�%�\�8���?�W��zH�>��ſJPF6!installation/angie/js/json.min.jsb����VQs�6~�z�
�Kň��^��r9����vk�[ڇ�Rw�D��ɓ��>[�}%KN�l�>L���y��"7�P�,bS�
y�@f�̖������ȴ��.{]N�n5�e�뫠Ȏ�I���;Q��(��Rtؙ�4E�
���;a�E^���6���h{Y��e�.6�wȆ�7j�T�O��[��������nv��x:�V�dB,ሦ��.��ӒU(�"Ȥ��J��Gr�'l�*Y)Pw�G��]�e&Bi�
C�\���J*1��<���!I��X&��G���|UD�2'�Vť^c��SjWYGg��Fᴡhz�x1�bY� 26g>K��`&�iu��pa�P�"He� (��uJ�4�h0���f�wHe�!;Z�v(���`)�N�&��%���V%�S-��@��0��$y��nja�:�H�E&EN�n%v�P�U�v�$�y�Ү.�`A�,�@�s��|�32k/�R�}�҅f]����e����Ҍ!G���j�B>����ih�S:��>�����s��A>�n�Kf>q ( ��X��N�$7	��	@-,�dFB��@�/�l`{�(
|Jۘ�^���P��{MA���Pb�xn/�ja����e�8��@P�ҩ�*i���H�<�K�BT7�/e��m�,���⁌n�.���(��͝�6}F�M34��.�%o5Gwd�r�mq
q�"��ElR��\��_����M�҄xFĊ�=��"W��a`�"��~(��箹$���.%`{��~'6rú�po���I���=��%5�3��)�Zwb�CY���W#��("�'������t{����L�_{���$�_��o�䉞�O��&��q�cd�/����r@��G��Z����iij�/�!��u�Х�kJ}�d����Yj�$�1y����4~��@[cI��'���6c��<,l�T��!���@B
=�M�<�F�6��ʼnA4T��"�L�t�BX�q���ܡ�рzA��:�A�
Jբ,n�\���$�wLz�A\�}m�:
�%{(}KQVr�:=Ҧ�
�:����+�Fd޹̹~Q���A,�-�����pUB� ��k8c�E���L*iH�|���l�`�%m0��
W�^O0��&�z�?ǡ>��[��F��Ge�3�gΦ�*����7�<�uf��� �K�o��d�L�◣7�次p��B�%d��s��O��rh�-B�v�z�6=ۋN����kfy#%�E{��=P��u����ěQu�t�`���$&�0z�P�3K���5�L��ӡ}/׹��&ֽG a�I����_��|OH�_�iU�U��ie_���JPF;&installation/angie/language/index.html�L��=O]K�0|���WzOB)���(���tm��nI҃��o7��>�����N~'�5��W��+�4��@p�;w�ʙ>�j��C�6�P�d�������8,��Y�O�<�܌v}�eK@���fPq?:���w��4}Y�mQ��J��'�ğF�=�=6��)4�1ku~r(�o�ԑ�C	��B�Oٶ���l��E��:��"�$�"&X*��Zg��f��Rn�?JPF:%installation/angie/language/en-GB.ini�@����}�r�ؕ�{�VM��*Y�t:��v�@$$1&	��V&S,��$�$��d�?bΩ������n���ܱϙ��,�������~���g��{�E7�W�U�IY�E\�y敋"�T�U^x�x�i���bq��&��(��J����?%�<�N�E�T�L�U���o�?�m��:�<hp�&��"���M�u�o���7�^�ۋ7��|�
��M��K�Ñׅݗ�&߮��{���WKk�.����φS�,ɒ"^y������mR����=��
6P��7o��_��`��"^'wy��['e	�.��,�f��J�"/���i�Ѩ��Q/΂�8�N'�Lf�����{�b+�N�b�.�I���k�O>̆a����(.?yϿ+�{Y���͖;�v�a4��`<;����:q�=��ʫ�2���_��<��|�J��C~��r>�ɝY���8ܡW�o��H���q�}W�v�;�A�i���d���4.}�0}�2�F��~`����
�a�-9��a����1�0�A'�KZ�Y�,`Y9�/�n
شɋ�@�m�~x��V�u��F�K����o�%��-|C@���n�r���������d'K3�1��%R�M��&Ɖ��j���ݣN.'_8*�xt��y�7��q�ϣ#��ް}����	##�~����	����>���4��t���ѱ����`=���#�z���d6�'��LJ&)��J�kX��"0uo
�͛'����}�]���&?�����6��Zo����)�����l��&8��f����J3����ѝ~��:���"Mn�U��s���1�D�,���ju���F�+��v��
L��;z��^�vqW��Ƌ�"_�={3x�'��W���M"���UZ��$̤O��Ӈ������xˤ����f�����q�	φ�I�uǟfE�ȯ������u\��؍�x��"E�͒"�Y׏��O�~��{2O�D�d/��W��[�a��@��k`8�g��ɟ���rc���{��j��߷Iq�hq�,�VG���u�G�E�����b�!�|�7�>e�]�6���T���1:�0
G��;�P�!T�%I�&��f����E����(��_���(�b���H�V�÷�-�1��p{�`<1���Q����W(���t9`K�6�K!ڳ���x���T� 
OO᧽;�P���
~o�L?���`�q�٢ʀ�)^��h|����q|�1U��긵��r��>�j�'�
O[$��}{g�*��Q�F���f	x�mQ$Y�S���m���M�
d�H^	?�����`�{��I4�
��ਇ�=���8��~oT�đoK�;�P�A��&Y|���8��$���*�3�EN�4�����ҍ76W ��O��t���>;T���O/�1�I�~:aoymЋs?��n([�=�fc��&�<Pǽ�C����eZ��y��!��ų,���+�]�|�]�0}��׉�c���.�j��t������C��^ig<��>��p/Ҭ�@&(��a�.�S�5\�)������n�
N{C�E��R`77��Z2��qx1Qrr7�s�{'E~W�9�[1�
&{f�d_61.�qwv$"�a��c4��9�IK��?!tt��^����}�$���"�qı�2�趓�
,���q�R�\�~��-71�3�b(��b�e!4v���<4+	��>.�>B�9���G�^��mn6�^��Q�p��'lz�<�#k��j�f}	�װ:��ft
(���
��|�\�$��U�9)��\�$�pH�/0���,@2���"J<�M\� ��o��=H�l��[���O�&��w�����0S���[oA��
A�G�}4<�E��̺M�0����q����SRq��u)�1�{����۠�D�.ٜH�E�t
B����7�l׭�^�U�&�
d��!ݘE�)�^l���YZ���0�;8�kr���$����+�q���>�l�o�Umq0]ox�d�e
�1�����U'E4��B���v�`�a�t#M���e���l��^��|����˗bQ?��#��(��7�@�|]�v�#M��j��,)����\�\�z+�s\;Hy����xA�@�9D����u}�)0LS�B7�S������=g@2�T
5?���lwe���.h�n0��HƏ< �۬����$D5�J�{@�K��<'樠�W OR�&!�^�ER�
yq�3#d�i��d�^����v;,��֥2T�ڋ<���K�%,
k!
:�M7�i�"c�Y7�"�0�N{?�(K��{��%WH���?��-��ҐQ��Rt�J}����J����Б]ŷ�r�'�$�dx,���;:���_�C=|���&ήQR�[4:�}�8[�;꛸1a[W�j	Ta΢�ן���:d������^� k	胒����V�mz��,�W�..A��M3�P�����!��}w���`������6-Y��6����̈́U�0��:���'�qo�S|���v���ԃգǛ���������DS�<�gz��������@����!�	(=q�
�	5>�\���:x󵜍,����ʀ%ç�ɶ ��h1IF��Y�O0�u�R����	J=���|v��i��.N+AT�`}�}5�#���|�^o�R��k��?�_�:�y�.,3.�!q�
�O8����D�T^�y����Dd�1�8�tk��e�8�f��Y�D��)\�����Qv(�i|���p�w�w�ޣ��dz<��H
�x�2��X`�0�G`Yx4��V��'j�0��P��>�		R�ʪȳ�{�4�a��S�c� �Ƌ�ux�Vr��h� �eDf�6.�X�Š�X��ѻ�2�y���K�&�N3P��ص�nOd
��4�2(�mRl�O0�I5o]r��{f������g#�i2}�Jo6	��!���@���e�΋�($��JY�*)x����@��#���V�"�m�%�0�L�:�)H�8c�ۂ�!�iZ¤�,�,*`�Д����}G��jXI8iD%��}Q�
��d��%Z2[�al-�ZPS�!���iW Ťx㖸�^��
��k�hޟ��0���8�����C�Z8��m�zp�&�#�cLdž�KG�$��	C���y��+D,8�"��6-���`������tn^�mŊo�'>��N�5B���D\���
4
XQT�[ +�
Ȩp���
�q��)H]�/"s���.�x�F�h�e
o#��aX�aFg%�@g�;O���E�5_#V!��!u�h�7���LI9`;$Ak����֤�0K��Z+���:�S�o���@�lOqv/@�{X��8�4[�_��1�l�O��?�}�R�\mZ8�q����(�e��5��WdC�k���6�qi4�P'�w�	�`��Տ��H��u��6���O�>_�I	R��%|JW[�t��*��k��̯�;R����
pfo����B„��ec�-�
��(���HG�p��$�"w&<���t���4�0{�����ӑi�W*"4JQ�n����i�� @��s�y:��0�ߓ[�i<蜍:d������n{�9��������	.�:-�0���y�*[����;y��`�^αk܄����f�c��-�w�!m椏�����>�����Mp�Z
ܮnryszL�1)���R�	y���dsg Ǝ�1���Wo(����O<����n�����Yp^�\���l�exT�=���X��M��z�x��9������hD�#�VE�B�%�Mu�ĥ`�2�q�La%��x�M��GzxP�Omje3��MS����F{3��p6�H9��lH��mT[��!{�����gz�W�z3�#����Hف�6��E>�"k�(�.!�M�-R�:ζ�}�֑��@.;�䳔t#e���Mz}���r�T'�ĂⲒЬ�� xC����F��
T~N�Ȃ��eGh����@�f�4�xc ����o��Ɉ|�4-��Pă�h:B|���
�5�����q��Q�]'N��MJi�������j2�	��@{a��?�B�L�-����V�!����/������J<��! y�}4�����5���E�(!����+�� Ph�
d,Ba):��M���ʐ�J;/kR8���A>#[���ݰI,�	�F��� ��(�&��5^Z����
s��Qߢب���0c�=ۺk�3�6h/A�h��?�������!�f����[
i��8a��t:mC�Vhh�(���8z���?�^�W�͘��	<��L=nq��ՃЭ>�)YD�D���rH�-NI�]d8�0����\1�^��Sװ~ҋ>nr���(o50^�p�u��U�۷ v~�lk�G��"�E�����A��W�ڱ�f�@��Զ���?�p������
�'(US�_�ul�����_W�߷����-�[��X���uZ=��lx���A1k���)t+�"0�O��O�}�/4�B��@���K���H,�<8���{����Żu�Q7����g��W��f؍!��=4������o��=y_v���>8�#��X�[+�Y����^���V_��_����-�X�h�`+ő�V�my�n����*��}�w/�+�I�n@2+��o:�*,p���G���}���~tpd�/��9���%@��@��dp���W�k������k�q��5꿿�ݫ�_�Z�:~��՛�^��?�)��{���1���PУm�g�� ���J�@ �
��g�$Vu�ޢ�o����E��m�l���r�?�F�'�i����_������3�3��6�:�5�_�z�W��{�fK��@9~��M���Q�_~�oo?�S�"��3��&6�S��
��_/��H�
,�6�C��9p�C�8x&9�p��M-Fq���P~"�@��S��3���F�_��[�Zz�-m�\����P\�1�xmd+ee�z�����i��	�2��
,h�ȱ�@�6\��F�[��X:A1_���Z�0�kG"x�N�n����;���<�����D���C#܎pC��:��))�~"w�>S.
푡�������#Ė
��i�0k��r�����-���*��Q��S�	�X�p������6R�R�	��v0y��X�6��Q����C�_�#��*ڒ��ꀚ`@*�ʦ�/[�ck���gy%��)�HE8���\ĕ >��nPg
LP�V��?	���{;��K�2��_F���v��8��.A��<�A�鏎�P��A6(�7�+�Zq�D�d��=�D���0�_w�:.�kLs:�kK(��SՍΔ�+�J��`�
N�H�*�º�Xٔf�Jx�J�/�cJ��^��T�=����_O�����2&}��nn(qfW39�������qx	@�G�<1)M���#gD�5�W�=��[5�up����?�8�^L�7?��>��'���Ҧsҩ��O�M7�����re�a]��*�>�C1@ZL0����_J���La��e���$LP��b�eF�@��ZWO)c����EC�o��O2�����n�_��X��{������g�-�yL�kӷ�$otgb�_,�mV�z?b�Ly'�:lԲ������wz	8�¸�1)ҫ�������IS��"lO�zR����=
T���|�B�|r��L���c\�B>-6lG�`�z>3�����P�F�`>�Z��H�!�Q/�B�?P0�s.v¾\�[,U~�#���.�/o1�U�:�
~�M؝|���䳤�Rb®�rT>fVA�w-c�F�-�
*���$����3)Q|c�[��<�x
�YҠl%�Â���a9X�ɴ�x|C�4�[�&8ֈ�4��/И�:����{h�� �M��j�di�xN�L���ծYe�F�O�th�aS��lu��ςq8����!��C�g!p7�(fթ�F��8��j���yh��8���:���� U�W�x�(Kѐ�9�����8"FLXǧcn�:~���`Ĝ��z?�0�+�J���|���U���(�@3�I\#�����:�Z��<!g0���T� ��qiE�(�C�k�R����M�Y�6g'`��Q��8�q�9FC��#�@%�N1Su2ezɩu9r�dy�y��
'�GP�s�s`r�U�`L��-�Ol�–4q!/V]D��޾���`�hH+M�oIw��W �v�:�!��%�g�SƆ����$E���͊91��
����)�
�nE_��'}ĭH0��/$�%^����r`g��#o_'$����M�2!�*�#_�:�a{�����D{{9�W*�%v"�6��-�7F}�#����00��� �D��V[�rي��*���e��C'��.�	�(�#�0��9�:��c�@:s��|(I��-\BzJ|.�'�g�2�#Sյ7%��[�	����ew��ă�^�����갫u�^�}��pؒ(4��ʱ#Y3��V� �i�R��>�����r{}
���B[�Nw��5؊}�q�=\�Ԁ��o��3)�v^�(�S��Mi&A�h1@ŠX���|��䚝 m�jݿ�$ip�]dX��r�0�-���Z��pJ�8�#$JҦȂ֕��m�g2LܓgN��dQd��L�KU�*��$��T��ն"�_U�b.���\1�6-������xp��nq�KO>d��a�M���S���>&��n��P�R���p�}��Չ���:��o�B*���G���<���S*�����X���t�<%\&È�if�9[�~ط�anx���Y��s��tH��)z�f;�Pv�.�Df��X�N�FNWU���h��IE��+D�q"�%#�1����9�9�<�
~�`t����z�TK[V3ӆ/���0�y��@X��R+5�r/��Dd�Y�l�Otk��<�R��|[)��Y�.�SR�*��I��1��2��������%	*3j�B�KW�V��u8F��0���`Pp�W�J���e�h�4X8�}�6ت��X��B4�ԃ���J���(8+�a����d{9�t8+�'�]�d-�G��%�s���W�H������-�� -w�
h�)em?��i����r���@y��Rg\�+F������m���S�?�죬<k]n�T��)�'����F'�	�
I��J1;�`�C?|\0��R-����,�S���u᝜�#ғ"�l8_j�y#CK%��@���Q8A#;�v�=
/=��e�5^�lա�a��:��?���Y�%U��'Iؤ3Y9����wl�?�
��=��V���ZDCg��]���]�Q�} oyE9���� ��� g��B�y�\U-+�L�V5�2Z��I��~���'2�
��5�Z������m�Rm�՘����y-(;yZ:�f�:~
��c�Ñ��#Ue)jeY��^5�cA���dC�f��C��qm��XYNf��d$r�!ѵF��\j��i�8��p4��X~܀obC�8/o����}Q�o�O�O�ƉʢƔs�ug8��Z8-h�m�A�r_��s�L�MЃu
�����9��s-\f&�T�q�rt�G',4�_�֕o�*,�@m��>m��c\��+2Cq�[`B�B�v9��]��U�2�5�F~J�m�P�p0�?���gϞ)�߃���5�t�2+$A�%�.quUo���.�+�NJ���.ͭT���N�s�Dm�n��Y�/�c0��&�8��/��Ǥ�Z-��(Q�d���X��'
��8Y0[�k���<Ĭ��p���������/����q��t��v=ǿ_��7�G-D�\.��Ș�.]>���D�S�δD�����(w�/e߅���+0�Y%
(�8B�sΐgQ�a��}��b"+X֚i��vR�uݒ��*C��/��.��kh�-ђ�<C�|���Ca�e ���6���Ш[$���TC�lL/��>���xFѥġ��3� �Z����&��-v�}=���:t���?M\e����FY�H�#�#W
��Z��W�"��6e@ܯ�(�'��?�8��t�8m��r
F@��͉+R�8���u.�����vQZ����.b���>&�G�$U��`�j�7<��)P("�*V6�e7��r;�kFA�g�!G
Y���+����&�#V�՞�^��9��4i]�}&A'b��hٵv��PΗ ���ʺ�yj�)��)Bx�	�o��Kr�R&
v%.�dF������Y�#Jud����+-�&��5�vG�� �Nfh�w���J7Hͺ31�V���Uz��C�vOT�A0�&&�F��=��cǝ��>����Z�$`��3�vt�MB2]?}i�/XHk�z�}�禆� )i�=A^9Ҟg��c]�8)��';Q����
��P�<�0��zi�[Tѕ**�W�y�G�L\�|����L�]��*&��w���7���
L/q	*�����K�b����2�o��>�K|���Pݙ�IpJ&����R��d�aD
���>QЩ}���[/8��M�X�����UY�'��j��Kx�(�MR�.�XRUi+x�v���t:�8�s�O�ل�Pr5ƅ�z�RE����;�!�ǝ����L�U�+'�󈗖�˓�@#V$�
ӂ�2(��n�%�,0���z
L!��*{�h;!��s�'�sI-k1Ty�B�
I}� �<�нas��i�- �t�m1���Z�\o=][$6��~oU5��Hp�lX�o��Z�}%��6�,��Ԭ�j~m��cN�� h\2p#@ty�'lk$+*+&�e.�.�[�
�ҋ?d�&?3�<_j�F	Z	��}*#�qqh!�Wu��|���,�ٹ�1��UR0bB�OI�a����#c�5kA_,q�R�DW�Ii��(h)zlW��\�w7��"Aר��0n\�0��^j|���_k���*ư!j��D�:�!�Zf�h��S>�=<�K֊�e��V�+s���Am\'�v3�?l���d��VKTE��v�no�1e�r����2��&!93I=0.o6VR�&���Y�K���
��-�B��fBJ��;-0����L5_�$��*]!m�f�Sl/_�G��������
�0']�X��A�7�(iq��Ï����D=�>�-���5R�E��s����D*���CA'�}T��c���R��9/��'��rNKͿ�ҹw�&��m��-�&0�y�j� �靧���.���7��]#qH��6�����V�W�{���_�k>�P^ H�$��Py�.�@ɷc<��">��<j;)�@���?�*��	=��S�t[�!�q��O�9DM�XXWi�_����}�ꢤjq�5��S^4ޅt&���A�-Ő_{�x�T�Nd�����bǏ)�@a����po[gP^�jQܘY4^�$��b>n�Y�|�=�s@	��fJ�ߕ��f�Y��U����!�67����'�d�Z=�Һ=�7v�̗pJ�Vt�\މb�jZ���Û�=h�4LnN��������V%Ϫ��ִٝiͻ���p�9�5�)��<����!f]��/�B`dx4L�Bh��Ӗ�[�Q|jY����-CLF�R*,���G.�G����߽��D�e�Ӣ3IEu�ۦzlM�����ǭ"�՝�wc��0�Ŗ�m��D�N�A��]�@����j�R1��U��u��ӗ��H�zX#�G��nA��N����ٙp5�j��o�Dӑ]�^:�Hm����َ�&%�L�8�ta=���mM��y4�P4���������dֳ�y���E�Qu-�e*�ׯ�#q�ڤz�����z8�!�5ECfv��y4�4
��:��O�T7�^_V��X�sS������؍[޷���0+v6�I�l��NJ�L4�K���U�	A�)�}���RG�ׂ�G�FU�0�y~�~�� �����юEDt:�5Q�I��\ά����*e޲$9'jU�Vo'�)�?&��N����U�'�酪�V�"M��iU�weq*Wqysh��T���ݝ]"�$Kl�*�me�U�a�)�AB�6�g�̋ް^�h��*�]X~}�N�U���;��D�ҠM:�'Y@�+֎ZUl��s�^�HM���4Ɗ� ����%)-�����Q��f3��k`G�nKDb]ׂ�B��+���d����<W�*ƽ1�օ���v���
�칕��!v���sƛd��R��(g�T��o�!�SwC�E=�	�*��P"��^p��>�߼�Ð �D��>���Nn\�^
sq�yH��k=@�!�/8d��
�0���!?��$T���b{3� :��ʐ��p�<�Sn�"_y)%��&@��V�p���NUؗ�X�]�j����.?��E��Jwl�<��_`]$Ϟ��r�G��x8�M�@��$�ۼ%s_�!'���7�yK��]��,�I��;	M~n���_z�HβkN��A�$�~���tvA�̘{�A��i��a��ğ?�_���!/�j�W}�ы�1JX6���&�F.�0�~�ɒO��+�~|���Q{ӆ\�����ıY-Q��R����xz6
]͕<_�J�ǧ^C
אc�V%5?�
����'iGG�$abN�J`�G�U_�<�`��n�����A�sw������ZS�TE�m���_�,3�T�/�n�$됀�˅Y> ? �;\��GB7CK-n����Q ,O��&�x[�ZM���|s�<߼�:��de�+��s�/V����v�m�es�-��_�+kG�=?�ܛ��՜(�/ֱ���"�7��ě�T���N�����_��W�SH&Ky6>�B��6�~zi�/���;��������>��7����N���41�:+�D�Sw�$���fET�r�״��(�Yi���TgѼ��PGʕ���V��?������s���(g�c�S�]�O���$'��c��,���՜m㯹"H��u��X��I�Wrs�j�6���7T@���W�
V�v4q���ub��/��d-\ʳۧ 92�>M�ϐ̵�H[�mbW���2��c�s[?u�1�d�x���0��W��8����+##	g��L<-�ؼ��H���i�f
���>��/Y���?kf��,>�`��о��uV���bGӒ*h��j��z�R�U��I!�0�-�v.�_�x����7�
2��<F���9R��I�d�cn�,+�S`���@�L��\	A�%���-عږ\zeT��Ҡ{��6��
��4B�-�P�L���#��k+Ew�\Qr9ڨ��e�n���,�BeE9A?*�Бi���	�4����\l��f�+��S�E��0QO��7�U���t��"�z�Tޓ�1T"K
��t�R���5�U19y&¦��z˚��V�M�Bm9+J��um4��h0b��1.-ۅ5iiIkuIMa�Td1�R9��/���0uܶU��B�&��'�I��D,��/ �����H?<��ф�c��їm�J�+��na�/ح��L%�t�a4��9oƒj����z�m���*%�4�����D���|F)7-��{@�\)��:
�gA���3���h�Ϝ�����׉����k/+fEY�nk+CR�j��h<�_�S+��,�d�C�1��78�[Y�:�HE��U��஽a4u�Jj'1�H+WMkn���e��e�(]�z/��qe�%˔T��r�lb`��98�h���l���1G�ѶXx��x���nl�;IF��K��y���DsE!�l�Dw��2� MtKnSz�2���'�mA���s`3|���܇d�\���������t�Ŋ�"��dEԗb�Ў��t�
E�QE�O�7�Qm�̛#W����SB�;en��ί⚫ڼ�_��,'��u����Io8��~�ƙDp11FS\Jf����t;TG�r�Q���<*��9t#��$�4bM�6R�`�2y!HGO$��&�O��jΝu?Wj�ŔJvljw��6�P��������~K�^�^��VRO\Z�{�ū�-���s`�)]�4�^LyH���VW��V�6�:+|�f�Q�"�X)l�%������<��5 �v�>%K���5U]��N��;aoǜ�9#�:�S��|��*��Ai�V�~��t*���.�<���L��]2w�'�P%���s�ȣy�*�c��HJ2���ڳ���jM���=��o4>�'�&���)ʷ�vP�#jJ���DUҵ�DǺS:5�~3MU���xR}���bJ�~o{��'�u��aE �i���}x��rb�<С�7O�R8��苯�P�u��l�������P=�L�wڢN��^���
C_��'�+��	�e8���0���D �
����$HFYP��*�h�%J� ���$uQ��
Bgu��%���<�m���C��Z�-RU34�R�9���5W4��{c6g�4�sL2
΀�b�#+�ǴQ��7]�Mk�<u�Y#j-��dp�ϬJՅ�]�Vjk�f�%|�*�9�
e겷ʘ4�W	s�sϼB�'��,d�(��,��ȸ<���&����DTCTU���9�_T�;v����d�����.4洉��j�.:�%���M�V�I��\zlגP�f�N��-*��c��$�_�r�\?�yƽ]7�*H)��x���I����T�9��0���͋�?�+7�;���9�~�@���<��]��A��?RO2��',��Q1�i&�H'�uh�K��P�i�V��bDVa�k��~�Mz��t�`��[w�D:���יu���,<=��ۮ�^Щ��Q�.{���~�v_��1&�n��E�^�v�D�%��}�᳨����O+��.���׃pU'Z�¼�E����j�����GXwd�N�D2G�;H�W�m�PL=��yL:��{�?�o)d
�nJRb�.I._�K�P��*�L��Ta$e��7)��ؘ���5��^�(���,�]I��)b�U�x<t�����n?�a�M�Ku�S��=
�:D�&U�e���C�v�㓷!�{N�>�	"TR�mm�T���m?�����K��zY��_R%�JgA�;\�Ҭa��U>�6-*�g���B:��R�-I��4�6

���Jz�`���,�zq*��\��9��rE�	�"C�!��`]��pʬ�v�b�8�Y����.��Bk���Uo�oU	�m~D��|l�	mB���T�
E���՛�D
��;�*<��%�5����T�0��UI�x�Қ�G|�1s���ՌѠ!D՝��5��
bd�����E���b�4.����	��8�z�j�:�ݐ��p3l'M��d�`a���~�������U,կ�h��C��$&A#�D�F��^�s�,�ō�ڦ���y���K����`��>��BV�AK4لB�ݒ���_?�t��M�7�j���)����n��_|��I��ж��?ͰR�����A�ɤ�2�_�Z̢�H5b�s�%��0R<����>z
��D�
w�|D�. ��"@V(����X�
@��B�َ��f>�hG
7�:�� 2����'F���MY��~pM��T��g]��1��4�YAn�t�L�T4���J�v�
~UH��zz����C��JQ�ܧ�(r�]젙:oF���tQ�+Ky$
���g�
Z��Qo�Ӧ�#��B�U�M�J�Ec/�Q���SO�L���e8Fm���6�eY9��F.h�����Xt=`:&�M���31gc�R�!��hr6�d-���D��8�x��]2?��2k$�^��c�(%�T~�*1v��<�]W`���Z_���'k��골V�M��3#3)���<f�#�qQ�Ck�P",�D;���/
�X����|�|�,����g=�t�j���.�F_b�b�8{��R�V���]�0�	�쿷��&�̌��Euoh������Dr�(�W�e�jd�0
�$P�U�|	�g�w�d %R����ɗ�!��*M��U������|���"y!�m�S�r� �h����X��+�_���׆.1<��^$R�K��Ũ��xZ�h}�4S	�AIl��uf+7b�}A����2r+cbNR��^��ߕ���~����8L�X�S����?�u*��6��K̅��Cq���|ۿ�)�:z�>�i�@֜Th��[�1�������*N�Q0|p�+tĊ����1�Fn{��(ɐ��F���S�4W��-�� ��ϰ�P:��y��YVqY`�y����w����ћ+o0��ȶ�S��J�{���Y^R��iCɳ��w߫��}��S/����{���M�ӊ&߫�а��S��#�N���?|��HTVӗ���6��ȧ��0dx?���ʥmbz�}Ώ��X����Y�i�3�����B��w��ݔ2�3Q�Ͼ���v�C[���ʧH�(���$��������n�D���UNV]4r�F����.ɻ���~/�7�3����`dR��)�IhqFa1���"0vc>�*�H���I0�@�����J�kM
|A���	L�n$�D��H�k�q;����(pG	<�A����4PgN���Om]��F2�ꌉ��o[G]���)z��*.T(R��

X���^ ��i�&`H����+]��ʰ�s&��X���+!:e�4��!�\�Û��Tn�8H+��w�}^�<�w �$O;��ϤB�.���1ݒ\M�*49�d��E�Q���gar��Y��e
�ZYLm�H�_�n.O.&p^KHƚ߃9���UB�<�{p���~��[n�/�����K56@D�k
��K�#��ʡW��H�+f�#���Q!X��di��d�̵���Q ���I!�Ht1���/�+��_�Rm�Tr�	���u�*R�<�j"=?Cߐ3\��欍�(gI���^�`�
�AD�2Q�0�L���h8�J�b��5����2�
o�h(@�1��h'<�30�p`
#��
д��C,Ё�8�[�rt|䑓��֫�!2�+phukS`��;�'���7
��(E4�51i/�]Q,���K3��!)�[J���ו!�\>.�t�Rn͗h[��ǣ)���{�*&�vI���U����YQ��.�mI�F��ظ6I�[�����"XZ�%�k^��Ƕ�:��}��E.���ę���
6Cs'>1���T""��&g�/��Z���͂:�}�BK��'�Њz�pN{�Q��r��#V�P��uU��)�*t���sF�G�6�W:=�}�c*��H��bcƮ����hF(�'z��R�W�3�xsAO�4����,v�j:23,�+�"�o!�j�n}��+~L"PҰ�����.���������r�v�uo�$�#��$��$�ERJَ���ڀٴkX4C'�I)�T�3�U
S�X�W�Ua�"�Ss;	���V*]��g)YE�z�ʩt-�\,�2%u��
MP� �����fxt\�m�d�:��>�7��DB�Ѻ���{���x/u�åC�u'U��^
jfUk}�Qxv�fC�#�Zx��q��mz���dtz���<H
�Lr�!���qI
�w�3��u�"���Z�N?���0��{������28�A�d�$j邛�}9�(X߈ށ��7�;��I�$3X�R��a��
����;����lI�(�7H���s��NrY�+"]�7�S�`�Q�(ě^�uf�XUO8
q#�zms�h�QY����B��/?���!�ݑO���9W�Xf����W�i\�>/�:��g�?0yG�>;�2�xȄb�R�-Ҧ��H?�M+._���=_��Vχf���~����n��"n6�w��kX�&K��nAϻ{p�Ϲ��IR��,����%��ʚӓ���ve�M�~ñƚE0o���=Ta���ls]6ޤ��b@By<���ޮ�SӲ��B��7jO���c#E�<�%�]����%z�)��܇��?�GT���EXk.n-��s�j;�=8��!v��Cr��I	C��s-'	�^�����5[�#|�h���մ7�?a�_?�@�2�|Hb�<j��!�}a�Zw��,<<a��Gr�Sn�2����/JPF>)installation/angie/models/offsitedirs.phpt��MP�J�@��W̭i�6�'Q�DK��(�g��4K�첻)��ݔVz{��{3���mg�2MRu�����@p�q<(���l@k.��w�S��x �fD�'j8ʓ��g��h=&�Y�# 
v���0vtj�<�O���Yv�ȳ|�Z��h�S4zc͠����
�xK+A���W�'*��q�����'�@�O�VW��t,���1I��I&��b�^��l>	��d~˘�<�)����_�vz�T΃���-���a�JPF8#installation/angie/models/steps.php	r#���Z_o�8�>+������m6i�6M�Mݢ�b� �%��V
�r��7CR)Y�����/�ə��o���o/W�U8~�<$��dz~qF���%�J�E�/�L[)�qAfq�\�X$���$�Ɗ�d�@&�)��䕥(�j���cNW��)!@0gT�%|� �|���� �8<�����)K<�%�}DހB��x�sI���*ղr��B�����T�9�X�`�\��5�e��I9 �y�)�XA�At7�����$"A��`x�	� ��~�S�_)���~Q�Ha]��_�!
@�$�e��Դ��%��XAԂI�+U���h��u,`?��q�[��d�
3��A�2fE�'�(�|��XųX�W
�PoH��U���sf��*Z�'�,�%�^Y�̗/ɜ�b^[�}��v͵Hz��͙�(�
 Qsd�j\��E�cS �Fd0@?�C\N*����QH�?���B�T�'�.��"-g.���71W^��t���������l�l<o������,��J=\$���w��eB��dTA&��j2��櫱y6c��rP��پ�)4�#}��P�)��5�u�/�N#rQ0�t�6��4���jN�(7�ᩬ�y���ɥh���x��78�c���d|�>+V��y�:�*@C��Mm{c��L/��5n�ڱ�z��
b'�ֲ"������
ߪJ�^��ZvӔ�[�žY[�;�xI�gC6o8����Yv��X�M��1*_n��H��e�,��q�k����#�0h��A��c
��y����_���>QT��W1�EJ�nV�hy�I;	@��RaS�iFO�N�+?&m��~�6����ƴ��j��=��<����Q����&��D�����4�J���vsݘ7��l6"�����%h$I�Ie�>ɩ���plhj�>����dGUE�26S�6Y��=c����C̥���wwӫ������Ô�H4֓��Dp���X���9S	���¢�T�8Woa��\�������e���H�a2��T
F�{*z|��>�M'�φ��9�>��4�I�d1h0����W?�� �w��JּáG���l]��Ǵ��@��~V<�n��SÑތ:�3��Eu@?q*��'ҳ��=ܚ@�4��M�Q[`&�J�vJ<e��m��ŞSV$y��;���'2��%��9�G�x��ױJT��qe��&V��P�{��A�\R��i���m<NS��Y�;�ر���\'-�Ak���x6�w�@I&������X��v(m&S�ç���\�8%�6�Z:������ؾ��Se�פ�*�{![W=�um�o}�{�D�".�p�k�ˍ)�k��ti�������8yEf��L�p�_*Lm����������8�	r�2HaMReu@��a3���E�p�
^\��Ѹ^0�.#��ử��du�۞;�I�fi�k��_Hm)�a4�L�� �$��Sb=���},R"!�F �Gd�>H�e<O�bgx�����ڂ�D��S���6��N\4����C�4�[W��E�����33�U�.�Bljs������!�A$�(� HQi�7����U��ԝ@9t�Ck P�%u�z���
Q(��x8���ǸY��38�w��c/҉�7�"ul14#��~�ͬY`L5�J��7� a �̋.��J��?��2M���8��u#��BU�L_�[��J��ƭ4�܋�fX��ICLx�us~�t��d�H+�T��uU��zH�DzA��t	���B6�@G�1���JoY�[�[�v��F����mO�o�O��^�ӹs$��h�m閁�_~�=�DUլ�{}mzFʋHU�G� "[�f��[�m&��r�c�h�Ma򧗕�
CB�d��~�vUy��K\7�Ɉ
m��������om�3�6#a�=�5Uv%��R~W��h��{�־���X�[oML����-���5�pk�5�n�����*{�C�����J�Y�#�m'H��:�sV�;��[d�^�\hw�3�و��yJM���m���1�9�YNx�F=�O�[6�e������shd��1�;ˆ��J�8}-�F"wl;�e�G�=0�U"�+)���7R#�ĩ�G��D6�K��K��5��k���`�VM���X\"��9�����-�$Kc���Vxw�g���x�Ć�k�U��:HQ����� �+������n��.3ϼ�WK�(V[�鱓�m0�n�N���фVJ�u�?��JPF=(installation/angie/models/ftpbrowser.php����Wmo�6�l��`@r�8i�XҤu�43�y����"����E��c���#)�VҮ�H���^y~��X���'<���br{p�D(�FPXj�b-de�D�a.���* V�R���Da�1��Ʒ���8�<�O>�L�
6� j�@bX4g�,�J,�Κ�(��wxp��"Y�,.���m)Ye������]�H0/Y���.0GgpY͈q��d��
�\��E�A��\�F������q8`�T`48�� �0fܿ��w��)�)Q�i�S"B�o�����%$2�11ᔳ��P��:V0&m���
%ք	�s]�	�U��V�jМ'�\$,�YF�(P�X�"_���cװg�EE�v�K��3��F:-=V��R���w�l�^�
���y�[��?���j�h�(\=�ȃ�zN���"Q��I��H�H�v�p� ��1ݑb��m.e��x���B*�+JdsF��R~O�"�چV�Q+`�,!�z�4�k�b����B2��KQ��^��xEjU�q
{V��`﴿fY���()�֦1�l���J���Ȟ�����hW�`��8Y�� {�k%���$'�<A������7��2޻�3
ϭ;�Q�X�(�D���Ǟ7G��	��7�NO�����^<�j����@7G���E�Z�UBl�ܙ�T9�s��w��u����^A���T��7��-٪�����u�.�!��ðn�-0��S7��?��&�A�\7F+��@��Ӏ�A`>�Rӈ�陪V�f���.�-�����V�vlK͌Ŀw�Q�6�M��%XT}�K]��z:�;�2l8ep����dJ��+�|	��}��Ű�Wj˳x�&6�Vf�r�1f�%K�1u��q�̬�#�&�Y?�:�4�a��|B�YF��ໜ���Z�� ~N�������;w�,$�!�5f��{�"Z�\�2���	�?��{��N�PCQ�R��n^C��}#g�9�u�Z��ҜA�+2��#�gSϝ�3�DV9�d#9�i��1�
��ri�J���ò�Vh ������Υ&�iقŬ�+u	��{d��R�1]Ay	6��y.u�d.��]��X3C�����T��m��,T���vd4�AqS���b���_�L�Hw��
�`��D[�i��`:�h�$��d�c��*ܱ�����~-4��ەd��l��U��>������F�Ŷ��k�� 4�v��a�CVU�J��t�
N��]��jF�;؉�?(i��$���"-�Yi��]e.��vf�����e9�a�]V"s�ٺ����e�l�V�]/<�_!F��f鯶����z}p-��xM�/	]$�f�u�������v�c]��W�>z��4ʨ�)?�ޮ�6I/��b��W2tԶ�i{�7�O�;S	~���ƭ��n,���(���)�?��W3K�pjz�-�C��jq{|h�Þ��K����&�Ӂ�\���?JPF;&installation/angie/models/database.php�����XYs�6~�~�f�R�H��r�VI�ԭ㤵��G����"X��6����!��������	��]~�}�,����1L.^���>\-9ha8(��T���N�(̥�Kn��J��kHg��0������a�Rfm��
�e����v-��F�����_q2zzt��ӣ��ÅH�2c~9�W��F�B���pl�����D�sM�__��y���]9�
8��w\i���1`H�P�p8L�\�<�������hD�T�xt<&肆	��F�<{��1��׆�)n�����eu��P:S��5Md�h�i�b~�c
�؆�=
%��'�KgV��o�V�kn���[�U��).i�s��-$)�Bc[�z�:Ճ��M�r@!�����7O��\�en�a��K��
[��.\��^e���h8�|�b�*̦�>�#��r�W[�(o��O�����W_��G��`/g+�)j��?OgfS�c���k�$��B:"8y�%I:��y�[��O��Rj�=Ҵ��!$�;d�W�^���#�H�G��zt�]�ΜqB���,�~���L�l�e����􍝆D�Վ�Q��A�ե�d^�oZb�RƸ2M�~c�r�
b����_S�����q"[U7�ӾہQ�p��M�-��%Ȳ�o���t�k���ţ���+d�b{�����*��Yl����f�%ħ�[i��>'���fy�ӄ{a���\�6A���"��)���k�D����)�Z��+�s����"#?�씗Y�A��%�p?)�Ԟ�C1�T��Y
�r��g܉]n��,OQv��Bm��!�8)v	�5�o�)�L���@ʉ�Q����,v�DF��q���^�V�j��?A�r���욠aj|R��cIX�Ě�(�;7f�5K2^�*�V]	����ƵfjӮ^���U�\���-WɡΰΪ�稑z	��8�\w"�k��s�{�/h�w����g�W�������8��P���9�V?�o�lP�,x�=��Q�M�����dG�؋4�@��)O�ۉ���`TD>V.=�9xt�]\��~��x��Q��&xD=*qʼnKn/gDyu��s��C�O�:����&w�W\-x|�W��,;+��)g��#�wU���:@@T�WL�6n�Ug�/�B��c'PK*+��	�Z�3��	���i'���hWgդDu�!���	=I�P�
�Z��#�v�(=�MI7��QD]� g��T�U��I���`�7��;b�R%����C��8B�*`.Yi��J��x�x����Q4�.��t�M�+�n�j�E����8s�5�<�V<�v�TҘ�G��gGn�tu;E2�c����M�Q�l(p����.�f�	."q��(@�6�,��%���΀�
wq���˜c
��x�ܗo.=UZ&�.��(O])Y��x3���H<
�\TVD^r�ܡ4X���pϑw9���#�#��!C���v������2��V�4�`��S��q������:�����.n���K
�ԛ���.ϮN{���3,�,�>וܨ�u�V��joۯ��UY�q�}Y��������Ny�_v�Ы�d�m����c������޷fʹE�Q3�m���t)	��j�V��t[��۪�V��� M��F���6��w;� ����E+N>Y��1!���)��
���x �d����%W�y/��wPZ���;��)���='�,�9�y�1춙����6��#Fl㜂h;�<j5�����2�NVb�x��L�f'�H�%���tG�L�\�tT
������*�dI��Ζ���S5�9�0�T\iX�5�U�^K�]D�Nߺ�]������u���m�_��Z¶��Q�
�qV6���JPF:%installation/angie/models/session.php7�	���V[O�H~��Y)�m6$)�J���@#h�H��>E�$�x��qB���3�K�h�}Y)��9��|�6��1�3��������a#(�$*-$�\��B�3
s!a���<&�PA(�i�`���q���H��d,A��l����h�B�m$_�.�_^����a��X$L�M.)������Wa���Jx��2��Gp�)J���|F�-�+���:�QJ��$��D8�)F�;
n��B���O'�&��"�d�ʺ�'�iD{��o���M��X�W&9�%�<�i�^K�))��WL*8�4O��ч��jjN���O�ֺ��נtta�4�y)bR����B�-�퀛	��;W(�3�\���Rl�•�R c�
�T����RcВ	��c���*֤ޱ�ȭd��a���쟵W��:ih�5���*�Z!�B��6c����{��K�Ҝ�s�	/x����B�d��X������D�;�m��Qz��f�6�U�?8�x���'
6/���S��;��������P}	y����yl�&��s�6z��H�c��S�׽�r�R�p5����Ew4f���:3e���󲴋���w�<0��T�7�pzf ����._�~�\u�κly����B�Ԓ/+�z:nϭ4
E���7�:�K��#I���Jl��˜<x��.�=n8%�]��4�TZ>�R]Z�4��Y>��,�LC���L0�*Acu,��.��S����eJ��74]�B��NYmm|
�E��`B��x��x<�M�����kp;���.&w������I�E�*���㥈@ӥS��A�aH˕�e��9�3�?��&�?b���K��i���+��� 0*�1)rS$�ߐ%"Wt�Q^�"������f�PͺQt?�[O/mu~LM��6��Y���2u�d>�X��y:��p)�Q�j˸�H���m
�2���5A�ɺ�����g�,0b�u�an	�De�n��I���+��5(�D��J���c�T8G�4d(��ޥ�U�W(�ǂ��A�F������z�v��섭�u�JPF;&installation/angie/models/finalise.phpn��M��N1��}��c �XMtF����k�i/374m�"1���]s���sw�;/��H`�j�x�c���9��L�,�
��.��j���Au|�H&�h��vD�D}VX}�l�q9��y�ʖ�,h�N7�1p�%<��
5�N&7��d:ÚU猌X^�):F��޸��J��eX����b��Y
��m�d��(�~��
y��B�\
�i˖t1����y]
��@3�[!T�Q��_�&��V�~�����.X|�#~JPFC.installation/angie/models/base/offsitedirs.phpc�
���VmO�F���PY�6
	�t�zW�#��h�B�TE�Z�
�blkwD��3~�;@����=;/���3�?���S���g�x��A�a2����6��F&1�@��,Qp˃�E
\s�(4Jp#B�]�w/�-��B#K�U%��|Mq��	T��"�I�T�nn�[����O���a(�yq
߻p���:I�E�h�1.L���d bM�χ�p.b�x��[\��b�Q(My}��a
�{����E�0���x�%�P
�bYB���ߒPD'\��lF�
�� ���C��V��,D�J��Dv���X�VP�e�6���q�U�N�St��1	$��Q�ьGZt��Nտ�yȣ�#g��,�̥�?^�u�Jk���8��WwM��EO�'�(dA��S��Zh��1&�0�� QR��ʐu ^D�������d�AE��V��LF"�TN�қ���W��›FC��٬��Nb��j�>��g�
6�t���Ꭽ��ré����SE�欉{�.�Д��p�9���n�����ɔa���d��
a��1؏R��\k���NCfߋ%��F�J��È*�O��6�1B&��;�~`.m�čY�ۢBH�P��StSu���,ϖA�o��i�,�ze�bc&M�K�L {�YB�%�%BSԔ����e��t�*ӛ�ݤ}B%�B�M���}t>$�g�Ce��v6���UNY�IŖR�6O�]�)��<�T['����	b���@���&Ȃϟ}���ή���`|�>O����h⟍�����A
�K���VC�ҷe<KV�8]�b
�
v���H���D���Y8�&]�c]
6�PXL�
��Qؔ�%'7D�)}]z����*R;Σ����������s���������rp�gmZ�RT��PZ�t��M��d�"�o��?��x<�*�]�b�:D��j�F�p���������ϖ��k�ׇ�R����ӛ�x�7=��H�v�s"�
�c�<�q�8�6���_0?�~aȅ�i�S��2p��X�������Úf�
���O�v��-{��3o�F�f���y�!峽1��vBmJ�c�}����[���E<:�����JPF<'installation/angie/models/base/main.php�����XYo�8~��0*�M��}ڤi�����\H��A �ms��$��h��w�:,�rΗb�b����p���L��֫Wmx���A�p�A	�Ar�cɴ�#P���q,aļ�4&���q��LsFsp�81��)"�X9��u��O	�	@���f͋��������u�no�����wp,�i0_7����*N�4�l:�od��"�����gq�8MG����KEv��4)@$2o��>�����:_��}���/���m��Hi�<
bQ�G�σ}����#��r�gq��0}�˒t�THt��Z�h� �cNUR��͘ĿQ�Fqt$�`�c���Q���s#U*#b���w�9c�F�9]��r8G\�]C�!�O"S�g`�'�M1[(�Vm��v˭��tJ�����B��0��KޭQ�2-S�����f��0*:��K��
��u�d�?���
�*+}�8B�:$�ܶW�r��˗�P�R�H[��b���"�7���4���̃��e��^c�zqbX�F�Z"��Gl-��0���JtU�w�W��})5L�&G4�)�H����DM(n��5e���O�W!i�iE����,��S�Tը.b��4R��VqEtk��b�@x��=����:Re&yT0�w��Z#&%��yˌ�JM+w�*����{�$����d�ig���z*��B�x�V<m��P��|��{�BI�>7�~�B���Sg�����Cg889�M���2y�_G��r�0א���F(��Rb����*�T	q�f�	�J��]�>��х^r���E�hܭ0%tb~�����>F���R܆IrKL�"S��,�W|^XlMc����J����n5(�ȸ.K=HU�Y�"{?�3D��׶�����gx����_N·Vwc��H��X�Sh�@��}{��YT���-�2?��}���Ӟ3�?��e���mx`=�
̴�n�J��s�����g�xQ�:�'{������~9}��T?�z�O���9����<��g�֚X1���&'�VÃ)���΂͘@�"z�ج���K��t�%��TOm6��uϜE��A$Lm�N�J��B�Q�}�ԋ�@pP�m廋M�[+�/x���9kK�N�Z�8��=!u`��0
0]A�1�\���$�zfmXT�D،���Gi8�ҍ�n�P/N#�(�V��O�;Y�w���-�h^��wHXe��K3�4�G�ݕ�aIf6���"r��g�Ñ�r����/(�����$y|��G�8�b�Z��Tv�B�8�"�ɄK`��Jh13oY	"�x��8�L7��Zذ��u���kuHк�Ʊ_��?5R#���c��ZL2,
�ѻ Sw�gIr{9�A)p���r�|�I}�U�NHe�A��^�������A��X���V���`-⿼�(hj>�)���Ni$&%�u�23lr�_�2�g1U/q�d��$u�Xۿ�&�������f
���	�wSP�]R,���VЌ0d�FnCcĕ�l�׻��vRd�T�ѶB\�4��(�����յ�6e�Pw�&I,�E��|��IA��k�ϩ�u�����z�8)����Y]z��+��f"jZ��мK{;pa�s�#���?���Et),1���4��x�a��ld%2f+���)��	��]N����
B�l��P2�����ѣ+��c<L.şɭ�E�
*?��CmJ�u���(۶Z�v۾m�JPFE0installation/angie/models/base/configuration.php�����Wmo�F���T���pܧ6M�I��.ir�U�B�=�*�k�����ά_x1NS��<3�����d�x��CapsyuGp�@0�"h4Via���Z&fJ�TiB����b���r�8,ތT�����Ю�#	�%�w�JVZ����Z�^��~��nd�P�0��heT��H�>>��يd��a��7_�c�"��tJ�9�|Dm8��m��"
@�r��B��C�9|��84[,J�[Ǟ'��jX��p�B�΄���gr���g�qH2n�� p���Хyx=�`K�Qh)��\���H�r-V����N��p�2;$f�$�t�w��SMuP];0H�Z��@D�
��bZBU��r�
�nG��YNi2	sG~�	d��#g��Į�����t���*��
��%�����/��܂�)!7�P�q\Z~���$�\��%Z8ί��\8(�*i����j'Ad{Xګ�(7S�~\xT�n�B9�ćPDfĽe��?�$ո�L�J�WЬ�
�=��
�i\�G��6�K�ET"�҂G%��Œ�t�d<��\�-���o�%�*zK�L�jf"���+��!� '�G��
��i�uIcw1��m6Y�h��0j�s �5���(�� \=&$=qXL���Qw��fC��~02Xc��-3�DWP��"L�#� -<I�pY�Jr��D�w���**5Mo<�����>��c�����X�k������v���o�t��E8��TD�=�)��y�7i��^�U&�3�dձ�6�&���~�.���4�J��6���u6��߼F�r��i�b؏��7uŽ��Sơz2�"�I�����Q$��4�1g�*<�}�MBW��ă�U������o���u��\�'��:=�\�C���K��x�2�iB���LE!]�S�����v7��Ϧ�K����p���
�_��L�f�*+x�Vxz�ޒe?w��!cy΂i&OY�7�%=�����Z�(�_�]��k2���
h	''���[����O�Iw&�����DP�
���K&NQ���ߤ<2yiC�
��C��n"�-�D������5�yl�A�����e�>yJ�Y;sY�S���X޵�l���m���Yt���4���Y	�e_�)�߮��xי��	�[,�˄Z\~���N'�H4{t)y�e��؜]�t�*[���HVHA�ed鏬C����}��)��x�JPF=(installation/angie/models/base/setup.php�	�"���Y�S�H�l�͆*��lv�YB`G֡��~)�X[:d�nfq]�o�<��qwu�.Kӯ��Mw���<�{ϟ��9G��N`�c*�$WZH���
e�k�
	�90��WJ�4�`���-�oE�7W"��Լ�q��8�nޅ"_�dk8*
­�/^�u�勗?�(	c�2�p�-��E�
{^�{Yi�L��w���g\�.�	.�{�xǥ�}��
��7 �y�ߏ�4�x4��󓓷�`����^��l��d��mQ0�
�&"��e�_q���_4�"\3��ȿ=�숅1���
b
��l��
���@RC��I���C�(ǘ��:��{����M+Ӱ�dE���p�2�"��r�Mb�+�9�lN���-�}�L��jK�d�"�f�Bi��oo�
´�B#`<.mx���6��TR�v��=�jU�L��wٮ1Q�z�M'j�u�76D��3����`Y�Ac��6U���:z�jN��L�������R&٬#��MR<J�t�XS)�Dh��Yx1	�S�x,w��I�ɢ#J���=^��߽��s#�G�MAe�o<�La��^��2�^Ek2`<�r�=Y�҈0raޢZ�H~z{�s�Q�Q����{n���-o}+�<O�m�k��W�EG�I�
Z�R�4��i^��D��y�ā����r�dT�HnL�j2A���C,�>\�WS�֒��'.D�a�H�a���mʓ�^�y� �V��o"�I\�
��h���.�m�Jz~Pq}
���s-�
��eb�(m�B��e�OAXH:��g�@KP������[�>Q�ɽo���T�v�t*J�SN*M����|�Ƞ{����(�hpo�e2��G
�R�B$��9n<}��&�"��9�1��:�"�����4�4y�������a�1�&��ļ8�yx��;!�,�F��@^���%�KK�2��ɓ�����mj�!#�:Ń*-��n6M��I��ݝ��`ۼ��̳�~��8�*�Fˎe��U�2�Л1��(�j��]F�ߖ��rrO���6A�w�H�Ј�,�w��VqFNr�� J��yX�mX��ׯ����OŐ�\�m&KL�l�4�I
����&��pqz� �}}�md��})��͎�7뉴&tU>|/����Z6J	�Qd�r!HQW�={�V���_]/������pt���r�������)G�	9��s&o�<��c�x�Ӕ��̙Z3�L��6c��OF��^,����(�@�xÉ�$2��do�
�m�]/TAN�����_�S�Sw-�2�-��}�e��+�%z!�Ƌ�H����8-x�<�)�Z"�������
����Ԇ���i��.�X^A�=�-Hn�oD?���O�N_����_��W8��Z�����\+H�®�k\e��;!��}F��7:�V�jY1�Vr�us��)s��Qfr�;��Pͱ;%]:��PP��gd�Ӭl�q�V�F�E���O]�)Cx(��[a�|m�Ý�O�JUֶ���7ns^���o�YY�{�Oz��|����JL��E�0y�#�	�
7r�eC��d�E;I� �1pY�)�k2ړ����N�t���!��7�>L��vp���z�ǘ{�X�;8��Ă�+�L9)X��M�~��s-;���^�܎�DN�+����u'I�0�x�K�x
?: �Fժ��uKI�m%�[��^�h�E��(%�&_�2�j���ф��{�WF3�E���6q�b�:�5�,�,a�\Ф؛�q��^h���t��C�R�[C��4�F�6�������rTK������}���hT���ip��͛��z�V��~Y�)2l�o[����_3d,R�r&ӄ<�GdS�C����[�&pXb��ģkfYod:Q�V�
j�V�3u�v���K]H=��u�*g�]�z��{��,�4OYh�v7x�`�뷛��<3)�t�/;�N$Ѻ�7���'MkH�Cs�!b�AM2�S>G���9&i��Bތ��	k�UkK�Ԧ���+\�!��Ա=v�f�z�2X��2������P�	�-�B�Ό�+��%�TX����g~�&x�o�EA	�߄����@����:�_K~թ�����r���Zpt���i�>��n��J�Y]!߬5�5��C�OȶW�m`���v@S����&�32h���F��s j�C̥�HkwG]Hu�ӳ8gD�c��h`��f�ָ�������>p�)tg���b�<�y��Z��!D.
��vT��U��=�M�<W��N9
�s9��G+5t���N��T)�n�t�I+P��-D�uȏ$�ӈ������l84�XiD���}��0�&�{\��+�`�ن'V͊��5�Ն��Ĺ���5G�Е��ԣJF����c�\e7�?�d7�� �0�$=�R�W%�gǸ�jnC��
�9a!��E���ɖ^�JPF@+installation/angie/models/base/finalise.php�����V�O#7~��]66�{�h���CG���9����f���p��{gl�@�>���7��|�߫��&��������<G0�"h4Via�*��ZVVJ�R�uB��|D�Fa1��f�K��̚�{U(�i�+��5��Z�;KU��r�[��~�����ɯǧ'���F��*��/	�A��FU�.��I���f��)���/o��K,Q���K2�u0>�6\�#��
*@S�$�2\��x��}���b6�C&1�EQJ̘�_*��B�S������,���)�
*�tU���bZ�(�*G�&�ÍzD����(|�3�1��o�eHC�S�\��d���#.�]�����l�yqus?�]_��W�7L9�)���h��܊4E���D2Hd)a%4��}XlDE���b��g����C�I�,��h0�/�m��ў�'\&�*Wr�V|� ���{ƒ��N�|Q�9Ľ�HSÕV ��U47��\A|�n�&�5����;���e�g��9j�vb��0�벐�Cg�]<lQϵ�gp�X�!�uJ�?���'Ī,���������m�K�]�Ѥ�J���q����MQ?�=k#\Ym�K
����wрn�]#s"����ƥ�T��0Z.Zl(��Z�k�0��f���*ܙ�g�EY*�7�PpS٭'������7�b.k����MU��Jh-���#�Х�3.o��]�#X�¸���E��I4h��O6BW�Q(a�к�s�r�]Wr�
���u���	�	H92'dS/��;9��tJWo�^�^���'��E9��$�k�#-،�p�ޗB�$Ѿ��z���8����?n)RN�u�a���^$0߈��������M�&�=G$Ǔ�4���cpN��}fM�@����Y�宐�sJ^ ���/�V+�3A�
�ת^��~Il?p2�T����DǕt@j���B���A�d��$�P�R���i3��-iwis��������H��޽��d���}���,2Y�S�N��~Cw8�څ���^��ҢH�׵���7�e��d4�œI*)�~�����{+m��!D��	��?C�z�0���䴏��'	�-|�����G����u��q_3�YU�I�+:(�t;�y�Ê'A6+����#�v]PO��fϲ�i�bS���1r����n;�^����x���JPFC.installation/angie/controllers/offsitedirs.php�����T�N�@}��b"ىੴ\	4"@i�>E��8޲�Z����;kH�B��d˞۞9gƟ�,��N^�M�ЃY�`�C0h�6�	��r#r�6�`��ȁ��Z���%o���<Zn��t�?�('/[",V6��҈e����-���Cog����gZ2�[0"@�չ.���<c꒪�����.��*4L—bA�6�����$�C�0L0
�8�����a�����19A�0�O�rFK��*M=_�0�ޡJ(��>�A^���Wt������߇�[!8
At�����R!	^�DQ�6�b	��O�
���3	X��m�ŭ���q��T˄Z��5_a�|�}h�L�ށPy�zKtqD�����Q��d�y�B�w�+c�ۦ^���*���Q1f+co�
��`��چ�h�4�o{{�8�:=���ƣ��||}=�\~N'�����%Za��]Y=��b�%2��H�?�VsT\'7�j�AW�_׾g�
?l\�p�b�V��J�cfqS�֝����Ll�Ol�3z�*xp�c{~F��\V�����_����hZ�͙���{�y5�-����P5z���n�ojUQ�B���F����4L�]a�]KBKjh;�{��տ0<lk�^.cƬ~V���ml��{��U�f(pT��S�#� �1�]�Bk٦�rqU��D��u�JPFA,installation/angie/controllers/dbrestore.php��
���W�NG�m?ũ�4�B~�)�%v��
�H����=a�������7����޵qB"�RH;�~�Ι��rR6�ww����EZЛ haj#7B�S%J#�`��Y	\�1G
�Bn0���{�!�� QdKʭ�%��nNK��1��X����\(1�x����������k��D�\��С�Z�r�K
�KW&s�r�b�������T<�_gCb�U`�Qi���=��rJ@��~���H�El�\v�g	��@&0��4�)��!�q���Q2�Qu��R�h��H�b6�l6J�{4+R_LÕ��f�x��{\@��v�D�ֱ(ʙi��D����<�0H)ㆿ��
E1�AoW�/�7�KL�H�Ç9uq��
!�tׯ�!CbNK��l"1|��hC��̢D֏)]�oc��5�
y���+QQS�,��`�CI3V'ix�\�
g�5�RR-%�5��D����;���.�?$W����,j�,�-m�rľ-W�Ti���\�Hp��Z,R�a��Ei(43UX�'{�	��9W9�I>�Pu����3�G�=*���
M�\�M�H���@S�H)Ej�̅ք�?N0V��|��ճ]v.\��.$�{��
=�Ï���R�u�n0rh��"�Ҷ�~ SN�eKw����8��k����p�	�-	U�59��ԿP#oǨE�|]*o��K��pMY���ik��}x'�6 G��r4LC���L�w-:��i+�ʹKOM��;%��Vq���U��8�"�
OK��U�� �1m
��o�n�F�:�\)�����=˿`d��m�������m��m��j��/�&�@�}L�t
��t�:�ǥ\O�8V��o�V��'_X'N��
�������k�{� _}����p�-�hU�I�T����M�S�o����R���k���g�<��Ai}Vk�Z��ڬ֩հ֩�0�QW3X�BFf�n`U��hV�r*�J�Y֎/q�rfb���l{5��A���DתD��L���s�Ÿ�`o<�W�O��^9�{=��1��O��X�����g.𡂢��@�%��@��p}_��S����ߟ�4SW.H����(Z=���5Іuf��
�����$�U��ޣ���#���c�i�fu�c��dY\\v]���tE
��B��&���/JPF?*installation/angie/controllers/session.php�V���R�n�@={�bH�Q95iHI�P�U���jY�
ww;�NA��Y���,��y�ͬ�߸ډA�/�����
X�^B,ɠ��H�kK��j�:��j���X�j�
�J��a�cga���cTV��Jc�S��HWu���*S�h8|W����kU�Fz�?��h筳mc=������?��$��v�<�'$��8^����B����,�>��No'i	��,�Bq���Κ@�i��;-�4%� �,��n��N�Cj��"a(���5�B�}1V<��b|��6I��#�k��/S�7Km�,g�N09�x_�Ԅ*d�6%n��o�4���Rކل�%�}tq�Є�˗�܎��k�=��;�0,�x�%6Y^�;����dP5dӭB��p�N�|s.���~�b��g�->��LY����Hd)�+]�~�o�vI��]�z=VL�{�JPF@+installation/angie/controllers/password.php���}�Mo�@��W�!�
%꡴�:
���PEh��
�v���;k�9X���y���O��k�j� ����s�`�E�h���J���Z��JÊ�m��<�{4�52�VG��+�D����1�gJY�A*�H�c\�G-7��Ǐ��Wַۭz�վ��䑊����I�ѨTe�2�,+�Y��78}�!&�Y�lE	���{������M�M���	��_�O��C�W]��T�{'	B��Q%V�8F=��Ai�f1���w���u��|�Y+�
�^�����ikH-ti@+����o��/g�b1
#���gVnl$M�'�4���m�Y�ݣ��􍘉�/|�2�?:���\f6��˫3Km��F[��A��^����#G�N�/�\����+����`{�3˂�BYI�9
����|k�O|��x��h�_�+m��yʭ��ƙ�#u:�w{��k�_���x�;���e켴�KҰU�R\�~&��8N��V:g����JPF@+installation/angie/controllers/finalise.phpx��eOMO1��W̍��K�d4�]Db �D<�n��mh�����%����̼�xx]�h$0B�Y�.0ƶ#D�L1y��x��؄��g4R��Ug���d"��jO�H���W��[�3����Y����3�|8�i����U��t2�O'�6Fu�ʈ�-�s�S���(���^�(r��_n>�$G,-�M&���G���� O�y��RM;�H��j�X��`���bx/��"���ܻ��Z��5ٔ�9���e��F|�#~JPFA,installation/angie/controllers/base/main.php�1��uS]o�0}��}�D@KA�i�J	��M+�+r���ڑ�dES����V��K�s�=�:�tS%�FF�mןW�®@�* 8�:�5�Se��u�s�X���(T��CPB~��1��F�'V[���KB��pT؜	[��:��]"����C:�L�`�Da5�pw	�d��mi+m=���A6ZZ	4>꯷�`����	�������=P$M��xPe2�gw��"#A*L�d�C}/�	�j�n�=n�2���$��~12͈��Ν X�P4���ӑ�ͳl-*#�;hK:f2l8�|��5w�������.��k�Wwr
�P>�1�hӔŤ�y���M�x��B#7� ]�����*O����_؛�|�.X��O�Ο��Rt�̣�&�}�^��?��oy�o�I�+J�(_2PF��%�7�Ÿ�O�38[a�JPFB-installation/angie/controllers/base/setup.php[���uS�N1}�~�TB�]�
O] ��@Z�TU��Nv�۲���ʿw��\��/q�g�9�xfj���؇����p_#8�,:�-�B+p�
�a�-L��`��b��E汄��'.:�*ב;-5q�sC��BTc�k����=\nNϏ?���a,x�%sp}�H��i���7�����\�5�W��2	ߚ	]�Mw9C�B_�=��$5`)y�$%N��2K�ף��0����'I�I��a�}���ZJ���z�UI��e�7!%`Z�i�x�#�Y�ZD�hp>c�e��%�m���?�\ûH{��Z���C�r�8W�O��r'���M�����3�Vxj�ڛ����F�	�ƮH��r�
brc��T����f��ͤp��(��B��E�	��
�1�s���Z�O��Y%��8�l��hbg{��k^D����Y���"�s�E��ѿ����0񝚫dgjqք�����}��"g����x9�Hq��:�t#}0w��E
��}��~d~�$�Y�#�i|l�򑶢ť=Hi1
��i���QK����W|*�"uo֐s=y��zn
��C���Q~��O�Mn���JPFE0installation/angie/controllers/base/finalise.phpD���mS�n�@}�_1H�Q���U�Ci��R���hY��ͮ���(�������m͜�s�{UVQ�ݎ�
��~:�<�V8��i�
,7�rPhK��u��Rl�7���C�F\2*?Y�����i�y�
�l\W{#V���+������0�ԒYx�����ҵ�z'�G��\RpT�翟��{Th��y�$<�4��u{T����(�B(̓x�=L&�,N= ��_�����=��-%���S(&%ƝC����F��@_Ԋ�~r�L�U�F
�6����5����ft�t���M^�e��
ߠ�Ja;w+t���}
�F�E�����ܝ�x��9s��d��X�-��4h�JP0i�D�^���s�i����cԃ�
���+t ��SO���$�U�_\��Y��R�4�VC���찣�|L~$���a_���"@/����Nd����m����
UU���xB[!%y\m0����T�~���	
m�!��R�;�,]��ps��.���V���C�5]�p]���+�SYH�q�ٌ��/Oqjr�Q�
.<.1���JPFL7installation/angie/views/database/tmpl/noconnectors.php��	���Umo�F���Eg�#M�M���XGl�ͥ��V�^`/�ޮMt���~�6� !ir)B��<ϼ<c7��Bh��c
��tz6�:��H��r�&����f��iޭ2\�{�A(Y���k0����X$��K�9��o�9@�9g�Y�����E�+#�����V?;=;���42��,h��"]�i�-� �
���,�~�C�%L1WS������L�u~�R�	HtnhZ�f<a��S�!S�)��3�v�iG�2�N#����( ��72��yC�
]��El<�/\'X��d�V뇱�����a�+��ꤊ��J�Tbo,�U�
�Q�V�ٟ1&�̽%��<�����8lT�i�mV�����/��?1�O@QZl�U�DT�e ��*	�G_k�V�,_�L�}�/.��[�ovL��Ag@�ɐP>Z_j�N�	z�1M^�a��%�H�q��h$�m�;^a�J��n�/�%����n{p�>%)�2]?�%�-�𥈱��*��T�:\��s�jk͈�C��Y��QA��z]����*�"�ݷ��ixW/�����G�y��!�'�l��{�:��ݑG�f��>1-2:��8Q��(�v�s��(h�Q0�%�_�7�+e8�X*S�oK�(���iT�CG䏱="7���ӸD�y{�����N�� �;��[w��(�{Z���:�C|�/���';~��vBy��R�u��R|��9��іA�1j���w=�vH��W�� m���_��.��q�!6�YHh��__%��x��=�Fw#�,��-d��=���G�o����>LT��I;㞾W��'��A�x�o~�o���v힧�$�� �e6~GX�	�݁E'�z�y��sn����;JPFG2installation/angie/views/database/tmpl/default.php��Z���\ys�H��|�^v�dOE���Tֱ� �l��9�RE5RcI#5���|�}�-�	I�x�u%���w��������^���/%�R:WM�HX� ��1�\�oy�\
�q7����uOd�Sb��)w�1��-3z���<��<x�o	B���"���zsߺST_\G����%�:~u�:�1vm�����S�
P5�Ѣ&�e[qF��s���C|l��t/P+|yO���u��J6(�C�j�d����P(T��HG��i�ãw%f*��=��OtlHa
|�Ȭ�)b��H�d��tB��E;��p���/_`�A�U�[BacN�ڙ�ƍ~(q;U���u*˩|$h����[��
�������?�H��<T7#9�� h� =G�N����Pj(�RS4u����z��k��{��}�B����>�ll�C�x~X��_��T>z����WGz���WI���dB�>x�!��*�M����p�뫗��Rr�]����ՉG�R��e��\v��n;��"�N⫁�=^f}@�@��Ip�����!#�B�<�x���a�k�1�&��?%k����t2$��
��0���J�-1�-�$X�ZUj�V4��iv5Ή�����g��*��t�[	.��Ϸ:J�&^%���J%�r�6)������Z�%���
���m����c�����S1i���4�6]����!b7j7W b^��ڄ]��M�k�D�.,~�wan�]J�*��}wB�RWY���El3@E�u��A�{d�����I}"�!�A���STе;#�h3��H}���<��hH�3�Bg ���G��	Cx$��al��sd�:���h.#?��	��L�5F�t2�r�%���ޮrm|�Z�-���S�ɀ�9�u�;����`���y .�4����+ǥ��A侊P	�ڼ�t�*j�,��R^��
0s�ٚ��(qMⰚ�٬<�t�V�9�~R��Ӈ��.�+��
o2���:1�kx�D#b��2�I�ɞI�h�����o��Q���(q�8tkB�)=̎����1H��š�!P�D��v��8s0ʊ�C�g��pJ)�fz�h�K�?&�nj��/�4��N���lV	}c��j��r��aU��l9\BTӉ�O��L�.��$�`�/J�3ӺG�y^�Մ�ia�2Hs���M+i秀@��/x"� ��yYH%�
*���x����+G7Dz�o����2{qV�~�%�$9=߽�� �-E����y,�����_if�t|���we�h�>��`;/���4`?�x�ߦI�6K� �y������5!yY�Ɉ]۔AX�dLhaΓ���r�Q3b�8g �Uk
B�4B�P3�p��2�ak{h��f2'�F)����-���C]��+���J]H�FQUW��&����& ���BC�5�dJ
���`_$f���|Y���W$����4;WB"��N��]��_o��֮҉'�u�j�+�5Ɇ���Hwl��Y�$'x[��m��L
#��:-j�FH��l��r��M��]��$;�Y$�c���A�d��� S#�+�,mg%�ED�⦌\ǰ-�ʯ�f�QG����G���!ud�8I�	rсY�Mw&�E9�X��\:���V��b��<~�a˞�� �%p�W���%�Y3S=�K&8�(�#M(���&�P����bp!�����b-$ɨ�UJ�y���V^�1~X��U��E:J>岥��„`���'�VZ���ח�J��6BIyf�R�n)J��:��
x�L�>5[��6[2���Ťl"S1�-_o��vHS��NC�*�̰��l.ފ��P����AxA?MĪ�Vs�c��e�d���
WW:u��a���Y� <���dW[Xn�L�>�`�(�i��a��Gy�/��PІL�R?)�[�<y��'be)����'���2��h��n7��/F���)���XA��[�\ۃ���S�A#l�]|�f�l9j��؝�*[Pn��M���_�]-��2�媊kZ-���7��/��6v&~���6������]���������yȍ��7�(8��hP[}̉�8lP��$38:M����)@
�7���a�8DK�JuZ7펶����x������6�լ?���?,�&E�fu,�tLk���b���~�%D�@ӿ�`&.K�l�B�Ǯo���e�[�n}w�Y��Cb���Ն��ߒas,�|�Д�ǹe�����X'�Flb#!��.{��M����_��@,���d����1 �Z�mI��/����
�Va�)u������˖�Mi8[vgCS(��c׆�r^�,�m�o����8�v��)�Ϋ��󑑞��ٞ�w)v���}x����db��xN��6	{���wm�?,G�a;�ߥ���#��`#�pኢgE�L�0�	=��?z��e�|�6j��:\���������9�F�}�ak�Y89�bC���<,W;��c�
����]�oC~�s’Y�o�ȷB��N\؜��SV~���-������ju?
zݾ>к����5Դ�ttt�$��3�X�:�n����=6���f��D
��Y����
�G#-l�i��F:����b�s}�xu���;k��	!��H899�5{ȃ~+����9�r	�X
����f�EdZ@a���V
?u���*�x��<�ݮ�F�$�ׯ����V�W�'���An���<�9!3�bY$۽�l��q7tdٛڶ�Lq:q���Ŕ�{-�y.���a��G�@̐1�:E��,���Ѝ�ک���Ģ[:���{�6,o����ԛ��p�(��������Wo�z[֮�Wo~=�'�'zr�iئ^���	��6
Uc�'�icKVXGb=id�l�O�bÝ�M�m̋���P.��z�5u�V
�bG�YۊG&F�x�\�d�κ#�‚}P���*�-���iqGA����c�1���G�5�p���A���R�3��P����h.��ᶂ$�gje��QK��G�߼���}����O��uq
8w�T��b�.�U4��_,��`��0?���Xő���'�Y����zs��*���\G#��fl�w5vگ̙��QC���=�P?75�O�CD�Y�������:^L��u��\1���M�s:q�!��uA��Hb-�H������[ P"���Zl��j!>\��#�ͣ�h�]jJ��M�6YN�Vc���u�h�?7m,}P�������*���L���R5�}�y�A�����Hl���nv��I6T�ed���Ćb�1�a�,#<�20�i�4�E��v��PO�7/�ux�R5�(y5:u�������v�;����DY\Lޭ�o�ð댰i�hN8J7�+����̠4��3�H��e���c�;�;HWo�J?�|��U��F|&{=ׇ���=9�"֭#��z��v�oʄa�"�,!Ÿ)���Pm^u��~)a)d�t�{sz��vBBq׵��;�˾���1��X��GE����ݏJ�&eC��aq�A���9@����GG�5,őuLAE����W{-��l���"aJGo�aq ��Rp ��e���7��[�]9O4^�.�3Z0�"m7$�}�.4�2�&�51�$1��
��`���G�v�����j]ƕ劜? 0�>�w��+s �R�8NR�l9CQr��U�3��UEW�Y?[X�I�,�	�fX�A�+���D���_/?��<��U)Sy����Q��N�e#��#��gA��˾�"�4m�/�d,�L�y ���A�V>����1�N��CH�O(;���q9���^�/�c��b�E�����
��:�]J��Q�_����RۚZ�LR�Ȇt ��}��IA~7�,��~J�JPFD/installation/angie/views/database/view.html.phpwx
���V�s�F��v&�D�6�I?��$�M�hD�����Щw'l�����d�/eF�����ow�ū,κ�Ǐ������1�,FP�F����L'"�$Ӱ�,��g�d'kTJd#�o���8gpVJ�QE�t�9�uF\�DX&hi��62Y���77���N��UƂ3�O��(����ꌩ��-���*c��՟�S���M>'LK��2q==
�S����n��$��u�x|�9=#%�w�!���3~L�i6gd4�q��O��ѫ�o���F���@'+�x���^���$�p!�!fkΖ6ݜIJ�^k���i~�n(Ϩ��o�e��j�2�@�s�1�S��ՊB��fJe";NL�C�c��'�L��$]�H�T�a� ��FŪ�<(m�w#P+X{�Y%k���ŧHϐ��K��n�ۡt�8Q�/�`�;�J�Q�5ǔ��M��祈��6�lN,���iF�ٗ�p��K'��,
�u��s��}��A�H(RM��^É��<����ѕ�=}�����ߨ��Ks�x�~!�Z�K��H�U��B׋ҀjY����[�*�I�n��}�cqO�PJ\���6�'Uy��T!	u�r��*�Q����%܀�Vpkͣ1�{3��N+`B;��6�:Ө�l#r�:��:Rb�#Q�2-s4Z�5�V����NP�)*yv�^n�?�����=��Zc� eB0M�C�4��̴W�`��s�`S#1�H�����˺ף�u?ϥ�T_6'�6���b�hL����\��D��G,�Q<Y��Q;����8�ح@�����Љ"4�Sp�{D�t���-�hpMvo�
���¸Z�ūQ,4���wf��7��5/�U&,�-,	�V2`���\�t�Z뎱{L���u�ҁ�K�f��À�7��<LϦ�3>����i:�
���[�\'G�ꘆ0cMQ�-���8J�j��c4��!�
���p�D:�_?;V�ٓ��rpмLB#�6g�)�X	�p')$�6�7�k
���m]kRવ��r�!�Qz�k�mI�����>�O��h��?��ik*}oч���c�e�@6����ʻ*M���츾1��\$˼���ɪ/l��L���7��?W��no���80��Da��&����^8�[�f�+ó��u>�WHS�ӓ_O�$���gC��̌[vD��O���_-"R7���Q���-����?;�}zY��k4s{��Y@�]
�~R�l�Os=3-�s�o�݁s���^
+�ͭ����
-2Er!�#���/6�-).wΉ�ܯ��NMj�n�?�����xVT��[L�����mÌ���������Ҝ�}v��-�&��ڿu�u�JPFG2installation/angie/views/password/tmpl/default.php�����T]o�J}6�b�"َ��ܧ�`R\��4���}�v��]��������ƭ�^��g�̙NpU�㟟;p��&
#,�Fc�fV(	&Ӣ��SR�=�0��2��"���#b�`�#$NU*�hh�Vt�r @.�=�Tu�"/,�{~�d���_�b�6"+T���`E��T]*������U��i�_o>�5JԬ��:�X��Ԧ�����h
���NH��6���e4�6.p2�t�3[ㅙ��L{�A�$�B.KKLء�^��(�v�!����em�^s�y�j�Y�˚�.\!9>�h.���/�J�R�Q���,�Xz�EV��QP�H�`D.��
d\ȼÌ�f܀�I��ɾy����$���~��;�V�v���m�O/�����i�~�#-U��yB��=+�Ĕ���O���%��%Q+*ת�!�ڂ=VH�"�W����O����h1��eH��Q/����&^��ϟ�h�i'�B��UwZ[K��C���y�Ƚ��n����^�>~���f��!dj�
��n�
j�������y�(HSW�K�/����
�9�a�_]8��>���8���s\-�F�'��ߴ5tZa�Y�M��C�t-�%Gcq?�*��(�=�	�ɮ��7���N��\'G<�f9ڸ��uy�!W����`�#��
v�m��CB�Rx{`Z���3u����!%�����"wj��Ĺ��<51C<�9OZ�+�5�z�w���JPFB-installation/angie/views/steps/tmpl/steps.phpA����U]o�6}�~ŝaTR�F�4[v*'n4q����@QTD��2f��ﻤ-5͚'I���ë��.k�wr�	$���)D0/hn(��T�pY����
� #t��@-��i���r�V�,�L6U�Z>I!��m�F/y`�QY�(
\l�����Fg��w0㴔�h��.�J�Z6Bj�=nL�j	NY�m���g�bSD��M���8�Li���) %�&�</g�X�i�q:�$~hr΂p�yݜ#蚒�h̫�1���K���%�_��v+s&>Vk�j�p�g�s�{�PR]Wڐ���w��)��b�E�"� <mAPY���?ĥ7�j륪y̴ſk��O���,�c�C�|�y�����k�tK&�F�^��_�cmV;[_�B��9�Ǒ[c�9_ţգq'eB�E񪐝��j��g������d�5�A�W���"��ܤ����w�w��_s?������^v[:�0ME�E���Cs�I3A���*�5�v����KeR�?�Y�{�d>K��n�8{dG��y1��7�����Ǥ۠�ΉZ���+I�#����rz�<�{�f/nD�!�w�ﴁ�B6U�jp���"4��-!ho.��+��߼��M��t���|WѨ~�Xb�a�G�p~;/g�0��dd��.�?�NR�n�V8h�|/�A�ŭ�����I7>�?��S���r���1;}��R�˝v͟�ٺ�!�<���ɒ�A�;��V�O{�I⊌��PL+��鱠4Q�
ַ��l���~q{��f��{��s��F���JPFD/installation/angie/views/steps/tmpl/buttons.php����V]O�H}�ŭ�D�O�4�E��Zi�B�&��̌��ъ�����ā�e_V���㹟�;�_��Ԅ;[[!lAox:�C���ts��pR+�+����0�Ma@�d*g�CbQ8La4��
�H�qe��ͥ�4��4�	��D��h3�r2u�q�'���w��ݽ�0��Tg"��.�PA�\]d:��:�'��X�LP��t���B+28/F����ڜ�z�
�RF
Xr�	��RaG׽�~���� ��߇|�	-7�9�H���Z�N�[Tʽ�a���v�1��#�9��;��}�V*�X�Ie
WZ�լ=�N1�thrh��_p����>EꩁʝP	Ƒ���!Z���V����)�jayU�/�W��gR9pa�В�pS˒|O�%o���8~������;E����a�*,wI��.�hF�DЅata]�bT��#�~��b�V�Q��Z=�����9�bJ����������E��ȵ��sF�@��:�]'��)�?

~瑻��b"�9�L.�Ͱ܇��8��88 Dj���D���ݕ-EϏp0��<�A��ڧA@@�����79hl?������)VI�<�/I��KMpI�?T<0���1�78_U�yYT�"I��_q���L���~M�E�.�gk�5����}����u�U0Y��9�C�=�sY�	�a����T��K�NJXQ��X@l���آ���DL�����<Ff3�O��S��+!�Xp����������6���f��M���6E��X}�N��MeS1Cp���s��98��p9`�Jr[dN��L�},Nla��w�v-x�徶���U����k&6��W���|4f��&t��=�? �%�Ϗ�ʈ�7���X�L���N���(���?|�y�����W���JPFG2installation/angie/views/dbrestore/tmpl/dbname.php����U�N�@}�_1��Hn�J
&�CL���By���0^kw�U����1�J���<��=sΙ��N#�R���o�>��v����A��B2�E*�<�p/$�,x���d�*$2�!�+p}�5"	�/#�02�S�e3 ��c�-�J�Y���T���G�G���A$b�����J�T�c��Zhtu�q�<�D�vmLP�n�>]@w}�@�L^�@)Ŕ���e�x��{S����^�B��ʙU�ʓR�ZV��i�3b��Ґ�G<��B�=�Д)�2�`)E2+�`\S�[�˖�WaWG\�^u�r����8w�O�V���Z��m�#o:�F�Л��}��M�<�Enu�J1�֭��B���Ի�麝~�`9U
ކ�b��
�9�p8�Ņ׹�Z�x,'��{�IJf�)K0�m�aɈ��y�]|
�</�]�L�� 𓎆�d��IM�]���R4]�L�\V�gf:�2�1B�)�n$.���r�r�Vp����a:Q$ݲ#"�c̲P�U.�mͮ?ƃ��]^-R\S8�\kO�J񼔿�L:�U�ub�33y��Yg3��f��ڰ*���="�l���U��=�����f��7�L�<�c�7�oS9y��h�nӗ�8Z+�tN��L'�t�aL_�`r��\�s�Z^s���џ�>c�r!Nߧu�u���+5�����2z�����o�"��kkzI;)��6>w�W��'�J�o�ƒ誑�<�����l�m�^�����
JPFG2installation/angie/views/dbrestore/tmpl/dbuser.php�����U]O�@}���Ƙ�..�&��HE"����L�+�X;�������R�MLp�4�̹�ܯ��4�(�*��kw<�a!(�$*-$�\$��S
B�ς�Y
L���@"�����g�\!����X������)�"���@�Kɧ�����RP�}��G�c�� 1Sp�Z��R�T�b��Rhtu�q�<�D�vomLP�ng>@wu8G�L\LJ@!��$�e���K�����~�B���U�ʓRȳ,S!��g�	�!aOx3�2�4�)Sj!d\�B�dZ�a�$�&w-?�6ž���}i�-ˉN�N�����٤��rGn�z��7�ޤ�����s[�m��TȆ,Ӻ���i�ٝx��]���,�Bƛ\�on���`0������y�7<��9T=U�eY��%۶�p׈��~�=|��'®����	
�EKÀw2��J.�>E}�JQw�2ysXI_�i�
����B����8�b�J��J��dn�S�"������c���r�Msv�9�_v��l����gZS{�e���gׄ��E_'�=5�g���q�1-ik&)�5W����~�τ���C����^��=2��[�����Y�)�</�F]9�۴�!'���I:��g��֘���t�5�ӭ1}��ɩ��ϻ���g\����3�@.d��Ǵn��N�;~�f�h1���o�������H�g��(]�^Ҝ�pgk�]z_��R��5�H�]5Ԓ'�R�(��$�d�/H�qJPFE0installation/angie/views/dbrestore/view.html.php5���=�Ik�0��ѯ�C!M�SI�qJB(t�Y��"�$$e1���#g�
����gW;�
�o���F*"x�z�5�W.��z(���p/ju��#�(�l �!��#�ʻՖn�33G.��*��&�k���/��'����q8�'�Q���X=��j�uv�m��zce�K+�&����',Ѡ��%����!��E���pƘĭ2({��|U��O
Ra�?eL�����<3B�SD#�J2�e��C�$�x�Z�G$nH�ӼQ�"��Ӊ!a!�i�����:���u�9��m��JPFI4installation/angie/views/ftpbrowser/tmpl/default.php�����U�n�6}��bVM!)�#7�TGv�v� �Y8N�P,�GLdI )�F��F6�6)�s9g��e�Nxz�)L�7s��*A�� (�&W��<͕,�s1�/eL�DnQW�
�w0yA�L��hW��4�U�O�'�O�ϋ��O��Y7�yp>��?����I�2
�������4��1n���R�1ӕ���\c����in��-*]���(2G�Zf(|�q�y>�N��: $���SQ��L��I���o_�ى�y������y�F�T̊��[&��'4W�a�3�sB�[�}�r>�pm�X���Ffg��#�˱�DU�@��o�L����v�b��Bn�qz����G�r厝^��]���O3>���.�~��/����l�Xܭfw��|�"B.�w�*c��h2���U�{�r��R7Yc���١�?�0�W�&�A0���i!&�	�綮H��xrl��;���E���<�z��`&��ht�z�F �kyㆄ�C�<rG�z�D�z��B�q*���%�iS��mW��rs�r�Uz�����.Ɍm�\X$�(dvؗ���7X���.X�2IŐIaV�{��	��cEa*��e����c��U�iڷ������[fW ��~�]M��Jn��}��R�|'}��$ݺ�kR�/;�	�w��������soOw�`OGX��4\�'d!rj�[5���k?��ХG�«&�??��i�\��Q��g+��{��ۨ�v�?��~��S�ݱz;��X�E�zWda�@h�z�?����i����%������)�q�:(m�Ղ6�1|f[V�R��A��H/@F��9����U}k�ˌW�����-��/�*@Džh[�Ƈ�-��7=$l�E'�&�0��9��JPFF1installation/angie/views/ftpbrowser/view.html.php���uS]o�@|ƿb�lP)�<%4mMJ�(�&�%���y�Oq�ܽs�U�߻g>j*U��������S���������vx��r��N
V�*d� �*A���+Z���a
I
�b"`�G��y0�a
/��X#�
��4eMj�;�:�"��F��h|%sSw��7T[S��0���K�Z������X�
j$Q��J8�}������T���a��)�i���f�i�< U�&A ���·kW&d�,�ơN9���S��J��A����P:��t��N׺*���.Wv�q��ޤXD��<W�)��uzE���n��0+R̓�����n�uBK���*�z)�Y�����W�U��U썋Z�0U����:|ޗj�@n�[=��\Y
�{�f��п�ϓF6� ��)�Є�6�=�'"�~\22���iG3"��%��)����h�3���[��p2�h��X6��F����G�{�a��-��?�_��"}�o�m�JPFF1installation/angie/views/finalise/tmpl/config.php������Qo�0ǟ�OqB�Hв xZ��B	Y�(���s$�ن�o?'�4U��=�ɧ����=�k��	F#Ffq�e���AP��T�0)@S�Z;�`K��Q�aG�@�lO���Q]3O�K���|km��`j�}���X����ԛ��_��x2���Fr���3,�����<p�!�j���Y�Q����b����� ���t7��ؑ�@�ˁ�T�c+w�	�h���b�z��s7wf;�V���-�t�/D	&��0�h=CX�ss�q��$��)ڤ�t�̳r�qT���*���-t����[*-�}����%$�7��K]�m�v�;��G�2*6g+�",�<�8�W��a�ۅm�w�3/��"��fA�^�ۧ%9�
�ߐ�y�(3���4L�s*ŎՇ��,�����|���u�^������pUF�_n�'JPFG2installation/angie/views/finalise/tmpl/success.php5����POo�0?�O��0s=M��9#!��3)�i	m�b�_1���4��}/Z(�H8@�m�	a������Z!0Le�(5���[T3..h�i�+(��K
�;���\��et1oʡ��px�1��Z������g�x4z�G�	d�qYS�gX�BW#�lki |d���yՂac:�Mv�
6�i
�m�H����<�[�vh'	��(�|��wI����#T�`F�bN�J\��*��Q�a�۟�^�'��E�M]]���m���޷Y�n�H�u�U��*�s/�����7m��7H��_��E��5'?JPFG2installation/angie/views/finalise/tmpl/default.php~F���Vao�8����*%TMR����i�v�r����tB&��`G�)E���7ve��rҮT��3o޼�y��y58<��C�/���(��"Ai!�f�K�k2�Lh|��	�q�@�XՐ�Ɋ��J����W�"XÔ���.�!0c`�b��$���\l��q��w����Y���*r�Z)��E&	�5z:��2W&�e��\I3��b��Wn>�T���#�-e؀��A����qH\g^E�y��M@������*ɁN�"�-��=v��x1�I���ڱ�b.�i�cqˤמ��6��(�%���:���N��q�N9�A4����Gc�4c
�NT2�&�x�Q�Y�S��l6�
��}j)�.�E�aL��WJ��_MV�t�cӽ[��Z��0��p!8�U�:�TaS�K�#�>�s ��ŧ4�2�N�^�`�ʺ��G���.�r������N�Wz���0�gte��d"��^+O�
d(�����Va�;y�P�c����a�k�Ic�} �t�щ4>H��t�d����r0���Z홠I�W#���M��*�U0Yh-�U���sD�vR�r#�j'�:W� X.�~��X̃5H�)0��uo3!�z��V��9�o͊�b:kW��4�fJ�vK��%/���p~��v?l�>�LE*��2J�b�����
��"�]&��� 2G�(��M�L��y(ժ�x9�y�L�)���@�����-�a��cY8�����|��^w�{�� 
;�p0>����� �	���3 YҪ��_�X�j���:<��p��W�Q8mA�-",�f�gs����(�����7�g#��|���������^�X��Q{��=O-���	��,b	s�YYYl�S�Ξs�A�ޤ�nf��{����r��6��^/u���6���+R��n�)�����v���]$*$>��0-x�i)��)`�g��T�3��
��`p=���Y��Se�Q;ɏ�������N���mn�_A����"ߧn�U.�ӗv*3��R�+%�/��1��*��cq�J/�w�W#��i��
�F�FS�{'�����mG��Xz�ڋ��)o�������~gۏ�]�JPFL7installation/angie/views/main/tmpl/panel_backupinfo.php�F���T�n�@}��Y���4�K)!�ԥ(�D�T������x�݅�����дo޹�9�3Ӹ���X==-�)����6�
�Bm�b����xd`,��{�E���9j�2�>�h� ��YD�/-�RHˆa�#�	P��cb�d�P|�Y}���y���>��_@�{�L��|��ZFr&����g�����:���?ACTL���%�2���y]|�$����j1��L���~r|�c<81�q�U���<jG_Z,A�#��m���,^ZS�&<����P��Ej	2�N�+��b��,0
�h6�H�5�7S��ʥ�V�?�ᴾ9���k8��R���*�Hմ^���T�{{e�f��f�1�q�E9�nR�b]bZ��)v��,)1������V(��v�kW���%��
s�'6�ֆf}^�o�K�S��aC�4=�jצ�
Y���J�p�Q��3v��m>k^&ȅyW�v����_Ȣ�,y��_l�2�~d^{s��'2�/�D�2u����)��"bK��w)�(=�]����;u��vٛ)��)=�ί�j�����	��=޹�18�7��.���w�Y`��T���B4ϑ8ݙ1t(�"���>,��G��%>q<��0��L�\�v�߄�=	�6�a�G�]'�Ķ/�ٔ$�

�E␼i�Y���?�p��b�K��)�c���v�	�E��|8��Zovp��z��7{|�W��ҵ�0�wt}{Wi�M�JPFL7installation/angie/views/main/tmpl/panel_serverinfo.php�H���RMs�0��W�0�$a��©Ԙ8
%�`�@�zdy�U�#	�}���t�q�M�v��{��y�;n��@�p<�U���AP��T�p��f�������.�X���)��lc
�uE�����(���6K7`6K����&5���b��N��v�=9K�����;+�e.wBjp�3�&)�g��~�1f����.�	���=*]��kIX�6�N�p��
 (ԭ9>�(�.L�uYrqlT �K�$躆����G�n��}�K��5�-Z� 9�P³G��k�i��.�mI�����{�_�
_L�]]΂Iݏ���C���F�������ږj�[��Q�����%�����-1B��g�I�yb�A�}����294c�3���5P���(�Bk�e>�M�Zn���=�ON�{ �I�z�����Q�M���hq��G;MV��G��<��n��������JPFJ5installation/angie/views/main/tmpl/panel_required.php�����T]o�@|�b����P�>���X��-�JU�����Ͻ;H���;ېФ�׷;��{cw�Y�U��8��?y��,FP\#HTZH��HA��gBB���:&ØoPA(�i� ��`�0�**Ҩ<��DP��2�,[",9ڳPd[ɗ���]T�f��j���a,���[�!A[%2�N��F�c�#˕�Se���1E���(�"�A��\�g@#%4�$p�b��&F�7�����:�ʔ�{�N�7�խ2���X����t!���6�n�'�K��ZdmhV{��N�,By
��<e����1WHS�`���ݞל�ȟ�7�d>�ލ&ލS�~�� �7rf�_��h$x�Ξ���d���#w���7�Xmz�ilO��{��f#X���]�M&�?{NOY<�zmmdiɼ��j�*ݞ�_SԚ�K��n�ۃc����Ğ��n���\b��>Qk���݈�Dyp�IX�Ii��v�}3{,*Km9I��cA�E��Le��7��1)�vN��ȕV5'�,S��ɞ�H��f3dz9�$s5�'�E�%����=S�i��
���pEGEρ;���6\K��v��Z�!*�@���Z�s���W�?��l��dtm��ݛ��گd��N���f��	w�,�Pد�Q�p'E���J���JPFM8installation/angie/views/main/tmpl/panel_recommended.php�����T]o�0}.��
u
T�B�ih)�
҉ҾLr�[b5�#�����8)Yک��<�~��s�v�<	��srR��{��6�C��@�� ��$,Q���>� ��l��@�0�GD��e���q���\$:J������d#�2T0��h��h|���f<FC	߿�P�H��U�%8��Z�˴�Ȼ��(H?V��$�Q�TW�3hI� �a��6.�D�Svw�����*d2M9�U:[�td�J���#���ګhl�H�4?����Byb���u�aj�П�j�5k�{�on��3wp=����Zu8�u}*�r�����Skr9))��'�#,P6>ۖJoUxf�
���F�Ӿ�Rط����@�Ɲ���(���?x~O��?�u����v6s��;��%��z��^��(�	
�f���	���	��T��K	z��y�޶z[�(��9�ߝ&#�ja�j��n��K�0Z�g�{ͨ�~Zt%�ʺ�n��=�ֽnXK�[�K���3M��JO����պ?+��u�d�v��6�hR_�r���f˪�ց4�l�#�sg�Y�kOoC���ՕU7�r'%�!!e
1������R���X���K{������ݻ-�8�o�2���F���G�ѯv��JPFC.installation/angie/views/main/tmpl/default.phpC`���Tmo�8�L~�UM��݇k�6�,M˥�z�I05qd��i����m7R˞g�g^<��bQ8�ёGă(�&$�
�Bm�b��t�xa`&LY��.��t�7�!U�f0�B�8e��Y��~g,�$KsU�)�#�9�{�,����/+/m���<=>=���)���\�����\���sMV�<�\[���o`��	xXO����
*m�:����}��p�s�<w܅a/p� ��5.�*��0f�5������|PvV��\ t x��I��`8��>�����_j�x�Zѻ��s��t���@�t*�f7��!KT�.+
��,I�;Gs����^�]��ˊyn�d˶��q�;k�dϯ�>oF�M�k��77���+k�����q�89m4�����%�ȿ;(4~}��9X+A�ty��s��� Qט
V5��n�oǎc����
(�%_A�j�5�j�����Y��f�<-/�W��8��ύ��sL�y�Y���-fQ`���Ɛ6���8E�I/�'�]��1�_^y�����l��:�0e$���R��ߌ+R"�]�<w�t�'ԣ���,~�6���t��n85�=���Ѯ������#�,K>�n�]H��6��m�Hڟ�ʪ^v�v.+:Zf|T:�;u&P(�MTJ�zש�gݶ�sP�	>����D��&���$��
�q=$t�/�m�`.>�{����I�����
Qm�;uC>�L�y~4�h]T�O~/;��$
��8�{�'�l�Vk���J-G�MF_���8�M4����&h����Z�{������ ���9�%�JPF?*installation/angie/views/main/view.raw.php�����UQo�6~�~ť0*;�� }j7'u#�ڤ���(J:Y�%R%)�Ɛ��#E)�o�_$��;~G�zY�Upz|�1,�nVK���As��P���R�N�dRA̒M]SIη�!Q���`�A�\���+����ai�V���@k�n-��N�un�]�4I��gg�g�g��'�,���\���������`R�U����7wp�+�:�
��7������	PI�(�4R̸�t��x�\^-©
H9N��AB4,����2.)�ڕ��`D���[�l�J������z��.��R��F�X�v}ޤ8�>P,e�P&�'��W����ECx+4�u�Y�$���.�?z��O�pAN)�X�����3�
_Q�A\m�\����w)˂�G�GQ��ނ��֧>.�}s�����!<���l�d��W�[LXU\S_�6�L�<x)�(@R��І�s�i�����"q,R\!q���d��g#��dlr�g\T��]��L‚��d�#B��#ۄ�BS+F�d��葈��v���)WL;"��թ��9xJ�r�͉�`�Q�z��?4��[�Ha�����5@4����.�n�������1�h�`�g��ͽ|'�A���e�d�{��M������b����!%.ۀA�е]��?�o�Ga?s;�9��K�/a�m�m$0��
�>��f��O�M����5]2��N|	s
�N;��k��_~sX<(��
i_ّ	6���	�Zٸ�+�5Ǧ5�7��z����Ll���RfXL3� ��6Sݿ'	�R����
d�5ͮݾ>��o���S
��$<��Vv/@�A�P��L�p�5��'�NFE���`�^���=��4�%�Ic+?<i��w~��.��s��^=�����fz�JPFJ5installation/angie/views/offsitedirs/tmpl/default.php����V]O�8}&����*-�4��2�L��)-j�hG�U�$Ncp��v��j��^;)P&��J�&�����􎋼p��]�`|1 E9A�j�$QZH���H%�eB�'we��LrzOJ$���(^����ڂ���P0>�����9A�صDKI�F�OO����������4��
}�3 �T�%
�+#�Z,F•��ߠ‰�]�1l�Q�yO�2q�G�$�'%�$m����`p�c�R��|r�T��=��ΩB�7J�w�H��U{^?\c��^8�Jz�9�g��Ŭ͙��%a�mWF���0��ih��v�r���?��ʻ�ʘ�a�o���"�L�T���J� H��<v�~�f�3�0\UO���}	���
�`�1�Z�K�ɢ����.�Y����Y?�?/�
�A2����?F>pk~Tz!t��Y&&���Rk�m�[X��{���m7���訏�\�B����C��-���W���ڣ\i�Td7��K�
GP.u��:�;N/����G���31o!����R����q�ɧV�ٱ� J�*J�$�@	ՙ�̘�����D�Q����<F���4�]���tv:���q��p�1��xR��]Y�&�?��2�Xk�/��)���}���Z�_==?4�Ve���s�Dr筛�l+\xsz:C�2o��N���*�h©���:FzY��K	������X���*�S��ƽ.%Gf
��uК��*063�A7�ώo/1��-�:��MQU��5��[9�0e�$�1g��t2m�XC�bN�-
7��2��fY̞�6�e�[�8O��v=�x�����l���N�Ρ%��?4��*$�:k��*��Y
]��j,�r����C��S�fV
g��&|;v6r1q����f�߯G�8������Xl"ոyO�.1�w�� ��xx3A<��vu��*��'ӫY}
����<z^.$����l�4��Ie����Fy���V󃷇�ddru6����t�M�AU2��^�h�ϥ(���1a+#�"��b�|��jg�`���
��M0�Yַ�E�<R^���G��x�f��{���d��]
nj��N�O-t�Y	P/xm���~��X��DUn�PL/ѓ@[��c�<oK��WJ�1�JPFG2installation/angie/views/offsitedirs/view.html.php�����Q�o�0~��= %A0}Z�u
+BU[:�u/ӄ�B�fvd;l���	�jڤ�!q��ǝ/?�e�ƃ�����F�D��!�N�V`����Bȸxnj�F�r��A�0�l�3b�a�v��PY�J�����	�����P���)|:�b�L'����dzK)J]qw���uSi�ǽ˃V%*��'X�B�+��d�}n�X?��h��0D3�c!�q�N���Y%�!�'�	�`!���J��X~S�4�C����z�ޱh�+�j��?|�R�	�0h���r�~�9V+������P��z�n���x~�A��[eW�(��!D'A����]���JiGWB+GY��d������?�H�[�:Z���c�,� ���utEf7�;������4ј��9�FJ�����L�}�.�����~qjU͏�Z�c�?����h�l�f�5F�����^�oJPFE0installation/angie/views/runscripts/view.raw.php
���TQo�0~^~�!!��h3�'6�[]���r�kk��v�*�į��K8�I�cB��}w�����y�̂p?�}��q�0Y"a4�4�BI0\���\i�1~�g�4_��h�kd��!�B�1��T7c�*��ڼ�(�@���⎫l��bi�>5y����I����1_��x݁3"�6*Sy��U��M�Z��(��>x�(Q���3
@�~Fm�\������� Hp.$&��4z��u�F����qp�` r��	���co,ʄb�>�P{�|�y.y!��]$�	�lt�0��b	�������a1��\�8�a�iFo�ɫ�h8�@a	��{6Z�:kS��!y�'8U�#���q�u�x�Q��2�ƻ�6�-�EѪ�{�����}TRǓi_C�S}�)��h�[LTNk����.�҃��f+�V��R��&�@?Q�{�Li�Iq������a=Nu���v���|Uɼ-��g�A�!n��<ɏo�a�[X�X�'�����w��t�����*���#�J�d��_�%X��A���2���(�G~>EI�ָ��.1��OB�)��m��ɜ��jVlWr.yi�n�<��]
�>��%�@�>�ȧ�?V���"�ܹ���e|�����A��ܪ���~e�T<MW#/�ё_S�U�:l�Թ��N�!�'Ɠ�߯��R$u�n�C
����qvtD���XF��
��O�!�r�'x�<�޺7�l�IgF�����6�	JPFF1installation/angie/views/session/tmpl/blocked.php�z����]o�0���_qVM�2*ڛQ
5���)A�i�++$�XM��s>���4غ+��׏�{9�E$���ǀ&#CH�@�d*K���4H.2X��~��� �OLA ����k@��}T�$�D�4N��5WB�K�KΊX�����(s�T���o�i�&<���Wp��:��JE��S��v��,Q94���%L�1LWs�v��Ĥ�}����kRnF�<aa�F�5�Tk䂐�z���KWO���~�ٳ�T��E\�/2M���X�?2��*�!��/�"2���f���G��믢
�CMh�=�5�+��<��L�PCB���u:K�Y��$�.�=�4�$�O��Uy�ب �l���nȟ���GE���<N��]g(D�n�Һ����|�e“e�+��Y�����"�G���z�Þg9����xH�
�K�	�yXw�����G�O��@{`W4J�wR w���������;[�OX�l�U�J�����w�Pn����A�%��븯
��������LJ�M�s�\��S��MYJ��3��GMg2�&�ڤ){`��<���⸎Cj'���=<���d��=�ɞpr3�?҃jJƖ����`Clc��o_�C��v��j���7�!#�n|O�
����5ѢT�@9��b���D��9s-r_��_��05��wc7�ꟷ���D��������fX���C��8�:��_��>jS���|q�'[��'JPFF1installation/angie/views/session/tmpl/default.php�����Xms�8�~��w��M��;�#��cJ t:���m�u�#�I����V�+`Hһ43���}vW+NNC/��޼��7�7��[�@S���
�8��XP���4h�8����C����;!�,����-�1:K)7ۙ0�����N� sJԞ���sO���4���?����1R�c>���&��VY��2�*Y>uHI�W�O��c��6�AzxGx$�:>D�p`n�j.�р�u���Ѳ�zzC���j��ډK��D���b���0�{�̵n��D�0���At:�u}bM&���vp6���װ�]Yz��r[ �[���O����Q�Y���*<]G�ӛ:���]�zb@(�+Yt�)���}q@|à����st��^��(%L��������`Fx9_����:�jII��Ψ%��r��e���k(�Kbj�dR\±��#��h�&ئ�%sM�f4����A$V!��� �xGɽ���5�z���"���W0nc�ܚs�%���m�g��̙o�MM�4hf��Ze���^,.����/���wm�zg�`��`c���Zf�:���dȍB�-�Ґ��Ls5Pǐ�)%X�ke@���&��~_X��J���rb&u�2������~3O_/e��*�z'I�")?K�H��
K��r�4��/\N�Q
�b�L�&T/��prϸ��{�����?�0W�/�s����lO)n]ʉS���q�菭��h�噁ܶ;�^���N������U�F�Qn7ۖ�*�W�v,���B�|�[�<���=��X�0�05�暳�F��*�I�W��fw�b݇���ǣ����Ľ��U�Ns��(�ޮ������a����ٱYa���!1���܈b{IEEn�?�ڦ�]I�
��g��|a��[�L�OF�ws3���j�7�--OZ2.�w���$鯙+��ԍe��Vӵ�o5�K:���oN!A�@݁�$�R��'��Z=Hz^$V��(����]��$'��n�Ȭ<��|�H�6�9ϝ��x36��J̮��5��PZ�c@�[<z
@����.�qy�*/���#��6�E�qύ{�
��޶ۯ4�Y�v�n�2�9;�W�9��Ewo���p��Fd�|�fT�_s-w��|�G&r�/I �s",��ϳU����h�[�Ú5n��="$Q5{>�cψvX��{-H��E��9Q!#"��u����7�,�&z����F#����E��DGo����C�} ���|����Z,C�t�2����&�7���4uX��a.���1Գ���4n�<��Ƞ�ŕ�W�Jک/=��́�Ś4J��ĹTˀ43D0(�&�9YE�,�.�P�_�jHz��Q뇙uД�Z_�
x�S�A�/e�kD��t�C�����{��,'"��a?JS?M��[0�,.�HA��������K�����j4�E���|��U�릿���Cg��JPFC.installation/angie/views/session/view.html.phpU���=P�n�0<��C�$�DOU�����!$(W�8��"�-�CA�^;n�;3;���j*��.�.����z��H:���Nj$�4
m!�b��VTr��"w�Cv�t��q�*�LV���#ؼ��<���΄6G+�����E2z��RT���;���njMпx�\�@Ea�d�
Thy
�&����-���o��T���˱�
�8ڦ��x�FI ���1�#�!�F��
�݂�*�@�_�1�nE�D�>�F��s.U����tn\%��BΛ�3���\�X�I[���V�F
�Š��z��-$9��™��J�pQ������}{b'�JPF=(installation/angie/assets/runscripts.phpA�	���U�n#7}���k;u� A��m�ds�4Y$Y�ѐ5�G�F�J�8F��~I?�_�#�/3i��E��1)���2/;���m���1��]��U`r�u"(k�K��@S�h"�CU�p2W��I:�3�,��y"�p�a��?�V[�i~,a3&��Lq�O�r��,t�>�dow�۝�ݽ}�R2�Zx�4���𶴕��F��!K���l|�z��Nٰ�>W�ri|d�c]�BI8\u:O���|:>><�C�����H@�tS��ć'~
l2Oe����{�t~� ?�u�iedb1��O,��`�^{��hT�Y�H�/�B@)�К]}2���9?�Bד��*�.��2��`�f<�9�6Q�]���޽�q8_ƍe�/�pN"\�|pw6����vG"+�Q>`<�I[�ְ	>��8�/1a]0�����{��8��k$��.V��q��ـzn�2RW�-p7�6�m��Q�
��Z����t�ʘ�P��m��[�0j
���@��
Li���_��}K��fk4-���U�av�-l_5�ꞑc,IԌ�P�P~��Jz�8M\D�ҖUm��<�\��5�X�4��(�f�
����YC\�vD���Y���H�D�<���R�dz�V@V�.~��K�	��Z~1g��)H�L�!Ĥ��=8����U��V���V�`�2G�A_<�"�s�lq���5�V�rYˊ�$�IC:�:�:��&:Q�m�=��0��)�J؎�Mµ�s�Ƞ)-��T
���6�,�2���xK� Si��
�]`��<�~�\���-�S�Z�^7��C�E������R͵e����k��QX�TQo����)1��k��\a�Ϲ��T�2?��TX�@v�����)�h~�xA
����}����#4!C��sD��?��~s�ys���w�j
mq���@�WP��ࢰq�̙�B�f��U|b���(�X��3<dbb��1u�d�4���H�D|]�ԅ�1��mDŽB�+�j�i�i��V��Tx@��VB�Q<l,$B ���Otl]rfKV���C��~��i�՝����<��G3>Z���t�*�V�f����JPF7"installation/angie/helpers/ini.php/���V�r9}���q�vlǗ$<�b� ��
�e�I�c9#2
�&��۷���=����T�R���Ӓ�>˓�>�t�Ё���B�%�r��N���F�f�D��E�ĉ��b#��S�,��Zʉ�߂E6-W��TcJ�k���J������|i�U��շV�>><|�;><~*Nt*,���X���\��0(s�vS���Xf�⿸�^�L�›b��:l�Hc	��. �t�K2޼|��]�5�3Q���0�{~q3�"tBH��3*���2p�^�����.���HS�@�F�I*�s戉��H���O��X�0"���iA0��~*���}��g1�S��2��k�	�0OE��Y(�p�Xc��Yг;�D�A\�F��+3�K����s,=�E"3X�q#�!k�L-�R'`s+�A�#b����Ru-�%8
:Ƭe��*�e�$c�Zy��9�	b��qP��	n�WXD��щ0�s!��T��SoK$�c���b��������t�
Q�����T�JTf�Ӓ�IAfESc�I����D:�)�pF�)�\��Lտ�k$���#�V"cHF�H�5r�9�1b�'7v�2b��r�(�����U$Y���w�'Z���ۗVz$�l
v�]y{��u-�b�ER���i�o��ҏ˝�
���u���WߕK��K��b�����[�X��NN���x�w�k��=�	4�L�X�[
2b��45������1��O4j�{��ۮ3�Љ��`5P~��my����2aV'|�eb��]ߦg�_~��Ο�x̠�t�ƪ3��L�r���Z$��'����#�>�T
�Ke{���B&�O�B�K ;�.��=�1�]��K�C�Xq2�𐝅�w��x�	q��)̐�*1�� �]���G��^��̻�	��&kj�Ge|�ˁkM,���1�0��V�)
}�bnOI
��Jo5ޛF��W����h�U�oW�({�̶ȣ�*�<�x���m��q>����jc��vgߚ�b�.^�z�,�Y%|��
�Q
|^pM�h�*�kf�PCԼ巆��U.q	DV�$�ߣ�9=��'�yu�O�8iE�#���a��h<�Jo��Q�yPI�x��oW�棲��pL�F�hÿ��FC�q�.��K�G��-��"Oم�vi���T�J.{�����
/=g7k8-k�±����G���RA�q�+bG,:t��mp
|�WR��Q5���!���F�V���MU�Дl�
�q�5jDm����6"�_�z�ZU>�~���v��u~4�-���Cc�iVͶ#6�~'∍}�F�(��̇�AM�߽�7�Sf��z^�D��!$��~��x	h�<�ە��Sn�!��'
�4H� ͏�q�C��w�:���Ɔx�'lH͒s�'�wD���qT�fyp�h��|0�L�{�Vu���J��R�?�G��G��n��"�@��Z���/�ϧ�T���`uC0��7{y�Wa*�g��m~��>\���7ۨ⸮�>��w&�?.�.���=���t���JPF9$installation/angie/helpers/setup.phpa����T�N�@}�WL��(�<��@/�!o��f3�WY{��cPT���8$$������3����W>){�zprqz�`\!DC#��ȸ���\��ҋփ
�2wAT�S�,�d�8Q��)���O��u\C�{��9p��`w��_3��n�2���Gpat嬊�oLh�w�u�u�M;,k46Q�O/n���e;����w��u�n�r�/�I�&�����E8�gh=�k��'�B���h����T�U3bU�Y�l�
6��W��)K8���7W#0������i��Qe�9����\2���3�.�<���$��&�R�3c�����*���� ����F<\���v�3�'@#��Ǘ;�|���Z���t��d1~(˽����"/A�����z�4
X�[�k���S�g���*yQ'p[ug
�)Cm\a��P�^��f<�rc�D
~���v���%�.��l��v���]�[%���i�@�Kه���JE�`�;b��]x��m��f�x��
�í�d6��Hle���e �wqK�7}�Z	��5�l7cSG�w�O�w��vՆ�p��K�س�}Q�xE��1yL�JPF:%installation/angie/helpers/select.php�%D���\ms�6�,�
��)�F���˝\9��n�gb'7��G���E�$�D�������&SϴA`��X���?=KVI����{v��;`�+�� ,Y�<�e�4Hrv�l�7	��b܉�-R�s���|b��s�#�u�U�0N���`:,Am�8٦�r����`1|���?�>y�#{,Vq�3�똝C�,N�Mg�P�q��D+"ʐ�����K�����f/؅zy'��q�@�Ha�a���Y��E�0[�N��_D���J�b����I+�A7�JY�2AEel�>x�e�$_ylz̞��nqHM{���lk�"�Ջ|������@�Sq�7a��x�}��r�1�L�<
��ꗭl�ƛd�Ci���x��)�b�a��xO�8�`����"1���'[�/|�
c������6aҨ�~��y(h����v�|[ٟ��F�l�n�ǿ�kP��Ӎ�#C>aEj7��0�n.>�5�t��q�*���`�,��t���a%��`i3z#(���0�rg���5pVDK��O�@qcʇ��E�nr��8z@˩�>��8x���¯�[���KE�I#�$�}�oY���ɦ�\�0&ꑴɑ��<|�oŤ�&��]��#zcv��������&W;'_��?n%mvC�(�D�e��<�C�#�3�ȝ��/��o'If�;VP\Nٯ����'D̬"����G�D�7Y���V��J�b�?~�:����/m�OA2
>�Bk�|s�.G{j�^g��-��dU!
�~<ј�)[鼬P��V�V��.���l������_�#�[qZ�QtF,�Q��%$Q��z6@+v�:��Sv��|�D��~k�C���׼�ܭ*��c��Yb6��h�,�+��\!5�!O�q]�9�)<@pd�\ќR()XFq
�[�2+q"k�g��Kg	̪=���8G���(�S�G%2�\�vI;��d��`8ed!:�MM�au4�)��d�ny��a��_o_4U�P!rr7"6/�C:&M�4ȓ�T%Iui�Lr
�
$.�,� �f�2��!�Ir��+�B��,���W��E��{t�Q����ЁC�%l
M�6��d}Ɛ
S|p��5��^$�ꅫZ�U{#W̬�ft��ڧ,���X!H@sp�g5�_hZ�����/���z@&;dϔ�N q;�9_�%���,\N>����f��ze�������"9�b"�5�����t*cq���x�F{�)�I��
{4��QZ��8��U?k}��bHk��{V?߄)�j�Q�%�.�j�k�ȥ;��f��/�d$�>�����Q�Eߨ��7>W��4A)�NCN�j���KAN�VfN;:�Y�3�+_v7uf�)rg�
���#h��[��G}:Q5�K�ヽ�r��B�-2h>�
7�j��D)ۤP.G>KK+V�d2�aH�_���paYF&p~Ԓ� ��qk��v
.�R@��t�'k/(�Z�.�9c��lf�>�)�� �aL��l]���
!E��9�J&�A����~&��C"�*��0�?J#�2FFc��.����#`�f8ޣ��m#����ʙM�[cU��b����:�h�j@�>����"^'�̓0ȷ�Y+j���'&L薬��b��a�^&�����&�o�l18�X	^�GvU56FJ�8yc2!f���.hϰ�����d6.ц)��ƨ���~)��jv�S�ݿ�vxݭe�6���T������:YY��2�CW|��5�ϧ�i�+�گ�w�|`��)f+�J��n��ž	�����.�`ċ5y���z�Y�
|��m��2s��a�a�i�i�QAEzjⶢ�/�
��w�X�Dq��15���1�I���<5���ؔ�ki�d�4�
0��Y�j��k"����N&��‘�x��%�Ԝ*�|�sNŜ��E��ɓťn���P�V@��E��,���N��R<2�|�G[�a�$���o�XW��t���\���*��ZUeMu���ס��4؟�L��Y0����R��b%���y%
�F.*�Q�#s�п<�a(�1�N?۲�e���H��,s���%3�ԑ����G'R��*�׉y�P=�p�����r�Uw�� q	Oi!����Q��<_:���s~�L4��(Ī<�^�����<7C��w
8�IC����g�s�s��i�Q��ob���o�=6@�uـ�5�d��ū�FV��`����?�FS[l�i�'��d�>[��U��A���e�:	�V��E	̝�x�k��"���HN��c�l��2���_[U>qC�! �O�o�_��|���sw��@d7v�a��d�;��u�$�Y�E�}L���q�N���T��? v>~�=�~�MI��Zz[�l�����-J��Byj��l~���m�;`9�(�S��%7P�:e��0���Ξ:y��C�@uJf�}x�����1���.�9PB���d�^���=�����-5����U�-�NH_��&�K����<
�v����C�B��KH�oy�<f��n���g����q+zx��
i?Ki����Uk�/͋]�=�rң
���*l�[��c���G�K|
�:	v�P�m8ϕ��G�Y��DRMc�)�]3L��/�or|�Or9��R�t/��چ}Յ�zv:�}9-��9����S돘�1�R��M�
�qu��Š;�Ů�م��H*K�p��iF�6bg��g/._�9��:�����(��K{<)�6Sl;��J�U%u���~LJ=mg�X��T����zs]�ZF���8��9+R�=h-��7W�1��s��T�)i�Ex3�V�,�E,�^�g��d1\��sQT�����xZ��.c��\8XحCz䞝9�@]]���=N2�q�:%y���3?}��a���#�,�(�s��^S��"�B�����;���;���@_e�]X	|��ո�
9*��Ӹ�]H�ȑ	���ڒ|��u��2�
�҉@�[C���b���I�Z��C?�X-��ʛ�Z�t�'Bm.,t`��Q���#�fH�!��kI>�)�����}4O�BX�|�q��u*
�4��7#�K2�{"NNqc$}�Ⱥl��?���Qh=b�~��P�j��_cWB�u_��_$�>ܽ��$&�4�6kx��C�~t1�:_b��(�K�+�T��"�z¯�#�SؑS��*�\�j����^��Z#�(x�-G+�=Vi*v��L��˾ʡuE�C�r�G��ūf�)��
��R��E�내
9��D�#3T�&!_���~Jv�8	]�|y��5�N?��zG4�D���e��y^K��QQo)��Vu�����T��ŏ�I�r����2"�2٦���x�ӭ2�!$�ybC^�7�/O���澡�W����l�|G�R?���?	!����ֳ{%�*�C�v���3�D�V�9|"��Y(��
��6����V���N�p���|�%��of
�|�D_9"�m����BG�Ж��)�o�d����M�	3��z��I%��GyH��b�"zY""c+?��J4n���b�9WZ�1ّc�ŗ<��w���3�X�hA�%-6�w%���؅2�g�O�C���3�
���j���l��B�_��"�P��b�a�Ԇ�maa:!Bt
]�i
f�N&@������.��?��C����ӷ�����N...�}v�Q�d�����A���3�ڷ#VCj皺Ӭ:�oP���z�m�{H��Z�jiY�����:��S�L�2dʂ
��Ss��,z睞\�<?�:�]<��]���lF�eIjK�*=�Z�a�f�o��.@nuKEg�;i|�n�4�5�$"��'�M�)ւ�M/�4(y(7V|�%��>9r��W�iB)jt#$&�y��i�����
��Wբ�d��%a�6�(����c�zT�Nwt�wS4�[�k��c�8�G
��Ů�9Ճ�� ��+-�lj�
9{�_Л�����E��\�2��quuQ�?O��+�״�ή߽�]�<?���|���P���|}�
�]ud~h%sr��H]��J�i+�����oϮί%c�V�#Ͳ,�p]�d!%�����l��K�V����JPF6!installation/angie/autoloader.php�h���U�o�6�_qAH
�K����27M��i�5�dM�,�4��#���ߑ���n@�`D��>x<�]4u�L��8�����o5���u�0'�ˍhT���o�^�g��
2�%�70["���"Tٯ<h�	#����.[ ,�5�������_��������)�	^k�,��["����^j��֕���
���~�kTh��{?�
��6��ؠ�t$I�Cɓ$)�
�,-fﯮ���<���u�T^��G��;R�J4�!'z�Pl�y�OB�`���0��3W�9(/%
ۓI�zN6���Z��$��5{ߡ�%�Ў`��8S���\�@�r�(�C�]º�%y�Hld�6�6�8KD���6R�,}�]���g珳�?��W�v�l	`K�E�!D��Q�,�e+��E��h��'88�4F�y�l
�7�u��<]zcP9�	��y	�[�A+
M��V�5�rFKI;�.Qھ��^�%�闎S��<�� ��s��D�p���`f�+�d�u&P^�	ƶ�:O	�@�"�v}�4j���*(3]
i�çO]�Ǔ�ڠ��}@t;�cM�I��jC���m�	:~j�l��>��]oo>�>�H�jŪ�BU9���+�����ʆ�����o���a�!�G��6��Ç��X4-�-�q:I����'Z^ƽ�vJKc��bA|��l[�+o��җXh�q�EoD�|��A���	�@?mz����0r
�e҄��E�d lwc5�^��[3L�uMc5�_j�y��mI��0֍��A�>�b`�~��?y~���$n)�����(�ii�_�Y�N�����F�A��^86AG�v������(ۡ��ٷ�fI������xH��@9��ָ�[�9�Ȧ�� ҅�a:�T�JPF0installation/tmp/index.html�L��=O]K�0|���WzOB)���(���tm��nI҃��o7��>�����N~'�5��W��+�4��@p�;w�ʙ>�j��C�6�P�d�������8,��Y�O�<�܌v}�eK@���fPq?:���w��4}Y�mQ��J��'�ğF�=�=6��)4�1ku~r(�o�ԑ�C	��B�Oٶ���l��E��:��"�$�"&X*��Zg��f��Rn�?JPF0installation/tmp/web.configr��������Q(K-*��ϳU2�3PRH�K�O��K�U*-IӵPR���I��K�L/-J,*��R�����\���$�X0��$#�(�
I!\.%5�R��h�����>�.},�l�-��GuJPF/installation/tmp/.htaccessh�����L��O)�IUP��O�O,-ɨ�O�/J�K���/JI-RHIͫ�I���/�r2Ҋ�s�|.}�f;.�9��((���f�:�䀸

P>�����) U��ʐLJPF&installation/sql/�APK�!]!4�y�y�Master/Installers/kickstart.txtnu&1i�<?php
/**
 * Akeeba Kickstart
 * An AJAX-powered archive extraction tool
 *
 * @package   kickstart
 * @copyright Copyright (c)2008-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

/**
 * Akeeba Kickstart
 * An AJAX-powered archive extraction tool
 *
 * @package   kickstart
 * @copyright Copyright (c)2008-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

// Uncomment the following line to enable Kickstart's debug mode
//define('KSDEBUG', 1);

// =====================================================================================================================
// DO NOT MODIFY BELOW THIS LINE
// =====================================================================================================================
define('KICKSTART', 1);

define('KICKSTART_MIN_PHP', '5.6.0');
define('KICKSTART_RECOMMENDED_PHP', '7.4.0');

if (!defined('VERSION'))
{
	define('VERSION', '8.0.2-dev202307211217-revb86be29');
}

if (!defined('KICKSTARTPRO'))
{
	define('KICKSTARTPRO', '0');
}

// Used during development
if (!defined('KSDEBUG') && isset($_SERVER) && isset($_SERVER['HTTP_HOST']) && (strpos($_SERVER['HTTP_HOST'], 'local.web') !== false))
{
	define('KSDEBUG', 1);
}

define('KSWINDOWS', substr(PHP_OS, 0, 3) == 'WIN');

if (!defined('KSROOTDIR'))
{
	define('KSROOTDIR', dirname(__FILE__));
}

if (defined('KSDEBUG'))
{
	ini_set('error_log', KSROOTDIR . '/kickstart_error_log');
	if (file_exists(KSROOTDIR . '/kickstart_error_log'))
	{
		@unlink(KSROOTDIR . '/kickstart_error_log');
	}
	error_reporting(E_ALL | E_STRICT);
}
else
{
	@error_reporting(0);
}

// ==========================================================================================
// IIS missing REQUEST_URI workaround
// ==========================================================================================

/*
 * Based REQUEST_URI for IIS Servers 1.0 by NeoSmart Technologies
 * The proper method to solve IIS problems is to take a look at this:
 * http://neosmart.net/dl.php?id=7
 */

//This file should be located in the same directory as php.exe or php5isapi.dll

if (!isset($_SERVER['REQUEST_URI']))
{
	if (isset($_SERVER['HTTP_REQUEST_URI']))
	{
		$_SERVER['REQUEST_URI'] = $_SERVER['HTTP_REQUEST_URI'];
		//Good to go!
	}
	else
	{
		//Someone didn't follow the instructions!
		if (isset($_SERVER['SCRIPT_NAME']))
		{
			$_SERVER['HTTP_REQUEST_URI'] = $_SERVER['SCRIPT_NAME'];
		}
		else
		{
			$_SERVER['HTTP_REQUEST_URI'] = $_SERVER['PHP_SELF'];
		}
		if (isset($_SERVER['QUERY_STRING']) && !empty($_SERVER['QUERY_STRING']))
		{
			$_SERVER['HTTP_REQUEST_URI'] .= '?' . $_SERVER['QUERY_STRING'];
		}
		//WARNING: This is a workaround!
		//For guaranteed compatibility, HTTP_REQUEST_URI *MUST* be defined!
		//See product documentation for instructions!
		$_SERVER['REQUEST_URI'] = $_SERVER['HTTP_REQUEST_URI'];
	}
}

// Define the cacert.pem location, if it exists
$cacertpem = KSROOTDIR . '/cacert.pem';
if (is_file($cacertpem))
{
	if (is_readable($cacertpem))
	{
		define('AKEEBA_CACERT_PEM', $cacertpem);
	}
}
unset($cacertpem);

/**
 * Loads other PHP files containing extra Kickstart features. You can do all sorts of tricks such as injecting HTML
 * and CSS code (see AKFeatureGeorgeWSpecialEdition), adding AJAX task handlers (see AKFeatureURLImport) etc.
 *
 * Feature files must follow one of the following naming conventions:
 *
 * - kickstart.SOMETHING.php
 * - script_basename.SOMETHING.php
 *
 * where script_basename is the base name of Kickstart's PHP file. If you have renamed kickstart.php to foobar.php this
 * means that feature files must be named foobar.SOMETHING.php.
 *
 * The file must contain a class whose name starts with "AKFeature". The rest of the name is irrelevant and does not
 * have to follow a convention. It is, however, prudent to name the class using something similar to the filename it is
 * stored in to preserve your sanity and avoid potential conflicts.
 *
 * The class is instantiated ONLY ONCE, when the first call to callExtraFeature() is made. Its methods are called using
 * callExtraFeature() from Kickstart's (non-user-modifiable) code.
 */
function importKickstartFeatures($directory, $prefixes = array('kickstart'))
{
	$dh = @opendir($directory);

	if ($dh === false)
	{
		return;
	}

	// Make sure the prefixes include 'kickstart' and our basename
	if (!in_array('kickstart', $prefixes))
	{
		$prefixes[] = 'kickstart';
	}

	$selfBasename = basename(defined('KSSELFNAME') ? KSSELFNAME : basename(__FILE__), '.php');

	if (!in_array($selfBasename, $prefixes))
	{
		$prefixes[] = $selfBasename;
	}

	// Loop all files in the directory
	while ($filename = readdir($dh))
	{
		if (in_array($filename, array('.', '..')))
		{
			continue;
		}

		if (!is_file($directory . '/' . $filename))
		{
			continue;
		}

		// Feature files must be prefixed with one of the prefixes.
		$found = false;

		foreach ($prefixes as $prefix)
		{
			if (substr($filename, 0, strlen($prefix) + 1) == ($prefix . '.'))
			{
				$found = true;
				break;
			}
		}

		if (!$found)
		{
			continue;
		}

		if (substr($filename, -4) != '.php')
		{
			continue;
		}

		/**
		 * We have to ignore files which are just the prefix and a .php extension (because one of these scripts is the
		 * currently executing script).
		 */
		foreach ($prefixes as $prefix)
		{
			if ($filename == ($prefix . '.php'))
			{
				continue 2;
			}
		}

		// Op-code busting before loading the feature (in case it's self-modifying)
		if (function_exists('opcache_invalidate'))
		{
			opcache_invalidate($directory . '/' . $filename, true);
		}

		if (function_exists('apc_compile_file'))
		{
			apc_compile_file($directory . '/' . $filename);
		}

		if (function_exists('wincache_refresh_if_changed'))
		{
			wincache_refresh_if_changed(array($directory . '/' . $filename));
		}

		if (function_exists('xcache_asm'))
		{
			xcache_asm($directory . '/' . $filename);
		}

		include_once $directory . '/' . $filename;
	}
}

// Import Kickstart features from the top level directory
importKickstartFeatures(KSROOTDIR);

/**
 * Akeeba Restore
 * An AJAX-powered archive extraction library for JPA, JPS and ZIP archives
 *
 * @package   restore
 * @copyright Copyright (c)2008-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

define('_AKEEBA_RESTORATION', 1);
defined('DS') or define('DS', DIRECTORY_SEPARATOR);

// Unarchiver run states
define('AK_STATE_NOFILE', 0); // File header not read yet
define('AK_STATE_HEADER', 1); // File header read; ready to process data
define('AK_STATE_DATA', 2); // Processing file data
define('AK_STATE_DATAREAD', 3); // Finished processing file data; ready to post-process
define('AK_STATE_POSTPROC', 4); // Post-processing
define('AK_STATE_DONE', 5); // Done with post-processing

/* Windows system detection */
if (!defined('_AKEEBA_IS_WINDOWS'))
{
	if (function_exists('php_uname'))
	{
		define('_AKEEBA_IS_WINDOWS', stristr(php_uname(), 'windows'));
	}
	else
	{
		define('_AKEEBA_IS_WINDOWS', DIRECTORY_SEPARATOR == '\\');
	}
}

// Get the file's root
if (!defined('KSROOTDIR'))
{
	define('KSROOTDIR', dirname(__FILE__));
}
if (!defined('KSLANGDIR'))
{
	define('KSLANGDIR', KSROOTDIR);
}

// Make sure the locale is correct for basename() to work
if (function_exists('setlocale'))
{
	@setlocale(LC_ALL, 'en_US.UTF8');
}

// fnmatch not available on non-POSIX systems
// Thanks to soywiz@php.net for this usefull alternative function [http://gr2.php.net/fnmatch]
if (!function_exists('fnmatch'))
{
	function fnmatch($pattern, $string)
	{
		return @preg_match(
			'/^' . strtr(addcslashes($pattern, '/\\.+^$(){}=!<>|'),
				array('*' => '.*', '?' => '.?')) . '$/i', $string
		);
	}
}

// Unicode-safe binary data length function
if (!function_exists('akstringlen'))
{
	if (function_exists('mb_strlen'))
	{
		function akstringlen($string)
		{
			return mb_strlen($string, '8bit');
		}
	}
	else
	{
		function akstringlen($string)
		{
			return strlen($string);
		}
	}
}

if (!function_exists('aksubstr'))
{
	if (function_exists('mb_strlen'))
	{
		function aksubstr($string, $start, $length = null)
		{
			return mb_substr($string, $start, $length, '8bit');
		}
	}
	else
	{
		function aksubstr($string, $start, $length = null)
		{
			return substr($string, $start, $length);
		}
	}
}

/**
 * Gets a query parameter from GET or POST data
 *
 * @param $key
 * @param $default
 */
function getQueryParam($key, $default = null)
{
	$value = $default;

	if (array_key_exists($key, $_REQUEST))
	{
		$value = $_REQUEST[$key];
	}

	if (version_compare(PHP_VERSION, '5.4.0', 'lt') && get_magic_quotes_gpc() && !is_null($value))
	{
		$value = stripslashes($value);
	}

	return $value;
}

// Debugging function
function debugMsg($msg)
{
	if (!defined('KSDEBUG'))
	{
		return;
	}

	$fp = fopen('debug.txt', 'a');

	fwrite($fp, $msg . PHP_EOL);
	fclose($fp);

	// Echo to stdout if KSDEBUGCLI is defined
	if (defined('KSDEBUGCLI'))
	{
		echo $msg . "\n";
	}
}

/**
 * Invalidate a file in OPcache.
 *
 * Only applies if the file has a .php extension.
 *
 * @param   string  $file  The filepath to clear from OPcache
 *
 * @return  boolean
 * @since   7.1.0
 */
function clearFileInOPCache($file)
{
	static $hasOpCache = null;

	if (is_null($hasOpCache))
	{
		$hasOpCache = ini_get('opcache.enable')
			&& function_exists('opcache_invalidate')
			&& (!ini_get('opcache.restrict_api') || stripos(realpath($_SERVER['SCRIPT_FILENAME']), ini_get('opcache.restrict_api')) === 0);
	}

	if ($hasOpCache && (strtolower(substr($file, -4)) === '.php'))
	{
		return opcache_invalidate($file, true);
	}

	return false;
}

/**
 * Akeeba Restore
 * An AJAX-powered archive extraction library for JPA, JPS and ZIP archives
 *
 * @package   restore
 * @copyright Copyright (c)2008-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

/**
 * The base class of Akeeba Engine objects. Allows for error and warnings logging
 * and propagation. Largely based on the Joomla! 1.5 JObject class.
 */
abstract class AKAbstractObject
{
	/** @var    array    The queue size of the $_errors array. Set to 0 for infinite size. */
	protected $_errors_queue_size = 0;
	/** @var    array    The queue size of the $_warnings array. Set to 0 for infinite size. */
	protected $_warnings_queue_size = 0;
	/** @var    array    An array of errors */
	private $_errors = array();
	/** @var    array    An array of warnings */
	private $_warnings = array();

	/**
	 * Get the most recent error message
	 *
	 * @param    integer $i Optional error index
	 *
	 * @return    string    Error message
	 */
	public function getError($i = null)
	{
		return $this->getItemFromArray($this->_errors, $i);
	}

	/**
	 * Returns the last item of a LIFO string message queue, or a specific item
	 * if so specified.
	 *
	 * @param array $array An array of strings, holding messages
	 * @param int   $i     Optional message index
	 *
	 * @return mixed The message string, or false if the key doesn't exist
	 */
	private function getItemFromArray($array, $i = null)
	{
		// Find the item
		if ($i === null)
		{
			// Default, return the last item
			$item = end($array);
		}
		else if (!array_key_exists($i, $array))
		{
			// If $i has been specified but does not exist, return false
			return false;
		}
		else
		{
			$item = $array[$i];
		}

		return $item;
	}

	/**
	 * Return all errors, if any
	 *
	 * @return    array    Array of error messages
	 */
	public function getErrors()
	{
		return $this->_errors;
	}

	/**
	 * Resets all error messages
	 */
	public function resetErrors()
	{
		$this->_errors = array();
	}

	/**
	 * Get the most recent warning message
	 *
	 * @param    integer $i Optional warning index
	 *
	 * @return    string    Error message
	 */
	public function getWarning($i = null)
	{
		return $this->getItemFromArray($this->_warnings, $i);
	}

	/**
	 * Return all warnings, if any
	 *
	 * @return    array    Array of error messages
	 */
	public function getWarnings()
	{
		return $this->_warnings;
	}

	/**
	 * Resets all warning messages
	 */
	public function resetWarnings()
	{
		$this->_warnings = array();
	}

	/**
	 * Propagates errors and warnings to a foreign object. The foreign object SHOULD
	 * implement the setError() and/or setWarning() methods but DOESN'T HAVE TO be of
	 * AKAbstractObject type. For example, this can even be used to propagate to a
	 * JObject instance in Joomla!. Propagated items will be removed from ourselves.
	 *
	 * @param object $object The object to propagate errors and warnings to.
	 */
	public function propagateToObject(&$object)
	{
		// Skip non-objects
		if (!is_object($object))
		{
			return;
		}

		if (method_exists($object, 'setError'))
		{
			if (!empty($this->_errors))
			{
				foreach ($this->_errors as $error)
				{
					$object->setError($error);
				}
				$this->_errors = array();
			}
		}

		if (method_exists($object, 'setWarning'))
		{
			if (!empty($this->_warnings))
			{
				foreach ($this->_warnings as $warning)
				{
					$object->setWarning($warning);
				}
				$this->_warnings = array();
			}
		}
	}

	/**
	 * Propagates errors and warnings from a foreign object. Each propagated list is
	 * then cleared on the foreign object, as long as it implements resetErrors() and/or
	 * resetWarnings() methods.
	 *
	 * @param object $object The object to propagate errors and warnings from
	 */
	public function propagateFromObject(&$object)
	{
		if (method_exists($object, 'getErrors'))
		{
			$errors = $object->getErrors();
			if (!empty($errors))
			{
				foreach ($errors as $error)
				{
					$this->setError($error);
				}
			}
			if (method_exists($object, 'resetErrors'))
			{
				$object->resetErrors();
			}
		}

		if (method_exists($object, 'getWarnings'))
		{
			$warnings = $object->getWarnings();
			if (!empty($warnings))
			{
				foreach ($warnings as $warning)
				{
					$this->setWarning($warning);
				}
			}
			if (method_exists($object, 'resetWarnings'))
			{
				$object->resetWarnings();
			}
		}
	}

	/**
	 * Add an error message
	 *
	 * @param    string $error Error message
	 */
	public function setError($error)
	{
		if ($this->_errors_queue_size > 0)
		{
			if (count($this->_errors) >= $this->_errors_queue_size)
			{
				array_shift($this->_errors);
			}
		}

		$this->_errors[] = $error;
	}

	/**
	 * Add an error message
	 *
	 * @param    string $error Error message
	 */
	public function setWarning($warning)
	{
		if ($this->_warnings_queue_size > 0)
		{
			if (count($this->_warnings) >= $this->_warnings_queue_size)
			{
				array_shift($this->_warnings);
			}
		}

		$this->_warnings[] = $warning;
	}

	/**
	 * Sets the size of the error queue (acts like a LIFO buffer)
	 *
	 * @param int $newSize The new queue size. Set to 0 for infinite length.
	 */
	protected function setErrorsQueueSize($newSize = 0)
	{
		$this->_errors_queue_size = (int) $newSize;
	}

	/**
	 * Sets the size of the warnings queue (acts like a LIFO buffer)
	 *
	 * @param int $newSize The new queue size. Set to 0 for infinite length.
	 */
	protected function setWarningsQueueSize($newSize = 0)
	{
		$this->_warnings_queue_size = (int) $newSize;
	}

}

/**
 * Akeeba Restore
 * An AJAX-powered archive extraction library for JPA, JPS and ZIP archives
 *
 * @package   restore
 * @copyright Copyright (c)2008-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

/**
 * The superclass of all Akeeba Kickstart parts. The "parts" are intelligent stateful
 * classes which perform a single procedure and have preparation, running and
 * finalization phases. The transition between phases is handled automatically by
 * this superclass' tick() final public method, which should be the ONLY public API
 * exposed to the rest of the Akeeba Engine.
 */
abstract class AKAbstractPart extends AKAbstractObject
{
	/**
	 * Indicates whether this part has finished its initialisation cycle
	 *
	 * @var boolean
	 */
	protected $isPrepared = false;

	/**
	 * Indicates whether this part has more work to do (it's in running state)
	 *
	 * @var boolean
	 */
	protected $isRunning = false;

	/**
	 * Indicates whether this part has finished its finalization cycle
	 *
	 * @var boolean
	 */
	protected $isFinished = false;

	/**
	 * Indicates whether this part has finished its run cycle
	 *
	 * @var boolean
	 */
	protected $hasRun = false;

	/**
	 * The name of the engine part (a.k.a. Domain), used in return table
	 * generation.
	 *
	 * @var string
	 */
	protected $active_domain = "";

	/**
	 * The step this engine part is in. Used verbatim in return table and
	 * should be set by the code in the _run() method.
	 *
	 * @var string
	 */
	protected $active_step = "";

	/**
	 * A more detailed description of the step this engine part is in. Used
	 * verbatim in return table and should be set by the code in the _run()
	 * method.
	 *
	 * @var string
	 */
	protected $active_substep = "";

	/**
	 * Any configuration variables, in the form of an array.
	 *
	 * @var array
	 */
	protected $_parametersArray = array();

	/** @var string The database root key */
	protected $databaseRoot = array();
	/** @var array An array of observers */
	protected $observers = array();
	/** @var int Last reported warnings's position in array */
	private $warnings_pointer = -1;

	/**
	 * 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 response array.
	 *
	 * @return    array    A Response Array
	 */
	final public function tick()
	{
		// Call the right action method, depending on engine part state
		switch ($this->getState())
		{
			case "init":
				$this->_prepare();
				break;
			case "prepared":
				$this->_run();
				break;
			case "running":
				$this->_run();
				break;
			case "postrun":
				$this->_finalize();
				break;
		}

		// Send a Return Table back to the caller
		$out = $this->_makeReturnTable();

		return $out;
	}

	/**
	 * Returns the state of this engine part.
	 *
	 * @return string The state of this engine part. It can be one of
	 * error, init, prepared, running, postrun, finished.
	 */
	final public function getState()
	{
		if ($this->getError())
		{
			return "error";
		}

		if (!($this->isPrepared))
		{
			return "init";
		}

		if (!($this->isFinished) && !($this->isRunning) && !($this->hasRun) && ($this->isPrepared))
		{
			return "prepared";
		}

		if (!($this->isFinished) && $this->isRunning && !($this->hasRun))
		{
			return "running";
		}

		if (!($this->isFinished) && !($this->isRunning) && $this->hasRun)
		{
			return "postrun";
		}

		if ($this->isFinished)
		{
			return "finished";
		}
	}

	/**
	 * Runs the preparation for this part. Should set _isPrepared
	 * to true
	 */
	abstract protected function _prepare();

	/**
	 * Runs the main functionality loop for this part. Upon calling,
	 * should set the _isRunning to true. When it finished, should set
	 * the _hasRan to true. If an error is encountered, setError should
	 * be used.
	 */
	abstract protected function _run();

	/**
	 * Runs the finalisation process for this part. Should set
	 * _isFinished to true.
	 */
	abstract protected function _finalize();

	/**
	 * Constructs a Response Array based on the engine part's state.
	 *
	 * @return array The Response Array for the current state
	 */
	final protected function _makeReturnTable()
	{
		// Get a list of warnings
		$warnings = $this->getWarnings();
		// Report only new warnings if there is no warnings queue size
		if ($this->_warnings_queue_size == 0)
		{
			if (($this->warnings_pointer > 0) && ($this->warnings_pointer < (count($warnings))))
			{
				$warnings = array_slice($warnings, $this->warnings_pointer + 1);
				$this->warnings_pointer += count($warnings);
			}
			else
			{
				$this->warnings_pointer = count($warnings);
			}
		}

		$out = array(
			'HasRun'   => (!($this->isFinished)),
			'Domain'   => $this->active_domain,
			'Step'     => $this->active_step,
			'Substep'  => $this->active_substep,
			'Error'    => $this->getError(),
			'Warnings' => $warnings
		);

		return $out;
	}

	/**
	 * Returns a copy of the class's status array
	 *
	 * @return array
	 */
	public function getStatusArray()
	{
		return $this->_makeReturnTable();
	}

	/**
	 * Sends any kind of setup information to the engine part. Using this,
	 * we avoid passing parameters to the constructor of the class. These
	 * parameters should be passed as an indexed array and should be taken
	 * into account during the preparation process only. This function will
	 * set the error flag if it's called after the engine part is prepared.
	 *
	 * @param array $parametersArray The parameters to be passed to the
	 *                               engine part.
	 */
	final public function setup($parametersArray)
	{
		if ($this->isPrepared)
		{
			$this->setState('error', "Can't modify configuration after the preparation of " . $this->active_domain);
		}
		else
		{
			$this->_parametersArray = $parametersArray;
			if (array_key_exists('root', $parametersArray))
			{
				$this->databaseRoot = $parametersArray['root'];
			}
		}
	}

	/**
	 * Sets the engine part's internal state, in an easy to use manner
	 *
	 * @param    string $state        One of init, prepared, running, postrun, finished, error
	 * @param    string $errorMessage The reported error message, should the state be set to error
	 */
	protected function setState($state = 'init', $errorMessage = 'Invalid setState argument')
	{
		switch ($state)
		{
			case 'init':
				$this->isPrepared = false;
				$this->isRunning  = false;
				$this->isFinished = false;
				$this->hasRun     = false;
				break;

			case 'prepared':
				$this->isPrepared = true;
				$this->isRunning  = false;
				$this->isFinished = false;
				$this->hasRun     = false;
				break;

			case 'running':
				$this->isPrepared = true;
				$this->isRunning  = true;
				$this->isFinished = false;
				$this->hasRun     = false;
				break;

			case 'postrun':
				$this->isPrepared = true;
				$this->isRunning  = false;
				$this->isFinished = false;
				$this->hasRun     = true;
				break;

			case 'finished':
				$this->isPrepared = true;
				$this->isRunning  = false;
				$this->isFinished = true;
				$this->hasRun     = false;
				break;

			case 'error':
			default:
				$this->setError($errorMessage);
				break;
		}
	}

	final public function getDomain()
	{
		return $this->active_domain;
	}

	final public function getStep()
	{
		return $this->active_step;
	}

	final public function getSubstep()
	{
		return $this->active_substep;
	}

	/**
	 * Attaches an observer object
	 *
	 * @param AKAbstractPartObserver $obs
	 */
	function attach(AKAbstractPartObserver $obs)
	{
		$this->observers["$obs"] = $obs;
	}

	/**
	 * Detaches an observer object
	 *
	 * @param AKAbstractPartObserver $obs
	 */
	function detach(AKAbstractPartObserver $obs)
	{
		unset($this->observers["$obs"]);
	}

	/**
	 * Sets the BREAKFLAG, which instructs this engine part that the current step must break immediately,
	 * in fear of timing out.
	 */
	protected function setBreakFlag()
	{
		AKFactory::set('volatile.breakflag', true);
	}

	final protected function setDomain($new_domain)
	{
		$this->active_domain = $new_domain;
	}

	final protected function setStep($new_step)
	{
		$this->active_step = $new_step;
	}

	final protected function setSubstep($new_substep)
	{
		$this->active_substep = $new_substep;
	}

	/**
	 * Notifies observers each time something interesting happened to the part
	 *
	 * @param mixed $message The event object
	 */
	protected function notify($message)
	{
		foreach ($this->observers as $obs)
		{
			$obs->update($this, $message);
		}
	}
}

/**
 * Akeeba Restore
 * An AJAX-powered archive extraction library for JPA, JPS and ZIP archives
 *
 * @package   restore
 * @copyright Copyright (c)2008-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

/**
 * The base class of unarchiver classes
 */
abstract class AKAbstractUnarchiver extends AKAbstractPart
{
	/** @var array List of the names of all archive parts */
	public $archiveList = array();
	/** @var int The total size of all archive parts */
	public $totalSize = array();
	/** @var array Which files to rename */
	public $renameFiles = array();
	/** @var array Which directories to rename */
	public $renameDirs = array();
	/** @var array Which files to skip */
	public $skipFiles = array();
	/** @var string Archive filename */
	protected $filename = null;
	/** @var integer Current archive part number */
	protected $currentPartNumber = -1;
	/** @var integer The offset inside the current part */
	protected $currentPartOffset = 0;
	/** @var bool Should I restore permissions? */
	protected $flagRestorePermissions = false;
	/** @var AKAbstractPostproc Post processing class */
	protected $postProcEngine = null;
	/** @var string Absolute path to prepend to extracted files */
	protected $addPath = '';
	/** @var string Absolute path to remove from extracted files */
	protected $removePath = '';
	/** @var integer Chunk size for processing */
	protected $chunkSize = 524288;

	/** @var resource File pointer to the current archive part file */
	protected $fp = null;

	/** @var int Run state when processing the current archive file */
	protected $runState = null;

	/** @var stdClass File header data, as read by the readFileHeader() method */
	protected $fileHeader = null;

	/** @var int How much of the uncompressed data we've read so far */
	protected $dataReadLength = 0;

	/** @var array Unwriteable files in these directories are always ignored and do not cause errors when not extracted */
	protected $ignoreDirectories = array();

	/**
	 * Wakeup function, called whenever the class is unserialized
	 */
	public function __wakeup()
	{
		if ($this->currentPartNumber >= 0)
		{
			$this->fp = @fopen($this->archiveList[$this->currentPartNumber], 'r');

			if ((is_resource($this->fp)) && ($this->currentPartOffset > 0))
			{
				@fseek($this->fp, $this->currentPartOffset);
			}
		}
	}

	/**
	 * Sleep function, called whenever the class is serialized
	 */
	public function shutdown()
	{
		if (is_resource($this->fp))
		{
			$this->currentPartOffset = @ftell($this->fp);
			@fclose($this->fp);
		}
	}

	/**
	 * Is this file or directory contained in a directory we've decided to ignore
	 * write errors for? This is useful to let the extraction work despite write
	 * errors in the log, logs and tmp directories which MIGHT be used by the system
	 * on some low quality hosts and Plesk-powered hosts.
	 *
	 * @param   string $shortFilename The relative path of the file/directory in the package
	 *
	 * @return  boolean  True if it belongs in an ignored directory
	 */
	public function isIgnoredDirectory($shortFilename)
	{
		// return false;

		if (substr($shortFilename, -1) == '/')
		{
			$check = rtrim($shortFilename, '/');
		}
		else
		{
			$check = dirname($shortFilename);
		}

		return in_array($check, $this->ignoreDirectories);
	}

	/**
	 * Implements the abstract _prepare() method
	 */
	final protected function _prepare()
	{
		if (count($this->_parametersArray) > 0)
		{
			foreach ($this->_parametersArray as $key => $value)
			{
				switch ($key)
				{
					// Archive's absolute filename
					case 'filename':
						$this->filename = $value;

						// Sanity check
						if (!empty($value))
						{
							$value = strtolower($value);

							if (strlen($value) > 6)
							{
								if (
									(substr($value, 0, 7) == 'http://')
									|| (substr($value, 0, 8) == 'https://')
									|| (substr($value, 0, 6) == 'ftp://')
									|| (substr($value, 0, 7) == 'ssh2://')
									|| (substr($value, 0, 6) == 'ssl://')
								)
								{
									$this->setState('error', 'Invalid archive location');
								}
							}
						}


						break;

					// Should I restore permissions?
					case 'restore_permissions':
						$this->flagRestorePermissions = $value;
						break;

					// Should I use FTP?
					case 'post_proc':
						$this->postProcEngine = AKFactory::getpostProc($value);
						break;

					// Path to add in the beginning
					case 'add_path':
						$this->addPath = $value;
						$this->addPath = str_replace('\\', '/', $this->addPath);
						$this->addPath = rtrim($this->addPath, '/');
						if (!empty($this->addPath))
						{
							$this->addPath .= '/';
						}
						break;

					// Path to remove from the beginning
					case 'remove_path':
						$this->removePath = $value;
						$this->removePath = str_replace('\\', '/', $this->removePath);
						$this->removePath = rtrim($this->removePath, '/');
						if (!empty($this->removePath))
						{
							$this->removePath .= '/';
						}
						break;

					// Which files to rename (hash array)
					case 'rename_files':
						$this->renameFiles = $value;
						break;

					// Which files to rename (hash array)
					case 'rename_dirs':
						$this->renameDirs = $value;
						break;

					// Which files to skip (indexed array)
					case 'skip_files':
						$this->skipFiles = $value;
						break;

					// Which directories to ignore when we can't write files in them (indexed array)
					case 'ignoredirectories':
						$this->ignoreDirectories = $value;
						break;
				}
			}
		}

		$this->scanArchives();

		$this->readArchiveHeader();
		$errMessage = $this->getError();
		if (!empty($errMessage))
		{
			$this->setState('error', $errMessage);
		}
		else
		{
			$this->runState = AK_STATE_NOFILE;
			$this->setState('prepared');
		}
	}

	/**
	 * Scans for archive parts
	 */
	private function scanArchives()
	{
		if (defined('KSDEBUG'))
		{
			@unlink('debug.txt');
		}
		debugMsg('Preparing to scan archives');

		$privateArchiveList = array();

		// Get the components of the archive filename
		$dirname         = dirname($this->filename);
		$base_extension  = $this->getBaseExtension();
		$basename        = basename($this->filename, $base_extension);
		$this->totalSize = 0;

		// Scan for multiple parts until we don't find any more of them
		$count             = 0;
		$found             = true;
		$this->archiveList = array();
		while ($found)
		{
			++$count;
			$extension = substr($base_extension, 0, 2) . sprintf('%02d', $count);
			$filename  = $dirname . DIRECTORY_SEPARATOR . $basename . $extension;
			$found     = file_exists($filename);
			if ($found)
			{
				debugMsg('- Found archive ' . $filename);
				// Add yet another part, with a numeric-appended filename
				$this->archiveList[] = $filename;

				$filesize = @filesize($filename);
				$this->totalSize += $filesize;

				$privateArchiveList[] = array($filename, $filesize);
			}
			else
			{
				debugMsg('- Found archive ' . $this->filename);
				// Add the last part, with the regular extension
				$this->archiveList[] = $this->filename;

				$filename = $this->filename;
				$filesize = @filesize($filename);
				$this->totalSize += $filesize;

				$privateArchiveList[] = array($filename, $filesize);
			}
		}
		debugMsg('Total archive parts: ' . $count);

		$this->currentPartNumber = -1;
		$this->currentPartOffset = 0;
		$this->runState          = AK_STATE_NOFILE;

		// Send start of file notification
		$message                     = new stdClass;
		$message->type               = 'totalsize';
		$message->content            = new stdClass;
		$message->content->totalsize = $this->totalSize;
		$message->content->filelist  = $privateArchiveList;
		$this->notify($message);
	}

	/**
	 * Returns the base extension of the file, e.g. '.jpa'
	 *
	 * @return string
	 */
	private function getBaseExtension()
	{
		static $baseextension;

		if (empty($baseextension))
		{
			$basename      = basename($this->filename);
			$lastdot       = strrpos($basename, '.');
			$baseextension = substr($basename, $lastdot);
		}

		return $baseextension;
	}

	/**
	 * Concrete classes are supposed to use this method in order to read the archive's header and
	 * prepare themselves to the point of being ready to extract the first file.
	 */
	protected abstract function readArchiveHeader();

	protected function _run()
	{
		if ($this->getState() == 'postrun')
		{
			return;
		}

		$this->setState('running');

		$timer = AKFactory::getTimer();

		$status = true;
		while ($status && ($timer->getTimeLeft() > 0))
		{
			switch ($this->runState)
			{
				case AK_STATE_NOFILE:
					debugMsg(__CLASS__ . '::_run() - Reading file header');
					$status = $this->readFileHeader();
					if ($status)
					{
						// Send start of file notification
						$message                        = new stdClass;
						$message->type                  = 'startfile';
						$message->content               = new stdClass;
						$message->content->realfile     = $this->fileHeader->file;
						$message->content->file         = $this->fileHeader->file;
						$message->content->uncompressed = $this->fileHeader->uncompressed;

						if (array_key_exists('realfile', get_object_vars($this->fileHeader)))
						{
							$message->content->realfile = $this->fileHeader->realFile;
						}

						if (array_key_exists('compressed', get_object_vars($this->fileHeader)))
						{
							$message->content->compressed = $this->fileHeader->compressed;
						}
						else
						{
							$message->content->compressed = 0;
						}

						debugMsg(__CLASS__ . '::_run() - Preparing to extract ' . $message->content->realfile);

						$this->notify($message);
					}
					else
					{
						debugMsg(__CLASS__ . '::_run() - Could not read file header');
					}
					break;

				case AK_STATE_HEADER:
				case AK_STATE_DATA:
					debugMsg(__CLASS__ . '::_run() - Processing file data');
					$status = $this->processFileData();
					break;

				case AK_STATE_DATAREAD:
				case AK_STATE_POSTPROC:
					debugMsg(__CLASS__ . '::_run() - Calling post-processing class');
					$this->postProcEngine->timestamp = $this->fileHeader->timestamp;
					$status                          = $this->postProcEngine->process();
					$this->propagateFromObject($this->postProcEngine);
					$this->runState = AK_STATE_DONE;
					break;

				case AK_STATE_DONE:
				default:
					if ($status)
					{
						debugMsg(__CLASS__ . '::_run() - Finished extracting file');
						// Send end of file notification
						$message          = new stdClass;
						$message->type    = 'endfile';
						$message->content = new stdClass;
						if (array_key_exists('realfile', get_object_vars($this->fileHeader)))
						{
							$message->content->realfile = $this->fileHeader->realFile;
						}
						else
						{
							$message->content->realfile = $this->fileHeader->file;
						}
						$message->content->file = $this->fileHeader->file;
						if (array_key_exists('compressed', get_object_vars($this->fileHeader)))
						{
							$message->content->compressed = $this->fileHeader->compressed;
						}
						else
						{
							$message->content->compressed = 0;
						}
						$message->content->uncompressed = $this->fileHeader->uncompressed;
						$this->notify($message);
					}
					$this->runState = AK_STATE_NOFILE;

					break;
			}
		}

		$error = $this->getError();

		if (!$status && ($this->runState == AK_STATE_NOFILE) && empty($error))
		{
			debugMsg(__CLASS__ . '::_run() - Just finished');
			// We just finished
			$this->setState('postrun');

			// Reset internal state, prevents __wakeup from trying to open a non-existent file
			$this->currentPartNumber = -1;
		}
		elseif (!empty($error))
		{
			debugMsg(__CLASS__ . '::_run() - Halted with an error:');
			debugMsg($error);
			$this->setState('error', $error);
		}
	}

	/**
	 * Concrete classes must use this method to read the file header
	 *
	 * @return bool True if reading the file was successful, false if an error occurred or we reached end of archive
	 */
	protected abstract function readFileHeader();

	/**
	 * Concrete classes must use this method to process file data. It must set $runState to AK_STATE_DATAREAD when
	 * it's finished processing the file data.
	 *
	 * @return bool True if processing the file data was successful, false if an error occurred
	 */
	protected abstract function processFileData();

	protected function _finalize()
	{
		// Nothing to do
		$this->setState('finished');
	}

	/**
	 * Opens the next part file for reading
	 */
	protected function nextFile()
	{
		debugMsg('Current part is ' . $this->currentPartNumber . '; opening the next part');
		++$this->currentPartNumber;

		if ($this->currentPartNumber > (count($this->archiveList) - 1))
		{
			$this->setState('postrun');

			return false;
		}
		else
		{
			if (is_resource($this->fp))
			{
				@fclose($this->fp);
			}
			debugMsg('Opening file ' . $this->archiveList[$this->currentPartNumber]);
			$this->fp = @fopen($this->archiveList[$this->currentPartNumber], 'r');
			if ($this->fp === false)
			{
				debugMsg('Could not open file - crash imminent');
				$this->setError(AKText::sprintf('ERR_COULD_NOT_OPEN_ARCHIVE_PART', $this->archiveList[$this->currentPartNumber]));
			}
			fseek($this->fp, 0);
			$this->currentPartOffset = 0;

			return true;
		}
	}

	/**
	 * Returns true if we have reached the end of file
	 *
	 * @param $local bool True to return EOF of the local file, false (default) to return if we have reached the end of
	 *               the archive set
	 *
	 * @return bool True if we have reached End Of File
	 */
	protected function isEOF($local = false)
	{
		$eof = @feof($this->fp);

		if (!$eof)
		{
			// Border case: right at the part's end (eeeek!!!). For the life of me, I don't understand why
			// feof() doesn't report true. It expects the fp to be positioned *beyond* the EOF to report
			// true. Incredible! :(
			$position = @ftell($this->fp);
			$filesize = @filesize($this->archiveList[$this->currentPartNumber]);
			if ($filesize <= 0)
			{
				// 2Gb or more files on a 32 bit version of PHP tend to get screwed up. Meh.
				$eof = false;
			}
			elseif ($position >= $filesize)
			{
				$eof = true;
			}
		}

		if ($local)
		{
			return $eof;
		}
		else
		{
			return $eof && ($this->currentPartNumber >= (count($this->archiveList) - 1));
		}
	}

	/**
	 * Tries to make a directory user-writable so that we can write a file to it
	 *
	 * @param $path string A path to a file
	 */
	protected function setCorrectPermissions($path)
	{
		static $rootDir = null;

		if (is_null($rootDir))
		{
			$rootDir = rtrim(AKFactory::get('kickstart.setup.destdir', ''), '/\\');
		}

		$directory = rtrim(dirname($path), '/\\');
		if ($directory != $rootDir)
		{
			// Is this an unwritable directory?
			if (!is_writeable($directory))
			{
				$this->postProcEngine->chmod($directory, 0755);
			}
		}
		$this->postProcEngine->chmod($path, 0644);
	}

	/**
	 * Reads data from the archive and notifies the observer with the 'reading' message
	 *
	 * @param $fp
	 * @param $length
	 */
	protected function fread($fp, $length = null)
	{
		if (is_numeric($length))
		{
			if ($length > 0)
			{
				$data = fread($fp, $length);
			}
			else
			{
				$data = fread($fp, PHP_INT_MAX);
			}
		}
		else
		{
			$data = fread($fp, PHP_INT_MAX);
		}
		if ($data === false)
		{
			$data = '';
		}

		// Send start of file notification
		$message                  = new stdClass;
		$message->type            = 'reading';
		$message->content         = new stdClass;
		$message->content->length = strlen($data);
		$this->notify($message);

		return $data;
	}

	/**
	 * Removes the configured $removePath from the path $path
	 *
	 * @param   string $path The path to reduce
	 *
	 * @return  string  The reduced path
	 */
	protected function removePath($path)
	{
		if (empty($this->removePath))
		{
			return $path;
		}

		if (strpos($path, $this->removePath) === 0)
		{
			$path = substr($path, strlen($this->removePath));
			$path = ltrim($path, '/\\');
		}

		return $path;
	}

	/**
	 * Am I supposed to skip the extraction of the current file? This depends on
	 *
	 * @return bool
	 */
	protected function mustSkip()
	{
		static $isDryRun = null;

		// List of files (and patterns) to extract
		static $extractList = null;

		// Internal cache of the last file we checked and whether it must be skipped
		static $lastFileName = '';
		static $mustSkip = false;

		// Make sure the dry run flag is, indeed, populated
		if (is_null($isDryRun))
		{
			$isDryRun = AKFactory::get('kickstart.setup.dryrun', '0');
		}

		// If it's a Kickstart dry run we have to skip the extraction of the file
		if ($isDryRun)
		{
			return true;
		}

		// Make sure I have a list of files and patterns to extract
		if (is_null($extractList))
		{
			$extractList = $this->getExtractList();
		}

		// No list of files to extract is given; we must extract everything.
		if (empty($extractList))
		{
			return false;
		}

		// I am asked about the same file again. Return the cached result.
		if ($this->fileHeader->file == $lastFileName)
		{
			return $mustSkip;
		}

		// Does the current file match the extract patterns or not?
		$lastFileName = $this->fileHeader->file;
		$lastFileName = (strpos($lastFileName, $this->addPath) === 0) ? substr($lastFileName, strlen(rtrim($this->addPath, "\\/")) + 1) : $lastFileName;
		$mustSkip     = !$this->matchesGlobPatterns($lastFileName, $extractList);

		return $mustSkip;
	}

	protected function fuzzySignatureSearch($requiredSignatures, $sigLen)
	{
		if (!is_array($requiredSignatures))
		{
			$requiredSignatures = [$requiredSignatures];
		}

		fseek($this->fp, 0, SEEK_SET);

		$stuff  = $this->fread($this->fp, 131072);
		$maxPos = function_exists('mb_strlen') ? mb_strlen($stuff, 'binary') : strlen($stuff);

		for ($i = 0; $i < $maxPos; $i++)
		{
			foreach ($requiredSignatures as $signature)
			{
				$sigBinary = function_exists('mb_substr') ? mb_substr($stuff, $i, $sigLen, 'binary') : substr($stuff, $i, $sigLen);

				if ($sigBinary === $signature)
				{
					fseek($this->fp, $i, SEEK_SET);

					return true;
				}
			}
		}

		return false;
	}

	/**
	 * Get the list of files / folders to extract. The list can contain filenames or glob patterns.
	 *
	 * @return  array
	 */
	private function getExtractList()
	{
		$rawList = AKFactory::get('kickstart.setup.extract_list', '');

		// Sometimes I could get an array, e.g. from CLI
		if (is_array($rawList))
		{
			$rawList = implode("\n", $rawList);
		}

		// Remove any whitespace
		$rawList = trim($rawList);

		if (empty($rawList))
		{
			return array();
		}

		// Convert commas to newlines so we can support both ways to express lists
		$rawList = str_replace(",", "\n", $rawList);
		$rawList = trim($rawList);

		// Convert the list to an array and clean it
		$list = explode("\n", $rawList);
		$list = array_map('trim', $list);

		return array_unique($list);
	}

	/**
	 * Tests whether the item $item matches the list of shell patterns $list.
	 *
	 * @param   string  $item  The file name to test
	 * @param   array   $list  The list of glob patterns to match
	 *
	 * @return  bool
	 */
	private function matchesGlobPatterns($item, array $list)
	{
		if (empty($list))
		{
			return true;
		}

		foreach ($list as $pattern)
		{
			if (fnmatch($pattern, $item))
			{
				return true;
			}
		}

		return false;
	}
}

/**
 * Akeeba Restore
 * An AJAX-powered archive extraction library for JPA, JPS and ZIP archives
 *
 * @package   restore
 * @copyright Copyright (c)2008-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

/**
 * File post processor engines base class
 */
abstract class AKAbstractPostproc extends AKAbstractObject
{
	/** @var int The UNIX timestamp of the file's desired modification date */
	public $timestamp = 0;
	/** @var string The current (real) file path we'll have to process */
	protected $filename = null;
	/** @var int The requested permissions */
	protected $perms = 0755;
	/** @var string The temporary file path we gave to the unarchiver engine */
	protected $tempFilename = null;
	/** @var string The temporary directory where the data will be stored */
	protected $tempDir = '';

	/**
	 * Processes the current file, e.g. moves it from temp to final location by FTP
	 */
	abstract public function process();

	/**
	 * The unarchiver tells us the path to the filename it wants to extract and we give it
	 * a different path instead.
	 *
	 * @param string $filename The path to the real file
	 * @param int    $perms    The permissions we need the file to have
	 *
	 * @return string The path to the temporary file
	 */
	abstract public function processFilename($filename, $perms = 0755);

	/**
	 * Recursively creates a directory if it doesn't exist
	 *
	 * @param string $dirName The directory to create
	 * @param int    $perms   The permissions to give to that directory
	 */
	abstract public function createDirRecursive($dirName, $perms);

	abstract public function chmod($file, $perms);

	abstract public function unlink($file);

	abstract public function rmdir($directory);

	abstract public function rename($from, $to);

	/**
	 * Returns the configured temporary directory
	 *
	 * @return string
	 */
	public function getTempDir()
	{
		return $this->tempDir;
	}
}


/**
 * Akeeba Restore
 * An AJAX-powered archive extraction library for JPA, JPS and ZIP archives
 *
 * @package   restore
 * @copyright Copyright (c)2008-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

/**
 * Descendants of this class can be used in the unarchiver's observer methods (attach, detach and notify)
 *
 * @author Nicholas
 *
 */
abstract class AKAbstractPartObserver
{
	abstract public function update($object, $message);
}


/**
 * Akeeba Restore
 * An AJAX-powered archive extraction library for JPA, JPS and ZIP archives
 *
 * @package   restore
 * @copyright Copyright (c)2008-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

/**
 * Direct file writer
 */
class AKPostprocDirect extends AKAbstractPostproc
{
	public function process()
	{
		$restorePerms = AKFactory::get('kickstart.setup.restoreperms', false);
		if ($restorePerms)
		{
			@chmod($this->filename, $this->perms);
		}
		else
		{
			if (@is_file($this->filename))
			{
				@chmod($this->filename, 0644);
			}
			else
			{
				@chmod($this->filename, 0755);
			}
		}
		if ($this->timestamp > 0)
		{
			@touch($this->filename, $this->timestamp);
		}

		if (@is_file($this->filename) || @is_link($this->filename))
		{
			clearFileInOPCache($this->filename);
		}

		return true;
	}

	public function processFilename($filename, $perms = 0755)
	{
		$this->perms    = $perms;
		$this->filename = $filename;

		return $filename;
	}

	public function createDirRecursive($dirName, $perms)
	{
		if (AKFactory::get('kickstart.setup.dryrun', '0'))
		{
			return true;
		}

		if (@mkdir($dirName, 0755, true))
		{
			@chmod($dirName, 0755);

			return true;
		}

		$root = AKFactory::get('kickstart.setup.destdir');
		$root = rtrim(str_replace('\\', '/', $root), '/');
		$dir  = rtrim(str_replace('\\', '/', $dirName), '/');
		if (strpos($dir, $root) === 0)
		{
			$dir = ltrim(substr($dir, strlen($root)), '/');
			$root .= '/';
		}
		else
		{
			$root = '';
		}

		if (empty($dir))
		{
			return true;
		}

		$dirArray = explode('/', $dir);
		$path     = '';
		foreach ($dirArray as $dir)
		{
			$path .= $dir . '/';
			$ret = is_dir($root . $path) ? true : @mkdir($root . $path);
			if (!$ret)
			{
				// Is this a file instead of a directory?
				if (is_file($root . $path))
				{
					@unlink($root . $path);
					$ret = @mkdir($root . $path);
				}
				if (!$ret)
				{
					$this->setError(AKText::sprintf('COULDNT_CREATE_DIR', $path));

					return false;
				}
			}
			// Try to set new directory permissions to 0755
			@chmod($root . $path, $perms);
		}

		return true;
	}

	public function chmod($file, $perms)
	{
		if (AKFactory::get('kickstart.setup.dryrun', '0'))
		{
			return true;
		}

		return @chmod($file, $perms);
	}

	public function unlink($file)
	{
		return @unlink($file);
	}

	public function rmdir($directory)
	{
		return @rmdir($directory);
	}

	public function rename($from, $to)
	{
		return @rename($from, $to);
	}

}

/**
 * Akeeba Restore
 * An AJAX-powered archive extraction library for JPA, JPS and ZIP archives
 *
 * @package   restore
 * @copyright Copyright (c)2008-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

/**
 * FTP file writer
 */
class AKPostprocFTP extends AKAbstractPostproc
{
	/** @var bool Should I use FTP over implicit SSL? */
	public $useSSL = false;
	/** @var bool use Passive mode? */
	public $passive = true;
	/** @var string FTP host name */
	public $host = '';
	/** @var int FTP port */
	public $port = 21;
	/** @var string FTP user name */
	public $user = '';
	/** @var string FTP password */
	public $pass = '';
	/** @var string FTP initial directory */
	public $dir = '';
	/** @var resource The FTP handle */
	private $handle = null;

	public function __construct()
	{
		$this->useSSL  = AKFactory::get('kickstart.ftp.ssl', false);
		$this->passive = AKFactory::get('kickstart.ftp.passive', true);
		$this->host    = AKFactory::get('kickstart.ftp.host', '');
		$this->port    = AKFactory::get('kickstart.ftp.port', 21);

		if (trim($this->port) == '')
		{
			$this->port = 21;
		}
		$this->user    = AKFactory::get('kickstart.ftp.user', '');
		$this->pass    = AKFactory::get('kickstart.ftp.pass', '');
		$this->dir     = AKFactory::get('kickstart.ftp.dir', '');
		$this->tempDir = AKFactory::get('kickstart.ftp.tempdir', '');

		$connected = $this->connect();

		if ($connected)
		{
			if (!empty($this->tempDir))
			{
				$tempDir  = rtrim($this->tempDir, '/\\') . '/';
				$writable = $this->isDirWritable($tempDir);
			}
			else
			{
				$tempDir  = '';
				$writable = false;
			}

			if (!$writable)
			{
				// Default temporary directory is the current root
				$tempDir = KSROOTDIR;
				if (empty($tempDir))
				{
					// Oh, we have no directory reported!
					$tempDir = '.';
				}
				$absoluteDirToHere = $tempDir;
				$tempDir           = rtrim(str_replace('\\', '/', $tempDir), '/');

				if (!empty($tempDir))
				{
					$tempDir .= '/';
				}

				$this->tempDir = $tempDir;
				// Is this directory writable?
				$writable = $this->isDirWritable($tempDir);
			}

			if (!$writable)
			{
				// Nope. Let's try creating a temporary directory in the site's root.
				$tempDir                 = $absoluteDirToHere . '/kicktemp';
				$trustMeIKnowWhatImDoing = 500 + 10 + 1; // working around overzealous scanners written by bozos
				$this->createDirRecursive($tempDir, $trustMeIKnowWhatImDoing);
				// Try making it writable...
				$this->fixPermissions($tempDir);
				$writable = $this->isDirWritable($tempDir);
			}

			// Was the new directory writable?
			if (!$writable)
			{
				// Let's see if the user has specified one
				$userdir = AKFactory::get('kickstart.ftp.tempdir', '');

				if (!empty($userdir))
				{
					// Is it an absolute or a relative directory?
					$absolute = false;
					$absolute = $absolute || (substr($userdir, 0, 1) == '/');
					$absolute = $absolute || (substr($userdir, 1, 1) == ':');
					$absolute = $absolute || (substr($userdir, 2, 1) == ':');

					if (!$absolute)
					{
						// Make absolute
						$tempDir = $absoluteDirToHere . $userdir;
					}
					else
					{
						// it's already absolute
						$tempDir = $userdir;
					}
					// Does the directory exist?
					if (is_dir($tempDir))
					{
						// Yeah. Is it writable?
						$writable = $this->isDirWritable($tempDir);
					}
				}
			}

			$this->tempDir = $tempDir;

			if (!$writable)
			{
				// No writable directory found!!!
				$this->setError(AKText::_('FTP_TEMPDIR_NOT_WRITABLE'));
			}
			else
			{
				AKFactory::set('kickstart.ftp.tempdir', $tempDir);
				$this->tempDir = $tempDir;
			}
		}
	}

	public function connect()
	{
		// Connect to server, using SSL if so required
		if ($this->useSSL)
		{
			$this->handle = @ftp_ssl_connect($this->host, $this->port);
		}
		else
		{
			$this->handle = @ftp_connect($this->host, $this->port);
		}

		if ($this->handle === false)
		{
			$this->setError(AKText::_('WRONG_FTP_HOST'));

			return false;
		}

		// Login
		if (!@ftp_login($this->handle, $this->user, $this->pass))
		{
			$this->setError(AKText::_('WRONG_FTP_USER'));
			@ftp_close($this->handle);

			return false;
		}

		// Change to initial directory
		if (!@ftp_chdir($this->handle, $this->dir))
		{
			$this->setError(AKText::_('WRONG_FTP_PATH1'));
			@ftp_close($this->handle);

			return false;
		}

		// Enable passive mode if the user requested it
		if ($this->passive)
		{
			@ftp_pasv($this->handle, true);
		}
		else
		{
			@ftp_pasv($this->handle, false);
		}

		// Try to download ourselves
		$testFilename = defined('KSSELFNAME') ? KSSELFNAME : basename(__FILE__);
		$tempHandle   = fopen('php://temp', 'r+');

		if (@ftp_fget($this->handle, $tempHandle, $testFilename, FTP_ASCII, 0) === false)
		{
			$this->setError(AKText::_('WRONG_FTP_PATH2'));
			@ftp_close($this->handle);
			fclose($tempHandle);

			return false;
		}

		fclose($tempHandle);

		return true;
	}

	private function isDirWritable($dir)
	{
		$fp = @fopen($dir . '/kickstart.dat', 'w');

		if ($fp === false)
		{
			return false;
		}
		else
		{
			@fclose($fp);
			unlink($dir . '/kickstart.dat');

			return true;
		}
	}

	public function createDirRecursive($dirName, $perms)
	{
		// Strip absolute filesystem path to website's root
		$removePath = AKFactory::get('kickstart.setup.destdir', '');

		if (!empty($removePath))
		{
			// UNIXize the paths
			$removePath = str_replace('\\', '/', $removePath);
			$dirName    = str_replace('\\', '/', $dirName);
			// Make sure they both end in a slash
			$removePath = rtrim($removePath, '/\\') . '/';
			$dirName    = rtrim($dirName, '/\\') . '/';
			// Process the path removal
			$left = substr($dirName, 0, strlen($removePath));

			if ($left == $removePath)
			{
				$dirName = substr($dirName, strlen($removePath));
			}
		}

		if (empty($dirName))
		{
			$dirName = '';
		} // 'cause the substr() above may return FALSE.

		$check = '/' . trim($this->dir, '/') . '/' . trim($dirName, '/');

		if ($this->is_dir($check))
		{
			return true;
		}

		$alldirs     = explode('/', $dirName);
		$previousDir = '/' . trim($this->dir);

		foreach ($alldirs as $curdir)
		{
			$check = $previousDir . '/' . $curdir;

			if (!$this->is_dir($check))
			{
				// Proactively try to delete a file by the same name
				@ftp_delete($this->handle, $check);

				if (@ftp_mkdir($this->handle, $check) === false)
				{
					// If we couldn't create the directory, attempt to fix the permissions in the PHP level and retry!
					$this->fixPermissions($removePath . $check);

					if (@ftp_mkdir($this->handle, $check) === false)
					{
						// Can we fall back to pure PHP mode, sire?
						if (!@mkdir($check))
						{
							$this->setError(AKText::sprintf('FTP_CANT_CREATE_DIR', $check));

							return false;
						}
						else
						{
							// Since the directory was built by PHP, change its permissions
							$trustMeIKnowWhatImDoing =
								500 + 10 + 1; // working around overzealous scanners written by bozos
							@chmod($check, $trustMeIKnowWhatImDoing);

							return true;
						}
					}
				}

				@ftp_chmod($this->handle, $perms, $check);

			}

			$previousDir = $check;
		}

		return true;
	}

	private function is_dir($dir)
	{
		return @ftp_chdir($this->handle, $dir);
	}

	private function fixPermissions($path)
	{
		// Turn off error reporting
		if (!defined('KSDEBUG'))
		{
			$oldErrorReporting = @error_reporting(0);
		}

		// Get UNIX style paths
		$relPath  = str_replace('\\', '/', $path);
		$basePath = rtrim(str_replace('\\', '/', KSROOTDIR), '/');
		$basePath = rtrim($basePath, '/');

		if (!empty($basePath))
		{
			$basePath .= '/';
		}

		// Remove the leading relative root
		if (substr($relPath, 0, strlen($basePath)) == $basePath)
		{
			$relPath = substr($relPath, strlen($basePath));
		}

		$dirArray  = explode('/', $relPath);
		$pathBuilt = rtrim($basePath, '/');

		foreach ($dirArray as $dir)
		{
			if (empty($dir))
			{
				continue;
			}
			$oldPath = $pathBuilt;
			$pathBuilt .= '/' . $dir;

			if (is_dir($oldPath . $dir))
			{
				$trustMeIKnowWhatImDoing = 500 + 10 + 1; // working around overzealous scanners written by bozos
				@chmod($oldPath . $dir, $trustMeIKnowWhatImDoing);
			}
			else
			{
				$trustMeIKnowWhatImDoing = 500 + 10 + 1; // working around overzealous scanners written by bozos
				if (@chmod($oldPath . $dir, $trustMeIKnowWhatImDoing) === false)
				{
					@unlink($oldPath . $dir);
				}
			}
		}

		// Restore error reporting
		if (!defined('KSDEBUG'))
		{
			@error_reporting($oldErrorReporting);
		}
	}

	public function __sleep()
	{
		if (!is_null($this->handle) && is_resource($this->handle))
		{
			@ftp_close($this->handle);
		}

		$this->handle = null;
	}

	public function __destruct()
	{
		if (!is_null($this->handle) && is_resource($this->handle))
		{
			@ftp_close($this->handle);
		}
	}


	public function __wakeup()
	{
		$this->connect();
	}

	public function process()
	{
		if (is_null($this->tempFilename))
		{
			// If an empty filename is passed, it means that we shouldn't do any post processing, i.e.
			// the entity was a directory or symlink
			return true;
		}

		$remotePath = dirname($this->filename);
		$removePath = AKFactory::get('kickstart.setup.destdir', '');

		if (!empty($removePath))
		{
			$removePath = ltrim($removePath, "/");
			$remotePath = ltrim($remotePath, "/");
			$left       = substr($remotePath, 0, strlen($removePath));

			if ($left == $removePath)
			{
				$remotePath = substr($remotePath, strlen($removePath));
			}
		}

		$absoluteFSPath  = dirname($this->filename);
		$relativeFTPPath = trim($remotePath, '/');
		$absoluteFTPPath = '/' . trim($this->dir, '/') . '/' . trim($remotePath, '/');
		$onlyFilename    = basename($this->filename);

		$remoteName = $absoluteFTPPath . '/' . $onlyFilename;

		$ret = @ftp_chdir($this->handle, $absoluteFTPPath);

		if ($ret === false)
		{
			$ret = $this->createDirRecursive($absoluteFSPath, 0755);

			if ($ret === false)
			{
				$this->setError(AKText::sprintf('FTP_COULDNT_UPLOAD', $this->filename));

				return false;
			}

			$ret = @ftp_chdir($this->handle, $absoluteFTPPath);

			if ($ret === false)
			{
				$this->setError(AKText::sprintf('FTP_COULDNT_UPLOAD', $this->filename));

				return false;
			}
		}

		$ret = @ftp_put($this->handle, $remoteName, $this->tempFilename, FTP_BINARY);

		if ($ret === false)
		{
			// If we couldn't create the file, attempt to fix the permissions in the PHP level and retry!
			$this->fixPermissions($this->filename);
			$this->unlink($this->filename);

			$fp = @fopen($this->tempFilename, 'r');

			if ($fp !== false)
			{
				$ret = @ftp_fput($this->handle, $remoteName, $fp, FTP_BINARY);
				@fclose($fp);
			}
			else
			{
				$ret = false;
			}
		}

		@unlink($this->tempFilename);

		if ($ret === false)
		{
			$this->setError(AKText::sprintf('FTP_COULDNT_UPLOAD', $this->filename));

			return false;
		}

		$restorePerms = AKFactory::get('kickstart.setup.restoreperms', false);

		if ($restorePerms)
		{
			@ftp_chmod($this->_handle, $this->perms, $remoteName);
		}
		else
		{
			@ftp_chmod($this->_handle, 0644, $remoteName);
		}

		if (@is_file($this->filename) || @is_link($this->filename))
		{
			clearFileInOPCache($this->filename);
		}

		return true;
	}

	/*
	 * Tries to fix directory/file permissions in the PHP level, so that
	 * the FTP operation doesn't fail.
	 * @param $path string The full path to a directory or file
	 */

	public function unlink($file)
	{
		$removePath = AKFactory::get('kickstart.setup.destdir', '');

		if (!empty($removePath))
		{
			$left = substr($file, 0, strlen($removePath));

			if ($left == $removePath)
			{
				$file = substr($file, strlen($removePath));
			}
		}

		$check = '/' . trim($this->dir, '/') . '/' . trim($file, '/');

		return @ftp_delete($this->handle, $check);
	}

	public function processFilename($filename, $perms = 0755)
	{
		// Catch some error conditions...
		if ($this->getError())
		{
			return false;
		}

		// If a null filename is passed, it means that we shouldn't do any post processing, i.e.
		// the entity was a directory or symlink
		if (is_null($filename))
		{
			$this->filename     = null;
			$this->tempFilename = null;

			return null;
		}

		// Strip absolute filesystem path to website's root
		$removePath = AKFactory::get('kickstart.setup.destdir', '');

		if (!empty($removePath))
		{
			$left = substr($filename, 0, strlen($removePath));

			if ($left == $removePath)
			{
				$filename = substr($filename, strlen($removePath));
			}
		}

		// Trim slash on the left
		$filename = ltrim($filename, '/');

		$this->filename     = $filename;
		$this->tempFilename = tempnam($this->tempDir, 'kickstart-');
		$this->perms        = $perms;

		if (empty($this->tempFilename))
		{
			// Oops! Let's try something different
			$this->tempFilename = $this->tempDir . '/kickstart-' . time() . '.dat';
		}

		return $this->tempFilename;
	}

	public function close()
	{
		@ftp_close($this->handle);
	}

	public function chmod($file, $perms)
	{
		return @ftp_chmod($this->handle, $perms, $file);
	}

	public function rmdir($directory)
	{
		$removePath = AKFactory::get('kickstart.setup.destdir', '');

		if (!empty($removePath))
		{
			$left = substr($directory, 0, strlen($removePath));

			if ($left == $removePath)
			{
				$directory = substr($directory, strlen($removePath));
			}
		}

		$check = '/' . trim($this->dir, '/') . '/' . trim($directory, '/');

		return @ftp_rmdir($this->handle, $check);
	}

	public function rename($from, $to)
	{
		$originalFrom = $from;
		$originalTo   = $to;

		$removePath = AKFactory::get('kickstart.setup.destdir', '');

		if (!empty($removePath))
		{
			$left = substr($from, 0, strlen($removePath));

			if ($left == $removePath)
			{
				$from = substr($from, strlen($removePath));
			}
		}

		$from = '/' . trim($this->dir, '/') . '/' . trim($from, '/');

		if (!empty($removePath))
		{
			$left = substr($to, 0, strlen($removePath));

			if ($left == $removePath)
			{
				$to = substr($to, strlen($removePath));
			}
		}

		$to = '/' . trim($this->dir, '/') . '/' . trim($to, '/');

		$result = @ftp_rename($this->handle, $from, $to);

		if ($result !== true)
		{
			return @rename($from, $to);
		}
		else
		{
			return true;
		}
	}

}


/**
 * Akeeba Restore
 * An AJAX-powered archive extraction library for JPA, JPS and ZIP archives
 *
 * @package   restore
 * @copyright Copyright (c)2008-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

/**
 * FTP file writer
 */
class AKPostprocSFTP extends AKAbstractPostproc
{
	/** @var bool Should I use FTP over implicit SSL? */
	public $useSSL = false;
	/** @var bool use Passive mode? */
	public $passive = true;
	/** @var string FTP host name */
	public $host = '';
	/** @var int FTP port */
	public $port = 21;
	/** @var string FTP user name */
	public $user = '';
	/** @var string FTP password */
	public $pass = '';
	/** @var string FTP initial directory */
	public $dir = '';

	/** @var resource SFTP resource handle */
	private $handle = null;

	/** @var resource SSH2 connection resource handle */
	private $_connection = null;

	/** @var string Current remote directory, including the remote directory string */
	private $_currentdir;

	public function __construct()
	{
		$this->host = AKFactory::get('kickstart.ftp.host', '');
		$this->port = AKFactory::get('kickstart.ftp.port', 22);

		if (trim($this->port) == '')
		{
			$this->port = 22;
		}

		$this->user    = AKFactory::get('kickstart.ftp.user', '');
		$this->pass    = AKFactory::get('kickstart.ftp.pass', '');
		$this->dir     = AKFactory::get('kickstart.ftp.dir', '');
		$this->tempDir = AKFactory::get('kickstart.ftp.tempdir', '');

		$connected = $this->connect();

		if ($connected)
		{
			if (!empty($this->tempDir))
			{
				$tempDir  = rtrim($this->tempDir, '/\\') . '/';
				$writable = $this->isDirWritable($tempDir);
			}
			else
			{
				$tempDir  = '';
				$writable = false;
			}

			if (!$writable)
			{
				// Default temporary directory is the current root
				$tempDir = KSROOTDIR;
				if (empty($tempDir))
				{
					// Oh, we have no directory reported!
					$tempDir = '.';
				}
				$absoluteDirToHere = $tempDir;
				$tempDir           = rtrim(str_replace('\\', '/', $tempDir), '/');
				if (!empty($tempDir))
				{
					$tempDir .= '/';
				}
				$this->tempDir = $tempDir;
				// Is this directory writable?
				$writable = $this->isDirWritable($tempDir);
			}

			if (!$writable)
			{
				// Nope. Let's try creating a temporary directory in the site's root.
				$tempDir                 = $absoluteDirToHere . '/kicktemp';
				$trustMeIKnowWhatImDoing = 500 + 10 + 1; // working around overzealous scanners written by bozos
				$this->createDirRecursive($tempDir, $trustMeIKnowWhatImDoing);
				// Try making it writable...
				$this->fixPermissions($tempDir);
				$writable = $this->isDirWritable($tempDir);
			}

			// Was the new directory writable?
			if (!$writable)
			{
				// Let's see if the user has specified one
				$userdir = AKFactory::get('kickstart.ftp.tempdir', '');
				if (!empty($userdir))
				{
					// Is it an absolute or a relative directory?
					$absolute = false;
					$absolute = $absolute || (substr($userdir, 0, 1) == '/');
					$absolute = $absolute || (substr($userdir, 1, 1) == ':');
					$absolute = $absolute || (substr($userdir, 2, 1) == ':');
					if (!$absolute)
					{
						// Make absolute
						$tempDir = $absoluteDirToHere . $userdir;
					}
					else
					{
						// it's already absolute
						$tempDir = $userdir;
					}
					// Does the directory exist?
					if (is_dir($tempDir))
					{
						// Yeah. Is it writable?
						$writable = $this->isDirWritable($tempDir);
					}
				}
			}
			$this->tempDir = $tempDir;

			if (!$writable)
			{
				// No writable directory found!!!
				$this->setError(AKText::_('SFTP_TEMPDIR_NOT_WRITABLE'));
			}
			else
			{
				AKFactory::set('kickstart.ftp.tempdir', $tempDir);
				$this->tempDir = $tempDir;
			}
		}
	}

	public function connect()
	{
		$this->_connection = false;

		if (!function_exists('ssh2_connect'))
		{
			$this->setError(AKText::_('SFTP_NO_SSH2'));

			return false;
		}

		$this->_connection = @ssh2_connect($this->host, $this->port);

		if (!@ssh2_auth_password($this->_connection, $this->user, $this->pass))
		{
			$this->setError(AKText::_('SFTP_WRONG_USER'));

			$this->_connection = false;

			return false;
		}

		$this->handle = @ssh2_sftp($this->_connection);

		// I must have an absolute directory
		if (!$this->dir)
		{
			$this->setError(AKText::_('SFTP_WRONG_STARTING_DIR'));

			return false;
		}

		// Change to initial directory
		if (!$this->sftp_chdir('/'))
		{
			$this->setError(AKText::_('SFTP_WRONG_STARTING_DIR'));

			unset($this->_connection);
			unset($this->handle);

			return false;
		}

		// Try to download ourselves
		$testFilename = defined('KSSELFNAME') ? KSSELFNAME : basename(__FILE__);
		$basePath     = '/' . trim($this->dir, '/');

		if (@fopen("ssh2.sftp://{$this->handle}$basePath/$testFilename", 'r+') === false)
		{
			$this->setError(AKText::_('SFTP_WRONG_STARTING_DIR'));

			unset($this->_connection);
			unset($this->handle);

			return false;
		}

		return true;
	}

	/**
	 * Changes to the requested directory in the remote server. You give only the
	 * path relative to the initial directory and it does all the rest by itself,
	 * including doing nothing if the remote directory is the one we want.
	 *
	 * @param   string $dir The (realtive) remote directory
	 *
	 * @return  bool True if successful, false otherwise.
	 */
	private function sftp_chdir($dir)
	{
		// Strip absolute filesystem path to website's root
		$removePath = AKFactory::get('kickstart.setup.destdir', '');
		if (!empty($removePath))
		{
			// UNIXize the paths
			$removePath = str_replace('\\', '/', $removePath);
			$dir        = str_replace('\\', '/', $dir);

			// Make sure they both end in a slash
			$removePath = rtrim($removePath, '/\\') . '/';
			$dir        = rtrim($dir, '/\\') . '/';

			// Process the path removal
			$left = substr($dir, 0, strlen($removePath));

			if ($left == $removePath)
			{
				$dir = substr($dir, strlen($removePath));
			}
		}

		if (empty($dir))
		{
			// Because the substr() above may return FALSE.
			$dir = '';
		}

		// Calculate "real" (absolute) SFTP path
		$realdir = substr($this->dir, -1) == '/' ? substr($this->dir, 0, strlen($this->dir) - 1) : $this->dir;
		$realdir .= '/' . $dir;
		$realdir = substr($realdir, 0, 1) == '/' ? $realdir : '/' . $realdir;

		if ($this->_currentdir == $realdir)
		{
			// Already there, do nothing
			return true;
		}

		$result = @ssh2_sftp_stat($this->handle, $realdir);

		if ($result === false)
		{
			return false;
		}
		else
		{
			// Update the private "current remote directory" variable
			$this->_currentdir = $realdir;

			return true;
		}
	}

	private function isDirWritable($dir)
	{
		if (@fopen("ssh2.sftp://{$this->handle}$dir/kickstart.dat", 'w') === false)
		{
			return false;
		}
		else
		{
			@ssh2_sftp_unlink($this->handle, $dir . '/kickstart.dat');

			return true;
		}
	}

	public function createDirRecursive($dirName, $perms)
	{
		// Strip absolute filesystem path to website's root
		$removePath = AKFactory::get('kickstart.setup.destdir', '');
		if (!empty($removePath))
		{
			// UNIXize the paths
			$removePath = str_replace('\\', '/', $removePath);
			$dirName    = str_replace('\\', '/', $dirName);
			// Make sure they both end in a slash
			$removePath = rtrim($removePath, '/\\') . '/';
			$dirName    = rtrim($dirName, '/\\') . '/';
			// Process the path removal
			$left = substr($dirName, 0, strlen($removePath));
			if ($left == $removePath)
			{
				$dirName = substr($dirName, strlen($removePath));
			}
		}
		if (empty($dirName))
		{
			$dirName = '';
		} // 'cause the substr() above may return FALSE.

		$check = '/' . trim($this->dir, '/ ') . '/' . trim($dirName, '/');

		if ($this->is_dir($check))
		{
			return true;
		}

		$alldirs     = explode('/', $dirName);
		$previousDir = '/' . trim($this->dir, '/ ');

		foreach ($alldirs as $curdir)
		{
			if (!$curdir)
			{
				continue;
			}

			$check = $previousDir . '/' . $curdir;

			if (!$this->is_dir($check))
			{
				// Proactively try to delete a file by the same name
				@ssh2_sftp_unlink($this->handle, $check);

				if (@ssh2_sftp_mkdir($this->handle, $check) === false)
				{
					// If we couldn't create the directory, attempt to fix the permissions in the PHP level and retry!
					$this->fixPermissions($check);

					if (@ssh2_sftp_mkdir($this->handle, $check) === false)
					{
						// Can we fall back to pure PHP mode, sire?
						if (!@mkdir($check))
						{
							$this->setError(AKText::sprintf('FTP_CANT_CREATE_DIR', $check));

							return false;
						}
						else
						{
							// Since the directory was built by PHP, change its permissions
							$trustMeIKnowWhatImDoing =
								500 + 10 + 1; // working around overzealous scanners written by bozos
							@chmod($check, $trustMeIKnowWhatImDoing);

							return true;
						}
					}
				}

				@ssh2_sftp_chmod($this->handle, $check, $perms);
			}

			$previousDir = $check;
		}

		return true;
	}

	private function is_dir($dir)
	{
		return $this->sftp_chdir($dir);
	}

	private function fixPermissions($path)
	{
		// Turn off error reporting
		if (!defined('KSDEBUG'))
		{
			$oldErrorReporting = @error_reporting(0);
		}

		// Get UNIX style paths
		$relPath  = str_replace('\\', '/', $path);
		$basePath = rtrim(str_replace('\\', '/', KSROOTDIR), '/');
		$basePath = rtrim($basePath, '/');

		if (!empty($basePath))
		{
			$basePath .= '/';
		}

		// Remove the leading relative root
		if (substr($relPath, 0, strlen($basePath)) == $basePath)
		{
			$relPath = substr($relPath, strlen($basePath));
		}

		$dirArray  = explode('/', $relPath);
		$pathBuilt = rtrim($basePath, '/');

		foreach ($dirArray as $dir)
		{
			if (empty($dir))
			{
				continue;
			}

			$oldPath = $pathBuilt;
			$pathBuilt .= '/' . $dir;

			if (is_dir($oldPath . '/' . $dir))
			{
				$trustMeIKnowWhatImDoing = 500 + 10 + 1; // working around overzealous scanners written by bozos
				@chmod($oldPath . '/' . $dir, $trustMeIKnowWhatImDoing);
			}
			else
			{
				$trustMeIKnowWhatImDoing = 500 + 10 + 1; // working around overzealous scanners written by bozos
				if (@chmod($oldPath . '/' . $dir, $trustMeIKnowWhatImDoing) === false)
				{
					@unlink($oldPath . $dir);
				}
			}
		}

		// Restore error reporting
		if (!defined('KSDEBUG'))
		{
			@error_reporting($oldErrorReporting);
		}
	}

	function __wakeup()
	{
		$this->connect();
	}

	/*
	 * Tries to fix directory/file permissions in the PHP level, so that
	 * the FTP operation doesn't fail.
	 * @param $path string The full path to a directory or file
	 */

	public function process()
	{
		if (is_null($this->tempFilename))
		{
			// If an empty filename is passed, it means that we shouldn't do any post processing, i.e.
			// the entity was a directory or symlink
			return true;
		}

		$remotePath      = dirname($this->filename);
		$absoluteFSPath  = dirname($this->filename);
		$absoluteFTPPath = '/' . trim($this->dir, '/') . '/' . trim($remotePath, '/');
		$onlyFilename    = basename($this->filename);

		$remoteName = $absoluteFTPPath . '/' . $onlyFilename;

		$ret = $this->sftp_chdir($absoluteFTPPath);

		if ($ret === false)
		{
			$ret = $this->createDirRecursive($absoluteFSPath, 0755);

			if ($ret === false)
			{
				$this->setError(AKText::sprintf('SFTP_COULDNT_UPLOAD', $this->filename));

				return false;
			}

			$ret = $this->sftp_chdir($absoluteFTPPath);

			if ($ret === false)
			{
				$this->setError(AKText::sprintf('SFTP_COULDNT_UPLOAD', $this->filename));

				return false;
			}
		}

		// Create the file
		$ret = $this->write($this->tempFilename, $remoteName);

		// If I got a -1 it means that I wasn't able to open the file, so I have to stop here
		if ($ret === -1)
		{
			$this->setError(AKText::sprintf('SFTP_COULDNT_UPLOAD', $this->filename));

			return false;
		}

		if ($ret === false)
		{
			// If we couldn't create the file, attempt to fix the permissions in the PHP level and retry!
			$this->fixPermissions($this->filename);
			$this->unlink($this->filename);

			$ret = $this->write($this->tempFilename, $remoteName);
		}

		@unlink($this->tempFilename);

		if ($ret === false)
		{
			$this->setError(AKText::sprintf('SFTP_COULDNT_UPLOAD', $this->filename));

			return false;
		}
		$restorePerms = AKFactory::get('kickstart.setup.restoreperms', false);

		if ($restorePerms)
		{
			$this->chmod($remoteName, $this->perms);
		}
		else
		{
			$this->chmod($remoteName, 0644);
		}

		if (@is_file($this->filename) || @is_link($this->filename))
		{
			clearFileInOPCache($this->filename);
		}

		return true;
	}

	private function write($local, $remote)
	{
		$fp      = @fopen("ssh2.sftp://{$this->handle}$remote", 'w');
		$localfp = @fopen($local, 'r');

		if ($fp === false)
		{
			return -1;
		}

		if ($localfp === false)
		{
			@fclose($fp);

			return -1;
		}

		$res = true;

		while (!feof($localfp) && ($res !== false))
		{
			$buffer = @fread($localfp, 65567);
			$res    = @fwrite($fp, $buffer);
		}

		@fclose($fp);
		@fclose($localfp);

		return $res;
	}

	public function unlink($file)
	{
		$check = '/' . trim($this->dir, '/') . '/' . trim($file, '/');

		return @ssh2_sftp_unlink($this->handle, $check);
	}

	public function chmod($file, $perms)
	{
		return @ssh2_sftp_chmod($this->handle, $file, $perms);
	}

	public function processFilename($filename, $perms = 0755)
	{
		// Catch some error conditions...
		if ($this->getError())
		{
			return false;
		}

		// If a null filename is passed, it means that we shouldn't do any post processing, i.e.
		// the entity was a directory or symlink
		if (is_null($filename))
		{
			$this->filename     = null;
			$this->tempFilename = null;

			return null;
		}

		// Strip absolute filesystem path to website's root
		$removePath = AKFactory::get('kickstart.setup.destdir', '');
		if (!empty($removePath))
		{
			$left = substr($filename, 0, strlen($removePath));
			if ($left == $removePath)
			{
				$filename = substr($filename, strlen($removePath));
			}
		}

		// Trim slash on the left
		$filename = ltrim($filename, '/');

		$this->filename     = $filename;
		$this->tempFilename = tempnam($this->tempDir, 'kickstart-');
		$this->perms        = $perms;

		if (empty($this->tempFilename))
		{
			// Oops! Let's try something different
			$this->tempFilename = $this->tempDir . '/kickstart-' . time() . '.dat';
		}

		return $this->tempFilename;
	}

	public function close()
	{
		unset($this->_connection);
		unset($this->handle);
	}

	public function rmdir($directory)
	{
		$check = '/' . trim($this->dir, '/') . '/' . trim($directory, '/');

		return @ssh2_sftp_rmdir($this->handle, $check);
	}

	public function rename($from, $to)
	{
		$from = '/' . trim($this->dir, '/') . '/' . trim($from, '/');
		$to   = '/' . trim($this->dir, '/') . '/' . trim($to, '/');

		$result = @ssh2_sftp_rename($this->handle, $from, $to);

		if ($result !== true)
		{
			return @rename($from, $to);
		}
		else
		{
			return true;
		}
	}

}


/**
 * Akeeba Restore
 * An AJAX-powered archive extraction library for JPA, JPS and ZIP archives
 *
 * @package   restore
 * @copyright Copyright (c)2008-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

/**
 * Hybrid direct / FTP mode file writer
 */
class AKPostprocHybrid extends AKAbstractPostproc
{

	/** @var bool Should I use the FTP layer? */
	public $useFTP = false;

	/** @var bool Should I use FTP over implicit SSL? */
	public $useSSL = false;

	/** @var bool use Passive mode? */
	public $passive = true;

	/** @var string FTP host name */
	public $host = '';

	/** @var int FTP port */
	public $port = 21;

	/** @var string FTP user name */
	public $user = '';

	/** @var string FTP password */
	public $pass = '';

	/** @var string FTP initial directory */
	public $dir = '';

	/** @var resource The FTP handle */
	private $handle = null;

	/** @var null The FTP connection handle */
	private $_handle = null;

	/**
	 * Public constructor. Tries to connect to the FTP server.
	 */
	public function __construct()
	{
		$this->useFTP  = true;
		$this->useSSL  = AKFactory::get('kickstart.ftp.ssl', false);
		$this->passive = AKFactory::get('kickstart.ftp.passive', true);
		$this->host    = AKFactory::get('kickstart.ftp.host', '');
		$this->port    = AKFactory::get('kickstart.ftp.port', 21);
		$this->user    = AKFactory::get('kickstart.ftp.user', '');
		$this->pass    = AKFactory::get('kickstart.ftp.pass', '');
		$this->dir     = AKFactory::get('kickstart.ftp.dir', '');
		$this->tempDir = AKFactory::get('kickstart.ftp.tempdir', '');

		if (trim($this->port) == '')
		{
			$this->port = 21;
		}

		// If FTP is not configured, skip it altogether
		if (empty($this->host) || empty($this->user) || empty($this->pass))
		{
			$this->useFTP = false;
		}

		// Try to connect to the FTP server
		$connected = $this->connect();

		// If the connection fails, skip FTP altogether
		if (!$connected)
		{
			$this->useFTP = false;
		}

		if ($connected)
		{
			if (!empty($this->tempDir))
			{
				$tempDir  = rtrim($this->tempDir, '/\\') . '/';
				$writable = $this->isDirWritable($tempDir);
			}
			else
			{
				$tempDir  = '';
				$writable = false;
			}

			if (!$writable)
			{
				// Default temporary directory is the current root
				$tempDir = KSROOTDIR;
				if (empty($tempDir))
				{
					// Oh, we have no directory reported!
					$tempDir = '.';
				}
				$absoluteDirToHere = $tempDir;
				$tempDir           = rtrim(str_replace('\\', '/', $tempDir), '/');
				if (!empty($tempDir))
				{
					$tempDir .= '/';
				}
				$this->tempDir = $tempDir;
				// Is this directory writable?
				$writable = $this->isDirWritable($tempDir);
			}

			if (!$writable)
			{
				// Nope. Let's try creating a temporary directory in the site's root.
				$tempDir                 = $absoluteDirToHere . '/kicktemp';
				$trustMeIKnowWhatImDoing = 500 + 10 + 1; // working around overzealous scanners written by bozos
				$this->createDirRecursive($tempDir, $trustMeIKnowWhatImDoing);
				// Try making it writable...
				$this->fixPermissions($tempDir);
				$writable = $this->isDirWritable($tempDir);
			}

			// Was the new directory writable?
			if (!$writable)
			{
				// Let's see if the user has specified one
				$userdir = AKFactory::get('kickstart.ftp.tempdir', '');
				if (!empty($userdir))
				{
					// Is it an absolute or a relative directory?
					$absolute = false;
					$absolute = $absolute || (substr($userdir, 0, 1) == '/');
					$absolute = $absolute || (substr($userdir, 1, 1) == ':');
					$absolute = $absolute || (substr($userdir, 2, 1) == ':');
					if (!$absolute)
					{
						// Make absolute
						$tempDir = $absoluteDirToHere . $userdir;
					}
					else
					{
						// it's already absolute
						$tempDir = $userdir;
					}
					// Does the directory exist?
					if (is_dir($tempDir))
					{
						// Yeah. Is it writable?
						$writable = $this->isDirWritable($tempDir);
					}
				}
			}
			$this->tempDir = $tempDir;

			if (!$writable)
			{
				// No writable directory found!!!
				$this->setError(AKText::_('FTP_TEMPDIR_NOT_WRITABLE'));
			}
			else
			{
				AKFactory::set('kickstart.ftp.tempdir', $tempDir);
				$this->tempDir = $tempDir;
			}
		}
	}

	/**
	 * Tries to connect to the FTP server
	 *
	 * @return bool
	 */
	public function connect()
	{
		if (!$this->useFTP)
		{
			return false;
		}

		// Connect to server, using SSL if so required
		if ($this->useSSL)
		{
			$this->handle = @ftp_ssl_connect($this->host, $this->port);
		}
		else
		{
			$this->handle = @ftp_connect($this->host, $this->port);
		}
		if ($this->handle === false)
		{
			$this->setError(AKText::_('WRONG_FTP_HOST'));

			return false;
		}

		// Login
		if (!@ftp_login($this->handle, $this->user, $this->pass))
		{
			$this->setError(AKText::_('WRONG_FTP_USER'));
			@ftp_close($this->handle);

			return false;
		}

		// Change to initial directory
		if (!@ftp_chdir($this->handle, $this->dir))
		{
			$this->setError(AKText::_('WRONG_FTP_PATH1'));
			@ftp_close($this->handle);

			return false;
		}

		// Enable passive mode if the user requested it
		if ($this->passive)
		{
			@ftp_pasv($this->handle, true);
		}
		else
		{
			@ftp_pasv($this->handle, false);
		}

		// Try to download ourselves
		$testFilename = defined('KSSELFNAME') ? KSSELFNAME : basename(__FILE__);
		$tempHandle   = fopen('php://temp', 'r+');

		if (@ftp_fget($this->handle, $tempHandle, $testFilename, FTP_ASCII, 0) === false)
		{
			$this->setError(AKText::_('WRONG_FTP_PATH2'));
			@ftp_close($this->handle);
			fclose($tempHandle);

			return false;
		}

		fclose($tempHandle);

		return true;
	}

	/**
	 * Is the directory writeable?
	 *
	 * @param string $dir The directory ti check
	 *
	 * @return bool
	 */
	private function isDirWritable($dir)
	{
		$fp = @fopen($dir . '/kickstart.dat', 'w');

		if ($fp === false)
		{
			return false;
		}

		@fclose($fp);
		unlink($dir . '/kickstart.dat');

		return true;
	}

	/**
	 * Create a directory, recursively
	 *
	 * @param string $dirName The directory to create
	 * @param int    $perms   The permissions to give to the directory
	 *
	 * @return bool
	 */
	public function createDirRecursive($dirName, $perms)
	{
		// Strip absolute filesystem path to website's root
		$removePath = AKFactory::get('kickstart.setup.destdir', '');

		if (!empty($removePath))
		{
			// UNIXize the paths
			$removePath = str_replace('\\', '/', $removePath);
			$dirName    = str_replace('\\', '/', $dirName);
			// Make sure they both end in a slash
			$removePath = rtrim($removePath, '/\\') . '/';
			$dirName    = rtrim($dirName, '/\\') . '/';
			// Process the path removal
			$left = substr($dirName, 0, strlen($removePath));

			if ($left == $removePath)
			{
				$dirName = substr($dirName, strlen($removePath));
			}
		}

		// 'cause the substr() above may return FALSE.
		if (empty($dirName))
		{
			$dirName = '';
		}

		$check   = '/' . trim($this->dir, '/') . '/' . trim($dirName, '/');
		$checkFS = $removePath . trim($dirName, '/');

		if ($this->is_dir($check))
		{
			return true;
		}

		$alldirs       = explode('/', $dirName);
		$previousDir   = '/' . trim($this->dir);
		$previousDirFS = rtrim($removePath, '/\\');

		foreach ($alldirs as $curdir)
		{
			$check   = $previousDir . '/' . $curdir;
			$checkFS = $previousDirFS . '/' . $curdir;

			if (!is_dir($checkFS) && !$this->is_dir($check))
			{
				// Proactively try to delete a file by the same name
				if (!@unlink($checkFS) && $this->useFTP)
				{
					@ftp_delete($this->handle, $check);
				}

				$createdDir = @mkdir($checkFS, 0755);

				if (!$createdDir && $this->useFTP)
				{
					$createdDir = @ftp_mkdir($this->handle, $check);
				}

				if ($createdDir === false)
				{
					// If we couldn't create the directory, attempt to fix the permissions in the PHP level and retry!
					$this->fixPermissions($checkFS);

					$createdDir = @mkdir($checkFS, 0755);
					if (!$createdDir && $this->useFTP)
					{
						$createdDir = @ftp_mkdir($this->handle, $check);
					}

					if ($createdDir === false)
					{
						$this->setError(AKText::sprintf('FTP_CANT_CREATE_DIR', $check));

						return false;
					}
				}

				if (!@chmod($checkFS, $perms) && $this->useFTP)
				{
					@ftp_chmod($this->handle, $perms, $check);
				}
			}

			$previousDir   = $check;
			$previousDirFS = $checkFS;
		}

		return true;
	}

	private function is_dir($dir)
	{
		if ($this->useFTP)
		{
			return @ftp_chdir($this->handle, $dir);
		}

		return false;
	}

	/**
	 * Tries to fix directory/file permissions in the PHP level, so that
	 * the FTP operation doesn't fail.
	 *
	 * @param $path string The full path to a directory or file
	 */
	private function fixPermissions($path)
	{
		// Turn off error reporting
		if (!defined('KSDEBUG'))
		{
			$oldErrorReporting = error_reporting(0);
		}

		// Get UNIX style paths
		$relPath  = str_replace('\\', '/', $path);
		$basePath = rtrim(str_replace('\\', '/', KSROOTDIR), '/');
		$basePath = rtrim($basePath, '/');

		if (!empty($basePath))
		{
			$basePath .= '/';
		}

		// Remove the leading relative root
		if (substr($relPath, 0, strlen($basePath)) == $basePath)
		{
			$relPath = substr($relPath, strlen($basePath));
		}

		$dirArray  = explode('/', $relPath);
		$pathBuilt = rtrim($basePath, '/');

		foreach ($dirArray as $dir)
		{
			if (empty($dir))
			{
				continue;
			}

			$oldPath = $pathBuilt;
			$pathBuilt .= '/' . $dir;

			if (is_dir($oldPath . $dir))
			{
				$trustMeIKnowWhatImDoing = 500 + 10 + 1; // working around overzealous scanners written by bozos
				@chmod($oldPath . $dir, $trustMeIKnowWhatImDoing);
			}
			else
			{
				$trustMeIKnowWhatImDoing = 500 + 10 + 1; // working around overzealous scanners written by bozos
				if (@chmod($oldPath . $dir, $trustMeIKnowWhatImDoing) === false)
				{
					@unlink($oldPath . $dir);
				}
			}
		}

		// Restore error reporting
		if (!defined('KSDEBUG'))
		{
			@error_reporting($oldErrorReporting);
		}
	}

	/**
	 * Called after unserialisation, tries to reconnect to FTP
	 */
	public function __wakeup()
	{
		if ($this->useFTP)
		{
			$this->connect();
		}
	}

	public function __sleep()
	{
		if ($this->useFTP)
		{
			if (!is_null($this->_handle) && is_resource($this->_handle))
			{
				@ftp_close($this->_handle);
			}
		}

		$this->_handle = null;
	}


	public function __destruct()
	{
		if ($this->useFTP)
		{
			if (!is_null($this->handle) && is_resource($this->handle))
			{
				@ftp_close($this->handle);
			}
		}
	}

	/**
	 * Post-process an extracted file, using FTP or direct file writes to move it
	 *
	 * @return bool
	 */
	public function process()
	{
		if (is_null($this->tempFilename))
		{
			// If an empty filename is passed, it means that we shouldn't do any post processing, i.e.
			// the entity was a directory or symlink
			return true;
		}

		$remotePath = dirname($this->filename);
		$removePath = AKFactory::get('kickstart.setup.destdir', '');
		$root       = rtrim($removePath, '/\\');

		if (!empty($removePath))
		{
			$removePath = ltrim($removePath, "/");
			$remotePath = ltrim($remotePath, "/");
			$left       = substr($remotePath, 0, strlen($removePath));

			if ($left == $removePath)
			{
				$remotePath = substr($remotePath, strlen($removePath));
			}
		}

		$absoluteFSPath  = dirname($this->filename);
		$relativeFTPPath = trim($remotePath, '/');
		$absoluteFTPPath = '/' . trim($this->dir, '/') . '/' . trim($remotePath, '/');
		$onlyFilename    = basename($this->filename);

		$remoteName = $absoluteFTPPath . '/' . $onlyFilename;

		// Does the directory exist?
		if (!is_dir($root . '/' . $absoluteFSPath))
		{
			$ret = $this->createDirRecursive($absoluteFSPath, 0755);

			if (($ret === false) && ($this->useFTP))
			{
				$ret = @ftp_chdir($this->handle, $absoluteFTPPath);
			}

			if ($ret === false)
			{
				$this->setError(AKText::sprintf('FTP_COULDNT_UPLOAD', $this->filename));

				return false;
			}
		}

		if ($this->useFTP)
		{
			$ret = @ftp_chdir($this->handle, $absoluteFTPPath);
		}

		// Try copying directly
		$ret = @copy($this->tempFilename, $root . '/' . $this->filename);

		if ($ret === false)
		{
			$this->fixPermissions($this->filename);
			$this->unlink($this->filename);

			$ret = @copy($this->tempFilename, $root . '/' . $this->filename);
		}

		if ($this->useFTP && ($ret === false))
		{
			$ret = @ftp_put($this->handle, $remoteName, $this->tempFilename, FTP_BINARY);

			if ($ret === false)
			{
				// If we couldn't create the file, attempt to fix the permissions in the PHP level and retry!
				$this->fixPermissions($this->filename);
				$this->unlink($this->filename);

				$fp = @fopen($this->tempFilename, 'r');
				if ($fp !== false)
				{
					$ret = @ftp_fput($this->handle, $remoteName, $fp, FTP_BINARY);
					@fclose($fp);
				}
				else
				{
					$ret = false;
				}
			}
		}

		@unlink($this->tempFilename);

		if ($ret === false)
		{
			$this->setError(AKText::sprintf('FTP_COULDNT_UPLOAD', $this->filename));

			return false;
		}

		$restorePerms = AKFactory::get('kickstart.setup.restoreperms', false);
		$perms        = $restorePerms ? $this->perms : 0644;

		$ret = @chmod($root . '/' . $this->filename, $perms);

		if ($this->useFTP && ($ret === false))
		{
			@ftp_chmod($this->_handle, $perms, $remoteName);
		}

		if (@is_file($this->filename) || @is_link($this->filename))
		{
			clearFileInOPCache($this->filename);
		}

		return true;
	}

	public function unlink($file)
	{
		$ret = @unlink($file);

		if (!$ret && $this->useFTP)
		{
			$removePath = AKFactory::get('kickstart.setup.destdir', '');
			if (!empty($removePath))
			{
				$left = substr($file, 0, strlen($removePath));
				if ($left == $removePath)
				{
					$file = substr($file, strlen($removePath));
				}
			}

			$check = '/' . trim($this->dir, '/') . '/' . trim($file, '/');

			$ret = @ftp_delete($this->handle, $check);
		}

		return $ret;
	}

	/**
	 * Create a temporary filename
	 *
	 * @param string $filename The original filename
	 * @param int    $perms    The file permissions
	 *
	 * @return string
	 */
	public function processFilename($filename, $perms = 0755)
	{
		// Catch some error conditions...
		if ($this->getError())
		{
			return false;
		}

		// If a null filename is passed, it means that we shouldn't do any post processing, i.e.
		// the entity was a directory or symlink
		if (is_null($filename))
		{
			$this->filename     = null;
			$this->tempFilename = null;

			return null;
		}

		// Strip absolute filesystem path to website's root
		$removePath = AKFactory::get('kickstart.setup.destdir', '');

		if (!empty($removePath))
		{
			$left = substr($filename, 0, strlen($removePath));

			if ($left == $removePath)
			{
				$filename = substr($filename, strlen($removePath));
			}
		}

		// Trim slash on the left
		$filename = ltrim($filename, '/');

		$this->filename     = $filename;
		$this->tempFilename = tempnam($this->tempDir, 'kickstart-');
		$this->perms        = $perms;

		if (empty($this->tempFilename))
		{
			// Oops! Let's try something different
			$this->tempFilename = $this->tempDir . '/kickstart-' . time() . '.dat';
		}

		return $this->tempFilename;
	}

	/**
	 * Closes the FTP connection
	 */
	public function close()
	{
		if (!$this->useFTP)
		{
			@ftp_close($this->handle);
		}
	}

	public function chmod($file, $perms)
	{
		if (AKFactory::get('kickstart.setup.dryrun', '0'))
		{
			return true;
		}

		$ret = @chmod($file, $perms);

		if (!$ret && $this->useFTP)
		{
			// Strip absolute filesystem path to website's root
			$removePath = AKFactory::get('kickstart.setup.destdir', '');

			if (!empty($removePath))
			{
				$left = substr($file, 0, strlen($removePath));

				if ($left == $removePath)
				{
					$file = substr($file, strlen($removePath));
				}
			}

			// Trim slash on the left
			$file = ltrim($file, '/');

			$ret = @ftp_chmod($this->handle, $perms, $file);
		}

		return $ret;
	}

	public function rmdir($directory)
	{
		$ret = @rmdir($directory);

		if (!$ret && $this->useFTP)
		{
			$removePath = AKFactory::get('kickstart.setup.destdir', '');
			if (!empty($removePath))
			{
				$left = substr($directory, 0, strlen($removePath));
				if ($left == $removePath)
				{
					$directory = substr($directory, strlen($removePath));
				}
			}

			$check = '/' . trim($this->dir, '/') . '/' . trim($directory, '/');

			$ret = @ftp_rmdir($this->handle, $check);
		}

		return $ret;
	}

	public function rename($from, $to)
	{
		$ret = @rename($from, $to);

		if (!$ret && $this->useFTP)
		{
			$originalFrom = $from;
			$originalTo   = $to;

			$removePath = AKFactory::get('kickstart.setup.destdir', '');
			if (!empty($removePath))
			{
				$left = substr($from, 0, strlen($removePath));
				if ($left == $removePath)
				{
					$from = substr($from, strlen($removePath));
				}
			}
			$from = '/' . trim($this->dir, '/') . '/' . trim($from, '/');

			if (!empty($removePath))
			{
				$left = substr($to, 0, strlen($removePath));
				if ($left == $removePath)
				{
					$to = substr($to, strlen($removePath));
				}
			}
			$to = '/' . trim($this->dir, '/') . '/' . trim($to, '/');

			$ret = @ftp_rename($this->handle, $from, $to);
		}

		return $ret;
	}
}

/**
 * Akeeba Restore
 * An AJAX-powered archive extraction library for JPA, JPS and ZIP archives
 *
 * @package   restore
 * @copyright Copyright (c)2008-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

/**
 * JPA archive extraction class
 */
class AKUnarchiverJPA extends AKAbstractUnarchiver
{
	protected $archiveHeaderData = [];

	protected function readArchiveHeader()
	{
		debugMsg('Preparing to read archive header');
		// Initialize header data array
		$this->archiveHeaderData = new stdClass();

		// Open the first part
		debugMsg('Opening the first part');
		$this->nextFile();

		// Fail for unreadable files
		if ($this->fp === false)
		{
			debugMsg('Could not open the first part');

			return false;
		}

		// Fuzzy check for the start of archive.
		debugMsg('Fuzzy checking for archive signature');

		$sigFound = $this->fuzzySignatureSearch([
			'JPA',
		], 3);

		if (!$sigFound)
		{
			debugMsg('Cannot find a valid archive signature in the first 128Kb of the first part file');

			$this->setError(AKText::sprintf('ERR_INVALID_ARCHIVE_LONG', 'jpa', 'j'));

			return false;
		}

		debugMsg(sprintf('File signature found, position %d', ftell($this->fp)));

		// Read the signature
		$sig = fread($this->fp, 3);

		if ($sig != 'JPA')
		{
			// Not a JPA file
			debugMsg('Invalid archive signature');
			$this->setError(AKText::sprintf('ERR_INVALID_ARCHIVE_LONG', 'jpa', 'j'));

			return false;
		}

		// Read and parse header length
		$header_length_array = unpack('v', fread($this->fp, 2));
		$header_length       = $header_length_array[1];

		// Read and parse the known portion of header data (14 bytes)
		$bin_data    = fread($this->fp, 14);
		$header_data = unpack('Cmajor/Cminor/Vcount/Vuncsize/Vcsize', $bin_data);

		// Temporary array with all the data we read
		$temp = [
			'signature'        => $sig,
			'length'           => $header_length,
			'major'            => $header_data['major'],
			'minor'            => $header_data['minor'],
			'filecount'        => $header_data['count'],
			'uncompressedsize' => $header_data['uncsize'],
			'compressedsize'   => $header_data['csize'],
			'unknowndata'      => '',
		];

		// Load additional header data
		$rest_length = $header_length - 19;
		$junk        = '';

		while ($rest_length > 8)
		{
			// Read the extra length signature and size
			$extraSig    = fread($this->fp, 4);
			$binData     = fread($this->fp, 2);
			$extraHeader = unpack('vlength', $binData);
			$length      = $extraHeader['length'] - 2;

			$rest_length -= 6 + $length;

			switch ($extraSig)
			{
				case "\x4A\x50\x01\x01":
					$moreBinData        = fread($this->fp, $length);
					$moreExtraHeader    = unpack('vtotalParts', $moreBinData);
					$temp['totalParts'] = $moreExtraHeader['totalParts'];
					break;

				case "\x4A\x50\x01\x02":
					$moreBinData              = fread($this->fp, $length);

					// Only decode on 64-bit versions of PHP
					if (PHP_INT_SIZE >= 8)
					{
						$moreExtraHeader          = unpack('Puncompressed/Pcompressed', $moreBinData);
						$header_data['uncsize']   = $moreExtraHeader['uncompressed'];
						$header_data['csize']     = $moreExtraHeader['compressed'];
						$temp['uncompressedsize'] = $moreExtraHeader['uncompressed'];
						$temp['compressedsize']   = $moreExtraHeader['compressed'];
					}

					break;

				default:
					$moreBinData = fread($this->fp, $length);
					$junk        .= $extraSig . $binData . $moreBinData;
					break;
			}
		}

		if ($rest_length > 0)
		{
			$junk .= fread($this->fp, $rest_length);
		}
		else
		{
			$junk .= '';
		}

		// Array-to-object conversion
		foreach ($temp as $key => $value)
		{
			$this->archiveHeaderData->{$key} = $value;
		}

		debugMsg('Header data:');
		debugMsg('Length              : ' . $header_length);
		debugMsg('Major               : ' . $header_data['major']);
		debugMsg('Minor               : ' . $header_data['minor']);
		debugMsg('File count          : ' . $header_data['count']);
		debugMsg('Uncompressed size   : ' . $header_data['uncsize']);
		debugMsg('Compressed size     : ' . $header_data['csize']);
		debugMsg('Total Parts         : ' . (isset($header_data['totalParts']) ? $header_data['totalParts'] : '1'));

		$this->currentPartOffset = @ftell($this->fp);

		$this->dataReadLength = 0;

		return true;
	}

	/**
	 * Concrete classes must use this method to read the file header
	 *
	 * @return bool True if reading the file was successful, false if an error occurred or we reached end of archive
	 */
	protected function readFileHeader()
	{
		// If the current part is over, proceed to the next part please
		if ($this->isEOF(true))
		{
			debugMsg('Archive part EOF; moving to next file');
			$this->nextFile();
		}

		$this->currentPartOffset = ftell($this->fp);

		debugMsg("Reading file signature; part {$this->currentPartNumber}, offset {$this->currentPartOffset}");
		// Get and decode Entity Description Block
		$signature = fread($this->fp, 3);

		$this->fileHeader            = new stdClass();
		$this->fileHeader->timestamp = 0;

		// Check signature
		if ($signature != 'JPF')
		{
			if ($this->isEOF(true))
			{
				// This file is finished; make sure it's the last one
				$gotNextFile = $this->nextFile();

				if (!$gotNextFile && $this->getState() !== 'postrun')
				{
					debugMsg(sprintf('Cannot open file %s for part #%d', $this->archiveList[$this->currentPartNumber] ?: '(unknown)', $this->currentPartNumber));

					$this->setError(AKText::sprintf(
						'INVALID_FILE_HEADER_OFFSET_ZERO',
						$this->archiveList[$this->currentPartNumber] ?: '(unknown)',
						$this->currentPartNumber,
						'jpa',
						'j'
					));

					return false;
				}

				if (!$this->isEOF(false))
				{
					debugMsg('Invalid file signature before end of archive encountered');
					$this->setError(AKText::sprintf(
						'INVALID_FILE_HEADER',
						$this->currentPartNumber,
						$this->currentPartOffset,
						'jpa',
						'j'
					));

					return false;
				}

				// We're just finished
				return false;
			}
			else
			{
				$screwed = true;

				if (AKFactory::get('kickstart.setup.ignoreerrors', false))
				{
					debugMsg('Invalid file block signature; launching heuristic file block signature scanner');
					$screwed = !$this->heuristicFileHeaderLocator();

					if (!$screwed)
					{
						$signature = 'JPF';
					}
					else
					{
						debugMsg('Heuristics failed. Brace yourself for the imminent crash.');
					}
				}

				if ($screwed)
				{
					// This is not a file block! The archive is corrupt.
					debugMsg('Invalid file block signature');

					if (count($this->archiveList) > 1)
					{
						$this->setError(AKText::sprintf(
							'INVALID_FILE_HEADER_MULTIPART',
							$this->currentPartNumber,
							$this->currentPartOffset,
							'jpa',
							'j'
						));

						return false;
					}

					$this->setError(AKText::sprintf('INVALID_FILE_HEADER', $this->currentPartNumber, $this->currentPartOffset));

					return false;
				}
			}
		}
		// This a JPA Entity Block. Process the header.

		$isBannedFile = false;

		// Read length of EDB and of the Entity Path Data
		$length_array = unpack('vblocksize/vpathsize', fread($this->fp, 4));
		// Read the path data
		if ($length_array['pathsize'] > 0)
		{
			$file = fread($this->fp, $length_array['pathsize']);
		}
		else
		{
			$file = '';
		}

		// Handle file renaming
		$isRenamed = false;
		if (is_array($this->renameFiles) && (count($this->renameFiles) > 0))
		{
			if (array_key_exists($file, $this->renameFiles))
			{
				$file      = $this->renameFiles[$file];
				$isRenamed = true;
			}
		}

		// Handle directory renaming
		$isDirRenamed = false;
		if (is_array($this->renameDirs) && (count($this->renameDirs) > 0))
		{
			if (array_key_exists(dirname($file), $this->renameDirs))
			{
				$file         = rtrim($this->renameDirs[dirname($file)], '/') . '/' . basename($file);
				$isRenamed    = true;
				$isDirRenamed = true;
			}
		}

		// Read and parse the known data portion
		$bin_data    = fread($this->fp, 14);
		$header_data = unpack('Ctype/Ccompression/Vcompsize/Vuncompsize/Vperms', $bin_data);
		// Read any unknown data
		$restBytes = $length_array['blocksize'] - (21 + $length_array['pathsize']);

		if ($restBytes > 0)
		{
			// Start reading the extra fields
			while ($restBytes >= 4)
			{
				$extra_header_data      = fread($this->fp, 4);
				$extra_header           = unpack('vsignature/vlength', $extra_header_data);
				$restBytes              -= 4;
				$extra_header['length'] -= 4;

				if ($extra_header['length'] > 0)
				{
					switch ($extra_header['signature'])
					{
						case 256:
							// File modified timestamp
							$bindata                     = fread($this->fp, $extra_header['length']);
							$restBytes                   -= $extra_header['length'];
							$timestamps                  = unpack('Vmodified', substr($bindata, 0, 4));
							$filectime                   = $timestamps['modified'];
							$this->fileHeader->timestamp = $filectime;
							break;

						case 512:
							$bindata                   = fread($this->fp, $extra_header['length']);
							$restBytes                 -= $extra_header['length'];

							// Only decode on 64-bit versions of PHP
							if (PHP_INT_SIZE >= 8)
							{
								$sizes                     = unpack('Pclen/Punclen', $bindata);
								$header_data['compsize']   = $sizes['clen'];
								$header_data['uncompsize'] = $sizes['unclen'];
							}
							break;

						default:
							// Unknown field
							$junk      = fread($this->fp, $extra_header['length']);
							$restBytes -= $extra_header['length'];
							break;
					}
				}

			}

			if ($restBytes > 0)
			{
				$junk = fread($this->fp, $restBytes);
			}
		}

		$compressionType = $header_data['compression'];

		// Populate the return array
		$this->fileHeader->file         = $file;
		$this->fileHeader->compressed   = $header_data['compsize'];
		$this->fileHeader->uncompressed = $header_data['uncompsize'];

		switch ($header_data['type'])
		{
			case 0:
				$this->fileHeader->type = 'dir';
				break;

			case 1:
				$this->fileHeader->type = 'file';
				break;

			case 2:
				$this->fileHeader->type = 'link';
				break;
		}

		switch ($compressionType)
		{
			case 0:
				$this->fileHeader->compression = 'none';
				break;
			case 1:
				$this->fileHeader->compression = 'gzip';
				break;
			case 2:
				$this->fileHeader->compression = 'bzip2';
				break;
		}

		$this->fileHeader->permissions = $header_data['perms'];

		// Find hard-coded banned files
		if ((basename($this->fileHeader->file) == ".") || (basename($this->fileHeader->file) == ".."))
		{
			$isBannedFile = true;
		}

		// Also try to find banned files passed in class configuration
		if ((count($this->skipFiles) > 0) && (!$isRenamed))
		{
			if (in_array($this->fileHeader->file, $this->skipFiles))
			{
				$isBannedFile = true;
			}
		}

		// If we have a banned file, let's skip it
		if ($isBannedFile)
		{
			debugMsg('Skipping file ' . $this->fileHeader->file);
			// Advance the file pointer, skipping exactly the size of the compressed data
			$seekleft = $this->fileHeader->compressed;
			while ($seekleft > 0)
			{
				// Ensure that we can seek past archive part boundaries
				$curSize = @filesize($this->archiveList[$this->currentPartNumber]);
				$curPos  = @ftell($this->fp);
				$canSeek = $curSize - $curPos;
				if ($canSeek > $seekleft)
				{
					$canSeek = $seekleft;
				}
				@fseek($this->fp, $canSeek, SEEK_CUR);
				$seekleft -= $canSeek;
				if ($seekleft)
				{
					$this->nextFile();
				}
			}

			$this->currentPartOffset = @ftell($this->fp);
			$this->runState          = AK_STATE_DONE;

			return true;
		}

		// Remove the removePath, if any
		$this->fileHeader->file = $this->removePath($this->fileHeader->file);

		// Last chance to prepend a path to the filename
		if (!empty($this->addPath) && !$isDirRenamed)
		{
			$this->fileHeader->file = $this->addPath . $this->fileHeader->file;
		}

		// Get the translated path name
		$restorePerms = AKFactory::get('kickstart.setup.restoreperms', false);

		if (!$this->mustSkip())
		{
			if ($this->fileHeader->type == 'file')
			{
				// Regular file; ask the postproc engine to process its filename
				if ($restorePerms)
				{
					$this->fileHeader->realFile =
						$this->postProcEngine->processFilename($this->fileHeader->file, $this->fileHeader->permissions);
				}
				else
				{
					$this->fileHeader->realFile = $this->postProcEngine->processFilename($this->fileHeader->file);
				}
			}
			elseif ($this->fileHeader->type == 'dir')
			{
				$dir = $this->fileHeader->file;

				// Directory; just create it
				if ($restorePerms)
				{
					$this->postProcEngine->createDirRecursive($dir, $this->fileHeader->permissions);
				}
				else
				{
					$this->postProcEngine->createDirRecursive($dir, 0755);
				}

				$this->postProcEngine->processFilename(null);
			}
			else
			{
				// Symlink; do not post-process
				$this->postProcEngine->processFilename(null);
			}

			$this->createDirectory();
		}

		// Header is read
		$this->runState = AK_STATE_HEADER;

		$this->dataReadLength = 0;

		return true;
	}

	protected function heuristicFileHeaderLocator()
	{
		$ret     = false;
		$fullEOF = false;

		while (!$ret && !$fullEOF)
		{
			$this->currentPartOffset = @ftell($this->fp);

			if ($this->isEOF(true))
			{
				$this->nextFile();
			}

			if ($this->isEOF(false))
			{
				$fullEOF = true;
				continue;
			}

			// Read 512Kb
			$chunk     = fread($this->fp, 524288);
			$size_read = mb_strlen($chunk, '8bit');
			//$pos = strpos($chunk, 'JPF');
			$pos = mb_strpos($chunk, 'JPF', 0, '8bit');

			if ($pos !== false)
			{
				// We found it!
				$this->currentPartOffset += $pos + 3;
				@fseek($this->fp, $this->currentPartOffset, SEEK_SET);
				$ret = true;
			}
			else
			{
				// Not yet found :(
				$this->currentPartOffset = @ftell($this->fp);
			}
		}

		return $ret;
	}

	/**
	 * Creates the directory this file points to
	 */
	protected function createDirectory()
	{
		if ($this->mustSkip())
		{
			return true;
		}

		// Do we need to create a directory?
		if (empty($this->fileHeader->realFile))
		{
			$this->fileHeader->realFile = $this->fileHeader->file;
		}

		$lastSlash = strrpos($this->fileHeader->realFile, '/');
		$dirName   = substr($this->fileHeader->realFile, 0, $lastSlash);
		$perms     = $this->flagRestorePermissions ? $this->fileHeader->permissions : 0755;
		$ignore    = AKFactory::get('kickstart.setup.ignoreerrors', false) || $this->isIgnoredDirectory($dirName);

		if (($this->postProcEngine->createDirRecursive($dirName, $perms) == false) && (!$ignore))
		{
			$this->setError(AKText::sprintf('COULDNT_CREATE_DIR', $dirName));

			return false;
		}
		else
		{
			return true;
		}
	}

	/**
	 * Concrete classes must use this method to process file data. It must set $runState to AK_STATE_DATAREAD when
	 * it's finished processing the file data.
	 *
	 * @return bool True if processing the file data was successful, false if an error occurred
	 */
	protected function processFileData()
	{
		switch ($this->fileHeader->type)
		{
			case 'dir':
				return $this->processTypeDir();
				break;

			case 'link':
				return $this->processTypeLink();
				break;

			case 'file':
				switch ($this->fileHeader->compression)
				{
					case 'none':
						return $this->processTypeFileUncompressed();
						break;

					case 'gzip':
					case 'bzip2':
						return $this->processTypeFileCompressedSimple();
						break;

				}
				break;

			default:
				debugMsg('Unknown file type ' . $this->fileHeader->type);
				break;
		}
	}

	/**
	 * Process the file data of a directory entry
	 *
	 * @return bool
	 */
	private function processTypeDir()
	{
		// Directory entries in the JPA do not have file data, therefore we're done processing the entry
		$this->runState = AK_STATE_DATAREAD;

		return true;
	}

	/**
	 * Process the file data of a link entry
	 *
	 * @return bool
	 */
	private function processTypeLink()
	{
		$readBytes   = 0;
		$toReadBytes = 0;
		$leftBytes   = $this->fileHeader->compressed;
		$data        = '';

		while ($leftBytes > 0)
		{
			$toReadBytes     = ($leftBytes > $this->chunkSize) ? $this->chunkSize : $leftBytes;
			$mydata          = $this->fread($this->fp, $toReadBytes);
			$reallyReadBytes = akstringlen($mydata);
			$data            .= $mydata;
			$leftBytes       -= $reallyReadBytes;

			if ($reallyReadBytes < $toReadBytes)
			{
				// We read less than requested! Why? Did we hit local EOF?
				if ($this->isEOF(true) && !$this->isEOF(false))
				{
					// Yeap. Let's go to the next file
					$this->nextFile();
				}
				else
				{
					debugMsg('End of local file before reading all data with no more parts left. The archive is corrupt or truncated.');
					// Nope. The archive is corrupt
					$this->setError(AKText::_('ERR_CORRUPT_ARCHIVE'));

					return false;
				}
			}
		}

		$filename = isset($this->fileHeader->realFile) ? $this->fileHeader->realFile : $this->fileHeader->file;

		if (!$this->mustSkip())
		{
			// Try to remove an existing file or directory by the same name
			if (file_exists($filename))
			{
				@unlink($filename);
				@rmdir($filename);
			}

			// Remove any trailing slash
			if (substr($filename, -1) == '/')
			{
				$filename = substr($filename, 0, -1);
			}
			// Create the symlink - only possible within PHP context. There's no support built in the FTP protocol, so no postproc use is possible here :(
			@symlink($data, $filename);
		}

		$this->runState = AK_STATE_DATAREAD;

		return true; // No matter if the link was created!
	}

	private function processTypeFileUncompressed()
	{
		// Uncompressed files are being processed in small chunks, to avoid timeouts
		if (($this->dataReadLength == 0) && !$this->mustSkip())
		{
			// Before processing file data, ensure permissions are adequate
			$this->setCorrectPermissions($this->fileHeader->file);
		}

		// Open the output file
		if (!$this->mustSkip())
		{
			$ignore =
				AKFactory::get('kickstart.setup.ignoreerrors', false) || $this->isIgnoredDirectory($this->fileHeader->file);

			if ($this->dataReadLength == 0)
			{
				$outfp = @fopen($this->fileHeader->realFile, 'w');
			}
			else
			{
				$outfp = @fopen($this->fileHeader->realFile, 'a');
			}

			// Can we write to the file?
			if (($outfp === false) && (!$ignore))
			{
				// An error occurred
				debugMsg('Could not write to output file');
				$this->setError(AKText::sprintf('COULDNT_WRITE_FILE', $this->fileHeader->realFile));

				return false;
			}
		}

		// Does the file have any data, at all?
		if ($this->fileHeader->compressed == 0)
		{
			// No file data!
			if (!$this->mustSkip() && is_resource($outfp))
			{
				@fclose($outfp);
			}

			$this->runState = AK_STATE_DATAREAD;

			return true;
		}

		// Reference to the global timer
		$timer = AKFactory::getTimer();

		$toReadBytes = 0;
		$leftBytes   = $this->fileHeader->compressed - $this->dataReadLength;

		// Loop while there's data to read and enough time to do it
		while (($leftBytes > 0) && ($timer->getTimeLeft() > 0))
		{
			$toReadBytes          = ($leftBytes > $this->chunkSize) ? $this->chunkSize : $leftBytes;
			$data                 = $this->fread($this->fp, $toReadBytes);
			$reallyReadBytes      = akstringlen($data);
			$leftBytes            -= $reallyReadBytes;
			$this->dataReadLength += $reallyReadBytes;

			if ($reallyReadBytes < $toReadBytes)
			{
				// We read less than requested! Why? Did we hit local EOF?
				if ($this->isEOF(true) && !$this->isEOF(false))
				{
					// Yeap. Let's go to the next file
					$this->nextFile();
				}
				else
				{
					// Nope. The archive is corrupt
					debugMsg('Not enough data in file. The archive is truncated or corrupt.');
					$this->setError(AKText::_('ERR_CORRUPT_ARCHIVE'));

					return false;
				}
			}

			if (!$this->mustSkip())
			{
				if (is_resource($outfp))
				{
					@fwrite($outfp, $data);
				}
			}
		}

		// Close the file pointer
		if (!$this->mustSkip())
		{
			if (is_resource($outfp))
			{
				@fclose($outfp);
			}
		}

		// Was this a pre-timeout bail out?
		if ($leftBytes > 0)
		{
			$this->runState = AK_STATE_DATA;
		}
		else
		{
			// Oh! We just finished!
			$this->runState       = AK_STATE_DATAREAD;
			$this->dataReadLength = 0;
		}

		return true;
	}

	private function processTypeFileCompressedSimple()
	{
		if (!$this->mustSkip())
		{
			// Before processing file data, ensure permissions are adequate
			$this->setCorrectPermissions($this->fileHeader->file);

			// Open the output file
			$outfp = @fopen($this->fileHeader->realFile, 'w');

			// Can we write to the file?
			$ignore =
				AKFactory::get('kickstart.setup.ignoreerrors', false) || $this->isIgnoredDirectory($this->fileHeader->file);

			if (($outfp === false) && (!$ignore))
			{
				// An error occurred
				debugMsg('Could not write to output file');
				$this->setError(AKText::sprintf('COULDNT_WRITE_FILE', $this->fileHeader->realFile));

				return false;
			}
		}

		// Does the file have any data, at all?
		if ($this->fileHeader->compressed == 0)
		{
			// No file data!
			if (!$this->mustSkip())
			{
				if (is_resource($outfp))
				{
					@fclose($outfp);
				}
			}
			$this->runState = AK_STATE_DATAREAD;

			return true;
		}

		// Simple compressed files are processed as a whole; we can't do chunk processing
		$zipData = $this->fread($this->fp, $this->fileHeader->compressed);
		while (akstringlen($zipData) < $this->fileHeader->compressed)
		{
			// End of local file before reading all data, but have more archive parts?
			if ($this->isEOF(true) && !$this->isEOF(false))
			{
				// Yeap. Read from the next file
				$this->nextFile();
				$bytes_left = $this->fileHeader->compressed - akstringlen($zipData);
				$zipData    .= $this->fread($this->fp, $bytes_left);
			}
			else
			{
				debugMsg('End of local file before reading all data with no more parts left. The archive is corrupt or truncated.');
				$this->setError(AKText::_('ERR_CORRUPT_ARCHIVE'));

				return false;
			}
		}

		if ($this->fileHeader->compression == 'gzip')
		{
			$unzipData = gzinflate($zipData);
		}
		elseif ($this->fileHeader->compression == 'bzip2')
		{
			$unzipData = bzdecompress($zipData);
		}
		unset($zipData);

		// Write to the file.
		if (!$this->mustSkip() && is_resource($outfp))
		{
			@fwrite($outfp, $unzipData, $this->fileHeader->uncompressed);
			@fclose($outfp);
		}
		unset($unzipData);

		$this->runState = AK_STATE_DATAREAD;

		return true;
	}
}

/**
 * Akeeba Restore
 * An AJAX-powered archive extraction library for JPA, JPS and ZIP archives
 *
 * @package   restore
 * @copyright Copyright (c)2008-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

/**
 * ZIP archive extraction class
 *
 * Since the file data portion of ZIP and JPA are similarly structured (it's empty for dirs,
 * linked node name for symlinks, dumped binary data for no compressions and dumped gzipped
 * binary data for gzip compression) we just have to subclass AKUnarchiverJPA and change the
 * header reading bits. Reusable code ;)
 */
class AKUnarchiverZIP extends AKUnarchiverJPA
{
	var $expectDataDescriptor = false;

	protected function readArchiveHeader()
	{
		debugMsg('Preparing to read archive header');
		// Initialize header data array
		$this->archiveHeaderData = new stdClass();

		// Open the first part
		debugMsg('Opening the first part');
		$this->nextFile();

		// Fail for unreadable files
		if ($this->fp === false)
		{
			debugMsg('The first part is not readable');

			return false;
		}

		// Fuzzy check for the start of archive.
		debugMsg('Fuzzy checking for archive signature');

		$sigFound = $this->fuzzySignatureSearch(array(
			pack('V', 0x08074b50), // Multi-part ZIP
			pack('V', 0x30304b50), // Multi-part ZIP (alternate)
			pack('V', 0x04034b50)  // Single file
		), 4);

		if (!$sigFound)
		{
			debugMsg('Cannot find a valid archive signature in the first 128Kb of the first part file');

			$this->setError(AKText::sprintf('ERR_INVALID_ARCHIVE_LONG', 'zip', 'z'));

			return false;
		}

		debugMsg(sprintf('File signature found, position %d', ftell($this->fp)));

		// Read a possible multipart signature
		$sigBinary  = fread($this->fp, 4);
		$headerData = unpack('Vsig', $sigBinary);

		// Roll back if it's not a multipart archive
		if ($headerData['sig'] == 0x04034b50)
		{
			debugMsg('The archive is not multipart');
			fseek($this->fp, -4, SEEK_CUR);
		}
		else
		{
			debugMsg('The archive is multipart');
		}

		$multiPartSigs = array(
			0x08074b50, // Multi-part ZIP
			0x30304b50, // Multi-part ZIP (alternate)
			0x04034b50  // Single file
		);
		if (!in_array($headerData['sig'], $multiPartSigs))
		{
			debugMsg('Invalid header signature ' . dechex($headerData['sig']));
			$this->setError(AKText::sprintf('ERR_INVALID_ARCHIVE_LONG', 'zip', 'z'));

			return false;
		}

		$this->currentPartOffset = @ftell($this->fp);
		debugMsg('Current part offset after reading header: ' . $this->currentPartOffset);

		$this->dataReadLength = 0;

		return true;
	}

	/**
	 * Concrete classes must use this method to read the file header
	 *
	 * @return bool True if reading the file was successful, false if an error occurred or we reached end of archive
	 */
	protected function readFileHeader()
	{
		// If the current part is over, proceed to the next part please
		if ($this->isEOF(true))
		{
			debugMsg('Opening next archive part');
			$gotNextFile = $this->nextFile();
		}

		$this->currentPartOffset = ftell($this->fp);

		if ($this->expectDataDescriptor)
		{
			// The last file had bit 3 of the general purpose bit flag set. This means that we have a
			// 12 byte data descriptor we need to skip. To make things worse, there might also be a 4
			// byte optional data descriptor header (0x08074b50).
			$junk = @fread($this->fp, 4);
			$junk = unpack('Vsig', $junk);
			if ($junk['sig'] == 0x08074b50)
			{
				// Yes, there was a signature
				$junk = @fread($this->fp, 12);
				debugMsg('Data descriptor (w/ header) skipped at ' . (ftell($this->fp) - 12));
			}
			else
			{
				// No, there was no signature, just read another 8 bytes
				$junk = @fread($this->fp, 8);
				debugMsg('Data descriptor (w/out header) skipped at ' . (ftell($this->fp) - 8));
			}

			// And check for EOF, too
			if ($this->isEOF(true))
			{
				debugMsg('EOF before reading header');

				$gotNextFile = $this->nextFile();
			}
		}

		// Get and decode Local File Header
		$headerBinary = fread($this->fp, 30);
		$headerData   =
			unpack('Vsig/C2ver/vbitflag/vcompmethod/vlastmodtime/vlastmoddate/Vcrc/Vcompsize/Vuncomp/vfnamelen/veflen', $headerBinary);

		// Check signature
		if (!($headerData['sig'] == 0x04034b50))
		{
			debugMsg('Not a file signature at ' . (ftell($this->fp) - 4));

			// The signature is not the one used for files. Is this a central directory record (i.e. we're done)?
			if ($headerData['sig'] == 0x02014b50)
			{
				debugMsg('EOCD signature at ' . (ftell($this->fp) - 4));
				// End of ZIP file detected. We'll just skip to the end of file...
				while ($this->nextFile())
				{
				};
				@fseek($this->fp, 0, SEEK_END); // Go to EOF
				return false;
			}
			else
			{
				if (isset($gotNextFile) && !$gotNextFile && $this->getState() !== 'postrun')
				{
					debugMsg(sprintf('Cannot open file %s for part #%d', $this->archiveList[$this->currentPartNumber] ?: '(unknown)', $this->currentPartNumber));

					$this->setError(AKText::sprintf(
						'INVALID_FILE_HEADER_OFFSET_ZERO',
						$this->archiveList[$this->currentPartNumber] ?: '(unknown)',
						$this->currentPartNumber,
						'zip',
						'z'
					));

					return false;
				}

				if ($this->currentPartOffset === 0 && $this->currentPartNumber > 0)
				{
					$this->setError(AKText::sprintf(
						'INVALID_FILE_HEADER_MULTIPART',
						$this->currentPartNumber,
						$this->currentPartOffset,
						'jpa',
						'j'
					));

					return false;
				}

				debugMsg('Invalid signature ' . dechex($headerData['sig']) . ' at ' . ftell($this->fp));

				if (count($this->archiveList) > 1)
				{
					$this->setError(AKText::sprintf(
						'INVALID_FILE_HEADER_MULTIPART',
						$this->currentPartNumber,
						$this->currentPartOffset,
						'zip',
						'z'
					));

					return false;
				}

				$this->setError(AKText::sprintf(
					'INVALID_FILE_HEADER',
					$this->currentPartNumber,
					$this->currentPartOffset,
					'zip',
					'z'
				));

				return false;
			}
		}

		// If bit 3 of the bitflag is set, expectDataDescriptor is true
		$this->expectDataDescriptor = ($headerData['bitflag'] & 4) == 4;

		$this->fileHeader            = new stdClass();
		$this->fileHeader->timestamp = 0;

		// Read the last modified data and time
		$lastmodtime = $headerData['lastmodtime'];
		$lastmoddate = $headerData['lastmoddate'];

		if ($lastmoddate && $lastmodtime)
		{
			// ----- Extract time
			$v_hour    = ($lastmodtime & 0xF800) >> 11;
			$v_minute  = ($lastmodtime & 0x07E0) >> 5;
			$v_seconde = ($lastmodtime & 0x001F) * 2;

			// ----- Extract date
			$v_year  = (($lastmoddate & 0xFE00) >> 9) + 1980;
			$v_month = ($lastmoddate & 0x01E0) >> 5;
			$v_day   = $lastmoddate & 0x001F;

			// ----- Get UNIX date format
			$this->fileHeader->timestamp = @mktime($v_hour, $v_minute, $v_seconde, $v_month, $v_day, $v_year);
		}

		$isBannedFile = false;

		$this->fileHeader->compressed   = $headerData['compsize'];
		$this->fileHeader->uncompressed = $headerData['uncomp'];
		$nameFieldLength                = $headerData['fnamelen'];
		$extraFieldLength               = $headerData['eflen'];

		// Read filename field
		$this->fileHeader->file = fread($this->fp, $nameFieldLength);

		// Handle file renaming
		$isRenamed = false;
		if (is_array($this->renameFiles) && (count($this->renameFiles) > 0))
		{
			if (array_key_exists($this->fileHeader->file, $this->renameFiles))
			{
				$this->fileHeader->file = $this->renameFiles[$this->fileHeader->file];
				$isRenamed              = true;
			}
		}

		// Handle directory renaming
		$isDirRenamed = false;
		if (is_array($this->renameDirs) && (count($this->renameDirs) > 0))
		{
			if (array_key_exists(dirname($this->fileHeader->file), $this->renameDirs))
			{
				$file         =
					rtrim($this->renameDirs[dirname($this->fileHeader->file)], '/') . '/' . basename($this->fileHeader->file);
				$isRenamed    = true;
				$isDirRenamed = true;
			}
		}

		// Read extra field if present
		if ($extraFieldLength > 0)
		{
			$extrafield = fread($this->fp, $extraFieldLength);
		}

		debugMsg('*' . ftell($this->fp) . ' IS START OF ' . $this->fileHeader->file . ' (' . $this->fileHeader->compressed . ' bytes)');


		// Decide filetype -- Check for directories
		$this->fileHeader->type = 'file';
		if (strrpos($this->fileHeader->file, '/') == strlen($this->fileHeader->file) - 1)
		{
			$this->fileHeader->type = 'dir';
		}
		// Decide filetype -- Check for symbolic links
		if (($headerData['ver1'] == 10) && ($headerData['ver2'] == 3))
		{
			$this->fileHeader->type = 'link';
		}

		switch ($headerData['compmethod'])
		{
			case 0:
				$this->fileHeader->compression = 'none';
				break;
			case 8:
				$this->fileHeader->compression = 'gzip';
				break;
		}

		// Find hard-coded banned files
		if ((basename($this->fileHeader->file) == ".") || (basename($this->fileHeader->file) == ".."))
		{
			$isBannedFile = true;
		}

		// Also try to find banned files passed in class configuration
		if ((count($this->skipFiles) > 0) && (!$isRenamed))
		{
			if (in_array($this->fileHeader->file, $this->skipFiles))
			{
				$isBannedFile = true;
			}
		}

		// If we have a banned file, let's skip it
		if ($isBannedFile)
		{
			// Advance the file pointer, skipping exactly the size of the compressed data
			$seekleft = $this->fileHeader->compressed;
			while ($seekleft > 0)
			{
				// Ensure that we can seek past archive part boundaries
				$curSize = @filesize($this->archiveList[$this->currentPartNumber]);
				$curPos  = @ftell($this->fp);
				$canSeek = $curSize - $curPos;
				if ($canSeek > $seekleft)
				{
					$canSeek = $seekleft;
				}
				@fseek($this->fp, $canSeek, SEEK_CUR);
				$seekleft -= $canSeek;
				if ($seekleft)
				{
					$this->nextFile();
				}
			}

			$this->currentPartOffset = @ftell($this->fp);
			$this->runState          = AK_STATE_DONE;

			return true;
		}

		// Remove the removePath, if any
		$this->fileHeader->file = $this->removePath($this->fileHeader->file);

		// Last chance to prepend a path to the filename
		if (!empty($this->addPath) && !$isDirRenamed)
		{
			$this->fileHeader->file = $this->addPath . $this->fileHeader->file;
		}

		// Get the translated path name
		if (!$this->mustSkip())
		{
			if ($this->fileHeader->type == 'file')
			{
				$this->fileHeader->realFile = $this->postProcEngine->processFilename($this->fileHeader->file);
			}
			elseif ($this->fileHeader->type == 'dir')
			{
				$this->fileHeader->timestamp = 0;

				$dir = $this->fileHeader->file;

				$this->postProcEngine->createDirRecursive($dir, 0755);
				$this->postProcEngine->processFilename(null);
			}
			else
			{
				// Symlink; do not post-process
				$this->fileHeader->timestamp = 0;
				$this->postProcEngine->processFilename(null);
			}

			$this->createDirectory();
		}

		// Header is read
		$this->runState = AK_STATE_HEADER;

		return true;
	}

}

/**
 * Akeeba Restore
 * An AJAX-powered archive extraction library for JPA, JPS and ZIP archives
 *
 * @package   restore
 * @copyright Copyright (c)2008-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

/**
 * JPS archive extraction class
 */
class AKUnarchiverJPS extends AKUnarchiverJPA
{
	/**
	 * Header data for the archive
	 *
	 * @var   array
	 */
	protected $archiveHeaderData = array();

	/**
	 * Plaintext password from which the encryption key will be derived with PBKDF2
	 *
	 * @var   string
	 */
	protected $password = '';

	/**
	 * Which hash algorithm should I use for key derivation with PBKDF2.
	 *
	 * @var   string
	 */
	private $pbkdf2Algorithm = 'sha1';

	/**
	 * How many iterations should I use for key derivation with PBKDF2
	 *
	 * @var   int
	 */
	private $pbkdf2Iterations = 1000;

	/**
	 * Should I use a static salt for key derivation with PBKDF2?
	 *
	 * @var   bool
	 */
	private $pbkdf2UseStaticSalt = 0;

	/**
	 * Static salt for key derivation with PBKDF2
	 *
	 * @var   string
	 */
	private $pbkdf2StaticSalt = "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0";

	/**
	 * How much compressed data I have read since the last file header read
	 *
	 * @var   int
	 */
	private $compressedSizeReadSinceLastFileHeader = 0;

	public function __construct()
	{
		$this->password = AKFactory::get('kickstart.jps.password', '');
	}

	public function __wakeup()
	{
		parent::__wakeup();

		// Make sure the decryption is all set up (required!)
		AKEncryptionAES::setPbkdf2Algorithm($this->pbkdf2Algorithm);
		AKEncryptionAES::setPbkdf2Iterations($this->pbkdf2Iterations);
		AKEncryptionAES::setPbkdf2UseStaticSalt($this->pbkdf2UseStaticSalt);
		AKEncryptionAES::setPbkdf2StaticSalt($this->pbkdf2StaticSalt);
	}


	protected function readArchiveHeader()
	{
		// Initialize header data array
		$this->archiveHeaderData = new stdClass();

		// Open the first part
		$this->nextFile();

		// Fail for unreadable files
		if ($this->fp === false)
		{
			return false;
		}

		// Fuzzy check for the start of archive.
		debugMsg('Fuzzy checking for archive signature');

		$sigFound = $this->fuzzySignatureSearch(array(
			'JPS'
		), 3);

		if (!$sigFound)
		{
			debugMsg('Cannot find a valid archive signature in the first 128Kb of the first part file');

			$this->setError(AKText::sprintf('ERR_INVALID_ARCHIVE_LONG', 'jps', 'j'));

			return false;
		}

		debugMsg(sprintf('File signature found, position %d', ftell($this->fp)));

		// Read the signature
		$sig = fread($this->fp, 3);

		if ($sig != 'JPS')
		{
			// Not a JPS file
			$this->setError(AKText::sprintf('ERR_INVALID_ARCHIVE_LONG', 'jps', 'j'));

			return false;
		}

		// Read and parse the known portion of header data (5 bytes)
		$bin_data    = fread($this->fp, 5);
		$header_data = unpack('Cmajor/Cminor/cspanned/vextra', $bin_data);

		// Is this a v2 archive?
		$versionHumanReadable = $header_data['major'] . '.' . $header_data['minor'];
		$isV2Archive = version_compare($versionHumanReadable, '2.0', 'ge');

		// Load any remaining header data
		$rest_length = $header_data['extra'];

		if ($isV2Archive && $rest_length)
		{
			// V2 archives only have one kind of extra header
			if (!$this->readKeyExpansionExtraHeader())
			{
				return false;
			}
		}
		elseif ($rest_length > 0)
		{
			$junk = fread($this->fp, $rest_length);
		}

		// Temporary array with all the data we read
		$temp = array(
			'signature' => $sig,
			'major'     => $header_data['major'],
			'minor'     => $header_data['minor'],
			'spanned'   => $header_data['spanned']
		);
		// Array-to-object conversion
		foreach ($temp as $key => $value)
		{
			$this->archiveHeaderData->{$key} = $value;
		}

		$this->currentPartOffset = @ftell($this->fp);

		$this->dataReadLength = 0;

		return true;
	}

	/**
	 * Concrete classes must use this method to read the file header
	 *
	 * @return bool True if reading the file was successful, false if an error occurred or we reached end of archive
	 */
	protected function readFileHeader()
	{
		// If the current part is over, proceed to the next part please
		if ($this->isEOF(true))
		{
			$this->nextFile();
		}

		$this->currentPartOffset = ftell($this->fp);

		// Get and decode Entity Description Block
		$signature = fread($this->fp, 3);

		// Check for end-of-archive siganture
		if ($signature == 'JPE')
		{
			$this->setState('postrun');

			return true;
		}

		$this->fileHeader            = new stdClass();
		$this->fileHeader->timestamp = 0;

		// Check signature
		if ($signature != 'JPF')
		{
			if ($this->isEOF(true))
			{
				// This file is finished; make sure it's the last one
				$gotNextFile = $this->nextFile();

				if (!$gotNextFile && $this->getState() !== 'postrun')
				{
					debugMsg(sprintf('Cannot open file %s for part #%d', $this->archiveList[$this->currentPartNumber] ?: '(unknown)', $this->currentPartNumber));

					$this->setError(AKText::sprintf(
						'INVALID_FILE_HEADER_OFFSET_ZERO',
						$this->archiveList[$this->currentPartNumber] ?: '(unknown)',
						$this->currentPartNumber,
						'jps',
						'j'
					));

					return false;
				}

				if (!$this->isEOF(false))
				{
					$this->setError(AKText::sprintf('INVALID_FILE_HEADER', $this->currentPartNumber, $this->currentPartOffset));

					return false;
				}

				// We're just finished
				return false;
			}
			else
			{
				fseek($this->fp, -6, SEEK_CUR);
				$signature = fread($this->fp, 3);
				if ($signature == 'JPE')
				{
					return false;
				}

				if (count($this->archiveList) > 1)
				{
					$this->setError(AKText::sprintf(
						'INVALID_FILE_HEADER_MULTIPART',
						$this->currentPartNumber,
						$this->currentPartOffset,
						'jps',
						'j'
					));

					return false;
				}

				$this->setError(AKText::sprintf(
					'INVALID_FILE_HEADER',
					$this->currentPartNumber,
					$this->currentPartOffset,
					'jps',
					'j'
				));

				return false;
			}
		}

		// This a JPS Entity Block. Process the header.

		$isBannedFile = false;

		// Make sure the decryption is all set up
		AKEncryptionAES::setPbkdf2Algorithm($this->pbkdf2Algorithm);
		AKEncryptionAES::setPbkdf2Iterations($this->pbkdf2Iterations);
		AKEncryptionAES::setPbkdf2UseStaticSalt($this->pbkdf2UseStaticSalt);
		AKEncryptionAES::setPbkdf2StaticSalt($this->pbkdf2StaticSalt);

		// Read and decrypt the header
		$edbhData = fread($this->fp, 4);
		$edbh     = unpack('vencsize/vdecsize', $edbhData);
		$bin_data = fread($this->fp, $edbh['encsize']);

		// Add the header length to the data read
		$this->compressedSizeReadSinceLastFileHeader += $edbh['encsize'] + 4;

		// Decrypt and truncate
		$bin_data = AKEncryptionAES::AESDecryptCBC($bin_data, $this->password);
		$bin_data = substr($bin_data, 0, $edbh['decsize']);

		// Read length of EDB and of the Entity Path Data
		$length_array = unpack('vpathsize', substr($bin_data, 0, 2));
		// Read the path data
		$file = substr($bin_data, 2, $length_array['pathsize']);

		// Handle file renaming
		$isRenamed = false;
		if (is_array($this->renameFiles) && (count($this->renameFiles) > 0))
		{
			if (array_key_exists($file, $this->renameFiles))
			{
				$file      = $this->renameFiles[$file];
				$isRenamed = true;
			}
		}

		// Handle directory renaming
		$isDirRenamed = false;
		if (is_array($this->renameDirs) && (count($this->renameDirs) > 0))
		{
			if (array_key_exists(dirname($file), $this->renameDirs))
			{
				$file         = rtrim($this->renameDirs[dirname($file)], '/') . '/' . basename($file);
				$isRenamed    = true;
				$isDirRenamed = true;
			}
		}

		// Read and parse the known data portion
		$bin_data    = substr($bin_data, 2 + $length_array['pathsize']);
		$header_data = unpack('Ctype/Ccompression/Vuncompsize/Vperms/Vfilectime', $bin_data);

		$this->fileHeader->timestamp = $header_data['filectime'];
		$compressionType             = $header_data['compression'];

		// Populate the return array
		$this->fileHeader->file         = $file;
		$this->fileHeader->uncompressed = $header_data['uncompsize'];
		switch ($header_data['type'])
		{
			case 0:
				$this->fileHeader->type = 'dir';
				break;

			case 1:
				$this->fileHeader->type = 'file';
				break;

			case 2:
				$this->fileHeader->type = 'link';
				break;
		}
		switch ($compressionType)
		{
			case 0:
				$this->fileHeader->compression = 'none';
				break;
			case 1:
				$this->fileHeader->compression = 'gzip';
				break;
			case 2:
				$this->fileHeader->compression = 'bzip2';
				break;
		}
		$this->fileHeader->permissions = $header_data['perms'];

		// Find hard-coded banned files
		if ((basename($this->fileHeader->file) == ".") || (basename($this->fileHeader->file) == ".."))
		{
			$isBannedFile = true;
		}

		// Also try to find banned files passed in class configuration
		if ((count($this->skipFiles) > 0) && (!$isRenamed))
		{
			if (in_array($this->fileHeader->file, $this->skipFiles))
			{
				$isBannedFile = true;
			}
		}

		// If we have a banned file, let's skip it
		if ($isBannedFile)
		{
			$done = false;
			while (!$done)
			{
				// Read the Data Chunk Block header
				$binMiniHead = fread($this->fp, 8);
				if (in_array(substr($binMiniHead, 0, 3), array('JPF', 'JPE')))
				{
					// Not a Data Chunk Block header, I am done skipping the file
					@fseek($this->fp, -8, SEEK_CUR); // Roll back the file pointer
					$done = true; // Mark as done
					continue; // Exit loop
				}
				else
				{
					// Skip forward by the amount of compressed data
					$miniHead = unpack('Vencsize/Vdecsize', $binMiniHead);
					@fseek($this->fp, $miniHead['encsize'], SEEK_CUR);
					$this->compressedSizeReadSinceLastFileHeader += 8 + $miniHead['encsize'];
				}
			}

			$this->currentPartOffset                     = @ftell($this->fp);
			$this->runState                              = AK_STATE_DONE;
			$this->fileHeader->compressed                = $this->compressedSizeReadSinceLastFileHeader;
			$this->compressedSizeReadSinceLastFileHeader = 0;

			return true;
		}

		// Remove the removePath, if any
		$this->fileHeader->file = $this->removePath($this->fileHeader->file);

		// Last chance to prepend a path to the filename
		if (!empty($this->addPath) && !$isDirRenamed)
		{
			$this->fileHeader->file = $this->addPath . $this->fileHeader->file;
		}

		// Get the translated path name
		$restorePerms = AKFactory::get('kickstart.setup.restoreperms', false);

		if (!$this->mustSkip())
		{
			if ($this->fileHeader->type == 'file')
			{
				// Regular file; ask the postproc engine to process its filename
				if ($restorePerms)
				{
					$this->fileHeader->realFile =
						$this->postProcEngine->processFilename($this->fileHeader->file, $this->fileHeader->permissions);
				}
				else
				{
					$this->fileHeader->realFile = $this->postProcEngine->processFilename($this->fileHeader->file);
				}
			}
			elseif ($this->fileHeader->type == 'dir')
			{
				$dir                        = $this->fileHeader->file;
				$this->fileHeader->realFile = $dir;

				// Directory; just create it
				if ($restorePerms)
				{
					$this->postProcEngine->createDirRecursive($this->fileHeader->file, $this->fileHeader->permissions);
				}
				else
				{
					$this->postProcEngine->createDirRecursive($this->fileHeader->file, 0755);
				}

				$this->postProcEngine->processFilename(null);
			}
			else
			{
				// Symlink; do not post-process
				$this->postProcEngine->processFilename(null);
			}

			$this->createDirectory();
		}


		$this->fileHeader->compressed                = $this->compressedSizeReadSinceLastFileHeader;
		$this->compressedSizeReadSinceLastFileHeader = 0;

		// Header is read
		$this->runState = AK_STATE_HEADER;

		$this->dataReadLength = 0;

		return true;
	}

	/**
	 * Creates the directory this file points to
	 */
	protected function createDirectory()
	{
		if ($this->mustSkip())
		{
			return true;
		}

		// Do we need to create a directory?
		$lastSlash = strrpos($this->fileHeader->realFile, '/');
		$dirName   = substr($this->fileHeader->realFile, 0, $lastSlash);
		$perms     = 0755;
		$ignore    = AKFactory::get('kickstart.setup.ignoreerrors', false) || $this->isIgnoredDirectory($dirName);

		if (($this->postProcEngine->createDirRecursive($dirName, $perms) == false) && (!$ignore))
		{
			$this->setError(AKText::sprintf('COULDNT_CREATE_DIR', $dirName));

			return false;
		}

		return true;
	}

	/**
	 * Concrete classes must use this method to process file data. It must set $runState to AK_STATE_DATAREAD when
	 * it's finished processing the file data.
	 *
	 * @return bool True if processing the file data was successful, false if an error occurred
	 */
	protected function processFileData()
	{
		switch ($this->fileHeader->type)
		{
			case 'dir':
				return $this->processTypeDir();
				break;

			case 'link':
				return $this->processTypeLink();
				break;

			case 'file':
				switch ($this->fileHeader->compression)
				{
					case 'none':
						return $this->processTypeFileUncompressed();
						break;

					case 'gzip':
					case 'bzip2':
						return $this->processTypeFileCompressedSimple();
						break;

				}
				break;
		}
	}

	/**
	 * Process the file data of a directory entry
	 *
	 * @return bool
	 */
	private function processTypeDir()
	{
		// Directory entries in the JPA do not have file data, therefore we're done processing the entry
		$this->runState = AK_STATE_DATAREAD;

		return true;
	}

	/**
	 * Process the file data of a link entry
	 *
	 * @return bool
	 */
	private function processTypeLink()
	{

		// Does the file have any data, at all?
		if ($this->fileHeader->uncompressed == 0)
		{
			// No file data!
			$this->runState = AK_STATE_DATAREAD;

			return true;
		}

		// Read the mini header
		$binMiniHeader   = fread($this->fp, 8);
		$reallyReadBytes = akstringlen($binMiniHeader);

		if ($reallyReadBytes < 8)
		{
			// We read less than requested! Why? Did we hit local EOF?
			if ($this->isEOF(true) && !$this->isEOF(false))
			{
				// Yeap. Let's go to the next file
				$this->nextFile();
				// Retry reading the header
				$binMiniHeader   = fread($this->fp, 8);
				$reallyReadBytes = akstringlen($binMiniHeader);
				// Still not enough data? If so, the archive is corrupt or missing parts.
				if ($reallyReadBytes < 8)
				{
					$this->setError(AKText::_('ERR_CORRUPT_ARCHIVE'));

					return false;
				}
			}
			else
			{
				// Nope. The archive is corrupt
				$this->setError(AKText::_('ERR_CORRUPT_ARCHIVE'));

				return false;
			}
		}

		// Read the encrypted data
		$miniHeader      = unpack('Vencsize/Vdecsize', $binMiniHeader);
		$toReadBytes     = $miniHeader['encsize'];
		$data            = $this->fread($this->fp, $toReadBytes);
		$reallyReadBytes = akstringlen($data);
		$this->compressedSizeReadSinceLastFileHeader += 8 + $miniHeader['encsize'];

		if ($reallyReadBytes < $toReadBytes)
		{
			// We read less than requested! Why? Did we hit local EOF?
			if ($this->isEOF(true) && !$this->isEOF(false))
			{
				// Yeap. Let's go to the next file
				$this->nextFile();
				// Read the rest of the data
				$toReadBytes -= $reallyReadBytes;
				$restData        = $this->fread($this->fp, $toReadBytes);
				$reallyReadBytes = akstringlen($data);
				if ($reallyReadBytes < $toReadBytes)
				{
					$this->setError(AKText::_('ERR_CORRUPT_ARCHIVE'));

					return false;
				}
				$data .= $restData;
			}
			else
			{
				// Nope. The archive is corrupt
				$this->setError(AKText::_('ERR_CORRUPT_ARCHIVE'));

				return false;
			}
		}

		// Decrypt the data
		$data = AKEncryptionAES::AESDecryptCBC($data, $this->password);

		// Is the length of the decrypted data less than expected?
		$data_length = akstringlen($data);
		if ($data_length < $miniHeader['decsize'])
		{
			$this->setError(AKText::_('ERR_INVALID_JPS_PASSWORD'));

			return false;
		}

		// Trim the data
		$data = substr($data, 0, $miniHeader['decsize']);

		if (!$this->mustSkip())
		{
			// Try to remove an existing file or directory by the same name
			if (file_exists($this->fileHeader->file))
			{
				@unlink($this->fileHeader->file);
				@rmdir($this->fileHeader->file);
			}
			// Remove any trailing slash
			if (substr($this->fileHeader->file, -1) == '/')
			{
				$this->fileHeader->file = substr($this->fileHeader->file, 0, -1);
			}
			// Create the symlink - only possible within PHP context. There's no support built in the FTP protocol, so no postproc use is possible here :(
			@symlink($data, $this->fileHeader->file);
		}

		$this->runState = AK_STATE_DATAREAD;

		return true; // No matter if the link was created!
	}

	private function processTypeFileUncompressed()
	{
		// Uncompressed files are being processed in small chunks, to avoid timeouts
		if (($this->dataReadLength == 0) && !$this->mustSkip())
		{
			// Before processing file data, ensure permissions are adequate
			$this->setCorrectPermissions($this->fileHeader->file);
		}

		// Open the output file
		if (!$this->mustSkip())
		{
			$ignore =
				AKFactory::get('kickstart.setup.ignoreerrors', false) || $this->isIgnoredDirectory($this->fileHeader->file);
			if ($this->dataReadLength == 0)
			{
				$outfp = @fopen($this->fileHeader->realFile, 'w');
			}
			else
			{
				$outfp = @fopen($this->fileHeader->realFile, 'a');
			}

			// Can we write to the file?
			if (($outfp === false) && (!$ignore))
			{
				// An error occurred
				$this->setError(AKText::sprintf('COULDNT_WRITE_FILE', $this->fileHeader->realFile));

				return false;
			}
		}

		// Does the file have any data, at all?
		if ($this->fileHeader->uncompressed == 0)
		{
			// No file data!
			if (!$this->mustSkip() && is_resource($outfp))
			{
				@fclose($outfp);
			}
			$this->runState = AK_STATE_DATAREAD;

			return true;
		}

		$this->setError('An uncompressed file was detected; this is not supported by this archive extraction utility');

		return false;
	}

	private function processTypeFileCompressedSimple()
	{
		$timer = AKFactory::getTimer();

		// Files are being processed in small chunks, to avoid timeouts
		if (($this->dataReadLength == 0) && !$this->mustSkip())
		{
			// Before processing file data, ensure permissions are adequate
			$this->setCorrectPermissions($this->fileHeader->file);
		}

		// Open the output file
		if (!$this->mustSkip())
		{
			// Open the output file
			$outfp = @fopen($this->fileHeader->realFile, 'w');

			// Can we write to the file?
			$ignore =
				AKFactory::get('kickstart.setup.ignoreerrors', false) || $this->isIgnoredDirectory($this->fileHeader->file);
			if (($outfp === false) && (!$ignore))
			{
				// An error occurred
				$this->setError(AKText::sprintf('COULDNT_WRITE_FILE', $this->fileHeader->realFile));

				return false;
			}
		}

		// Does the file have any data, at all?
		if ($this->fileHeader->uncompressed == 0)
		{
			// No file data!
			if (!$this->mustSkip())
			{
				if (is_resource($outfp))
				{
					@fclose($outfp);
				}
			}
			$this->runState = AK_STATE_DATAREAD;

			return true;
		}

		$leftBytes = $this->fileHeader->uncompressed - $this->dataReadLength;

		// Loop while there's data to write and enough time to do it
		while (($leftBytes > 0) && ($timer->getTimeLeft() > 0))
		{
			// Read the mini header
			$binMiniHeader   = fread($this->fp, 8);
			$reallyReadBytes = akstringlen($binMiniHeader);
			if ($reallyReadBytes < 8)
			{
				// We read less than requested! Why? Did we hit local EOF?
				if ($this->isEOF(true) && !$this->isEOF(false))
				{
					// Yeap. Let's go to the next file
					$this->nextFile();
					// Retry reading the header
					$binMiniHeader   = fread($this->fp, 8);
					$reallyReadBytes = akstringlen($binMiniHeader);
					// Still not enough data? If so, the archive is corrupt or missing parts.
					if ($reallyReadBytes < 8)
					{
						$this->setError(AKText::_('ERR_CORRUPT_ARCHIVE'));

						return false;
					}
				}
				else
				{
					// Nope. The archive is corrupt
					$this->setError(AKText::_('ERR_CORRUPT_ARCHIVE'));

					return false;
				}
			}

			// Read the encrypted data
			$miniHeader      = unpack('Vencsize/Vdecsize', $binMiniHeader);
			$toReadBytes     = $miniHeader['encsize'];
			$data            = $this->fread($this->fp, $toReadBytes);
			$reallyReadBytes = akstringlen($data);

			$this->compressedSizeReadSinceLastFileHeader += $miniHeader['encsize'] + 8;

			if ($reallyReadBytes < $toReadBytes)
			{
				// We read less than requested! Why? Did we hit local EOF?
				if ($this->isEOF(true) && !$this->isEOF(false))
				{
					// Yeap. Let's go to the next file
					$this->nextFile();
					// Read the rest of the data
					$toReadBytes -= $reallyReadBytes;
					$restData        = $this->fread($this->fp, $toReadBytes);
					$reallyReadBytes = akstringlen($restData);
					if ($reallyReadBytes < $toReadBytes)
					{
						$this->setError(AKText::_('ERR_CORRUPT_ARCHIVE'));

						return false;
					}
					if (akstringlen($data) == 0)
					{
						$data = $restData;
					}
					else
					{
						$data .= $restData;
					}
				}
				else
				{
					// Nope. The archive is corrupt
					$this->setError(AKText::_('ERR_CORRUPT_ARCHIVE'));

					return false;
				}
			}

			// Decrypt the data
			$data = AKEncryptionAES::AESDecryptCBC($data, $this->password);

			// Is the length of the decrypted data less than expected?
			$data_length = akstringlen($data);
			if ($data_length < $miniHeader['decsize'])
			{
				$this->setError(AKText::_('ERR_INVALID_JPS_PASSWORD'));

				return false;
			}

			// Trim the data
			$data = substr($data, 0, $miniHeader['decsize']);

			// Decompress
			$data    = gzinflate($data);
			$unc_len = akstringlen($data);

			// Write the decrypted data
			if (!$this->mustSkip())
			{
				if (is_resource($outfp))
				{
					@fwrite($outfp, $data, akstringlen($data));
				}
			}

			// Update the read length
			$this->dataReadLength += $unc_len;
			$leftBytes = $this->fileHeader->uncompressed - $this->dataReadLength;
		}

		// Close the file pointer
		if (!$this->mustSkip())
		{
			if (is_resource($outfp))
			{
				@fclose($outfp);
			}
		}

		// Was this a pre-timeout bail out?
		if ($leftBytes > 0)
		{
			$this->runState = AK_STATE_DATA;
		}
		else
		{
			// Oh! We just finished!
			$this->runState       = AK_STATE_DATAREAD;
			$this->dataReadLength = 0;
		}

		return true;
	}

	private function readKeyExpansionExtraHeader()
	{
		$signature = fread($this->fp, 4);

		if ($signature != "JH\x00\x01")
		{
			// Not a valid JPS file
			$this->setError(AKText::_('ERR_NOT_A_JPS_FILE'));

			return false;
		}

		$bin_data    = fread($this->fp, 8);
		$header_data = unpack('vlength/Calgo/Viterations/CuseStaticSalt', $bin_data);

		if ($header_data['length'] != 76)
		{
			// Not a valid JPS file
			$this->setError(AKText::_('ERR_NOT_A_JPS_FILE'));

			return false;
		}

		switch ($header_data['algo'])
		{
			case 0:
				$algorithm = 'sha1';
				break;

			case 1:
				$algorithm = 'sha256';
				break;

			case 2:
				$algorithm = 'sha512';
				break;

			default:
				// Not a valid JPS file
				$this->setError(AKText::_('ERR_NOT_A_JPS_FILE'));

				return false;
				break;
		}

		$this->pbkdf2Algorithm     = $algorithm;
		$this->pbkdf2Iterations    = $header_data['iterations'];
		$this->pbkdf2UseStaticSalt = $header_data['useStaticSalt'];
		$this->pbkdf2StaticSalt    = fread($this->fp, 64);

		return true;
	}
}

/**
 * Akeeba Restore
 * An AJAX-powered archive extraction library for JPA, JPS and ZIP archives
 *
 * @package   restore
 * @copyright Copyright (c)2008-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

/**
 * Timer class
 */
class AKCoreTimer extends AKAbstractObject
{
	/** @var int Maximum execution time allowance per step */
	private $max_exec_time = null;

	/** @var int Timestamp of execution start */
	private $start_time = null;

	/**
	 * Public constructor, creates the timer object and calculates the execution time limits
	 *
	 * @return  void
	 */
	public function __construct()
	{
		// Initialize start time
		$this->start_time = $this->microtime_float();

		// Get configured max time per step and bias
		$config_max_exec_time = AKFactory::get('kickstart.tuning.max_exec_time', 14);
		$bias                 = AKFactory::get('kickstart.tuning.run_time_bias', 75) / 100;

		// Get PHP's maximum execution time (our upper limit)
		if (@function_exists('ini_get'))
		{
			$php_max_exec_time = @ini_get("maximum_execution_time");
			if ((!is_numeric($php_max_exec_time)) || ($php_max_exec_time == 0))
			{
				// If we have no time limit, set a hard limit of about 10 seconds
				// (safe for Apache and IIS timeouts, verbose enough for users)
				$php_max_exec_time = 14;
			}
		}
		else
		{
			// If ini_get is not available, use a rough default
			$php_max_exec_time = 14;
		}

		// Apply an arbitrary correction to counter CMS load time
		$php_max_exec_time--;

		// Apply bias
		$php_max_exec_time    = $php_max_exec_time * $bias;
		$config_max_exec_time = $config_max_exec_time * $bias;

		// Use the most appropriate time limit value
		if ($config_max_exec_time > $php_max_exec_time)
		{
			$this->max_exec_time = $php_max_exec_time;
		}
		else
		{
			$this->max_exec_time = $config_max_exec_time;
		}
	}

	/**
	 * Returns the current timestampt in decimal seconds
	 */
	private function microtime_float()
	{
		list($usec, $sec) = explode(" ", microtime());

		return ((float) $usec + (float) $sec);
	}

	/**
	 * Wake-up function to reset internal timer when we get unserialized
	 */
	public function __wakeup()
	{
		// Re-initialize start time on wake-up
		$this->start_time = $this->microtime_float();
	}

	/**
	 * Gets the number of seconds left, before we hit the "must break" threshold
	 *
	 * @return float
	 */
	public function getTimeLeft()
	{
		return $this->max_exec_time - $this->getRunningTime();
	}

	/**
	 * Gets the time elapsed since object creation/unserialization, effectively how
	 * long Akeeba Engine has been processing data
	 *
	 * @return float
	 */
	public function getRunningTime()
	{
		return $this->microtime_float() - $this->start_time;
	}

	/**
	 * Enforce the minimum execution time
	 */
	public function enforce_min_exec_time()
	{
		// Try to get a sane value for PHP's maximum_execution_time INI parameter
		if (@function_exists('ini_get'))
		{
			$php_max_exec = @ini_get("maximum_execution_time");
		}
		else
		{
			$php_max_exec = 10;
		}
		if (($php_max_exec == "") || ($php_max_exec == 0))
		{
			$php_max_exec = 10;
		}
		// Decrease $php_max_exec time by 500 msec we need (approx.) to tear down
		// the application, as well as another 500msec added for rounding
		// error purposes. Also make sure this is never gonna be less than 0.
		$php_max_exec = max($php_max_exec * 1000 - 1000, 0);

		// Get the "minimum execution time per step" Akeeba Backup configuration variable
		$minexectime = AKFactory::get('kickstart.tuning.min_exec_time', 0);
		if (!is_numeric($minexectime))
		{
			$minexectime = 0;
		}

		// Make sure we are not over PHP's time limit!
		if ($minexectime > $php_max_exec)
		{
			$minexectime = $php_max_exec;
		}

		// Get current running time
		$elapsed_time = $this->getRunningTime() * 1000;
		$minexectime = 1000.0 * $minexectime;

		// Only run a sleep delay if we haven't reached the minexectime execution time
		if (($minexectime > $elapsed_time) && ($elapsed_time > 0))
		{
			$sleep_msec = (int)($minexectime - $elapsed_time);

			if (function_exists('usleep'))
			{
				usleep(1000 * $sleep_msec);
			}
			elseif (function_exists('time_nanosleep'))
			{
				$sleep_sec  = floor($sleep_msec / 1000);
				$sleep_nsec = 1000000 * ($sleep_msec - ($sleep_sec * 1000));
				time_nanosleep($sleep_sec, $sleep_nsec);
			}
			elseif (function_exists('time_sleep_until'))
			{
				$until_timestamp = time() + $sleep_msec / 1000;
				time_sleep_until($until_timestamp);
			}
			elseif (function_exists('sleep'))
			{
				$sleep_sec = ceil($sleep_msec / 1000);
				sleep($sleep_sec);
			}
		}
	}

	/**
	 * Reset the timer. It should only be used in CLI mode!
	 */
	public function resetTime()
	{
		$this->start_time = $this->microtime_float();
	}

	/**
	 * @param int $max_exec_time
	 */
	public function setMaxExecTime($max_exec_time)
	{
		$this->max_exec_time = $max_exec_time;
	}
}

/**
 * Akeeba Restore
 * An AJAX-powered archive extraction library for JPA, JPS and ZIP archives
 *
 * @package   restore
 * @copyright Copyright (c)2008-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */


class AKUtilsHtaccess extends AKAbstractObject
{
	/**
	 * Extract the PHP handler configuration from a .htaccess file.
	 *
	 * This method supports AddHandler lines and SetHandler blocks.
	 *
	 * @param   string  $htaccess
	 *
	 * @return  string|null  NULL when not found
	 */
	public static function extractHandler($htaccess)
	{
		// Normalize the .htaccess
		$htaccess = self::normalizeHtaccess($htaccess);

		// Look for SetHandler and AddHandler in Files and FilesMatch containers
		foreach (['Files', 'FilesMatch'] as $container)
		{
			$result = self::extractContainer($container, $htaccess);

			if (!is_null($result))
			{
				return $result;
			}
		}

		// Fallback: extract an AddHandler line
		$found = preg_match('#^AddHandler\s?.*\.php.*$#mi', $htaccess, $matches);

		if ($found >= 1)
		{
			return $matches[0];
		}

		return null;
	}

	/**
	 * Extracts a Files or FilesMatch container with an AddHandler or SetHandler line
	 *
	 * @param   string  $container  "Files" or "FilesMatch"
	 * @param   string  $htaccess   The .htaccess file content
	 *
	 * @return  string|null  NULL when not found
	 */
	protected static function extractContainer($container, $htaccess)
	{
		// Try to find the opening container tag e.g. <Files....>
		$pattern = sprintf('#<%s\s*.*\.php.*>#m', $container);
		$found   = preg_match($pattern, $htaccess, $matches, PREG_OFFSET_CAPTURE);

		if (!$found)
		{
			return null;
		}

		// Get the rest of the .htaccess sample
		$openContainer = $matches[0][0];
		$htaccess      = trim(substr($htaccess, $matches[0][1] + strlen($matches[0][0])));

		// Try to find the closing container tag
		$pattern = sprintf('#</%s\s*>#m', $container);
		$found   = preg_match($pattern, $htaccess, $matches, PREG_OFFSET_CAPTURE);

		if (!$found)
		{
			return null;
		}

		// Get the rest of the .htaccess sample
		$htaccess       = trim(substr($htaccess, 0, $matches[$found - 1][1]));
		$closeContainer = $matches[$found - 1][0];

		if (empty($htaccess))
		{
			return null;
		}

		// Now we'll explode remaining lines and find the first SetHandler or AddHandler line
		$lines = array_map('trim', explode("\n", $htaccess));
		$lines = array_filter($lines, function ($line) {
			return preg_match('#(Add|Set)Handler\s?#i', $line) >= 1;
		});

		if (empty($lines))
		{
			return null;
		}

		return $openContainer . "\n" . array_shift($lines) . "\n" . $closeContainer;
	}

	/**
	 * Normalize the .htaccess file content, making it suitable for handler extraction
	 *
	 * @param   string  $htaccess  The original file
	 *
	 * @return  string  The normalized file
	 */
	private static function normalizeHtaccess($htaccess)
	{
		// Convert all newlines into UNIX style
		$htaccess = str_replace("\r\n", "\n", $htaccess);
		$htaccess = str_replace("\r", "\n", $htaccess);

		// Return only non-comment, non-empty lines
		$isNonEmptyNonComment = function ($line) {
			$line = trim($line);

			return !empty($line) && (substr($line, 0, 1) !== '#');
		};

		$lines = array_map('trim', explode("\n", $htaccess));

		return implode("\n", array_filter($lines, $isNonEmptyNonComment));
	}
}

/**
 * Akeeba Restore
 * An AJAX-powered archive extraction library for JPA, JPS and ZIP archives
 *
 * @package   restore
 * @copyright Copyright (c)2008-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

/**
 * A filesystem scanner which uses opendir()
 */
class AKUtilsLister extends AKAbstractObject
{
	public function &getFiles($folder, $pattern = '*')
	{
		// Initialize variables
		$arr   = array();
		$false = false;

		if (!is_dir($folder))
		{
			return $false;
		}

		$handle = @opendir($folder);
		// If directory is not accessible, just return FALSE
		if ($handle === false)
		{
			$this->setWarning('Unreadable directory ' . $folder);

			return $false;
		}

		while (($file = @readdir($handle)) !== false)
		{
			if (!fnmatch($pattern, $file))
			{
				continue;
			}

			if (($file != '.') && ($file != '..'))
			{
				$ds    =
					($folder == '') || ($folder == '/') || (@substr($folder, -1) == '/') || (@substr($folder, -1) == DIRECTORY_SEPARATOR) ?
						'' : DIRECTORY_SEPARATOR;
				$dir   = $folder . $ds . $file;
				$isDir = is_dir($dir);
				if (!$isDir)
				{
					$arr[] = $dir;
				}
			}
		}
		@closedir($handle);

		return $arr;
	}

	public function &getFolders($folder, $pattern = '*')
	{
		// Initialize variables
		$arr   = array();
		$false = false;

		if (!is_dir($folder))
		{
			return $false;
		}

		$handle = @opendir($folder);
		// If directory is not accessible, just return FALSE
		if ($handle === false)
		{
			$this->setWarning('Unreadable directory ' . $folder);

			return $false;
		}

		while (($file = @readdir($handle)) !== false)
		{
			if (!fnmatch($pattern, $file))
			{
				continue;
			}

			if (($file != '.') && ($file != '..'))
			{
				$ds    =
					($folder == '') || ($folder == '/') || (@substr($folder, -1) == '/') || (@substr($folder, -1) == DIRECTORY_SEPARATOR) ?
						'' : DIRECTORY_SEPARATOR;
				$dir   = $folder . $ds . $file;
				$isDir = is_dir($dir);
				if ($isDir)
				{
					$arr[] = $dir;
				}
			}
		}
		@closedir($handle);

		return $arr;
	}
}

/**
 * Akeeba Restore
 * An AJAX-powered archive extraction library for JPA, JPS and ZIP archives
 *
 * @package   restore
 * @copyright Copyright (c)2008-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

/**
 * A filesystem zapper - removes all files and folders under a root
 */
class AKUtilsZapper extends AKAbstractPart
{
	/** @var array Directories left to be deleted */
	private $directory_list;

	/** @var array Files left to be deleted */
	private $file_list;

	/**
	 * Have we finished scanning all subdirectories of the current directory?
	 *
	 * @var   boolean
	 */
	private $done_subdir_scanning = false;

	/**
	 * Have we finished scanning all files of the current directory?
	 *
	 * @var   boolean
	 */
	private $done_file_scanning = true;

	/**
	 * Is the current directory completely excluded?
	 *
	 * @var boolean
	 */
	private $excluded_folder = false;

	/** @var   integer  How many files have been processed in the current step */
	private $processed_files_counter;

	/** @var   string  Current directory being scanned */
	private $current_directory;

	/** @var   string  Current root directory being processed */
	private $root = '';

	/** @var   integer  Total files to process */
	private $total_files = 0;

	/** @var   integer  Total files already processed */
	private $done_files = 0;

	/** @var   integer  Total folders to process */
	private $total_folders = 0;

	/** @var   integer  Total folders already processed */
	private $done_folders = 0;

	/** @var array Absolute filesystem patterns to never delete (e.g. /var/www/html/*.jpa) */
	private $excluded = array();

	/** @var bool Are we in a dry-run? */
	private $dryRun = false;

	/**
	 * Implements the _prepare() abstract method
	 *
	 * Configuration parameters:
	 *
	 * root      The root under which we are going to be deleting files
	 * excluded  Absolute filesystem patterns to never delete (e.g. /var/www/html/*.jpa)
	 *
	 * @return  void
	 */
	protected function _prepare()
	{
		debugMsg(__CLASS__ . " :: Starting _prepare()");

		$defaultExcluded = $this->getDefaultExclusions();

		$parameters = array_merge(array(
			'root'     => rtrim(AKFactory::get('kickstart.setup.destdir'), '/' . DIRECTORY_SEPARATOR),
			'excluded' => $defaultExcluded,
            'dryRun'   => AKFactory::get('kickstart.setup.dryrun', false)
		), $this->_parametersArray);

		$this->root                 = $parameters['root'];
		$this->excluded             = $parameters['excluded'];
		$this->directory_list[]     = $this->root;
		$this->done_subdir_scanning = true;
		$this->done_file_scanning   = true;
		$this->total_files          = 0;
		$this->done_files           = 0;
		$this->total_folders        = 0;
		$this->done_folders         = 0;
		$this->dryRun               = $parameters['dryRun'];

		if (empty($this->root))
		{
			$error = "The folder to delete was not specified.";

			debugMsg(__CLASS__ . " :: " . $error);
			$this->setError($error);

			return;
		}

		if (!is_dir($this->root))
		{
			$error = sprintf("Folder %s does not exist", $this->root);

			debugMsg(__CLASS__ . " :: " . $error);
			$this->setError($error);

			return;
		}

		$this->setState('prepared');

		debugMsg(__CLASS__ . " :: prepared");
	}

	protected function _run()
	{
		if ($this->getState() == 'postrun')
		{
			debugMsg(__CLASS__ . " :: Already finished");
			$this->setStep("-");
			$this->setSubstep("");

			return true;
		}

		// If I'm done scanning files and subdirectories and there are no more files to pack get the next
		// directory. This block is triggered in the first step in a new root.
		if (empty($this->file_list) && $this->done_subdir_scanning && $this->done_file_scanning)
		{
			$this->progressMarkFolderDone();

			if (!$this->getNextDirectory())
			{
			    $this->setState('postrun');
				return true;
			}
		}

		// If I'm not done scanning for files and the file list is empty then scan for more files
		if (!$this->done_file_scanning && empty($this->file_list))
		{
			$this->scanFiles();
		}
		// If I have files left, delete them
		elseif (!empty($this->file_list))
		{
			$this->delete_files();
		}
		// If I'm not done scanning subdirectories, go ahead and scan some more of them
		elseif (!$this->done_subdir_scanning)
		{
			$this->scanSubdirs();
		}

		// Do I have an error?
		if ($this->getError())
		{
			return false;
		}

		return true;
	}

	/**
	 * Implements the _finalize() abstract method
	 *
	 */
	protected function _finalize()
	{
		// No finalization is required
		$this->setState('finished');
	}

	// ============================================================================================
	// PRIVATE METHODS
	// ============================================================================================

	/**
	 * Gets the next directory to scan from the stack. It also applies folder
	 * filters (directory exclusion, subdirectory exclusion, file exclusion),
	 * updating the operation toggle properties of the class.
	 *
	 * @return   boolean  True if we found a directory, false if the directory
	 *                    stack is empty. It also returns true if the folder is
	 *                    filtered (we are told to skip it)
	 */
	private function getNextDirectory()
	{
		// Reset the file / folder scanning positions
		$this->done_file_scanning   = false;
		$this->done_subdir_scanning = false;
		$this->excluded_folder      = false;

		if (count($this->directory_list) == 0)
		{
			// No directories left to scan
			return false;
		}

		// Get and remove the last entry from the $directory_list array
		$this->current_directory = array_pop($this->directory_list);
		$this->setStep($this->current_directory);
		$this->processed_files_counter = 0;

		// Apply directory exclusion filters
		if ($this->isFiltered($this->current_directory))
		{
			debugMsg("Skipping directory " . $this->current_directory);
			$this->done_subdir_scanning = true;
			$this->done_file_scanning   = true;
			$this->excluded_folder      = true;

			return true;
		}

		return true;
	}

	/**
	 * Try to delete some files from the $file_list
	 *
	 * @return   boolean   True if there were files deleted , false otherwise
	 *                     (empty filelist or fatal error)
	 */
	protected function delete_files()
	{
		// Get a reference to the archiver and the timer classes
		$timer = AKFactory::getTimer();

		// Normal file removal loop; we keep on processing the file list, removing files as we go.
		if (count($this->file_list) == 0)
		{
			// No files left to pack. Return true and let the engine loop
			$this->progressMarkFolderDone();

			return true;
		}

		debugMsg("Deleting files");

		$numberOfFiles = 0;
		$postProc = AKFactory::getPostProc();

		while ((count($this->file_list) > 0))
		{
			$file = @array_shift($this->file_list);

			$numberOfFiles++;

			// Remove the file
            $this->setSubstep($file);
            $this->notify((object) array(
                'type' => 'deleteFile',
                'file' => $file
            ));

            if (!$this->dryRun)
            {
                $postProc->unlink($file);
	            clearFileInOPCache($file);
            }

			// Mark a done file
			$this->progressMarkFileDone();

			if ($this->getError())
			{
				return false;
			}

			// I am running out of time.
			if ($timer->getTimeLeft() <= 0)
			{
				return true;
			}
		}

		// True if we have more files, false if we're done packing
		return (count($this->file_list) > 0);
	}

	protected function progressAddFile()
	{
		$this->total_files++;
	}

	protected function progressMarkFileDone()
	{
		$this->done_files++;
	}

	protected function progressAddFolder()
	{
		$this->total_folders++;
	}

	protected function progressMarkFolderDone()
	{
        debugMsg("Deleting directory " . $this->current_directory);

        $this->setSubstep($this->current_directory);
        $this->notify((object) array(
            'type' => 'deleteFolder',
            'file' => $this->current_directory
        ));

        if (!$this->dryRun)
        {
            /**
             * The scanner goes from shallow to deep directory. However this means that when it scans
             * <root>/foo/bar/baz/bat
             * it will only be able to remove the 'bat' directory, thus leaving foo/bar/baz on the disk. The following
             * method will check if the directory is a subdirectory of the site root and work its way up the tree until
             * it finds the site root. Therefore it will end up deleting the parent folders as well.
             */
            $this->deleteParentFolders($this->current_directory);
        }
	}

	/**
	 * Returns the site root, the translated site root and the translated current directory
	 *
	 * @return array
	 */
	protected function getCleanDirectoryComponents()
	{
		$root            = $this->root;
		$translated_root = $root;
		$dir             = TrimTrailingSlash($this->current_directory);

		if (strtoupper(substr(PHP_OS, 0, 3)) == 'WIN')
		{
			$translated_root = TranslateWinPath($translated_root);
			$dir             = TranslateWinPath($dir);
		}

		if (substr($dir, 0, strlen($translated_root)) == $translated_root)
		{
			$dir = substr($dir, strlen($translated_root));
		}
		elseif (in_array(substr($translated_root, -1), array('/', '\\')))
		{
			$new_translated_root = rtrim($translated_root, '/\\');

			if (substr($dir, 0, strlen($new_translated_root)) == $new_translated_root)
			{
				$dir = substr($dir, strlen($new_translated_root));
			}
		}

		if (substr($dir, 0, 1) == '/')
		{
			$dir = substr($dir, 1);
		}

		return array($root, $translated_root, $dir);
	}

	/**
	 * Steps the subdirectory scanning of the current directory
	 *
	 * @return  boolean  True on success, false on fatal error
	 */
	protected function scanSubdirs()
	{
		$lister = new AKUtilsLister();

		list($root, $translated_root, $dir) = $this->getCleanDirectoryComponents();

		debugMsg("Scanning directories of " . $this->current_directory);

		// Get subdirectories
		$subdirectories = $lister->getFolders($this->current_directory);

		// Error propagation
		$this->propagateFromObject($lister);

		// Error control
		if ($this->getError())
		{
			return false;
		}

		// Start adding the subdirectories
		if (!empty($subdirectories) && is_array($subdirectories))
		{
			// Treat symlinks to directories as simple symlink files
			foreach ($subdirectories as $subdirectory)
			{
				if (is_link($subdirectory))
				{
					// Symlink detected; apply directory filters to it
					if (empty($dir))
					{
						$dirSlash = $dir;
					}
					else
					{
						$dirSlash = $dir . '/';
					}

					$check = $dirSlash . basename($subdirectory);
					debugMsg("Directory symlink detected: $check");

					if (strtoupper(substr(PHP_OS, 0, 3)) == 'WIN')
					{
						$check = TranslateWinPath($check);
					}

					$check = $translated_root . '/' . $check;

					// Check for excluded symlinks
					if ($this->isFiltered($check))
					{
						debugMsg("Skipping directory symlink " . $check);

						continue;
					}

					debugMsg('Adding folder symlink: ' . $check);

					$this->file_list[] = $subdirectory;
					$this->progressAddFile();
				}

				$this->directory_list[] = $subdirectory;
				$this->progressAddFolder();
			}
		}

		$this->done_subdir_scanning = true;

		return true;
	}

	/**
	 * Steps the files scanning of the current directory
	 *
	 * @return  boolean  True on success, false on fatal error
	 */
	protected function scanFiles()
	{
		$lister = new AKUtilsLister();

		list($root, $translated_root, $dir) = $this->getCleanDirectoryComponents();

		debugMsg("Scanning files of " . $this->current_directory);
		$this->processed_files_counter = 0;

		// Get file listing
		$fileList = $lister->getFiles($this->current_directory);

		// Error propagation
		$this->propagateFromObject($lister);

		// Error control
		if ($this->getError())
		{
			return false;
		}

		// Do I have an unreadable directory?
		if (($fileList === false))
		{
			$this->setWarning('Unreadable directory ' . $this->current_directory);

			$this->done_file_scanning = true;

			return true;
		}

		// Directory was readable, process the file list
		if (is_array($fileList) && !empty($fileList))
		{
			// Add required trailing slash to $dir
			if (!empty($dir))
			{
				$dir .= '/';
			}

			// Scan all directory entries
			foreach ($fileList as $fileName)
			{
				$check = $dir . basename($fileName);

				if (strtoupper(substr(PHP_OS, 0, 3)) == 'WIN')
				{
					$check = TranslateWinPath($check);
				}

				$check        = $translated_root . '/' . $check;
				$skipThisFile = $this->isFiltered($check);

				if ($skipThisFile)
				{
					debugMsg("Skipping file $fileName");

					continue;
				}

				$this->file_list[] = $fileName;
				$this->processed_files_counter++;
				$this->progressAddFile();
			}
		}

		$this->done_file_scanning = true;

		return true;
	}

	/**
	 * Is a file or folder filtered (protected from deletion)
	 *
	 * @param   string  $fileOrFolder
	 *
	 * @return  bool
	 */
	private function isFiltered($fileOrFolder)
	{
		foreach ($this->excluded as $pattern)
		{
			if (fnmatch($pattern, $fileOrFolder))
			{
				return true;
			}
		}

		return false;
	}

	/**
	 * Get the default exceptions from deletion
	 *
	 * @return  array
	 */
	private function getDefaultExclusions()
	{
		$ret     = array();
		$destDir = AKFactory::get('kickstart.setup.destdir');

		/**
		 * Exclude Kickstart / restore.php itself. Otherwise it'd crash!
		 */
		$myName = defined('KSSELFNAME') ? KSSELFNAME : basename(__FILE__);
		$ret[] = KSROOTDIR . '/' . $myName;

		/**
		 * Cheat: exclude the directory used in development (see source/buildscripts/kickstart_test.php)
		 *
		 * This directory contains the non-concatenated source code for Kickstart. We need to keep it protected.
		 */
		if (defined('MINIBUILD') && (MINIBUILD != $destDir))
		{
			$ret[] = TranslateWinPath(MINIBUILD);
		}

		/**
		 * Exclude the backup archive directory if it's not the site's root. This prevents mindlessly deleting all your
		 * backups before you restore from a previous backup which might not be the one you actually wanted. I will call
		 * this feature "clumsy-proofing".
		 */
		$backupArchive   = AKFactory::get('kickstart.setup.sourcefile');
		$backupDirectory = AKFactory::get('kickstart.setup.sourcepath');
		$backupDirectory = empty($backupDirectory) ? dirname($backupArchive) : $backupDirectory;

		if ($backupDirectory != $destDir)
		{
			$ret[] = TranslateWinPath($backupDirectory);
		}

		/**
		 * Exclude the backup archive files
		 *
		 * This obviously only makes sense when the backup archives are stored in the extraction target folder which is
		 * the most common use of Kickstart. In this case the backups folder is not excluded above.
		 */
		$plainBackupName = basename($backupArchive, '.jpa');
		$plainBackupName = basename($plainBackupName, '.jps');
		$plainBackupName = basename($plainBackupName, '.zip');
		$ret[]           = TranslateWinPath($backupDirectory . '/' . $plainBackupName) . '.*';

		/**
		 * Exclude Kickstart language files. Only applies in Kickstart mode.
		 */
		if (defined('KICKSTART'))
		{
			$langDir        = defined('KSLANGDIR') ? KSLANGDIR : KSROOTDIR;
            $iniFilePattern = basename(KSSELFNAME, '.php') . '.*.ini';

			if ($langDir != KSROOTDIR)
            {
                $ret[] = KSLANGDIR;
            }

            $ret[]   = $langDir . '/' . $iniFilePattern;
            $ret[]   = KSROOTDIR . '/' . $iniFilePattern;
		}

		/**
		 * Exclude Kickstart resources (cacert.pem). Only applies in Kickstart mode.
		 */
		if (defined('KICKSTART'))
		{
			$ret[] = TranslateWinPath(KSROOTDIR . '/cacert.pem');
		}

		// Exclude the Kickstart temporary directory, if one is used by the post-processing engine
		$postProc = AKFactory::getPostProc();
		$tempDir  = $postProc->getTempDir();

		if (!empty($tempDir) && (realpath($tempDir) != realpath($destDir)))
		{
			$ret[] = TranslateWinPath($tempDir);
		}

		/**
		 * Exclude the configured Skipped Files ('kickstart.setup.skipfiles'). Also exclude the various restoration.php
		 * files if we are in restore.php mode and the files are present. These are required for the integrated
		 * restoration to actually work :)
		 */
		$skippedFiles = AKFactory::get('kickstart.setup.skipfiles', array(
			basename(__FILE__), 'kickstart.php', 'abiautomation.ini', 'htaccess.bak', 'php.ini.bak',
			'cacert.pem',
		));

		if (!defined('KICKSTART'))
		{
			// In restore.php mode we have to exclude the various restoration.php files
			$skippedFiles = array_merge(array(
				// Akeeba Backup for Joomla!
				'administrator/components/com_akeeba/restoration.php',
				'administrator/components/com_akeebabackup/restoration.php',
				// Joomla! Update
				'administrator/components/com_joomlaupdate/restoration.php',
				// Akeeba Backup for WordPress
				'wp-content/plugins/akeebabackupwp/app/restoration.php',
				'wp-content/plugins/akeebabackupcorewp/app/restoration.php',
				'wp-content/plugins/akeebabackup/app/restoration.php',
				'wp-content/plugins/akeebabackupwpcore/app/restoration.php',
				// Akeeba Solo
				'app/restoration.php',
			), $skippedFiles);
		}

		foreach ($skippedFiles as $file)
		{
			$checkFile = $destDir . '/' . $file;

			if (file_exists($checkFile))
			{
				$ret[] = TranslateWinPath($checkFile);
			}
		}

		/**
		 * Exclude .htaccess if the stealth feature is enabled. Otherwise we'd unset the stealth mode.
		 * Exclude it even if we have any AddHandler directive, otherwise the site will be borked if the user
		 * chooses not to rename the .htaccess file
		 */
		if (AKFactory::get('kickstart.stealth.enable') || AKFactory::get('kickstart.setup.phphandlers', array()))
		{
			$ret[] = $destDir . '/.htaccess';
		}

		// Remove any duplicate lines
        $ret = array_unique($ret);

		return $ret;
	}

    /**
     * Recursively delete an empty folder and any of its empty parent folders.
     *
     * @param   string  $folder  The folder to deletes
     */
	private function deleteParentFolders($folder)
    {
        // Don't try to delete an empty folder or the filesystem root
        if (empty($folder) || ($folder == '/'))
        {
            return;
        }

        $folder = TranslateWinPath($folder);
        $root   = TranslateWinPath($this->root);

        // Don't try to delete the site's root
        if ($folder === $root)
        {
            return;
        }

        // Delete the leaf folder
        $postProc = AKFactory::getPostProc();
        $postProc->rmdir($folder);

        // If the leaf folder is not under the site's root don't delete its parents
        if (strpos($folder, $root) !== 0)
        {
            return;
        }

        // Get and recursively delete the parent folder
        $this->deleteParentFolders(dirname($folder));
    }
}

/**
 * Runs the Zapper and returns a status table. The Zapper only runs if the feature is enabled (kickstart.setup.zapbefore
 * is 1) and there are more Zapper steps to run (its state is not postrun). If any of these conditions is not met we
 * return boolean false.
 *
 * @param   AKAbstractPartObserver  $observer  Optional observer to attack to the Zapper instance
 *
 * @return  bool|array  Boolean false or a status array
 */
function runZapper(AKAbstractPartObserver $observer = null)
{
	// This method should only run in restore.php mode or when we have Kickstart Professional.
	$isKickstart = defined('KICKSTART');
	$isPro       = defined('KICKSTARTPRO') ? KICKSTARTPRO : false;
	$isDebug     = defined('KSDEBUG') ? KSDEBUG : false;

	if ($isKickstart && (!$isPro && !$isDebug))
	{
		return false;
	}

	// Is the feature enabled?
    $enabled = AKFactory::get('kickstart.setup.zapbefore', 0);

    if (!$enabled)
    {
        return false;
    }

    // Do I still have work to do?
    $zapper = AKFactory::getZapper();

    if ($zapper->getState() == 'finished')
    {
        return false;
    }

    // Attach the observer
    if (is_object($observer))
    {
        $zapper->attach($observer);
    }

    // Run a step, create and return a status array
	$timer = AKFactory::getTimer();

    while ($timer->getTimeLeft() > 0)
    {
	    $ret = $zapper->tick();

	    if ($ret['Error'] != '')
	    {
	    	break;
	    }
    }

    $retArray = array(
        'status'  => true,
        'message' => null,
        'done' => false,
    );

    if ($ret['Error'] != '')
    {
        $retArray['status']  = false;
        $retArray['done']    = true;
        $retArray['message'] = $ret['Error'];
    }
    else
    {
        $retArray['files']    = 0;
        $retArray['bytesIn']  = 0;
        $retArray['bytesOut'] = 0;
        $retArray['factory']  = AKFactory::serialize();
        $retArray['lastfile'] = 'Deleting: ' . $zapper->getSubstep();
    }

	$timer->enforce_min_exec_time();

    return $retArray;
}

/**
 * Akeeba Restore
 * An AJAX-powered archive extraction library for JPA, JPS and ZIP archives
 *
 * @package   restore
 * @copyright Copyright (c)2008-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

/**
 * A simple INI-based i18n engine
 */
class AKText extends AKAbstractObject
{
	/**
	 * The default (en_GB) translation used when no other translation is available
	 *
	 * @var array
	 */
	private $default_translation = [
		'AUTOMODEON'                      => 'Auto-mode enabled',
		'ERR_NOT_A_JPA_FILE'              => 'The file is not a JPA archive',
		'ERR_CORRUPT_ARCHIVE'             => 'The archive file is corrupt, truncated or archive parts are missing',
		'ERR_INVALID_ARCHIVE_LONG'        => 'The archive file appears to be corrupt, or archive parts are missing. If your backups consists of multiple files, please make sure that you have downloaded all the archive part files (files with the same name and extensions .%s, .%s01, .%2$s02…). Please make sure to download <em>and</em> upload files using SFTP, or FTP in Binary transfer mode and do check that their file size matches the sizes reported in the Manage Backups page of Akeeba Backup / Akeeba Solo.',
		'ERR_INVALID_LOGIN'               => 'Invalid login',
		'COULDNT_CREATE_DIR'              => 'Could not create %s folder',
		'COULDNT_WRITE_FILE'              => 'Could not open %s for writing.',
		'WRONG_FTP_HOST'                  => 'Wrong FTP host or port',
		'WRONG_FTP_USER'                  => 'Wrong FTP username or password',
		'WRONG_FTP_PATH1'                 => 'Wrong FTP initial directory - the directory doesn\'t exist',
		'FTP_CANT_CREATE_DIR'             => 'Could not create directory %s',
		'FTP_TEMPDIR_NOT_WRITABLE'        => 'Could not find or create a writable temporary directory',
		'SFTP_TEMPDIR_NOT_WRITABLE'       => 'Could not find or create a writable temporary directory',
		'FTP_COULDNT_UPLOAD'              => 'Could not upload %s',
		'THINGS_HEADER'                   => 'Things you should know about Akeeba Kickstart',
		'THINGS_01'                       => 'Kickstart is not an installer. It is an archive extraction tool. The actual installer was put inside the archive file at backup time.',
		'THINGS_03'                       => 'Kickstart is bound by your server\'s configuration. As such, it may not work at all.',
		'THINGS_04'                       => 'You should download and upload your archive files using FTP in Binary transfer mode. Any other method could lead to a corrupt backup archive and restoration failure.',
		'THINGS_05'                       => 'Post-restoration site load errors are usually caused by .htaccess or php.ini directives. You should understand that blank pages, 404 and 500 errors can usually be worked around by editing the aforementioned files. It is not our job to mess with your configuration files, because this could be dangerous for your site.',
		'THINGS_06'                       => 'Kickstart overwrites files without a warning. If you are not sure that you are OK with that do not continue.',
		'THINGS_07'                       => 'Trying to restore to the temporary URL of a cPanel host (e.g. http://1.2.3.4/~username) will lead to restoration failure and your site will appear to be not working. This is normal and it\'s just how your server and CMS software work.',
		'THINGS_08'                       => 'You are supposed to read the documentation before using this software. Most issues can be avoided, or easily worked around, by understanding how this software works.',
		'THINGS_09'                       => 'This text does not imply that there is a problem detected. It is standard text displayed every time you launch Kickstart.',
		'CLOSE_LIGHTBOX'                  => 'Click here or press ESC to close this message',
		'SELECT_ARCHIVE'                  => 'Select a backup archive',
		'ARCHIVE_FILE'                    => 'Archive file:',
		'SELECT_EXTRACTION'               => 'Select an extraction method',
		'WRITE_TO_FILES'                  => 'Write to files:',
		'WRITE_HYBRID'                    => 'Hybrid (use FTP only if needed)',
		'WRITE_DIRECTLY'                  => 'Directly',
		'WRITE_FTP'                       => 'Use FTP for all files',
		'WRITE_SFTP'                      => 'Use SFTP for all files',
		'FTP_HOST'                        => '(S)FTP host name:',
		'FTP_PORT'                        => '(S)FTP port:',
		'FTP_FTPS'                        => 'Use FTP over SSL (FTPS)',
		'FTP_PASSIVE'                     => 'Use FTP Passive Mode',
		'FTP_USER'                        => '(S)FTP user name:',
		'FTP_PASS'                        => '(S)FTP password:',
		'FTP_DIR'                         => '(S)FTP directory:',
		'FTP_TEMPDIR'                     => 'Temporary directory:',
		'FTP_CONNECTION_OK'               => 'FTP Connection Established',
		'SFTP_CONNECTION_OK'              => 'SFTP Connection Established',
		'FTP_CONNECTION_FAILURE'          => 'The FTP Connection Failed',
		'SFTP_CONNECTION_FAILURE'         => 'The SFTP Connection Failed',
		'FTP_TEMPDIR_WRITABLE'            => 'The temporary directory is writable.',
		'FTP_TEMPDIR_UNWRITABLE'          => 'The temporary directory is not writable. Please check the permissions.',
		'FTP_BROWSE'                      => 'Browse',
		'FTPBROWSER_LBL_INSTRUCTIONS'     => 'Click on a directory to navigate into it. Click on OK to select that directory, Cancel to abort the procedure.',
		'FTPBROWSER_ERROR_HOSTNAME'       => 'Invalid FTP host or port',
		'FTPBROWSER_ERROR_USERPASS'       => 'Invalid FTP username or password',
		'FTPBROWSER_ERROR_NOACCESS'       => 'Directory doesn\'t exist or you don\'t have enough permissions to access it',
		'FTPBROWSER_ERROR_UNSUPPORTED'    => 'Sorry, your FTP server doesn\'t support our FTP directory browser.',
		'FTPBROWSER_LBL_GOPARENT'         => '&lt;up one level&gt;',
		'FTPBROWSER_LBL_ERROR'            => 'An error occurred',
		'SFTP_NO_SSH2'                    => 'Your web server does not have the SSH2 PHP module, therefore can not connect to SFTP servers.',
		'SFTP_NO_FTP_SUPPORT'             => 'Your SSH server does not allow SFTP connections',
		'SFTP_WRONG_USER'                 => 'Wrong SFTP username or password',
		'SFTP_WRONG_STARTING_DIR'         => 'You must supply a valid absolute path',
		'SFTPBROWSER_ERROR_NOACCESS'      => 'Directory doesn\'t exist or you don\'t have enough permissions to access it',
		'SFTP_COULDNT_UPLOAD'             => 'Could not upload %s',
		'SFTP_CANT_CREATE_DIR'            => 'Could not create directory %s',
		'UI-ROOT'                         => '&lt;root&gt;',
		'CONFIG_UI_FTPBROWSER_TITLE'      => 'FTP Directory Browser',
		'BTN_CHECK'                       => 'Check',
		'BTN_RESET'                       => 'Reset',
		'BTN_TESTFTPCON'                  => 'Test FTP connection',
		'BTN_TESTSFTPCON'                 => 'Test SFTP connection',
		'BTN_GOTOSTART'                   => 'Start over',
		'BTN_RETRY'                       => 'Retry',
		'FINE_TUNE'                       => 'Fine tune',
		'MIN_EXEC_TIME'                   => 'Minimum execution time:',
		'MAX_EXEC_TIME'                   => 'Maximum execution time:',
		'SECONDS_PER_STEP'                => 'seconds per step',
		'EXTRACT_FILES'                   => 'Extract files',
		'BTN_START'                       => 'Start',
		'EXTRACTING'                      => 'Extracting',
		'DO_NOT_CLOSE_EXTRACT'            => 'Do not close this window while the extraction is in progress',
		'RESTACLEANUP'                    => 'Restoration and Clean Up',
		'BTN_RUNINSTALLER'                => 'Run the Installer',
		'BTN_CLEANUP'                     => 'Clean Up',
		'BTN_SITEFE'                      => 'Visit your site\'s frontend',
		'BTN_SITEBE'                      => 'Visit your site\'s backend',
		'WARNINGS'                        => 'Extraction Warnings',
		'ERROR_OCCURED'                   => 'An error occurred',
		'STEALTH_MODE'                    => 'Stealth mode',
		'STEALTH_URL'                     => 'HTML file to show to web visitors',
		'ERR_NOT_A_JPS_FILE'              => 'The file is not a JPA archive',
		'ERR_INVALID_JPS_PASSWORD'        => 'The password you gave is wrong or the archive is corrupt',
		'JPS_PASSWORD'                    => 'Archive Password (for JPS files)',
		'INVALID_FILE_HEADER_OFFSET_ZERO' => 'Cannot open the file %s for reading. This is part #%d of your backup archive which consists of multiple files (files with the same name and extensions .%s, .%s01, .%4$s02…). Please make sure that you have all of these files in the same folder as Kickstart.',
		'INVALID_FILE_HEADER'             => 'Invalid header in archive file, part %s, offset %s. Please make sure to download <em>and</em> upload backup archive files using SFTP, or FTP in Binary transfer mode and do check that their file size matches the sizes reported in the Manage Backups page of Akeeba Backup / Akeeba Solo.',
		'INVALID_FILE_HEADER_MULTIPART'   => 'Invalid header in archive file, part %s, offset %s. Your backup archive consists of multiple files (files with the same name and extensions .%s, .%s01, .%4$s02…). Either some files are missing, or they are corrupt or truncated. You will need all of these files to be present in the same directory. Please make sure to download <em>and</em> upload backup archive files using SFTP, or FTP in Binary transfer mode and do check that their file size matches the sizes reported in the Manage Backups page of Akeeba Backup / Akeeba Solo.',
		'UPDATE_HEADER'                   => 'An updated version of Akeeba Kickstart (<span id="update-version">unknown</span>) is available!',
		'UPDATE_NOTICE'                   => 'You are advised to always use the latest version of Akeeba Kickstart available. Older versions may be subject to bugs and will not be supported.',
		'UPDATE_DLNOW'                    => 'Download now',
		'UPDATE_MOREINFO'                 => 'More information',
		'NEEDSOMEHELPKS'                  => 'Want some help to use this tool? Read this first:',
		'QUICKSTART'                      => 'Using Kickstart',
		'CANTGETITTOWORK'                 => 'Can\'t get it to work? Click me!',
		'NOARCHIVESCLICKHERE'             => 'No archives detected. Click here for troubleshooting instructions.',
		'POSTRESTORATIONTROUBLESHOOTING'  => 'Something not working after the restoration? Click here for troubleshooting instructions.',
		'IGNORE_MOST_ERRORS'              => 'Ignore most errors',
		'TIME_SETTINGS_HELP'              => 'Increase the minimum to 3 if you get AJAX errors. Increase the maximum to 10 for faster extraction, decrease back to 5 if you get AJAX errors. Try minimum 5, maximum 1 (not a typo!) if you keep getting AJAX errors.',
		'STEALTH_MODE_HELP'               => 'When enabled, only visitors from your IP address will be able to see the site until the restoration is complete. Everyone else will be redirected to and only see the URL above. Your server must see the real IP of the visitor (this is controlled by your host, not you or us).',
		'RENAME_FILES_HELP'               => 'Renames .htaccess, web.config, php.ini and .user.ini contained in the archive while extracting. Files are renamed with a .bak extension. The file names are restored when you click on Clean Up.',
		'RESTORE_PERMISSIONS_HELP'        => 'Applies the file permissions (but NOT file ownership) which was stored at backup time. Only works with JPA and JPS archives. Does not work on Windows (PHP does not offer such a feature).',
		'EXTRACT_LIST'                    => 'Files to extract',
		'EXTRACT_LIST_HELP'               => 'Enter a file path such as <code>images/cat.png</code> or shell pattern such as <code>images/*.png</code> on each line. Only files matching this list will be written to disk. Leave empty to extract everything (default).',
		'AKS3_IMPORT'                     => 'Import from Amazon S3',
		'AKS3_TITLE_STEP1'                => 'Connect to Amazon S3',
		'AKS3_ACCESS'                     => 'Access Key',
		'AKS3_SECRET'                     => 'Secret Key',
		'AKS3_CONNECT'                    => 'Connect to Amazon S3',
		'AKS3_CANCEL'                     => 'Cancel import',
		'AKS3_TITLE_STEP2'                => 'Select your Amazon S3 bucket',
		'AKS3_BUCKET'                     => 'Bucket',
		'AKS3_LISTCONTENTS'               => 'List contents',
		'AKS3_TITLE_STEP3'                => 'Select archive to import',
		'AKS3_FOLDERS'                    => 'Folders',
		'AKS3_FILES'                      => 'Archive Files',
		'AKS3_TITLE_STEP4'                => 'Importing...',
		'AKS3_DO_NOT_CLOSE'               => 'Please do not close this window while your backup archives are being imported',
		'AKS3_TITLE_STEP5'                => 'Import is complete',
		'AKS3_BTN_RELOAD'                 => 'Reload Kickstart',
		'WRONG_FTP_PATH2'                 => 'Wrong FTP initial directory - the directory doesn\'t correspond to your site\'s web root',
		'ARCHIVE_DIRECTORY'               => 'Archive directory:',
		'RELOAD_ARCHIVES'                 => 'Reload',
		'CONFIG_UI_SFTPBROWSER_TITLE'     => 'SFTP Directory Browser',
		'ERR_COULD_NOT_OPEN_ARCHIVE_PART' => 'Could not open archive part file %s for reading. Check that the file exists, is readable by the web server and is not in a directory made out of reach by chroot, open_basedir restrictions or any other restriction put in place by your host.',
		'RENAME_FILES'                    => 'Rename server configuration files before extraction',
		'BTN_SHOW_FINE_TUNE'              => 'Show advanced options (for experts)',
		'RESTORE_PERMISSIONS'             => 'Restore file permissions',
		'ZAPBEFORE'                       => 'Delete everything before extraction',
		'ZAPBEFORE_HELP'                  => 'Tries to delete all existing files and folders under the directory where Kickstart is stored before extracting the backup archive. It DOES NOT take into account which files and folders exist in the backup archive. Files and folders deleted by this feature CAN NOT be recovered. <strong>WARNING! THIS MAY DELETE FILES AND FOLDERS WHICH DO NOT BELONG TO YOUR SITE. USE WITH EXTREME CAUTION. BY ENABLING THIS FEATURE YOU ASSUME ALL RESPONSIBILITY AND LIABILITY.</strong>',
	];

	/** END OF ARRAY — DO NOT EDIT OR REMOVE **/

	/**
	 * The array holding the translation keys
	 *
	 * @var array
	 */
	private $strings;

	/**
	 * The currently detected language (ISO code)
	 *
	 * @var string
	 */
	private $language;

	/*
	 * Initializes the translation engine
	 * @return AKText
	 */
	public function __construct()
	{
		// Start with the default translation
		$this->strings = $this->default_translation;
		// Try loading the translation file in English, if it exists
		$this->loadTranslation('en-GB');
		// Try loading the translation file in the browser's preferred language, if it exists
		$this->getBrowserLanguage();
		if (!is_null($this->language))
		{
			$this->loadTranslation();
		}
	}

	/**
	 * A PHP based INI file parser.
	 *
	 * Thanks to asohn ~at~ aircanopy ~dot~ net for posting this handy function on
	 * the parse_ini_file page on http://gr.php.net/parse_ini_file
	 *
	 * @param   string  $file              Filename to process
	 * @param   bool    $process_sections  True to also process INI sections
	 * @param   bool    $rawdata           If true, the $file contains raw INI data, not a filename
	 *
	 * @return array An associative array of sections, keys and values
	 * @access private
	 */
	public static function parse_ini_file($file, $process_sections = false, $rawdata = false)
	{
		$process_sections = ($process_sections !== true) ? false : true;

		if (!$rawdata)
		{
			$ini = file($file);
		}
		else
		{
			$file = str_replace("\r", "", $file);
			$ini  = explode("\n", $file);
		}

		if (!is_array($ini))
		{
			return [];
		}

		if (count($ini) == 0)
		{
			return [];
		}

		$sections = [];
		$values   = [];
		$result   = [];
		$globals  = [];
		$i        = 0;
		foreach ($ini as $line)
		{
			$line = trim($line);
			$line = str_replace("\t", " ", $line);

			// Comments
			if (!preg_match('/^[a-zA-Z0-9[]/', $line))
			{
				continue;
			}

			// Sections
			if ($line[0] == '[')
			{
				$tmp        = explode(']', $line);
				$sections[] = trim(substr($tmp[0], 1));
				$i++;
				continue;
			}

			// Key-value pair
			$lineParts = explode('=', $line, 2);
			if (count($lineParts) != 2)
			{
				continue;
			}
			$key   = trim($lineParts[0]);
			$value = trim($lineParts[1]);
			unset($lineParts);

			if (strstr($value, ";"))
			{
				$tmp = explode(';', $value);
				if (count($tmp) == 2)
				{
					if ((($value[0] != '"') && ($value[0] != "'")) ||
						preg_match('/^".*"\s*;/', $value) || preg_match('/^".*;[^"]*$/', $value) ||
						preg_match("/^'.*'\s*;/", $value) || preg_match("/^'.*;[^']*$/", $value)
					)
					{
						$value = $tmp[0];
					}
				}
				else
				{
					if ($value[0] == '"')
					{
						$value = preg_replace('/^"(.*)".*/', '$1', $value);
					}
					elseif ($value[0] == "'")
					{
						$value = preg_replace("/^'(.*)'.*/", '$1', $value);
					}
					else
					{
						$value = $tmp[0];
					}
				}
			}
			$value = trim($value);
			$value = trim($value, "'\"");

			if ($i == 0)
			{
				if (substr($line, -1, 2) == '[]')
				{
					$globals[$key][] = $value;
				}
				else
				{
					$globals[$key] = $value;
				}
			}
			else
			{
				if (substr($line, -1, 2) == '[]')
				{
					$values[$i - 1][$key][] = $value;
				}
				else
				{
					$values[$i - 1][$key] = $value;
				}
			}
		}

		for ($j = 0; $j < $i; $j++)
		{
			if ($process_sections === true)
			{
				if (isset($sections[$j]) && isset($values[$j]))
				{
					$result[$sections[$j]] = $values[$j];
				}
			}
			else
			{
				if (isset($values[$j]))
				{
					$result[] = $values[$j];
				}
			}
		}

		return $result + $globals;
	}

	public static function sprintf($key)
	{
		$text = self::getInstance();
		$args = func_get_args();
		if (count($args) > 0)
		{
			$args[0] = $text->_($args[0]);

			return @call_user_func_array('sprintf', $args);
		}

		return '';
	}

	/**
	 * Singleton pattern for Language
	 *
	 * @return AKText The global AKText instance
	 */
	public static function &getInstance()
	{
		static $instance;

		if (!is_object($instance))
		{
			$instance = new AKText();
		}

		return $instance;
	}

	public static function _($string)
	{
		$text = self::getInstance();

		$key = strtoupper($string);
		$key = substr($key, 0, 1) == '_' ? substr($key, 1) : $key;

		if (isset ($text->strings[$key]))
		{
			$string = $text->strings[$key];
		}
		else
		{
			if (defined($string))
			{
				$string = constant($string);
			}
		}

		return $string;
	}

	public function getBrowserLanguage()
	{
		// Detection code from Full Operating system language detection, by Harald Hope
		// Retrieved from http://techpatterns.com/downloads/php_language_detection.php
		$user_languages = [];
		//check to see if language is set
		if (isset($_SERVER["HTTP_ACCEPT_LANGUAGE"]))
		{
			$languages = strtolower($_SERVER["HTTP_ACCEPT_LANGUAGE"]);
			// $languages = ' fr-ch;q=0.3, da, en-us;q=0.8, en;q=0.5, fr;q=0.3';
			// need to remove spaces from strings to avoid error
			$languages = str_replace(' ', '', $languages);
			$languages = explode(",", $languages);

			foreach ($languages as $language_list)
			{
				// pull out the language, place languages into array of full and primary
				// string structure:
				$temp_array = [];
				// slice out the part before ; on first step, the part before - on second, place into array
				$temp_array[0] = substr($language_list, 0, strcspn($language_list, ';'));//full language
				$temp_array[1] = substr($language_list, 0, 2);// cut out primary language
				if ((strlen($temp_array[0]) == 5) && ((substr($temp_array[0], 2, 1) == '-') || (substr($temp_array[0], 2, 1) == '_')))
				{
					$langLocation  = strtoupper(substr($temp_array[0], 3, 2));
					$temp_array[0] = $temp_array[1] . '-' . $langLocation;
				}
				//place this array into main $user_languages language array
				$user_languages[] = $temp_array;
			}
		}
		else// if no languages found
		{
			$user_languages[0] = ['', '']; //return blank array.
		}

		$this->language = null;
		$basename       = basename(__FILE__, '.php') . '.ini';

		// Try to match main language part of the filename, irrespective of the location, e.g. de_DE will do if de_CH doesn't exist.
		if (class_exists('AKUtilsLister'))
		{
			$fs       = new AKUtilsLister();
			$iniFiles = $fs->getFiles(KSROOTDIR, '*.' . $basename);
			if (empty($iniFiles) && ($basename != 'kickstart.ini'))
			{
				$basename = 'kickstart.ini';
				$iniFiles = $fs->getFiles(KSROOTDIR, '*.' . $basename);
			}
		}
		else
		{
			$iniFiles = null;
		}

		if (is_array($iniFiles))
		{
			foreach ($user_languages as $languageStruct)
			{
				if (is_null($this->language))
				{
					// Get files matching the main lang part
					$iniFiles = $fs->getFiles(KSROOTDIR, $languageStruct[1] . '-??.' . $basename);
					if (count($iniFiles) > 0)
					{
						$filename       = $iniFiles[0];
						$filename       = substr($filename, strlen(KSROOTDIR) + 1);
						$this->language = substr($filename, 0, 5);
					}
					else
					{
						$this->language = null;
					}
				}
			}
		}

		if (is_null($this->language))
		{
			// Try to find a full language match
			foreach ($user_languages as $languageStruct)
			{
				if (@file_exists($languageStruct[0] . '.' . $basename) && is_null($this->language))
				{
					$this->language = $languageStruct[0];
				}
			}
		}
		else
		{
			// Do we have an exact match?
			foreach ($user_languages as $languageStruct)
			{
				if (substr($this->language, 0, strlen($languageStruct[1])) == $languageStruct[1])
				{
					if (file_exists($languageStruct[0] . '.' . $basename))
					{
						$this->language = $languageStruct[0];
					}
				}
			}
		}

		// Now, scan for full language based on the partial match

	}

	public function dumpLanguage()
	{
		$out = '';
		foreach ($this->strings as $key => $value)
		{
			$out .= "$key=$value\n";
		}

		return $out;
	}

	public function asJavascript()
	{
		$out = '';
		foreach ($this->strings as $key => $value)
		{
			$key   = addcslashes($key, '\\\'"');
			$value = addcslashes($value, '\\\'"');
			if (!empty($out))
			{
				$out .= ",\n";
			}
			$out .= "'$key':\t'$value'";
		}

		return $out;
	}

	public function resetTranslation()
	{
		$this->strings = $this->default_translation;
	}

	public function addDefaultLanguageStrings($stringList = [])
	{
		if (!is_array($stringList))
		{
			return;
		}
		if (empty($stringList))
		{
			return;
		}

		$this->strings = array_merge($stringList, $this->strings);
	}

	private function loadTranslation($lang = null)
	{
		if (defined('KSLANGDIR'))
		{
			$dirname = KSLANGDIR;
		}
		else
		{
			$dirname = KSROOTDIR;
		}

		$myName   = defined('KSSELFNAME') ? KSSELFNAME : basename(__FILE__);
		$basename = basename($myName, '.php') . '.ini';

		if (empty($lang))
		{
			$lang = $this->language;
		}

		$translationFilename = $dirname . DIRECTORY_SEPARATOR . $lang . '.' . $basename;
		if (!@file_exists($translationFilename) && ($basename != 'kickstart.ini'))
		{
			$basename            = 'kickstart.ini';
			$translationFilename = $dirname . DIRECTORY_SEPARATOR . $lang . '.' . $basename;
		}
		if (!@file_exists($translationFilename))
		{
			return;
		}
		$temp = self::parse_ini_file($translationFilename, false);

		if (!is_array($this->strings))
		{
			$this->strings = [];
		}
		if (empty($temp))
		{
			$this->strings = array_merge($this->default_translation, $this->strings);
		}
		else
		{
			$this->strings = array_merge($this->strings, $temp);
		}
	}
}

/**
 * Akeeba Restore
 * An AJAX-powered archive extraction library for JPA, JPS and ZIP archives
 *
 * @package   restore
 * @copyright Copyright (c)2008-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

/**
 * The Akeeba Kickstart Factory class
 *
 * This class is reponssible for instantiating all Akeeba Kickstart classes
 */
class AKFactory
{
	/** @var   array  A list of instantiated objects */
	private $objectlist = array();

	/** @var   array  Simple hash data storage */
	private $varlist = array();

	/** @var   self   Static instance */
	private static $instance = null;

	/**
	 * AKFactory constructor.
	 *
	 * This is a private constructor makes sure we can't instantiate the class unless we go through the static
	 * getInstance singleton method. This is different than making the class abstract (preventing any kind of object
	 * instantiation).
	 */
	private function __construct()
	{
	}

	/**
	 * Gets a serialized snapshot of the Factory for safekeeping (hibernate)
	 *
	 * @return string The serialized snapshot of the Factory
	 */
	public static function serialize()
	{
		$engine = self::getUnarchiver();
		$engine->shutdown();
		$serialized = serialize(self::getInstance());

		if (function_exists('base64_encode') && function_exists('base64_decode'))
		{
			$serialized = base64_encode($serialized);
		}

		return $serialized;
	}

	/**
	 * Gets the unarchiver engine
	 *
	 * @return AKAbstractUnarchiver
	 */
	public static function &getUnarchiver($configOverride = null)
	{
		static $class_name;

		if (!empty($configOverride) && isset($configOverride['reset']) && $configOverride['reset'])
		{
			$class_name = null;
		}

		if (empty($class_name))
		{
			$filetype = self::get('kickstart.setup.filetype', null);

			if (empty($filetype))
			{
				$filename      = self::get('kickstart.setup.sourcefile', null);
				$basename      = basename($filename);
				$baseextension = strtoupper(substr($basename, -3));

				switch ($baseextension)
				{
					case 'JPA':
						$filetype = 'JPA';
						break;

					case 'JPS':
						$filetype = 'JPS';
						break;

					case 'ZIP':
						$filetype = 'ZIP';
						break;

					default:
						die('Invalid archive type or extension in file ' . $filename);
						break;
				}
			}

			$class_name = 'AKUnarchiver' . ucfirst($filetype);
		}

		$destdir = self::get('kickstart.setup.destdir', null);

		if (empty($destdir))
		{
			$destdir = KSROOTDIR;
		}

		/** @var AKAbstractUnarchiver $object */
		$object = self::getClassInstance($class_name);

		if ($object->getState() == 'init')
		{
			$sourcePath = self::get('kickstart.setup.sourcepath', '');
			$sourceFile = self::get('kickstart.setup.sourcefile', '');

			if (!empty($sourcePath))
			{
				$sourceFile = rtrim($sourcePath, '/\\') . '/' . $sourceFile;
			}

			// Initialize the object –– Any change here MUST be reflected to echoHeadJavascript (default values)
			$config = array(
				'filename'            => $sourceFile,
				'restore_permissions' => self::get('kickstart.setup.restoreperms', 0),
				'post_proc'           => self::get('kickstart.procengine', 'direct'),
				'add_path'            => self::get('kickstart.setup.targetpath', $destdir),
				'remove_path'         => self::get('kickstart.setup.removepath', ''),
				'rename_files'        => self::get('kickstart.setup.renamefiles', array(
					'.htaccess' => 'htaccess.bak', 'php.ini' => 'php.ini.bak', 'web.config' => 'web.config.bak',
					'.user.ini' => '.user.ini.bak',
				)),
				'skip_files'          => self::get('kickstart.setup.skipfiles', array(
					basename(__FILE__), 'kickstart.php', 'abiautomation.ini', 'htaccess.bak', 'php.ini.bak',
					'cacert.pem',
				)),
				'ignoredirectories'   => self::get('kickstart.setup.ignoredirectories', array(
					'tmp', 'log', 'logs',
				)),
			);

			if (!defined('KICKSTART'))
			{
				// In restore.php mode we have to exclude the restoration.php files
				$moreSkippedFiles     = array(
					// Akeeba Backup for Joomla!
					'administrator/components/com_akeeba/restoration.php',
					'administrator/components/com_akeebabackup/restoration.php',
					// Joomla! Update
					'administrator/components/com_joomlaupdate/restoration.php',
					// Akeeba Backup for WordPress
					'wp-content/plugins/akeebabackupwp/app/restoration.php',
					'wp-content/plugins/akeebabackupcorewp/app/restoration.php',
					'wp-content/plugins/akeebabackup/app/restoration.php',
					'wp-content/plugins/akeebabackupwpcore/app/restoration.php',
					// Akeeba Solo
					'app/restoration.php',
				);

				$config['skip_files'] = array_merge($config['skip_files'], $moreSkippedFiles);
			}

			if (!empty($configOverride))
			{
				$config = array_merge($config, $configOverride);
			}

			$object->setup($config);
		}

		return $object;
	}

	// ========================================================================
	// Public factory interface
	// ========================================================================

	public static function get($key, $default = null)
	{
		$self = self::getInstance();

		if (array_key_exists($key, $self->varlist))
		{
			return $self->varlist[$key];
		}

		return $default;
	}

	/**
	 * Gets a single, internally used instance of the Factory
	 *
	 * @param string $serialized_data [optional] Serialized data to spawn the instance from
	 *
	 * @return AKFactory A reference to the unique Factory object instance
	 */
	protected static function &getInstance($serialized_data = null)
	{
		if (!is_object(self::$instance) || !is_null($serialized_data))
		{
			if (!is_null($serialized_data))
			{
				self::$instance = unserialize($serialized_data);

				return self::$instance;
			}

			self::$instance = new self();
		}

		return self::$instance;
	}

	/**
	 * Internal function which instantiates a class named $class_name.
	 * The autoloader
	 *
	 * @param string $class_name
	 *
	 * @return object
	 */
	protected static function &getClassInstance($class_name)
	{
		$self = self::getInstance();

		if (!isset($self->objectlist[$class_name]))
		{
			$self->objectlist[$class_name] = new $class_name;
		}

		return $self->objectlist[$class_name];
	}

	// ========================================================================
	// Public hash data storage interface
	// ========================================================================

	/**
	 * Regenerates the full Factory state from a serialized snapshot (resume)
	 *
	 * @param string $serialized_data The serialized snapshot to resume from
	 */
	public static function unserialize($serialized_data)
	{
		if (function_exists('base64_encode') && function_exists('base64_decode'))
		{
			$serialized_data = base64_decode($serialized_data);
		}

		self::getInstance($serialized_data);
	}

	/**
	 * Reset the internal factory state, freeing all previously created objects
	 */
	public static function nuke()
	{
		self::$instance = null;
	}

	// ========================================================================
	// Akeeba Kickstart classes
	// ========================================================================

	public static function set($key, $value)
	{
		$self                = self::getInstance();
		$self->varlist[$key] = $value;
	}

	/**
	 * Gets the post processing engine
	 *
	 * @param string $proc_engine
	 *
	 * @return AKAbstractPostproc
	 */
	public static function &getPostProc($proc_engine = null)
	{
		static $class_name;

		if (empty($class_name))
		{
			if (empty($proc_engine))
			{
				$proc_engine = self::get('kickstart.procengine', 'direct');
			}

			$class_name = 'AKPostproc' . ucfirst($proc_engine);
		}

		return self::getClassInstance($class_name);
	}

	/**
	 * Get the a reference to the Akeeba Engine's timer
	 *
	 * @return AKCoreTimer
	 */
	public static function &getTimer()
	{
		return self::getClassInstance('AKCoreTimer');
	}

	/**
	 * Get an instance of the filesystem zapper
	 *
	 * @return AKUtilsZapper
	 */
	public static function &getZapper()
	{
		return self::getClassInstance('AKUtilsZapper');
	}
}

/**
 * Akeeba Restore
 * An AJAX-powered archive extraction library for JPA, JPS and ZIP archives
 *
 * @package   restore
 * @copyright Copyright (c)2008-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

/**
 * Interface for AES encryption adapters
 */
interface AKEncryptionAESAdapterInterface
{
	/**
	 * Decrypts a string. Returns the raw binary ciphertext, zero-padded.
	 *
	 * @param   string       $plainText  The plaintext to encrypt
	 * @param   string       $key        The raw binary key (will be zero-padded or chopped if its size is different than the block size)
	 *
	 * @return  string  The raw encrypted binary string.
	 */
	public function decrypt($plainText, $key);

	/**
	 * Returns the encryption block size in bytes
	 *
	 * @return  int
	 */
	public function getBlockSize();

	/**
	 * Is this adapter supported?
	 *
	 * @return  bool
	 */
	public function isSupported();
}

/**
 * Akeeba Restore
 * An AJAX-powered archive extraction library for JPA, JPS and ZIP archives
 *
 * @package   restore
 * @copyright Copyright (c)2008-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

/**
 * Abstract AES encryption class
 */
abstract class AKEncryptionAESAdapterAbstract
{
	/**
	 * Trims or zero-pads a key / IV
	 *
	 * @param   string $key  The key or IV to treat
	 * @param   int    $size The block size of the currently used algorithm
	 *
	 * @return  null|string  Null if $key is null, treated string of $size byte length otherwise
	 */
	public function resizeKey($key, $size)
	{
		if (empty($key))
		{
			return null;
		}

		$keyLength = strlen($key);

		if (function_exists('mb_strlen'))
		{
			$keyLength = mb_strlen($key, 'ASCII');
		}

		if ($keyLength == $size)
		{
			return $key;
		}

		if ($keyLength > $size)
		{
			if (function_exists('mb_substr'))
			{
				return mb_substr($key, 0, $size, 'ASCII');
			}

			return substr($key, 0, $size);
		}

		return $key . str_repeat("\0", ($size - $keyLength));
	}

	/**
	 * Returns null bytes to append to the string so that it's zero padded to the specified block size
	 *
	 * @param   string $string    The binary string which will be zero padded
	 * @param   int    $blockSize The block size
	 *
	 * @return  string  The zero bytes to append to the string to zero pad it to $blockSize
	 */
	protected function getZeroPadding($string, $blockSize)
	{
		$stringSize = strlen($string);

		if (function_exists('mb_strlen'))
		{
			$stringSize = mb_strlen($string, 'ASCII');
		}

		if ($stringSize == $blockSize)
		{
			return '';
		}

		if ($stringSize < $blockSize)
		{
			return str_repeat("\0", $blockSize - $stringSize);
		}

		$paddingBytes = $stringSize % $blockSize;

		return str_repeat("\0", $blockSize - $paddingBytes);
	}
}

/**
 * Akeeba Restore
 * An AJAX-powered archive extraction library for JPA, JPS and ZIP archives
 *
 * @package   restore
 * @copyright Copyright (c)2008-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

class Mcrypt extends AKEncryptionAESAdapterAbstract implements AKEncryptionAESAdapterInterface
{
	protected $cipherType = MCRYPT_RIJNDAEL_128;

	protected $cipherMode = MCRYPT_MODE_CBC;

	public function decrypt($cipherText, $key)
	{
		$iv_size    = $this->getBlockSize();
		$key        = $this->resizeKey($key, $iv_size);
		$iv         = substr($cipherText, 0, $iv_size);
		$cipherText = substr($cipherText, $iv_size);
		$plainText  = mcrypt_decrypt($this->cipherType, $key, $cipherText, $this->cipherMode, $iv);

		return $plainText;
	}

	public function isSupported()
	{
		if (!function_exists('mcrypt_get_key_size'))
		{
			return false;
		}

		if (!function_exists('mcrypt_get_iv_size'))
		{
			return false;
		}

		if (!function_exists('mcrypt_create_iv'))
		{
			return false;
		}

		if (!function_exists('mcrypt_encrypt'))
		{
			return false;
		}

		if (!function_exists('mcrypt_decrypt'))
		{
			return false;
		}

		if (!function_exists('mcrypt_list_algorithms'))
		{
			return false;
		}

		if (!function_exists('hash'))
		{
			return false;
		}

		if (!function_exists('hash_algos'))
		{
			return false;
		}

		$algorightms = mcrypt_list_algorithms();

		if (!in_array('rijndael-128', $algorightms))
		{
			return false;
		}

		if (!in_array('rijndael-192', $algorightms))
		{
			return false;
		}

		if (!in_array('rijndael-256', $algorightms))
		{
			return false;
		}

		$algorightms = hash_algos();

		if (!in_array('sha256', $algorightms))
		{
			return false;
		}

		return true;
	}

	public function getBlockSize()
	{
		return mcrypt_get_iv_size($this->cipherType, $this->cipherMode);
	}
}

/**
 * Akeeba Restore
 * An AJAX-powered archive extraction library for JPA, JPS and ZIP archives
 *
 * @package   restore
 * @copyright Copyright (c)2008-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

class OpenSSL extends AKEncryptionAESAdapterAbstract implements AKEncryptionAESAdapterInterface
{
	/**
	 * The OpenSSL options for encryption / decryption
	 *
	 * @var  int
	 */
	protected $openSSLOptions = 0;

	/**
	 * The encryption method to use
	 *
	 * @var  string
	 */
	protected $method = 'aes-128-cbc';

	public function __construct()
	{
		$this->openSSLOptions = OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING;
	}

	public function decrypt($cipherText, $key)
	{
		$iv_size    = $this->getBlockSize();
		$key        = $this->resizeKey($key, $iv_size);
		$iv         = substr($cipherText, 0, $iv_size);
		$cipherText = substr($cipherText, $iv_size);
		$plainText  = openssl_decrypt($cipherText, $this->method, $key, $this->openSSLOptions, $iv);

		return $plainText;
	}

	public function isSupported()
	{
		if (!function_exists('openssl_get_cipher_methods'))
		{
			return false;
		}

		if (!function_exists('openssl_random_pseudo_bytes'))
		{
			return false;
		}

		if (!function_exists('openssl_cipher_iv_length'))
		{
			return false;
		}

		if (!function_exists('openssl_encrypt'))
		{
			return false;
		}

		if (!function_exists('openssl_decrypt'))
		{
			return false;
		}

		if (!function_exists('hash'))
		{
			return false;
		}

		if (!function_exists('hash_algos'))
		{
			return false;
		}

		$algorightms = openssl_get_cipher_methods();

		if (!in_array('aes-128-cbc', $algorightms))
		{
			return false;
		}

		$algorightms = hash_algos();

		if (!in_array('sha256', $algorightms))
		{
			return false;
		}

		return true;
	}

	/**
	 * @return int
	 */
	public function getBlockSize()
	{
		return openssl_cipher_iv_length($this->method);
	}
}

/**
 * Akeeba Restore
 * An AJAX-powered archive extraction library for JPA, JPS and ZIP archives
 *
 * @package   restore
 * @copyright Copyright (c)2008-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

/**
 * AES implementation in PHP (c) Chris Veness 2005-2016.
 * Right to use and adapt is granted for under a simple creative commons attribution
 * licence. No warranty of any form is offered.
 *
 * Heavily modified for Akeeba Backup by Nicholas K. Dionysopoulos
 * Also added AES-128 CBC mode (with mcrypt and OpenSSL) on top of AES CTR
 * Removed CTR encrypt / decrypt (no longer used)
 */
class AKEncryptionAES
{
	// Sbox is pre-computed multiplicative inverse in GF(2^8) used in SubBytes and KeyExpansion [�5.1.1]
	protected static $Sbox =
		array(0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76,
			0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0,
			0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15,
			0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75,
			0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84,
			0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf,
			0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8,
			0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2,
			0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73,
			0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb,
			0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79,
			0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08,
			0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a,
			0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e,
			0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf,
			0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16);

	// Rcon is Round Constant used for the Key Expansion [1st col is 2^(r-1) in GF(2^8)] [�5.2]
	protected static $Rcon = array(
		array(0x00, 0x00, 0x00, 0x00),
		array(0x01, 0x00, 0x00, 0x00),
		array(0x02, 0x00, 0x00, 0x00),
		array(0x04, 0x00, 0x00, 0x00),
		array(0x08, 0x00, 0x00, 0x00),
		array(0x10, 0x00, 0x00, 0x00),
		array(0x20, 0x00, 0x00, 0x00),
		array(0x40, 0x00, 0x00, 0x00),
		array(0x80, 0x00, 0x00, 0x00),
		array(0x1b, 0x00, 0x00, 0x00),
		array(0x36, 0x00, 0x00, 0x00));

	protected static $passwords = array();

	/**
	 * The algorithm to use for PBKDF2. Must be a supported hash_hmac algorithm. Default: sha1
	 *
	 * @var  string
	 */
	private static $pbkdf2Algorithm = 'sha1';

	/**
	 * Number of iterations to use for PBKDF2
	 *
	 * @var  int
	 */
	private static $pbkdf2Iterations = 1000;

	/**
	 * Should we use a static salt for PBKDF2?
	 *
	 * @var  int
	 */
	private static $pbkdf2UseStaticSalt = 0;

	/**
	 * The static salt to use for PBKDF2
	 *
	 * @var  string
	 */
	private static $pbkdf2StaticSalt = "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0";

	/**
	 * AES Cipher function: encrypt 'input' with Rijndael algorithm
	 *
	 * @param   array $input    Message as byte-array (16 bytes)
	 * @param   array $w        key schedule as 2D byte-array (Nr+1 x Nb bytes) -
	 *                          generated from the cipher key by KeyExpansion()
	 *
	 * @return  string  Ciphertext as byte-array (16 bytes)
	 */
	protected static function Cipher($input, $w)
	{
		// main Cipher function [�5.1]
		$Nb = 4;                 // block size (in words): no of columns in state (fixed at 4 for AES)
		$Nr = count($w) / $Nb - 1; // no of rounds: 10/12/14 for 128/192/256-bit keys

		$state = array();  // initialise 4xNb byte-array 'state' with input [�3.4]

		for ($i = 0; $i < 4 * $Nb; $i++)
		{
			$state[$i % 4][floor($i / 4)] = $input[$i];
		}

		$state = self::AddRoundKey($state, $w, 0, $Nb);

		for ($round = 1; $round < $Nr; $round++)
		{  // apply Nr rounds
			$state = self::SubBytes($state, $Nb);
			$state = self::ShiftRows($state, $Nb);
			$state = self::MixColumns($state);
			$state = self::AddRoundKey($state, $w, $round, $Nb);
		}

		$state = self::SubBytes($state, $Nb);
		$state = self::ShiftRows($state, $Nb);
		$state = self::AddRoundKey($state, $w, $Nr, $Nb);

		$output = array(4 * $Nb);  // convert state to 1-d array before returning [�3.4]

		for ($i = 0; $i < 4 * $Nb; $i++)
		{
			$output[$i] = $state[$i % 4][floor($i / 4)];
		}

		return $output;
	}

	protected static function AddRoundKey($state, $w, $rnd, $Nb)
	{
		// xor Round Key into state S [�5.1.4]
		for ($r = 0; $r < 4; $r++)
		{
			for ($c = 0; $c < $Nb; $c++)
			{
				$state[$r][$c] ^= $w[$rnd * 4 + $c][$r];
			}
		}

		return $state;
	}

	protected static function SubBytes($s, $Nb)
	{
		// apply SBox to state S [�5.1.1]
		for ($r = 0; $r < 4; $r++)
		{
			for ($c = 0; $c < $Nb; $c++)
			{
				$s[$r][$c] = self::$Sbox[$s[$r][$c]];
			}
		}

		return $s;
	}

	protected static function ShiftRows($s, $Nb)
	{
		// shift row r of state S left by r bytes [�5.1.2]
		$t = array(4);

		for ($r = 1; $r < 4; $r++)
		{
			for ($c = 0; $c < 4; $c++)
			{
				$t[$c] = $s[$r][($c + $r) % $Nb];
			}  // shift into temp copy

			for ($c = 0; $c < 4; $c++)
			{
				$s[$r][$c] = $t[$c];
			}         // and copy back
		}          // note that this will work for Nb=4,5,6, but not 7,8 (always 4 for AES):

		return $s;  // see fp.gladman.plus.com/cryptography_technology/rijndael/aes.spec.311.pdf
	}

	protected static function MixColumns($s)
	{
		// combine bytes of each col of state S [�5.1.3]
		for ($c = 0; $c < 4; $c++)
		{
			$a = array(4);  // 'a' is a copy of the current column from 's'
			$b = array(4);  // 'b' is a�{02} in GF(2^8)

			for ($i = 0; $i < 4; $i++)
			{
				$a[$i] = $s[$i][$c];
				$b[$i] = $s[$i][$c] & 0x80 ? $s[$i][$c] << 1 ^ 0x011b : $s[$i][$c] << 1;
			}

			// a[n] ^ b[n] is a�{03} in GF(2^8)
			$s[0][$c] = $b[0] ^ $a[1] ^ $b[1] ^ $a[2] ^ $a[3]; // 2*a0 + 3*a1 + a2 + a3
			$s[1][$c] = $a[0] ^ $b[1] ^ $a[2] ^ $b[2] ^ $a[3]; // a0 * 2*a1 + 3*a2 + a3
			$s[2][$c] = $a[0] ^ $a[1] ^ $b[2] ^ $a[3] ^ $b[3]; // a0 + a1 + 2*a2 + 3*a3
			$s[3][$c] = $a[0] ^ $b[0] ^ $a[1] ^ $a[2] ^ $b[3]; // 3*a0 + a1 + a2 + 2*a3
		}

		return $s;
	}

	/**
	 * Key expansion for Rijndael Cipher(): performs key expansion on cipher key
	 * to generate a key schedule
	 *
	 * @param   array $key Cipher key byte-array (16 bytes)
	 *
	 * @return  array  Key schedule as 2D byte-array (Nr+1 x Nb bytes)
	 */
	protected static function KeyExpansion($key)
	{
		// generate Key Schedule from Cipher Key [�5.2]

		// block size (in words): no of columns in state (fixed at 4 for AES)
		$Nb = 4;
		// key length (in words): 4/6/8 for 128/192/256-bit keys
		$Nk = (int) (count($key) / 4);
		// no of rounds: 10/12/14 for 128/192/256-bit keys
		$Nr = $Nk + 6;

		$w    = array();
		$temp = array();

		for ($i = 0; $i < $Nk; $i++)
		{
			$r     = array($key[4 * $i], $key[4 * $i + 1], $key[4 * $i + 2], $key[4 * $i + 3]);
			$w[$i] = $r;
		}

		for ($i = $Nk; $i < ($Nb * ($Nr + 1)); $i++)
		{
			$w[$i] = array();
			for ($t = 0; $t < 4; $t++)
			{
				$temp[$t] = $w[$i - 1][$t];
			}
			if ($i % $Nk == 0)
			{
				$temp = self::SubWord(self::RotWord($temp));
				for ($t = 0; $t < 4; $t++)
				{
					$rConIndex = (int) ($i / $Nk);
					$temp[$t] ^= self::$Rcon[$rConIndex][$t];
				}
			}
			else if ($Nk > 6 && $i % $Nk == 4)
			{
				$temp = self::SubWord($temp);
			}
			for ($t = 0; $t < 4; $t++)
			{
				$w[$i][$t] = $w[$i - $Nk][$t] ^ $temp[$t];
			}
		}

		return $w;
	}

	protected static function SubWord($w)
	{
		// apply SBox to 4-byte word w
		for ($i = 0; $i < 4; $i++)
		{
			$w[$i] = self::$Sbox[$w[$i]];
		}

		return $w;
	}

	/*
	 * Unsigned right shift function, since PHP has neither >>> operator nor unsigned ints
	 *
	 * @param a  number to be shifted (32-bit integer)
	 * @param b  number of bits to shift a to the right (0..31)
	 * @return   a right-shifted and zero-filled by b bits
	 */

	protected static function RotWord($w)
	{
		// rotate 4-byte word w left by one byte
		$tmp = $w[0];
		for ($i = 0; $i < 3; $i++)
		{
			$w[$i] = $w[$i + 1];
		}
		$w[3] = $tmp;

		return $w;
	}

	protected static function urs($a, $b)
	{
		$a &= 0xffffffff;
		$b &= 0x1f;  // (bounds check)
		if ($a & 0x80000000 && $b > 0)
		{   // if left-most bit set
			$a = ($a >> 1) & 0x7fffffff;   //   right-shift one bit & clear left-most bit
			$a = $a >> ($b - 1);           //   remaining right-shifts
		}
		else
		{                       // otherwise
			$a = ($a >> $b);               //   use normal right-shift
		}

		return $a;
	}

	/**
	 * AES decryption in CBC mode. This is the standard mode (the CTR methods
	 * actually use Rijndael-128 in CTR mode, which - technically - isn't AES).
	 *
	 * It supports AES-128 only. It assumes that the last 4 bytes
	 * contain a little-endian unsigned long integer representing the unpadded
	 * data length.
	 *
	 * @since  3.0.1
	 * @author Nicholas K. Dionysopoulos
	 *
	 * @param   string $ciphertext The data to encrypt
	 * @param   string $password   Encryption password
	 *
	 * @return  string  The plaintext
	 */
	public static function AESDecryptCBC($ciphertext, $password)
	{
		$adapter = self::getAdapter();

		if (!$adapter->isSupported())
		{
			return false;
		}

		// Read the data size
		$data_size = unpack('V', substr($ciphertext, -4));

		// Do I have a PBKDF2 salt?
		$salt             = substr($ciphertext, -92, 68);
		$rightStringLimit = -4;

		$params        = self::getKeyDerivationParameters();
		$keySizeBytes  = $params['keySize'];
		$algorithm     = $params['algorithm'];
		$iterations    = $params['iterations'];
		$useStaticSalt = $params['useStaticSalt'];

		if (substr($salt, 0, 4) == 'JPST')
		{
			// We have a stored salt. Retrieve it and tell decrypt to process the string minus the last 44 bytes
			// (4 bytes for JPST, 16 bytes for the salt, 4 bytes for JPIV, 16 bytes for the IV, 4 bytes for the
			// uncompressed string length - note that using PBKDF2 means we're also using a randomized IV per the
			// format specification).
			$salt             = substr($salt, 4);
			$rightStringLimit -= 68;

			$key          = self::pbkdf2($password, $salt, $algorithm, $iterations, $keySizeBytes);
		}
		elseif ($useStaticSalt)
		{
			// We have a static salt. Use it for PBKDF2.
			$key = self::getStaticSaltExpandedKey($password);
		}
		else
		{
			// Get the expanded key from the password. THIS USES THE OLD, INSECURE METHOD.
			$key = self::expandKey($password);
		}

		// Try to get the IV from the data
		$iv               = substr($ciphertext, -24, 20);

		if (substr($iv, 0, 4) == 'JPIV')
		{
			// We have a stored IV. Retrieve it and tell mdecrypt to process the string minus the last 24 bytes
			// (4 bytes for JPIV, 16 bytes for the IV, 4 bytes for the uncompressed string length)
			$iv               = substr($iv, 4);
			$rightStringLimit -= 20;
		}
		else
		{
			// No stored IV. Do it the dumb way.
			$iv = self::createTheWrongIV($password);
		}

		// Decrypt
		$plaintext = $adapter->decrypt($iv . substr($ciphertext, 0, $rightStringLimit), $key);

		// Trim padding, if necessary
		if (strlen($plaintext) > $data_size)
		{
			$plaintext = substr($plaintext, 0, $data_size);
		}

		return $plaintext;
	}

	/**
	 * That's the old way of creating an IV that's definitely not cryptographically sound.
	 *
	 * DO NOT USE, EVER, UNLESS YOU WANT TO DECRYPT LEGACY DATA
	 *
	 * @param   string $password The raw password from which we create an IV in a super bozo way
	 *
	 * @return  string  A 16-byte IV string
	 */
	public static function createTheWrongIV($password)
	{
		static $ivs = array();

		$key = md5($password);

		if (!isset($ivs[$key]))
		{
			$nBytes  = 16;  // AES uses a 128 -bit (16 byte) block size, hence the IV size is always 16 bytes
			$pwBytes = array();
			for ($i = 0; $i < $nBytes; $i++)
			{
				$pwBytes[$i] = ord(substr($password, $i, 1)) & 0xff;
			}
			$iv    = self::Cipher($pwBytes, self::KeyExpansion($pwBytes));
			$newIV = '';
			foreach ($iv as $int)
			{
				$newIV .= chr($int);
			}

			$ivs[$key] = $newIV;
		}

		return $ivs[$key];
	}

	/**
	 * Expand the password to an appropriate 128-bit encryption key
	 *
	 * @param   string $password
	 *
	 * @return  string
	 *
	 * @since   5.2.0
	 * @author  Nicholas K. Dionysopoulos
	 */
	public static function expandKey($password)
	{
		// Try to fetch cached key or create it if it doesn't exist
		$nBits     = 128;
		$lookupKey = md5($password . '-' . $nBits);

		if (array_key_exists($lookupKey, self::$passwords))
		{
			$key = self::$passwords[$lookupKey];

			return $key;
		}

		// use AES itself to encrypt password to get cipher key (using plain password as source for
		// key expansion) - gives us well encrypted key.
		$nBytes  = $nBits / 8; // Number of bytes in key
		$pwBytes = array();

		for ($i = 0; $i < $nBytes; $i++)
		{
			$pwBytes[$i] = ord(substr($password, $i, 1)) & 0xff;
		}

		$key    = self::Cipher($pwBytes, self::KeyExpansion($pwBytes));
		$key    = array_merge($key, array_slice($key, 0, $nBytes - 16)); // expand key to 16/24/32 bytes long
		$newKey = '';

		foreach ($key as $int)
		{
			$newKey .= chr($int);
		}

		$key = $newKey;

		self::$passwords[$lookupKey] = $key;

		return $key;
	}

	/**
	 * Returns the correct AES-128 CBC encryption adapter
	 *
	 * @return  AKEncryptionAESAdapterInterface
	 *
	 * @since   5.2.0
	 * @author  Nicholas K. Dionysopoulos
	 */
	public static function getAdapter()
	{
		static $adapter = null;

		if (is_object($adapter) && ($adapter instanceof AKEncryptionAESAdapterInterface))
		{
			return $adapter;
		}

		$adapter = new OpenSSL();

		if (!$adapter->isSupported())
		{
			$adapter = new Mcrypt();
		}

		return $adapter;
	}

	/**
	 * @return string
	 */
	public static function getPbkdf2Algorithm()
	{
		return self::$pbkdf2Algorithm;
	}

	/**
	 * @param string $pbkdf2Algorithm
	 * @return void
	 */
	public static function setPbkdf2Algorithm($pbkdf2Algorithm)
	{
		self::$pbkdf2Algorithm = $pbkdf2Algorithm;
	}

	/**
	 * @return int
	 */
	public static function getPbkdf2Iterations()
	{
		return self::$pbkdf2Iterations;
	}

	/**
	 * @param int $pbkdf2Iterations
	 * @return void
	 */
	public static function setPbkdf2Iterations($pbkdf2Iterations)
	{
		self::$pbkdf2Iterations = $pbkdf2Iterations;
	}

	/**
	 * @return int
	 */
	public static function getPbkdf2UseStaticSalt()
	{
		return self::$pbkdf2UseStaticSalt;
	}

	/**
	 * @param int $pbkdf2UseStaticSalt
	 * @return void
	 */
	public static function setPbkdf2UseStaticSalt($pbkdf2UseStaticSalt)
	{
		self::$pbkdf2UseStaticSalt = $pbkdf2UseStaticSalt;
	}

	/**
	 * @return string
	 */
	public static function getPbkdf2StaticSalt()
	{
		return self::$pbkdf2StaticSalt;
	}

	/**
	 * @param string $pbkdf2StaticSalt
	 * @return void
	 */
	public static function setPbkdf2StaticSalt($pbkdf2StaticSalt)
	{
		self::$pbkdf2StaticSalt = $pbkdf2StaticSalt;
	}

	/**
	 * Get the parameters fed into PBKDF2 to expand the user password into an encryption key. These are the static
	 * parameters (key size, hashing algorithm and number of iterations). A new salt is used for each encryption block
	 * to minimize the risk of attacks against the password.
	 *
	 * @return  array
	 */
	public static function getKeyDerivationParameters()
	{
		return array(
			'keySize'       => 16,
			'algorithm'     => self::$pbkdf2Algorithm,
			'iterations'    => self::$pbkdf2Iterations,
			'useStaticSalt' => self::$pbkdf2UseStaticSalt,
			'staticSalt'    => self::$pbkdf2StaticSalt,
		);
	}

	/**
	 * PBKDF2 key derivation function as defined by RSA's PKCS #5: https://www.ietf.org/rfc/rfc2898.txt
	 *
	 * Test vectors can be found here: https://www.ietf.org/rfc/rfc6070.txt
	 *
	 * This implementation of PBKDF2 was originally created by https://defuse.ca
	 * With improvements by http://www.variations-of-shadow.com
	 * Modified for Akeeba Engine by Akeeba Ltd (removed unnecessary checks to make it faster)
	 *
	 * @param   string  $password    The password.
	 * @param   string  $salt        A salt that is unique to the password.
	 * @param   string  $algorithm   The hash algorithm to use. Default is sha1.
	 * @param   int     $count       Iteration count. Higher is better, but slower. Default: 1000.
	 * @param   int     $key_length  The length of the derived key in bytes.
	 *
	 * @return  string  A string of $key_length bytes
	 */
	public static function pbkdf2($password, $salt, $algorithm = 'sha1', $count = 1000, $key_length = 16)
	{
		if (function_exists("hash_pbkdf2"))
		{
			return hash_pbkdf2($algorithm, $password, $salt, $count, $key_length, true);
		}

		$hash_length = akstringlen(hash($algorithm, "", true));
		$block_count = ceil($key_length / $hash_length);

		$output = "";

		for ($i = 1; $i <= $block_count; $i++)
		{
			// $i encoded as 4 bytes, big endian.
			$last = $salt . pack("N", $i);

			// First iteration
			$xorResult = hash_hmac($algorithm, $last, $password, true);
			$last      = $xorResult;

			// Perform the other $count - 1 iterations
			for ($j = 1; $j < $count; $j++)
			{
				$last = hash_hmac($algorithm, $last, $password, true);
				$xorResult ^= $last;
			}

			$output .= $xorResult;
		}

		return aksubstr($output, 0, $key_length);
	}

	/**
	 * Get the expanded key from the user supplied password using a static salt. The results are cached for performance
	 * reasons.
	 *
	 * @param   string  $password  The user-supplied password, UTF-8 encoded.
	 *
	 * @return  string  The expanded key
	 */
	private static function getStaticSaltExpandedKey($password)
	{
		$params        = self::getKeyDerivationParameters();
		$keySizeBytes  = $params['keySize'];
		$algorithm     = $params['algorithm'];
		$iterations    = $params['iterations'];
		$staticSalt    = $params['staticSalt'];

		$lookupKey = "PBKDF2-$algorithm-$iterations-" . md5($password . $staticSalt);

		if (!array_key_exists($lookupKey, self::$passwords))
		{
			self::$passwords[$lookupKey] = self::pbkdf2($password, $staticSalt, $algorithm, $iterations, $keySizeBytes);
		}

		return self::$passwords[$lookupKey];
	}

}

/**
 * Akeeba Restore
 * An AJAX-powered archive extraction library for JPA, JPS and ZIP archives
 *
 * @package   restore
 * @copyright Copyright (c)2008-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

/**
 * A timing safe equals comparison
 *
 * @param   string  $safe  The internal (safe) value to be checked
 * @param   string  $user  The user submitted (unsafe) value
 *
 * @return  boolean  True if the two strings are identical.
 *
 * @see     http://blog.ircmaxell.com/2014/11/its-all-about-time.html
 */
function timingSafeEquals($safe, $user)
{
	$safeLen = strlen($safe);
	$userLen = strlen($user);

	if ($userLen != $safeLen)
	{
		return false;
	}

	$result = 0;

	for ($i = 0; $i < $userLen; $i++)
	{
		$result |= (ord($safe[$i]) ^ ord($user[$i]));
	}

	// They are only identical strings if $result is exactly 0...
	return $result === 0;
}

/**
 * The Master Setup will read the configuration parameters from restoration.php or
 * the JSON-encoded "configuration" input variable and return the status.
 *
 * @return bool True if the master configuration was applied to the Factory object
 */
function masterSetup()
{
	// ------------------------------------------------------------
	// 1. Import basic setup parameters
	// ------------------------------------------------------------

	$ini_data = null;

	// In restore.php mode, require restoration.php or fail
	if (!defined('KICKSTART'))
	{
		// This is the standalone mode, used by Akeeba Backup Professional. It looks for a restoration.php
		// file to perform its magic. If the file is not there, we will abort.
		$setupFile = 'restoration.php';

		if (!file_exists($setupFile))
		{
			AKFactory::set('kickstart.enabled', false);

			return false;
		}

		/**
		 * If the setup file was created more than 1.5 hours ago we can assume that it's stale and someone forgot to
		 * remove it from the server. This hinders brute force attacks against the Kickstart password. Even a simple
		 * 8 character simple alphanum (a-z, 0-9) password yields over 2.8e12. Assuming a very fast server which can
		 * serve 100 requests to restore.php per second and an easy to attack password requiring going over just 1% of
		 * the search space it'd still take over 282 million seconds to brute force it. Our limit is more than 4 orders
		 * of magnitude lower than this best practical case scenario, giving us adequate protection against all but the
		 * luckiest attacker (spoiler alert: the mathematics of probabilities say you're not gonna get lucky).
		 *
		 * It is still advisable to remove the restoration.php file once you are done with the extraction. This check
		 * here is only meant as a failsafe in case of a server error during the extraction and subsequent lack of user
		 * action to remove the restoration.php file from their server.
		 */
		$setupFieCreationTime = filectime($setupFile);

		if (abs(time() - $setupFieCreationTime) > 5400)
		{
			AKFactory::set('kickstart.enabled', false);

			return false;
		}

		// Load restoration.php. It creates a global variable named $restoration_setup
		require_once $setupFile;

		$ini_data = $restoration_setup;

		if (empty($ini_data))
		{
			// No parameters fetched. Darn, how am I supposed to work like that?!
			AKFactory::set('kickstart.enabled', false);

			return false;
		}

		AKFactory::set('kickstart.enabled', true);
	}
	else
	{
		// Maybe we have $restoration_setup defined in the head of kickstart.php
		global $restoration_setup;

		if (!empty($restoration_setup) && !is_array($restoration_setup))
		{
			$ini_data = AKText::parse_ini_file($restoration_setup, false, true);
		}
		elseif (is_array($restoration_setup))
		{
			$ini_data = $restoration_setup;
		}
	}

	// Import any data from $restoration_setup
	if (!empty($ini_data))
	{
		foreach ($ini_data as $key => $value)
		{
			AKFactory::set($key, $value);
		}
		AKFactory::set('kickstart.enabled', true);
	}

	// Reinitialize $ini_data
	$ini_data = null;

	/**
	 * August 2018. Some third party developer with a dubious skill level (or complete lack thereof) wrote a piece of
	 * code which uses restore.php with an empty password (and never deleted the restoration.php file he created).
	 * According to his code comments he did this because he couldn't figure out how to make encrypted requests work,
	 * DESPITE THE FACT that com_joomlaupdate (part of Joomla! itself) has working code which does EXACTLY THAT. >:-o
	 *
	 * As a result of his actions all sites running his software have a massive vulnerability inflicted upon them. An
	 * attacker can absuse the (unlocked) restore.php to upload and install any arbitrary code in a ZIP archive,
	 * possibly overwriting core code. Discovering this problem takes a few seconds and there is code which is doing
	 * exactly that published years ago (during the active maintenance period of Joomla! 3.4, that long ago).
	 *
	 * This bit of code here detects an empty password and disables restore.php. His badly written software fails to
	 * execute and, most importantly, the unlucky users of his software will no longer have a remote code upload /
	 * remote code execution vulnerability on their sites.
	 *
	 * Remember, people, if you can't be bothered to take web application security seriously DO NOT SELL WEB SOFTWARE
	 * FOR A LIVING. There are other honest jobs you can do which don't involve using a computer in a dangerous and
	 * irresponsible manner.
	 */
	$password = AKFactory::get('kickstart.security.password', null);

	if (empty($password) || (trim($password) == '') || (strlen(trim($password)) < 10))
	{
		AKFactory::set('kickstart.enabled', false);

		return false;
	}


	// ------------------------------------------------------------
	// 2. Explode JSON parameters into $_REQUEST scope
	// ------------------------------------------------------------

	// Detect a JSON string in the request variable and store it.
	$json = getQueryParam('json', null);

	// Detect a password in the request variable and store it.
	$userPassword = getQueryParam('password', '');

	// Remove everything from the request, post and get arrays
	if (!empty($_REQUEST))
	{
		foreach ($_REQUEST as $key => $value)
		{
			unset($_REQUEST[$key]);
		}
	}

	if (!empty($_POST))
	{
		foreach ($_POST as $key => $value)
		{
			unset($_POST[$key]);
		}
	}

	if (!empty($_GET))
	{
		foreach ($_GET as $key => $value)
		{
			unset($_GET[$key]);
		}
	}

	// Authentication - Akeeba Restore 5.4.0 or later
	$password = AKFactory::get('kickstart.security.password', null);
	$isAuthenticated = false;

	/**
	 * Akeeba Restore 5.3.1 and earlier use a custom implementation of AES-128 in CTR mode to encrypt the JSON data
	 * between client and server. This is not used as a means to maintain secrecy (it's symmetrical encryption and the
	 * key is, by necessity, transmitted with the HTML page to the client). It's meant as a form of authentication, so
	 * that the server part can ensure that it only receives commands by an authorized client.
	 *
	 * The downside is that encryption in CTR mode (like CBC) is an all-or-nothing affair. This opens the possibility
	 * for a padding oracle attack (https://en.wikipedia.org/wiki/Padding_oracle_attack). While Akeeba Restore was
	 * hardened in 2014 to prevent the bulk of suck attacks it is still possible to attack the encryption using a very
	 * large number of requests (several dozens of thousands).
	 *
	 * Since Akeeba Restore 5.4.0 we have removed this authentication method and replaced it with the transmission of a
	 * very large length password. On the server side we use a timing safe password comparison. By its very nature, it
	 * will only leak the (well known, constant and large) length of the password but no more information about the
	 * password itself. See http://blog.ircmaxell.com/2014/11/its-all-about-time.html  As a result this form of
	 * authentication is many orders of magnitude harder to crack than regular encryption.
	 *
	 * Now you may wonder "how is sending a password in the clear hardier than encryption?". If you ask that question
	 * you were not paying attention. The password needs to be known by BOTH the server AND the client (browser). Since
	 * this password is generated programmatically by the server, it MUST be sent to the client by the server. If an
	 * attacker is able to intercept this transmission (man in the middle attack) using encryption is irrelevant: the
	 * attacker already knows your password. This situation also applies when the user sends their own password to the
	 * server, e.g. when logging into their site. The ONLY way to avoid security issues regarding information being
	 * stolen in transit is using HTTPS with a commercially signed SSL certificate. Unlike 2008, when Kickstart was
	 * originally written, obtaining such a certificate nowadays is trivial and costs absolutely nothing thanks to Let's
	 * Encrypt (https://letsencrypt.org/).
	 *
	 * TL;DR: Use HTTPS with a commercially signed SSL certificate, e.g. a free certificate from Let's Encrypt. Client-
	 * side cryptography does NOT protect you against an attacker (see
	 * https://www.nccgroup.trust/us/about-us/newsroom-and-events/blog/2011/august/javascript-cryptography-considered-harmful/).
	 * Moreover, sending a plaintext password is safer than relying on client-side encryption for authentication as it
	 * removes the possibility of an attacker inferring the contents of the authentication key (password) in a relatively
	 * easy and automated manner.
	 */
	if (!empty($password))
	{
		// Timing-safe password comparison. See http://blog.ircmaxell.com/2014/11/its-all-about-time.html
		if (!timingSafeEquals($password, $userPassword))
		{
			die('###{"status":false,"message":"Invalid login"}###');
		}
	}

	// No JSON data? Die.
	if (empty($json))
	{
		die('###{"status":false,"message":"Invalid JSON data"}###');
	}

	// Handle the JSON string
	$raw = json_decode($json, true);

	// Invalid JSON data?
	if (empty($raw))
	{
		die('###{"status":false,"message":"Invalid JSON data"}###');
	}

	// Pass all JSON data to the request array
	if (!empty($raw))
	{
		foreach ($raw as $key => $value)
		{
			$_REQUEST[$key] = $value;
		}
	}

	// ------------------------------------------------------------
	// 3. Try the "factory" variable
	// ------------------------------------------------------------
	// A "factory" variable will override all other settings.
	$serialized = getQueryParam('factory', null);

	if (!is_null($serialized))
	{
		// Get the serialized factory
		AKFactory::unserialize($serialized);
		AKFactory::set('kickstart.enabled', true);

		return true;
	}

	// ------------------------------------------------------------
	// 4. Try the configuration variable for Kickstart
	// ------------------------------------------------------------
	if (defined('KICKSTART'))
	{
		$configuration = getQueryParam('configuration');

		if (!is_null($configuration))
		{
			// Let's decode the configuration from JSON to array
			$ini_data = json_decode($configuration, true);
		}
		else
		{
			// Neither exists. Enable Kickstart's interface anyway.
			$ini_data = array('kickstart.enabled' => true);
		}

		// Import any INI data we might have from other sources
		if (!empty($ini_data))
		{
			foreach ($ini_data as $key => $value)
			{
				AKFactory::set($key, $value);
			}

			AKFactory::set('kickstart.enabled', true);

			return true;
		}
	}
}

/**
 * Akeeba Restore
 * An AJAX-powered archive extraction library for JPA, JPS and ZIP archives
 *
 * @package   restore
 * @copyright Copyright (c)2008-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

// Mini-controller for restore.php
if (!defined('KICKSTART'))
{
	// The observer class, used to report number of files and bytes processed
	class RestorationObserver extends AKAbstractPartObserver
	{
		public $compressedTotal = 0;
		public $uncompressedTotal = 0;
		public $filesProcessed = 0;

		public function update($object, $message)
		{
			if (!is_object($message))
			{
				return;
			}

			if (!array_key_exists('type', get_object_vars($message)))
			{
				return;
			}

			if ($message->type == 'startfile')
			{
				$this->filesProcessed++;
				$this->compressedTotal += $message->content->compressed;
				$this->uncompressedTotal += $message->content->uncompressed;
			}
		}

		public function __toString()
		{
			return __CLASS__;
		}

	}

	// Import configuration
	masterSetup();

	$retArray = array(
		'status'  => true,
		'message' => null
	);

	$enabled = AKFactory::get('kickstart.enabled', false);

	if ($enabled)
	{
		$task = getQueryParam('task');

		switch ($task)
		{
			case 'ping':
				// ping task - really does nothing!
				$timer = AKFactory::getTimer();
				$timer->enforce_min_exec_time();
				break;

			/**
			 * There are two separate steps here since we were using an inefficient restoration initialization method in
			 * the past. Now both startRestore and stepRestore are identical. The difference in behavior depends
			 * exclusively on the calling Javascript. If no serialized factory was passed in the request then we start a
			 * new restoration. If a serialized factory was passed in the request then the restoration is resumed. For
			 * this reason we should NEVER call AKFactory::nuke() in startRestore anymore: that would simply reset the
			 * extraction engine configuration which was done in masterSetup() leading to an error about the file being
			 * invalid (since no file is found).
			 */
			case 'startRestore':
			case 'stepRestore':
				if ($task == 'startRestore')
				{
					// Fetch path to the site root from the restoration.php file, so we can tell the engine where it should operate
					$siteRoot = AKFactory::get('kickstart.setup.destdir', '');

					// Before starting, read and save any custom AddHandler directive
					$phpHandlers = getPhpHandlers($siteRoot);
					AKFactory::set('kickstart.setup.phphandlers', $phpHandlers);

					// If the Stealth Mode is enabled, create the .htaccess file
					if (AKFactory::get('kickstart.stealth.enable', false))
					{
						createStealthURL($siteRoot);
					}
					// No stealth mode, but we have custom handler directives, must write our own file
					elseif ($phpHandlers)
					{
						writePhpHandlers($siteRoot);
					}
				}

				/**
				 * First try to run the filesystem zapper (remove all existing files and folders). If the Zapper is
				 * disabled or has already finished running we will get a FALSE result. Otherwise it's a status array
				 * which we can pass directly back to the caller.
				 */
				$ret = runZapper();

				// If the Zapper had a step to run we stop here and return its status array to the caller.
				if ($ret !== false)
				{
					$retArray = array_merge($retArray, $ret);

					break;
				}

				$engine   = AKFactory::getUnarchiver(); // Get the engine
				$observer = new RestorationObserver(); // Create a new observer
				$engine->attach($observer); // Attach the observer
				$engine->tick();
				$ret = $engine->getStatusArray();

				if ($ret['Error'] != '')
				{
					$retArray['status']  = false;
					$retArray['done']    = true;
					$retArray['message'] = $ret['Error'];
				}
				elseif (!$ret['HasRun'])
				{
					$retArray['files']    = $observer->filesProcessed;
					$retArray['bytesIn']  = $observer->compressedTotal;
					$retArray['bytesOut'] = $observer->uncompressedTotal;
					$retArray['status']   = true;
					$retArray['done']     = true;
				}
				else
				{
					$retArray['files']    = $observer->filesProcessed;
					$retArray['bytesIn']  = $observer->compressedTotal;
					$retArray['bytesOut'] = $observer->uncompressedTotal;
					$retArray['status']   = true;
					$retArray['done']     = false;
					$retArray['factory']  = AKFactory::serialize();
				}

				$timer = AKFactory::getTimer();
				$timer->enforce_min_exec_time();

				break;

			case 'finalizeRestore':
				$root = AKFactory::get('kickstart.setup.destdir');
				// Remove the installation directory
				recursive_remove_directory($root . '/installation');

				$postproc = AKFactory::getPostProc();

				/**
				 * Should I rename the htaccess.bak and web.config.bak files back to their live filenames...?
				 */
				$renameFiles = AKFactory::get('kickstart.setup.postrenamefiles', true);

				if ($renameFiles)
				{
					// Rename htaccess.bak to .htaccess
					if (file_exists($root . '/htaccess.bak'))
					{
						if (file_exists($root . '/.htaccess'))
						{
							$postproc->unlink($root . '/.htaccess');
						}

						$postproc->rename($root . '/htaccess.bak', $root . '/.htaccess');
					}

					// Rename htaccess.bak to .htaccess
					if (file_exists($root . '/web.config.bak'))
					{
						if (file_exists($root . '/web.config'))
						{
							$postproc->unlink($root . '/web.config');
						}

						$postproc->rename($root . '/web.config.bak', $root . '/web.config');
					}
				}

				// Remove restoration.php
				$basepath = KSROOTDIR;
				$basepath = rtrim(str_replace('\\', '/', $basepath), '/');

				if (!empty($basepath))
				{
					$basepath .= '/';
				}

				$postproc->unlink($basepath . 'restoration.php');
				clearFileInOPCache($basepath . 'restoration.php');

				// Import a custom finalisation file
				$filename = dirname(__FILE__) . '/restore_finalisation.php';

				if (file_exists($filename))
				{
					// opcode cache busting before including the filename
					if (function_exists('opcache_invalidate'))
					{
						opcache_invalidate($filename, true);
					}

					if (function_exists('apc_compile_file'))
					{
						apc_compile_file($filename);
					}

					if (function_exists('wincache_refresh_if_changed'))
					{
						wincache_refresh_if_changed([$filename]);
					}

					if (function_exists('xcache_asm'))
					{
						xcache_asm($filename);
					}

					include_once $filename;
				}

				// Run a custom finalisation script
				if (function_exists('finalizeRestore'))
				{
					finalizeRestore($root, $basepath);
				}

				break;

			default:
				// Invalid task!
				$enabled = false;
				break;
		}
	}

	// Maybe we weren't authorized or the task was invalid?
	if (!$enabled)
	{
		// Maybe the user failed to enter any information
		$retArray['status']  = false;
		$retArray['message'] = AKText::_('ERR_INVALID_LOGIN');
	}

	// JSON encode the message
	$json = json_encode($retArray);

	// Return the message
	echo "###$json###";

}

// ------------ lixlpixel recursive PHP functions -------------
// recursive_remove_directory( directory to delete, empty )
// expects path to directory and optional TRUE / FALSE to empty
// of course PHP has to have the rights to delete the directory
// you specify and all files and folders inside the directory
// ------------------------------------------------------------
function recursive_remove_directory($directory)
{
	// if the path has a slash at the end we remove it here
	if (substr($directory, -1) == '/')
	{
		$directory = substr($directory, 0, -1);
	}

	// if the path is not valid or is not a directory ...
	if (!file_exists($directory) || !is_dir($directory))
	{
		// ... we return false and exit the function
		return false;
		// ... if the path is not readable
	}
	elseif (!is_readable($directory))
	{
		// ... we return false and exit the function
		return false;
		// ... else if the path is readable
	}
	else
	{
		// we open the directory
		$handle   = opendir($directory);
		$postproc = AKFactory::getPostProc();

		// and scan through the items inside
		while (false !== ($item = readdir($handle)))
		{
			// if the filepointer is not the current directory
			// or the parent directory

			if ($item != '.' && $item != '..')
			{
				// we build the new path to delete
				$path = $directory . '/' . $item;

				// if the new path is a directory
				if (is_dir($path))
				{
					// we call this function with the new path
					recursive_remove_directory($path);
					// if the new path is a file
				}
				else
				{
					// we remove the file
					$postproc->unlink($path);
					clearFileInOPCache($path);
				}
			}
		}

		// close the directory
		closedir($handle);

		// try to delete the now empty directory
		if (!$postproc->rmdir($directory))
		{
			// return false if not possible
			return false;
		}

		// return success
		return true;
	}
}

function createStealthURL($siteRoot = '')
{
	$filename = AKFactory::get('kickstart.stealth.url', '');

	// We need an HTML file!
	if (empty($filename))
	{
		return;
	}

	// Make sure it ends in .html or .htm
	$filename = basename($filename);

	if ((strtolower(substr($filename, -5)) != '.html') && (strtolower(substr($filename, -4)) != '.htm'))
	{
		return;
	}

	if ($siteRoot)
	{
		$siteRoot = rtrim($siteRoot, '/').'/';
	}

	$filename_quoted = str_replace('.', '\\.', $filename);
	$rewrite_base    = trim(dirname(AKFactory::get('kickstart.stealth.url', '')), '/');

	// Get the IP
	$userIP = $_SERVER['REMOTE_ADDR'];
	$userIP = str_replace('.', '\.', $userIP);

	// Get the .htaccess contents
	$stealthHtaccess = <<<ENDHTACCESS
RewriteEngine On
RewriteBase /$rewrite_base
RewriteCond %{REMOTE_ADDR}		!$userIP
RewriteCond %{REQUEST_URI}		!$filename_quoted
RewriteCond %{REQUEST_URI}		!(\.png|\.jpg|\.gif|\.jpeg|\.bmp|\.swf|\.css|\.js)$
RewriteRule (.*)				$filename	[R=307,L]

ENDHTACCESS;

	$customHandlers = portPhpHandlers();

	// Port any custom handlers in the stealth file
	if ($customHandlers)
	{
		$stealthHtaccess .= "\n".$customHandlers."\n";
	}

	// Write the new .htaccess, removing the old one first
	$postproc = AKFactory::getpostProc();
	$postproc->unlink($siteRoot.'.htaccess');
	$tempfile = $postproc->processFilename($siteRoot.'.htaccess');
	@file_put_contents($tempfile, $stealthHtaccess);
	$postproc->process();
}

/**
 * Checks if there is an .htaccess file and has any AddHandler directive in it.
 * In that case, we return the affected lines so they could be stored for later use
 *
 * @return  array
 */
function getPhpHandlers($root = null)
{
	if (!$root)
	{
		$root = AKKickstartUtils::getPath();
	}

	$htaccess   = $root.'/.htaccess';
	$directives = array();

	if (!file_exists($htaccess))
	{
		return $directives;
	}

	$contents   = file_get_contents($htaccess);
	$directives = AKUtilsHtaccess::extractHandler($contents);
	$directives = explode("\n", $directives);

	return $directives;
}

/**
 * Fetches any stored php handler directive stored inside the factory and creates a string with the correct markers
 *
 * @return string
 */
function portPhpHandlers()
{
	$phpHandlers = AKFactory::get('kickstart.setup.phphandlers', array());

	if (!$phpHandlers)
	{
		return '';
	}

	$customHandler  = "### AKEEBA_KICKSTART_PHP_HANDLER_BEGIN ###\n";
	$customHandler .= implode("\n", $phpHandlers)."\n";
	$customHandler .= "### AKEEBA_KICKSTART_PHP_HANDLER_END ###\n";

	return $customHandler;
}

function writePhpHandlers($siteRoot = '')
{
	$contents = portPhpHandlers();

	if (!$contents)
	{
		return;
	}

	if ($siteRoot)
	{
		$siteRoot = rtrim($siteRoot, '/').'/';
	}

	// Write the new .htaccess, removing the old one first
	$postproc = AKFactory::getpostProc();
	$postproc->unlink($siteRoot.'.htaccess');
	$tempfile = $postproc->processFilename($siteRoot.'.htaccess');
	@file_put_contents($tempfile, $contents);
	$postproc->process();
}


/**
 * Akeeba Kickstart
 * An AJAX-powered archive extraction tool
 *
 * @package   kickstart
 * @copyright Copyright (c)2008-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */
class AKKickstartUtils
{
	/**
	 * Guess the best path containing backup archives. The default strategy is check in the current directory first,
	 * then attempt to find an Akeeba Backup for Joomla!, Akeeba Solo or Akeeba Backup for WordPress default backup
	 * output directory under the current root. The first one containing backup archives wins.
	 *
	 * @return string The path to get archives from
	 */
	public static function getBestArchivePath()
	{
		$basePath      = self::getPath();
		$basePathSlash = (empty($basePath) ? '.' : rtrim($basePath, '/\\')) . '/';

		$paths = array(
			// Root, same as the directory we're in
			$basePath,
			// Standard temporary directory
			$basePath . '/kicktemp',
			// Akeeba Backup for Joomla!, default output directory
			$basePathSlash . 'administrator/components/com_akeeba/backup',
			$basePathSlash . 'administrator/components/com_akeebabackup/backup',
			// Akeeba Solo, default output directory
			$basePathSlash . 'backups',
			// Akeeba Backup for WordPress, default output directory
			$basePathSlash . 'wp-content/plugins/akeebabackupwp/app/backups',
		);

		foreach ($paths as $path)
		{
			$archives = self::findArchives($path);

			if (!empty($archives))
			{
				return $path;
			}
		}

		return $basePath;
	}

	/**
	 * Gets the directory the file is in
	 *
	 * @return string
	 */
	public static function getPath()
	{
		$path = KSROOTDIR;
		$path = rtrim(str_replace('\\', '/', $path), '/');

		if (!empty($path))
		{
			$path .= '/';
		}

		return $path;
	}

	/**
	 * Scans the current directory for archive files (JPA, JPS and ZIP format)
	 *
	 * @param string $path The path to look for archives. null for automatic path
	 *
	 * @return array
	 */
	public static function findArchives($path)
	{
		$ret = array();

		if (empty($path))
		{
			$path = self::getPath();
		}

		if (empty($path))
		{
			$path = '.';
		}

		$dh = @opendir($path);

		if ($dh === false)
		{
			return $ret;
		}

		while (false !== $file = @readdir($dh))
		{
			$dotpos = strrpos($file, '.');

			if ($dotpos === false)
			{
				continue;
			}

			if ($dotpos == strlen($file))
			{
				continue;
			}

			$extension = strtolower(substr($file, $dotpos + 1));

			if (in_array($extension, array('jpa', 'zip', 'jps')))
			{
				$ret[] = $file;
			}
		}

		closedir($dh);

		if (!empty($ret))
		{
			return $ret;
		}

		// On some hosts using opendir doesn't work. Let's try Dir instead
		$d = dir($path);

		while (false != ($file = $d->read()))
		{
			$dotpos = strrpos($file, '.');

			if ($dotpos === false)
			{
				continue;
			}

			if ($dotpos == strlen($file))
			{
				continue;
			}

			$extension = strtolower(substr($file, $dotpos + 1));

			if (in_array($extension, array('jpa', 'zip', 'jps')))
			{
				$ret[] = $file;
			}
		}

		return $ret;
	}

	/**
	 * Gets the most appropriate temporary path
	 *
	 * @return string
	 */
	public static function getTemporaryPath()
	{
		$path = self::getPath();

		$candidateDirs = array(
			$path,
			$path . '/kicktemp',
		);

		if (function_exists('sys_get_temp_dir'))
		{
			$candidateDirs[] = sys_get_temp_dir();
		}

		foreach ($candidateDirs as $dir)
		{
			if (is_dir($dir) && is_writable($dir))
			{
				return $dir;
			}
		}

		// Failsafe
		return $path;
	}

	/**
	 * Scans the current directory for archive files and returns them as <OPTION> tags
	 *
	 * @param string $path The path to look for archives. null for automatic path
	 *
	 * @return string
	 */
	public static function getArchivesAsOptions($path = null)
	{
		$ret = '';

		$archives = self::findArchives($path);

		if (empty($archives))
		{
			return $ret;
		}

		foreach ($archives as $file)
		{
			//$file = htmlentities($file);
			$ret .= '<option value="' . $file . '">' . $file . '</option>' . "\n";
		}

		return $ret;
	}
}


/**
 * Akeeba Kickstart
 * An AJAX-powered archive extraction tool
 *
 * @package   kickstart
 * @copyright Copyright (c)2008-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */
class ExtractionObserver extends AKAbstractPartObserver
{
	public $compressedTotal = 0;
	public $uncompressedTotal = 0;
	public $filesProcessed = 0;
	public $totalSize = null;
	public $fileList = null;
	public $lastFile = '';

	public function update($object, $message)
	{
		if (!is_object($message))
		{
			return;
		}

		if (!array_key_exists('type', get_object_vars($message)))
		{
			return;
		}

		switch ($message->type)
		{
			// Sent when we read the list of archive parts and their total size
			case 'totalsize':
				$this->totalSize = $message->content->totalsize;
				$this->fileList  = $message->content->filelist;
				break;

			// Sent when a file header is read from the archive
			case 'startfile':
				$this->lastFile = $message->content->file;
				$this->filesProcessed++;
				$this->compressedTotal += $message->content->compressed;
				$this->uncompressedTotal += $message->content->uncompressed;
				break;
		}

	}

	public function __toString()
	{
		return __CLASS__;
	}

}

/**
 * Akeeba Kickstart
 * An AJAX-powered archive extraction tool
 *
 * @package   kickstart
 * @copyright Copyright (c)2008-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

function callExtraFeature($method = null, array $params = array())
{
	static $extraFeatureObjects = null;

	if (!is_array($extraFeatureObjects))
	{
		$extraFeatureObjects = array();
		$allClasses          = get_declared_classes();
		foreach ($allClasses as $class)
		{
			if (substr($class, 0, 9) == 'AKFeature')
			{
				$extraFeatureObjects[] = new $class;
			}
		}
	}

	if (is_null($method))
	{
		return;
	}

	if (empty($extraFeatureObjects))
	{
		return;
	}

	$result = null;
	foreach ($extraFeatureObjects as $o)
	{
		if (!method_exists($o, $method))
		{
			continue;
		}
		$result = call_user_func(array($o, $method), $params);
	}

	return $result;
}

/**
 * Akeeba Kickstart
 * An AJAX-powered archive extraction tool
 *
 * @package   kickstart
 * @copyright Copyright (c)2008-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

/**
 * Removes trailing slash or backslash from a pathname
 *
 * @param   string  $path  The path to treat
 *
 * @return  string  The path without the trailing slash/backslash
 */
function TrimTrailingSlash($path)
{
	$newpath = $path;

	if (substr($path, strlen($path) - 1, 1) == '\\')
	{
		$newpath = substr($path, 0, strlen($path) - 1);
	}

	if (substr($path, strlen($path) - 1, 1) == '/')
	{
		$newpath = substr($path, 0, strlen($path) - 1);
	}

	return $newpath;
}


function TranslateWinPath($p_path)
{
	$is_unc = false;

	if (KSWINDOWS)
	{
		// Is this a UNC path?
		$is_unc = (substr($p_path, 0, 2) == '\\\\') || (substr($p_path, 0, 2) == '//');

		// Change potential windows directory separator
		if ((strpos($p_path, '\\') > 0) || (substr($p_path, 0, 1) == '\\'))
		{
			$p_path = strtr($p_path, '\\', '/');
		}
	}

	// Remove multiple slashes
	$p_path = str_replace('///', '/', $p_path);
	$p_path = str_replace('//', '/', $p_path);

	// Fix UNC paths
	if ($is_unc)
	{
		$p_path = '//' . ltrim($p_path, '/');
	}

	return $p_path;
}


/**
 * FTP Functions
 */
function getListing($directory, $host, $port, $username, $password, $passive, $ssl)
{
	$directory = resolvePath($directory);
	$dir       = $directory;

	// Parse directory to parts
	$parsed_dir = trim($dir, '/');
	$parts      = empty($parsed_dir) ? array() : explode('/', $parsed_dir);

	// Find the path to the parent directory
	if (!empty($parts))
	{
		$copy_of_parts = $parts;
		array_pop($copy_of_parts);

		if (!empty($copy_of_parts))
		{
			$parent_directory = '/' . implode('/', $copy_of_parts);
		}
		else
		{
			$parent_directory = '/';
		}
	}
	else
	{
		$parent_directory = '';
	}

	// Connect to the server
	if ($ssl)
	{
		$con = @ftp_ssl_connect($host, $port);
	}
	else
	{
		$con = @ftp_connect($host, $port);
	}

	if ($con === false)
	{
		return array(
			'error' => 'FTPBROWSER_ERROR_HOSTNAME'
		);
	}

	// Login
	$result = @ftp_login($con, $username, $password);

	if ($result === false)
	{
		return array(
			'error' => 'FTPBROWSER_ERROR_USERPASS'
		);
	}

	// Set the passive mode -- don't care if it fails, though!
	@ftp_pasv($con, $passive);

	// Try to chdir to the specified directory
	if (!empty($dir))
	{
		$result = @ftp_chdir($con, $dir);

		if ($result === false)
		{
			return array(
				'error' => 'FTPBROWSER_ERROR_NOACCESS'
			);
		}
	}
	else
	{
		$directory = @ftp_pwd($con);

		$parsed_dir       = trim($directory, '/');
		$parts            = empty($parsed_dir) ? array() : explode('/', $parsed_dir);
		$parent_directory = $this->directory;
	}

	// Get a raw directory listing (hoping it's a UNIX server!)
	$list = @ftp_rawlist($con, '.');
	ftp_close($con);

	if ($list === false)
	{
		return array(
			'error' => 'FTPBROWSER_ERROR_UNSUPPORTED'
		);
	}

	// Parse the raw listing into an array
	$folders = parse_rawlist($list);

	return array(
		'error'       => '',
		'list'        => $folders,
		'breadcrumbs' => $parts,
		'directory'   => $directory,
		'parent'      => $parent_directory
	);
}

function parse_rawlist($list)
{
	$folders = array();

	foreach ($list as $v)
	{
		$info  = array();
		$vinfo = preg_split("/[\s]+/", $v, 9);

		if ($vinfo[0] !== "total")
		{
			$perms = $vinfo[0];

			if (substr($perms, 0, 1) == 'd')
			{
				$folders[] = $vinfo[8];
			}
		}
	}

	asort($folders);

	return $folders;
}

function getSftpListing($directory, $host, $port, $username, $password)
{
	$directory = resolvePath($directory);
	$dir       = $directory;

	// Parse directory to parts
	$parsed_dir = trim($dir, '/');
	$parts      = empty($parsed_dir) ? array() : explode('/', $parsed_dir);

	// Find the path to the parent directory
	if (!empty($parts))
	{
		$copy_of_parts = $parts;
		array_pop($copy_of_parts);

		if (!empty($copy_of_parts))
		{
			$parent_directory = '/' . implode('/', $copy_of_parts);
		}
		else
		{
			$parent_directory = '/';
		}
	}
	else
	{
		$parent_directory = '';
	}

	// Initialise
	$connection = null;
	$sftphandle = null;

	// Open a connection
	if (!function_exists('ssh2_connect'))
	{
		return array(
			'error' => AKText::_('SFTP_NO_SSH2')
		);
	}

	$connection = ssh2_connect($host, $port);

	if ($connection === false)
	{
		return array(
			'error' => AKText::_('SFTP_WRONG_USER')
		);
	}

	if (!ssh2_auth_password($connection, $username, $password))
	{
		return array(
			'error' => AKText::_('SFTP_WRONG_USER')
		);
	}

	$sftphandle = ssh2_sftp($connection);

	if ($sftphandle === false)
	{
		return array(
			'error' => AKText::_('SFTP_NO_FTP_SUPPORT')
		);
	}

	// Get a raw directory listing (hoping it's a UNIX server!)
	$list = array();
	$dir  = ltrim($dir, '/');

	if (empty($dir))
	{
		$dir       = ssh2_sftp_realpath($sftphandle, ".");
		$directory = $dir;

		// Parse directory to parts
		$parsed_dir = trim($dir, '/');
		$parts      = empty($parsed_dir) ? array() : explode('/', $parsed_dir);

		// Find the path to the parent directory
		if (!empty($parts))
		{
			$copy_of_parts = $parts;
			array_pop($copy_of_parts);

			if (!empty($copy_of_parts))
			{
				$parent_directory = '/' . implode('/', $copy_of_parts);
			}
			else
			{
				$parent_directory = '/';
			}
		}
		else
		{
			$parent_directory = '';
		}
	}

	$handle = opendir("ssh2.sftp://$sftphandle/$dir");

	if (!is_resource($handle))
	{
		return array(
			'error' => AKText::_('SFTPBROWSER_ERROR_NOACCESS')
		);
	}

	while (($entry = readdir($handle)) !== false)
	{
		if (!is_dir("ssh2.sftp://$sftphandle/$dir/$entry"))
		{
			continue;
		}

		$list[] = $entry;
	}

	closedir($handle);

	if (!empty($list))
	{
		asort($list);
	}

	return array(
		'error'       => '',
		'list'        => $list,
		'breadcrumbs' => $parts,
		'directory'   => $directory,
		'parent'      => $parent_directory
	);
}

/**
 * Simple function to resolve relative paths.
 * Note that it is unable to resolve pathnames any higher than the present working directory.
 * I.E. It doesn't know about any directory names that you don't tell it about; hence: ../../foo becomes foo.
 *
 * @param $filename
 *
 * @return string
 */
function resolvePath($filename)
{
	$filename = str_replace('//', '/', $filename);
	$parts    = explode('/', $filename);
	$out      = array();

	foreach ($parts as $part)
	{
		if ($part == '.')
		{
			continue;
		}

		if ($part == '..')
		{
			array_pop($out);
			continue;
		}

		$out[] = $part;
	}

	return implode('/', $out);
}

/**
 * Akeeba Kickstart
 * An AJAX-powered archive extraction tool
 *
 * @package   kickstart
 * @copyright Copyright (c)2008-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

function echoCSS()
{
	echo <<<CSS

:root {
    --teal-dark: #339092;
    --teal: #40B5B8;
    --teal-light: #62c6c9;

    --red-dark: #c81d23;
    --red: #E2363C;
    --red-light: #e86367;

    --grey-superdark: #272727;
    --grey-dark: #373637;
    --grey: #514F50;
    --grey-light: #6b686a;

    --green-dark: #79a638;;
    --green: #93C34E;
    --green-light: #aad074;

    --orange-dark: #ec971f;
    --orange: #F0AD4E;
    --orange-light: #f4c37d;

    --lightgrey-dark: #d6d6d6;
    --lightgrey: #EFEFEF;
    --lightgrey-light: #fcfcfc;

    --white: #ffffff;
    --black: #000000;

    --system-ui: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
    --monospace: 'Berkeley Mono', ui-monospace, 'SF Mono', SFMono-Regular, 'DejaVu Sans Mono', Menlo, Consolas, "Courier New", Courier, monospace;
}

html {
    background: var(--white);
    font-size: 62.5%;
}

a, a:visited {
	color: var(--teal);
}

a:hover, a:active {
	color: var(--teal);
}

body {
    font-size: 12pt;
    font-family: var(--system-ui);
    text-rendering: optimizeLegibility;
    background: transparent;
    color: var(--grey-dark);
    width: 100%;
    max-width: 980px;
    margin: 0 auto;
}

#page-container {
    position: relative;
    margin: 5% 0;
    background: var(--lightgrey-light);
    border: medium solid var(--lightgrey-dark);
}

#header {
    color: var(--grey-dark);
    background: var(--lightgrey);
    background-clip: padding-box;
    margin-bottom: 0.7em;
    border-bottom: 2px solid var(--lightgrey-dark);
    padding: .25em;
    font-size: 24pt;
    line-height: 1.2;
    text-align: center;
}

#logo {
	background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABkAAAAZCAYAAADE6YVjAAAACXBIWXMAAAHFAAABxQG6eNsrAAAA/0lEQVRIx+WVUQ2DMBCGv01B5wAHYw4qARxMwiQgYQ6YAyRMAjgAB+CgezmSSwOjZfRplzSXNE2/3t1/V/hXuwMjkKcC5ICTNQImBaRVEAfUKdLkFlaWMorDo8lUHXIvsvEoyEMubFai21TaOQByVeqaFWVUPewRkEz5FqjEa+Aus3KpAfqVojsvjXYtdacVSA8MAgqRaSd+AMrYzt6zgmriv3zaeNS08MhNiD5UArcvoA64AE+1FySEYiF0I939Fj/3iFVFd6F9g1KU33yNGiX+JDYCbmIkXHm1sd6YX/pT7pKF3VYrSB87fc8RZyfgJX5I8WEVsfn+5fs1/LV9AHnjYQzAbyUrAAAAAElFTkSuQmCC);
	display: inline-block;
	width: 24px;
	height: 24px;
}

#footer {
    font-size: 9pt;
    color: var(--grey-light);
    text-align: center;
    border-top: 1px solid var(--lightgrey-dark);
    padding: 1em 1em;
    background: var(--lightgrey);
    clear: both;
}

#footer a {
    color: var(--teal-dark);
    text-decoration: none;
}

#error, .error {
    x-display: none;
    border: solid var(--red-dark);
    border-width: 4px 0;
    background: var(--red-light);
    color: var(--grey-dark);
    padding: 1em 2em;
    margin-bottom: 1.15em;
    text-align: center;
}

#error h3, .error h3, .warning h3, .notice h3 {
    margin: 0;
    padding: 0;
    font-size: 12pt;
}

.warning {
    border: solid var(--orange-dark);
    border-width: 4px 0;
    background: var(--orange-light);
    color: var(--grey-dark);
    padding: 1em 2em;
    margin-bottom: 1.15em;
    text-align: center;
}

.notice {
    border: solid var(--teal-dark);
    border-width: 4px 0;
    background: var(--teal-light);
    color: var(--grey-dark);
    padding: 1em 2em;
    margin-bottom: 1.15em;
    text-align: center;
}

.clr {
    clear: both;
}

.circle {
    display: block;
    float: left;
    border-radius: 2em;
    border: 2px solid var(--lightgrey);
    font-weight: bold;
    font-size: 14pt;
    line-height: 1.5em;
    color: var(--lightgrey-light);
    height: 1.5em;
    width: 1.5em;
    margin: 0.75em;
    text-align: center;
    background: var(--teal);
}

.area-container {
    margin: 1em 2em;
}

#page2a .area-container {
    margin: 1em 0;
}

#runInstaller,
#runCleanup,
#gotoSite,
#gotoAdministrator,
#gotoPostRestorationRroubleshooting {
    margin: 0 2em 1.3em;
}

h2 {
    font-size: 18pt;
    font-weight: normal;
    line-height: 1.3;
    border: solid var(--lightgrey-dark);
    border-left: none;
    border-right: none;
    padding: 0.5em 0;
    background: var(--lightgrey);
}

#preextraction h2 {
    margin-top: 0;
    border-top: 0;
    text-align: center;
}

input,
select,
textarea {
    font-size: 100%;
    margin: 0;
    vertical-align: baseline;
    *vertical-align: middle;
}

button,
input {
    line-height: normal;
    font-weight: normal;
    *overflow: visible;
}

input,
select,
textarea {
    background: var(--white);
    color: var(--grey-dark);
    font-size: 12pt;
    border: 1px solid var(--lightgrey-dark);
    border-radius: .25em;
    box-sizing: border-box;
    width: 50%;
    padding: 0 0 0 .5em;
}

.monospaced {
	font-family: var(--monospace);
}

input[type="checkbox"] {
    width: auto;
}

.field {
    height: 1.5em;
}

label {
    display: inline-block;
    width: 30%;
    font-size: 95%;
    font-weight: normal;
    cursor: pointer;
    color: #333;
    margin: .5em 0;
}

.help {
    width: 60%;
    margin-left: 30%;
    margin-bottom: 1.5em;
    font-size: small;
    color: var(--grey);
}

input:focus, input:hover {
    background-color: var(--lightgrey);
}

.button {
    display: inline-block;
    margin: 1em .25em;
    padding: 1em 2em;
    background: var(--green-dark);
    color: var(--white);
    border: 1px solid var(--green);
    cursor: pointer;
    border-radius: .25em;
    transition: 0.3s linear all;
}

#checkFTPTempDir.button,
#resetFTPTempDir.button,
#testFTP.button,
#browseFTP,
#reloadArchives,
#notWorking.button {
    padding: .5em 1em;
}

.button:hover, .button:active {
    border: 1px solid var(--green-light);
    background: var(--green);
    color: var(--white);
}

#notWorking.button, .bluebutton {
    text-decoration: none;
    background: var(--teal);
    border-color: var(--teal-dark);
    color: var(--white);
}

#notWorking.button:hover, .bluebutton:hover {
    background: var(--teal-light);
    border-color: var(--teal);
    color: var(--white);
}

#notWorking.button:active, .bluebutton:active {
    background: var(--teal-light);
    border-color: var(--teal);
}

.loprofile {
    padding: 0.5em 1em;
    font-size: 80%;
}

.black_overlay {
    display: none;
    position: absolute;
    top: 0%;
    left: 0%;
    width: 100%;
    height: 100%;
    background-color: var(--black);
    z-index: 1001;
    -moz-opacity: 0.8;
    opacity: .80;
    filter: alpha(opacity=80);
}

.white_content {
    display: none;
    position: absolute;
    padding: 0 0 1em;
    background: var(--lightgrey-light);
    border: 1px solid rgba(0, 0, 0, .3);
    z-index: 1002;
    overflow: hidden;
}

.white_content a {
    margin-left: 4em;
}

ol {
    margin: 0 2em;
    padding: 0 2em 1em;
}

li {
    margin: 0 0 .5em;
}

#genericerror {
    background-color: var(--orange-light);
    border: 4px solid var(--orange) !important;
}

#genericerrorInner {
    font-size: 110%;
    color: var(--grey-dark);
}

#warn-not-close, .warn-not-close {
    padding: 0.2em 0.5em;
    text-align: center;
    background: var(--orange);
    font-size: smaller;
    font-weight: bold;
}

#progressbar, .progressbar {
    display: block;
    width: 80%;
    height: 32px;
    border: 1px solid var(--lightgrey-dark);
    margin: 1em 10% 0.2em;
    border-radius: .25em;
}

#progressbar-inner, .progressbar-inner {
    display: block;
    width: 100%;
    height: 100%;
    background: var(--teal-dark);
}

#currentFile {
    font-family: var(--monospace);
    font-size: 9pt;
    height: 10pt;
    overflow: hidden;
    text-overflow: ellipsis;
    background: var(--lightgrey-dark);
    margin: 0 10% 1em;
    padding: .125em;
}

#extractionComplete {
}

#warningsContainer {
    border-bottom: 2px solid var(--orange-dark);
    border-left: 2px solid var(--orange-dark);
    border-right: 2px solid var(--orange-dark);
    padding: 5px 0;
    background: var(--orange-light);
    border-bottom-right-radius: 5px;
    border-bottom-left-radius: 5px;
}

#warningsHeader h2 {
    color: var(--grey-dark);
    border-top: 2px solid var(--orange-dark);
    border-left: 2px solid var(--orange-dark);
    border-right: 2px solid var(--orange-dark);
    border-bottom: thin solid var(--orange-dark);
    border-top-right-radius: 5px;
    border-top-left-radius: 5px;
    background: var(--orange);
    font-size: large;
    padding: 2px 5px;
    margin: 0px;
}

#warnings {
    height: 200px;
    overflow-y: scroll;
}

#warnings div {
    background: var(--orange-light);
    font-size: small;
    padding: 2px 4px;
    border-bottom: thin solid var(--grey-dark);
}

.helpme,
#warn-not-close {
    background: var(--orange-light);
    padding: 0.75em 0.5em;
    border: solid var(--orange);
    border-width: 1px 0;
    text-align: center;
}

/* FTP / S3 Browser */
.breadcrumb {
    background-color: var(--lightgrey);
    border-radius: 4px;
    list-style: none outside none;
    margin: 0 0 18px;
    padding: 8px 15px;
}

.breadcrumb > li {
    display: inline-block;
    text-shadow: 0 1px 0 var(--lightgrey-light);
}

#ak_crumbs span {
    padding: 1px 3px;
}

#ak_crumbs a {
    cursor: pointer;
}

#ftpBrowserFolderList a {
    cursor: pointer
}

/* Bootstrap porting */
.table {
    margin-bottom: 18px;
    width: 100%;
}

.table th, .table td {
    border-top: 1px solid var(--lightgrey-dark);
    line-height: 18px;
    padding: 8px;
    text-align: left;
    vertical-align: top;
}

.table-striped tbody > tr:nth-child(2n+1) > td, .table-striped tbody > tr:nth-child(2n+1) > th {
    background-color: var(--lightgrey-light);
}

@media (prefers-color-scheme: dark) {
	html {
		background: var(--grey-superdark);
		color: var(--white);
	}

	body {
		background: var(--grey-superdark);
		color: var(--white);
	}

	#logo {
		background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABkAAAAZCAYAAADE6YVjAAAACXBIWXMAAAHFAAABxQG6eNsrAAABFElEQVRIx+VVQZHDMAxcFYGPQRg0ZRAIKYODUAiFcAxyDAKhEFwGCYOEwfYjT1VPWjtt/Ko+mvFYXkm7koGvNJK/JCeSdSmAmnebSLoSIJ6P1pVo05JVJavYthqSleGhjiqbtgI56YP9k+qSSttl4OzV10FR6gMfzRYglfGe5BmABxAk7N5tUaNcOJIDX1tvYhZbJ09ABgCjZp8j06v6UUSOSRDNxr8rFBGRHE7izOfEu/NCkkkQe+kI4PAC6CoiPwD+zJnLIb0NjJozR7IjeVEfZqQxpDN3bmAUFQ9fH1ZJvIk1xscxKaCzXX5RptPSn6Krpv1ktXQGZFi7fXcr7s4A/gHMIjKW+LDaVf3+8Pt1+Gq7AZ5SjMx2RnT3AAAAAElFTkSuQmCC);
	}

	#page-container {
		background: var(--grey-dark);
		border: medium solid var(--grey);
	}

	#header {
	    color: var(--lightgrey-dark);
	    background: var(--grey-light);
	    border-bottom: 2px solid var(--grey);
	}

	#footer {
	    color: var(--lightgrey-light);
	    border-top: 1px solid var(--grey-dark);
	    background: var(--grey);
	}

	#footer a {
	    color: var(--teal);
	}

	h2 {
		border: solid var(--grey-light);
		background: var(--grey);
	}

	input,
	select,
	textarea {
	    background: var(--grey-dark);
	    color: var(--lightgrey);
	    border: 1px solid var(--grey);
	}

	label {
		color: var(--lightgrey)
	}

	.help {
	    color: var(--lightgrey-dark);
	}

	input:focus, input:hover {
	    background-color: var(--grey-light);
	}

	.white_content {
		background-color: var(--grey-dark);
	}

	#warn-not-close, .warn-not-close {
	    color: var(--white);
	}

	#progressbar, .progressbar {
	    border: 1px solid var(--grey-light);
	}

	#currentFile {
		background-color: var(--grey);
	}

	#warningsContainer {
		color: var(--grey-dark);
	}

	.helpme, #warn-not-close {
		color: var(--grey-dark);
	}

	.helpme a, #warn-not-close a {
		color: var(--teal-dark);
	}
}

CSS;

	callExtraFeature('onExtraHeadCSS');
}

/**
 * Akeeba Kickstart
 * An AJAX-powered archive extraction tool
 *
 * @package   kickstart
 * @copyright Copyright (c)2008-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

function echoHeadJavascript()
{
	?>
    <script type="text/javascript" language="javascript">
		var akeeba = {};

		var akeeba_debug     = <?php echo defined('KSDEBUG') ? 'true' : 'false' ?>;
		var akeeba_pro       = <?php echo KICKSTARTPRO ? 1 : 0 ?>;
		var sftp_path        = '<?php echo TranslateWinPath(defined('KSROOTDIR') ? KSROOTDIR : dirname(__FILE__)); ?>/';
		var akeeba_ajax_url  = '<?php echo defined('KSSELFNAME') ? KSSELFNAME : basename(__FILE__); ?>';
		var default_temp_dir = '<?php echo addcslashes(AKKickstartUtils::getPath(), '\\\'"') ?>';
		var translation      = {
			<?php echoTranslationStrings(); ?>
		};
		var isJoomla         = true;

/**
 * Akeeba Kickstart
 * An AJAX-powered archive extraction tool
 *
 * @package   kickstart
 * @copyright Copyright (c)2008-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */


/*
 *	https://raw.githubusercontent.com/douglascrockford/JSON-js/master/json2.js
 *  2016-05-01
 *  Public Domain.
 *  NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.
 *  See http://www.JSON.org/js.html
 */
// Create a JSON object only if one does not already exist. We create the
// methods in a closure to avoid creating global variables.

if (typeof JSON !== "object")
{
	JSON = {};
}

(function ()
{
	"use strict";

	var rx_one       = /^[\],:{}\s]*$/;
	var rx_two       = /\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g;
	var rx_three     = /"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g;
	var rx_four      = /(?:^|:|,)(?:\s*\[)+/g;
	var rx_escapable = /[\\\"\u0000-\u001f\u007f-\u009f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g;
	var rx_dangerous = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g;

	function f(n)
	{
		// Format integers to have at least two digits.
		return n < 10
			? "0" + n
			: n;
	}

	function this_value()
	{
		return this.valueOf();
	}

	if (typeof Date.prototype.toJSON !== "function")
	{

		Date.prototype.toJSON = function ()
		{

			return isFinite(this.valueOf())
				? this.getUTCFullYear() + "-" +
				f(this.getUTCMonth() + 1) + "-" +
				f(this.getUTCDate()) + "T" +
				f(this.getUTCHours()) + ":" +
				f(this.getUTCMinutes()) + ":" +
				f(this.getUTCSeconds()) + "Z"
				: null;
		};

		Boolean.prototype.toJSON = this_value;
		Number.prototype.toJSON  = this_value;
		String.prototype.toJSON  = this_value;
	}

	var gap;
	var indent;
	var meta;
	var rep;


	function quote(string)
	{

// If the string contains no control characters, no quote characters, and no
// backslash characters, then we can safely slap some quotes around it.
// Otherwise we must also replace the offending characters with safe escape
// sequences.

		rx_escapable.lastIndex = 0;
		return rx_escapable.test(string)
			? "\"" + string.replace(rx_escapable, function (a)
		{
			var c = meta[a];
			return typeof c === "string"
				? c
				: "\\u" + ("0000" + a.charCodeAt(0).toString(16)).slice(-4);
		}) + "\""
			: "\"" + string + "\"";
	}


	function str(key, holder)
	{

// Produce a string from holder[key].

		var i;          // The loop counter.
		var k;          // The member key.
		var v;          // The member value.
		var length;
		var mind  = gap;
		var partial;
		var value = holder[key];

// If the value has a toJSON method, call it to obtain a replacement value.

		if (value && typeof value === "object" &&
			typeof value.toJSON === "function")
		{
			value = value.toJSON(key);
		}

// If we were called with a replacer function, then call the replacer to
// obtain a replacement value.

		if (typeof rep === "function")
		{
			value = rep.call(holder, key, value);
		}

// What happens next depends on the value's type.

		switch (typeof value)
		{
			case "string":
				return quote(value);

			case "number":

// JSON numbers must be finite. Encode non-finite numbers as null.

				return isFinite(value)
					? String(value)
					: "null";

			case "boolean":
			case "null":

// If the value is a boolean or null, convert it to a string. Note:
// typeof null does not produce "null". The case is included here in
// the remote chance that this gets fixed someday.

				return String(value);

// If the type is "object", we might be dealing with an object or an array or
// null.

			case "object":

// Due to a specification blunder in ECMAScript, typeof null is "object",
// so watch out for that case.

				if (!value)
				{
					return "null";
				}

// Make an array to hold the partial results of stringifying this object value.

				gap += indent;
				partial = [];

// Is the value an array?

				if (Object.prototype.toString.apply(value) === "[object Array]")
				{

// The value is an array. Stringify every element. Use null as a placeholder
// for non-JSON values.

					length = value.length;
					for (i = 0; i < length; i += 1)
					{
						partial[i] = str(i, value) || "null";
					}

// Join all of the elements together, separated with commas, and wrap them in
// brackets.

					v   = partial.length === 0
						? "[]"
						: gap
							? "[\n" + gap + partial.join(",\n" + gap) + "\n" + mind + "]"
							: "[" + partial.join(",") + "]";
					gap = mind;
					return v;
				}

// If the replacer is an array, use it to select the members to be stringified.

				if (rep && typeof rep === "object")
				{
					length = rep.length;
					for (i = 0; i < length; i += 1)
					{
						if (typeof rep[i] === "string")
						{
							k = rep[i];
							v = str(k, value);
							if (v)
							{
								partial.push(quote(k) + (
									gap
										? ": "
										: ":"
								) + v);
							}
						}
					}
				}
				else
				{

// Otherwise, iterate through all of the keys in the object.

					for (k in value)
					{
						if (Object.prototype.hasOwnProperty.call(value, k))
						{
							v = str(k, value);
							if (v)
							{
								partial.push(quote(k) + (
									gap
										? ": "
										: ":"
								) + v);
							}
						}
					}
				}

// Join all of the member texts together, separated with commas,
// and wrap them in braces.

				v   = partial.length === 0
					? "{}"
					: gap
						? "{\n" + gap + partial.join(",\n" + gap) + "\n" + mind + "}"
						: "{" + partial.join(",") + "}";
				gap = mind;
				return v;
		}
	}

// If the JSON object does not yet have a stringify method, give it one.

	if (typeof JSON.stringify !== "function")
	{
		meta           = {    // table of character substitutions
			"\b": "\\b",
			"\t": "\\t",
			"\n": "\\n",
			"\f": "\\f",
			"\r": "\\r",
			"\"": "\\\"",
			"\\": "\\\\"
		};
		JSON.stringify = function (value, replacer, space)
		{

// The stringify method takes a value and an optional replacer, and an optional
// space parameter, and returns a JSON text. The replacer can be a function
// that can replace values, or an array of strings that will select the keys.
// A default replacer method can be provided. Use of the space parameter can
// produce text that is more easily readable.

			var i;
			gap    = "";
			indent = "";

// If the space parameter is a number, make an indent string containing that
// many spaces.

			if (typeof space === "number")
			{
				for (i = 0; i < space; i += 1)
				{
					indent += " ";
				}

// If the space parameter is a string, it will be used as the indent string.

			}
			else if (typeof space === "string")
			{
				indent = space;
			}

// If there is a replacer, it must be a function or an array.
// Otherwise, throw an error.

			rep = replacer;
			if (replacer && typeof replacer !== "function" &&
				(typeof replacer !== "object" ||
					typeof replacer.length !== "number"))
			{
				throw new Error("JSON.stringify");
			}

// Make a fake root object containing our value under the key of "".
// Return the result of stringifying the value.

			return str("", {"": value});
		};
	}


// If the JSON object does not yet have a parse method, give it one.

	if (typeof JSON.parse !== "function")
	{
		JSON.parse = function (text, reviver)
		{

// The parse method takes a text and an optional reviver function, and returns
// a JavaScript value if the text is a valid JSON text.

			var j;

			function walk(holder, key)
			{

// The walk method is used to recursively walk the resulting structure so
// that modifications can be made.

				var k;
				var v;
				var value = holder[key];
				if (value && typeof value === "object")
				{
					for (k in value)
					{
						if (Object.prototype.hasOwnProperty.call(value, k))
						{
							v = walk(value, k);
							if (v !== undefined)
							{
								value[k] = v;
							}
							else
							{
								delete value[k];
							}
						}
					}
				}
				return reviver.call(holder, key, value);
			}


// Parsing happens in four stages. In the first stage, we replace certain
// Unicode characters with escape sequences. JavaScript handles many characters
// incorrectly, either silently deleting them, or treating them as line endings.

			text                   = String(text);
			rx_dangerous.lastIndex = 0;
			if (rx_dangerous.test(text))
			{
				text = text.replace(rx_dangerous, function (a)
				{
					return "\\u" +
						("0000" + a.charCodeAt(0).toString(16)).slice(-4);
				});
			}

// In the second stage, we run the text against regular expressions that look
// for non-JSON patterns. We are especially concerned with "()" and "new"
// because they can cause invocation, and "=" because it can cause mutation.
// But just to be safe, we want to reject all unexpected forms.

// We split the second stage into 4 regexp operations in order to work around
// crippling inefficiencies in IE's and Safari's regexp engines. First we
// replace the JSON backslash pairs with "@" (a non-JSON character). Second, we
// replace all simple value tokens with "]" characters. Third, we delete all
// open brackets that follow a colon or comma or that begin the text. Finally,
// we look to see that the remaining characters are only whitespace or "]" or
// "," or ":" or "{" or "}". If that is so, then the text is safe for eval.

			if (
				rx_one.test(
					text
						.replace(rx_two, "@")
						.replace(rx_three, "]")
						.replace(rx_four, "")
				)
			)
			{

// In the third stage we use the eval function to compile the text into a
// JavaScript structure. The "{" operator is subject to a syntactic ambiguity
// in JavaScript: it can begin a block or an object literal. We wrap the text
// in parens to eliminate the ambiguity.

				j = eval("(" + text + ")");

// In the optional fourth stage, we recursively walk the new structure, passing
// each name/value pair to a reviver function for possible transformation.

				return (typeof reviver === "function")
					? walk({"": j}, "")
					: j;
			}

// If the text is not JSON parseable, then a SyntaxError is thrown.

			throw new SyntaxError("JSON.parse");
		};
	}
}());


/**
 * Akeeba Kickstart
 * An AJAX-powered archive extraction tool
 *
 * @package   kickstart
 * @copyright Copyright (c)2008-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

/**
 * Returns the version of Internet Explorer or a -1
 * (indicating the use of another browser).
 *
 * @return   integer  MSIE version or -1
 */
function getInternetExplorerVersion()
{
	var rv = -1; // Return value assumes failure.
	if (navigator.appName == "Microsoft Internet Explorer")
	{
		var ua = navigator.userAgent;
		var re = new RegExp("MSIE ([0-9]{1,}[\.0-9]{0,})");
		if (re.exec(ua) != null)
		{
			rv = parseFloat(RegExp.$1);
		}
	}
	return rv;
}

function resolvePath(filename)
{
	filename  = filename.replace("\/\/g", "\/");
	var parts = filename.split("/");
	var out   = [];

	for (var i = 0; i < parts.length; i++)
	{
		var part = parts[i];

		if (part === ".")
		{
			continue;
		}

		if (part === "..")
		{
			out.pop();

			continue;
		}

		out.push(part);
	}

	return out.join("/");
}

/*
 * Courtesy of PHPjs -- http://phpjs.org
 * @license GPL, version 2
 */

function version_compare(v1, v2, operator)
{
	// BEGIN REDUNDANT
	this.php_js     = this.php_js || {};
	this.php_js.ENV = this.php_js.ENV || {};
	// END REDUNDANT
	// Important: compare must be initialized at 0.
	var i           = 0,
		x           = 0,
		compare     = 0,
		// vm maps textual PHP versions to negatives so they're less than 0.
		// PHP currently defines these as CASE-SENSITIVE. It is important to
		// leave these as negatives so that they can come before numerical versions
		// and as if no letters were there to begin with.
		// (1alpha is < 1 and < 1.1 but > 1dev1)
		// If a non-numerical value can't be mapped to this table, it receives
		// -7 as its value.
		vm          = {
			'dev':   -6,
			'alpha': -5,
			'a':     -5,
			'beta':  -4,
			'b':     -4,
			'RC':    -3,
			'rc':    -3,
			'#':     -2,
			'p':     -1,
			'pl':    -1
		},
		// This function will be called to prepare each version argument.
		// It replaces every _, -, and + with a dot.
		// It surrounds any nonsequence of numbers/dots with dots.
		// It replaces sequences of dots with a single dot.
		//    version_compare('4..0', '4.0') == 0
		// Important: A string of 0 length needs to be converted into a value
		// even less than an unexisting value in vm (-7), hence [-8].
		// It's also important to not strip spaces because of this.
		//   version_compare('', ' ') == 1
		prepVersion = function (v)
		{
			v = ('' + v).replace(/[_\-+]/g, '.');
			v = v.replace(/([^.\d]+)/g, '.$1.').replace(/\.{2,}/g, '.');
			return (!v.length ? [-8] : v.split('.'));
		},
		// This converts a version component to a number.
		// Empty component becomes 0.
		// Non-numerical component becomes a negative number.
		// Numerical component becomes itself as an integer.
		numVersion  = function (v)
		{
			return !v ? 0 : (isNaN(v) ? vm[v] || -7 : parseInt(v, 10));
		};
	v1              = prepVersion(v1);
	v2              = prepVersion(v2);
	x               = Math.max(v1.length, v2.length);
	for (i = 0; i < x; i++)
	{
		if (v1[i] == v2[i])
		{
			continue;
		}
		v1[i] = numVersion(v1[i]);
		v2[i] = numVersion(v2[i]);
		if (v1[i] < v2[i])
		{
			compare = -1;
			break;
		}
		else if (v1[i] > v2[i])
		{
			compare = 1;
			break;
		}
	}
	if (!operator)
	{
		return compare;
	}

	// Important: operator is CASE-SENSITIVE.
	// "No operator" seems to be treated as less than
	// Any other values seem to make the function return null.
	switch (operator)
	{
		case '>':
		case 'gt':
			return (compare > 0);
		case '>=':
		case 'ge':
			return (compare >= 0);
		case '<=':
		case 'le':
			return (compare <= 0);
		case '==':
		case '=':
		case 'eq':
			return (compare === 0);
		case '<>':
		case '!=':
		case 'ne':
			return (compare !== 0);
		case '':
		case '<':
		case 'lt':
			return (compare < 0);
		default:
			return null;
	}
}

function is_array(mixed_var)
{
	var key         = "";
	var getFuncName = function (fn)
	{
		var name = (/\W*function\s+([\w\$]+)\s*\(/).exec(fn);
		if (!name)
		{
			return "(Anonymous)";
		}
		return name[1];
	};

	if (!mixed_var)
	{
		return false;
	}

	// BEGIN REDUNDANT
	this.php_js     = this.php_js || {};
	this.php_js.ini = this.php_js.ini || {};
	// END REDUNDANT

	if (typeof mixed_var === "object")
	{

		if (this.php_js.ini["phpjs.objectsAsArrays"] &&  // Strict checking for being a JavaScript array (only check this way if
											 // call ini_set('phpjs.objectsAsArrays', 0) to disallow objects as arrays)
			(
				(this.php_js.ini["phpjs.objectsAsArrays"].local_value.toLowerCase &&
					this.php_js.ini["phpjs.objectsAsArrays"].local_value.toLowerCase() === "off") ||
				parseInt(this.php_js.ini["phpjs.objectsAsArrays"].local_value, 10) === 0)
		)
		{
			return mixed_var.hasOwnProperty("length") && // Not non-enumerable because of being on parent class
				!mixed_var.propertyIsEnumerable("length") && // Since is own property, if not enumerable, it must be a
															 // built-in function
				getFuncName(mixed_var.constructor) !== "String"; // exclude String()
		}

		if (mixed_var.hasOwnProperty)
		{
			for (key in mixed_var)
			{
				// Checks whether the object has the specified property
				// if not, we figure it's not an object in the sense of a php-associative-array.
				if (false === mixed_var.hasOwnProperty(key))
				{
					return false;
				}
			}
		}

		// Read discussion at: http://kevin.vanzonneveld.net/techblog/article/javascript_equivalent_for_phps_is_array/
		return true;
	}

	return false;
}

function array_key_exists(key, search)
{
	if (!search || (search.constructor !== Array && search.constructor !== Object))
	{
		return false;
	}
	return key in search;
}

function basename(path, suffix)
{
	var b = path.replace(/^.*[\/\\]/g, "");
	if (typeof(suffix) == "string" && b.substr(b.length - suffix.length) == suffix)
	{
		b = b.substr(0, b.length - suffix.length);
	}
	return b;
}

function number_format(number, decimals, dec_point, thousands_sep)
{
	var n = number, c = isNaN(decimals = Math.abs(decimals)) ? 2 : decimals;
	var d = dec_point == undefined ? "," : dec_point;
	var t = thousands_sep == undefined ? "." : thousands_sep, s = n < 0 ? "-" : "";
	var i = parseInt(n = Math.abs(+n || 0).toFixed(c)) + "", j = (j = i.length) > 3 ? j % 3 : 0;

	return s + (j ? i.substr(0, j) + t : "") + i.substr(j)
		.replace(/(\d{3})(?=\d)/g, "$1" + t) + (c ? d + Math.abs(n - i).toFixed(c).slice(2) : "");
}

function size_format(filesize)
{
	if (filesize >= 1073741824)
	{
		filesize = number_format(filesize / 1073741824, 2, ".", "") + " GB";
	}
	else
	{
		if (filesize >= 1048576)
		{
			filesize = number_format(filesize / 1048576, 2, ".", "") + " MB";
		}
		else
		{
			filesize = number_format(filesize / 1024, 2, ".", "") + " KB";
		}
	}
	return filesize;
}

/**
 * Checks if a variable is empty. From the php.js library.
 */
function empty(mixed_var)
{
	var key;

	if (mixed_var === "" ||
		mixed_var === 0 ||
		mixed_var === "0" ||
		mixed_var === null ||
		mixed_var === false ||
		typeof mixed_var === "undefined"
	)
	{
		return true;
	}

	if (typeof mixed_var == "object")
	{
		for (key in mixed_var)
		{
			return false;
		}
		return true;
	}

	return false;
}

function ltrim(str, charlist)
{
	// Strips whitespace from the beginning of a string
	//
	// version: 1008.1718
	// discuss at: http://phpjs.org/functions/ltrim    // +   original by: Kevin van Zonneveld
	// (http://kevin.vanzonneveld.net) +      input by: Erkekjetter +   improved by: Kevin van Zonneveld
	// (http://kevin.vanzonneveld.net) +   bugfixed by: Onno Marsman *     example 1: ltrim('    Kevin van Zonneveld
	// ');    // *     returns 1: 'Kevin van Zonneveld    '
	charlist = !charlist ? " \\s\u00A0" : (charlist + "").replace(/([\[\]\(\)\.\?\/\*\{\}\+\$\^\:])/g, "$1");
	var re   = new RegExp("^[" + charlist + "]+", "g");
	return (str + "").replace(re, "");
}

function array_shift(inputArr)
{
	// http://kevin.vanzonneveld.net
	// +   original by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
	// +   improved by: Martijn Wieringa
	// %        note 1: Currently does not handle objects
	// *     example 1: array_shift(['Kevin', 'van', 'Zonneveld']);
	// *     returns 1: 'Kevin'

	var props                                                  = false,
		shift = undefined, pr = "", allDigits = /^\d$/, int_ct = -1,
		_checkToUpIndices                                      = function (arr, ct, key)
		{
			// Deal with situation, e.g., if encounter index 4 and try to set it to 0, but 0 exists later in loop (need
			// to increment all subsequent (skipping current key, since we need its value below) until find unused)
			if (arr[ct] !== undefined)
			{
				var tmp = ct;
				ct += 1;
				if (ct === key)
				{
					ct += 1;
				}
				ct      = _checkToUpIndices(arr, ct, key);
				arr[ct] = arr[tmp];
				delete arr[tmp];
			}
			return ct;
		};


	if (inputArr.length === 0)
	{
		return null;
	}
	if (inputArr.length > 0)
	{
		return inputArr.shift();
	}
}

function trim(str, charlist)
{
	var whitespace, l = 0, i = 0;
	str += "";

	if (!charlist)
	{
		// default list
		whitespace =
			" \n\r\t\f\x0b\xa0\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u200b\u2028\u2029\u3000";
	}
	else
	{
		// preg_quote custom list
		charlist += "";
		whitespace = charlist.replace(/([\[\]\(\)\.\?\/\*\{\}\+\$\^\:])/g, "$1");
	}

	l = str.length;
	for (i = 0; i < l; i++)
	{
		if (whitespace.indexOf(str.charAt(i)) === -1)
		{
			str = str.substring(i);
			break;
		}
	}

	l = str.length;
	for (i = l - 1; i >= 0; i--)
	{
		if (whitespace.indexOf(str.charAt(i)) === -1)
		{
			str = str.substring(0, i + 1);
			break;
		}
	}

	return whitespace.indexOf(str.charAt(0)) === -1 ? str : "";
}

function array_merge()
{
	// Merges elements from passed arrays into one array
	//
	// version: 1103.1210
	// discuss at: http://phpjs.org/functions/array_merge
	// +   original by: Brett Zamir (http://brett-zamir.me)
	// +   bugfixed by: Nate
	// +   input by: josh
	// +   bugfixed by: Brett Zamir (http://brett-zamir.me)
	// *     example 1: arr1 = {"color": "red", 0: 2, 1: 4}
	// *     example 1: arr2 = {0: "a", 1: "b", "color": "green", "shape": "trapezoid", 2: 4}
	// *     example 1: array_merge(arr1, arr2)
	// *     returns 1: {"color": "green", 0: 2, 1: 4, 2: "a", 3: "b", "shape": "trapezoid", 4: 4}
	// *     example 2: arr1 = []
	// *     example 2: arr2 = {1: "data"}
	// *     example 2: array_merge(arr1, arr2)
	// *     returns 2: {0: "data"}
	var args   = Array.prototype.slice.call(arguments),
		retObj = {},
		k, j   = 0,
		i      = 0,
		retArr = true;

	for (i = 0; i < args.length; i++)
	{
		if (!(args[i] instanceof Array))
		{
			retArr = false;
			break;
		}
	}

	if (retArr)
	{
		retArr = [];
		for (i = 0; i < args.length; i++)
		{
			retArr = retArr.concat(args[i]);
		}
		return retArr;
	}
	var ct = 0;

	for (i = 0, ct = 0; i < args.length; i++)
	{
		if (args[i] instanceof Array)
		{
			for (j = 0; j < args[i].length; j++)
			{
				retObj[ct++] = args[i][j];
			}
		}
		else
		{
			for (k in args[i])
			{
				if (args[i].hasOwnProperty(k))
				{
					if (parseInt(k, 10) + "" === k)
					{
						retObj[ct++] = args[i][k];
					}
					else
					{
						retObj[k] = args[i][k];
					}
				}
			}
		}
	}
	return retObj;
}

function array_diff(arr1)
{ // eslint-disable-line camelcase
	//  discuss at: http://locutus.io/php/array_diff/
	// original by: Kevin van Zonneveld (http://kvz.io)
	// improved by: Sanjoy Roy
	//  revised by: Brett Zamir (http://brett-zamir.me)
	//   example 1: array_diff(['Kevin', 'van', 'Zonneveld'], ['van', 'Zonneveld'])
	//   returns 1: {0:'Kevin'}

	var retArr = {};
	var argl   = arguments.length;
	var k1     = "";
	var i      = 1;
	var k      = "";
	var arr    = {};

	arr1keys: for (k1 in arr1)
	{
		for (i = 1; i < argl; i++)
		{
			arr = arguments[i];
			for (k in arr)
			{
				if (arr[k] === arr1[k1])
				{
					// If it reaches here, it was found in at least one array, so try next value
					continue arr1keys;
				}
			}
			retArr[k1] = arr1[k1];
		}
	}

	return retArr;
}

//=============================================================================
// Object.keys polyfill
//=============================================================================

// From https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/keys
if (!Object.keys)
{
	Object.keys = (function () {
		"use strict";
		var hasOwnProperty  = Object.prototype.hasOwnProperty,
			hasDontEnumBug  = !({toString: null}).propertyIsEnumerable("toString"),
			dontEnums       = [
				"toString",
				"toLocaleString",
				"valueOf",
				"hasOwnProperty",
				"isPrototypeOf",
				"propertyIsEnumerable",
				"constructor"
			],
			dontEnumsLength = dontEnums.length;

		return function (obj) {
			if (typeof obj !== "object" && (typeof obj !== "function" || obj === null))
			{
				throw new TypeError("Object.keys called on non-object");
			}

			var result = [], prop, i;

			for (prop in obj)
			{
				if (hasOwnProperty.call(obj, prop))
				{
					result.push(prop);
				}
			}

			if (hasDontEnumBug)
			{
				for (i = 0; i < dontEnumsLength; i++)
				{
					if (hasOwnProperty.call(obj, dontEnums[i]))
					{
						result.push(dontEnums[i]);
					}
				}
			}
			return result;
		};
	}());
}

/**
 * Akeeba Kickstart
 * An AJAX-powered archive extraction tool
 *
 * @package   kickstart
 * @copyright Copyright (c)2008-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

akeeba.System = {};

akeeba.System.documentReady = function (callback, context)
{
};

akeeba.System.notification = {
	hasDesktopNotification: false,
	iconURL:                ''
};
akeeba.System.params       = {
	AjaxURL:               '',
	errorCallback:         onGenericError,
	password:              '',
	errorDialogId:         'errorDialog',
	errorDialogMessageId:  'errorDialogPre'
};

/**
 * An extremely simple error handler, dumping error messages to screen
 *
 * @param  error  The error message string
 */
akeeba.System.defaultErrorHandler = function (error)
{
	alert("An error has occurred\n" + error);
};

akeeba.System.params.errorCallback = onGenericError;

/**
 * Performs an AJAX request and returns the parsed JSON output.
 * akeeba.System.params.AjaxURL is used as the AJAX proxy URL.
 * If there is no errorCallback, the global akeeba.System.params.errorCallback is used.
 *
 * @param  data             An object with the query data, e.g. a serialized form
 * @param  successCallback  A function accepting a single object parameter, called on success
 * @param  errorCallback    A function accepting a single string parameter, called on failure
 * @param  useCaching       Should we use the cache?
 * @param  timeout          Timeout before cancelling the request (default 60s)
 */
akeeba.System.doAjax = function (data, successCallback, errorCallback, useCaching, timeout)
{
	if (useCaching == null)
	{
		useCaching = true;
	}

	// We always want to burst the cache
	var now                = new Date().getTime() / 1000;
	var s                  = parseInt(now, 10);
	data._cacheBustingJunk = Math.round((now - s) * 1000) / 1000;

	if (timeout == null)
	{
		timeout = 600000;
	}

	var structure =
			{
				type:    "POST",
				url:     akeeba.System.params.AjaxURL,
				cache:   false,
				data:    data,
				timeout: timeout,
				success: function (msg)
				{
					// Initialize
					var message = "";

					// Get rid of junk before the data
					var valid_pos = msg.indexOf('###');

					if (valid_pos === -1)
					{
						// Valid data not found in the response
						msg = akeeba.System.sanitizeErrorMessage(msg);
						msg = 'Invalid AJAX data: ' + msg;

						if (errorCallback == null)
						{
							if (akeeba.System.params.errorCallback != null)
							{
								akeeba.System.params.errorCallback(msg);
							}
						}
						else
						{
							errorCallback(msg);
						}

						return;
					}
					else if (valid_pos !== 0)
					{
						// Data is prefixed with junk
						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
					valid_pos = message.lastIndexOf('###');
					message   = message.substr(0, valid_pos); // Remove triple hash in the end

					try
					{
						var data = JSON.parse(message);
					}
					catch (err)
					{
						message = akeeba.System.sanitizeErrorMessage(message);
						msg     = err.message + "\n<br/>\n<pre>\n" + message + "\n</pre>";

						if (errorCallback == null)
						{
							if (akeeba.System.params.errorCallback != null)
							{
								akeeba.System.params.errorCallback(msg);
							}
						}
						else
						{
							errorCallback(msg);
						}

						return;
					}

					// Call the callback function
					successCallback(data);
				},
				error:   function (Request, textStatus, errorThrown)
				{
					var text    = Request.responseText ? Request.responseText : '';
					var message = '<strong>AJAX Loading Error</strong><br/>HTTP Status: ' + Request.status +
						' (' + Request.statusText + ')<br/>';

					message = message + 'Internal status: ' + textStatus + '<br/>';
					message = message + 'XHR ReadyState: ' + Request.readyState + '<br/>';
					message = message + 'Raw server response:<br/>' + akeeba.System.sanitizeErrorMessage(text);

					if (errorCallback == null)
					{
						if (akeeba.System.params.errorCallback != null)
						{
							akeeba.System.params.errorCallback(message);
						}
					}
					else
					{
						errorCallback(message);
					}
				}
			};

	if (useCaching)
	{
		akeeba.Ajax.enqueue(structure);
	}
	else
	{
		akeeba.Ajax.ajax(structure);
	}
};

/**
 * Sanitize a message before displaying it in an error dialog. Some servers return an HTML page with DOM modifying
 * JavaScript when they block the backup script for any reason (usually with a 5xx HTTP error code). Displaying the
 * raw response in the error dialog has the side-effect of killing our backup resumption JavaScript or even completely
 * destroy the page, making backup restart impossible.
 *
 * @param {string} msg The message to sanitize
 *
 * @returns {string}
 */
akeeba.System.sanitizeErrorMessage = function (msg)
{
	if (msg.indexOf("<script") > -1)
	{
		try
		{
			msg = (new DOMParser().parseFromString(msg ?? "", "text/html")).textContent;
		}
		catch (e)
		{
			msg = "(HTML containing script tags)";
		}
	}

	return msg;
};

/**
 * Get and set data to elements. Use:
 * akeeba.System.data.set(element, property, value)
 * akeeba.System.data.get(element, property, defaultValue)
 *
 * On modern browsers (minimum IE 11, Chrome 8, FF 6, Opera 11, Safari 6) this will use the data-* attributes of the
 * elements where possible. On old browsers it will use an internal cache and manually apply data-* attributes.
 */
akeeba.System.data = (function ()
{
	var lastId = 0,
		store  = {};

	return {
		set: function (element, property, value)
		{
			// IE 11, modern browsers
			if (element.dataset)
			{
				element.dataset[property] = value;

				if (value == null)
				{
					delete element.dataset[property];
				}

				return;
			}

			// IE 8 to 10, old browsers
			var id;

			if (element.myCustomDataTag === undefined)
			{
				id                      = lastId++;
				element.myCustomDataTag = id;
			}

			if (typeof(store[id]) === 'undefined')
			{
				store[id] = {};
			}

			// Store the value in the internal cache...
			store[id][property] = value;

			// ...and the DOM

			// Convert the property to dash-format
			var dataAttributeName = 'data-' + property.split(/(?=[A-Z])/).join('-').toLowerCase();

			if (element.setAttribute)
			{
				element.setAttribute(dataAttributeName, value);
			}

			if (value == null)
			{
				// IE 8 throws an exception on "delete"
				try
				{
					delete store[id][property];
					element.removeAttribute(dataAttributeName);
				}
				catch (e)
				{
					store[id][property] = null;
				}
			}
		},

		get: function (element, property, defaultValue)
		{
			// IE 11, modern browsers
			if (element.dataset)
			{
				if (typeof(element.dataset[property]) === 'undefined')
				{
					element.dataset[property] = defaultValue;
				}

				return element.dataset[property];
			}
			// IE 8 to 10, old browsers

			if (typeof(defaultValue) === 'undefined')
			{
				defaultValue = null;
			}

			// Make sure we have an internal storage
			if (typeof(store[element.myCustomDataTag]) === 'undefined')
			{
				store[element.myCustomDataTag] = {};
			}

			// Convert the property to dash-format
			var dataAttributeName = 'data-' + property.split(/(?=[A-Z])/).join('-').toLowerCase();

			// data-* attributes have precedence
			if (typeof(element[dataAttributeName]) !== 'undefined')
			{
				store[element.myCustomDataTag][property] = element[dataAttributeName];
			}

			// No data-* attribute and no stored value? Use the default.
			if (typeof(store[element.myCustomDataTag][property]) === 'undefined')
			{
				this.set(element, property, defaultValue);
			}

			// Return the value of the data
			return store[element.myCustomDataTag][property];
		}
	};
}());

/**
 * Adds an event listener to an element
 *
 * @param element
 * @param eventName
 * @param listener
 */
akeeba.System.addEventListener = function (element, eventName, listener)
{
	// Allow the passing of an element ID string instead of the DOM elem
	if (typeof element === "string")
	{
		element = document.getElementById(element);
	}

	if (element == null)
	{
		return;
	}

	if (typeof element !== 'object')
	{
		return;
	}

	// Handles the listener in a way that returning boolean false will cancel the event propagation
	function listenHandler(e)
	{
		var ret = listener.apply(this, arguments);

		if (ret === false)
		{
			if (e.stopPropagation())
			{
				e.stopPropagation();
			}

			if (e.preventDefault)
			{
				e.preventDefault();
			}
			else
			{
				e.returnValue = false;
			}
		}

		return (ret);
	}

	// Equivalent of listenHandler for IE8
	function attachHandler()
	{
		// Normalize the target of the event –– PhpStorm detects this as an error
		// window.event.target = window.event.srcElement;

		var ret = listener.call(element, window.event);

		if (ret === false)
		{
			window.event.returnValue  = false;
			window.event.cancelBubble = true;
		}

		return (ret);
	}

	if (element.addEventListener)
	{
		element.addEventListener(eventName, listenHandler, false);

		return;
	}

	element.attachEvent("on" + eventName, attachHandler);
};

/**
 * Remove an event listener from an element
 *
 * @param element
 * @param eventName
 * @param listener
 */
akeeba.System.removeEventListener = function (element, eventName, listener)
{
	// Allow the passing of an element ID string instead of the DOM elem
	if (typeof element === "string")
	{
		element = document.getElementById(element);
	}

	if (element == null)
	{
		return;
	}

	if (typeof element !== 'object')
	{
		return;
	}

	if (element.removeEventListener)
	{
		element.removeEventListener(eventName, listener);

		return;
	}

	element.detachEvent("on" + eventName, listener);
};

akeeba.System.triggerEvent = function (element, eventName)
{
	if (typeof element === 'undefined')
	{
		return;
	}

	if (element === null)
	{
		return;
	}

	// Allow the passing of an element ID string instead of the DOM elem
	if (typeof element === "string")
	{
		element = document.getElementById(element);
	}

	if (typeof element !== 'object')
	{
		return;
	}

	if (!(element instanceof Element))
	{
		return;
	}

	// Use jQuery and be done with it!
	if (typeof window.jQuery === 'function')
	{
		window.jQuery(element).trigger(eventName);

		return;
	}

	// Internet Explorer way
	if (document.fireEvent && (typeof window.Event === 'undefined'))
	{
		element.fireEvent('on' + eventName);

		return;
	}

	// This works on Chrome and Edge but not on Firefox. Ugh.
	var event = document.createEvent("Event");
	event.initEvent(eventName, true, true);
	element.dispatchEvent(event);
};

// document.ready equivalent from https://github.com/jfriend00/docReady/blob/master/docready.js
(function (funcName, baseObj)
{
	funcName = funcName || "documentReady";
	baseObj  = baseObj || akeeba.System;

	var readyList                   = [];
	var readyFired                  = false;
	var readyEventHandlersInstalled = false;

	// Call this when the document is ready. This function protects itself against being called more than once.
	function ready()
	{
		if (!readyFired)
		{
			// This must be set to true before we start calling callbacks
			readyFired = true;

			for (var i = 0; i < readyList.length; i++)
			{
				/**
				 * If a callback here happens to add new ready handlers, this function will see that it already
				 * fired and will schedule the callback to run right after this event loop finishes so all handlers
				 * will still execute in order and no new ones will be added to the readyList while we are
				 * processing the list.
				 */
				readyList[i].fn.call(window, readyList[i].ctx);
			}

			// Allow any closures held by these functions to free
			readyList = [];
		}
	}

	/**
	 * Solely for the benefit of Internet Explorer
	 */
	function readyStateChange()
	{
		if (document.readyState === "complete")
		{
			ready();
		}
	}

	/**
	 * This is the one public interface:
	 *
	 * akeeba.System.documentReady(fn, context);
	 *
	 * @param   callback   The callback function to execute when the document is ready.
	 * @param   context    Optional. If present, it will be passed as an argument to the callback.
	 */
	//
	//
	//
	baseObj[funcName] = function (callback, context)
	{
		// If ready() has already fired, then just schedule the callback to fire asynchronously
		if (readyFired)
		{
			setTimeout(function ()
			{
				callback(context);
			}, 1);

			return;
		}

		// Add the function and context to the queue
		readyList.push({fn: callback, ctx: context});

		/**
		 * If the document is already ready, schedule the ready() function to run immediately.
		 *
		 * Note: IE is only safe when the readyState is "complete", other browsers are safe when the readyState is
		 * "interactive"
		 */
		if (document.readyState === "complete" || (!document.attachEvent && document.readyState === "interactive"))
		{
			setTimeout(ready, 1);

			return;
		}

		// If the handlers are already installed just quit
		if (readyEventHandlersInstalled)
		{
			return;
		}

		// We don't have event handlers installed, install them
		readyEventHandlersInstalled = true;

		// -- We have an addEventListener method in the document, this is a modern browser.

		if (document.addEventListener)
		{
			// Prefer using the DOMContentLoaded event
			document.addEventListener("DOMContentLoaded", ready, false);

			// Our backup is the window's "load" event
			window.addEventListener("load", ready, false);

			return;
		}

		// -- Most likely we're stuck with an ancient version of IE

		// Our primary method of activation is the onreadystatechange event
		document.attachEvent("onreadystatechange", readyStateChange);

		// Our backup is the windows's "load" event
		window.attachEvent("onload", ready);
	}
})("documentReady", akeeba.System);

akeeba.System.addClass = function (element, newClasses)
{
	if (!element || !element.className)
	{
		return;
	}

	var currentClasses = element.className.split(' ');

	if ((typeof newClasses) === 'string')
	{
		newClasses = newClasses.split(' ');
	}

	currentClasses = array_merge(currentClasses, newClasses);

	element.className = '';

	for (property in currentClasses)
	{
		if (currentClasses.hasOwnProperty(property))
		{
			element.className += currentClasses[property] + ' ';
		}
	}

	if (element.className.trim)
	{
		element.className = element.className.trim();
	}
};

akeeba.System.removeClass = function (element, oldClasses)
{
	if (!element || !element.className)
	{
		return;
	}

	var currentClasses = element.className.split(' ');

	if ((typeof oldClasses) === 'string')
	{
		oldClasses = oldClasses.split(' ');
	}

	currentClasses = array_diff(currentClasses, oldClasses);

	element.className = '';

	for (property in currentClasses)
	{
		if (currentClasses.hasOwnProperty(property))
		{
			element.className += currentClasses[property] + ' ';
		}
	}

	if (element.className.trim)
	{
		element.className = element.className.trim();
	}
};

akeeba.System.hasClass = function (element, aClass)
{
	if (!element || !element.className)
	{
		return;
	}

	var currentClasses = element.className.split(' ');

	for (i = 0; i < currentClasses.length; i++)
	{
		if (currentClasses[i] === aClass)
		{
			return true;
		}
	}

	return false;
};

akeeba.System.toggleClass = function(element, aClass)
{
	if (akeeba.System.hasClass(element, aClass))
	{
		akeeba.System.removeClass(element, aClass);

		return;
	}

	akeeba.System.addClass(element, aClass);
};


/**
 * Akeeba Kickstart
 * An AJAX-powered archive extraction tool
 *
 * @package   kickstart
 * @copyright Copyright (c)2008-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

/**
 * An AJAX abstraction layer for use with Akeeba software
 */
akeeba.Ajax = {
	// Maps nonsense HTTP status codes to what should actually be returned
	xhrSuccessStatus: {
		// File protocol always yields status code 0, assume 200
		0: 200,
		// Support: IE <=9 only. Sometimes IE returns 1223 when it should be 204
		1223: 204
	},
	// Used for chained AJAX: each request will be launched once the previous one is done (successfully or not)
	requestArray: [],
	processingQueue: false
};

/**
 * Performs an asynchronous AJAX request. Mostly compatible with jQuery 1.5+ calling conventions, or at least the
 * subset
 * of the features we used in our software.
 *
 * The parameters can be
 * method        string      HTTP method (GET, POST, PUT, ...). Default: POST.
 * url        string      URL to access over AJAX. Required.
 * timeout    int         Request timeout in msec. Default: 600,000 (ten minutes)
 * data        object      Data to send to the AJAX URL. Default: empty
 * success    function    function(string responseText, string responseStatus, XMLHttpRequest xhr)
 * error        function    function(XMLHttpRequest xhr, string errorType, Exception e)
 * beforeSend    function    function(XMLHttpRequest xhr, object parameters) You can modify xhr, not parameters. Return
 * false to abort the request.
 *
 * @param   url         {string}  URL to send the AJAX request to
 * @param   parameters  {object}  Configuration parameters
 */
akeeba.Ajax.ajax = function (url, parameters)
{
	// Handles jQuery 1.0 calling style of .ajax(parameters), passing the URL as a property of the parameters object
	if (typeof(parameters) === "undefined")
	{
		parameters = url;
		url        = parameters.url;
	}

	// Get the parameters I will use throughout
	var method          = (typeof(parameters.type) === "undefined") ? "POST" : parameters.type;
	method              = method.toUpperCase();
	var data            = (typeof(parameters.data) === "undefined") ? {} : parameters.data;
	var sendData        = null;
	var successCallback = (typeof(parameters.success) === "undefined") ? null : parameters.success;
	var errorCallback   = (typeof(parameters.error) === "undefined") ? null : parameters.error;

	// === Cache busting
	var cache = (typeof(parameters.cache) === "undefined") ? false : parameters.url;

	if (!cache)
	{
		var now                = new Date().getTime() / 1000;
		var s                  = parseInt(now, 10);
		data._cacheBustingJunk = Math.round((now - s) * 1000) / 1000;
	}

	// === Interpolate the data
	if ((method === "POST") || (method === "PUT"))
	{
		sendData = this.interpolateParameters(data);
	}
	else
	{
		url += url.indexOf("?") === -1 ? "?" : "&";
		url += this.interpolateParameters(data);
	}

	// === Get the XHR object
	var xhr = new XMLHttpRequest();
	xhr.open(method, url);

	// === Handle POST / PUT data
	if ((method === "POST") || (method === "PUT"))
	{
		xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
	}

	// --- Set the load handler
	xhr.onload = function (event)
	{
		var status         = akeeba.Ajax.xhrSuccessStatus[xhr.status] || xhr.status;
		var statusText     = xhr.statusText;
		var isBinaryResult = (xhr.responseType || "text") !== "text" || typeof xhr.responseText !== "string";
		var responseText   = isBinaryResult ? xhr.response : xhr.responseText;
		var headers        = xhr.getAllResponseHeaders();

		if (status === 200)
		{
			if (successCallback != null)
			{
				akeeba.Ajax.triggerCallbacks(successCallback, responseText, statusText, xhr);
			}

			return;
		}

		if (errorCallback)
		{
			akeeba.Ajax.triggerCallbacks(errorCallback, xhr, "error", null);
		}
	};

	// --- Set the error handler
	xhr.onerror = function (event)
	{
		if (errorCallback)
		{
			akeeba.Ajax.triggerCallbacks(errorCallback, xhr, "error", null);
		}
	};

	// IE 8 is a pain the butt
	if (window.attachEvent && !window.addEventListener)
	{
		xhr.onreadystatechange = function ()
		{
			if (this.readyState === 4)
			{
				var status = akeeba.Ajax.xhrSuccessStatus[this.status] || this.status;

				if (status >= 200 && status < 400)
				{
					// Success!
					xhr.onload();
				}
				else
				{
					xhr.onerror();
				}
			}
		};
	}

	// --- Set the timeout handler
	xhr.ontimeout = function ()
	{
		if (errorCallback)
		{
			akeeba.Ajax.triggerCallbacks(errorCallback, xhr, "timeout", null);
		}
	};

	// --- Set the abort handler
	xhr.onabort = function ()
	{
		if (errorCallback)
		{
			akeeba.Ajax.triggerCallbacks(errorCallback, xhr, "abort", null);
		}
	};

	// --- Apply the timeout before running the request
	var timeout = (typeof(parameters.timeout) === "undefined") ? 600000 : parameters.timeout;

	if (timeout > 0)
	{
		xhr.timeout = timeout;
	}

	// --- Call the beforeSend event handler. If it returns false the request is canceled.
	if (typeof(parameters.beforeSend) !== "undefined")
	{
		if (parameters.beforeSend(xhr, parameters) === false)
		{
			return;
		}
	}

	xhr.send(sendData);
};

/**
 * Adds an AJAX request to the request queue and begins processing the queue if it's not already started. The request
 * queue is a FIFO buffer. Each request will be executed as soon as the one preceeding it has completed processing
 * (successfully or otherwise).
 *
 * It's the same syntax as .ajax() with the difference that the request is queued instead of executed right away.
 *
 * @param   url         {string}  The URL to send the request to
 * @param   parameters  {object}  Configuration parameters
 */
akeeba.Ajax.enqueue = function (url, parameters)
{
	// Handles jQuery 1.0 calling style of .ajax(parameters), passing the URL as a property of the parameters object
	if (typeof(parameters) === "undefined")
	{
		parameters = url;
		url        = parameters.url;
	}

	parameters.url = url;
	akeeba.Ajax.requestArray.push(parameters);

	akeeba.Ajax.processQueue();
};

/**
 * Converts a simple object containing query string parameters to a single, escaped query string
 *
 * @param    object   {object}  A plain object containing the query parameters to pass
 * @param    prefix   {string}  Prefix for array-type parameters
 *
 * @returns  {string}
 *
 * @access  private
 */
akeeba.Ajax.interpolateParameters = function (object, prefix)
{
	prefix            = prefix || "";
	var encodedString = "";

	for (var prop in object)
	{
		if (object.hasOwnProperty(prop))
		{
			if (encodedString.length > 0)
			{
				encodedString += "&";
			}

			if (typeof object[prop] !== "object")
			{
				if (prefix === "")
				{
					encodedString += encodeURIComponent(prop) + "=" + encodeURIComponent(object[prop]);
				}
				else
				{
					encodedString += encodeURIComponent(prefix) + "[" + encodeURIComponent(prop) + "]=" + encodeURIComponent(object[prop]);
				}

				continue;
			}

			// Objects need special handling
			encodedString += akeeba.Ajax.interpolateParameters(object[prop], prop);
		}
	}
	return encodedString;
};

/**
 * Goes through a list of callbacks and calls them in succession. Accepts a variable number of arguments.
 */
akeeba.Ajax.triggerCallbacks = function ()
{
	// converts arguments to real array
	var args         = Array.prototype.slice.call(arguments);
	var callbackList = args.shift();

	if (typeof(callbackList) === "function")
	{
		return callbackList.apply(null, args);
	}

	if (callbackList instanceof Array)
	{
		for (var i = 0; i < callbackList.length; i++)
		{
			var callBack = callbackList[i];

			if (callBack.apply(null, args) === false)
			{
				return false;
			}
		}
	}

	return null;
};

/**
 * This helper function triggers the request queue processing using a short (50 msec) timer. This prevents a long
 * function nesting which could cause some browser to abort processing.
 *
 * @access  private
 */
akeeba.Ajax.processQueueHelper = function ()
{
	akeeba.Ajax.processingQueue = false;

	setTimeout("akeeba.Ajax.processQueue();", 50);
};

/**
 * Processes the request queue
 *
 * @access  private
 */
akeeba.Ajax.processQueue = function ()
{
	// If I don't have any more requests reset and return
	if (!akeeba.Ajax.requestArray.length)
	{
		akeeba.Ajax.processingQueue = false;
		return;
	}

	// If I am already processing an AJAX request do nothing (I will be called again when the request completes)
	if (akeeba.Ajax.processingQueue)
	{
		return;
	}

	// Extract the URL from the parameters
	var parameters = akeeba.Ajax.requestArray.shift();
	var url        = parameters.url;

	/**
	 * Add our queue processing helper to the top of the success and error callback function stacks, ensuring that we
	 * will process the next request in the queue as soon as the previous one completes (successfully or not)
	 */
	var successCallback = (typeof(parameters.success) === "undefined") ? [] : parameters.success;
	var errorCallback   = (typeof(parameters.error) === "undefined") ? [] : parameters.error;

	if ((typeof(successCallback) !== "object") || !(successCallback instanceof Array))
	{
		successCallback = [successCallback];
	}

	if ((typeof(errorCallback) !== "object") || !(errorCallback instanceof Array))
	{
		errorCallback = [errorCallback];
	}

	successCallback.unshift(akeeba.Ajax.processQueueHelper);
	errorCallback.unshift(akeeba.Ajax.processQueueHelper);

	parameters.success = successCallback;
	parameters.error   = errorCallback;

	// Mark the queue as currently being processed, blocking further requests until this one completes
	akeeba.Ajax.processingQueue = true;

	// Perform the actual request
	akeeba.Ajax.ajax(url, parameters);
};


/**
 * Akeeba Kickstart
 * An AJAX-powered archive extraction tool
 *
 * @package   kickstart
 * @copyright Copyright (c)2008-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

function translateGUI()
{
	var allElements = document.querySelectorAll('*');

	for (var i = 0; i < allElements.length; i++)
	{
		var e = allElements[i];

		if (typeof e.innerHTML === "undefined")
		{
			continue;
		}

		transKey = e.innerHTML;

		if (!array_key_exists(transKey, translation))
		{
			continue;
		}

		e.innerHTML = translation[transKey];
	}
}

function trans(key)
{
	if (array_key_exists(key, translation))
	{
		return translation[key];
	}

	return key;
}

/**
 * Akeeba Kickstart
 * An AJAX-powered archive extraction tool
 *
 * @package   kickstart
 * @copyright Copyright (c)2008-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

var akeeba_error_callback            = onGenericError;
var akeeba_restoration_stat_inbytes  = 0;
var akeeba_restoration_stat_outbytes = 0;
var akeeba_restoration_stat_files    = 0;
var akeeba_restoration_stat_total    = 0;
var akeeba_factory                   = null;
var akeeba_next_step_post            = null;

var akeeba_ftpbrowser_modal     = null;
var akeeba_ftpbrowser_host      = null;
var akeeba_ftpbrowser_port      = 21;
var akeeba_ftpbrowser_username  = null;
var akeeba_ftpbrowser_password  = null;
var akeeba_ftpbrowser_passive   = 1;
var akeeba_ftpbrowser_ssl       = 0;
var akeeba_ftpbrowser_directory = "";

var akeeba_sftpbrowser_host      = null;
var akeeba_sftpbrowser_port      = 21;
var akeeba_sftpbrowser_username  = null;
var akeeba_sftpbrowser_password  = null;
var akeeba_sftpbrowser_pubkey    = null;
var akeeba_sftpbrowser_privkey   = null;
var akeeba_sftpbrowser_directory = "";

akeeba.System.documentReady(function () {
	// Hide 2nd Page
	document.getElementById("page2").style.display = "none";

	// Translate the GUI
	translateGUI();

	// Hook interaction handlers
	akeeba.System.addEventListener(document, "keyup", closeLightbox);
	akeeba.System.addEventListener(document.getElementById("kickstart.procengine"), "change", onChangeProcengine);
	akeeba.System.addEventListener(document.getElementById("kickstart.setup.sourcepath"), "change", onArchiveListReload);
	akeeba.System.addEventListener(document.getElementById("reloadArchives"), "click", onArchiveListReload);
	akeeba.System.addEventListener(document.getElementById("checkFTPTempDir"), "click", oncheckFTPTempDirClick);
	akeeba.System.addEventListener(document.getElementById("resetFTPTempDir"), "click", onresetFTPTempDir);
	akeeba.System.addEventListener(document.getElementById("testFTP"), "click", onTestFTPClick);
	akeeba.System.addEventListener(document.getElementById("gobutton_top"), "click", onStartExtraction);
	akeeba.System.addEventListener(document.getElementById("gobutton"), "click", onStartExtraction);
	akeeba.System.addEventListener(document.getElementById("gobutton"), "click", onStartExtraction);
	akeeba.System.addEventListener(document.getElementById("runCleanup"), "click", onRunCleanupClick);
	akeeba.System.addEventListener(document.getElementById("runInstaller"), "click", onRunInstallerClick);
	akeeba.System.addEventListener(document.getElementById("gotoStart"), "click", onGotoStartClick);
	akeeba.System.addEventListener(document.getElementById("retry"), "click", onRetryClick);

	akeeba.System.addEventListener(document.getElementById("gotoSite"), "click", function (event)
	{
		window.open("index.php", "finalstepsite");
		window.close();
	});

	akeeba.System.addEventListener(document.getElementById("gotoAdministrator"), "click", function (event)
	{
		window.open("administrator/index.php", "finalstepadmin");
		window.close();
	});

	// Reset the progress bar
	setProgressBar(0);

	if (!akeeba_debug)
	{
		document.getElementById("preextraction").style.display = "block";
		document.getElementById("fade").style.display          = "block";
	}

	// Trigger change, so we avoid problems if the user refreshes the page
	onChangeProcengine();

	akeeba.System.triggerEvent(document.getElementById("kickstart.procengine", "change"));
});


/**
 * Akeeba Kickstart
 * An AJAX-powered archive extraction tool
 *
 * @package   kickstart
 * @copyright Copyright (c)2008-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

/**
 * Generic error handler
 *
 * @param   {string}  msg  Error message to display
 */
function onGenericError(msg)
{
	document.getElementById("genericerrorInner").innerHTML = msg;
	document.getElementById("genericerror").style.display  = "block";
	document.getElementById("fade").style.display          = "block";

	akeeba.System.addEventListener(document, "keyup", closeLightbox());
}

/**
 * Set the progress bar to a specific percentage
 *
 * @param   {int}  percent  Percentage (or float 0.0 to 1.0) to display in the progress bar.
 */
function setProgressBar(percent)
{
	var newValue = percent;

	if (percent <= 1)
	{
		newValue = 100 * percent;
	}

	document.getElementById("progressbar-inner").style.width = newValue + "%";
}

/**
 * Close the lightbox
 *
 * @param   {KeyboardEvent|MouseEvent}  event
 */
function closeLightbox(event)
{
	var closeMe = false;

	if ((event == null) || (event === undefined))
	{
		closeMe = true;
	}
	else if (event.keyCode == "27")
	{
		closeMe = true;
	}

	if (!closeMe)
	{
		return;
	}

	document.getElementById("preextraction").style.display = "none";
	document.getElementById("genericerror").style.display  = "none";
	document.getElementById("fade").style.display          = "none";

	akeeba.System.removeEventListener(document, "keyup", closeLightbox);
}


/**
 * Akeeba Kickstart
 * An AJAX-powered archive extraction tool
 *
 * @package   kickstart
 * @copyright Copyright (c)2008-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

/**
 * Event handler for changing the Archive directory.
 */
function onArchiveListReload()
{
	post = {
		'task': 'listArchives',
		'json': JSON.stringify({
			path: document.getElementById('kickstart\.setup\.sourcepath').value
		})
	};

	akeeba.System.doAjax(post, function (ret)
	{
		document.getElementById('sourcefileContainer').innerHTML = ret;
	});
}

/**
 * Event handler for switching the Write To File method
 *
 * @param   {Event}  event
 */
function onChangeProcengine(event)
{
	var elProcEngine = document.getElementById("kickstart.procengine");
	var procEngine   = elProcEngine.value;
	var elFtpOptions = document.getElementById("ftp-options");
	var elPassive    = document.getElementById("ftp-ssl-passive");
	var elTestBtn    = document.getElementById("testFTP");

	// Only hide the (S)FTP options when using direct file writes
	elFtpOptions.style.display = (procEngine === "direct") ? "none" : "block";

	// Set up the interface for a plain FTP or Hybrid extraction engine
	elPassive.style.display = "block";
	elTestBtn.innerHTML     = trans("BTN_TESTFTPCON");

	// If the SFTP engine is selected I need to make some interface changes
	if (procEngine === "sftp")
	{
		// Insert the SFTP path if none is currently specified
		var elFtpDir = document.getElementById("kickstart.ftp.dir");

		if (elFtpDir.value === "")
		{
			elFtpDir.value = sftp_path;
		}

		// Hide the passive mode (it's an FTP-only thing) and change the button label.
		elPassive.style.display = "none";
		elTestBtn.innerHTML     = trans("BTN_TESTSFTPCON");
	}
}

/**
 * Event handler for the Check button next to the Temporary Directory
 *
 * @param   {MouseEvent}  event
 */
function oncheckFTPTempDirClick(event)
{
	var data = {
		'task': 'checkTempdir',
		'json': JSON.stringify({
			'kickstart.ftp.tempdir': document.getElementById('kickstart\.ftp.tempdir').value
		})
	};

	akeeba.System.doAjax(data, function (ret)
	{
		var key = ret.status ? 'FTP_TEMPDIR_WRITABLE' : 'FTP_TEMPDIR_UNWRITABLE';

		alert(trans(key));
	});
}

/**
 * Event handler for the Reset button next to the Temporary Directory
 *
 * @param   {MouseEvent}  event
 */
function onresetFTPTempDir(event)
{
	document.getElementById('kickstart\.ftp\.tempdir').value = default_temp_dir;
}

/**
 * Event handler for the Test FTP Connection button
 *
 * @param   {MouseEvent}  event
 */
function onTestFTPClick(event)
{
	var type = 'ftp';

	if (document.getElementById('kickstart.procengine').value === 'sftp')
	{
		type = 'sftp';
	}

	var data = {
		'task': 'checkFTP',
		'json': JSON.stringify({
			'type':                  type,
			'kickstart.ftp.host':    document.getElementById('kickstart.ftp.host').value,
			'kickstart.ftp.port':    document.getElementById('kickstart.ftp.port').value,
			'kickstart.ftp.ssl':     document.getElementById('kickstart.ftp.ssl').checked,
			'kickstart.ftp.passive': document.getElementById('kickstart.ftp.passive').checked,
			'kickstart.ftp.user':    document.getElementById('kickstart.ftp.user').value,
			'kickstart.ftp.pass':    document.getElementById('kickstart.ftp.pass').value,
			'kickstart.ftp.dir':     document.getElementById('kickstart.ftp.dir').value,
			'kickstart.ftp.tempdir': document.getElementById('kickstart.ftp.tempdir').value
		})
	};

	akeeba.System.doAjax(data, function (ret)
	{
		var key = ret.status ? 'FTP_CONNECTION_OK' : 'FTP_CONNECTION_FAILURE';

		if (type === 'sftp')
		{
			key = ret.status ? 'SFTP_CONNECTION_OK' : 'SFTP_CONNECTION_FAILURE';
		}


		alert(trans(key) + "\n\n" + (ret.status ? '' : ret.message));
	});
}

/**
 * Akeeba Kickstart
 * An AJAX-powered archive extraction tool
 *
 * @package   kickstart
 * @copyright Copyright (c)2008-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

/**
 * Generic error handler
 *
 * @param   {string}  msg  The error message to display
 */
function errorHandler(msg)
{
	document.getElementById("errorMessage").innerHTML = msg;
	document.getElementById("error").style.display    = "block";
}

function onRetryClick(){
	document.getElementById("errorMessage").innerHTML = "";
	document.getElementById("error").style.display    = "none";

	setTimeout(runNextExtractionStep, 10);
}

/**
 * Initialize the archive extraction
 */
function onStartExtraction()
{
	document.getElementById("page1").style.display   = "none";
	document.getElementById("page2").style.display   = "block";
	document.getElementById("currentFile").innerText = "";

	akeeba_error_callback = errorHandler;

	var zapBefore = 0;
	var elZap     = document.getElementById("kickstart\.setup\.zapbefore");

	if (elZap !== null)
	{
		zapBefore = elZap.checked;
	}

	var elRestorePermissions = document.getElementById("kickstart\.setup\.restoreperms");
	var restorePermissions   = false;

	if (elRestorePermissions !== null)
	{
		elRestorePermissions.checked;
	}

	akeeba_next_step_post = {
		"task": "startExtracting",
		"json": JSON.stringify({
			"kickstart.setup.sourcepath": document.getElementById("kickstart\.setup\.sourcepath").value,
			"kickstart.setup.sourcefile": document.getElementById("kickstart\.setup\.sourcefile").value,
			"kickstart.jps.password": document.getElementById("kickstart\.jps\.password").value,
			"kickstart.tuning.min_exec_time": document.getElementById("kickstart\.tuning\.min_exec_time").value,
			"kickstart.tuning.max_exec_time": document.getElementById("kickstart\.tuning\.max_exec_time").value,
			"kickstart.stealth.enable": document.getElementById("kickstart\.stealth\.enable").checked,
			"kickstart.stealth.url": document.getElementById("kickstart\.stealth\.url").value,
			"kickstart.setup.zapbefore": zapBefore,
			"kickstart.tuning.run_time_bias": 75,
			"kickstart.setup.restoreperms": restorePermissions,
			"kickstart.setup.dryrun": 0,
			"kickstart.setup.ignoreerrors": document.getElementById("kickstart\.setup\.ignoreerrors").checked,
			"kickstart.enabled": 1,
			"kickstart.security.password": "",
			"kickstart.setup.renameback": document.getElementById("kickstart\.setup\.renameback").checked,
			"kickstart.procengine": document.getElementById("kickstart\.procengine").value,
			"kickstart.ftp.host": document.getElementById("kickstart\.ftp\.host").value,
			"kickstart.ftp.port": document.getElementById("kickstart\.ftp\.port").value,
			"kickstart.ftp.ssl": document.getElementById("kickstart\.ftp\.ssl").checked,
			"kickstart.ftp.passive": document.getElementById("kickstart\.ftp\.passive").checked,
			"kickstart.ftp.user": document.getElementById("kickstart\.ftp\.user").value,
			"kickstart.ftp.pass": document.getElementById("kickstart\.ftp\.pass").value,
			"kickstart.ftp.dir": document.getElementById("kickstart\.ftp\.dir").value,
			"kickstart.ftp.tempdir": document.getElementById("kickstart\.ftp\.tempdir").value,
			"kickstart.setup.extract_list": document.getElementById("kickstart\.setup\.extract_list").value
		})
	};

	setTimeout(runNextExtractionStep, 10);
}

/**
 * Runs an extraction step.
 *
 * We call it through setTimeout to avoid crashing the JS due to stack exhaustion after a long list of chained function
 * calls.
 */
function runNextExtractionStep()
{
	akeeba.System.doAjax(akeeba_next_step_post,
		function (ret){
			processRestorationStep(ret);
		},
		function (){
			errorHandler("An unexpected error occurred");
		});
}

/**
 * AJAX callback whenever a restoration step runs
 *
 * @param   {object}  data
 */
function processRestorationStep(data)
{
	// Look for errors
	if (!data.status)
	{
		errorHandler(data.message);

		return;
	}

	// Propagate warnings to the GUI
	if (!empty(data.Warnings))
	{
		var elWarnings    = document.getElementById("warnings");
		var elWarningsBox = document.getElementById("warningsBox");

		for (var i = 0; i < data.Warnings.length; i++)
		{
			var item         = data.Warnings[i];
			var elWarningRow = document.createElement("div");

			elWarningRow.innerHTML = item;
			elWarnings.appendChild(elWarningRow);
			elWarningsBox.style.display = "block";
		}
	}

	// Parse total size, if exists
	if (array_key_exists("totalsize", data))
	{
		if (is_array(data.filelist))
		{
			akeeba_restoration_stat_total = 0;

			for (var j = 0; j < data.filelist.length; j++)
			{
				var statItem = data.filelist[j];
				akeeba_restoration_stat_total += statItem[1];
			}
		}

		akeeba_restoration_stat_outbytes = 0;
		akeeba_restoration_stat_inbytes  = 0;
		akeeba_restoration_stat_files    = 0;
	}

	// Update GUI
	akeeba_restoration_stat_inbytes += data.bytesIn;
	akeeba_restoration_stat_outbytes += data.bytesOut;
	akeeba_restoration_stat_files += data.files;

	var percentage = 0;

	if (akeeba_restoration_stat_total > 0)
	{
		percentage = 100 * akeeba_restoration_stat_inbytes / akeeba_restoration_stat_total;
		percentage = Math.max(0, percentage);
		percentage = Math.min(percentage, 100);
	}

	if (data.done)
	{
		percentage = 100;
	}

	setProgressBar(percentage);

	document.getElementById("currentFile").innerText = data.lastfile;

	if (!empty(data.factory))
	{
		akeeba_factory = data.factory;
	}

	if (!data.done)
	{
		akeeba_next_step_post = {
			"task": "continueExtracting",
			"json": JSON.stringify({factory: akeeba_factory})
		};

		setTimeout(runNextExtractionStep, 10);

		return;
	}

	document.getElementById("page2a").style.display             = "none";
	document.getElementById("extractionComplete").style.display = "block";
	document.getElementById("runInstaller").style.display       = "inline-block";
}


/**
 * Akeeba Kickstart
 * An AJAX-powered archive extraction tool
 *
 * @package   kickstart
 * @copyright Copyright (c)2008-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

function onGotoStartClick(event)
{
	document.getElementById("page2").style.display = "none";
	document.getElementById("error").style.display = "none";
	document.getElementById("page1").style.display = "block";
}

function onRunInstallerClick(event)
{
	var windowReference = window.open("installation/index.php", "installer");

	if (!windowReference.opener)
	{
		windowReference.opener = this.window;
	}

	document.getElementById("runCleanup").style.display   = "inline-block";
	document.getElementById("runInstaller").style.display = "none";
}

function onRunCleanupClick(event)
{
	post = {
		"task": "isJoomla",
		// Passing the factory preserves the renamed files array
		"json": JSON.stringify({factory: akeeba_factory})
	};

	akeeba.System.doAjax(post, function (ret)
	{
		isJoomla = ret;
		onRealRunCleanupClick();
	});
}

function onRealRunCleanupClick()
{
	post = {
		"task": "cleanUp",
		// Passing the factory preserves the renamed files array
		"json": JSON.stringify({factory: akeeba_factory})
	};

	akeeba.System.doAjax(post, function (ret)
	{
		document.getElementById("runCleanup").style.display                         = "none";
		document.getElementById("gotoSite").style.display                           = "inline-block";
		document.getElementById("gotoAdministrator").style.display                  = "none";
		document.getElementById("gotoPostRestorationRroubleshooting").style.display = "block";

		if (isJoomla)
		{
			document.getElementById("gotoAdministrator").style.display = "inline-block";
		}
	});
}



		<?php callExtraFeature('onExtraHeadJavascript'); ?>
    </script>
	<?php
}

/**
 * Akeeba Kickstart
 * An AJAX-powered archive extraction tool
 *
 * @package   kickstart
 * @copyright Copyright (c)2008-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

function echoTranslationStrings()
{
	callExtraFeature('onLoadTranslations');
	$translation = AKText::getInstance();
	echo $translation->asJavascript();
}

function echoPage()
{
	$edition         = KICKSTARTPRO ? 'Professional' : 'Core';
	$bestArchivePath = AKKickstartUtils::getBestArchivePath();
	$filelist        = AKKickstartUtils::getArchivesAsOptions($bestArchivePath);
	?>
	<!DOCTYPE html>
	<html>
	<head>
		<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
		<title>Akeeba Kickstart <?php echo $edition ?> <?php echo VERSION ?></title>
		<style type="text/css" media="all" rel="stylesheet">
			<?php echoCSS();?>
		</style>
		<?php echoHeadJavascript(); ?>
	</head>
	<body>

	<div id="fade" class="black_overlay"></div>

	<div id="page-container">

		<div id="preextraction" class="white_content">
			<h2>THINGS_HEADER</h2>
			<ol>
				<li>THINGS_01</li>
				<li>THINGS_03</li>
				<li>THINGS_04</li>
				<li>THINGS_05</li>
				<li>THINGS_06</li>
				<li>THINGS_07</li>
				<li>THINGS_08</li>
				<li>THINGS_09</li>
			</ol>
			<a href="javascript:void(0)" onclick="closeLightbox();">CLOSE_LIGHTBOX</a>
		</div>

		<div id="genericerror" class="white_content">
			<pre id="genericerrorInner"></pre>
		</div>

		<div id="header">
			<div class="title">
				<span id="logo" alt="Akeeba Kickstart logo"></span>
				Akeeba Kickstart <?php echo $edition ?> <?php echo defined('VERSION') ? VERSION : '8.0.2-dev202307211217-revb86be29' ?>
			</div>
		</div>

		<div id="page1">
			<?php callExtraFeature('onPage1'); ?>

			<div id="page1-content">

				<div class="helpme">
					<span>NEEDSOMEHELPKS</span> <a
						href="https://www.akeeba.com/documentation/akeeba-kickstart-documentation/using-kickstart.html"
						target="_blank">QUICKSTART</a>
				</div>

				<div class="step1">
					<div class="circle">1</div>
					<h2>SELECT_ARCHIVE</h2>
					<div class="area-container">
						<?php callExtraFeature('onPage1Step1'); ?>
						<div class="clr"></div>

						<label for="kickstart.setup.sourcepath">ARCHIVE_DIRECTORY</label>
			<span class="field">
				<input type="text" id="kickstart.setup.sourcepath"
				       value="<?php echo htmlentities($bestArchivePath); ?>"/>
				<span class="button" id="reloadArchives" style="margin-top:0;margin-bottom:0">RELOAD_ARCHIVES</span>
			</span>
						<br/>

						<label for="kickstart.setup.sourcefile">ARCHIVE_FILE</label>
			<span class="field" id="sourcefileContainer">
				<?php if (!empty($filelist)): ?>
					<select id="kickstart.setup.sourcefile">
						<?php echo $filelist; ?>
					</select>
				<?php else: ?>
					<a href="https://www.akeeba.com/documentation/akeeba-kickstart-documentation/ksnoarchives.html"
					   target="_blank">NOARCHIVESCLICKHERE</a>
				<?php endif; ?>
			</span>
						<br/>
						<label for="kickstart.jps.password">JPS_PASSWORD</label>
						<span class="field"><input type="password" id="kickstart.jps.password" value=""/></span>
					</div>
                    <div class="area-container">
                        <label for="gobutton_top"></label>
                        <span id="gobutton_top" class="button" style="padding: 0.5em 2em; margin: 0;">BTN_START</span>
                    </div>
                </div>

				<div class="clr"></div>

				<div class="step2">
					<div class="circle">2</div>
					<h2>SELECT_EXTRACTION</h2>
					<div class="area-container">
						<label for="kickstart.procengine">WRITE_TO_FILES</label>
			<span class="field">
				<select id="kickstart.procengine">
					<option value="direct">WRITE_DIRECTLY</option>
					<option value="hybrid">WRITE_HYBRID</option>
					<option value="ftp">WRITE_FTP</option>
					<option value="sftp">WRITE_SFTP</option>
				</select>
			</span><br/>

						<label for="kickstart.setup.ignoreerrors">IGNORE_MOST_ERRORS</label>
						<span class="field"><input type="checkbox" id="kickstart.setup.ignoreerrors"/></span>

						<div id="ftp-options">
							<label for="kickstart.ftp.host">FTP_HOST</label>
							<span class="field"><input type="text" id="kickstart.ftp.host"
							                           value="localhost"/></span><br/>
							<label for="kickstart.ftp.port">FTP_PORT</label>
							<span class="field"><input type="text" id="kickstart.ftp.port" value="21"/></span><br/>
							<div id="ftp-ssl-passive">
								<label for="kickstart.ftp.ssl">FTP_FTPS</label>
								<span class="field"><input type="checkbox" id="kickstart.ftp.ssl"/></span><br/>
								<label for="kickstart.ftp.passive">FTP_PASSIVE</label>
								<span class="field"><input type="checkbox" id="kickstart.ftp.passive"
								                           checked="checked"/></span><br/>
							</div>
							<label for="kickstart.ftp.user">FTP_USER</label>
							<span class="field"><input type="text" id="kickstart.ftp.user" value=""/></span><br/>
							<label for="kickstart.ftp.pass">FTP_PASS</label>
							<span class="field"><input type="password" id="kickstart.ftp.pass" value=""/></span><br/>
							<label for="kickstart.ftp.dir">FTP_DIR</label>
				<span class="field">
                    <input type="text" id="kickstart.ftp.dir" value=""/>
                    <?php //<span class="button" id="browseFTP" style="margin-top:0;margin-bottom:0">FTP_BROWSE</span> ?>
                </span><br/>

							<label for="kickstart.ftp.tempdir">FTP_TEMPDIR</label>
				<span class="field">
					<input type="text" id="kickstart.ftp.tempdir"
					       value="<?php echo htmlentities(AKKickstartUtils::getTemporaryPath()) ?>"/>
					<span class="button" id="checkFTPTempDir">BTN_CHECK</span>
					<span class="button" id="resetFTPTempDir">BTN_RESET</span>
				</span><br/>
							<label></label>
							<span class="button" id="testFTP">BTN_TESTFTPCON</span>
							<a id="notWorking" class="button"
							   href="https://www.akeeba.com/documentation/akeeba-kickstart-documentation/kscantextract.html"
							   target="_blank">CANTGETITTOWORK</a>
							<br/>
						</div>

					</div>
				</div>

				<div class="clr"></div>

				<div class="step3">
					<div class="circle">3</div>
					<h2>FINE_TUNE</h2>
					<div id="fine-tune-holder" class="area-container">
						<label for="kickstart.tuning.min_exec_time">MIN_EXEC_TIME</label>
						<span class="field"><input type="text" id="kickstart.tuning.min_exec_time" value="1"/></span>
						<span>SECONDS_PER_STEP</span><br/>
						<label for="kickstart.tuning.max_exec_time">MAX_EXEC_TIME</label>
						<span class="field"><input type="text" id="kickstart.tuning.max_exec_time" value="5"/></span>
						<span>SECONDS_PER_STEP</span><br/>
                        <div class="help">TIME_SETTINGS_HELP</div>

						<label for="kickstart.stealth.enable">STEALTH_MODE</label>
						<span class="field"><input type="checkbox" id="kickstart.stealth.enable"/></span><br/>
						<label for="kickstart.stealth.url">STEALTH_URL</label>
						<span class="field"><input type="text" id="kickstart.stealth.url" value="installation/offline.html"/></span><br/>
                        <div class="help">STEALTH_MODE_HELP</div>

                        <?php if (defined('KICKSTARTPRO') && KICKSTARTPRO): ?>
                        <label for="kickstart.setup.zapbefore">ZAPBEFORE</label>
                        <span class="field"><input type="checkbox" id="kickstart.setup.zapbefore"/></span><br/>
                        <div class="help">ZAPBEFORE_HELP</div>
                        <?php endif; ?>

                        <label for="kickstart.setup.renameback">RENAME_FILES</label>
						<span class="field"><input type="checkbox" id="kickstart.setup.renameback"
						                           checked="checked"/></span><br/>
                        <div class="help">RENAME_FILES_HELP</div>

						<label for="kickstart.setup.restoreperms">RESTORE_PERMISSIONS</label>
						<span class="field"><input type="checkbox" id="kickstart.setup.restoreperms"/></span><br/>
                        <div class="help">RESTORE_PERMISSIONS_HELP</div>

                        <label for="kickstart.setup.extract_list">EXTRACT_LIST</label>
                        <span class="field"><textarea class="monospaced" id="kickstart.setup.extract_list" rows="5" cols="50"></textarea></span><br/>
                        <div class="help">EXTRACT_LIST_HELP</div>
					</div>
				</div>

				<div class="clr"></div>

				<div class="step4">
					<div class="circle">4</div>
					<h2>EXTRACT_FILES</h2>
					<div class="area-container">
                        <label for="gobutton"></label>
						<span id="gobutton" class="button">BTN_START</span>
					</div>
                </div>

				<div class="clr"></div>

			</div>
		</div>

		<div id="page2">
			<div id="page2a">
				<div class="circle">5</div>
				<h2>EXTRACTING</h2>
				<div class="area-container">
					<div id="warn-not-close">DO_NOT_CLOSE_EXTRACT</div>
					<div id="progressbar">
						<div id="progressbar-inner">&nbsp;</div>
					</div>
					<div id="currentFile"></div>
				</div>
			</div>

			<div id="extractionComplete" style="display: none">
				<div class="circle">6</div>
				<h2>RESTACLEANUP</h2>
				<div id="runInstaller" class="button">BTN_RUNINSTALLER</div>
				<div id="runCleanup" class="button" style="display:none">BTN_CLEANUP</div>
				<div id="gotoSite" class="button" style="display:none">BTN_SITEFE</div>
				<div id="gotoAdministrator" class="button" style="display:none">BTN_SITEBE</div>
				<div id="gotoPostRestorationRroubleshooting" style="display:none">
					<a href="https://www.akeeba.com/documentation/akeeba-kickstart-documentation/post-restoration.html"
					   target="_blank">POSTRESTORATIONTROUBLESHOOTING</a>
				</div>
			</div>

			<div id="warningsBox" style="display: none;">
				<div id="warningsHeader">
					<h2>WARNINGS</h2>
				</div>
				<div id="warningsContainer">
					<div id="warnings"></div>
				</div>
			</div>

			<div id="error" style="display: none;">
				<h3>ERROR_OCCURED</h3>
				<p id="errorMessage"></p>
				<div id="gotoStart" class="button">BTN_GOTOSTART</div>
				<div id="retry" class="button bluebutton">BTN_RETRY</div>
				<div>
					<a href="https://www.akeeba.com/documentation/akeeba-kickstart-documentation/kscantextract.html" class="whitelink"
					   target="_blank">CANTGETITTOWORK</a>
				</div>
			</div>
		</div>

		<div id="footer">
			<div class="copyright">Copyright &copy; 2008&ndash;<?php echo date('Y'); ?> <a
					href="https://www.akeeba.com">Nicholas K.
					Dionysopoulos / Akeeba Ltd</a>. All legal rights reserved.<br/>

				This program is free software: you can redistribute it and/or modify it under the terms of
				the <a href="http://www.gnu.org/gpl-3.html">GNU General
					Public License</a> as published by the Free Software Foundation, either version 3 of the License,
				or (at your option) any later version.<br/>
			</div>
		</div>

	</div>

	</body>
	</html>
	<?php
}

/**
 * Akeeba Kickstart
 * An AJAX-powered archive extraction tool
 *
 * @package   kickstart
 * @copyright Copyright (c)2008-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

/**
 * Clear the code caches for the extracted files. Used when finalizing the restoration.
 *
 * @return  void
 */
function clearCodeCaches()
{
	// Zend OPcache — No longer needed; we invalidate each .php file we delete, overwrite or create.
//	if (function_exists('opcache_reset'))
//	{
//		opcache_reset();
//	}

	// APC code cache
	if (function_exists('apc_clear_cache'))
	{
		@apc_clear_cache();
	}
}

/**
 * Removes all files pertaining to Kickstart.
 *
 * Using when finalizing the archive extraction from the web
 *
 * @param   AKAbstractPostproc   $postProc  The post-processing engine of Akeeba Restore in use
 */
function removeKickstartFiles(AKAbstractPostproc $postProc)
{
	// Remove self
	$postProc->unlink(basename(__FILE__));

	// Delete translations
	removeKickstartTranslationFiles($postProc);

	// Delete feature files
	deleteKickstartFeatureFiles($postProc);

	// Delete the temporary directory IF AND ONLY IF it's called "kicktemp"
	deleteKickstartTempDirectory($postProc);

	// Delete cacert.pem
	$postProc->unlink('cacert.pem');
}

/**
 * Remove feature files, e.g. kickstart.transfer.php
 *
 * @param AKAbstractPostproc $postProc
 *
 * @return void
 */
function deleteKickstartFeatureFiles(AKAbstractPostproc $postProc)
{
	$dh = opendir(AKKickstartUtils::getPath());

	if ($dh === false)
	{
		return;
	}

	$basename = basename(__FILE__, '.php');

	while (false !== $file = @readdir($dh))
	{
		if (
			(substr($file, 0, strlen($basename) + 1) == $basename . '.')
			&& (substr($file, -4) == '.php')
		)
		{
			$postProc->unlink($file);
		}
	}

	closedir($dh);
}

/**
 * Delete the temporary directory IF AND ONLY IF it's called "kicktemp"
 *
 * @param AKAbstractPostproc $postProc
 *
 * @return void
 */
function deleteKickstartTempDirectory(AKAbstractPostproc $postProc)
{
	$tempDir = $postProc->getTempDir();
	$tempDir = trim($tempDir);

	if (empty($tempDir))
	{
		return;
	}

	$basename = basename($tempDir);

	if (strtolower($basename) != 'kicktemp')
	{
		return;
	}

	recursive_remove_directory($tempDir);
}

/**
 * Delete language files, e.g. el-GR.kickstart.ini
 *
 * @param AKAbstractPostproc $postProc
 *
 * @return void
 */
function removeKickstartTranslationFiles(AKAbstractPostproc $postProc)
{
	$dh = opendir(AKKickstartUtils::getPath());

	if ($dh === false)
	{
		return;
	}

	$basename = basename(__FILE__, '.php');

	while (false !== $file = @readdir($dh))
	{
		if (strstr($file, $basename . '.ini'))
		{
			$postProc->unlink($file);
		}
	}

	closedir($dh);
}

/**
 * Finalization after the restoration. Removes the installation directory, the backup archive and rolls back automatic
 * file renames.
 *
 * @param   AKAbstractUnarchiver  $unarchiver  The unarchiver engine used by Akeeba Restore
 * @param   AKAbstractPostproc    $postProc    The post-processing engine used by Akeeba Restore
 */
function finalizeAfterRestoration(AKAbstractUnarchiver $unarchiver, AKAbstractPostproc $postProc)
{
    // Remove installation
	recursive_remove_directory('installation');

	// Run the renames, backwards
	rollbackAutomaticRenames($unarchiver, $postProc);

	// Delete the archive
	foreach ($unarchiver->archiveList as $archive)
	{
		$postProc->unlink($archive);
	}
}

/**
 * Rolls back automatic file renames.
 *
 * @param   AKAbstractUnarchiver  $unarchiver  The unarchiver engine used by Akeeba Restore
 * @param   AKAbstractPostproc    $postProc    The post-processing engine used by Akeeba Restore
 */
function rollbackAutomaticRenames(AKAbstractUnarchiver $unarchiver, AKAbstractPostproc $postProc)
{
	$renameBack = AKFactory::get('kickstart.setup.renameback', true);

	if ($renameBack)
	{
		$renames = $unarchiver->renameFiles;

		if (!empty($renames))
		{
			foreach ($renames as $original => $renamed)
			{
				$postProc->rename($renamed, $original);
			}
		}
	}
}

/**
 * Akeeba Kickstart
 * An AJAX-powered archive extraction tool
 *
 * @package   kickstart
 * @copyright Copyright (c)2008-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

/**
 * Utility class to parse CLI parameters in a POSIX way
 */
class AKCliParams
{
	/**
	 * POSIX-style CLI options. Access them with through the getOption method.
	 *
	 * @var   array
	 */
	protected static $cliOptions = array();

	/**
	 * Parses POSIX command line options and sets the self::$cliOptions associative array. Each array item contains
	 * a single dimensional array of values. Arguments without a dash are silently ignored.
	 *
	 * @return  void
	 */
	public static function parseOptions()
	{
		global $argc, $argv;

		// Workaround for PHP-CGI
		if (!isset($argc) && !isset($argv))
		{
			$query = "";

			if (!empty($_GET))
			{
				foreach ($_GET as $k => $v)
				{
					$query .= " $k";

					if ($v != "")
					{
						$query .= "=$v";
					}
				}
			}

			$query = ltrim($query);
			$argv  = explode(' ', $query);
			$argc  = count($argv);
		}

		$currentName = "";
		$options     = array();

		for ($i = 1; $i < $argc; $i++)
		{
			$argument = $argv[$i];

			$value = $argument;

			if (strpos($argument, "-") === 0)
			{
				$argument = ltrim($argument, '-');

				$name  = $argument;
				$value = null;

				if (strstr($argument, '='))
				{
					list($name, $value) = explode('=', $argument, 2);
				}

				$currentName = $name;

				if (!isset($options[$currentName]) || ($options[$currentName] == null))
				{
					$options[$currentName] = array();
				}
			}

			if ((!is_null($value)) && (!is_null($currentName)))
			{
				$key = null;

				if (strstr($value, '='))
				{
					$parts = explode('=', $value, 2);
					$key   = $parts[0];
					$value = $parts[1];
				}

				$values = $options[$currentName];

				if (is_null($values))
				{
					$values = array();
				}

				if (is_null($key))
				{
					$values[] = $value;
				}
				else
				{
					$values[$key] = $value;
				}

				$options[$currentName] = $values;
			}
		}

		self::$cliOptions = $options;
	}

	/**
	 * Returns the value of a command line option
	 *
	 * @param   string $key     The full name of the option, e.g. "foobar"
	 * @param   mixed  $default The default value to return
	 * @param   bool   $array   Should I return an array parameter?
	 *
	 * @return  mixed  The value of the option
	 */
	public static function getOption($key, $default = null, $array = false)
	{
		// If the key doesn't exist set it to the default value
		if (!array_key_exists($key, self::$cliOptions))
		{
			self::$cliOptions[$key] = is_array($default) ? $default : array($default);
		}

		if ($array)
		{
			return self::$cliOptions[$key];
		}

		return self::$cliOptions[$key][0];
	}

	/**
	 * Is the specified param key used in the command line?
	 *
	 * @param   string $key The full name of the option, e.g. "foobar"
	 *
	 * @return  mixed  The value of the option
	 */
	public static function hasOption($key)
	{
		return array_key_exists($key, self::$cliOptions);
	}
}

class CLIExtractionObserver extends ExtractionObserver
{
	public static $silent = false;

	public function update($object, $message)
	{
		parent::update($object, $message);

		if (self::$silent)
		{
			return;
		}

		if (!is_object($message))
		{
			return;
		}

		if (!array_key_exists('type', get_object_vars($message)))
		{
			return;
		}

		if ($message->type == 'startfile')
		{
			echo $message->content->file . "\n";
		}
	}

}

class CLIDeletionObserver extends ExtractionObserver
{
	public static $silent = false;

	public function update($object, $message)
	{
		if (self::$silent)
		{
			return;
		}

		if (!is_object($message))
		{
			return;
		}

		if (!array_key_exists('type', get_object_vars($message)))
		{
			return;
		}

		switch ($message->type)
        {
            case 'setup':
                echo "I will delete existing files and folders\n";
                break;

            case 'deleteFile':
                echo "DELETE FILE  : $message->file\n";
                break;

            case 'deleteFolder':
                echo "DELETE FOLDER: $message->file\n";
                break;
        }
	}

}

/**
 * Routes the Kickstart CLI application
 */
function kickstart_application_cli()
{
	AKCliParams::parseOptions();
	$silent = AKCliParams::hasOption('silent');
	$year   = gmdate('Y');

	if (!$silent)
	{
		$version = defined('VERSION') ? VERSION : '8.0.2-dev202307211217-revb86be29';
		echo <<< BANNER
Akeeba Kickstart CLI $version
Copyright (c) 2008-$year Akeeba Ltd / Nicholas K. Dionysopoulos
-------------------------------------------------------------------------------
Akeeba Kickstart is Free Software, distributed under the terms of the GNU General
Public License version 3 or, at your option, any later version.
This program comes with ABSOLUTELY NO WARRANTY as per sections 15 & 16 of the
license. See http://www.gnu.org/licenses/gpl-3.0.html for details.
-------------------------------------------------------------------------------


BANNER;
	}

	$paths = AKCliParams::getOption('', array(), true);

	if (empty($paths))
	{
		global $argv;

		echo <<< HOWTOUSE
Usage: {$argv[0]} archive.jpa [output_path] [--password=yourPassword]
         [--silent] [--permissions] [--dry-run] [--ignore-errors]
         [--delete-before]
         [--extract=<pattern>[,<pattern>...]]


HOWTOUSE;

		die;
	}

	AKFactory::nuke();

	$targetPath  = isset($paths[1]) ? $paths[1] : getcwd();
	$targetPath  = realpath($targetPath);
	$archive     = $paths[0];
	$archive     = realpath($archive);
	$archivePath = dirname($archive);
	$archivePath = empty($archivePath) ? getcwd() : $archivePath;
	$archivePath = empty($archivePath) ? __DIR__ : $archivePath;
	$archiveName = basename($paths[0]);

	$archiveForDisplay = $archive;
	$cwd               = getcwd();

	if ($archivePath == realpath($cwd))
	{
		$archiveForDisplay = $archiveName;
	}

	if (!$silent)
	{
		echo <<< BANNER
Extracting $archiveForDisplay
to folder  $targetPath

BANNER;
	}

	// What am I extracting?
	AKFactory::set('kickstart.setup.sourcepath', $archivePath);
	AKFactory::set('kickstart.setup.sourcefile', $archiveName);
	// JPS password
	AKFactory::set('kickstart.jps.password', AKCliParams::getOption('password'));
	// Restore permissions?
	AKFactory::set('kickstart.setup.restoreperms', AKCliParams::hasOption('permissions'));
	// Dry run?
	AKFactory::set('kickstart.setup.dryrun', AKCliParams::hasOption('dry-run'));
	// Ignore errors?
	AKFactory::set('kickstart.setup.ignoreerrors', AKCliParams::hasOption('ignore-errors'));
	// Delete all files and folders before extraction?
	AKFactory::set('kickstart.setup.zapbefore', AKCliParams::hasOption('delete-before'));
	// Which files should I extract?
	AKFactory::set('kickstart.setup.extract_list', AKCliParams::getOption('extract', '', true));
	// Do not rename any files (this is the CLI...)
	AKFactory::set('kickstart.setup.renamefiles', array());
	// Optimize time limits
	AKFactory::set('kickstart.tuning.max_exec_time', 20);
	AKFactory::set('kickstart.tuning.run_time_bias', 75);
	AKFactory::set('kickstart.tuning.min_exec_time', 0);
	AKFactory::set('kickstart.procengine', 'direct');

	// Make sure that the destination directory is always set (req'd by both FTP and Direct Writes modes)
	if (empty($targetPath))
	{
		$targetPath = AKKickstartUtils::getPath();
	}

	AKFactory::set('kickstart.setup.destdir', $targetPath);

	$unarchiver = AKFactory::getUnarchiver();
	$observer   = new CLIExtractionObserver();
	$unarchiver->attach($observer);

	if ($silent)
	{
		CLIExtractionObserver::$silent = true;
	}

	if (!$silent)
	{
		echo "\n\n";
	}

	$retArray = array(
		'done' => false,
	);

	while (!$retArray['done'])
	{
	    $timer = AKFactory::getTimer();
	    $timer->resetTime();

        /**
         * First try to run the filesystem zapper (remove all existing files and folders). If the Zapper is
         * disabled or has already finished running we will get a FALSE result. Otherwise it's a status array
         * which we can pass directly back to the caller.
         */
        $ret = runZapper(new CLIDeletionObserver());

        // If the Zapper had a step to run we stop here and return its status array to the caller.
        if ($ret !== false)
        {
            continue;
        }

        $unarchiver->tick();
		$ret = $unarchiver->getStatusArray();

		if ($ret['Error'] != '')
		{
			$retArray['status']  = false;
			$retArray['done']    = true;
			$retArray['message'] = $ret['Error'];
		}
		elseif (!$ret['HasRun'])
		{
			$retArray['files']    = $observer->filesProcessed;
			$retArray['bytesIn']  = $observer->compressedTotal;
			$retArray['bytesOut'] = $observer->uncompressedTotal;
			$retArray['status']   = true;
			$retArray['done']     = true;
		}
		else
		{
			$retArray['files']    = $observer->filesProcessed;
			$retArray['bytesIn']  = $observer->compressedTotal;
			$retArray['bytesOut'] = $observer->uncompressedTotal;
			$retArray['status']   = true;
			$retArray['done']     = false;
		}

		if (!is_null($observer->totalSize))
		{
			$retArray['totalsize'] = $observer->totalSize;
			$retArray['filelist']  = $observer->fileList;
		}

		$retArray['Warnings'] = $ret['Warnings'];
		$retArray['lastfile'] = $observer->lastFile;

		if (!empty($retArray['Warnings']) && !$silent)
		{
			echo "\n\n";

			foreach ($retArray['Warnings'] as $line)
			{
				echo "\t$line\n";
			}

			echo "\n";
		}
	}

	if (!$silent)
	{
		echo "\n\n";
	}

	if (!$retArray['status'])
	{
		if (!$silent)
		{
			echo "An error has occurred:\n{$retArray['message']}\n\n";
		}

		exit(255);
	}

	// Finalize
	$postProc = AKFactory::getPostProc();

	rollbackAutomaticRenames($unarchiver, $postProc);
	clearCodeCaches();
}

/**
 * Akeeba Kickstart
 * An AJAX-powered archive extraction tool
 *
 * @package   kickstart
 * @copyright Copyright (c)2008-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

/**
 * Routes the Kickstart web application
 */
function kickstart_application_web()
{
	$retArray = array(
		'status'  => true,
		'message' => null
	);

	$task = getQueryParam('task', 'display');
	$json = getQueryParam('json');
	$ajax = true;

	switch ($task)
	{
		case 'checkTempdir':
			$retArray['status'] = false;

			if (!empty($json))
			{
				$data = json_decode($json, true);
				$dir  = @$data['kickstart.ftp.tempdir'];

				if (!empty($dir))
				{
					$retArray['status'] = is_writable($dir);
				}
			}
			break;

		case 'checkFTP':
			$retArray['status'] = false;

			if (!empty($json))
			{
				$data = json_decode($json, true);

				foreach ($data as $key => $value)
				{
					AKFactory::set($key, $value);
				}

				if ($data['type'] == 'ftp')
				{
					$ftp = new AKPostprocFTP();
				}
				else
				{
					$ftp = new AKPostprocSFTP();
				}

				$retArray['message'] = $ftp->getError();
				$retArray['status']  = empty($retArray['message']);
			}
			break;

		case 'ftpbrowse':
			if (!empty($json))
			{
				$data = json_decode($json, true);

				$retArray =
					getListing($data['directory'], $data['host'], $data['port'], $data['username'], $data['password'], $data['passive'], $data['ssl']);
			}
			break;

		case 'sftpbrowse':
			if (!empty($json))
			{
				$data = json_decode($json, true);

				$retArray =
					getSftpListing($data['directory'], $data['host'], $data['port'], $data['username'], $data['password']);
			}
			break;

		case 'startExtracting':
		case 'continueExtracting':
			// Look for configuration values
			$retArray['status'] = false;

			if (!empty($json))
			{
				if ($task == 'startExtracting')
				{
					AKFactory::nuke();
				}

				$oldJSON = $json;
				$json    = json_decode($json, true);

				if (is_null($json))
				{
					$json = stripslashes($oldJSON);
					$json = json_decode($json, true);
				}

				if (!empty($json))
				{
					foreach ($json as $key => $value)
					{
						if (substr($key, 0, 9) == 'kickstart')
						{
							AKFactory::set($key, $value);
						}
					}
				}

				// A "factory" variable will override all other settings.
				if (array_key_exists('factory', $json))
				{
					// Get the serialized factory
					$serialized = $json['factory'];
					AKFactory::unserialize($serialized);
					AKFactory::set('kickstart.enabled', true);
				}

				// Make sure that the destination directory is always set (req'd by both FTP and Direct Writes modes)
				$removePath = AKFactory::get('kickstart.setup.destdir', '');

				if (empty($removePath))
				{
					AKFactory::set('kickstart.setup.destdir', AKKickstartUtils::getPath());
				}

				if ($task == 'startExtracting')
				{
					// Before starting, read and save any custom AddHandler directive
					$phpHandlers = getPhpHandlers();
					AKFactory::set('kickstart.setup.phphandlers', $phpHandlers);

					// If the Stealth Mode is enabled, create the .htaccess file
					if (AKFactory::get('kickstart.stealth.enable', false))
					{
						createStealthURL();
					}
					// No stealth mode, but we have custom handler directives, must write our own file
					elseif ($phpHandlers)
					{
						writePhpHandlers();
					}
				}

                /**
                 * First try to run the filesystem zapper (remove all existing files and folders). If the Zapper is
                 * disabled or has already finished running we will get a FALSE result. Otherwise it's a status array
                 * which we can pass directly back to the caller.
                 */
                $ret = runZapper();

                // If the Zapper had a step to run we stop here and return its status array to the caller.
                if ($ret !== false)
                {
                	$retArray = array_merge($retArray, $ret);

                    break;
                }

                $engine   = AKFactory::getUnarchiver(); // Get the engine
				$observer = new ExtractionObserver(); // Create a new observer
				$engine->attach($observer); // Attach the observer
				$engine->tick();
				$ret = $engine->getStatusArray();

				if ($ret['Error'] != '')
				{
					$retArray['status']  = false;
					$retArray['done']    = true;
					$retArray['message'] = $ret['Error'];
				}
				elseif (!$ret['HasRun'])
				{
					$retArray['files']    = $observer->filesProcessed;
					$retArray['bytesIn']  = $observer->compressedTotal;
					$retArray['bytesOut'] = $observer->uncompressedTotal;
					$retArray['status']   = true;
					$retArray['done']     = true;
				}
				else
				{
					$retArray['files']    = $observer->filesProcessed;
					$retArray['bytesIn']  = $observer->compressedTotal;
					$retArray['bytesOut'] = $observer->uncompressedTotal;
					$retArray['status']   = true;
					$retArray['done']     = false;
					$retArray['factory']  = AKFactory::serialize();
				}

				if (!is_null($observer->totalSize))
				{
					$retArray['totalsize'] = $observer->totalSize;
					$retArray['filelist']  = $observer->fileList;
				}

				$retArray['Warnings'] = $ret['Warnings'];
				$retArray['lastfile'] = empty($observer->lastFile) ? 'Extracting, please wait...' : $observer->lastFile;

				$timer = AKFactory::getTimer();
				$timer->enforce_min_exec_time();
			}
			break;

		case 'cleanUp':
			if (!empty($json))
			{
				$json = json_decode($json, true);

				if (array_key_exists('factory', $json))
				{
					// Get the serialized factory
					$serialized = $json['factory'];
					AKFactory::unserialize($serialized);
					AKFactory::set('kickstart.enabled', true);
				}
			}

			$unarchiver = AKFactory::getUnarchiver(); // Get the engine
			$postProc   = AKFactory::getPostProc();

			finalizeAfterRestoration($unarchiver, $postProc);
			removeKickstartFiles($postProc);
			clearCodeCaches();

			break;

		case 'display':
			$ajax = false;
			echoPage();
			break;

		case 'isJoomla':
			$ajax = true;

			if (!empty($json))
			{
				$json = json_decode($json, true);

				if (array_key_exists('factory', $json))
				{
					// Get the serialized factory
					$serialized = $json['factory'];
					AKFactory::unserialize($serialized);
					AKFactory::set('kickstart.enabled', true);
				}
			}

			$path     = AKFactory::get('kickstart.setup.destdir', '');
			$path     = rtrim($path, '/\\');
			$isJoomla = @is_dir($path . '/administrator');

			if ($isJoomla)
			{
				$isJoomla = @is_dir($path . '/libraries/joomla');
			}

			$retArray = $isJoomla;

			break;

		case 'listArchives':
			$ajax = true;

			$path = null;

			if (!empty($json))
			{
				$json = json_decode($json, true);

				if (array_key_exists('path', $json))
				{
					$path = $json['path'];
				}
			}

			if (empty($path) || !@is_dir($path))
			{
				$filelist = null;
			}
			else
			{
				$filelist = AKKickstartUtils::getArchivesAsOptions($path);
			}

			if (empty($filelist))
			{
				$retArray =
					'<a href="https://www.akeeba.com/documentation/akeeba-kickstart-documentation/ksnoarchives.html" target="_blank">' .
					AKText::_('NOARCHIVESCLICKHERE')
					. '</a>';
			}
			else
			{
				$retArray = '<select id="kickstart.setup.sourcefile">' . $filelist . '</select>';
			}

			break;

		default:
			$ajax = true;

			if (!empty($json))
			{
				$params = json_decode($json, true);
			}
			else
			{
				$params = array();
			}

			$retArray = callExtraFeature($task, $params);

			break;
	}

	if ($ajax)
	{
		// JSON encode the message
		$json = json_encode($retArray);

		// Return the message
		echo "###$json###";
	}
}

/**
 * Akeeba Kickstart
 * An AJAX-powered archive extraction tool
 *
 * @package   kickstart
 * @copyright Copyright (c)2008-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

// Register additional feature classes
callExtraFeature();

// Is this a CLI call?
$isCli = !isset($_SERVER) || !is_array($_SERVER);

if (isset($_SERVER) && is_array($_SERVER))
{
	$isCli = !array_key_exists('REQUEST_METHOD', $_SERVER);
}

if (isset($_GET) && is_array($_GET) && !empty($_GET))
{
	if (isset($_GET['cli']))
	{
		$isCli = $_GET['cli'] == 1;
	}
	elseif (isset($_GET['web']))
	{
		$isCli = $_GET['web'] != 1;
	}
}

// Route the application
if ($isCli)
{
	kickstart_application_cli();
}
else
{
	kickstart_application_web();
}
PK�!]A�{AiiMaster/Installers/angie.jsonnu&1i�{
    "angie": {
        "name": "ANGIE for Joomla! Sites",
        "package": "angie.jpa,angie-joomla.jpa",
        "language": "language-angie.jpa,language-joomla.jpa",
        "installerroot": "installation",
        "sqlroot": "installation\/sql",
        "databasesini": "1",
        "readme": "1",
        "extrainfo": "1",
        "password": "1"
    }
}PK�!]k��66Master/Installers/index.htmlnu&1i�<html><head><title></title></head><body></body></html>PK�!]y~�=hhMaster/Installers/none.ininu&1i�[none]
name="No Installer"
package=""
installerroot=""
sqlroot="sql"
databasesini=0
readme=0
extrainfo=0PK�!])���cqcq"Master/Installers/angie-joomla.jpanu&1i�JPA�w�lJPF6!installation/platform/defines.php�Y��=PAn�0��s3A�Dp��Ԛ6B�4�����,��[�A�u���ٗW��$����պ����U 8�8���S6�hj!Og�d�.�!�@
�+���X�}�@vF��1ļ�Ȋ��(h�0i�թ�x��F2���t��樔��'|ă��Xs��#{d���yi%��������zrB�\G坼��C�����\\Β������Œ�t4�n�n�:��ݞ�e�=T��`�Oc~�`�"�JPF:%installation/platform/js/setup.min.jsKV���UMo�0��W,ᒨ�[�l-�U�*u驪�c�&��mlgK���I�~��Tp��73o&�Kb&\��sg�X�������k�/��A�q:S�m���BRǕ�8���A�0Z-�	����	f�9H�~`�9�N��B�"�	9�<��OD1�gSz� 2uٔG+�	N��CH%!�d])1n� e��:�A"}�TI�e�΅����м�r��+�uu���k��˥�6�~+��A�䗺�8�����_X�3r�)���-j��Kd�$�.��Wo�(�TX��������,����ћ�
��m������5�~���ܥ�z����v��^��^x��}�6��pzFJ0�J00�� I"��x蒈�!����
6��b�	6����#��6��cFd
}�#M
��gxM���ηlk�̻Ec[���CLg'ъb2M��j8:䄋���5:�&�>)�^�2���q��$)j�#p��u'��^�q�ڕ9���[m��=C[Ӽk'��(�g[���柳�����Z�us����g.������WGG���'V��g�5�����r.�w�r�JPF9$installation/platform/js/main.min.js&&������WV(�/-JN�M,(��K
��M�����b���JPFA,installation/platform/models/jconfig/j15.php>p���V�o�6���+0 [�Xv�<t���{mS�C�{2(�$��H����C���QR,;�[7�����}��/_WE5�vw�����7��������
/��XYyȌ�X$wu�&��G�E�1����;�X�I'�Ӟrc�!���"G�%Zb��ʼ�p���s��|�b�`~��2)�����8S�ZQ��ʧ���	j�/��5Z��C�:�=Z�q=�
IQ����4!'�.O��d�N'���.����p����S�	��hW��A��:�ڄ�d�LI����w���W%:��"�m!]����R{�B'8{[�^������܅~��8c�l脍iQ�o�)�x�s4�L��L6���v�����d-
�)U�����,p���\$M�S�3�E���s��i�*$�l�?*��M���P��@_+ƅ�)���
i����5z�%�Fj��D��������
��e_�V4uƦC�G�9h�pڧa�zW3���>�Z'��P$�:���֊�S�F�4o�M��AK�1�\��Ő����|���;T�_�?��c���ik�s�(SΔH�:��X䶨��Y=F��ؕ��X>�C�UU[Ō���(b��S����P�s�l��\�l��|���Mg�c�u���LG�֘Q;��m�/ْ/&D��cc�M����m�k˳	��V˂3���!j.j��sJ5�@Hj��g�C��Ǚ�����v$d��6%+1���\/����Iei4�ގ�Rj.c��H'�-p�M:�ҹYF�R��l�uq��^�y�&��j��Z����$�R�Om�N�߆Y��̚�I�����>@��ӣ����RG=uC��)Aۅe�X;2}��>wm̧�v�x�	��Q�����o��)�(o��3��;�X��X+���8G���*��^�s�.�b�
r��
ՊDx��*�tm�mS҆�����P�k��rx6�ע/��j[�V�v�ۜc*�yĪ��ϯ���Ӯ����v�������^��3�t�.�b~����¡X"��l~��~5�JPFA,installation/platform/models/jconfig/j30.phpQ	���U�n�6}�~Mk4��yh7u�¹��@��I�R�C�V
�{���]]Z�i��9�×�\�V���J��ˋ�����A:��@�� ��@/2�v�ҫJ�	�A�E֊�@&�/����'4�>�����!ؠ��a
]�uYq�����~���t�ٞ�K�*4���x����$���!��2Z��x���gq�4�c�1!>�
x�y�� 8%�	x>��V���xw�9G[�Rܯ�r��o~� ϚP�S��ɋ1�HEb�������c��̕}�1���搓J�숒����F�.���A�u��ٔҶ��R�U�9��Ӑ�XKm�Y
����Ry6G��B�E�)s�u]�uK=� k��vhj�B*�&gd�bQm!���q2�e�\���A۶V����S}�eюL
�<�����Fh�]�й��� %2�����}��Ƌw���_�O��#��R�2�gj3�*�0mAĝ��tv8'��{�E��~���o�X�5�˳
�=O����������y�kms�[�p�B��Ta�F�ow�����Z^������=�0򷐷��������v3�n�EQ2��a�8�q��į);;u�̜,o�®?Z��}j����ǾT��?�g�o���xt�;�����Z"���?;����ޚ�%�g�*X�̼H~
Hp�M�H�2*!��	A���9�8�6��-�|���rM���l��Y�c���Øl�5Šy��{�H�	e�&{tlI�m8��\�q�Ff:�dco�\�R�4:���z6�������t�0c�gS���xX���.	L����5V+́LS�,胋�JPFA,installation/platform/models/jconfig/j25.php]	���V�n�6}�~Mj4��yh7u��m�A��@Q#�^�Ò�m��w(i]]آO+�3�p��굩�&99ٰvvy�������9�Yp-�5s�J�Y���\Zø��DŽ�`y��9g?��8"Q!�n~4��
#�JB�	4��U����S��~����~��.��Qq��m�zP��`���/����]�����
�+��͉`�G��q=��QH��t8�l9q���9�RV�~��~�e��vʞ�|�³��UK7�H���^��K�As-`�*�,��}P��9�q�e�"+��v�$\�y��'���El��}$bj���3/u׈��󙒍�`?�A<���m���G3E\S��L�Φs)9�jt��)�:�K�Pn�K��׈�Pʻ�_�����@�gaq���!�gi�X�6�`В�}��L��i�
T�y�$�^��٢�c�K�.�nK
�M�S�ML�>@G1��B�
�F�p�HM?/��a��&�����Ӹ��CƗ�E��C%��h�A�OW�k����~P��Li�Y�����t.	�I�l�r��#:�l�᭯WA"�[�c�<���S��b���6fm�b�N6�_����:�W��+/��ٝ���o�����UL��~Hy�d�H�aԤs��F]tk�f�-9זcϨVK�8�VnE��?�,�q
+*�!�S�7&�+Y��d��"u�T�{���^��{�[�'7��������*��A±�y��,و	Y�a�,]�d,����L؄CզQS4b#��т�����PR(�
�����t���tF�.�lՙ���bO�f<3o�E�W��1�C0rJPFA,installation/platform/models/jconfig/j40.phphM���X�n�6}�����5�8M�\��q�q� k-�B���ĘU������P�]��M�A����3�}�ʫ�����eG��_>e��,f���X���d&֢�,U�E<��+�u��O`X��[HX԰����ǝE�,GJ*\����B�g�d�X��F�,��x���������6;q�$7�=�
5FU��ʰ�r��8_R�P��=�4��]!�N:�hC纽��H�q�t'�5{���X���v�vnMwق±kE�2�U��~Ti*E	쐥\�?B������,�
-�N�e��YpQZ(y��A��w8n1�!�p�f<Cf�*'Ao�D�J�&/5�lD�6�.h7%/�+�
����t�N	W)kqw��H���cp�"����W6�9��ިƆR�"p�Gx��O���2�rw�[Q����D��ܩ��+�桺s)]p)֚�'�����b.��l�����K',������tw�Aw�Ӑ1]�zz���T:��?}i܏�W�ޜ��rUR�|T&��l��LW3V�A�ЯKas��A�rr�I�$2FbF������������fHb7����?,�|y/-�nm?���9���Y���<����.(��
�%f �*?c(
�QR!�u/��Qy�Z+j���$��Z�	r�U�%�����7����Kщ�ٴ��s���Wx����T������{W-t���]^��J຿Lj�p��qڠo|��=w�=�Jy��,E�'�� gH.�4�?��^���*w�K-�󊵪S��,�l�'&�),JO2���U2t���~���=��R��'�S\Q�3�Ha���b�I[��a��jB�m�u���bq�ޞ��5al�^NU	{l����G�c8�X�]t5�o�?�jZ�4�PNb�+��J�Q@$�;���!��?K�-���C��@2։�6B�p�+���f��)�
�9�����Ri�ϰ���^����r�d���F,	��I�%5z�M'M'TU�� ����㵖���)R�����m�Yz þ���.�m�']��h+r�]v֞�/�`D89��I����Y]�lS!�4�B�J�번9�W�C��S�o0�����`>��c���6ft�����o��?����G�5��+6v@�7\H?��u�����q?Ԇ��ή��
��aד����[4M=���)T7��Z���!(��͚�FOM$���0.l���G�E�տ�r%�9z���!h�vdR�X���t9����1Q�m��D;n�ea�3��,�S�b���q�u��y6$��$V�����Y;̀��n��7H�o��7�ݵ���������7����k_C�"�����n��GDu��E� OSO�!��_����˂%B���{oZ��ڟ�A@:*��R�H�����Z���_�}l�H���54�}�㽮EZ�	+=�H�^�{0{�ݷ�@���5ZEj<�|��
e�Ը�#����mϜ�W\S���hE��S�ץ�G��u��d��g�?�7�"��~`ޗ(���
�j�ԅ�&w����n�;s`�[h�%L��6~�H}��HZN^�JPFA,installation/platform/models/joomlasetup.phpy%�����=iwǑ��_�r� �C�S�e�"-&:�������&9&0�D2�����p����\(+٬߳M�tWUwWW�������o~��~��|r�6��Vy\h��H����D��,��2��(��g*����G��q��BGjt�n���;i�D�� ����|;���V
\Ś����}_]���ww�������}�����t��}����Y:����28^���c����7��:�Y8Q���P���G��8�'=C��2輵�>��Hׇ�yT��μ?���G�2Nt�	�<:�� �"�(֝�3�4�N�����qv?+>�^�a9��Fz�
����w�N"��wa�����׈�a���q|7����5\�5��0S�l��Ͳ�#�Jm����/m��ۦ�y���U؁��/Vl�K�շqr���x���%/��<Dž����_���]_�IX��ϲp�j�����o�iR�� ��7�����7W��?�=<���p}�ֶ�c�*���Ɉ�q�!0�_c��	
&[R�C~ߩ��u�s_��6�d\s���GMd��p�Y�=��&U��D;`55ly
�<j��y�cR�i:�i�2��<������(q����)̦J©�;|�Y�H 	�i��S���u�@
\l�8M@6���j��`/��,Z	u�s��nYa	�a!2µ�i7��#�A�A~6�q�h�
x'�	jkg���e�N:k|��7�l@M=s��:[��uH�R�������4�������񫕠����X��Κ����}'�US_x�/�\?���'��9�o|�:"*��t:3�YY�tI�좌��A�ɩ��&Z�ʽ�Q��n����t%N�,fC�i�,�Sq1���Q{j��d��u�Չ+f�ty�Y�5�ŧ��A�������ڀ�.i�Q�Y!K!B�)	6�@`��K�B]� ����1�9Ĉw���k�w%b�_Zi��ƴ��5��[�)-ՊPqeVkJQ�F�)m�h�jgNj�a�U����m�2��o��b�1{EޓB��~�W/����ة�@�<��b��5���}��U�M�����}��{.?hzz�]S~c��ك{���\��Z
���_
�럯����A5\Y~G#�*=k�����n��]�v��p&��{,���	�pxcM�x9<l���J_�p%p��v)8j�
HX���q~��4�/��mgP����>��ԯ]:`2)���T��jg9J�!�ф�/�DD.���hQ�y��F�
��C�`�'W�u�S?l�B��^2R�O�q���/ݑ�MG)�9kV(��l�t�A�DR���>��7����:�8)��tǞ��q��]O��IzK�xZ�����SDpQg
b�pI����Fp�ݐ�ѼIk�v�)I�$�螬�.I� \Cv0,��te��	[=T��V@�EO#��V1�|� D`���)0����'8��T��j����\h�o�F��=�rcW��c���PS���g@Pˆ�!����lIƹ�Z�Y�_R���sȃ���X�H�o�=�=kx�tr��NOE?�<��V��|�A�)�((��k"��*,�D�
.YݢcG	7�U��w���屔��t:��`�=���mH������;��sd�W��E�Hi����5>}yZb��P����@�xvY�a_���NΏT_[5Zk�?��g�b86`-DVCK�9�{�n��^��0~��h'ҳ
��ۇ]��#�6Ğ^�O,�R?���+������^�ݲ~b}�][N���]�<4��=.f{{��$M"�),&S�S�2��78�Ãw�o�''��&͍E#��k��;����,ƣ��RM�R�:M���_=}*���􀣻��]�6�ݸ����qO�U���|2�Z�
>�uNrm �{/�l��[ohNO�M^�W��e=���]��@%x���8O��Of�\��79����Y�w�3�A�E�z�rt��j��s���oi~���w�jO��p�COg�} +$���<ɯ�ˢÛ��G�^u�я`�t��RqD�ݓ�ؘ\�4��4
��g�yKe~�#"�)�n���ή��w�0���g�	ϧ�!�̒���^��M�
��V���!FT���c85�(��q����)),c@e�t�N�F����L��Œ���a#�u`�4�A	Ö%�����a�[f��t0ܤ�G�:\��;|(��}{N�R����Pߤ�-i����fS•�SY1�����f;�0���nmޗL���$�[�݂�������F�	5�_���T ��m�_�����^z8��t�Nԭ2�	�4��v��G���)50�H���p2���>d吐c��l}ur)���sܸ�o���
n�{VS8&��![��u�͖��Yl�wY\V
XG�S4��H����L��B�f����a
z2�prޣ��G�U*灇b_�E1����������e���6�ٹg�L�XG��:M��^$�i�^L��|)�������㶩�Ǟ��5L�%{K�{o @�QRę�cZ
ħ	�6+�$@Oȸ�ZR��ϚF�B_��	=ә�����_����r�;�^���)�.�d�"���0��c�߂�|\�F-X_�W%�/^��9����=#�;0#�]�9�\���]O��p���q
������u�pG�|$�&�G��8=�ʬy�Gξ4q���'��ek�@�N��H�C|���J�K��):
1E�G���)��fWnSKkd�}��4FQ=����?��H���Q����=E�`��e3�2�m��,|��<�3a͕�'D��u\��e�F�>�u�v{�h��1r�(�T� I73=������l�����>,D�7\����h�I�X:����~^G`Q�9�"���T@����x4^��\A�H�Gi�ј�p�Y$e	j�H�B�/�i�h��ē�0�ܙ�])
��	�Fn�d^��bd��E{Ͷ�1H����|���kj,��$�x�p����ܮ!4��!z�<0~d)h��zC�œ��4�������n�M����N����RO��'��w<e��9���4T�ɛ����W�'o�ЙBy�[��?����.=��މtnTQ��ͯ�
�@�jO�	'bL9g�NJd~3�o�|Hn1�a6��먼@���3L�ښylA⹋��/��L���5��QvJ	d��nP2�C�Y��C�QI�E���)έĝoB��Ư*jŨ�3#�:]3	��H�x�qX��8'��"��*K糼����=�iD����;�泻5�	��C
~J`����̈́���v/5��fy
naF�k���a]�g���qzݑ��5��j�H�
('�=��U!Cx�8�Jw�&b�����1�ߎ��#sF��Yx�=���6��P�t�����$2TYj�{ќ-�ׂY#q�:/�Ì��+����<(dQ���[�iza���ƕ���J��
$��31(�q���Q�'G+��p�]���7���s�%" �jeLj��!��i8keRnay��M�459(��.��
�A;�noq�lx{�N�Ӥ��mb$i���6��a�s��]��R�c�u��gf�8r����VS�?�ȧ2�8����*Ϭ���-#y|�m�v���Ud�W�<�*q�2o)�
&���Έ��\�g`�c��;۔�C�E(h`}��ـ�pv~�#�Ib�E��0����*
��P&�#4�MW,R#���-�Gv�#z)��إ:QQ�v�%�ձ��
�$I�fi��؈�8����Gr�S��`�οq-�3Z㢑6�p�&yO}s:�^��	>`���yb�/^V;�x8�;Q��6:��Y��7���O2\��1���U�VA��H�R�N��i����)��E�R�.bqWI/����Q��$�u8��$W,4���J�T����9���)�g�=���f�'�;��5cm>�`�.-a8|qr6�q�{%Dug9�C�mBF'Y�y<��]�0����5�;�*������v#��M)H�7ɬ�Z���[�D�3��B��r��r*�Ļǰ��Zs�no��#�MRqУ�h\�
P?��B���7�t��H��F� va~.Z{�-��2[��+�_></ܪ6��Y	$�(����L�S*�T�8�@�^M��SH�[�\�$6�L����f@|-��|�@c-��ߞ�ێ����4��#��	����X&R`��-ڈze5K�Om�U69���>��6�M$Eׯ�|Yi٩��
c�,[�x��F_��Փ}����S�M<���8j�U��H����j��p[	���4E����	�N��Kֆ�����W�Q�RG�=�Y\,�9߇����Mi�\��R��pw��l��8�R��R��l7����/���&��v�]��7����w�ã����`��۳�oߜ�>8?|)9�?���k'Q$�>%IZ��D+O�>����'��^����B{3mα�AQ���G�$EaipU�}��58�Inn]f�����yg���-�����/UT�T��RX��Q���
�&���=c��KW�ί�Փ�n�������Y�E�6��2̯���ї>c������I3��V��YC���$�Ғ����X���T����u�_�Ä�Q��	�b�0�6v5�oǹݵ�*H�9�)�ޔ���!MD���-&!k�_�9��&X4�:bt���W���m�ȭ�� _�*1L-i��sQ_�v�0�A�n�|ٵ��sI�A�U�mV齅7�8(�Uzʈ��w�g==��U�Cq�����2�������_a�[]�=�yK�<hp���5v�FC?'�82�]���浠�N�煑u����7��D'W��[ᰯ�Gc0O���o&�$��������;|qt��˓?����7oO�t68��������}��˯~���� Ji��L���	�)�^�.�����ԙ�XFG�j0=�cJ�I@�b�*Lw�J��#rjMPR�����h��R���Q+��q��um8���m5�ud��*g��q0<wC���R��-�Kg�% �2B��^
6J���f��oj���%�:�A��1
��O� B�4�C��!�Q��*
�M��+�PJ�EP$Z1�j��(S����]���/��a��6f���b4n�~[�p�2�$��;�������3���F�M-��x����7�nW��"���%D���֞_��6_
I5�+ր�J��#�b�I�4�h�T�C����A5��,���|�Gq>�4ey��Ԋ�ʪ���i1�q,�q6~�ۙ��,E!��>����$7����w3�?~���0��ϱڄ�v5L�o�~��`�s���y�(gO䎮&�Ҵ�٧���rpG؛ ���-�U�T��d*Ri˂��K�:�jX���&V(����R�K;XSG�Z��G-V�,�V��X��Ɓ5�-`������ph�ſ2F��P�_���2� �E;��)t(��J��f��ƫ��c������&Ot@�ʍ!<�C�Ho���v�+��q�������N0��Ğ1X��`�PUx��� L�XN@/��L¥EY��K]��-E�(�^N�VBg����D��6�pH��6�r�i������u߲�X�e�vl��:�I���Q\������og�63
�%L�y���\�!�H�8_�3�Ń���y ����ߔn�C���z�����#�L_qM2����t�"f��S/��]���o��}��c,@�a�>��H�乏��E(�1�ߢ�t~��lr�S�rJ��h,��s��)�x��G�R�%�=�a��\B��,��4U�%�s~[ n�h��K�i^YJs6o��8g�v[��Th���J��8�Wk�h�S�H����JN?�����M?���KM}��O.2m/1�74��q�"�Ҷ.Ds�ͥJ1���b�>�f��ZR"ּ+M��N�(��![�^��虽4���^w���lY-ٿ�^�d����kO�2#�dV00�P&S�E�
6���TX��6(���V�-��[\����/'F�7�dS�3�Z8�E��4	��ץ�K�[�	��&[�b۞�N��M]ZT൥��m�9c'�%_=*%������p/NpP��q�Y'2Z���$�{�*x[�׍g�֥خ�H��)	��Zi�~�96�1-���Ot�G}QA�e	E�򔢚�xfUG�Д"�mN�?%'�[I�Y_�dĪ��P�U��.��ۤ���K�~�3�<�z�O��e�Oy�}���O�Y�S��O�i'j��-�C	�,y:�Pn�n�&ѥX��3*�%O���2�E"��I�1�������~L�M�b��[��
V[F�k�ˆ;�]��`W�r/�"V)���s��j>�f�T�3�y<\�>-/z��C���9��� ��27�u���	��^� h��
� �u��ٽ�ʈs*CWExz���e�I�K�P��E�b�IU��.�
�	-�)�/�����y"��Jq�ƃɻ*,����F�� Im梹�$�U���>.k@�r+�߷��zŝ��M�q�wJE{�[�3M*������}��4��vJ>9[��s-�q��u?V��Po��
��ie�"JCkaek���*�Z���}�F�e�vouTU��>61��J�Aa�����} -��*R��pک=qͮ�{=f�����s���1�\�{���7��r��y��K���pj�M�ܪ�،��rYb�0�����[7�4c&�X����4�<5|�1�v���p'"Uxso�8��\�9X!oXFjT�TN`u�</�I:,���5~��t�"�h�#�H�3�DZ�]W�2�aF���������Yu>{j>cW���\_2����P7��Mcݶ�a��i�{Da���e��PvMɑ��r�QsIO��C;�2�U�G��ǫw�ù�B�O���1Kӊ��2�0g˞C��?_�K}Wda�H]�ʙ�ev�]��)�3�v��t��^|�=�t�`�۞��K�VЧF

vvK-䞄UI� m��Su{{�� x��}>CqWԞ�Zv�:]�V���u���7d��-�Ak��pՍ�)��
��m��t�َ߿k��xṱU	�V� /�L���$Ln�I�4��]�K4�.�Ŧ
_gᥢ��M.�����*؜����5���
��'\�,���{�֤�h=����!����+�Mr�K�x�	\Uq��,���L	\�qK��h�)�k���7N�4��En����c�uZj֮�>�!ʼn��i�LJ
�䲙E��� �2J�R�9�z�C�2�P��4�t����#���f���螿)��e"�A!dmz�(�u���9��'\8��^jU�Ђ}��f�E,��1��"��S���,��T[=�E�\۠���j�R.M�|�V����+b�a�Xf�����V��囨lC�W��uR
�B;�����Hŭ��=���9�Ⱉ���(G����b�E�So�f���_����Ŵz]
�
���F[�Dc�hp��i�_N,�J��j��g+�˧�z�g��9�܇w{{�>�f[A��$��x_�^F���9���n���nl�Հ���M�uj�[eӯ�i&�~J�3c�Z«4��Bt���Z7�d�8D���\�ׅuLx�ǝ�y;��/(� ��?���,�n��t��)�}�>i��&������c�t�_���X�m�a��Hj�(0�k��
����$�����Z�ꯞ�{;@A OL�_	5�o鉋s���~<�}^�.x~/# �k�B"ǒ[�ʋ`��(/�p#3�*����,�-���_Ž�[<U�\��5H�LS܍�t�K�gm=0�J���D,X�Z�:��}5���M/"3oV�����,�X��q�����@Fv?度�
g�����ՔĖ���e���h�RI��R�„%�yjc�v�"
�˟��:e8g�~Ww�Ö�"���W�n�Dy����B�-�B��J�&�ay�q�w�$Fi�o�=�V�0�;�9�����&��@��E�Wl���x��Vj�ל_.�6��ן=�n�rl�S���oԵ�����+��I�7��TuK�.P�B���hŏL?�^:�l78�Jo���1P써��s4��Id��>.}�l��g}�5�[�s�7�La#�xL�k��ќJ�i:���9���[y6�0�-��y��-�L�N�'���q\ḘS��E�a�J�Gۢ�fW�/%�&EV��np3-@���{Ő�&���������@ì��$e`di�ih�̧�9�#����D� Sp��,�4���}	W<!��)��-v�I�����'��%��y`���T@5��O��?�}���1oҹ��ƣq���|C�*�p%���� ��?xe�o[6-�]i��O��͝�Ǭ�{8@��pz*[[����>t\!I\���7/��bN7i8��_��m��f��a}����BŔ�>T���:��@�'�{���[����X�͹$����t)�
߾{s�M7��meXfނn=ǦH��;L�I�Z�۞����+K�?/ҷz�ʺ����D��4�oq����?���(��}c����|{�2��%O��]]�f�v��{enJ�
�VJZ��Ӏ�r���+`e���Ʉ5�+�[=œԭ�Z��!��b�9*�:yp�&yH1��w\���{CB��o��;��k�i��wLc�^���Z���ǪKՒ5f9�%o�	�u�vXs�X����\Z�\y�qW�|R��ܾl��D�n��5d�y��I�3�Y���^�.Y�UǪG��Ze��=��7�V)�]� 6�֯#�}�E��xR�OR]r|gn�m�F�:��:��?�7��G�ԿN��}.����Jpy"��T��.��c������ݩ�%
=o}�����
QS�3z�(:��5~�vԶU���S���p4%��#�9~U�[5�^�{��]�l'h.׆���9����[=H�M�Ŋ��=��n��_mqR�Oɷ,�#R���z�Z>o%Դ�C�Ğ���H�f4!n'��h̯~�+�����A9��{�
g��*��n9��lx_?�.��&o�'W�x�Z�"��ӉT���3
=�������t�v�A��PJ5_��15����!fj�6����XU�y�<�O���m�P.�_*�+�볮0_�)C�LH�Gռ
J8Z��ol-\|��TbB<��暠m\N��JPF@+installation/platform/models/joomlamain.php Q���Wms�8��B����IL��ws�!���v��7}�0(5�+�I��V�M1�i{�����V���>�Z��*���ʓ'E�X�F�Nv�`
D0	�����>.g�$〓!u?E!�ܝ�K��@%��pN�OCJ���?JW�І2s�.�!(0a��� �s6�JR[|������;{�{����i�QAޘ��E�RIm��H���P��3�8�H/�i%�����k��K:��p�X���0*���^�ZFY	���/�E!b)��`�i�<ڦ�'�E�?ZެRj��O��b\@t>'2 #��J"1Ա�GD�*��+�B�G����;�EK�bU�8HrH�=�k J\3|:\�z���jV�V�Y��I��;��`��D*ć٘��y�0!Ei������]/��.�ž>]غ�$hև+r�@�B�h����rҞ\����	X���n�|	�5659A�ռDB3di�I}I�{i��&�<0�[ .�������j
>��ޜ�(���o�f��NX�G�j轖58��1�f!��%�M���^��<����1����!+dSB�$�o�%P"��� ����%�"!"W
��Mg�%SI�e -����s�8��K�O��zs��ӢGh'�>v��|M��G�DUZ)��"[�n����u�ꎓ�P�KK2��ںH£����ɍ�Ě��d�q�YH9�����o�b��'o�dk���޵a㙹g	G
�%�Y������ͧ+�7��q/��R^�L�GJ����!xF�p��Xl�ϟ��3_�KF�jv�V����9h�9���f��X{�jy;��F��/
�l�w�p����y���T��>rXkStV�/(�����nAV��۵F��=9IA���|�h�脹��(� �I��Uxx{�8��_��OC���V�j��r��]ܙx�����k�;��F��t�3�t�n����Ob����	���{D���Zv=V�W�T�[b�3�ߺ��5�|�3b̆Bru�ܨ�@;���$�AP���e��~b���3��hQ��{|�����oDU��D5|�$ΩF>����~�7*���G��站h�
��4k���N�I
��8Q��=�7{�ov]콭�u|gJ���t2��W���j���U=��i���]�o*�W�g=�p�2T�<�s�C��h�tKy�B����#L���k$FK���Eĥ#��b0L���٘M�����#&�+ΰ�x�N�L�}�-�d|9XF|x'�3~ z�n��8�[Gp��N��6YMx�,n$�Z���<�bZ/���p��g2�gN�8�����3������Tsl���g\J�)
��+�Ekt�l���:�(�|؎�v���w�v>t#&B���<��A ��TS̉O�j�n��"�/׮e4�,`7F^�����~�,�k8�g\�wz����z��{6�����B�0��0�AmA`�n���u6��lq'2i$����������qOd^�_Y�!�k�[��AMU��p�
�/JPFK6installation/platform/models/serverconfig/htaccess.txtM����V�r�6}�WlF��H�si;M�Le��Ց%W�GV��D�$��d��w$e����43������V��*��0��-9�ߔ�#F��J2-���l~�:ux���4���'$\�H���p¬�ڴ�E��
hn�^�E���4.�QG\r�"8M�z��b%�(
C�_�p�n�돼����j�B��0��a|�Agpr���^���p6�@�x0y0��t��y�*�qr���;H,�3�PE�Z���'�م�e�KJ��G<6�6�b�-
���L5s�eׂ���H��,�	��F- V���k-,o�<�X��lŁE�� �9GR
�0� Cna�|,���bB�q�
��@F�C&�B.	cl��B�|�qW��*�\�&�f�8���R<W&�k�t��c.]�
k�� �*�R��8�U$�j��
d#R,�BǢ�s��R[2ˍ-B8V�#T������Z�����>d�1MOrD�RY�+v��NB���(�+�QswfX��،�te���K�4�=�AR�$!��(yt|���-f�6W+L�Xl�m��t��qIN��B�!r�R�0��t�D<�d�taNls)���TK��h�$RšBD.q-��Ԯ� ��RH�pS.}G��T��ѡ�<H�N#O
���%J�'v�p
��(�f����Rn�B��ޕ����d��"�ruFy	����Uf��k7(&�^�$��P<;׿O����h<�n�o�~���k��u|�a:ΞLN�)��i�����%�w�������1���s���W�����w�J�������z��vo+�ó��`߯��U{s>�9ߙ�7n7?ͮ��n���M~�>�@U���C��_�/�=]]ܦZ›��p��\J6DK-Ƅ�K:ctm�d��P+4���#�[��a�3�=<��V&0=�����!���I�ƪ�1��D[�b�Q���jCKQ�8H�I%��(>��5���~l6�0z����T:q1��rt�w��TӲ2!6E�[+'L;�mjL&-�ʭC����_6z3XDlI�r�ԣf��{"K?!��sDҊ���"7�5�羾kH"�
���<NJ2�]��K��8J�C�l�<n��s8#��$����k_�<x�j�B��
�np��Ž�+A|�F�!�m��~ �&L������E{2>�����v�߶Sd]�?����J8Y�7w�J�8�][E�/�Y�A�!�~��{�U�%~#gT$A��w`��>��b�.&��-<���>�xL� ���ō��:��Y��?�od�O�~��ô���LE�� ]@b{���=����=����JPFM8installation/platform/models/serverconfig/web.config.txt%����Umo�0��_�Yk��A苦MKZQ
��v[�o2�5X2qd;����1�P��k�D~ξ��w�Ͻ�3���<��q���iy�ݪ�?��7��-�RA��w~�r�
%D�<\�sXN��qa�&�ؠ��T@���^
>��yȐA��[�$`䬶
��`	���܀�0��<���1#o��A�Iœo� ��G���eLT0B�`�[���#�\@�H���a};!��B"�#V<M�u�Ow��$��Ty��{��4[~������U te�:�gS��^�ku����ψto:�(R�g��e[��^N��AU�YG�X*��Ij����F��f�ά{�)�?��?{��'����hS��bF�Y�|�m�i�}�S݉cd��k���h�]!%Y��ʲ���ϊ��"3UV��0�xa�i�AY��u�L�����������S�بj�o�����z8F�(������� ���j�Q�)]W4�ɻe�X�U�`g�u�x,���=�ϟ���ܫ��ЊG�מp��9\g1C�q���JPFI4installation/platform/models/joomlaconfiguration.php&	W���Xmo7�l�
��R ����!�=%M�9���-��0�]�Z{�ܒ����g��7k���`kEg�Ùg����e9�޿?�����q N�J��)a�u�H��B��d�m�\&W�RH�,�keEb�t*�])5��U�(�j��5l�����J\d��]nLv�t�u�4J��<<|�Hg�R�Ҋ�b�=�X]�u���V6޻�u�Y�
K���,ުB�����}��V�ҾM��c���A�Y��Qt>��͛W�hLi�F�g�A����ԩ��z��׺Xd�*�ɩ"mK��Vud�J��J(p�K��Kp酈"��+�Nju�����D֙u�FÄ�AV#7��D�`�I�n͆��X�x��{өx-�\8�p)�*��Uj?��i��IK+E���U���0ܫ��0z�I'2$
ZeI
*��ͦ�n�ك�����A���r��2(�&"zFލl!F��+�9W�2�lG:l`� p�V���f��� �4�n�N����{-Ӟ�^K��y�
��vT�&׹���.��DZ�dn鋲��KB��!b�q�N+njU��hKw�p��Α֩�CO
�#պ����O1�5%��CNu��K�\1�T���C�4��y�#?W����08+���(I.j��oė�<�.ɱ���7||D>�w�Hg���3�(�dT<��6���OH`��(��y���^����~����i�ď9FOvu
;��kvq��p�5B��W-���z)o�����S6K���b�^�B!͑�k[��	Z:%�}��F�*A�~�r]�j�B�,���
�3Ke�kj��-+u����͐�ͱ\)
ۏ��"��w��"�U�ef'��w�G�Ng���N��uL��
�\MWԎ�������	}wCM��`�,�j�������<�����TY�'��V���ǰQ��pM�C�'��=x��E�̈+�ɻA^�:^�a�
��-+.��M�·�k���	ƹ��JK�����@p��~�|W�\��b�g��H�u
���9�8��)��b��1���J�2��N6��<H�SK��.��5xg�f+�:eK�[q���y��U��z���F���&y3��Y��y�4�	ؔJ֎�f���z.�{����t�J4��-rj�(��(�6��f��4l���u�s�s�/|!����op'�x����I��,XҐB���s]$ʧ{���25{h�?
JI �|��KS�e8��K�x	$��Z��|���	Cx�UՃf��~֭�ZRN=mo-�upg��Nf����%�&-.m��n�W)R����cO>!��J���Q.���5����ϩ��7ߚ��\�q���7��딺W��6W�h�"�7⚓����'heX�'(��,,�S��-2&õqtp��mW`I))"iA�϶�6�����!Vf!�иcNa�����MJ��6d�obL��6m������y�I�6@�+�A�K��ߖ:��V���rF���X!�g|�ފ>Q�+���0�z]��]�
�Ӏv%�Y5�̞{Xs�2-3"-���m��tJ=�N���c�GHE�&�[*�2����Egg����>&�G��@Hi��ُ�2��<��`!{*Fg�g�2Gc�O���-�I�Y1ޯ1g�G5�����p�)��)�įY��4�
�
t1�+��7Ҥ�?�
�F)���F!��[�*�I��_��D�/�ĸ�jg�`O���|9�|ul�p��Fw:w�2����h�L�jc�ȣ�I�Z������U�ё���^!�|C<�:�э��ՠ iv�>��Vm���8*D��ZVP���Q�\��^��nh�Q�[}4�	�.KM%^r�
�
�w��tL3\�\���S��{
�y?P�)��.Lխ��|s�!��4>��zS�� ���ur��r�Qg.�!���@�Ô�Uc�
�aau�9��ݤ1{�Is��\۫����h���r_�d	�ѫ������?t��]����խ��5V-	BYS�ԻC��.�!��6��Cu��w(f�H<s��ǫ��*�h�T�0�����'��`��=�;��I���T��su��}�hs��3�F�����O+
zM�j��5�ʾ��
F��pY��zw��F�@�F織�[����ܾ�F�v��w�g2^��8?�Q�/�΅37(��P��9�[���SP??�/�L��A�/���i���)�a����"X�z߁��t�w���7S��m������[�^�!'���2R��߹Iv�8n�`K��VWw�σJPFF1installation/platform/controllers/joomlasetup.phpx��e�OO1���Ɵ�K�d4�	Q��<�n;�6�N�"1~w��x��ͼ�}�E1
QV˗Fض�h!PLd2�U0>a��T����5G�P�d"���rOTK�.���͆-g����gU6d�1t�)��`�6a�7��`2ߍ&���Q-[���st��`9��z��>��F���e��%9
���Pg�x��^��J6��B��8��G�Z,feo��Pp/��"�.��]
l-�W�O+7�rg�J��?d&#��-ď�JPFE0installation/platform/controllers/joomlamain.phpj����S]k�0|��>씤)�ӵ�\�6��A�����XE2�J����8�Ѓ�[;;;3��~��:�p��d�VV8��iÜ�
,7�vPjsƗM
��J��7�0�@�D�3��!Tѝ�j�i�sUS�-����zcĢrp���x������������d��m��u#��a7���K
��z���p�

���̩�������@�$0�<��K����Y�0_�i�
�Y�"�9I��{�7Z9��D�C�dOL(�7�����f}=~�#�vD:�fNSȬ�U)M����I��Du+�l[X�k�Y/��+�Q����r�HwH�`�A8?�-Z�ړ�9���J�H��q�M���`v���53��']�lm�LKV��w���K�C�9��j��c�c��}H?�3�m�^�SH�分��'��;��gԞ�#C�A��
�Lw��]����F�� 閤�oۋ�RHTl��K>�>{�L�p��<
cE	�o�ᛰ�f{��:,;Jh֯+d:y}w$掐�]DW� k���$K�A��|-a�1QjY���/l?6��EWz>���Z&�{��V3T��e�4J[zQZ����d��Z��6�
JPFG2installation/platform/views/setup/tmpl/default.php�
I���\ks�H�l~E5;�S�q�kcg0Ȇ"�nͤ(!5H�P�ԍ�����z�$�Ȥǒ��>ι��R�o����?W�Ϩٿ�jHEc#�0�L	�Q3p|�f$@S�|X��L�y��6���50��:*�Y���dp1���Ԙc�����I������j�����?�ӓ�3�wL��E��6(�L�O�.����1K��:&�(o���	�b����)<@���#(����I.@�z�b��a��L��i�uS9�,�/*�U��G#@?2ۡ��
���'30���"�r�=�.�B�I<f@��ze�>h!\�^�1kG�E�IU(gY��xMN���x��;�J(����������^�r����F(�g�o�@e��ml�FȓZ���^�2*MP���{�3exq75†�\�-=S4q�g� ]?I����a�YS��\�quA��:�K��]C1�]� l�"$iz�c��n�P�a�֧Kƈ'\P�������q}��Rl�|z^�?==GF�dQ�-��S��L�J��5��-\��xUipx�g,�eU<���*2��.�	6U���&�e�'�U B�e5���FT�&��_�E�zU9hX�#�w�
�Ԙ2OU-#x�"ʞ]�>�s$b󢊈g�.����.�C�`�C3å����7��9����W�:"��6y�H�#���!�U�]C�3���K���
��T�<��@L�D;RU�h�	P���08&�vas�S���Id�
<�A�Q.1��QX
�]5xfE�@�1����'5E�Ɵ����lk���k��y�C��΀ZBL=l�ۛ��s���s�)vyn��ќ-ѣ�-z�{B�~�N��aku�\t�x��!��Oԯ"ǒ���L�
w	7$�Q�P =�l�ר4H��z$(Ma('�
�P�
�|�<�qQ����Z+/#��	^�[	����E(G�"�����"�}7T��o
�0�bo���j�Zb�$4���V)x��/���&�鯀�ˇ�@����5Α�$R"�����<.�r����P�.���ag8�G����K/�w���y��A���1�'#�o; ���t@kUTr}q�M@���^��7G�n�v�$�З�z9�������xOt�E����Z�FL��9ta0�^9;����ޕ���gm��o���vWo^�������zO�Q9L4N" ��}�~�xBi�N�y��&�VCj��4�1�.F���BԮ��?Q��5Q؎ɸ�ک�$���Ŀ5����ȁ��)'%-J;�{�Ҿ\�}�l�[$��"�`v)���qg/$����(�z�7�T~D�hUcsM����ŔƋ5�kTA9<B���/�'0�U�X9ܚS�N!�)a�ܰ�x�k�hp=��h�#!z *��9_�q`�IY��)F|��]����x�LZ�f���G;���=�h�C.����<-�$(Z�ճ"�ol�P �!p���m,�"���įTi��9]��Ӥe����B/"����u˦&>�&�P4N�y��u��ǥ�*��%�9w�吪<v�a=��D�b��|�<ۅ�Tt�%��&?�:�W��I2>I����=H�:��ڨ���R���E�|�..�����N|H\00��;�?���L��ݵ�b�H�	hA"�4�]��WwЇ�?�wdt���p�3\��`7�7T��_7��
����a�=��lu����{��2y�|�`<d��=���S,�U�'
�m����0�S�N$��O��"�l��sw-�X ����s,�V� �����vw��d���mע$�&��y��$��ͮ��2�u�hê����Í����L��izk��F,���Q���0M�����8|m�v��/�_�vO1;�f��*�s�����a��m޴*;gJ�&BYؒb�e</��d.�w-ff�~���f	�1UU����[NB��^��M|�aB8s��+�c�bK�ғT+w'�89���^�f�MƙM�%2%�իD���
�շ��Gڰ�li��u�'.�;���mC^�ao\�"�q�ٷ7��0jA����J��l���je�H�ýT)Z)�>��s7��
;�n��q��v��1iirWA7�Do�����C�O֫�'ޠ5���R�T�x��HL�Z����r-�;+��;�t��ؒ�K�<���m�x8�ۇ�d����$?�f���! �m�4�;�|�>�[贾?��`��mu�F�_%�g&���=�ы�\��̴e˃���Q�[���0�Q�
�XP����r D�����o�}��i9����^�OD��d��r������bd'����st	��%��6���R�5�U(FM0���Y�]�<��Sw�%�OCm�	:޼ӥRϒ2?��m:��.sV���Y����}wgYaUK����;�	�L�åaG�"�M�A�v�����={�t��N��%�GP3w��Q�F��I�H�+�"��$��B���1^�څJ��뼆��w�*�Q�u�fp��+ZX�%f�������>�`�K3��kh�P�볳���Ԝk<D�Q4�Z[70(��v�$����wn�Y�x(�᱓6��E����>?�-��|����c��{z�:%���9:=�mz/@�P��&O7���q9�O�vZ2&�rt��t�|���'�l[��8ѡpBmPp��i�忹�y{!>n5�i��|��l�L�V�_���dt1����t�Xk�ʄP�N^�HnT2�����\d�[.���gt�+����sE����@/��@�6��x�,:�x���Ų"wǗ�]�._�j�y��0�ɯ5�ۀ_8.��p0�!k���܊^��9��~X����g��2��*�7����A�sҹ2!k��,:��;��k�W7�/:0V�s�˓‡_�ǂW{g,��o����vw{D�+!��h�%r�F+��U\�X��|�$��j�]��!X��4 Or�ß�5x�p�uX�vx!��n�L�@T^=�~�f��A��������� sv*˦�ޞ�@�ל�e7��}�;�)���d�C|V!"і:̠I���N�N����JƘ![��?+9x^1c*��pY�Z`�d^EF�B4
�8�%����9�t��FnD*'V�1�n�g���!D��c�P���$P%bs�zr6EJ��/:Hn����Ք,��5<��ɱ�}Y�pr�*�1�ſ�����h��#�$H��jD߼Ye��W���
��y �&�>���R�d�/�L�goo��碒�>���L�{��H�߲�9G�횜噰lό����
ɚNF�װ,��lLs�[a�	[�GH���@��L�=2���}Z�xK��?$�I |U�?JPFD/installation/platform/views/setup/view.html.phpa1���X{o�H�>�\զ�K�tR�%)I����8)Bh����Z�]��ov��&����HA���7����4��wUx��Uˁ=��)H�(*D1�t����!q�N# �ٌJp%�z0\@�+�Cg	E�+=pԡ�|�p��)�5k.���}�ٓ����?|�:�y@$|އ4h!yħ�p��h+��
�KC��_u�ኆT���!n@;ٜQ!�_v]
���ժGG,��m
��a�4�Ǩ]�X��h�������y�*�~S4�pY/U��V0��iF�� �R+Q�}G*��@��c��"��C΋�>��w]$UbJ�jE�JP�c��a��1LC�
h``}�>�T���Ԑg�[^�"�K��M����c�@nօP��*�X�m{=�Vo-Hz��Q����A6A'|F�~�Bvi��<�7�Eq�#uQ'oũ6U�h�{����7��Ʉ�!WM,�@���	c,�G���BR���`!1�,SӼCh�J�dFA!����\�z�6А�Bk�C������1���<�frB�믃�'�u(�5�i��xxF�y�k�B�V�`�Vv����I��פAD�mI(K��<$M�RL�BT�H��흌���/I?�v������22��q�=��$ q�ؙ�cp%y^�eZQ��c���|'�Y
a/Yf:��t#9�ˀ~cRI��h�ޅ�{x�6m��8���v
���ё���4�5U���4�d�T����q0^��,xF]0y�k��eu
�A/�=��l\��y]�&y�ˤ��L��p���L,�%=	*�m�+�,��,k�,zL�ұ�>�Ԋс�=��;h��=��=��\����̓�mv[�֠鴻���ORҀt��1�qq�lt.���I�5�k����2��1��^G�7�V�5�Զ4���R �=�w_�q<�ia�L)��M99�V�����-X��V�l&%6���'+Gj�k�sL�YQ��d�S!h�����9��i�a����5�Аz]�=�˒y߽hܽL&佪���9)�/O���n�q�4���N�WZe��o���9K~X�/��W�ͻn��{�xQ��������W�t�Lh<n1���@���H�(��t�s��\E�vȧ��~���7���cA�:�奼V�
,��&�WJE<'��E�exkq	�U��yI��кN̜�Y���O$�G���d�!f��5�Ո"�gf�c�խ�<9�;,�/R�K#R�IY��]�Uq{^��t�D;C�`F�a{�0��a�]�5��>x�T�GOlK[bV��X�F�)Btz��[%1�#��$�`+d�DhV����M��� �ϔ
�ɐXVݝ���$U�Ey�S�tb��sv}!l��rZ��z�_�uZ��^-ݟ��
��{��]syJð�e���w��]"���,�(��K��{ie�/Ͻxg��8���ĕS���&b�L���1U�g�M���(�g��තq��Y������n��E�JIӯ	V6*���h�3D
6��5�
��0F��������H�ϫ5���D�L�1`c�X����N���\�·�8�^9�cu�,��g���.=W�JPFG2installation/platform/views/finalise/view.html.php-���eS�N1}��b��EI��h�5����d]�������;�
7)�3s�̙3�����>�}�'W�c�c�tD���2jg!(��s�a&�S]���K�<ʈ�6�?!�$���x�L�qԣi󣢬\ SL�j��p�*;���G0ѪtF��Èm��\m\���w�H\F+��Ὶ��+�襁�zF	��%��C3�Qh$Cx*>`����X�7���s�5�B��NS$!@��qu��4��p��i��u��ݼ�*���9�yx/��0�}�b�C�L9)��w&��j�ウƑS�3�(2��4�b���]����h���-����5����9a{�رd}�W���D�j�\���g-*���-pݧ����Js3�9���%��q�׶=����r�YF�r�SbK��m*zw:{Ϯ@C�v��5�M$m��m4�nEf��A���FZ���iޅA[�� �TӒhK�7�6Z�������D��k����/>¨Oǒ��Kz/7"�~�z�4^j�D@��H3o}c�-D_#Ŷl�JPFC.installation/platform/views/main/tmpl/init.php/W���Rao�0����c %���A�u˶j��V0:��P�ڗ�Ե�����$�	�@B�����޻�|rV�ED"8�tz=�Y��G�輱�K��q+���`|Y�,��p�̣���%��nB���;�L�h΋�e��%�5n��������_	o�:�פ���T��(��
WA�֙”�8�c/j,%9jW�_O��5Z��M�
�k������J*,`��4�̀�5�i���̈́I
�����#3�Q$�<�/ҸUa�I�8�0m��@&R��᪨(��y,��0Xh�6���UQZ�����)�l6mV���fE���
���.Jk��gcV��s�RO��LZ��À��͟�!G��n�4e�DM��H�/��[x�yN����z&D�8���ZC��
g�(:�^��';?,~��o�!�O�\���:e����IUZ�D{
Dmi:ï�ߟ'�$M���v��nt;�
g6�0�[�50
ȃ;j!���+Bn��!KKȋN8�(����ӂiT�Q)-����O
�_#�C��'���Gt�ԙ�w�M�0v��PK�!]A�{Aii#Master/Installers/angie-joomla.jsonnu&1i�{
    "angie": {
        "name": "ANGIE for Joomla! Sites",
        "package": "angie.jpa,angie-joomla.jpa",
        "language": "language-angie.jpa,language-joomla.jpa",
        "installerroot": "installation",
        "sqlroot": "installation\/sql",
        "databasesini": "1",
        "readme": "1",
        "extrainfo": "1",
        "password": "1"
    }
}PK�!]չQ��Master/Installers/none.jsonnu&1i�{
  "none": {
    "name": "No Installer",
    "package": "",
    "installerroot": "",
    "sqlroot": "sql",
    "databasesini": 0,
    "readme": 0,
    "extrainfo": 0
  }
}PK�!]kX0q��Master/Installers/web.confignu&1i�<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <system.web>
        <authorization>
            <deny users="*"/>
        </authorization>
    </system.web>
</configuration>PK�!]����Master/Installers/.htaccessnu&1i�<IfModule !mod_authz_core.c>
Order deny,allow
Deny from all
</IfModule>
<IfModule mod_authz_core.c>
  <RequireAll>
    Require all denied
  </RequireAll>
</IfModule>PK�!]蕿���Master/Installers/angie.ininu&1i�[angie]
name="ANGIE for Joomla! Sites"
package="angie.jpa,angie-joomla.jpa"
language="language-angie.jpa,language-joomla.jpa"
installerroot="installation"
sqlroot="installation/sql"
databasesini=1
readme=1
extrainfo=1
password=1
PK�!]-c=:ppMaster/Stats/usagestats.phpnu&1i�<?php
/**
 * @package   Usagestats
 * @copyright Copyright (c)2014-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

class AkeebaUsagestats
{
	/**
	 * Unique identifier for the site, created from server variables
	 *
	 * @var string
	 */
	private $siteId;

	/**
	 * Associative array of data being sent
	 *
	 * @var array
	 */
	private $data = [];

	/**
	 * Remote url to upload the stats
	 *
	 * @var string
	 */
	private $remoteUrl = 'https://abrandnewsite.com/index.php';

	/**
	 * Set the unique, anonymous site identifier
	 *
	 * @param   string  $siteId  The site ID to set
	 *
	 * @return  void
	 */
	public function setSiteId($siteId)
	{
		$this->siteId = $siteId;
	}

	/**
	 * Sets the value of a collected variable. Use NULL to unset it.
	 *
	 * @param   string  $key    Variable name
	 * @param   string  $value  Variable value
	 */
	public function setValue($key, $value)
	{
		$this->data[$key] = $value;

		if (is_null($value))
		{
			unset($this->data[$key]);
		}
	}

	/**
	 * Uploads collected data to the remote server
	 *
	 * @param   bool  $useIframe  Should I create an iframe to upload data or should I use cURL/fopen?
	 *
	 * @return  string|bool  The HTML code if an iframe is requested or a boolean if we're using cURL/fopen
	 */
	public function sendInfo($useIframe = false)
	{
		// No site ID? Well, simply do nothing
		if (!$this->siteId)
		{
			return '';
		}

		// First of all let's add the siteId
		$this->setValue('sid', $this->siteId);

		// Then let's create the url
		$url = $this->remoteUrl . '?' . http_build_query($this->data);

		// Should I create an iframe?
		if ($useIframe)
		{
			return '<!-- Anonymous usage statistics collection for Akeeba software --><iframe style="display: none" src="' . $url . '"></iframe>';
		}

		// Do we have cURL installed?
		if (
			function_exists('curl_init')
			&& function_exists('curl_setopt')
			&& function_exists('curl_exec'))
		{
			$ch = curl_init($url);

			curl_setopt($ch, CURLOPT_TIMEOUT, 5);

			return curl_exec($ch);
		}

		// We do not have cURL. Let's try with fopen instead.
		return @fopen($url, 'r');
	}
}
PK�!]��ѡ��BackupPlatform/.htaccessnu&1i�<IfModule !mod_authz_core.c>
Order deny,allow
Deny from all
</IfModule>
<IfModule mod_authz_core.c>
  <RequireAll>
    Require all denied
  </RequireAll>
</IfModule>
PK�!]���2�t�t$BackupPlatform/Joomla3x/Platform.phpnu&1i�<?php
/**
 * @package   akeebabackup
 * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

namespace Akeeba\Engine\Platform;

// Protection against direct access
defined('AKEEBAENGINE') || die();

use Akeeba\Engine\Driver\Joomla;
use Akeeba\Engine\Driver\Mysql;
use Akeeba\Engine\Driver\Mysqli;
use Akeeba\Engine\Driver\Pdomysql;
use Akeeba\Engine\Factory;
use Akeeba\Engine\Finalization\TestExtract;
use Akeeba\Engine\Platform;
use Akeeba\Engine\Platform\Base as BasePlatform;
use Akeeba\Engine\Psr\Log\LogLevel;
use DateTimeZone;
use Exception;
use FOF40\Container\Container;
use FOF40\Date\Date;
use JLoader;
use JMail;
use Joomla\CMS\Access\Access;
use Joomla\CMS\Filesystem\File;
use Joomla\CMS\Filesystem\Folder;
use Joomla\CMS\Language\Text;
use Joomla\CMS\Mail\Mail;
use Joomla\CMS\Uri\Uri;
use Joomla\CMS\Version;

if (!defined('DS'))
{
	define('DS', DIRECTORY_SEPARATOR); // Still required by Joomla! :(
}

/**
 * Joomla! 3.x platform class
 */
class Joomla3x extends BasePlatform
{
	/**
	 * Override profile ID, for use in automated testing only
	 *
	 * @var   int|null
	 */
	public static $profile_id = null;
	/**
	 * Platform class priority
	 *
	 * @var  int
	 */
	public $priority = 53;
	/**
	 * This platform's name
	 *
	 * @var  string
	 */
	public $platformName = 'joomla3x';
	/**
	 * The container of the Akeeba Backup component
	 *
	 * @var  Container
	 */
	protected $container = null;
	/**
	 * Flash variables for the CLI application. We use this array since we're hell bent on NOT using Joomla's broken
	 * session package.
	 *
	 * @var   array
	 *
	 * @since 5.3.5
	 */
	protected $flashVariables = [];

	/**
	 * Public constructor
	 */
	function __construct()
	{
		$this->container = Container::getInstance('com_akeeba');
	}

	public static function quirk_013()
	{
		$stock_dirs  = Platform::getInstance()->get_stock_directories();
		$default_out = @realpath($stock_dirs['[DEFAULT_OUTPUT]']);

		$registry = Factory::getConfiguration();
		$outdir   = $registry->get('akeeba.basic.output_directory');

		foreach ($stock_dirs as $macro => $replacement)
		{
			$outdir = str_replace($macro, $replacement, $outdir);
		}

		$outdir_real = @realpath($outdir);

		// If the output folder is the default one (or any subdir), we are safe
		if (strpos($outdir_real, $default_out) !== false)
		{
			return false;
		}

		$component_path = @realpath(JPATH_ADMINISTRATOR . '/components/com_akeeba');

		$forbiddenPaths = [
			'akeeba',
			'AliceChecks',
			'AliceEngine',
			'alice',
			'assets',
			'Assets',
			'BackupEngine',
			'BackupPlatform',
			'Controller',
			'controllers',
			'Dispatcher',
			'engine',
			'fields',
			'Helper',
			'helpers',
			'Master',
			'Model',
			'models',
			'platform',
			'plugins',
			'sql',
			'tables',
			'Toolbar',
			'View',
			'views',
			'ViewTemplates',
		];

		foreach ($forbiddenPaths as $subdir)
		{
			$checkPath = realpath($component_path . '/' . $subdir);

			if ($checkPath === false)
			{
				continue;
			}

			$checkPath .= DIRECTORY_SEPARATOR;

			if (strpos($outdir_real, $checkPath) === 0)
			{
				return true;
			}
		}

		return false;
	}

	public static function quirk_400()
	{
		return version_compare(JVERSION, '4.0', 'ge');
	}

	/**
	 * Loads the current configuration off the database table
	 *
	 * @param   int  $profile_id  The profile where to read the configuration from, defaults to current profile
	 *
	 * @return  bool  True if everything was read properly
	 */
	public function load_configuration($profile_id = null, $reset = true)
	{
		// Load the configuration
		parent::load_configuration($profile_id, $reset);

		// If there is no embedded installer or the wrong embedded installer is selected, fix it automatically
		$config             = Factory::getConfiguration();
		$embedded_installer = $config->get('akeeba.advanced.embedded_installer', null);

		if (empty($embedded_installer) || ($embedded_installer == 'angie-joomla'))
		{
			$protectedKeys = $config->getProtectedKeys();
			$config->setProtectedKeys([]);
			$config->set('akeeba.advanced.embedded_installer', 'angie');
			$config->setProtectedKeys($protectedKeys);
		}

		return true;
	}

	/**
	 * Saves the current configuration to the database table
	 *
	 * @param   int  $profile_id  The profile where to save the configuration to, defaults to current profile
	 *
	 * @return  bool  True if everything was saved properly
	 */
	public function save_configuration($profile_id = null)
	{
		// If there is no embedded installer or the wrong embedded installer is selected, fix it automatically
		$config             = Factory::getConfiguration();
		$embedded_installer = $config->get('akeeba.advanced.embedded_installer', null);

		if (empty($embedded_installer) || ($embedded_installer == 'angie-joomla'))
		{
			$protectedKeys = $config->getProtectedKeys();
			$config->setProtectedKeys([]);
			$config->set('akeeba.advanced.embedded_installer', 'angie');
			$config->setProtectedKeys($protectedKeys);
		}

		// Save the configuration
		return parent::save_configuration($profile_id);
	}

	/**
	 * Performs heuristics to determine if this platform object is the ideal
	 * candidate for the environment Akeeba Engine is running in.
	 *
	 * @return bool
	 */
	public function isThisPlatform()
	{
		// Make sure _JEXEC is defined
		if (!defined('_JEXEC'))
		{
			return false;
		}

		// We need JVERSION to be defined
		if (!defined('JVERSION'))
		{
			return false;
		}

		// Check if the Joomla Factory class exists
		if (!class_exists('JFactory') && !class_exists('Joomla\CMS\Factory'))
		{
			return false;
		}

		// Check if a valid application class exists
		$appExists = class_exists('Joomla\CMS\Application\CMSApplication')
			|| class_exists('Joomla\CMS\Application\CliApplication')
			|| class_exists('FOFApplicationCLI');

		if (!$appExists)
		{
			return false;
		}

		return true;
	}

	/**
	 * Returns an associative array of stock platform directories
	 *
	 * @return array
	 */
	public function get_stock_directories()
	{
		static $stock_directories = [];

		if (empty($stock_directories))
		{
			$jreg                                  = $this->container->platform->getConfig();
			$tmpdir                                = $jreg->get('tmp_path');
			$stock_directories['[SITEROOT]']       = $this->get_site_root();
			$stock_directories['[ROOTPARENT]']     = @realpath($this->get_site_root() . '/..');
			$stock_directories['[SITETMP]']        = $tmpdir;
			$stock_directories['[DEFAULT_OUTPUT]'] = $this->get_site_root() . '/administrator/components/com_akeeba/backup';
		}

		return $stock_directories;
	}

	/**
	 * Returns the absolute path to the site's root
	 *
	 * @return string
	 */
	public function get_site_root()
	{
		static $root = null;

		if (empty($root) || is_null($root))
		{
			$root = JPATH_ROOT;

			if (empty($root) || ($root == DIRECTORY_SEPARATOR) || ($root == '/'))
			{
				// Try to get the current root in a different way
				if (function_exists('getcwd'))
				{
					$root = getcwd();
				}

				if ($this->container->platform->isBackend())
				{
					if (empty($root))
					{
						$root = '../';
					}
					else
					{
						$adminPos = strpos($root, 'administrator');
						if ($adminPos !== false)
						{
							$root = substr($root, 0, $adminPos);
						}
						else
						{
							$root = '../';
						}

						// Degenerate case where $root = 'administrator'
						// without a leading slash before entering this
						// if-block
						if (empty($root))
						{
							$root = '../';
						}
					}
				}
				else
				{
					if (empty($root) || ($root == DIRECTORY_SEPARATOR) || ($root == '/'))
					{
						$root = './';
					}
				}
			}

			if (!in_array(substr($root, -1), ['/', '\\']))
			{
				$root .= DIRECTORY_SEPARATOR;
			}
		}

		return $root;
	}

	/**
	 * Returns the absolute path to the installer images directory
	 *
	 * @return string
	 */
	public function get_installer_images_path()
	{
		return JPATH_ADMINISTRATOR . '/components/com_akeeba/Master/Installers';
	}

	/**
	 * Returns the active profile number
	 *
	 * @return int
	 */
	public function get_active_profile()
	{
		// Automated testing override
		if (!is_null(self::$profile_id) && (self::$profile_id > 0))
		{
			return self::$profile_id;
		}
		// Constant override
		elseif (defined('AKEEBA_PROFILE'))
		{
			return AKEEBA_PROFILE;
		}
		// Use the session. If it's a CLI app always default to profile #1 (unless explicitly set otherwise)
		else
		{
			$defaultProfile = $this->container->platform->isCli() ? 1 : null;

			return $this->container->platform->getSessionVar('profile', $defaultProfile, 'akeeba');
		}
	}

	/**
	 * Returns the selected profile's name. If no ID is specified, the current
	 * profile's name is returned.
	 *
	 * @return string
	 */
	public function get_profile_name($id = null)
	{
		if (empty($id))
		{
			$id = $this->get_active_profile();
		}
		$id = (int) $id;

		$db  = Factory::getDatabase($this->get_platform_database_options());
		$sql = $db->getQuery(true)
			->select($db->qn('description'))
			->from($db->qn('#__ak_profiles'))
			->where($db->qn('id') . ' = ' . $db->q($id));
		$db->setQuery($sql);

		return $db->loadResult();
	}

	/**
	 * Returns the backup origin
	 *
	 * @return string Backup origin: backend|frontend
	 */
	public function get_backup_origin()
	{
		if (defined('AKEEBA_BACKUP_ORIGIN'))
		{
			return AKEEBA_BACKUP_ORIGIN;
		}

		if ($this->container->platform->isBackend())
		{
			return 'backend';
		}

		if ($this->container->platform->isFrontend())
		{
			return 'frontend';
		}

		return 'cli';
	}

	/**
	 * Returns a MySQL-formatted timestamp out of the current date
	 *
	 * @param   string  $date  [optional] The timestamp to use. Omit to use current timestamp.
	 *
	 * @return string
	 */
	public function get_timestamp_database($date = 'now')
	{
		$date = new Date($date);

		if (method_exists($date, 'toSql'))
		{
			return $date->toSql();
		}

		if (method_exists($date, 'toMySQL'))
		{
			return $date->toMySQL();
		}


		return '0000-00-00 00:00:00';
	}

	/**
	 * Returns the current timestamp, taking into account any TZ information,
	 * in the format specified by $format.
	 *
	 * @param   string  $format  Timestamp format string (standard PHP format string)
	 *
	 * @return string
	 */
	public function get_local_timestamp($format)
	{
		// Do I have a forced timezone?
		$tz = $this->get_platform_configuration_option('forced_backup_timezone', 'AKEEBA/DEFAULT');

		// No forced timezone set? Use the default Joomla! behavior.
		if (empty($tz) || ($tz == 'AKEEBA/DEFAULT'))
		{
			$tz = $this->getJoomlaTimezone();
		}

		$utcTimeZone = new DateTimeZone('UTC');
		$dateNow     = new Date('now', $utcTimeZone);
		$timezone    = new DateTimeZone($tz);
		$dateNow->setTimezone($timezone);

		return $dateNow->format($format, true);
	}

	/**
	 * Returns the current host name
	 *
	 * @return string
	 */
	public function get_host()
	{
		if ($this->container->platform->isCli())
		{
			$url  = Platform::getInstance()->get_platform_configuration_option('siteurl', '');
			$oURI = new Uri($url);
		}
		else
		{
			// Running under the web server
			$oURI = Uri::getInstance();
		}

		return $oURI->getHost();
	}

	public function get_site_name()
	{
		$jconfig = $this->container->platform->getConfig();

		return $jconfig->get('sitename', '');
	}

	/**
	 * Gets the best matching database driver class, according to CMS settings
	 *
	 * @param   bool  $use_platform  If set to false, it will forcibly try to assign one of the primitive type
	 *                               (Mysql/Mysqli) and NEVER tell you to use a platform driver.
	 *
	 * @return string
	 */
	public function get_default_database_driver($use_platform = true)
	{
		$jconfig = $this->container->platform->getConfig();
		$driver  = $jconfig->get('dbtype');
		$driver  = strtolower($driver);

		$hasPdo    = class_exists('\PDO');
		$hasMySQL  = function_exists('mysql_connect');
		$hasMySQLi = function_exists('mysqli_connect');

		// Prime with a default return value, favoring PDO MySQL if available
		$defaultDriver = Pdomysql::class;

		if (!$hasPdo)
		{
			// Second best choice is MySQLi
			$defaultDriver = Mysqli::class;

			// Third best choice is MySQL
			if (!$hasMySQLi && $hasMySQL)
			{
				$defaultDriver = Mysql::class;
			}
		}

		// Let's see what driver Joomla! uses...
		if ($use_platform)
		{
			$hasNookuContent = file_exists(JPATH_ROOT . '/plugins/system/nooku.php');

			switch ($driver)
			{
				// MySQL or MySQLi drivers are known to be working; use their
				// Akeeba Engine extended version, Akeeba\Engine\Driver\Joomla
				case 'mysql':
					// So, Joomla! 4's "mysql" is, actually, "pdomysql". Therefore I can use our own wrapper driver
					if (version_compare(JVERSION, '3.999.999', 'gt'))
					{
						return Joomla::class;
					}

					// The piece of crap called FaLang is lying about the database driver
					if (!$hasMySQL)
					{
						return Mysqli::class;
					}

					if ($hasNookuContent)
					{
						return Mysql::class;
					}

					return Joomla::class;

					break;

				case 'mysqli':
					if ($hasNookuContent)
					{
						return Mysqli::class;
					}

					return Joomla::class;

					break;

				// Any other case, use our platform-specific driver
				default:
					return Joomla::class;

					break;
			}
		}

		// Is this a subcase of mysqli or mysql drivers?
		if (substr($driver, 0, 8) == 'pdomysql')
		{
			return Pdomysql::class;
		}
		elseif (substr($driver, 0, 6) == 'mysqli')
		{
			return Mysqli::class;
		}
		elseif (substr($driver, 0, 5) == 'mysql')
		{
			// The piece of crap called FaLang is lying about the database driver
			if (!$hasMySQL)
			{
				return Mysqli::class;
			}

			return Mysql::class;
		}

		// Sometimes we get driver names in the form of foomysql instead of mysqlfoo. Let's look for that too.
		if (substr($driver, -8) == 'pdomysql')
		{
			return Pdomysql::class;
		}
		elseif (substr($driver, -6) == 'mysqli')
		{
			return Mysqli::class;
		}
		elseif (substr($driver, -5) == 'mysql')
		{
			/**
			 * Apparently there are some folks of dubious intelligence out there writing custom database drivers without
			 * understanding or caring about the differences between mysql and mysqli drivers in PHP. They don't play
			 * nice but I have my way to work around their ignorance, FORCING mysqli when they erroneously report mysql
			 * on servers which no longer support this ancient, obsolete database connector. Of course the proper way
			 * to address this would be having these folks fix their broken software but I think I'm asking for too
			 * much. They know who they are, fa la la...
			 */
			if (!$hasMySQL)
			{
				return Mysqli::class;
			}

			return Mysql::class;
		}

		// I give up! You'd better be usign a MySQL db server.
		return $defaultDriver;
	}

	/**
	 * Returns a set of options to connect to the default database of the current CMS
	 *
	 * @return array
	 */
	public function get_platform_database_options()
	{
		static $options;

		if (empty($options))
		{
			$conf    = $this->container->platform->getConfig();
			$options = [
				'host'     => $conf->get('host'),
				'user'     => $conf->get('user'),
				'password' => $conf->get('password'),
				'database' => $conf->get('db'),
				'prefix'   => $conf->get('dbprefix'),
			];
		}

		return $options;
	}

	/**
	 * Provides a platform-specific translation function
	 *
	 * @param   string  $key  The translation key
	 *
	 * @return string
	 */
	public function translate($key)
	{
		return Text::_($key);
	}

	/**
	 * Populates global constants holding the Akeeba version
	 */
	public function load_version_defines()
	{
		$basePath = JPATH_ADMINISTRATOR . '/components/com_akeeba';

		if (file_exists($basePath . '/version.php'))
		{
			require_once($basePath . '/version.php');
		}

		if (!defined('AKEEBA_VERSION'))
		{
			define("AKEEBA_VERSION", "dev");
		}
		if (!defined('AKEEBA_PRO'))
		{
			define('AKEEBA_PRO', false);
		}
		if (!defined('AKEEBA_DATE'))
		{
			$date = new Date();

			define("AKEEBA_DATE", $date->format('Y-m-d'));
		}
	}

	/**
	 * Returns the platform name and version
	 *
	 * @param   string  $platform_name  Name of the platform, e.g. Joomla!
	 * @param   string  $version        Full version of the platform
	 */
	public function getPlatformVersion()
	{
		$v = new Version();

		return [
			'name'    => 'Joomla!',
			'version' => $v->getShortVersion(),
		];
	}

	/**
	 * Logs platform-specific directories with LogLevel::INFO log level
	 */
	public function log_platform_special_directories()
	{
		$ret = [];

		Factory::getLog()->log(LogLevel::INFO, "JPATH_BASE         :" . JPATH_BASE, ['translate_root' => false]);
		Factory::getLog()->log(LogLevel::INFO, "JPATH_SITE         :" . JPATH_SITE, ['translate_root' => false]);
		Factory::getLog()->log(LogLevel::INFO, "JPATH_ROOT         :" . JPATH_ROOT, ['translate_root' => false]);
		Factory::getLog()->log(LogLevel::INFO, "JPATH_CACHE        :" . JPATH_CACHE, ['translate_root' => false]);
		Factory::getLog()->log(LogLevel::INFO, "Computed <root>    :" . $this->get_site_root(), ['translate_root' => false]);

		// If the release is older than 3 months, issue a warning
		if (defined('AKEEBA_DATE'))
		{
			$releaseDate = new Date(AKEEBA_DATE);

			if (time() - $releaseDate->toUnix() > 10368000)
			{
				if (!isset($ret['warnings']))
				{
					$ret['warnings'] = [];
					$ret['warnings'] = array_merge($ret['warnings'], [
						'Your version of Akeeba Backup is more than 120 days old and most likely already out of date. Please check if a newer version is published and install it.',
					]);
				}
			}

		}

		// Detect UNC paths and warn the user
		if (DIRECTORY_SEPARATOR == '\\')
		{
			if ((substr(JPATH_ROOT, 0, 2) == '\\\\') || (substr(JPATH_ROOT, 0, 2) == '//'))
			{
				if (!isset($ret['warnings']))
				{
					$ret['warnings'] = [];
				}

				$ret['warnings'] = array_merge($ret['warnings'], [
					'Your site\'s root is using a UNC path (e.g. \\\\SERVER\\path\\to\\root). PHP has known bugs which may',
					'prevent it from working properly on a site like this. Please take a look at',
					'https://bugs.php.net/bug.php?id=40163 and https://bugs.php.net/bug.php?id=52376. As a result your',
					'backup may fail.',
				]);
			}
		}

		if (empty($ret))
		{
			$ret = null;
		}

		return $ret;
	}

	/**
	 * Loads a platform-specific software configuration option
	 *
	 * @param   string  $key
	 * @param   mixed   $default
	 *
	 * @return mixed
	 */
	public function get_platform_configuration_option($key, $default)
	{
		$value = $this->container->params->get($key, $default);

		// Some configuration options may have to be decrypted
		switch ($key)
		{
			case 'frontend_secret_word':
				$secureSettings = Factory::getSecureSettings();
				$value          = $secureSettings->decryptSettings($value);
				break;
		}

		return $value;
	}

	/**
	 * Returns a list of emails to the Super Administrators
	 *
	 * @return  array
	 */
	public function get_administrator_emails()
	{
		$options = $this->get_platform_database_options();
		$db      = Factory::getDatabase($options);

		// Get all usergroups with Super User access
		$q      = $db->getQuery(true)
			->select([$db->qn('id')])
			->from($db->qn('#__usergroups'));
		$groups = $db->setQuery($q)->loadColumn();

		// Get the groups that are Super Users
		$groups = array_filter($groups, function ($gid) {
			return Access::checkGroup($gid, 'core.admin');
		});

		$mails = [];

		foreach ($groups as $gid)
		{
			$uids = Access::getUsersByGroup($gid);
			array_walk($uids, function ($uid, $index) use (&$mails) {
				$mails[] = $this->container->platform->getUser($uid)->email;
			});
		}

		return array_unique($mails);
	}

	/**
	 * Sends a very simple email using the platform's mailer facility
	 *
	 * @param   string  $to          The recipient's email address
	 * @param   string  $subject     The subject of the email
	 * @param   string  $body        The body of the email
	 * @param   string  $attachFile  The file to attach (null to not attach any files)
	 *
	 * @return  boolean
	 */
	public function send_email($to, $subject, $body, $attachFile = null)
	{
		Factory::getLog()->log(LogLevel::DEBUG, "-- Fetching mailer object");

		/** @var JMail $mailer */
		try
		{
			$mailer = Platform::getInstance()->getMailer();
		}
		catch (Exception $e)
		{
			$mailer = null;
		}

		if (!is_object($mailer))
		{
			Factory::getLog()->log(LogLevel::WARNING, "Could not send email to $to - Joomla! cannot send e-mails. Please check your From EMail and From Name fields in Global Configuration.");

			return false;
		}

		Factory::getLog()->log(LogLevel::DEBUG, "-- Creating email message");

		try
		{
			$recipient = [$to];

			$mailer->addRecipient($recipient);
			$mailer->setSubject($subject);
			$mailer->setBody($body);
		}
		catch (Exception $e)
		{
			Factory::getLog()->log(LogLevel::WARNING, "Could not send email to $to - Problem setting up the email. Joomla! reports error: " . $e->getMessage());

			return false;
		}

		try
		{
			if (!empty($attachFile))
			{
				Factory::getLog()->log(LogLevel::INFO, "-- Attaching $attachFile");

				if (!file_exists($attachFile) || !(is_file($attachFile) || is_link($attachFile)))
				{
					Factory::getLog()->log(LogLevel::WARNING, "The file does not exist, or it's not a file; no email sent");

					return false;
				}

				if (!is_readable($attachFile))
				{
					Factory::getLog()->log(LogLevel::WARNING, "The file is not readable; no email sent");

					return false;
				}

				$filesize = @filesize($attachFile);

				if ($filesize)
				{
					// Check that we have AT LEAST 2.5 times free RAM as the filesize (that's how much we'll need)
					if (!function_exists('ini_get'))
					{
						// Assume 8Mb of PHP memory limit (worst case scenario)
						$totalRAM = 8388608;
					}
					else
					{
						$totalRAM = ini_get('memory_limit');
						if (strstr($totalRAM, 'M'))
						{
							$totalRAM = (int) $totalRAM * 1048576;
						}
						elseif (strstr($totalRAM, 'K'))
						{
							$totalRAM = (int) $totalRAM * 1024;
						}
						elseif (strstr($totalRAM, 'G'))
						{
							$totalRAM = (int) $totalRAM * 1073741824;
						}
						else
						{
							$totalRAM = (int) $totalRAM;
						}
						if ($totalRAM <= 0)
						{
							// No memory limit? Cool! Assume 1Gb of available RAM (which is absurdely abundant as of March 2011...)
							$totalRAM = 1086373952;
						}
					}
					if (!function_exists('memory_get_usage'))
					{
						$usedRAM = 8388608;
					}
					else
					{
						$usedRAM = memory_get_usage();
					}

					$availableRAM = $totalRAM - $usedRAM;

					if ($availableRAM < 2.5 * $filesize)
					{
						Factory::getLog()->log(LogLevel::WARNING, "The file is too big to be sent by email. Please use a smaller Part Size for Split Archives setting.");
						Factory::getLog()->log(LogLevel::DEBUG, "Memory limit $totalRAM bytes -- Used memory $usedRAM bytes -- File size $filesize -- Attachment requires approx. " . (2.5 * $filesize) . " bytes");

						return false;
					}
				}
				else
				{
					Factory::getLog()->log(LogLevel::WARNING, "Your server fails to report the file size of $attachFile. If the backup crashes, please use a smaller Part Size for Split Archives setting");
				}

				$mailer->addAttachment($attachFile);
			}
		}
		catch (Exception $e)
		{
			Factory::getLog()->log(LogLevel::WARNING, "Could not send email to $to - Problem attaching file. Joomla! reports error: " . $e->getMessage());

			return false;
		}

		Factory::getLog()->log(LogLevel::DEBUG, "-- Sending message");

		try
		{
			$result = $mailer->Send();
		}
		catch (Exception $e)
		{
			$result = $e;
		}

		if ($result instanceof Exception)
		{
			Factory::getLog()->log(LogLevel::WARNING, "Could not email $to:");
			Factory::getLog()->log(LogLevel::WARNING, $result->getMessage());
			$ret = $result->getMessage();
			unset($result);
			unset($mailer);

			return $ret;
		}

		Factory::getLog()->log(LogLevel::DEBUG, "-- Email sent");

		return true;
	}

	/**
	 * Deletes a file from the local server using direct file access or FTP
	 *
	 * @param   string  $file
	 *
	 * @return bool
	 */
	public function unlink($file)
	{
		if (function_exists('jimport'))
		{
			$result = File::delete($file);

			if (!$result)
			{
				$result = @unlink($file);
			}
		}
		else
		{
			$result = parent::unlink($file);
		}

		return $result;
	}

	/**
	 * Moves a file around within the local server using direct file access or FTP
	 *
	 * @param   string  $from
	 * @param   string  $to
	 *
	 * @return bool
	 */
	public function move($from, $to)
	{
		if (function_exists('jimport'))
		{
			$result = File::move($from, $to);

			// JFile failed. Let's try rename()
			if (!$result)
			{
				$result = @rename($from, $to);
			}
			// Rename failed, too. Let's try copy/delete
			if (!$result)
			{
				// Try copying with JFile. If it fails, use copy().
				$result = File::copy($from, $to);
				if (!$result)
				{
					$result = @copy($from, $to);
				}

				// If the copy succeeded, try deleting the original with JFile. If it fails, use unlink().
				if ($result)
				{
					$result = $this->unlink($from);
				}
			}
		}
		else
		{
			$result = parent::move($from, $to);
		}

		return $result;
	}

	/**
	 * Joomla!-specific function to get an instance of the mailer class
	 *
	 * @return Mail
	 */
	public function &getMailer()
	{
		$mailer = \Joomla\CMS\Factory::getMailer();
		if (!is_object($mailer))
		{
			Factory::getLog()->log(LogLevel::WARNING, "Fetching Joomla!'s mailer was impossible; imminent crash!");
		}
		else
		{
			$emailMethod = $mailer->Mailer;
			Factory::getLog()->log(LogLevel::DEBUG, "-- Joomla!'s mailer is using $emailMethod mail method.");
		}

		return $mailer;
	}

	/**
	 * Stores a flash (temporary) variable in the session.
	 *
	 * @param   string  $name   The name of the variable to store
	 * @param   string  $value  The value of the variable to store
	 *
	 * @return  void
	 */
	public function set_flash_variable($name, $value)
	{
		if ($this->container->platform->isCli())
		{
			$this->flashVariables[$name] = $value;

			return;
		}

		$this->container->platform->setSessionVar($name, $value, 'akeeba');
	}

	/**
	 * Return the value of a flash (temporary) variable from the session and
	 * immediately removes it.
	 *
	 * @param   string  $name     The name of the flash variable
	 * @param   mixed   $default  Default value, if the variable is not defined
	 *
	 * @return  mixed  The value of the variable or $default if it's not set
	 */
	public function get_flash_variable($name, $default = null)
	{
		if ($this->container->platform->isCli())
		{
			$ret = $default;

			if (isset($this->flashVariables[$name]))
			{
				$ret = $this->flashVariables[$name];
				unset($this->flashVariables[$name]);
			}

			return $ret;
		}

		$ret = $this->container->platform->getSessionVar($name, $default, 'akeeba');
		$this->container->platform->setSessionVar($name, null, 'akeeba');

		return $ret;
	}

	/**
	 * Perform an immediate redirection to the defined URL
	 *
	 * @param   string  $url  The URL to redirect to
	 *
	 * @return  void
	 */
	public function redirect($url)
	{
		$this->container->platform->redirect($url);
	}

	public function apply_quirk_definitions()
	{
		Factory::getConfigurationChecks()->addConfigurationCheckDefinition('013', 'critical', 'COM_AKEEBA_CPANEL_WARNING_Q013', [
			Joomla3x::class, 'quirk_013',
		]);
		Factory::getConfigurationChecks()->addConfigurationCheckDefinition('400', 'critical', 'COM_AKEEBA_CPANEL_WARNING_Q400', [
			Joomla3x::class, 'quirk_400',
		]);
	}

	/** @inheritdoc  */
	protected function detectProxySettings()
	{
		try
		{
			$app = \Joomla\CMS\Factory::getApplication();
		}
		catch (Exception $e)
		{
			$this->proxyEnabled                = false;
			$this->hasInitialisedProxySettings = true;
		}

		$enabled = $app->get('proxy_enable', false);
		$host    = $app->get('proxy_host', '');
		$port    = (int) $app->get('proxy_port', 8080);
		$user    = $app->get('proxy_user', '');
		$pass    = $app->get('proxy_pass', '');

		$this->setProxySettings($enabled, $host, $port, $user, $pass);
	}


	/**
	 * Registers Akeeba Engine's core classes with JLoader
	 *
	 * @param   string  $path_prefix  The path prefix to look in
	 */
	protected function register_akeeba_engine_classes($path_prefix)
	{
		global $Akeeba_Class_Map;

		foreach ($Akeeba_Class_Map as $class_prefix => $path_suffix)
		{
			// Bail out if there is such directory, so as not to have Joomla! throw errors
			if (!@is_dir($path_prefix . '/' . $path_suffix))
			{
				continue;
			}

			$file_list = Folder::files($path_prefix . '/' . $path_suffix, '.*\.php');
			if (is_array($file_list) && !empty($file_list))
			{
				foreach ($file_list as $file)
				{
					$class_suffix = ucfirst(basename($file, '.php'));
					JLoader::register($class_prefix . $class_suffix, $path_prefix . '/' . $path_suffix . '/' . $file);
				}
			}
		}
	}

	/**
	 * Get the applicable timezone in the same way Joomla! calculates it: if there is a logged in
	 * user with a specific timezone set, use it. Otherwise use the Server Timezone defined in the
	 * site's Global Configuration. If nothing is set there, use GMT instead.
	 *
	 * @return  string
	 */
	private function getJoomlaTimezone()
	{
		// Out ultimate default is the server timezone set up in the Global Configuration
		$jregistry = $this->container->platform->getConfig();
		$tz        = $jregistry->get('offset', 'GMT');

		// If this is a CLI script, tough luck, we can't use a different TZ
		if ($this->container->platform->isCli())
		{
			return $tz;
		}

		// If it's a guest user they can't have a special TZ set, return.
		$user = $this->container->platform->getUser();

		if ($user->guest)
		{
			return $tz;
		}

		$tz = $user->getParam('timezone', $tz);

		return $tz;
	}
}
PK�!]�ܛKK)BackupPlatform/Joomla3x/Driver/Joomla.phpnu&1i�<?php
/**
 * @package   akeebabackup
 * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

namespace Akeeba\Engine\Driver;

// Protection against direct access
defined('AKEEBAENGINE') || die();

use Akeeba\Engine\Platform;
use Exception;
use Joomla\CMS\Factory;

class Joomla
{
	/** @var Base The real database connection object */
	private $dbo;

	/**
	 * Database object constructor
	 *
	 * @param   array  $options  List of options used to configure the connection
	 */
	public function __construct($options = [])
	{
		// Get best matching Akeeba Backup driver instance
		if (class_exists('JFactory'))
		{
			// Get the database driver *AND* make sure it's connected.
			$db = Factory::getDBO();
			$db->connect();

			$options['connection'] = $db->getConnection();

			switch ($db->name)
			{
				case 'mysql':
					// So, Joomla! 4's "mysql" is, actually, "pdomysql".
					$driver = 'mysql';

					if (version_compare(JVERSION, '3.999.999', 'gt'))
					{
						$driver = 'pdomysql';
					}
					break;

				case 'mysqli':
					$driver = 'mysqli';
					break;

				case 'pdomysql':
					$driver = 'pdomysql';
					break;

				default:
					throw new \RuntimeException("Unsupported database driver {$db->name}");

					break;
			}

			$driver = '\\Akeeba\\Engine\\Driver\\' . ucfirst($driver);
		}
		else
		{
			$driver = Platform::getInstance()->get_default_database_driver(false);
		}

		$this->dbo = new $driver($options);
	}

	public function close()
	{
		/**
		 * We should not, in fact, try to close the connection by calling the parent method.
		 *
		 * If you close the connection we ask PHP's mysql / mysqli / pdomysql driver to disconnect the MySQL connection
		 * resource from the database server inside our instance of Akeeba Engine's database driver. However, this
		 * identical resource is also present in Joomla's database driver. Joomla will also try to close the connection
		 * to a now invalid resource, causing a PHP notice to be recorded.
		 *
		 * By setting the connection resource to null in our own driver object we prevent closing the resource,
		 * delegating that responsibility to Joomla. It will gladly do so at the very least automatically, through its
		 * db driver's __destruct.
		 */
		$this->dbo->setConnection(null);
	}

	public function open()
	{
		if (method_exists($this->dbo, 'open'))
		{
			$this->dbo->open();
		}
		elseif (method_exists($this->dbo, 'connect'))
		{
			$this->dbo->connect();
		}
	}

	/**
	 * Magic method to proxy all calls to the loaded database driver object
	 *
	 * @throws  Exception
	 */
	public function __call($name, array $arguments)
	{
		if (is_null($this->dbo))
		{
			throw new Exception('Akeeba Engine database driver is not loaded');
		}

		if (method_exists($this->dbo, $name) || in_array($name, ['q', 'nq', 'qn']))
		{
			// Call_user_func_array is ~3 times slower than direct method calls.
			// (thank you, Nooku Framework, for the tip!)
			switch (count($arguments))
			{
				case 0 :
					$result = $this->dbo->$name();
					break;
				case 1 :
					$result = $this->dbo->$name($arguments[0]);
					break;
				case 2:
					$result = $this->dbo->$name($arguments[0], $arguments[1]);
					break;
				case 3:
					$result = $this->dbo->$name($arguments[0], $arguments[1], $arguments[2]);
					break;
				case 4:
					$result = $this->dbo->$name($arguments[0], $arguments[1], $arguments[2], $arguments[3]);
					break;
				case 5:
					$result = $this->dbo->$name($arguments[0], $arguments[1], $arguments[2], $arguments[3], $arguments[4]);
					break;
				default:
					// Resort to using call_user_func_array for many segments
					$result = call_user_func_array([$this->dbo, $name], $arguments);
			}

			return $result;
		}
		else
		{
			throw new Exception('Method ' . $name . ' not found in Akeeba Platform');
		}
	}

	public function __get($name)
	{
		if (isset($this->dbo->$name) || property_exists($this->dbo, $name))
		{
			return $this->dbo->$name;
		}
		else
		{
			$this->dbo->$name = null;

			user_error('Database driver does not support property ' . $name);
		}

		return null;
	}

	public function __set($name, $value)
	{
		if (isset($this->dbo->name) || property_exists($this->dbo, $name))
		{
			$this->dbo->$name = $value;
		}
		else
		{
			$this->dbo->$name = null;
			user_error('Database driver not support property ' . $name);
		}
	}
}
PK�!]s��b��/BackupPlatform/Joomla3x/Config/02.advanced.jsonnu&1i�{
    "_group": {
        "description": "COM_AKEEBA_CONFIG_ADVANCED"
    },
    "akeeba.advanced.dump_engine": {
        "default": "native",
        "type": "engine",
        "subtype": "dump",
        "title": "COM_AKEEBA_CONFIG_DUMPENGINE_TITLE",
        "description": "COM_AKEEBA_CONFIG_DUMPENGINE_DESCRIPTION",
        "protected": "0"
    },
    "akeeba.advanced.scan_engine": {
        "default": "smart",
        "type": "engine",
        "subtype": "scan",
        "protected": "1",
        "title": "COM_AKEEBA_CONFIG_SCANENGINE_TITLE",
        "description": "COM_AKEEBA_CONFIG_SCANENGINE_DESCRIPTION"
    },
    "akeeba.advanced.archiver_engine": {
        "default": "jpa",
        "type": "engine",
        "subtype": "archiver",
        "title": "COM_AKEEBA_CONFIG_ARCHIVERENGINE_TITLE",
        "description": "COM_AKEEBA_CONFIG_ARCHIVERENGINE_DESCRIPTION"
    },
    "akeeba.advanced.postproc_engine": {
        "default": "none",
        "type": "none",
        "protected": "1"
    },
    "akeeba.advanced.embedded_installer": {
        "default": "angie",
        "type": "installer",
        "title": "COM_AKEEBA_CONFIG_INSTALLER_TITLE",
        "description": "COM_AKEEBA_CONFIG_INSTALLER_DESCRIPTION",
        "protected": "0"
    },
    "engine.installer.angie.key": {
        "default": "",
        "type": "password",
        "title": "COM_AKEEBA_CONFIG_ANGIE_KEY_TITLE",
        "description": "COM_AKEEBA_CONFIG_ANGIE_KEY_DESCRIPTION",
        "protected": "0"
    }
}PK�!]�aa2BackupPlatform/Joomla3x/Filter/Joomlaskipfiles.phpnu&1i�<?php
/**
 * @package   akeebabackup
 * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

namespace Akeeba\Engine\Filter;

// Protection against direct access
defined('AKEEBAENGINE') || die();

use Akeeba\Engine\Factory;
use Akeeba\Engine\Platform;
use FOF40\Container\Container;

/**
 * Subdirectories exclusion filter. Excludes temporary, cache and backup output
 * directories' contents from being backed up.
 */
class Joomlaskipfiles extends Base
{
	public function __construct()
	{
		$this->object      = 'dir';
		$this->subtype     = 'content';
		$this->method      = 'direct';
		$this->filter_name = 'Joomlaskipfiles';

		// We take advantage of the filter class magic to inject our custom filters
		$configuration = Factory::getConfiguration();
		$container     = Container::getInstance('com_akeeba');
		$jreg          = $container->platform->getConfig();

		$tmpdir  = $jreg->get('tmp_path');
		$logsdir = $jreg->get('log_path');

		// Get the site's root
		if ($configuration->get('akeeba.platform.override_root', 0))
		{
			$root = $configuration->get('akeeba.platform.newroot', '[SITEROOT]');
		}
		else
		{
			$root = '[SITEROOT]';
		}

		$this->filter_data[$root] = [
			// Output & temp directory of the component
			$this->treatDirectory($configuration->get('akeeba.basic.output_directory')),

			// Joomla! temporary directory
			$this->treatDirectory($tmpdir),

			// Joomla! logs directory
			$this->treatDirectory($logsdir),

			// default temp directory
			$this->treatDirectory(JPATH_SITE . '/tmp'),
			'tmp',
			$this->treatDirectory(Platform::getInstance()->get_site_root() . '/tmp'),

			// Joomla! front- and back-end cache, as reported by Joomla!
			$this->treatDirectory(JPATH_CACHE),
			$this->treatDirectory(JPATH_ADMINISTRATOR . '/cache'),
			$this->treatDirectory(JPATH_ROOT . '/cache'),
			// cache directories fallback
			'cache',
			'administrator/cache',
			// Joomla! front- and back-end cache, as calculated by us (redundancy, for funky server setups)
			$this->treatDirectory(Platform::getInstance()->get_site_root() . '/cache'),
			$this->treatDirectory(Platform::getInstance()->get_site_root() . '/administrator/cache'),

			// This is not needed except on sites running SVN or beta releases
			$this->treatDirectory(JPATH_ROOT . '/installation'),
			// ...and the fallbacks
			'installation',
			$this->treatDirectory(Platform::getInstance()->get_site_root() . '/installation'),

			// Default backup output (many people change it, forget to remove old backup archives and they end up backing up old backups)
			$this->treatDirectory(JPATH_ADMINISTRATOR . '/components/com_akeeba/backup'),
			'administrator/components/com_akeeba/backup',
			$this->treatDirectory(Platform::getInstance()->get_site_root() . '/administrator/components/com_akeeba/backup'),

			// MyBlog's cache
			$this->treatDirectory(JPATH_SITE . '/components/libraries/cmslib/cache'),
			// ...and fallbacks
			'components/libraries/cmslib/cache',
			$this->treatDirectory(Platform::getInstance()->get_site_root() . '/components/libraries/cmslib/cache'),

			// Used by Plesk to store its logs. It's in the public root, owned by root and read-only. Yipee!
			$this->treatDirectory(JPATH_ROOT . '/logs'),
			'logs',
			$this->treatDirectory(Platform::getInstance()->get_site_root() . '/logs'),

			// Some developers hardcode this path for their log files. I guess they never heard of Joomla!'s Global Configuration?
			$this->treatDirectory(JPATH_ROOT . '/log'),
			'log',
			$this->treatDirectory(Platform::getInstance()->get_site_root() . '/log'),

			// Joomla! 3.6 is loads of fun. It changed the logs folder location.
			$this->treatDirectory(JPATH_ADMINISTRATOR . '/logs'),
			'administrator/logs',
			$this->treatDirectory(Platform::getInstance()->get_site_root() . '/administrator/logs'),

			// Also in case a Joomla! 3.6 site admin cocks up, let's try a singular folder name.
			$this->treatDirectory(JPATH_ADMINISTRATOR . '/log'),
			'administrator/log',
			$this->treatDirectory(Platform::getInstance()->get_site_root() . '/administrator/log'),
		];

		parent::__construct();
	}
}
PK�!]�x����3BackupPlatform/Joomla3x/Filter/Excludetabledata.phpnu&1i�<?php
/**
 * @package   akeebabackup
 * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

namespace Akeeba\Engine\Filter;

// Protection against direct access
defined('AKEEBAENGINE') || die();

use Akeeba\Engine\Factory;

/**
 * Subdirectories exclusion filter. Excludes temporary, cache and backup output
 * directories' contents from being backed up.
 */
class Excludetabledata extends Base
{
	public function __construct()
	{
		$this->object      = 'dbobject';
		$this->subtype     = 'content';
		$this->method      = 'direct';
		$this->filter_name = 'Excludetabledata';

		// We take advantage of the filter class magic to inject our custom filters
		$this->filter_data['[SITEDB]'] = array(
			'#__session',        // Sessions table
			'#__guardxt_runs'    // Guard XT's run log (bloated to the bone)
		);

		parent::__construct();
	}

}
PK�!]��EJ``1BackupPlatform/Joomla3x/Filter/Joomlaskipdirs.phpnu&1i�<?php
/**
 * @package   akeebabackup
 * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

namespace Akeeba\Engine\Filter;

// Protection against direct access
defined('AKEEBAENGINE') || die();

use Akeeba\Engine\Factory;
use Akeeba\Engine\Platform;
use FOF40\Container\Container;

/**
 * Subdirectories exclusion filter. Excludes temporary, cache and backup output
 * directories' contents from being backed up.
 */
class Joomlaskipdirs extends Base
{
	public function __construct()
	{
		$this->object      = 'dir';
		$this->subtype     = 'children';
		$this->method      = 'direct';
		$this->filter_name = 'Joomlaskipdirs';

		// We take advantage of the filter class magic to inject our custom filters
		$configuration = Factory::getConfiguration();
		$container     = Container::getInstance('com_akeeba');
		$jreg          = $container->platform->getConfig();

		$tmpdir  = $jreg->get('tmp_path');
		$logsdir = $jreg->get('log_path');

		// Get the site's root
		if ($configuration->get('akeeba.platform.override_root', 0))
		{
			$root = $configuration->get('akeeba.platform.newroot', '[SITEROOT]');
		}
		else
		{
			$root = '[SITEROOT]';
		}

		$this->filter_data[$root] = [
			// Output & temp directory of the component
			$this->treatDirectory($configuration->get('akeeba.basic.output_directory')),

			// Joomla! temporary directory
			$this->treatDirectory($tmpdir),

			// Joomla! logs directory
			$this->treatDirectory($logsdir),

			// default temp directory
			$this->treatDirectory(JPATH_SITE . '/tmp'),
			'tmp',
			$this->treatDirectory(Platform::getInstance()->get_site_root() . '/tmp'),

			// Joomla! front- and back-end cache, as reported by Joomla!
			$this->treatDirectory(JPATH_CACHE),
			$this->treatDirectory(JPATH_ADMINISTRATOR . '/cache'),
			$this->treatDirectory(JPATH_ROOT . '/cache'),
			// cache directories fallback
			'cache',
			'administrator/cache',
			// Joomla! front- and back-end cache, as calculated by us (redundancy, for funky server setups)
			$this->treatDirectory(Platform::getInstance()->get_site_root() . '/cache'),
			$this->treatDirectory(Platform::getInstance()->get_site_root() . '/administrator/cache'),

			// This is not needed except on sites running SVN or beta releases
			$this->treatDirectory(JPATH_ROOT . '/installation'),
			// ...and the fallbacks
			'installation',
			$this->treatDirectory(Platform::getInstance()->get_site_root() . '/installation'),

			// Default backup output (many people change it, forget to remove old backup archives and they end up backing up old backups)
			$this->treatDirectory(JPATH_ADMINISTRATOR . '/components/com_akeeba/backup'),
			'administrator/components/com_akeeba/backup',
			$this->treatDirectory(Platform::getInstance()->get_site_root() . '/administrator/components/com_akeeba/backup'),

			// MyBlog's cache
			$this->treatDirectory(JPATH_SITE . '/components/libraries/cmslib/cache'),
			// ...and fallbacks
			'components/libraries/cmslib/cache',
			$this->treatDirectory(Platform::getInstance()->get_site_root() . '/components/libraries/cmslib/cache'),

			// Used by Plesk to store its logs. It's in the public root, owned by root and read-only. Yipee!
			$this->treatDirectory(JPATH_ROOT . '/logs'),
			'logs',
			$this->treatDirectory(Platform::getInstance()->get_site_root() . '/logs'),

			// Some developers hardcode this path for their log files. I guess they never heard of Joomla!'s Global Configuration?
			$this->treatDirectory(JPATH_ROOT . '/log'),
			'log',
			$this->treatDirectory(Platform::getInstance()->get_site_root() . '/log'),

			// Joomla! 3.6 is loads of fun. It changed the logs folder location.
			$this->treatDirectory(JPATH_ADMINISTRATOR . '/logs'),
			'administrator/logs',
			$this->treatDirectory(Platform::getInstance()->get_site_root() . '/administrator/logs'),

			// Also in case a Joomla! 3.6 site admin cocks up, let's try a singular folder name.
			$this->treatDirectory(JPATH_ADMINISTRATOR . '/log'),
			'administrator/log',
			$this->treatDirectory(Platform::getInstance()->get_site_root() . '/administrator/log'),
		];

		parent::__construct();
	}
}
PK�!]Q��))-BackupPlatform/Joomla3x/Filter/Cvsfolders.phpnu&1i�<?php
/**
 * @package   akeebabackup
 * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

namespace Akeeba\Engine\Filter;

// Protection against direct access
defined('AKEEBAENGINE') || die();

use Akeeba\Engine\Factory;

/**
 * Folder exclusion filter based on regular expressions
 */
class Cvsfolders extends Base
{
	function __construct()
	{
		$this->object      = 'dir';
		$this->subtype     = 'all';
		$this->method      = 'regex';
		$this->filter_name = 'Cvsfolders';

		if (empty($this->filter_name))
		{
			$this->filter_name = strtolower(basename(__FILE__, '.php'));
		}

		parent::__construct();

		// Get the site's root
		$configuration = Factory::getConfiguration();

		if ($configuration->get('akeeba.platform.override_root', 0))
		{
			$root = $configuration->get('akeeba.platform.newroot', '[SITEROOT]');
		}
		else
		{
			$root = '[SITEROOT]';
		}

		$this->filter_data[$root] = array(
			'#/\.git$#',
			'#^\.git$#',
			'#/\.svn$#',
			'#^\.svn$#'
		);
	}
}
PK�!]ެ�n��+BackupPlatform/Joomla3x/Filter/Siteroot.phpnu&1i�<?php
/**
 * @package   akeebabackup
 * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

namespace Akeeba\Engine\Filter;

// Protection against direct access
defined('AKEEBAENGINE') || die();

use Akeeba\Engine\Factory;

/**
 * Add site's root to the backup set.
 */
class Siteroot extends Base
{
	public function __construct()
	{
		// This is a directory inclusion filter.
		$this->object      = 'dir';
		$this->subtype     = 'inclusion';
		$this->method      = 'direct';
		$this->filter_name = 'Siteroot';

		// Directory inclusion format:
		// array(real_directory, add_path)
		$add_path = null; // A null add_path means that we dump this dir's contents in the archive's root

		// We take advantage of the filter class magic to inject our custom filters
		$configuration = Factory::getConfiguration();

		if ($configuration->get('akeeba.platform.override_root', 0))
		{
			$root = $configuration->get('akeeba.platform.newroot', '[SITEROOT]');
		}
		else
		{
			$root = '[SITEROOT]';
		}

		$this->filter_data[] = array(
			$root,
			$add_path
		);

		parent::__construct();
	}
}
PK�!]n�hh3BackupPlatform/Joomla3x/Filter/Systemcachefiles.phpnu&1i�<?php
/**
 * @package   akeebabackup
 * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

namespace Akeeba\Engine\Filter;

// Protection against direct access
defined('AKEEBAENGINE') || die();

use Akeeba\Engine\Factory;

/**
 * Files exclusion filter based on regular expressions
 */
class Systemcachefiles extends Base
{
	function __construct()
	{
		$this->object      = 'file';
		$this->subtype     = 'all';
		$this->method      = 'regex';
		$this->filter_name = 'Systemcachefiles';

		if (empty($this->filter_name))
		{
			$this->filter_name = strtolower(basename(__FILE__, '.php'));
		}

		parent::__construct();

		// Get the site's root
		$configuration = Factory::getConfiguration();

		if ($configuration->get('akeeba.platform.override_root', 0))
		{
			$root = $configuration->get('akeeba.platform.newroot', '[SITEROOT]');
		}
		else
		{
			$root = '[SITEROOT]';
		}

		$this->filter_data[$root] = array(
			'#/Thumbs\.db$#',
			'#^Thumbs\.db$#',
			'#/\.DS_Store$#i',
			'#^\.DS_Store$#i',
			'#^core\.[\d]{1,10}$#i',
		);
	}
}
PK�!]�L���,BackupPlatform/Joomla3x/Filter/Libraries.phpnu&1i�<?php
/**
 * @package   akeebabackup
 * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

namespace Akeeba\Engine\Filter;

// Protection against direct access
defined('AKEEBAENGINE') || die();

use Akeeba\Engine\Factory;
use Akeeba\Engine\Platform;

/**
 * Joomla! 1.6 libraries off-site relocation workaround
 *
 * After the application of patch 23377
 * (http://joomlacode.org/gf/project/joomla/tracker/?action=TrackerItemEdit&tracker_item_id=23377)
 * it is possible for the webmaster to move the libraries directory of his Joomla!
 * site to an arbitrary location in the folder tree. This filter works around this
 * new feature by creating a new extra directory inclusion filter.
 */
class Libraries extends Base
{
	public function __construct()
	{
		$this->object      = 'dir';
		$this->subtype     = 'inclusion';
		$this->method      = 'direct';
		$this->filter_name = 'Libraries';

		// FIXME This filter doesn't work very well on many live hosts. Disabled for now.
		parent::__construct();

		return;


		if (empty($this->filter_name))
		{
			$this->filter_name = strtolower(basename(__FILE__, '.php'));
		}

		// Get the saved library path and compare it to the default
		$jlibdir = Platform::getInstance()->get_platform_configuration_option('jlibrariesdir', '');
		if (empty($jlibdir))
		{
			if (defined('JPATH_LIBRARIES'))
			{
				$jlibdir = JPATH_LIBRARIES;
			}
			elseif (defined('JPATH_PLATFORM'))
			{
				$jlibdir = JPATH_PLATFORM;
			}
			else
			{
				$jlibdir = false;
			}
		}

		if ($jlibdir !== false)
		{
			$jlibdir          = Factory::getFilesystemTools()->TranslateWinPath($jlibdir);
			$defaultLibraries = Factory::getFilesystemTools()->TranslateWinPath(JPATH_SITE . '/libraries');

			if ($defaultLibraries != $jlibdir)
			{
				// The path differs, add it here
				$this->filter_data['JPATH_LIBRARIES'] = $jlibdir;
			}
		}
		else
		{
			$this->filter_data = array();
		}
		parent::__construct();
	}
}
PK�!]ώ{�0BackupPlatform/Joomla3x/Filter/Stack/finder.jsonnu&1i�{
    "core.filters.finder.enabled": {
        "default": "1",
        "type": "bool",
        "title": "COM_AKEEBA_CONFIG_OPTIONALFILTERS_FINDER_ENABLED_TITLE",
        "description": "COM_AKEEBA_CONFIG_OPTIONALFILTERS_FINDER_ENABLED_DESCRIPTION",
        "bold": "1"
    }
}PK�!]�FW�  4BackupPlatform/Joomla3x/Filter/Stack/actionlogs.jsonnu&1i�{
    "core.filters.actionlogs.enabled": {
        "default": "1",
        "type": "bool",
        "title": "COM_AKEEBA_CONFIG_OPTIONALFILTERS_ACTIONLOGS_ENABLED_TITLE",
        "description": "COM_AKEEBA_CONFIG_OPTIONALFILTERS_ACTIONLOGS_ENABLED_DESCRIPTION",
        "bold": "1"
    }
}PK�!]�:

4BackupPlatform/Joomla3x/Filter/Stack/StackFinder.phpnu&1i�<?php
/**
 * @package   akeebabackup
 * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

namespace Akeeba\Engine\Filter\Stack;

// Protection against direct access
defined('AKEEBAENGINE') || die();

use Akeeba\Engine\Factory;
use Akeeba\Engine\Filter\Base as FilterBase;

/**
 * Date conditional filter
 *
 * It will only backup files modified after a specific date and time
 *
 * @since  3.4.0
 */
class StackFinder extends FilterBase
{
	/** @inheritDoc */
	public function __construct()
	{
		parent::__construct();

		$this->object  = 'dbobject';
		$this->subtype = 'content';
		$this->method  = 'api';
	}

	/**
	 * Extra SQL statements to append to the SQL dump file.
	 *
	 * Joomla 4's #__finder_taxonomy table is a tree. We always need a root node. This adds the root node back to the
	 * tree even though we just excluded it.
	 *
	 * @param   string  $root  The database for which to get the extra SQL statements
	 *
	 * @return  string  Extra SQL statements
	 *
	 * @since   7.2.0
	 */
	public function getExtraSQL(string $root): array
	{
		// Only run on Joomla! 4 and for the main site database
		if (($root != '[SITEDB]') || !version_compare(JVERSION, '3.999.999', 'gt'))
		{
			return [];
		}

		// Get the SQL query, constructed correctly for the DB technology in use.
		$db  = Factory::getDatabase();
		$sql = (string) $db->getQuery(true)
			->insert($db->quoteName('#__finder_taxonomy'))
			->columns(array_map([$db, 'quoteName'], [
				'id', 'parent_id', 'lft', 'rgt', 'level', 'path', 'title', 'alias', 'state', 'access', 'language',
			]))
			->values(implode(", ", array_map([$db, 'quote'], [
				1, 0, 0, 1, 0, '', 'ROOT', 'root', 1, 1, '*',
			])));

		// Make sure there's a trailing semicolon before returning the SQL query.
		$sql = rtrim(trim($sql), ';') . ';';

		return [$sql];
	}

	/**
	 * This method must be overriden by API-type exclusion filters.
	 *
	 * @param   string  $test  The object to test for exclusion
	 * @param   string  $root  The object's root
	 *
	 * @return  bool    Return true if it matches your filters
	 *
	 * @since   3.4.0
	 */
	protected function is_excluded_by_api($test, $root)
	{
		static $finderTables = [
			/**
			 * Common tables, J3 and J4.
			 *
			 * Note that the taxonomy table contents are removed BUT the root node for Joomla 4 is added back with the
			 * getExtraSQL() method trick.
			 */
			'#__finder_links', '#__finder_taxonomy', '#__finder_taxonomy_map', '#__finder_terms',
			// Joomla 3 only
			'#__finder_links_terms0', '#__finder_links_terms1',
			'#__finder_links_terms2', '#__finder_links_terms3', '#__finder_links_terms4',
			'#__finder_links_terms5', '#__finder_links_terms6', '#__finder_links_terms7',
			'#__finder_links_terms8', '#__finder_links_terms9', '#__finder_links_termsa',
			'#__finder_links_termsb', '#__finder_links_termsc', '#__finder_links_termsd',
			'#__finder_links_termse', '#__finder_links_termsf',
			// Joomla 4 only
			'#__finder_links_terms', '#__finder_logging',
		];

		// Not the site's database? Include the tables
		if ($root != '[SITEDB]')
		{
			return false;
		}

		// Is it one of the blacklisted tables?
		if (in_array($test, $finderTables))
		{
			return true;
		}

		// No match? Just include the file!
		return false;
	}

}
PK�!]{O998BackupPlatform/Joomla3x/Filter/Stack/StackActionlogs.phpnu&1i�<?php
/**
 * @package   akeebabackup
 * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

namespace Akeeba\Engine\Filter\Stack;

use Akeeba\Engine\Filter\Base;

// Protection against direct access
defined('AKEEBAENGINE') || die();

/**
 * Exclude Joomla 3.9+ actions log table
 */
class StackActionlogs extends Base
{
	public function __construct()
	{
		$this->object  = 'dbobject';
		$this->subtype = 'content';
		$this->method  = 'api';

		parent::__construct();
	}

	protected function is_excluded_by_api($test, $root)
	{
		static $excluded = [
			'#__action_logs',
		];

		// Is it one of the blacklisted tables?
		if (in_array($test, $excluded))
		{
			return true;
		}

		// No match? Just include the file!
		return false;
	}

}
PK�!]G�
y�
�
)BackupPlatform/Joomla3x/Filter/Sitedb.phpnu&1i�<?php
/**
 * @package   akeebabackup
 * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

namespace Akeeba\Engine\Filter;

// Protection against direct access
defined('AKEEBAENGINE') || die();

use Akeeba\Engine\Factory;
use Akeeba\Engine\Platform;

/**
 * Add site's main database to the backup set.
 */
class Sitedb extends Base
{
	public function __construct()
	{
		// This is a directory inclusion filter.
		$this->object      = 'db';
		$this->subtype     = 'inclusion';
		$this->method      = 'direct';
		$this->filter_name = 'Sitedb';

		// Add a new record for the core Joomla! database
		// Get core database options
		$configuration = Factory::getConfiguration();

		if ($configuration->get('akeeba.platform.override_db', 0))
		{
			$options = array(
				'port'     => $configuration->get('akeeba.platform.dbport', ''),
				'host'     => $configuration->get('akeeba.platform.dbhost', ''),
				'user'     => $configuration->get('akeeba.platform.dbusername', ''),
				'password' => $configuration->get('akeeba.platform.dbpassword', ''),
				'database' => $configuration->get('akeeba.platform.dbname', ''),
				'prefix'   => $configuration->get('akeeba.platform.dbprefix', ''),
			);
			$driver  = '\\Akeeba\\Engine\\Driver\\' . ucfirst($configuration->get('akeeba.platform.dbdriver', 'mysqli'));
		}
		else
		{
			$options = Platform::getInstance()->get_platform_database_options();
			$driver  = Platform::getInstance()->get_default_database_driver(true);
		}


		$host = $options['host'];
		$port = array_key_exists('port', $options) ? $options['port'] : null;

		if (empty($port))
		{
			$port = null;
		}

		$socket     = null;
		$targetSlot = substr(strstr($host, ":"), 1);

		if ( !empty($targetSlot))
		{
			// Get the port number or socket name
			if (is_numeric($targetSlot) && is_null($port))
			{
				$port = $targetSlot;
			}
			else
			{
				$socket = $targetSlot;
			}

			// Extract the host name only
			$host = substr($host, 0, strlen($host) - (strlen($targetSlot) + 1));
			// This will take care of the following notation: ":3306"
			if ($host == '')
			{
				$host = 'localhost';
			}
		}

		// This is the format of the database inclusion filters
		$entry = array(
			'host'     => $host,
			'port'     => is_null($socket) ? (is_null($port) ? '' : $port) : $socket,
			'username' => $options['user'],
			'password' => $options['password'],
			'database' => $options['database'],
			'prefix'   => $options['prefix'],
			'dumpFile' => 'site.sql',
			'driver'   => $driver
		);

		// We take advantage of the filter class magic to inject our custom filters
		$configuration = Factory::getConfiguration();

		$this->filter_data['[SITEDB]'] = $entry;

		parent::__construct();
	}
}
PK�!]1ɠ
dd/BackupPlatform/Joomla3x/Filter/Excludefiles.phpnu&1i�<?php
/**
 * @package   akeebabackup
 * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

namespace Akeeba\Engine\Filter;

// Protection against direct access
defined('AKEEBAENGINE') || die();

use Akeeba\Engine\Factory;

/**
 * Subdirectories exclusion filter. Excludes temporary, cache and backup output
 * directories' contents from being backed up.
 */
class Excludefiles extends Base
{
	public function __construct()
	{
		$this->object      = 'file';
		$this->subtype     = 'all';
		$this->method      = 'direct';
		$this->filter_name = 'Excludefiles';

		// Get the site's root
		$configuration = Factory::getConfiguration();

		if ($configuration->get('akeeba.platform.override_root', 0))
		{
			$root = $configuration->get('akeeba.platform.newroot', '[SITEROOT]');
		}
		else
		{
			$root = '[SITEROOT]';
		}

		// We take advantage of the filter class magic to inject our custom filters
		$this->filter_data[$root] = array(
			'kickstart.php',
			'error_log',
			'administrator/error_log'
		);

		parent::__construct();
	}

}
PK�!]�3)�1BackupPlatform/Joomla3x/Filter/Excludefolders.phpnu&1i�<?php
/**
 * @package   akeebabackup
 * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

namespace Akeeba\Engine\Filter;

// Protection against direct access
defined('AKEEBAENGINE') || die();

use Akeeba\Engine\Factory;

/**
 * Folder exclusion filter. Excludes certain hosting directories.
 */
class Excludefolders extends Base
{
	public function __construct()
	{
		$this->object      = 'dir';
		$this->subtype     = 'all';
		$this->method      = 'direct';
		$this->filter_name = 'Excludefolders';

		// Get the site's root
		$configuration = Factory::getConfiguration();

		if ($configuration->get('akeeba.platform.override_root', 0))
		{
			$root = $configuration->get('akeeba.platform.newroot', '[SITEROOT]');
		}
		else
		{
			$root = '[SITEROOT]';
		}

		// We take advantage of the filter class magic to inject our custom filters
		$this->filter_data[$root] = [
			'.cagefs',
			'awstats',
			'cgi-bin',
		];

		parent::__construct();
	}

}
PK�!]|��NBackupPlatform/web.confignu&1i�<?xml version="1.0"?>
<!--
    This only works on IIS 7 or later. See https://www.iis.net/configreference/system.webserver/security/requestfiltering/fileextensions
-->
<configuration>
    <system.webServer>
        <security>
            <requestFiltering>
                <fileExtensions allowUnlisted="false" >
                    <clear />
                    <add fileExtension=".html" allowed="true"/>
                </fileExtensions>
            </requestFiltering>
        </security>
    </system.webServer>
</configuration>PK�!]/���::fof.xmlnu&1i�<?xml version="1.0" encoding="UTF-8"?>
<!--~
  ~ @package   akeebabackup
  ~ @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
  ~ @license   GNU General Public License version 3, or later
  -->
<fof>
    <common>
        <container>
            <option name="componentNamespace">Akeeba\Backup</option>
            <option name="factoryClass">FOF40\Factory\BasicFactory</option>
            <option name="scaffolding">0</option>
            <option name="saveScaffolding">0</option>
            <option name="rendererClass">FOF40\Render\FEF</option>
        </container>
    </common>

    <backend>
        <model name="Profiles">
            <behaviors>Filters</behaviors>
        </model>
        <model name="Stats">
            <behaviors>Filters</behaviors>
        </model>
    </backend>
</fof>
PK�!]�<�s^�^�
CHANGELOG.phpnu&1i�<?php die();?>
Akeeba Backup 8.4.1
================================================================================
~ Automatically exclude the .cagefs directory present in some cPanel installations
# [HIGH] Some OneDrive multipart uploads fail
# [LOW] WebDAV: deleting backups may file on some servers
# [HIGH] Box: cannot refresh the authentication token

Akeeba Backup 8.4.0
================================================================================
- Remove the Joomla! version reminder
- Remove CURLOPT_BINARYTRANSFER
+ Separate remote and local quota settings
+ Upload to OneDrive (app-specific folder)
+ Support for uploading to Shared With Me folders in Google Drive
+ Expert options for the Upload to Amazon S3 configuration
+ Self-hosted OAuth2 helpers
+ Alternate Configuration page saving method which doesn't hit maximum POST parameter count limits
+ Option to avoid using `flush()` on broken servers
+ Remove MariaDB MyISAM option PAGE_CHECKSUM from the database dump
+ Restoration: Use transactions to speed up large table restoration
+ Restoration: Automatically downgrade utf8mb4_900_* collations to utf8mb4_unicode_520_ci on MariaDB
+ Restoration: allows you to change the robots (search engine) option
~ Improved mixed– and upper–case database prefix support at backup time
~ Prevent backup failure on push notification error
~ Change the wording of the message when navigating to an off-site directory in the directory browser
~ Improve database dump with table names similar to default values
~ PHP 8.4 compatibility changes
# [MEDIUM] Tables or databases named `0` can cause the database dump to stop prematurely, or not execute at all
# [LOW] Deprecation notice in Configuration Wizard
# [LOW] Restoration error when you have a newer Admin Tools version installed
# [LOW] Cannot transfer files to DreamObjects and Google Storage using the S3 API

Akeeba Backup 8.3.4
================================================================================
+ Workaround for Wasabi S3v4 signatures
# [MEDIUM] Upload to S3 would always use v2 signatures with a custom endpoint.
# [MEDIUM] PHP 8.2 compatibility in Site Transfer Wizard

Akeeba Backup 8.3.3
================================================================================
# [MEDIUM] Resetting corrupt backups can cause a crash
# [LOW] Wrong mixed case detection

Akeeba Backup 8.3.2
================================================================================
! FINAL VERSION FOR JOOMLA! 3
# [LOW] Test FTP Connection does not work in the Configuration page

Akeeba Backup 8.3.1
================================================================================
# [MEDIUM] HTTP PUT might fail on some servers
# [LOW] opcache_invalidate may not invalidate a file
# [LOW] Would not work on 32-bit versions of PHP

Akeeba Backup 8.3.0
================================================================================
+ Support for files and archives over 2GiB (JPA file format 1.3)
~ Stops Joomla from lying about whether our software supports Joomla 4

Akeeba Backup 8.2.8
================================================================================
~ Disabled deprecated API methods
~ Improve the Schedule Automatic Backups page
# [LOW] Joomla ShowOn is not available on ancient Joomla 3.9 builds (e.g. 3.9.0)

Akeeba Backup 8.2.7
================================================================================
# [HIGH] Cannot select extra options for OAuth2-based post-processing engines

Akeeba Backup 8.2.7
================================================================================
+ Option to treat failed uploads as a backup error

Akeeba Backup 8.2.6
================================================================================
! A packaging issue broke the restoration script in backup archives

Akeeba Backup 8.2.5
================================================================================
# [HIGH] Cannot open the Step 1 - Authentication page for OAuth2-based post-processing engines

Akeeba Backup 8.2.4
================================================================================
# [HIGH] Some password managers prevent successful submission of the Site Setup page (you get an error about a missing email address)
# [LOW] Push messages may be untranslated strings when a backup is taken over the API or the frontend backup URL

Akeeba Backup 8.2.3
================================================================================
# [HIGH] BackBlaze B2 single file uploads were broken
# [MEDIUM] Restoration. Administrator email appears as "undefined" in the Site Setup page
# [LOW] Restoration: Wrong message about the email address when the administrator passwords don't match

Akeeba Backup 8.2.2
================================================================================
~ Changed all warnings to much more compact DETAILS elements
~ Much simpler message if you try to run Akeeba Backup on an unsupported (too low) version of PHP.
+ Allow installation on all Joomla 4 versions so you can uninstall the package in some uncommon cases
+ Option about including the latest backup in remote quotas
- Removed the PHP version warning. Joomla already warns you about EOL versions of PHP.
# [LOW] ZIP Archiver, invalid CRC32 calculated for some small files in the installation folder

Akeeba Backup 8.2.1
================================================================================
~ Better warnings about CRC32 for ZIP files on 32-bit versions of PHP
# [HIGH] Quota settings and emails are not processed at the end of the backup process

Akeeba Backup 8.2.0
================================================================================
+ ANGIE for Joomla: reset session and cache options in Site Setup
+ Support for ShowOn to conditionally show options in the Configuration page
# [HIGH] Single part uploads to Azure stopped working

Akeeba Backup 8.1.10
================================================================================
+ Upload to Swift: Support for Keystone v3
# [LOW] "Test FTP connection" button was not correctly applying the passive mode

Akeeba Backup 8.1.9
================================================================================
+ More informative error messages for database connection issues during restoration
~ Workaround for utf8_encode and _decode being deprecated in PHP 8.2
# [LOW] Restoration: You were shown separate port and socket options which were not taken into account
# [MEDIUM] Restoration: Using a custom port or socket might result in the wrong hostname being written in the restored site's configuration file
# [MEDIUM] Possible infinite loop on PHP 8 during DB restoration if a SQL file is missing
# [LOW] Invalid SQL dump if we cannot get the create commands for a function, procedure or trigger

Akeeba Backup 8.1.8
================================================================================
+ Restoration: Warn about missing mysqli / PDO MySQL and REFUSE to proceed
# [HIGH] Cannot download file from Amazon S3
# [LOW] PHP Warning when backing up a database (purely cosmetic issue)

Akeeba Backup 8.1.7
================================================================================
+ Restoration: Warn about missing mysqli / PDO MySQL and REFUSE to proceed
# [HIGH] Cannot download file from Amazon S3
# [LOW] PHP Warning when backing up a database (purely cosmetic issue)

Akeeba Backup 8.1.6
================================================================================
# [HIGH] Cannot connect to databases on localhost using the default named pipe
# [MEDIUM] Custom Amazon S3 regions would not work with custom endpoints

Akeeba Backup 8.1.5
================================================================================
+ Support for custom Amazon S3 regions
# [HIGH] Pagination in the Manage Backups page was broken

Akeeba Backup 8.1.4
================================================================================
+ Much improved FTP functions for uploading backup archives and transferring sites
+ Upload to Azure BLOB Storage now supports chunked uploads, files up to 190.7TB (up from 64Mb)
+ OneDrive for Business: you can now use Drives other than your personal
~ Stricter conditions for determining when to show the “Manage remotely stored files” button in Manage Backups
# [MEDIUM] OneDrive: Uploads may fail if they are between 4Mb and 100Mb

Akeeba Backup 8.1.3
================================================================================
+ Restoration: ANGIE now applies very high memory and execution time limits to prevent some timeout / memory outage issues on most hosts.
+ Restoration: ANGIE now warns you if you leave the database connection information empty
+ Option to set a really large PHP memory limit during backup
~ More helpful and forceful installation abortion message when you try to install this package on Joomla 4.1 or later.
# [LOW] The JPS archiver would show warnings about unreadable files when archiving directories without any files in them.

Akeeba Backup 8.1.2
================================================================================
~ Make it clearer that you need Akeeba Backup 9 on Joomla 4.
# [HIGH] Uploading to OVH is broken on many servers not using a proxy
# [HIGH] Wrong RewriteBase set up in the .htaccess Maker when restoring a Joomla site with Admin Tools Professional installed

Akeeba Backup 8.1.1
================================================================================
~ PHP 8.1 compatibility changes

Akeeba Backup 8.1.0
================================================================================
+ Allow using [REMOTESTATUS] in the email subject, not just the body
+ Joomla restoration: modify domains in the Admin Tools' Allowed Domains and server config maker features if necessary
# [HIGH] Problems restoring if a table name ends in 0 when another table with an identical name EXCEPT the trailing zero is also being backed up
# [HIGH] Backing up to SQL: indices would not have the correct table name prefix
# [HIGH] Backing up as SQL: the query for finder_taxonomy does not use the correct prefix
# [MEDIUM] Log Priorities global configuration option got mangled restoring a Joomla 4 site
# [HIGH] PHP fatal error on PHP 8 if the output directory does not exist
# [LOW] Occasional display issue on Chromium browsers with the database and file / folder filter pages.
# [LOW] RackSpace CloudFiles: some hosts change the case of HTTP headers

Akeeba Backup 8.0.15
================================================================================
+ Support for MySQL 8 invisible columns
# [LOW] Rare type error under PHP 8 during restoration
# [LOW] Backend still tries to load PieCon, causing an error to be logged

Akeeba Backup 8.0.14
================================================================================
- Remove piecon (pie graph favicon showing the backup progress)
~ JSON API: Forcibly use the ‘json’ origin everywhere
~ JSON API: Throw an error if the backup ID sent to stepBackup does not exist
~ JSON API: Improved backup IDs prevent a number of JSON API issues

Akeeba Backup 8.0.13
================================================================================
- Removed iDriveSync; the service has been discontinued by the provider.
- Removed the “Archive integrity check” feature.
~ Dropbox connector updated to require TLS v1.2
~ Improved the display of the files and folders filters page
# [LOW] Check failed backups: All Super Users were notified even when an email was supplied

Akeeba Backup 8.0.12
================================================================================
# [LOW] PHP 8 error if the output directory is empty

Akeeba Backup 8.0.11
================================================================================
~ Remove dash from automatically generated random values for archive naming
+ Increase the maximum Size Quota limit to 1Pb
+ Support for Joomla proxy configuration
# [MEDIUM] Cannot restore on PHP 8 if Two Factor Authentication is enabled in any user account
# [HIGH] Backing up to Box, Dropbox, Google Drive or OneDrive may not be possible if you are using an add-on Download ID

Akeeba Backup 8.0.10
================================================================================
# [HIGH] Uninstallation broken on Joomla 4 due to different installation script event handling (wow, they even broke components' uninstallation, not just the packages!).
# [LOW] Warning in Manage Backups page if you have deleted the backup profile used to take a backup listed there

Akeeba Backup 8.0.9
================================================================================
~ Changes in Joomla 4.0.0-RC5 broke the date and time input fields. Now using native HTML 5 controls.
# [HIGH] Uninstallation broken on Joomla 4 due to different installation script event handling.

Akeeba Backup 8.0.8
================================================================================
! Exception when you do not have the package extension installed on your site. Shouldn't happen unless you've messed with your database.

Akeeba Backup 8.0.7
================================================================================
+ Restoration: information about disabling the password protection.
~ Remove ROW_FORMAT during backup and restoration, makes it easier restoring sites using InnoDB across different MySQL server versions
~ Joomla changed the location of cacert.pem, breaking backup upload to remote storage on some servers
# [HIGH] Remote JSON API v2 fails on PHP 8
# [MEDIUM] Tables with only numbers in their names cause the backup to fail

Akeeba Backup 8.0.6
================================================================================
! Encrypted settings could not be read

Akeeba Backup 8.0.5
================================================================================
- Removed Upload to pCloud
+ Only show failed backups' log files in ALICE
+ Stealth Mode support in integrated restoration
# [MEDIUM] Backend backup may fail when using multiple profiles with different output directories.
# [LOW] MySQL spatial data might be impossible to restore if there is a collation mismatch between the origin and target server.
# [LOW] PHP 8 could still throw an error while backing up under some rare circumstances
# [LOW] Fixed fatal error while sending backup email notification under certain server configuration
# [LOW] PHP warning when the site's root is in an absolute root subdirectory (e.g. /site instead)

Akeeba Backup 8.0.4
================================================================================
# [MEDIUM] Failed backups check would show old failed backups again after visiting Akeeba Backup's Control Panel page
# [MEDIUM] Backup on Update message was never shown
# [LOW] JSON API could return additional information around the JSON content when XDebug is enabled
# [LOW] Backup on Update boolean controls appear inverted (Yes is No and vice-versa)

Akeeba Backup 8.0.3
================================================================================
~ Rewritten installer plugin
~ Converted all tables to InnoDB for better performance
# [HIGH] Cannot take split archive backups under PHP 8
# [HIGH] Backup on Update message shown to non-Super Users
# [HIGH] Latest backup restoration backend menu item didn't work
# [LOW] Annoying error message, without any real consequence, shown when clicking any feature button before checking the output folder security has completed in the background

Akeeba Backup 8.0.2
================================================================================
! Update fails on some hosts which use opcache if the any of our software's installer plugin is enabled, you have gone through the Joomla Control Panel (with the extension updates quickicon plugin enabled) or the Extensions Update page before installing the new version, either as an automatic update or by manual installation (upload & install or install from URL).
~ Will no longer uninstall FOF 3 even if it's no longer needed due to broken THIRD PARTY extensions using it.
~ Workaround for Joomla bug which may not install the included FEF version 2 framework completely, leading to the component being broken after the update.
~ Servers with opcache may report that FOF 4 classes are missing even though they are actually there.

Akeeba Backup 8.0.1
================================================================================
! Update could fail on sites with old plugins we have removed years ago still installed

Akeeba Backup 8.0.0
================================================================================
+ Rewritten with FOF 4
+ Now using FEF 2 with a common JavaScript library across all Akeeba extensions
+ Renamed ViewTemplates to tmpl (Joomla 4 convention, with fallback code for Joomla 3)
+ Yes/No options in the component and plugin options now work correctly under Joomla 4.0 beta 7 and later
# [HIGH] Dropbox for Business wouldn't work with the new scoped access tokens
# [HIGH] Dropbox refresh token would disappear after the first refresh, making it impossible to use Dropbox reliably

Akeeba Backup 7.5.2
================================================================================
+ Rewritten Backup on Update plugin for improved UX (gh-685)
+ Joomla 4: backup profile selection uses Choices.js for easier navigation among many backup profiles
~ Internals: normalised use JVERSION conditionals
~ Document Microsoft Edge “sleeping tabs” and workarounds for long-running backups in background browser tabs
~ Improved CHANGELOG layout in the Control Panel page
~ Code modernisation: using built-in random_bytes() instead of OpenSSL or mcrypt for random number generation
# [HIGH] Import from S3: you cannot select .jps files
# [MEDIUM] Frozen backups toggle wouldn't work on Joomla 4
# [LOW] Import from S3: invisible breadcrumbs in Dark Mode
# [LOW] Recommended PHP version was shown as 7.3 instead of 7.4
# [LOW] Unable to access the component on Joomla 4 when using the PDOMySQL database driver with Site Debug enabled, see https://github.com/joomla/joomla-cms/issues/32019

Akeeba Backup 7.5.1
================================================================================
+ Post-backup emails can now display the total backup size and the approximate size of each part file
# [HIGH] The Joomla 4 console plugin could not install due to a bug in Joomla 4's plugins installer code
# [HIGH] Backup failure to S3 with a PHP Type Error when the Dual Stack option has no value
# [HIGH] Uploading to Dropbox would fail if you linked your Dropbox account after December 2020
# [LOW] No list of backup files in the post-backup email when using a post-processing engine
# [LOW] Manage Remotely Stored Files actions could fail on Box, Dropbox, OneDrive and Google Drive if the access token had expired in the meantime.

Akeeba Backup 7.5.0.1
================================================================================
! [HIGH] The Backup on Update plugin can cause the site to fail to load

Akeeba Backup 7.5.0
================================================================================
- Dropped support for PHP 7.1.0
+ Dropbox is now using the scoped API access.
+ Amazon S3: Added support for Dual Stack option (use of IPv6 when available)
+ Joomla 4 CLI (joomla.php) support – full CLI client to Akeeba Backup
~ Add PHP 8.0 in the list of known PHP versions, recommend PHP 7.4 or later
~ Remove the JPS and ANGIE password fields from the Backup Now page. You can still configure these features in the backup profile's Configuration page.
# [MEDIUM] PHP 8: fatal error uploading to Amazon S3, CloudFiles
# [LOW] Using [SITENAME] in a backup archive name resulted in a single dash being output.
# [LOW] UI elements in the the Files and Folders Exclusion pages would still show native tooltips with HTML tags in them.
# [HIGH] Joomla 4 beta 6 changes how the session works, breaking everything.

Akeeba Backup 7.4.0.1
================================================================================
! Akeeba Backup Core: cannot access the plugin or take a backup because of a PHP error due to an incorrect reference to a Pro-only class.
# [LOW] Backup failure with an error if you import a profile that uses a post-processing engine created with the Pro version into the Core version which does not have this post-processing engine.

Akeeba Backup 7.4.0
================================================================================
+ Files and Directories Exclusion: mark folder and file symlinks as such [gh-676]
+ Automatically rewrite the Output Directory using site path variables such as [SITEROOT] for portability [gh-678]
+ Automatically rewrite the Off-site Folders Inclusion using site path variables for portability
+ Remote backup JSON API version 2
+ ANGIE: Added feature to resume restoring the database if an error occurs
~ Deprecated Upload to pCloud
~ Removed tooltips from Database Tables Exclusion and Files and Folders Exclusion pages to clean up the UI
~ Using nullable TIMESTAMP fields instead of zero dates
# [MEDIUM] Recent Chrome and Chromium-based browsers open OAuth2 windows without opener information, making linking to Google Drive, Dropbox etc impossible without manually copying the tokens (the button causes you to log out of the site)
# [LOW] Files and Directories Exclusion: the folder up is not clickable / doesn't do anything [gh-675]
# [LOW] Scheduling information button appears in the Configuration Wizard's finale page in the Core version

Akeeba Backup 7.3.2.1
================================================================================
! CLI backups broken in version 7.3.2
# [LOW] PHP notices from Joomla core code when running akeeba-check-failed.php in CLI

Akeeba Backup 7.3.2
================================================================================
- Removed update notifications inside the component
~ Normalized the default backup description under all backup methods (backend, frontend, CLI, JSON API)
# [HIGH] WebDAV fails to upload because of the wrong absolute URL being calculated
# [MEDIUM] The Resume and Cancel buttons in the backend backup didn't work due to a typo in the JavaScript
# [MEDIUM] Restoring a JPS backup archive through the integrated restoration was broken if it contained charactes other than a-z, A-Z, 0-9, dash, dot or underscore.
# [LOW] pCloud was erroneously listed in the free of charge Core version (it requires a paid subscription and was thus unusable)

Akeeba Backup 7.3.1
================================================================================
- Removed the System - Akeeba Backup Update Check plugin
~ Improved unhandled PHP exception error page
# [HIGH] Media query strings missing from JavaScript, causing issues to people upgrading from 7.2.2 or earlier.
# [LOW] Frontend backup URL does not work if the secret key contains the plus sign (+) character due to a PHP bug.

Akeeba Backup 7.3.0
================================================================================
+ S3: Add support for Cape Town and Milan regions
+ Inherit the base font size instead of defining a fixed one
+ Added feature to "freeze" some backup records to keep them indefinitely
- Removed support for Internet Explorer
~ Improve default header and body fonts for similar cross-platform "feel" without the need to use custom fonts.
~ Rendering improvements
~ Loading all JavaScript defered
~ Do not show the Backup on Update icon when Joomla is in record add / edit mode (main menu hidden and status bar locked).
~ Adjust size of control panel icons
~ More clarity in the in-component update notifications, explaining they come from Joomla itself
# [HIGH] Replacing (not just removing) AddHandler/SetHandler lines would fail during restoration
# [MEDIUM] Fetching back to server the archives from these provides would result in invalid archives: Amazon S3, Backblaze, Cloudfiles, OVH, Swift
# [MEDIUM] Greedy RegEx match in database dump could mess up views containing the literal ' view ' (word "view" surrounded by spaces) in their definition.

Akeeba Backup 7.2.2
================================================================================
+ Automatic UTF8MB4 character encoding downgrades from MySQL 8 to 5.7/5.6/5.5 on restoration.
# [LOW] The package would install on unsupported PHP versions 5.6 and 7.0 and Joomla 3.8, leading to errors
# [HIGH] The System - Akeeba Backup Update Check plugin throws a fatal error since version 7.1.4 when an update is available

Akeeba Backup 7.2.1
================================================================================
~ Small change in the FOF library to prevent harmless but confusing and annoying errors from appearing during upgrade
~ The following items are carried over from unpublished version 7.2.0
+ Restoration: Enable UTF8MB4 compatibility detection by default
~ Minimum requirements raised to PHP 7.1, Joomla 3.9
~ Using Joomla's cacert.pem instead of providing our own copy
~ Component Options page looks a bit nicer on Joomla 4
~ Joomla 4: fix profile selection drop-down display
# [HIGH] The restoration script can't read unquoted numeric values from the configuration.php file (used in Joomla 4)
# [HIGH] Joomla 4: Using the Smart Search filter during backup makes it impossible to use Smart Search on the restored site.
# [HIGH] Import from S3: infinite redirection loop
# [LOW] Very rare backup failures with a JS error
# [LOW] Unhandled exception page was incompatible with Joomla 4

Akeeba Backup 7.2.0
================================================================================
+ Restoration: Enable UTF8MB4 compatibility detection by default
~ Minimum requirements raised to PHP 7.1, Joomla 3.9
~ Using Joomla's cacert.pem instead of providing our own copy
~ Component Options page looks a bit nicer on Joomla 4
~ Joomla 4: fix profile selection drop-down display
# [HIGH] The restoration script can't read unquoted numeric values from the configuration.php file (used in Joomla 4)
# [HIGH] Joomla 4: Using the Smart Search filter during backup makes it impossible to use Smart Search on the restored site.
# [HIGH] Import from S3: infinite redirection loop
# [LOW] Very rare backup failures with a JS error
# [LOW] Unhandled exception page was incompatible with Joomla 4

Akeeba Backup 7.1.4
================================================================================
~ Now getting Super Users list using core Joomla API instead of direct database queries
# [LOW] Multipart upload to BackBlaze B2 might fail due to a silent B2 behavior change
# [LOW] OneDrive upload failure if a part upload starts >3600s after token issuance

Akeeba Backup 7.1.3
================================================================================
~ Got rid of the Optimize JavaScript feature.

Akeeba Backup 7.1.2
================================================================================
# [LOW] The Optimize JavaScript was not working properly on some low end servers due to the way browsers parse deferred scripts at the bottom of the HTML body

Akeeba Backup 7.1.1
================================================================================
~ Possible exception when the user has erroneously put their backup output directory to the site's root with open_basedir restrictions restricting access to its parent folder.
# [HIGH] The Optimize JavaScript option causes a missing class fatal error on Joomla! 3.8 sites
# [LOW] Missing icon in Manage Backups page, Import Archive toolbar button

Akeeba Backup 7.1.0
================================================================================
+ Automatic security check of the backup output directory
+ Automatic JavaScript bundling for improved performance
~ Improved storage of temporary data during backup [akeeba/engine#114]
~ Log files now have a .php extension to prevent unauthorized access in very rare cases
~ Enforce the recommended, sensible security measures when using the default backup output directory
~ Ongoing JavaScript refactoring
~ Google Drive: fetch up to 100 shared drives (previously: up to 10)
# [HIGH] An invalid output directory (e.g. by importing a backup profile) will cause a fatal exception in the Control Panel (gh-667)
# [MEDIUM] CloudFiles post-processing engine: Fixed file uploads
# [MEDIUM] Swift post-processing engine: Fixed file uploads
# [LOW] Send by Email reported a successful email sent as a warning
# [LOW] Database dump: foreign keys' (constraints) and local indices' names did not get their prefix replaced like tables, views etc do

Akeeba Backup 7.0.2
================================================================================
~ Log the full path to the computed site's root, without <root> replacement
~ Use Chosen in the Control Panel's profile selection page
# [HIGH] Core (free of charge) version only: the PayPal donation link included a tracking pixel. Changed to donation link, without tracking.
# [HIGH] Core (free of charge) version only: the system/akeebaupdatecheck plugin would always throw an error
# [HIGH] Restoration will fail if a table's name is a superset of another table's name e.g. foo_example_2020 being a superset of foo_example_2.
# [MEDIUM] WebDav post-processing engine: first backup archive was always uploaded on the remote root, ignoring any directory settings

Akeeba Backup 7.0.1
================================================================================
- pCloud: removing download to browser (cannot work properly due to undocumented API restrictions)
# [HIGH] An error about not being able to open a file with an empty name occurs when taking a SQL-only backup but there's a row over 1MB big
# [LOW] Schedule Automatic Backups shown in the Configuration page of the Core version
# [LOW] A secret work would not be proposed when one was not set or set to something insecure
# [LOW] The akeeba-altbackup.php and akeeba-altcheck-failed.php CRON scripts falsely report front-end backup is not enabled
# [LOW] Dark Mode: modal close icon was invisible both in the backup software and during restoration
# [LOW] Fixed automatically filling DropBox tokens after OAuth authentication

Akeeba Backup 7.0.0
================================================================================
+ Custom description for backups taken with the Backup on Update plugin
+ Remove TABLESPACE and DATA|INDEX DIRECTORY table options during backup
# [LOW] Fixed applying quotas for obsolete backups

Akeeba Backup 7.0.0.rc1
================================================================================
+ Upload to OVH now supports Keystone v3 authentication, mandatory starting mid-January 2020
# [HIGH] An error in an early backup domain could result in a forever-running backup
# [HIGH] DB connection errors wouldn't result in the backup failing, as it should be doing

Akeeba Backup 7.0.0.b3
================================================================================
+ Common PHP version warning scripts
+ Reinstated support for pCloud after they fixed their OAuth2 server
~ Improved Dark Mode
~ Improved PHP 7.4 compatibility
~ Improved Joomla 4 styling
~ Clearer message when setting decryption fails in CLI backup script
~ Remove JavaScript eval() from FileFilters page
# [HIGH] The database dump was broken with some versions of PCRE (e.g. the one distributed with Ubuntu 18.04)
# [HIGH] Site Transfer Wizard inaccessible on case-sensitive filesystems

Akeeba Backup 7.0.0.b2
================================================================================
- Removed pCloud support
+ ANGIE: Options to remove AddHandler lines on restoration
# [MEDIUM] Fixed OAuth authentication flow
# [MEDIUM] Fixed fatal error under Joomla 3.8.x

Akeeba Backup 7.0.0.b1
================================================================================
+ Amazon S3 now supports Bahrain and Stockholm regions
+ Amazon S3 now supports Intelligent Tiering, Glacier and Deep Archive storage classes
+ Google Storage now supports the nearline and coldline storage classes
+ Manage Backups: Improved performance of the Transfer (re-upload to remote storage) feature
+ Windows Azure BLOB Storage: download back to server and download to browser are now supported
+ New OneDrive integration supports both regular OneDrive and OneDrive for Business
+ pCloud support
+ Support for Dropbox for Business
+ Dark Mode support
+ Support for Joomla 4 Download Key management in the Update Sites page
+ Minimum required PHP version is now 5.6.0
~ All views have been converted to Blade for easier development and better future-proofing
~ The integrated restoration feature is now only available in the Professional version
~ The archive integrity check feature is now only available in the Professional version
~ The front-end legacy backup API and the Remote JSON API are now available only in the Professional version and can be enabled / disabled independently of each other
~ The Site Transfer Wizard is now only available in the Professional version
~ SugarSync integration: you now need to provide your own access keys following the documentation instructions
~ Backup error handling and reporting (to the log and to the interface) during backup has been improved.
~ The Test FTP/SFTP Connection buttons now return much more informative error messages.
~ Manage Backups: much more informative error messages if the Transfer to remote storage process fails.
~ The backup and log IDs will follow the numbering you see in the left hand column of the Manage Backups page.
~ Manage Backups: The Remote File Management page is now giving better, more accurate information.
~ Manage Backups: Fetch Back To Server was rewritten to gracefully deal with more problematic cases.
~ Joomla 4: The backup on update plugin no longer displayed correctly after J4 changed its template, again.
~ Joomla 4: The backup quick icon was displayed in the wrong place after J4 changed its template, again and also partially broke backwards compatibility to how quick icon plugins work.
~ Removed AES encapsulations from the JSON API for security reasons. We recommend you always use HTTPS with the JSON API.
# [HIGH] CLI (CRON) scripts could sometimes stop with a Joomla crash due to Joomla's mishandling of the session under CLI.
# [HIGH] Changing the database prefix would not change it in the referenced tables inside PROCEDUREs, FUNCTIONs and TRIGGERs
# [HIGH] Backing up PROCEDUREs, FUNCTIONs and TRIGGERs was broken
# [MEDIUM] Database only backup of PROCEDUREs, FUNCTIONs and TRIGGERs does not output the necessary DELIMITER commands to allow direct import
# [MEDIUM] PHP Notice at the end of each backup step due to double attempt to close the database connection.
# [MEDIUM] BackBlaze B2: upload error when chunk size is higher than the backup archive's file size
# [LOW] Manage Backups: downloading a part file from S3 beginning with text data would result in inline display of the file instead of download.
PK�!]��ѡ��
sql/.htaccessnu&1i�<IfModule !mod_authz_core.c>
Order deny,allow
Deny from all
</IfModule>
<IfModule mod_authz_core.c>
  <RequireAll>
    Require all denied
  </RequireAll>
</IfModule>
PK�!]#v�jjsql/xml/mysql.xmlnu&1i�<?xml version="1.0" encoding="UTF-8"?>
<!--~
  ~ @package   akeebabackup
  ~ @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
  ~ @license   GNU General Public License version 3, or later
  -->

<schema>
    <!-- Metadata -->
    <meta>
        <!-- Supported driver types -->
        <drivers>
            <driver>mysql</driver>
            <driver>mysqli</driver>
            <driver>pdomysql</driver>
        </drivers>
    </meta>

    <!-- SQL commands to run on installation and update -->
    <sql>
        <!-- Create the #__ak_profiles table if it's missing -->
        <action table="#__ak_profiles" canfail="0">
            <condition type="missing" value="" />
            <query><![CDATA[
CREATE TABLE `#__ak_profiles` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`description` varchar(255) NOT NULL,
`configuration` longtext,
`filters` longtext,
`quickicon` tinyint(3) NOT NULL DEFAULT '1',
PRIMARY KEY (`id`)
) DEFAULT COLLATE utf8_general_ci;
            ]]></query>
        </action>

        <!-- Insert into #__ak_profiles if id=1 is not there -->
        <action table="#__ak_profiles" canfail="1">
            <condition type="equals" operator="not" value="1"><![CDATA[
SELECT COUNT(*) FROM `#__ak_profiles` WHERE `id` = 1;
            ]]></condition>

            <query><![CDATA[
INSERT IGNORE INTO `#__ak_profiles`
(`id`,`description`, `configuration`, `filters`, `quickicon`) VALUES
(1,'Default Backup Profile','','',1);
            ]]></query>
        </action>

        <!-- Create #__ak_stats if it's missing -->
        <action table="#__ak_stats" canfail="0">
            <condition type="missing" value="" />
            <query><![CDATA[
CREATE TABLE `#__ak_stats` (
	`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
	`description` varchar(255) NOT NULL,
	`comment` longtext,
	`backupstart` timestamp NULL DEFAULT NULL,
	`backupend` timestamp NULL DEFAULT NULL,
	`status` enum('run','fail','complete') NOT NULL DEFAULT 'run',
	`origin` varchar(30) NOT NULL DEFAULT 'backend',
	`type` varchar(30) NOT NULL DEFAULT 'full',
	`profile_id` bigint(20) NOT NULL DEFAULT '1',
	`archivename` longtext,
	`absolute_path` longtext,
	`multipart` int(11) NOT NULL DEFAULT '0',
	`tag` varchar(255) DEFAULT NULL,
	`backupid` varchar(255) DEFAULT NULL,
	`filesexist` tinyint(3) NOT NULL DEFAULT '1',
	`remote_filename` varchar(1000) DEFAULT NULL,
	`total_size` bigint(20) NOT NULL DEFAULT '0',
	`frozen` tinyint(1) NOT NULL DEFAULT '0',
	`instep` tinyint(1) NOT NULL DEFAULT '0',
	PRIMARY KEY (`id`),
	KEY `idx_fullstatus` (`filesexist`,`status`),
	KEY `idx_stale` (`status`,`origin`)
) DEFAULT COLLATE utf8_general_ci;
            ]]></query>
        </action>

        <!-- Create #__ak_storage if it's missing -->
        <action table="#__ak_storage" canfail="0">
            <condition type="missing" value="" />
            <query><![CDATA[
CREATE TABLE `#__ak_storage` (
	`tag` varchar(255) NOT NULL,
	`lastupdate` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
	`data` longtext,
	PRIMARY KEY (`tag`(100))
) DEFAULT COLLATE utf8_general_ci;
            ]]></query>
        </action>

        <!-- Add the backupid column to #__ak_stats if it's missing -->
        <action table="#__ak_stats" canfail="1">
            <condition type="missing" value="backupid" />
            <query><![CDATA[
ALTER TABLE `#__ak_stats`
ADD COLUMN `backupid` varchar(255) DEFAULT NULL
AFTER `tag`
            ]]></query>
        </action>

        <!-- Add the quickicon column to #__ak_profiles if it's missing -->
        <action table="#__ak_profiles" canfail="1">
            <condition type="missing" value="quickicon" />
            <query><![CDATA[
ALTER TABLE `#__ak_profiles`
ADD COLUMN `quickicon` tinyint(3) NOT NULL DEFAULT '1'
AFTER `filters`
            ]]></query>
        </action>

        <!-- Shorten the primary key before upgrading to utf8mb4 (in Joomla! 3.5+) -->
        <action table="#__ak_storage" canfail="1">
            <condition type="utf8mb4upgrade" />
            <query><![CDATA[
ALTER TABLE `#__ak_storage` DROP PRIMARY KEY;
            ]]></query>
            <query><![CDATA[
ALTER TABLE `#__ak_storage` ADD PRIMARY KEY (`tag`(100));
            ]]></query>
        </action>

        <!-- Add the frozen column to #__ak_stats if it's missing -->
        <action table="#__ak_stats" canfail="1">
            <condition type="missing" value="frozen" />
            <query><![CDATA[
ALTER TABLE `#__ak_stats` ADD COLUMN `frozen` tinyint(1) DEFAULT '0';
            ]]></query>
        </action>

        <!-- Add the instep column to #__ak_stats if it's missing -->
        <action table="#__ak_stats" canfail="1">
            <condition type="missing" value="instep" />
            <query><![CDATA[
ALTER TABLE `#__ak_stats` ADD COLUMN `instep` tinyint(1) DEFAULT '0';
            ]]></query>
        </action>

        <!-- Change datetime fields to nullable -->
        <action table="#__ak_stats" canfail="1">
            <condition type="nullable" value="backupstart" operator="not"/>
            <query><![CDATA[
        ALTER TABLE `#__ak_stats` MODIFY `backupstart` TIMESTAMP NULL DEFAULT NULL;
        ]]></query>
            <query><![CDATA[
        UPDATE `#__ak_stats` SET `backupstart` = NULL WHERE `backupstart` = '0000-00-00 00:00:00';
        ]]></query>
        </action>

        <action table="#__ak_stats" canfail="1">
            <condition type="nullable" value="backupend" operator="not"/>
            <query><![CDATA[
        ALTER TABLE `#__ak_stats` MODIFY `backupend` TIMESTAMP NULL DEFAULT NULL;
        ]]></query>
            <query><![CDATA[
        UPDATE `#__ak_stats` SET `backupend` = NULL WHERE `backupend` = '0000-00-00 00:00:00';
        ]]></query>
        </action>

        <!-- Nuke any old record inside the ak_storage table -->
        <action table="#__ak_storage" canfail="1">
            <condition type="equals" operator="not" value="0"><![CDATA[
SELECT COUNT(*) FROM `#__ak_storage` WHERE `tag` != "lastupdate";
            ]]></condition>

            <query><![CDATA[
DELETE FROM `#__ak_storage` WHERE `tag` != "lastupdate";
            ]]></query>
        </action>

        <!-- 8.0.4 :: Convert tables to InnoDB -->
        <action table="#__ak_profiles" canfail="1">
            <condition type="equals" operator="not" value="1"><![CDATA[
SELECT COUNT(*) FROM `INFORMATION_SCHEMA`.`TABLES` WHERE (`TABLE_NAME` = '#__ak_profiles') AND (`TABLE_SCHEMA` = DATABASE()) AND (`ENGINE` = 'InnoDB');
            ]]></condition>
            <query><![CDATA[
ALTER TABLE `#__ak_profiles` ENGINE InnoDB;
            ]]></query>
        </action>

        <action table="#__ak_stats" canfail="1">
            <condition type="equals" operator="not" value="1"><![CDATA[
SELECT COUNT(*) FROM `INFORMATION_SCHEMA`.`TABLES` WHERE (`TABLE_NAME` = '#__ak_stats') AND (`TABLE_SCHEMA` = DATABASE()) AND (`ENGINE` = 'InnoDB');
            ]]></condition>
            <query><![CDATA[
ALTER TABLE `#__ak_stats` ENGINE InnoDB;
            ]]></query>
        </action>

        <action table="#__ak_storage" canfail="1">
            <condition type="equals" operator="not" value="1"><![CDATA[
SELECT COUNT(*) FROM `INFORMATION_SCHEMA`.`TABLES` WHERE (`TABLE_NAME` = '#__ak_storage') AND (`TABLE_SCHEMA` = DATABASE()) AND (`ENGINE` = 'InnoDB');
            ]]></condition>
            <query><![CDATA[
ALTER TABLE `#__ak_storage` ENGINE InnoDB;
            ]]></query>
        </action>
    </sql>
</schema>PK�!]|��Nsql/web.confignu&1i�<?xml version="1.0"?>
<!--
    This only works on IIS 7 or later. See https://www.iis.net/configreference/system.webserver/security/requestfiltering/fileextensions
-->
<configuration>
    <system.webServer>
        <security>
            <requestFiltering>
                <fileExtensions allowUnlisted="false" >
                    <clear />
                    <add fileExtension=".html" allowed="true"/>
                </fileExtensions>
            </requestFiltering>
        </security>
    </system.webServer>
</configuration>PK�!]L�J���sql/index.htmlnu&1i�<!--~
  ~ @package   akeebabackup
  ~ @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
  ~ @license   GNU General Public License version 3, or later
  -->

<html><head><title></title></head><body></body></html>PK�!]X�%!33
config.xmlnu&1i�<?xml version="1.0" encoding="utf-8"?>
<!--~
  ~ @package   akeebabackup
  ~ @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
  ~ @license   GNU General Public License version 3, or later
  -->
<config addfieldpath="/administrator/components/com_akeeba/fields">
	<fieldset
			name="permissions"
			label="JCONFIG_PERMISSIONS_LABEL"
			description="JCONFIG_PERMISSIONS_DESC"
	>

		<field
				name="rules"
				type="rules"
				label="JCONFIG_PERMISSIONS_LABEL"
				class="inputbox"
				filter="rules"
				component="com_akeeba"
				section="component"/>
	</fieldset>

	<fieldset name="backend" label="COM_AKEEBA_CONFIG_BACKEND_HEADER_LABEL"
			  description="COM_AKEEBA_CONFIG_BACKEND_HEADER_DESC">

		<field name="dark_mode" type="list" default="-1"
			   label="COM_AKEEBA_CONFIG_BACKEND_DARKMODE_LABEL"
			   description="COM_AKEEBA_CONFIG_BACKEND_DARKMODE_DESC">
			<option value="-1">COM_AKEEBA_CONFIG_BACKEND_DARKMODE_AUTO</option>
			<option value="0">COM_AKEEBA_CONFIG_BACKEND_DARKMODE_NEVER</option>
			<option value="1">COM_AKEEBA_CONFIG_BACKEND_DARKMODE_ALWAYS</option>
		</field>

		<field name="dateformat" type="text" default="" size="30"
			   label="COM_AKEEBA_CONFIG_DATEFORMAT_LABEL"
			   description="COM_AKEEBA_CONFIG_DATEFORMAT_DESC"/>

		<field name="localtime" type="fancyradio" default="1"
			   label="COM_AKEEBA_CONFIG_BACKEND_LOCALTIME_LABEL"
			   description="COM_AKEEBA_CONFIG_BACKEND_LOCALTIME_DESC"
			   class="btn-group btn-group-yesno">
			<option value="0">JNo</option>
			<option value="1">JYes</option>
		</field>

		<field name="timezonetext" type="list" default="T"
			   label="COM_AKEEBA_CONFIG_BACKEND_TIMEZONETEXT_LABEL"
			   description="COM_AKEEBA_CONFIG_BACKEND_TIMEZONETEXT_DESC">
			<option value="">COM_AKEEBA_CONFIG_BACKEND_TIMEZONETEXT_NONE</option>
			<option value="T">COM_AKEEBA_CONFIG_BACKEND_TIMEZONETEXT_ABBREVIATION</option>
			<option value="\G\M\TP">COM_AKEEBA_CONFIG_BACKEND_TIMEZONETEXT_GMTOFFSET</option>
		</field>

		<field name="showDeleteOnRestore" type="fancyradio" default="0"
			   label="COM_AKEEBA_CONFIG_BACKEND_SHOWDELETEONRESTORE_LABEL"
			   description="COM_AKEEBA_CONFIG_BACKEND_SHOWDELETEONRESTORE_DESC"
			   class="btn-group btn-group-yesno">
			<option value="0">JNo</option>
			<option value="1">JYes</option>
		</field>

		<field name="no_flush"
			   type="fancyradio"
			   default="0"
			   label="COM_AKEEBA_CONFIG_SECURITY_NO_FLUSH_LABEL"
			   description="COM_AKEEBA_CONFIG_SECURITY_NO_FLUSH_DESCRIPTION"
			   class="btn-group btn-group-yesno">
			<option value="0">JNo</option>
			<option value="1">JYes</option>
		</field>

	</fieldset>

	<fieldset name="frontend" label="COM_AKEEBA_CONFIG_FRONTEND_HEADER_LABEL"
			  description="COM_AKEEBA_CONFIG_FRONTEND_HEADER_DESC">

		<field name="legacyapi_enabled" type="fancyradio" default="0"
			   label="COM_AKEEBA_CONFIG_LEGACYAPI_ENABLED_LABEL"
			   description="COM_AKEEBA_CONFIG_LEGACYAPI_ENABLED_DESC"
			   class="btn-group btn-group-yesno">
			<option value="0">JNo</option>
			<option value="1">JYes</option>
		</field>

		<field name="jsonapi_enabled" type="fancyradio" default="0"
			   label="COM_AKEEBA_CONFIG_JSONAPI_ENABLED_LABEL"
			   description="COM_AKEEBA_CONFIG_JSONAPI_ENABLED_DESC"
			   class="btn-group btn-group-yesno">
			<option value="0">JNo</option>
			<option value="1">JYes</option>
		</field>

		<field name="frontend_secret_word" type="akencrypted"
			   default="" size="30"
			   label="COM_AKEEBA_CONFIG_SECRETWORD_LABEL"
			   description="COM_AKEEBA_CONFIG_SECRETWORD_DESC"
			   class="input-xxlarge"
			   showon="legacyapi_enabled:1[OR]jsonapi_enabled:1"
		/>

		<field name="forced_backup_timezone" type="timezone" default="AKEEBA/DEFAULT"
			   size="1"
			   label="COM_AKEEBA_CONFIG_FORCEDBACKUPTZ_LABEL"
			   description="COM_AKEEBA_CONFIG_FORCEDBACKUPTZ_DESC"
			   class="input-xxlarge">
			<option value="AKEEBA/DEFAULT">COM_AKEEBA_CONFIG_FORCEDBACKUPTZ_DEFAULT</option>
			<option value="GMT">GMT</option>
		</field>

		<field name="frontend_email_on_finish" type="fancyradio" default="0"
			   label="COM_AKEEBA_CONFIG_FRONTENDEMAIL_LABEL"
			   description="COM_AKEEBA_CONFIG_FRONTENDEMAIL_DESC"
			   class="btn-group btn-group-yesno">
			<option value="0">JNo</option>
			<option value="1">JYes</option>
		</field>

		<field name="frontend_email_when" type="list" default="always"
			   label="COM_AKEEBA_CONFIG_FRONTEND_EMAIL_WHEN_LABEL"
			   description="COM_AKEEBA_CONFIG_FRONTEND_EMAIL_WHEN_DESC"
			   showon="frontend_email_on_finish:1"
		>
			<option value="always">COM_AKEEBA_CONFIG_FRONTEND_EMAIL_WHEN_ALWAYS</option>
			<option value="failedupload">COM_AKEEBA_CONFIG_FRONTEND_EMAIL_WHEN_FAILEDUPLOAD</option>
		</field>

		<field name="frontend_email_address" type="text" default="" size="50"
			   label="COM_AKEEBA_CONFIG_ARBITRARYFEEMAIL_LABEL"
			   description="COM_AKEEBA_CONFIG_ARBITRARYFEEMAIL_DESC"
			   class="input-xxlarge"
			   showon="frontend_email_on_finish:1"
		/>

		<field name="frontend_email_subject" type="text" default="" size="50"
			   label="COM_AKEEBA_CONFIG_FEEMAILSUBJECT_LABEL"
			   description="COM_AKEEBA_CONFIG_FEEMAILSUBJECT_DESC"
			   class="input-xxlarge"
			   showon="frontend_email_on_finish:1"
		/>

		<field name="frontend_email_body" type="textarea" default="" rows="10" cols="55"
			   label="COM_AKEEBA_CONFIG_FEEMAILBODY_LABEL"
			   description="COM_AKEEBA_CONFIG_FEEMAILBODY_DESC"
			   showon="frontend_email_on_finish:1"
		/>

		<!-- FAILURE CHECK SETTINGS -->
		<field type="spacer" label="COM_AKEEBA_CONFIG_FAILURE_SEPARATOR"/>

		<field name="failure_timeout" type="text" default="180"
			   filter="integer"
			   label="COM_AKEEBA_CONFIG_FAILURE_TIMEOUT_LABEL"
			   description="COM_AKEEBA_CONFIG_FAILURE_TIMEOUT_DESC"
		/>

		<field name="failure_email_address" type="text" default="" size="50"
			   label="COM_AKEEBA_CONFIG_FAILURE_EMAILADDRESS_LABEL"
			   description="COM_AKEEBA_CONFIG_FAILURE_EMAILADDRESS_DESC"/>

		<field name="failure_email_subject" type="text" default="" size="50"
			   label="COM_AKEEBA_CONFIG_FAILURE_EMAILSUBJECT_LABEL"
			   description="COM_AKEEBA_CONFIG_FAILURE_EMAILSUBJECT_DESC"/>

		<field name="failure_email_body" type="textarea" default="" rows="10" cols="55"
			   label="COM_AKEEBA_CONFIG_FAILURE_EMAILBODY_LABEL"
			   description="COM_AKEEBA_CONFIG_FAILURE_EMAILBODY_DESC"/>

		<field name="siteurl" type="hidden" default="" label=""/>
		<field name="jversion" type="hidden" default="" label=""/>
		<field name="jlibrariesdir" type="hidden" default="" label=""/>
		<field name="lastversion" type="hidden" default="" label=""/>
		<field name="angieupgrade" type="hidden" default="0" label=""/>
		<field name="show_howtorestoremodal" type="hidden" default="1" label=""/>
		<field name="updatedb" type="hidden" default="" label=""/>
	</fieldset>

	<fieldset name="liveupdate" label="COM_AKEEBA_CONFIG_LIVEUPDATE_HEADER_LABEL"
			  description="COM_AKEEBA_CONFIG_LIVEUPDATE_HEADER_DESC">
		<field name="update_dlid" type="text" default="" size="30"
			   label="COM_AKEEBA_CONFIG_DOWNLOADID_LABEL"
			   description="COM_AKEEBA_CONFIG_DOWNLOADID_DESC"/>

		<field name="stats_enabled"
			   type="fancyradio"
			   default="1"
			   label="COM_AKEEBA_CONFIG_USAGESTATS_LABEL"
			   description="COM_AKEEBA_CONFIG_USAGESTATS_DESC"
			   class="btn-group btn-group-yesno">
			<option value="0">JNO</option>
			<option value="1">JYES</option>
		</field>
	</fieldset>

	<fieldset name="security" label="COM_AKEEBA_CONFIG_SECURITY_HEADER_LABEL"
			  description="COM_AKEEBA_CONFIG_SECURITY_HEADER_DESC">
		<field name="useencryption" type="fancyradio" default="1"
			   label="COM_AKEEBA_CONFIG_SECURITY_USEENCRYPTION_LABEL"
			   description="COM_AKEEBA_CONFIG_SECURITY_USEENCRYPTION_DESCRIPTION"
			   class="btn-group btn-group-yesno">
			<option value="0">JNo</option>
			<option value="1">JYes</option>
		</field>

	</fieldset>

	<fieldset name="push" label="COM_AKEEBA_CONFIG_PUSH_HEADER_LABEL" description="COM_AKEEBA_CONFIG_PUSH_HEADER_DESC">
		<field name="desktop_notifications" type="fancyradio" default="0"
			   label="COM_AKEEBA_CONFIG_DESKTOP_NOTIFICATIONS_LABEL"
			   description="COM_AKEEBA_CONFIG_DESKTOP_NOTIFICATIONS_DESC"
			   class="btn-group btn-group-yesno">
		<option value="0">JNO</option>
			<option value="1">JYES</option>
		</field>

		<field name="push_preference" type="list" default="0"
			   label="COM_AKEEBA_CONFIG_PUSH_PREFERENCE_LABEL"
			   description="COM_AKEEBA_CONFIG_PUSH_PREFERENCE_DESC">
			<option value="0">COM_AKEEBA_CONFIG_PUSH_PREFERENCE_OPT_NONE</option>
			<option value="1">COM_AKEEBA_CONFIG_PUSH_PREFERENCE_OPT_PUSHBULLET</option>
		</field>

		<field name="push_apikey" type="text" default="" size="30"
			   label="COM_AKEEBA_CONFIG_PUSH_APIKEY_LABEL"
			   description="COM_AKEEBA_CONFIG_PUSH_APIKEY_DESC"
			   showon="push_preference:1"
		/>
	</fieldset>

	<fieldset name="oauth2"
			  label="COM_AKEEBA_CONFIG_OAUTH2_HEADER_LABEL"
			  description="COM_AKEEBA_CONFIG_OAUTH2_HEADER_DESC"
	>
		<!-- Box.com -->

		<field name="oauth2_client_box"
			   type="radio"
			   layout="joomla.form.field.radio.switcher"
			   default="0"
			   label="COM_AKEEBA_CONFIG_OAUTH2_CLIENT_BOX_LABEL"
			   description="COM_AKEEBA_CONFIG_OAUTH2_CLIENT_BOX_DESC"
			   class="btn-group btn-group-yesno">
			<option value="0">JNO</option>
			<option value="1">JYES</option>
		</field>

		<field name="box_info"
			   type="Oauth2url"
			   showon="oauth2_client_box:1"
			   engine="box"
		/>

		<field name="box_client_id"
			   type="text"
			   label="COM_AKEEBA_CONFIG_BOX_CLIENT_ID_LABEL"
			   description="COM_AKEEBA_CONFIG_BOX_CLIENT_ID_DESC"
			   showon="oauth2_client_box:1"
		/>

		<field name="box_client_secret"
			   type="password"
			   label="COM_AKEEBA_CONFIG_BOX_CLIENT_SECRET_LABEL"
			   description="COM_AKEEBA_CONFIG_BOX_CLIENT_SECRET_DESC"
			   showon="oauth2_client_box:1"
		/>

		<!-- Dropbox -->

		<field name="oauth2_client_dropbox"
			   type="radio"
			   layout="joomla.form.field.radio.switcher"
			   default="0"
			   label="COM_AKEEBA_CONFIG_OAUTH2_CLIENT_DROPBOX_LABEL"
			   description="COM_AKEEBA_CONFIG_OAUTH2_CLIENT_DROPBOX_DESC"
			   class="btn-group btn-group-yesno">
			<option value="0">JNO</option>
			<option value="1">JYES</option>
		</field>

		<field name="dropbox_info"
			   type="Oauth2url"
			   showon="oauth2_client_dropbox:1"
			   engine="dropbox"
		/>

		<field name="dropbox_client_id"
			   type="text"
			   label="COM_AKEEBA_CONFIG_DROPBOX_CLIENT_ID_LABEL"
			   description="COM_AKEEBA_CONFIG_DROPBOX_CLIENT_ID_DESC"
			   showon="oauth2_client_dropbox:1"
		/>

		<field name="dropbox_client_secret"
			   type="password"
			   label="COM_AKEEBA_CONFIG_DROPBOX_CLIENT_SECRET_LABEL"
			   description="COM_AKEEBA_CONFIG_DROPBOX_CLIENT_SECRET_DESC"
			   showon="oauth2_client_dropbox:1"
		/>

		<!-- Google Drive -->

		<field name="oauth2_client_googledrive"
			   type="radio"
			   layout="joomla.form.field.radio.switcher"
			   default="0"
			   label="COM_AKEEBA_CONFIG_OAUTH2_CLIENT_GOOGLEDRIVE_LABEL"
			   description="COM_AKEEBA_CONFIG_OAUTH2_CLIENT_GOOGLEDRIVE_DESC"
			   class="btn-group btn-group-yesno">
			<option value="0">JNO</option>
			<option value="1">JYES</option>
		</field>

		<field name="googledrive_info"
			   type="Oauth2url"
			   showon="oauth2_client_googledrive:1"
			   engine="googledrive"
		/>

		<field name="googledrive_client_id"
			   type="text"
			   label="COM_AKEEBA_CONFIG_GOOGLEDRIVE_CLIENT_ID_LABEL"
			   description="COM_AKEEBA_CONFIG_GOOGLEDRIVE_CLIENT_ID_DESC"
			   showon="oauth2_client_googledrive:1"
		/>

		<field name="googledrive_client_secret"
			   type="password"
			   label="COM_AKEEBA_CONFIG_GOOGLEDRIVE_CLIENT_SECRET_LABEL"
			   description="COM_AKEEBA_CONFIG_GOOGLEDRIVE_CLIENT_SECRET_DESC"
			   showon="oauth2_client_googledrive:1"
		/>

		<!-- OneDrive Business -->

		<field name="oauth2_client_onedrivebusiness"
			   type="radio"
			   layout="joomla.form.field.radio.switcher"
			   default="0"
			   label="COM_AKEEBA_CONFIG_OAUTH2_CLIENT_ONEDRIVEBUSINESS_LABEL"
			   description="COM_AKEEBA_CONFIG_OAUTH2_CLIENT_ONEDRIVEBUSINESS_DESC"
			   class="btn-group btn-group-yesno">
			<option value="0">JNO</option>
			<option value="1">JYES</option>
		</field>

		<field name="onedrivebusiness_info"
			   type="Oauth2url"
			   showon="oauth2_client_onedrivebusiness:1"
			   engine="onedrivebusiness"
		/>

		<field name="onedrivebusiness_client_id"
			   type="text"
			   label="COM_AKEEBA_CONFIG_ONEDRIVEBUSINESS_CLIENT_ID_LABEL"
			   description="COM_AKEEBA_CONFIG_ONEDRIVEBUSINESS_CLIENT_ID_DESC"
			   showon="oauth2_client_onedrivebusiness:1"
		/>

		<field name="onedrivebusiness_client_secret"
			   type="password"
			   label="COM_AKEEBA_CONFIG_ONEDRIVEBUSINESS_CLIENT_SECRET_LABEL"
			   description="COM_AKEEBA_CONFIG_ONEDRIVEBUSINESS_CLIENT_SECRET_DESC"
			   showon="oauth2_client_onedrivebusiness:1"
		/>
	</fieldset>

</config>
PK�!]���cddView/FileFilters/Html.phpnu&1i�<?php
/**
 * @package   akeebabackup
 * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

namespace Akeeba\Backup\Admin\View\FileFilters;

// Protect from unauthorized access
defined('_JEXEC') || die();

use Akeeba\Backup\Admin\Model\FileFilters;
use Akeeba\Backup\Admin\View\ViewTraits\ProfileIdAndName;
use Akeeba\Engine\Factory;
use FOF40\View\DataView\Html as BaseView;
use Joomla\CMS\HTML\HTMLHelper as JHtml;
use Joomla\CMS\Language\Text as JText;
use Joomla\CMS\Uri\Uri as JUri;

class Html extends BaseView
{
	use ProfileIdAndName;

	/**
	 * SELECT element for choosing a database root
	 *
	 * @var  string
	 */
	public $root_select = '';

	/**
	 * List of database roots
	 *
	 * @var  array
	 */
	public $roots = [];

	/**
	 * @return  void
	 */
	public function onBeforeMain()
	{
		$this->container->template->addJS('media://com_akeeba/js/FileFilters.min.js', true, false, $this->container->mediaVersion);

		/** @var FileFilters $model */
		$model = $this->getModel();

		// Add custom submenus
		$task    = $model->getState('browse_task', 'normal', 'cmd');
		$toolbar = $this->container->toolbar;

		$toolbar->appendLink(
			JText::_('COM_AKEEBA_FILEFILTERS_LABEL_NORMALVIEW'),
			JUri::base() . 'index.php?option=com_akeeba&view=FileFilters&task=normal',
			($task == 'normal')
		);
		$toolbar->appendLink(
			JText::_('COM_AKEEBA_FILEFILTERS_LABEL_TABULARVIEW'),
			JUri::base() . 'index.php?option=com_akeeba&view=FileFilters&task=tabular',
			($task == 'tabular')
		);

		// Get a JSON representation of the available roots
		$filters   = Factory::getFilters();
		$root_info = $filters->getInclusions('dir');
		$roots     = [];
		$options   = [];

		if (!empty($root_info))
		{
			// Loop all dir definitions
			foreach ($root_info as $dir_definition)
			{
				if (is_null($dir_definition[1]))
				{
					// Site root definition has a null element 1. It is always pushed on top of the stack.
					array_unshift($roots, $dir_definition[0]);
				}
				else
				{
					$roots[] = $dir_definition[0];
				}

				$options[] = JHtml::_('select.option', $dir_definition[0], $dir_definition[0]);
			}
		}

		$siteRoot      = $roots[0];
		$selectOptions = [
			'list.select' => $siteRoot,
			'id'          => 'active_root',
		];

		$this->root_select = JHtml::_('select.genericlist', $options, 'root', $selectOptions);
		$this->roots       = $roots;
		$platform          = $this->container->platform;

		// Add script options
		$platform->addScriptOptions('akeeba.System.params.AjaxURL', 'index.php?option=com_akeeba&view=FileFilters&task=ajax');
		$platform->addScriptOptions('akeeba.Fsfilters.loadingGif', $this->container->template->parsePath('media://com_akeeba/icons/loading.gif'));

		switch ($task)
		{
			case 'normal':
			default:
				$this->setLayout('default');

				// Get a JSON representation of the directory data
				$platform->addScriptOptions('akeeba.FileFilters.guiData', $model->make_listing($siteRoot, [], ''));
				$platform->addScriptOptions('akeeba.FileFilters.viewType', "list");

				break;

			case 'tabular':
				$this->setLayout('tabular');

				// Get a JSON representation of the tabular filter data
				$platform->addScriptOptions('akeeba.FileFilters.guiData', $model->get_filters($siteRoot));
				$platform->addScriptOptions('akeeba.FileFilters.viewType', "tabular");

				break;
		}

		// Push translations
		JText::script('COM_AKEEBA_FILEFILTERS_LABEL_UIROOT');
		JText::script('COM_AKEEBA_FILEFILTERS_LABEL_UIERRORFILTER');
		JText::script('COM_AKEEBA_FILEFILTERS_TYPE_DIRECTORIES');
		JText::script('COM_AKEEBA_FILEFILTERS_TYPE_SKIPFILES');
		JText::script('COM_AKEEBA_FILEFILTERS_TYPE_SKIPDIRS');
		JText::script('COM_AKEEBA_FILEFILTERS_TYPE_FILES');
		JText::script('COM_AKEEBA_FILEFILTERS_TYPE_DIRECTORIES_ALL');
		JText::script('COM_AKEEBA_FILEFILTERS_TYPE_SKIPFILES_ALL');
		JText::script('COM_AKEEBA_FILEFILTERS_TYPE_SKIPDIRS_ALL');
		JText::script('COM_AKEEBA_FILEFILTERS_TYPE_FILES_ALL');
		JText::script('COM_AKEEBA_FILEFILTERS_TYPE_APPLYTOALLDIRS');
		JText::script('COM_AKEEBA_FILEFILTERS_TYPE_APPLYTOALLFILES');

		$this->getProfileIdAndName();
	}

}
PK�!]��6�#�#View/errorhandler.phpnu&1i�<?php
/**
 * @package   akeebabackup
 * @copyright Copyright (c)2006-2021 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

// Protect from unauthorized access
defined('_JEXEC') or die();

/** @var Throwable $e */
/** @var string $title */
/** @var bool $isPro */

$code = $e->getCode();
$code = !empty($code) ? $code : 500;

$app  = class_exists('\Joomla\CMS\Factory') ? \Joomla\CMS\Factory::getApplication() : \JFactory::getApplication();

$user30 = (class_exists('JFactory') && method_exists('JFactory', 'getUser')) ? JFactory::getUser() : null;
$user38 = class_exists('\Joomla\CMS\Factory') && method_exists('\Joomla\CMS\Factory', 'getUser') ? \Joomla\CMS\Factory::getUser() : null;
$user40 = (is_object($app) && method_exists($app, 'getIdentity')) ? $app->getIdentity() : null;
$user = is_null($user40) ? $user38 : $user40;
$user = is_null($user40) ? $user30 : $user;
$isSuper = !is_null($user) && $user->authorise('core.admin');

$isFrontend   = class_exists('JApplicationSite') && ($app instanceof JApplicationSite);
$isFrontend   = $isFrontend || (class_exists('\Joomla\CMS\Application\SiteApplication') && ($app instanceof \Joomla\CMS\Application\SiteApplication));
$user         = $isFrontend ? (method_exists($app, 'getIdentity') ? $app->getIdentity() : JFactory::getUser()) : null;
$hideTheError = $isFrontend && !(defined('JDEBUG') && (JDEBUG == 1)) && !$isSuper;
$isPro        = !isset($isPro) ? false : $isPro;

// 403 and 404 are re-thrown
if (in_array($code, [403, 404]))
{
	throw $e;
}

if (version_compare(JVERSION, '4', 'lt'))
{
	$app->setHeader('HTTP/1.1', $code);
}
else
{
	// In Joomla 4 we have to use the "Status" header, otherwise we get a fatal error saying that
	// HTTP/1.1 is not a valid header
	$app->setHeader('Status', $code);
}

if (!$isFrontend)
{
	if (class_exists('\Joomla\CMS\Toolbar\ToolbarHelper'))
	{
		\Joomla\CMS\Toolbar\ToolbarHelper::title($title . ' <small>Unhandled Exception</small>');
	}
	else
	{
		JToolbarHelper::title($title . ' <small>Unhandled Exception</small>');
	}

}

?>

<?php if ($hideTheError): ?>
	<h1>The application has stopped responding</h1>
	<p>
		Please contact the administrator of the site and let them know of this error and what you were doing when this
		happened.
	</p>
	<?php return true; endif; ?>

<h1><?php echo $title ?> - An unhandled Exception has been detected</h1>
<h3>
	<?php if (version_compare(JVERSION, '3.999.999', 'le')): ?>
		<span class="label label-danger"><?php echo htmlentities($code) ?></span> <?php echo htmlentities($e->getMessage()) ?>
	<?php else: ?>
		<span class="badge badge-danger"><?php echo htmlentities($code) ?></span> <?php echo htmlentities($e->getMessage()) ?>
	<?php endif; ?>
</h3>
<p>
	File <code><?php echo htmlentities(str_ireplace(JPATH_ROOT, '&lt;root&gt;', $e->getFile())) ?></code>
	Line <span class="label label-info"><?php echo (int) $e->getLine() ?></span>
</p>

<?php if ($isPro): ?>
	<div class="<?php if (version_compare(JVERSION, '3.999.999', 'le')):?>hero-unit<?php else: ?>alert alert-primary<?php endif; ?>">
		<p>
			<strong>Would you like us to help you faster?</strong>
		</p>
		<p>
			Save this page as PDF or HTML. When filing a support ticket please attach that PDF or HTML file.
		</p>
	</div>
	<p>
		<strong>Why do we need all that information?</strong> This information is an x-ray of your site at the time the
		error
		occurred. It lets us reproduce the issue or, if it's not a bug in our software, help you pinpoint the external
		reason which
		led to it.
	</p>
	<p>
		<strong>What about privacy?</strong>
		Attachments are private in our ticket system: only you and us can see them, <em>even if you file a public
			ticket</em>, and
		they are automatically deleted after a month.
	</p>
<?php endif; ?>

<hr />
<p>
	<span class="icon icon-warning-2"></span>
	<em>
		The content below this point is for developers and power users.
	</em>
</p>
<hr />

<p class="alert alert-warning">
	Joomla <?= JVERSION ?> – PHP <?= PHP_VERSION ?> on <?= PHP_OS ?>
</p>

<h3>Debug information</h3>
<p>
	Exception type: <code><?php echo htmlentities(get_class($e)) ?></code>
</p>
<pre><?php echo htmlentities($e->getTraceAsString()) ?></pre>

<h3>System information</h3>
<table class="table table-striped">
	<tr>
		<td>Operating System (reported by PHP)</td>
		<td><?php echo PHP_OS ?></td>
	</tr>
	<tr>
		<td>PHP version (as reported <em>by your server</em>)</td>
		<td><?php echo PHP_VERSION ?></td>
	</tr>
	<tr>
		<td>PHP Built On</td>
		<td><?php echo htmlentities(php_uname()); ?></td>
	</tr>
	<tr>
		<td>PHP SAPI</td>
		<td><?php echo PHP_SAPI ?></td>
	</tr>
	<tr>
		<td>Server identity</td>
		<td><?php echo htmlentities(isset($_SERVER['SERVER_SOFTWARE']) ? $_SERVER['SERVER_SOFTWARE'] : getenv('SERVER_SOFTWARE')) ?></td>
	</tr>
	<tr>
		<td>Browser identity</td>
		<td><?php echo htmlentities(isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : '') ?></td>
	</tr>
	<tr>
		<td>Joomla! version</td>
		<td><?php echo JVERSION ?></td>
	</tr>
	<?php
	$db = JFactory::getDbo();
	if (!is_null($db)):
	?>
	<tr>
		<td>Database driver name</td>
		<td><?php echo $db->getName() ?></td>
	</tr>
	<tr>
		<td>Database driver type</td>
		<td><?php echo $db->getServerType() ?></td>
	</tr>
	<tr>
		<td>Database server version</td>
		<td><?php echo $db->getVersion() ?></td>
	</tr>
	<tr>
		<td>Database collation</td>
		<td><?php echo $db->getCollation() ?></td>
	</tr>
	<tr>
		<td>Database connection collation</td>
		<td><?php echo $db->getConnectionCollation() ?></td>
	</tr>
	<?php endif; ?>
	<tr>
		<td>PHP Memory limit</td>
		<td><?php echo function_exists('ini_get') ? htmlentities(ini_get('memory_limit')) : 'N/A' ?></td>
	</tr>
	<tr>
		<td>Peak Memory usage</td>
		<td><?php echo function_exists('memory_get_peak_usage') ? sprintf('%0.2fM', (memory_get_peak_usage() / 1024 / 1024)) : 'N/A' ?></td>
	</tr>
	<tr>
		<td>PHP Timeout (seconds)</td>
		<td><?php echo function_exists('ini_get') ? htmlentities(ini_get('max_execution_time')) : 'N/A' ?></td>
	</tr>
</table>

<h3>Request information</h3>
<h4>$_GET</h4>
<pre><?php echo htmlentities(print_r($_GET, true)) ?></pre>
<h4>$_POST</h4>
<pre><?php echo htmlentities(print_r($_POST, true)) ?></pre>
<h4>$_COOKIE</h4>
<pre><?php echo htmlentities(print_r($_COOKIE, true)) ?></pre>
<h4>$_REQUEST</h4>
<pre><?php echo htmlentities(print_r($_REQUEST, true)) ?></pre>

<h3>Session state</h3>
<pre><?php
	if (version_compare(JVERSION, '4', 'lt'))
	{
		echo htmlentities(print_r($app->getSession()->getData()->toArray(), true));
	}
	else
	{
		echo htmlentities(print_r($app->getSession()->all(), true));
	}
	?></pre>

<?php
if (version_compare(JVERSION, '3.999.999', 'le'))
{
	if (!include_once(JPATH_ADMINISTRATOR . '/components/com_admin/models/sysinfo.php'))
	{
		return;
	}

	$model       = new AdminModelSysInfo();
}
else
{
	try
	{
		/** @var MVCFactoryInterface $factory */
		$factory = $app->bootComponent('com_admin')->getMVCFactory();
		/** @var \Joomla\Component\Admin\Administrator\Model\SysinfoModel $model */
		$model = $factory->createModel('Sysinfo', 'Administrator');
	}
	catch (Exception $e)
	{
		return;
	}
}

$directories = $model->getDirectory();

try
{
	$extensions = $model->getExtensions();
}
catch (Exception $e)
{
	$extension = [];
}

$phpSettings = $model->getPhpSettings();
$hasPHPInfo  = $model->phpinfoEnabled();
?>

<h3>PHP Settings</h3>
<table class="table table-striped">
	<?php foreach ($phpSettings as $k => $v): ?>
		<tr>
			<td><?php echo $k ?></td>
			<td><?php echo htmlentities(print_r($v, true)) ?></td>
		</tr>
	<?php endforeach; ?>
</table>

<?php if ($hasPHPInfo):
	$phpInfo = $model->getPhpInfoArray(); ?>
	<h3>Loaded PHP Extensions</h3>
	<table class="table table-striped">
		<?php foreach ($phpInfo as $section => $data):
			if ($section == 'Core')
			{
				continue;
			} ?>
			<tr>
				<td><?php echo htmlentities($section) ?></td>
				<td>
					<?php if (in_array($section, ['curl', 'openssl', 'ssh2', 'ftp', 'session', 'tokenizer'])): ?>
						<pre><?php echo htmlentities(print_r($data, true)) ?></pre>
					<?php endif; ?>
				</td>
			</tr>
		<?php endforeach; ?>
	</table>
<?php endif; ?>

<h3>Enabled Extensions</h3>
<table class="table table-striped">
	<?php foreach ($extensions as $extension => $info):
		if (strtoupper($info['state']) != 'ENABLED')
		{
			continue;
		} ?>
		<tr>
			<td><?php echo htmlentities($extension) ?></td>
			<td><?php echo htmlentities($info['version']) ?></td>
			<td><?php echo htmlentities($info['type']) ?></td>
			<td><?php echo htmlentities($info['author']) ?></td>
			<td><?php echo htmlentities($info['authorUrl']) ?></td>
		</tr>
	<?php endforeach; ?>
</table>

<h3>Directory Status</h3>
<table class="table table-striped">
	<?php foreach ($directories as $k => $v): ?>
		<tr>
			<td>
				<?php echo htmlentities($k) ?>
				<?php echo !empty($v['message']) ? "[{$v['message']}]" : '' ?>
			</td>
			<td>
				<?php if ($v['writable']): ?>
					<span class="label label-success">Writeable</span>
				<?php else: ?>
					<span class="label label-danger">Unwriteable</span>
				<?php endif; ?>
			</td>
		</tr>
	<?php endforeach; ?>
</table>PK�!]�,�_$#$#View/ControlPanel/Html.phpnu&1i�<?php
/**
 * @package   akeebabackup
 * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

namespace Akeeba\Backup\Admin\View\ControlPanel;

// Protect from unauthorized access
defined('_JEXEC') || die();

use Akeeba\Backup\Admin\Helper\Status;
use Akeeba\Backup\Admin\Model\ControlPanel;
use Akeeba\Backup\Admin\Model\UsageStatistics;
use Akeeba\Backup\Admin\View\ViewTraits\ProfileIdAndName;
use Akeeba\Backup\Admin\View\ViewTraits\ProfileList;
use Akeeba\Engine\Factory;
use Akeeba\Engine\Platform;
use FOF40\View\DataView\Html as BaseView;

class Html extends BaseView
{
	use ProfileList, ProfileIdAndName;

	/**
	 * List of profiles to display as Quick Icons in the control panel page
	 *
	 * @var   array  Array of stdClass objects
	 */
	public $quickIconProfiles = [];

	/**
	 * The HTML for the backup status cell
	 *
	 * @var   string
	 */
	public $statusCell = '';

	/**
	 * HTML for the warnings (status details)
	 *
	 * @var   string
	 */
	public $detailsCell = '';

	/**
	 * Details of the latest backup as HTML
	 *
	 * @var   string
	 */
	public $latestBackupCell = '';

	/**
	 * Do I have to ask the user to fix the permissions?
	 *
	 * @var   bool
	 */
	public $areMediaPermissionsFixed = false;

	/**
	 * Do I have to ask the user to provide a Download ID?
	 *
	 * @var   bool
	 */
	public $needsDownloadID = false;

	/**
	 * Did a Core edition user provide a Download ID instead of installing Akeeba Backup Professional?
	 *
	 * @var   bool
	 */
	public $coreWarningForDownloadID = false;

	/**
	 * Our extension ID
	 *
	 * @var   int
	 */
	public $extension_id = 0;

	/**
	 * Should I have the browser ask for desktop notification permissions?
	 *
	 * @var   bool
	 */
	public $desktopNotifications = false;

	/**
	 * If anonymous statistics collection is enabled and we have to collect statistics this will include the HTML for
	 * the IFRAME that performs the anonymous stats collection.
	 *
	 * @var   string
	 */
	public $statsIframe = '';

	/**
	 * If front-end backup is enabled and the secret word has an issue (too insecure) we populate this variable
	 *
	 * @var  string
	 */
	public $frontEndSecretWordIssue = '';

	/**
	 * In case the existing Secret Word is insecure we generate a new one. This variable contains the new Secret Word.
	 *
	 * @var  string
	 */
	public $newSecretWord = '';

	/**
	 * Is the mbstring extension installed and enabled? This is required by Joomla and Akeeba Backup to correctly work
	 *
	 * @var  bool
	 */
	public $checkMbstring = true;

	/**
	 * The fancy formatted changelog of the component
	 *
	 * @var  string
	 */
	public $formattedChangelog = '';

	/**
	 * Should I pormpt the user ot run the configuration wizard?
	 *
	 * @var  bool
	 */
	public $promptForConfigurationWizard = false;

	/**
	 * How many warnings do I have to display?
	 *
	 * @var  int
	 */
	public $countWarnings = 0;

	/**
	 * Do I have stuck updates pending?
	 *
	 * @var  bool
	 */
	public $stuckUpdates = false;

	/**
	 * Cache the user permissions
	 *
	 * @var   array
	 *
	 * @since 5.3.0
	 */
	public $permissions = [];

	/**
	 * Timestamp when the Core user last dismissed the upsell to Pro
	 *
	 * @var   int
	 * @since 7.0.0
	 */
	public $lastUpsellDismiss = 0;

	/**
	 * Is the output directory under the site's root?
	 *
	 * @var   bool
	 * @since 7.0.3
	 */
	public $isOutputDirectoryUnderSiteRoot = false;

	/**
	 * Does the output directory have the expected security files?
	 *
	 * @var   bool
	 * @since 7.0.3
	 */
	public $hasOutputDirectorySecurityFiles = false;

	/**
	 * Executes before displaying the control panel page
	 */
	public function onBeforeMain()
	{
		/** @var ControlPanel $model */
		$model = $this->getModel();

		$statusHelper      = Status::getInstance();
		$this->statsIframe = '';

		try
		{
			/** @var UsageStatistics $usageStatsModel */
			$usageStatsModel = $this->container->factory->model('UsageStatistics')->tmpInstance();

			if (
				is_object($usageStatsModel)
				&& class_exists('Akeeba\\Backup\\Admin\\Model\\UsageStatistics')
				&& ($usageStatsModel instanceof UsageStatistics)
				&& method_exists($usageStatsModel, 'collectStatistics')
			)
			{
				$this->statsIframe = $usageStatsModel->collectStatistics(true);
			}
		}
		catch (\Exception $e)
		{
			// Don't give a crap if usage stats ain't loaded
		}

		$this->getProfileList();
		$this->getProfileIdAndName();

		$this->quickIconProfiles               = $model->getQuickIconProfiles();
		$this->statusCell                      = $statusHelper->getStatusCell();
		$this->detailsCell                     = $statusHelper->getQuirksCell();
		$this->latestBackupCell                = $statusHelper->getLatestBackupDetails();
		$this->areMediaPermissionsFixed        = $model->fixMediaPermissions();
		$this->checkMbstring                   = $model->checkMbstring();
		$this->needsDownloadID                 = $model->needsDownloadID() ? 1 : 0;
		$this->coreWarningForDownloadID        = $model->mustWarnAboutDownloadIDInCore();
		$this->extension_id                    = $model->getState('extension_id', 0, 'int');
		$this->frontEndSecretWordIssue         = $model->getFrontendSecretWordError();
		$this->newSecretWord                   = $this->container->platform->getSessionVar('newSecretWord', null, 'akeeba.cpanel');
		$this->desktopNotifications            = $this->container->params->get('desktop_notifications', '0') ? 1 : 0;
		$this->formattedChangelog              = $this->formatChangelog();
		$this->promptForConfigurationWizard    = Factory::getConfiguration()->get('akeeba.flag.confwiz', 0) == 0;
		$this->countWarnings                   = count(Factory::getConfigurationChecks()->getDetailedStatus());
		$this->stuckUpdates                    = ($this->container->params->get('updatedb', 0) == 1);
		$user                                  = $this->container->platform->getUser();
		$this->permissions                     = [
			'configure' => $user->authorise('akeeba.configure', 'com_akeeba'),
			'backup'    => $user->authorise('akeeba.backup', 'com_akeeba'),
			'download'  => $user->authorise('akeeba.download', 'com_akeeba'),
		];
		$this->isOutputDirectoryUnderSiteRoot  = $model->isOutputDirectoryUnderSiteRoot();
		$this->hasOutputDirectorySecurityFiles = $model->hasOutputDirectorySecurityFiles();

		$this->lastUpsellDismiss = $this->container->params->get('lastUpsellDismiss', 0);

		// Load the version constants
		Platform::getInstance()->load_version_defines();

		// Add the Javascript to the document
		$this->container->template->addJS('media://com_akeeba/js/ControlPanel.min.js', true, false, $this->container->mediaVersion);
		$this->addJSScriptOptions();
	}

	/**
	 * Adds inline Javascript to the document
	 */
	protected function addJSScriptOptions()
	{
		$platform = $this->container->platform;
		$platform->addScriptOptions('akeeba.System.notification.hasDesktopNotification', (bool)$this->desktopNotifications);
		$platform->addScriptOptions('akeeba.ControlPanel.needsDownloadID', (bool) $this->needsDownloadID);
		$platform->addScriptOptions('akeeba.ControlPanel.outputDirUnderSiteRoot', (bool) $this->isOutputDirectoryUnderSiteRoot);
		$platform->addScriptOptions('akeeba.ControlPanel.hasSecurityFiles', (bool) $this->hasOutputDirectorySecurityFiles);
	}

	protected function formatChangelog($onlyLast = false)
	{
		$ret   = '';
		$file  = $this->container->backEndPath . '/CHANGELOG.php';
		$lines = @file($file);

		if (empty($lines))
		{
			return $ret;
		}

		array_shift($lines);

		foreach ($lines as $line)
		{
			$line = trim($line);

			if (empty($line))
			{
				continue;
			}

			$type = substr($line, 0, 1);

			switch ($type)
			{
				case '=':
					continue 2;
					break;

				case '+':
					$ret .= "\t" . '<li><span class="akeeba-label--green">Added</span> ' . htmlentities(trim(substr($line, 2))) . "</li>\n";
					break;

				case '-':
					$ret .= "\t" . '<li><span class="akeeba-label--grey">Removed</span> ' . htmlentities(trim(substr($line, 2))) . "</li>\n";
					break;

				case '~':
				case '^':
					$ret .= "\t" . '<li><span class="akeeba-label--grey">Changed</span> ' . htmlentities(trim(substr($line, 2))) . "</li>\n";
					break;

				case '*':
					$ret .= "\t" . '<li><span class="akeeba-label--red">Security</span> ' . htmlentities(trim(substr($line, 2))) . "</li>\n";
					break;

				case '!':
					$ret .= "\t" . '<li><span class="akeeba-label--orange">Important</span> ' . htmlentities(trim(substr($line, 2))) . "</li>\n";
					break;

				case '#':
					$ret .= "\t" . '<li><span class="akeeba-label--teal">Fixed</span> ' . htmlentities(trim(substr($line, 2))) . "</li>\n";
					break;

				default:
					if (!empty($ret))
					{
						$ret .= "</ul>";
						if ($onlyLast)
						{
							return $ret;
						}
					}

					if (!$onlyLast)
					{
						$ret .= "<h4>$line</h4>\n";
					}
					$ret .= "<ul class=\"akeeba-changelog\">\n";

					break;
			}
		}

		return $ret;
	}
}
PK�!]� �U	U	View/Browser/Html.phpnu&1i�<?php
/**
 * @package   akeebabackup
 * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

namespace Akeeba\Backup\Admin\View\Browser;

// Protect from unauthorized access
defined('_JEXEC') || die();

use Akeeba\Backup\Admin\Model\Browser;
use FOF40\View\DataView\Html as BaseView;

class Html extends BaseView
{
	/**
	 * Path to current folder (with variables such as [SITEROOT] replaced)
	 *
	 * @var  string
	 */
	public $folder = '';

	/**
	 * Path to current folder (WITHOUT variables such as [SITEROOT] replaced)
	 *
	 * @var  string
	 */
	public $folder_raw = '';

	/**
	 * Parent folder
	 *
	 * @var  string
	 */
	public $parent = '';

	/**
	 * Does the current folder exist in the filesystem?
	 *
	 * @var  bool
	 */
	public $exists = false;

	/**
	 * Is the current folder under the site's root directory? False means it's an off-site directory.
	 *
	 * @var  bool
	 */
	public $inRoot = false;

	/**
	 * Is the current folder restricted by open_basedir?
	 *
	 * @var  bool
	 */
	public $openbasedirRestricted = false;

	/**
	 * Is the current folder writable?
	 *
	 * @var  bool
	 */
	public $writable = false;

	/**
	 * Subdirectories
	 *
	 * @var  array
	 */
	public $subfolders = [];

	/**
	 * Breadcrumbs to display in the browser view
	 *
	 * @var  array
	 */
	public $breadcrumbs = [];

	protected function onBeforeMain()
	{
		// Load the view-specific Javascript
		$this->container->template->addJS('media://com_akeeba/js/Browser.min.js', true, false, $this->container->mediaVersion);

		/** @var Browser $model */
		$model = $this->getModel();

		// Pass the data from the model to the view template
		$this->folder                = $model->getState('folder', '', 'string');
		$this->folder_raw            = $model->getState('folder_raw', '', 'string');
		$this->parent                = $model->getState('parent', '', 'string');
		$this->exists                = $model->getState('exists', 0, 'boolean');
		$this->inRoot                = $model->getState('inRoot', 0, 'boolean');
		$this->openbasedirRestricted = $model->getState('openbasedirRestricted', 0, 'boolean');
		$this->writable              = $model->getState('writable', 0, 'boolean');
		$this->subfolders            = $model->getState('subfolders');
		$this->breadcrumbs           = $model->getState('breadcrumbs');
	}
}
PK�!]�2��

View/fof.phpnu&1i�<?php
/**
 * @package   akeebabackup
 * @copyright Copyright (c)2006-2021 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

// Protect from unauthorized access
defined('_JEXEC') or die();

$tooLongAgo = (int) gmdate('Y') - 2015;
?>

<div style="margin: 1em">
	<h1>Akeeba Framework-on-Framework (FOF) version 3 could not be found on this site</h1>
	<hr/>
	<div class="alert alert-warning">
		<h2>
			This component requires the Akeeba FOF framework package to be installed on your site. Please go to <a
					href="https://www.akeeba.com/download/fof3.html">our download page</a> to download it, then install it on your site.
		</h2>
	</div>
	<hr/>
	<h4>Further information</h4>
	<p>
		FOF is a Joomla component framework. It's the low level code which sits between our Joomla! extensions and
		Joomla! itself. It is automatically installed when you install our extensions on your site.
	</p>
	<p>
		FOF can be missing from your site either because Joomla failed to install it or because you, another Super User,
		or another extension mistakenly uninstalled it.
	</p>
	<p>
		If it's missing, our components cannot talk to Joomla &mdash; or vice versa. Because of that they can not run.
		That's why you see this message.
	</p>
	<p>
		You do not have to worry about adding bloat to your site. FOF is very small. It will also be automatically
		uninstalled when you uninstall all components which depend on it.
	</p>
	<p>
		FOF is installed in the <code><?php echo JPATH_LIBRARIES . DIRECTORY_SEPARATOR?>/fof30</code> folder on your
		server. It appears in Joomla's Extensions, Manage page as <code>FOF30</code>. Please do not remove it from your
		site.
	</p>
<?php if (version_compare(JVERSION, '3.9999.9999', 'le')): ?>
	<h4>Why do I have multiple FOF entries in Joomla?</h4>
	<p>
		Joomla <?php echo JVERSION ?> includes an <em>old, obsolete</em> version of FOF - version 2.x. It is installed
		in the <code><?php echo JPATH_LIBRARIES . DIRECTORY_SEPARATOR ?>fof</code> folder on your server. It appears in
		Joomla's Extensions, Manage page as <code>FOF</code>. Please do not remove it from your site; Joomla needs it
		to function properly.
	</p>
	<p>
		We discontinued FOF 2.x in 2015 &mdash; that's <?php echo $tooLongAgo ?> years ago. Ever since, we replaced it
		with FOF 3.x. The two versions are incompatible with each other but both are required; FOF 2.x for Joomla!
		itself and FOF 3.x for our extensions. That's why you see both. You must not remove either of them or something
		will break!
	</p>
<?php endif; ?>
</div>
PK�!]�b�,�,View/wrongphp.phpnu&1i�<?php
/**
 * @package   akeebabackup
 * @copyright Copyright (c)2006-2021 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

(defined('_JEXEC') || defined('WPINC') || defined('APATH_BASE') || defined('AKEEBA_COMMON_WRONGPHP') || defined('KICKSTART')) or die;

if (!function_exists('akeeba_common_wrongphp'))
{
	/**
	 * This function checks if you are using an obsolete PHP version. It returns and boolean status and optionally
	 * prints an error page if your PHP version is, indeed, too old.
	 *
	 * * minPHPVersion: minimum PHP version supported by this software, e.g. "7.2.0"
	 * * recommendedPHPVersion: recommended PHP version to use with this software, e.g. "7.3"
	 * * softwareName: human-readable software name, e.g. "Akeeba Example"
	 * * silentResutls: suppress error messages on old PHP version, just return false (default: TRUE)
	 * * longVersion: current PHP version, long format, e.g. "7.3.1-12ubuntu3.2". Skip to automatically determine.
	 * * shortVersion: current PHP version, short format, e.g. "7.3". Skip to automatically determine.
	 * * currentTimestamp: current UNIX timestamp. Skip to automatically determine.
	 *
	 * You need to provide at the very least the minPHPVersion, recommendedPHPVersion and softwareName.
	 *
	 * @param  array  $config
	 *
	 * @return bool  FALSE if your PHP version is too old. TRUE if your PHP version is still supported.
	 * @throws Exception
	 */
	function akeeba_common_wrongphp($config = array())
	{
		/**
		 * Format: version => [maintenance_date, eol_date]
		 *
		 * For versions older than 5.6 we use a fake maintenance_date because this information no longer exists on PHP's
		 * site and it's irrelevant anyway; these PHP versions are already EOL therefore we only use their EOL date.
		 */
		$phpDates = array(
			'3.0' => array('1990-01-01 00:00:00', '2000-10-20 00:00:00'),
			'4.0' => array('1990-01-01 00:00:00', '2001-06-23 00:00:00'),
			'4.1' => array('1990-01-01 00:00:00', '2002-03-12 00:00:00'),
			'4.2' => array('1990-01-01 00:00:00', '2002-09-06 00:00:00'),
			'4.3' => array('1990-01-01 00:00:00', '2005-03-31 00:00:00'),
			'4.4' => array('1990-01-01 00:00:00', '2008-08-07 00:00:00'),
			'5.0' => array('1990-01-01 00:00:00', '2005-09-05 00:00:00'),
			'5.1' => array('1990-01-01 00:00:00', '2006-08-24 00:00:00'),
			'5.2' => array('1990-01-01 00:00:00', '2011-01-11 00:00:00'),
			'5.3' => array('1990-01-01 00:00:00', '2014-08-14 00:00:00'),
			'5.4' => array('1990-01-01 00:00:00', '2015-09-03 00:00:00'),
			'5.5' => array('1990-01-01 00:00:00', '2016-07-10 00:00:00'),
			'5.6' => array('2017-01-10 00:00:00', '2018-12-31 00:00:00'),
			'7.0' => array('2018-01-01 00:00:00', '2019-01-10 00:00:00'),
			'7.1' => array('2018-12-01 00:00:00', '2019-12-01 00:00:00'),
			'7.2' => array('2019-11-30 00:00:00', '2020-11-30 00:00:00'),
			'7.3' => array('2020-12-06 00:00:00', '2021-12-06 00:00:00'),
			'7.4' => array('2021-11-28 00:00:00', '2022-11-28 00:00:00'),
			'8.0' => array('2022-11-26 00:00:00', '2023-11-26 00:00:00'),
		);

		// Make sure I have all necessary configuration variables
		$config = array_merge(array(
			'minPHPVersion'         => '7.2.0',
			'recommendedPHPVersion' => '7.3',
			'softwareName'          => 'This software',
			'silentResults'         => false,
			'longVersion'           => PHP_VERSION,
			'shortVersion'          => sprintf('%d.%d', PHP_MAJOR_VERSION, PHP_MINOR_VERSION),
			'currentTimestamp'      => time(),
		), $config);

		// Selectively extract configuration variables. Do not use extract(), it's potentially dangerous.
		$minPHPVersion         = $config['minPHPVersion'];
		$recommendedPHPVersion = $config['recommendedPHPVersion'];
		$softwareName          = $config['softwareName'];
		$silentResults         = $config['silentResults'];
		$longVersion           = $config['longVersion'];
		$shortVersion          = $config['shortVersion'];
		$currentTimestamp      = $config['currentTimestamp'];

		if (!version_compare($longVersion, $minPHPVersion, 'lt'))
		{
			unset($minPHPVersion, $recommendedPHPVersion, $softwareName, $longVersion, $shortVersion, $phpDates,
				$silentResults, $currentTimestamp);

			return true;
		}

// Typically used in the frontend to not divulge any information about the server
		if ($silentResults)
		{
			return false;
		}

		/**
		 * Safe defaults for PHP versions older than 5.3.0.
		 *
		 * Older PHP versions don't even have support for DateTime so we need these defaults to prevent this warning script from
		 * bringing the site down with an error.
		 */
		$isEol      = true;
		$isAncient  = true;
		$isSecurity = false;
		$isCurrent  = false;

		$eolDateFormatted      = $phpDates[$shortVersion][1];
		$securityDateFormatted = $phpDates[$shortVersion][0];


		/**
		 * This can only work on PHP 5.2.0 or later
		 */
		if (version_compare($longVersion, '5.2.0', 'ge'))
		{
			$tzGmt        = new DateTimeZone('GMT');
			$securityDate = new DateTime($phpDates[$shortVersion][0], $tzGmt);
			$eolDate      = new DateTime($phpDates[$shortVersion][1], $tzGmt);

			/**
			 * Ancient:  This PHP version has reached end-of-life more than 2 years ago
			 * EOL:      This PHP version has reached end-of-life
			 * Security: This PHP version has reached the Security Support date but not the EOL date yet
			 * Current:  This PHP version is still in Active Support
			 */
			$isEol      = $eolDate->getTimestamp() <= $currentTimestamp;
			$isAncient  = $isEol && (($currentTimestamp - $eolDate->getTimestamp()) >= 63072000);
			$isSecurity = !$isEol && ($securityDate->getTimestamp() <= $currentTimestamp);
			$isCurrent  = !$isEol && !$isSecurity;

			$eolDateFormatted      = $eolDate->format('l, d F Y');
			$securityDateFormatted = $securityDate->format('l, d F Y');
		}

		$characterization = $isCurrent ? 'unsupported' : 'older';
		$characterization = $isEol ? 'obsolete' : $characterization;
		$characterization = $isAncient ? 'dangerously obsolete' : $characterization;

		?>

		<div style="margin: 1em">
			<p style="font-size: 180%; margin: 2em 1em; padding: 2em 1em; text-align: center; border: thin solid #f0ad4e; background-color: gold; font-weight: bold; border-radius: 0.25em">
				<?php echo $softwareName ?> requires PHP <?php echo $minPHPVersion ?> or later.
			</p>
			<h2><?php echo ucfirst($characterization) ?> PHP version <?php echo $longVersion ?> detected</h2>
			<hr />
			<p>
				We recommend that you upgrade your site to PHP <?php echo $recommendedPHPVersion ?> or later. If you are
				unsure how to do this, please ask your host.
			</p>
			<p>
				<a href="https://www.akeeba.com/how-do-version-numbers-work.html">Version numbers don't make
					sense?</a>
			</p>

			<hr />

			<?php if ($isAncient): ?>
				<h3>Urgent security advice</h3>

				<p>
					Your version of PHP, <?php echo $longVersion ?>, <a href="http://php.net/eol.php">has reached the end
						of its life</a> a <strong>very</strong> long time ago, namely on
					<?php echo $eolDateFormatted ?>. It has known security vulnerabilities which are used to
					compromise (“hack”) web servers. It is no longer safe using it in production. You are <strong>VERY
						STRONGLY</strong> advised to upgrade your server to a <a
							href="https://www.php.net/supported-versions.php">supported PHP version</a> as soon as possible.
				</p>
			<?php elseif ($isEol): ?>
				<h3>Security advice</h3>

				<p>
					Your version of PHP, <?php echo $longVersion ?>, <a href="http://php.net/eol.php">has reached the end
						of its life</a> on <?php echo $eolDateFormatted ?>. End-of-life PHP versions may have
					as-yet-undiscovered security vulnerabilities which can be used to compromise (“hack”) your site. It is
					no
					longer safe using it in production, even if your host or your Linux distribution claim otherwise – the
					PHP
					developers themselves have said time over time that not all security vulnerabilities fixes can be
					backported
					to End-of-Life versions of PHP since they may require architectural changes in PHP itself. You are
					<strong>strongly</strong> advised to upgrade your server to a <a
							href="https://www.php.net/supported-versions.php">supported PHP version</a> as soon as possible.
				</p>

			<?php elseif ($isSecurity): ?>
				<h3>Security reminder</h3>

				<p>
					Your version of PHP, <?php echo $longVersion ?>, has entered the “Security Support” phase of its life on
					<?php echo $securityDateFormatted ?>. As such, only security issues will be addressed but not
					any of its known functional issues (“bugs”). Unfixed functional issues in PHP can lead to your site not
					working
					properly. It is advisable to plan migrating your site to a
					<a href="https://www.php.net/supported-versions.php">supported PHP version</a> no later than
					<?php echo $eolDateFormatted ?> – that's when PHP
					<?php echo $shortVersion ?> will become End-of-Life, therefore
					completely
					unsuitable for use on a live server.
				</p>
			<?php endif; ?>

			<?php if ($isSecurity || $isCurrent): ?>
				<h3>Why is my PHP version not supported?</h3>

				<p>
					Even though PHP <?php echo $shortVersion ?> will be supported by the PHP
					project until <?php echo $eolDateFormatted ?> we are unfortunately unable to provide
					support for it in our software. This has to do either with missing features or third party libraries.
					Older
					PHP versions are missing features we require for our software to work efficiently and be written in a
					way
					that makes it possible for us to provide a plethora of relevant features while maintaining good quality
					control. Moreover, third party libraries we use to provide some of the software's features do not
					support
					older PHP versions for the same reason – so even if we don't absolutely need to use at least PHP
					<?php echo $minPHPVersion ?> the third party libraries do, making it impossible for our software to run on
					your
					older version <?php echo $shortVersion ?>. We apologize for the inconvenience.
				</p>
				<p>
					We'd like to remind you, however, that newer PHP versions are always faster and more well-tested than
					their
					predecessors. Upgrading your site to a newer PHP version will not only let our software run but will
					also
					make your site faster, more stable and help it perform better in search engine results.
				</p>
			<?php endif; ?>
		</div>

		<?php return false;
	}
}

/**
 * Immediately executes the akeeba_common_wrongphp() function on all of our software except Kickstart.
 */
if (!defined('KICKSTART'))
{
	try
	{
		return akeeba_common_wrongphp(array(
			// Configuration -- Override before calling this script
			'minPHPVersion'         => isset($minPHPVersion) ? $minPHPVersion : '7.2.0',
			'recommendedPHPVersion' => isset($recommendedPHPVersion) ? $recommendedPHPVersion : '7.3',
			'softwareName'          => isset($softwareName) ? $softwareName : 'This software',
			'silentResults'         => isset($silentResults) ? $silentResults : false,
			// Override these to test the script
			'longVersion'           => isset($longVersion) ? $longVersion : PHP_VERSION,
			'shortVersion'          => isset($shortVersion) ? $shortVersion : sprintf('%d.%d', PHP_MAJOR_VERSION, PHP_MINOR_VERSION),
			'currentTimestamp'      => isset($currentTimestamp) ? $currentTimestamp : time(),
		));
	}
	catch (Exception $e)
	{
		// This should never happen
		return false;
	}
}PK�!]l�?��View/DatabaseFilters/Html.phpnu&1i�<?php
/**
 * @package   akeebabackup
 * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

namespace Akeeba\Backup\Admin\View\DatabaseFilters;

// Protect from unauthorized access
defined('_JEXEC') || die();

use Akeeba\Backup\Admin\Model\DatabaseFilters;
use Akeeba\Backup\Admin\View\ViewTraits\ProfileIdAndName;
use FOF40\View\DataView\Html as BaseView;
use Joomla\CMS\HTML\HTMLHelper as JHtml;
use Joomla\CMS\Language\Text as JText;
use Joomla\CMS\Uri\Uri as JUri;

/**
 * View for database table exclusion
 */
class Html extends BaseView
{
	use ProfileIdAndName;

	/**
	 * SELECT element for choosing a database root
	 *
	 * @var  string
	 */
	public $root_select = '';

	/**
	 * List of database roots
	 *
	 * @var  array
	 */
	public $roots = [];

	/**
	 * Main page
	 */
	public function onBeforeMain()
	{
		// Load Javascript files
		$this->container->template->addJS('media://com_akeeba/js/FileFilters.min.js', true, false, $this->container->mediaVersion);
		$this->container->template->addJS('media://com_akeeba/js/DatabaseFilters.min.js', true, false, $this->container->mediaVersion);

		/** @var DatabaseFilters $model */
		$model = $this->getModel();

		// Add custom submenus
		$task    = $model->getState('browse_task', 'normal', 'cmd');
		$toolbar = $this->container->toolbar;

		$toolbar->appendLink(
			JText::_('COM_AKEEBA_FILEFILTERS_LABEL_NORMALVIEW'),
			JUri::base() . 'index.php?option=com_akeeba&view=DatabaseFilters&task=normal',
			($task == 'normal')
		);
		$toolbar->appendLink(
			JText::_('COM_AKEEBA_FILEFILTERS_LABEL_TABULARVIEW'),
			JUri::base() . 'index.php?option=com_akeeba&view=DatabaseFilters&task=tabular',
			($task == 'tabular')
		);

		// Get a JSON representation of the available roots
		$root_info = $model->get_roots();
		$roots     = [];
		$options   = [];

		if (!empty($root_info))
		{
			// Loop all dir definitions
			foreach ($root_info as $def)
			{
				$roots[]   = $def->value;
				$options[] = JHtml::_('select.option', $def->value, $def->text);
			}
		}

		$siteRoot          = '[SITEDB]';
		$selectOptions     = [
			'list.select' => $siteRoot,
			'id'          => 'active_root',
		];
		$this->root_select = JHtml::_('select.genericlist', $options, 'root', $selectOptions);
		$this->roots       = $roots;
		$platform          = $this->container->platform;

		// Add script options
		$platform->addScriptOptions('akeeba.System.params.AjaxURL', 'index.php?option=com_akeeba&view=DatabaseFilters&task=ajax');

		switch ($task)
		{
			case 'normal':
			default:
				$this->setLayout('default');

				// Get the database entities GUI data
				$platform->addScriptOptions('akeeba.DatabaseFilters.guiData', $model->make_listing($siteRoot));
				$platform->addScriptOptions('akeeba.DatabaseFilters.viewType', 'list');

				break;

			case 'tabular':
				$this->setLayout('tabular');

				// Get the filter data for tabular display
				$platform->addScriptOptions('akeeba.DatabaseFilters.guiData', $model->get_filters($siteRoot));
				$platform->addScriptOptions('akeeba.DatabaseFilters.viewType', 'tabular');


				break;
		}

		// Translations
		JText::script('COM_AKEEBA_FILEFILTERS_LABEL_UIROOT');
		JText::script('COM_AKEEBA_FILEFILTERS_LABEL_UIERRORFILTER');
		JText::script('COM_AKEEBA_DBFILTER_TYPE_TABLES');
		JText::script('COM_AKEEBA_DBFILTER_TYPE_TABLEDATA');
		JText::script('COM_AKEEBA_DBFILTER_TABLE_MISC');
		JText::script('COM_AKEEBA_DBFILTER_TABLE_TABLE');
		JText::script('COM_AKEEBA_DBFILTER_TABLE_VIEW');
		JText::script('COM_AKEEBA_DBFILTER_TABLE_PROCEDURE');
		JText::script('COM_AKEEBA_DBFILTER_TABLE_FUNCTION');
		JText::script('COM_AKEEBA_DBFILTER_TABLE_TRIGGER');
		JText::script('COM_AKEEBA_DBFILTER_TABLE_META_ROWCOUNT');

		$this->getProfileIdAndName();
	}
}
PK�!]��C!!
View/hhvm.phpnu&1i�<?php
/**
 * @package   akeebabackup
 * @copyright Copyright (c)2006-2021 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

// Protect from unauthorized access
defined('_JEXEC') or die();

?>

<div style="margin: 1em">
	<h1>We have detected that you are running HHVM instead of PHP. This software WILL NOT WORK properly on HHVM. Please switch to PHP 7 instead.</h1>
	<hr/>
	<p>
        HHVM was Facebook's attempt at modernizing the PHP 5.x language and making it faster. Unfortunately it's also incompatible with PHP proper.
        PHP 7 has solved all these issues. It's fast, modern and <em>fully compatible with our software</em>.
        Please switch to PHP 7. If you are unsure how to do that, contact your host or the person responsible for maintaining your server.
        They are the only people who can help you configure your server.
	</p>
	<p>
        Kindly note that HHVM is not -and has never been- a supported execution environment for our software.
        As a result, if you see this message on your site you are unfortunately ineligible for support and / or filing bug reports.
        Please switch to PHP 7. If your problem persists after that we can help you / accept your bug report. Thank you for your understanding.
	</p>
</div>
PK�!]�|A@@View/ViewTraits/ProfileList.phpnu&1i�<?php
/**
 * @package   akeebabackup
 * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

namespace Akeeba\Backup\Admin\View\ViewTraits;

// Protect from unauthorized access
use Joomla\CMS\HTML\HTMLHelper;

defined('_JEXEC') || die();

trait ProfileList
{
	/**
	 * List of backup profiles, for use with JHtmlSelect
	 *
	 * @var   array
	 */
	public $profileList = [];

	/**
	 * Populates the profileList property with an options list for use by JHtmlSelect
	 *
	 * @param   bool  $includeId  Should I include the profile ID in front of the name?
	 *
	 * @return  void
	 */
	protected function getProfileList($includeId = true)
	{
		/** @var \JDatabaseDriver $db */
		$db = $this->container->db;

		$query = $db->getQuery(true)
			->select([
				$db->qn('id'),
				$db->qn('description'),
			])->from($db->qn('#__ak_profiles'))
			->order($db->qn('id') . " ASC");

		$db->setQuery($query);
		$rawList = $db->loadAssocList();

		$this->profileList = [];

		if (!is_array($rawList))
		{
			return;
		}

		foreach ($rawList as $row)
		{
			$description = $row['description'];

			if ($includeId)
			{
				$description = '#' . $row['id'] . '. ' . $description;
			}

			$this->profileList[] = HTMLHelper::_('select.option', $row['id'], $description);
		}
	}
}
PK�!]<B&�ww$View/ViewTraits/ProfileIdAndName.phpnu&1i�<?php
/**
 * @package   akeebabackup
 * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

namespace Akeeba\Backup\Admin\View\ViewTraits;

// Protect from unauthorized access
defined('_JEXEC') || die();

use Akeeba\Backup\Admin\Model\Profiles;
use Akeeba\Engine\Platform;

trait ProfileIdAndName
{
	/**
	 * Active profile ID
	 *
	 * @var  int
	 */
	public $profileId = 0;

	/**
	 * Active profile's description
	 *
	 * @var  string
	 */
	public $profileName = '';

	/**
	 * Is this profile available as an One Click Backup icon? 0/1
	 *
	 * @var  int
	 */
	public $quickIcon = 0;

	/**
	 * Find the currently active profile ID and name and put them in properties accessible by the view template
	 */
	protected function getProfileIdAndName()
	{
		/** @var Profiles $profilesModel */
		$profilesModel = $this->container->factory->model('Profiles')->tmpInstance();
		$profileId     = Platform::getInstance()->get_active_profile();

		try
		{
			$this->profileName = $profilesModel->findOrFail($profileId)->description;
			$this->profileId   = $profileId;
			$this->quickIcon   = $profilesModel->quickicon;
		}
		catch (\Exception $e)
		{
			$this->container->platform->setSessionVar('profile', 1, 'akeeba');

			$this->profileId   = 1;
			$this->profileName = $profilesModel->findOrFail(1)->description;
		}
	}
}
PK�!]]�!��!View/ConfigurationWizard/Html.phpnu&1i�<?php
/**
 * @package   akeebabackup
 * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

namespace Akeeba\Backup\Admin\View\ConfigurationWizard;

// Protect from unauthorized access
defined('_JEXEC') || die();

use FOF40\View\DataView\Html as BaseView;
use Joomla\CMS\Language\Text;

class Html extends BaseView
{
	protected function onBeforeMain()
	{
		// Push translations
		// -- Wizard
		Text::script('COM_AKEEBA_CONFWIZ_UI_MINEXECTRY');
		Text::script('COM_AKEEBA_CONFWIZ_UI_CANTSAVEMINEXEC');
		Text::script('COM_AKEEBA_CONFWIZ_UI_SAVEMINEXEC');
		Text::script('COM_AKEEBA_CONFWIZ_UI_CANTDETERMINEMINEXEC');
		Text::script('COM_AKEEBA_CONFWIZ_UI_CANTFIXDIRECTORIES');
		Text::script('COM_AKEEBA_CONFWIZ_UI_CANTDBOPT');
		Text::script('COM_AKEEBA_CONFWIZ_UI_EXECTOOLOW');
		Text::script('COM_AKEEBA_CONFWIZ_UI_SAVINGMAXEXEC');
		Text::script('COM_AKEEBA_CONFWIZ_UI_CANTSAVEMAXEXEC');
		Text::script('COM_AKEEBA_CONFWIZ_UI_CANTDETERMINEPARTSIZE');
		Text::script('COM_AKEEBA_CONFWIZ_UI_PARTSIZE');

		// -- Backup
		Text::script('COM_AKEEBA_BACKUP_TEXT_LASTRESPONSE', true);

		// Load the Configuration Wizard Javascript file
		$this->container->template->addJS('media://com_akeeba/js/Backup.min.js', true, false, $this->container->mediaVersion);
		$this->container->template->addJS('media://com_akeeba/js/ConfigurationWizard.min.js', true, false, $this->container->mediaVersion);

		$platform = $this->container->platform;
		$platform->addScriptOptions('akeeba.System.params.AjaxURL', 'index.php?option=com_akeeba&view=ConfigurationWizard&task=ajax');

		// Set the layour
		$this->setLayout('wizard');
	}
}
PK�!]s+��#
#
View/Log/Html.phpnu&1i�<?php
/**
 * @package   akeebabackup
 * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

namespace Akeeba\Backup\Admin\View\Log;

// Protect from unauthorized access
defined('_JEXEC') || die();

use Akeeba\Backup\Admin\Model\Log;
use Akeeba\Backup\Admin\View\ViewTraits\ProfileIdAndName;
use Akeeba\Engine\Factory;
use FOF40\View\DataView\Html as BaseView;
use Joomla\CMS\HTML\HTMLHelper;

/**
 * View controller for the Log Viewer page
 */
class Html extends BaseView
{
	use ProfileIdAndName;

	/**
	 * Big log file threshold: 2Mb
	 */
	public const bigLogSize = 2097152;
	/**
	 * JHtml list of available log files
	 *
	 * @var  array
	 */
	public $logs = [];
	/**
	 * Currently selected log file tag
	 *
	 * @var  string
	 */
	public $tag;
	/**
	 * Is the select log too big for being
	 *
	 * @var bool
	 */
	public $logTooBig = false;
	/**
	 * Size of the log file
	 *
	 * @var int
	 */
	public $logSize = 0;

	/**
	 * The main page of the log viewer. It allows you to select a profile to display. When you do it displays the IFRAME
	 * with the actual log content and the button to download the raw log file.
	 *
	 * @return  void
	 */
	public function onBeforeMain()
	{
		// Load the view-specific Javascript
		$this->container->template->addJS('media://com_akeeba/js/Log.min.js', true, false, $this->container->mediaVersion);

		if (version_compare(JVERSION, '3.999.999', 'lt'))
		{
			HTMLHelper::_('formbehavior.chosen');
		}

		// Get a list of log names
		/** @var Log $model */
		$model      = $this->getModel();
		$this->logs = $model->getLogList();

		$tag = $model->getState('tag', '', 'string');

		if (empty($tag))
		{
			$tag = null;
		}

		$this->tag = $tag;

		// Let's check if the file is too big to display
		if ($this->tag)
		{
			$logFile = Factory::getLog()->getLogFilename($this->tag);

			if (!@is_file($logFile) && @file_exists(substr($logFile, 0, -4)))
			{
				/**
				 * Transitional period: the log file akeeba.tag.log.php may not exist but the akeeba.tag.log does. This
				 * addresses this transition.
				 */
				$logFile = substr($logFile, 0, -4);
			}

			if (@file_exists($logFile))
			{
				$this->logSize   = filesize($logFile);
				$this->logTooBig = ($this->logSize >= self::bigLogSize);
			}
		}

		if ($this->logTooBig)
		{
			$src = 'index.php?option=com_akeeba&view=Log&task=inlineRaw&&tag=' . urlencode($this->tag) . '&tmpl=component';
			$this->container->platform->addScriptOptions('akeeba.Log.iFrameSrc', $src);
		}

		$this->getProfileIdAndName();
	}
}
PK�!]�s[ttView/Log/Raw.phpnu&1i�<?php
/**
 * @package   akeebabackup
 * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

namespace Akeeba\Backup\Admin\View\Log;

// Protect from unauthorized access
defined('_JEXEC') || die();

use Akeeba\Backup\Admin\Model\Log;
use Akeeba\Engine\Platform;
use FOF40\View\DataView\Html as BaseView;

/**
 * View controller for the Log Viewer page
 */
class Raw extends BaseView
{
	/**
	 * Currently selected log file tag
	 *
	 * @var  string
	 */
	public $tag;

	/**
	 * Renders the actual log content, for use in the IFRAME
	 *
	 * @return  void
	 */
	public function onBeforeIframe()
	{
		/** @var Log $model */
		$model = $this->getModel();
		$tag   = $model->getState('tag', '', 'string');

		if (empty($tag))
		{
			$tag = null;
		}

		$this->tag = $tag;

		$this->setLayout('raw');
	}
}
PK�!]�Eȥg:g:View/Manage/Html.phpnu&1i�<?php
/**
 * @package   akeebabackup
 * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

namespace Akeeba\Backup\Admin\View\Manage;

// Protect from unauthorized access
defined('_JEXEC') || die();

use Akeeba\Backup\Admin\Model\Profiles;
use Akeeba\Backup\Admin\Model\Statistics;
use Akeeba\Engine\Factory;
use Akeeba\Engine\Platform;
use DateTimeZone;
use Exception;
use FOF40\Date\Date;
use FOF40\View\DataView\Html as BaseView;
use Joomla\CMS\Factory as JFactory;
use Joomla\CMS\HTML\HTMLHelper as JHtml;
use Joomla\CMS\Language\Text as JText;
use Joomla\CMS\Pagination\Pagination;
use Joomla\CMS\Uri\Uri as JUri;
use stdClass;

/**
 * View controller for the Backup Now page
 */
class Html extends BaseView
{
	/**
	 * Should I use the user's local time zone for display?
	 *
	 * @var  boolean
	 */
	public $useLocalTime;

	/**
	 * Time format string to use for the time zone suffix
	 *
	 * @var  string
	 */
	public $timeZoneFormat;

	/**
	 * The backup record for the showcomment view
	 *
	 * @var  array
	 */
	public $record = [];

	/**
	 * The backup record ID for the showcomment view
	 *
	 * @var  int
	 */
	public $record_id = 0;

	/**
	 * List of Profiles objects
	 *
	 * @var  array
	 */
	public $profiles = [];

	/**
	 * List of profiles for JHtmlSelect
	 *
	 * @var  array
	 */
	public $profilesList = [];

	/**
	 * List of frozen options for JHtmlSelect
	 *
	 * @var  array
	 */
	public $frozenList = [];

	/**
	 * Order direction, ASC/DESC
	 *
	 * @var  string
	 */
	public $order_Dir = 'DESC';

	/**
	 * Description filter
	 *
	 * @var string
	 */
	public $fltDescription = '';

	/**
	 * From date filter
	 *
	 * @var  string
	 */
	public $fltFrom = '';

	/**
	 * To date filter
	 *
	 * @var  string
	 */
	public $fltTo = '';

	/**
	 * Origin filter
	 *
	 * @var  string
	 */
	public $fltOrigin = '';

	/**
	 * Profile filter
	 *
	 * @var  string
	 */
	public $fltProfile = '';

	/**
	 * Frozen records filter
	 *
	 * @var string
	 */
	public $fltFrozen = '';

	/**
	 * List of records to display
	 *
	 * @var  array
	 */
	public $items = [];

	/**
	 * Pagination object
	 *
	 * @var Pagination
	 */
	public $pagination = null;

	/**
	 * Date format for the backup start time
	 *
	 * @var  string
	 */
	public $dateFormat = '';

	/**
	 * Should I pormpt the user ot run the configuration wizard?
	 *
	 * @var  bool
	 */
	public $promptForBackupRestoration = false;

	/**
	 * Sorting order options
	 *
	 * @var  array
	 */
	public $sortFields = [];

	/**
	 * Cache the user permissions
	 *
	 * @var   array
	 *
	 * @since 5.3.0
	 */
	public $permissions = [];

	/**
	 * List the backup records
	 *
	 * @return  void
	 *
	 * @throws  Exception
	 */
	public function onBeforeMain()
	{
		// Load custom Javascript for this page
		$this->container->template->addJS('media://com_akeeba/js/Manage.min.js', true, false, $this->container->mediaVersion);

		$user              = $this->container->platform->getUser();
		$this->permissions = [
			'configure' => $user->authorise('akeeba.configure', 'com_akeeba'),
			'backup'    => $user->authorise('akeeba.backup', 'com_akeeba'),
			'download'  => $user->authorise('akeeba.download', 'com_akeeba'),
		];


		/** @var Profiles $profilesModel */
		$profilesModel           = $this->container->factory->model('Profiles')->tmpInstance();
		$enginesPerPprofile      = $profilesModel->getPostProcessingEnginePerProfile();
		$this->enginesPerProfile = $enginesPerPprofile;

		// "Show warning first" download button.
		JText::script('COM_AKEEBA_BUADMIN_LOG_DOWNLOAD_CONFIRM', false);
		$this->container->platform->addScriptOptions('akeeba.Manage.baseURI', JUri::base());

		if (version_compare(JVERSION, '3.999.999', 'le'))
		{
			JHtml::_('behavior.calendar');
		}

		$hash = 'akeebamanage';

		// ...ordering
		$platform = $this->container->platform;
		$input    = $this->input;

		// ...filter state
		$this->fltDescription   = $platform->getUserStateFromRequest($hash . 'filter_description', 'description', $input, '');
		$this->fltFrom          = $platform->getUserStateFromRequest($hash . 'filter_from', 'from', $input, '');
		$this->fltTo            = $platform->getUserStateFromRequest($hash . 'filter_to', 'to', $input, '');
		$this->fltOrigin        = $platform->getUserStateFromRequest($hash . 'filter_origin', 'origin', $input, '');
		$this->fltProfile       = $platform->getUserStateFromRequest($hash . 'filter_profile', 'profile', $input, '');
		$this->fltFrozen        = $platform->getUserStateFromRequest($hash . 'filter_frozen', 'frozen', $input, '');

		$this->lists            = new stdClass();
		$this->lists->order     = $platform->getUserStateFromRequest($hash . 'filter_order', 'filter_order', $input, 'backupstart');
		$this->lists->order_Dir = $platform->getUserStateFromRequest($hash . 'filter_order_Dir', 'filter_order_Dir', $input, 'DESC');

		$filters  = $this->getFilters();
		$ordering = $this->getOrdering();

		/** @var Statistics $model */
		$model       = $this->getModel();
		$this->items = $model->getStatisticsListWithMeta(false, $filters, $ordering);

		// Default limits
		$defaultLimit = 20;

		if (!$this->container->platform->isCli() && class_exists('JFactory'))
		{
			$app = JFactory::getApplication();

			if (method_exists($app, 'get'))
			{
				$defaultLimit = $app->get('list_limit');
			}
		}

		$this->lists->limitStart = $model->getState('limitstart', 0, 'int');
		$this->lists->limit      = $model->getState('limit', $defaultLimit, 'int');

		// Let's create an array indexed with the profile id for better handling
		$profiles = $profilesModel->get(true);

		$profilesList = [
			JHtml::_('select.option', '', '–' . JText::_('COM_AKEEBA_BUADMIN_LABEL_PROFILEID') . '–'),
		];

		if (!empty($profiles))
		{
			foreach ($profiles as $profile)
			{
				$profilesList[] = JHtml::_('select.option', $profile->id, '#' . $profile->id . '. ' . $profile->description);
			}
		}

		// Assign data to the view
		$this->profiles     = $profiles; // Profiles
		$this->profilesList = $profilesList; // Profiles list for select box
		$this->itemCount    = count($this->items);
		$this->pagination   = $model->getPagination($filters); // Pagination object

		$this->frozenList = [
			JHtml::_('select.option', '', '–' . JText::_('COM_AKEEBA_BUADMIN_LABEL_FROZEN_SELECT') . '–'),
			JHtml::_('select.option', '1', JText::_('COM_AKEEBA_BUADMIN_LABEL_FROZEN_FROZEN')),
			JHtml::_('select.option', '2', JText::_('COM_AKEEBA_BUADMIN_LABEL_FROZEN_UNFROZEN')),
		];

		if ($this->lists->order_Dir)
		{
			$this->lists->order_Dir = strtolower($this->lists->order_Dir);
		}

		// Date format
		$dateFormat       = $this->container->params->get('dateformat', '');
		$dateFormat       = trim($dateFormat);
		$this->dateFormat = !empty($dateFormat) ? $dateFormat : JText::_('DATE_FORMAT_LC4');

		// Time zone options
		$this->useLocalTime   = $this->container->params->get('localtime', '1') == 1;
		$this->timeZoneFormat = $this->container->params->get('timezonetext', 'T');

		// Should I show the prompt for the configuration wizard?
		$this->promptForBackupRestoration = $this->container->params->get('show_howtorestoremodal', 1) != 0;

		// Construct the array of sorting fields
		$this->sortFields = [
			'id'          => JText::_('COM_AKEEBA_BUADMIN_LABEL_ID'),
			'description' => JText::_('COM_AKEEBA_BUADMIN_LABEL_DESCRIPTION'),
			'backupstart' => JText::_('COM_AKEEBA_BUADMIN_LABEL_START'),
			'profile_id'  => JText::_('COM_AKEEBA_BUADMIN_LABEL_PROFILEID'),
		];
	}

	/**
	 * Edit a backup record's description and comment
	 *
	 * @return  void
	 */
	public function onBeforeShowcomment()
	{
		/** @var Statistics $model */
		$model           = $this->getModel();
		$id              = $model->getState('id', 0, 'int');
		$record          = Platform::getInstance()->get_statistics($id);
		$this->record    = $record;
		$this->record_id = $id;

		$this->setLayout('comment');
	}

	/**
	 * File size formatting function. COnverts number of bytes to a human readable represenation.
	 *
	 * @param   int     $sizeInBytes         Size in bytes
	 * @param   int     $decimals            How many decimals should I use? Default: 2
	 * @param   string  $decSeparator        Decimal separator
	 * @param   string  $thousandsSeparator  Thousands grouping character
	 *
	 * @return string
	 */
	public function formatFilesize($sizeInBytes, $decimals = 2, $decSeparator = '.', $thousandsSeparator = '')
	{
		if ($sizeInBytes <= 0)
		{
			return '-';
		}

		$units = ['b', 'KB', 'MB', 'GB', 'TB'];
		$unit  = floor(log($sizeInBytes, 2) / 10);

		if ($unit == 0)
		{
			$decimals = 0;
		}

		return number_format($sizeInBytes / (1024 ** $unit), $decimals, $decSeparator, $thousandsSeparator) . ' ' . $units[$unit];
	}

	/**
	 * Translates the internal backup type (e.g. cli) to a human readable string
	 *
	 * @param   string  $recordType  The internal backup type
	 *
	 * @return  string
	 */
	public function translateBackupType($recordType)
	{
		static $backup_types = null;

		if (!is_array($backup_types))
		{
			// Load a mapping of backup types to textual representation
			$scripting    = Factory::getEngineParamsProvider()->loadScripting();
			$backup_types = [];
			foreach ($scripting['scripts'] as $key => $data)
			{
				$backup_types[$key] = JText::_($data['text']);
			}
		}

		if (array_key_exists($recordType, $backup_types))
		{
			return $backup_types[$recordType];
		}

		return '&ndash;';
	}

	/**
	 * Returns the origin's translated name and the appropriate icon class
	 *
	 * @param   array  $record  A backup record
	 *
	 * @return  array  array(originTranslation, iconClass)
	 */
	protected function getOriginInformation($record)
	{
		$originLanguageKey = 'COM_AKEEBA_BUADMIN_LABEL_ORIGIN_' . $record['origin'];
		$originDescription = JText::_($originLanguageKey);

		switch (strtolower($record['origin']))
		{
			case 'backend':
				$originIcon = 'akion-android-desktop';
				break;

			case 'frontend':
				$originIcon = 'akion-ios-world';
				break;

			case 'json':
				$originIcon = 'akion-android-cloud';
				break;

			case 'cli':
				$originIcon = 'akion-ios-paper-outline';
				break;

			case 'xmlrpc':
				$originIcon = 'akion-code';
				break;

			case 'lazy':
				$originIcon = 'akion-cube';
				break;

			default:
				$originIcon = 'akion-help';
				break;
		}

		if (empty($originLanguageKey) || ($originDescription == $originLanguageKey))
		{
			$originDescription = '&ndash;';
			$originIcon        = 'akion-help';

			return [$originDescription, $originIcon];
		}

		return [$originDescription, $originIcon];
	}

	/**
	 * Get the start time and duration of a backup record
	 *
	 * @param   array  $record  A backup record
	 *
	 * @return  array  array(startTimeAsString, durationAsString)
	 */
	protected function getTimeInformation($record)
	{
		$utcTimeZone = new DateTimeZone('UTC');
		$startTime   = new Date($record['backupstart'], $utcTimeZone);
		$endTime     = new Date($record['backupend'], $utcTimeZone);

		$duration = $endTime->toUnix() - $startTime->toUnix();

		if ($duration > 0)
		{
			$seconds  = $duration % 60;
			$duration = $duration - $seconds;

			$minutes  = ($duration % 3600) / 60;
			$duration = $duration - $minutes * 60;

			$hours    = $duration / 3600;
			$duration = sprintf('%02d', $hours) . ':' . sprintf('%02d', $minutes) . ':' . sprintf('%02d', $seconds);
		}
		else
		{
			$duration = '';
		}

		$user   = $this->container->platform->getUser();
		$userTZ = $user->getParam('timezone', 'UTC');
		$tz     = new DateTimeZone($userTZ);
		$startTime->setTimezone($tz);

		$timeZoneSuffix = '';

		if (!empty($this->timeZoneFormat))
		{
			$timeZoneSuffix = $startTime->format($this->timeZoneFormat, $this->useLocalTime);
		}

		return [
			$startTime->format($this->dateFormat, $this->useLocalTime),
			$duration,
			$timeZoneSuffix,
		];
	}

	/**
	 * Get the class and icon for the backup status indicator
	 *
	 * @param   array  $record  A backup record
	 *
	 * @return  array  array(class, icon)
	 */
	protected function getStatusInformation($record)
	{
		$statusClass = '';

		switch ($record['meta'])
		{
			case 'ok':
				$statusIcon  = 'akion-checkmark';
				$statusClass = 'akeeba-label--green';
				break;
			case 'pending':
				$statusIcon  = 'akion-play';
				$statusClass = 'akeeba-label--orange';
				break;
			case 'fail':
				$statusIcon  = 'akion-android-cancel';
				$statusClass = 'akeeba-label--red';
				break;
			case 'remote':
				$statusIcon  = 'akion-cloud';
				$statusClass = 'akeeba-label--teal';
				break;
			default:
				$statusIcon  = 'akion-trash-a';
				$statusClass = 'akeeba-label--grey';
				break;
		}

		return [$statusClass, $statusIcon];
	}

	/**
	 * Get the profile name for the backup record (or "–" if the profile no longer exists)
	 *
	 * @param   array  $record  A backup record
	 *
	 * @return  string
	 */
	protected function getProfileName($record)
	{
		$profileName = '&mdash;';

		if (isset($this->profiles[$record['profile_id']]))
		{
			$profileName = $this->escape($this->profiles[$record['profile_id']]->description);

			return $profileName;
		}

		return $profileName;
	}

	/**
	 * Get the filters in a format that Akeeba Engine understands
	 *
	 * @return  array
	 */
	private function getFilters()
	{
		$filters = [];

		if ($this->fltDescription)
		{
			$filters[] = [
				'field'   => 'description',
				'operand' => 'LIKE',
				'value'   => $this->fltDescription,
			];
		}

		if ($this->fltFrom && $this->fltTo)
		{
			$filters[] = [
				'field'   => 'backupstart',
				'operand' => 'BETWEEN',
				'value'   => $this->fltFrom,
				'value2'  => $this->fltTo,
			];
		}
		elseif ($this->fltFrom)
		{
			$filters[] = [
				'field'   => 'backupstart',
				'operand' => '>=',
				'value'   => $this->fltFrom,
			];
		}
		elseif ($this->fltTo)
		{
			$toDate = new Date($this->fltTo);
			$to     = $toDate->format('Y-m-d') . ' 23:59:59';

			$filters[] = [
				'field'   => 'backupstart',
				'operand' => '<=',
				'value'   => $to,
			];
		}

		if ($this->fltOrigin)
		{
			$filters[] = [
				'field'   => 'origin',
				'operand' => '=',
				'value'   => $this->fltOrigin,
			];
		}

		if ($this->fltProfile)
		{
			$filters[] = [
				'field'   => 'profile_id',
				'operand' => '=',
				'value'   => (int) $this->fltProfile,
			];
		}

		if ($this->fltFrozen == 1)
		{
			$filters[] = [
				'field'   => 'frozen',
				'operand' => '=',
				'value'   => 1,
			];
		}
		elseif ($this->fltFrozen == 2)
		{
			$filters[] = [
				'field'   => 'frozen',
				'operand' => '=',
				'value'   => 0,
			];
		}

		if (empty($filters))
		{
			$filters = null;
		}

		return $filters;
	}

	/**
	 * Get the list ordering in a format that Akeeba Engine understands
	 *
	 * @return  array
	 */
	private function getOrdering()
	{
		$order = [
			'by'    => $this->lists->order,
			'order' => strtoupper($this->lists->order_Dir),
		];

		return $order;
	}

}
PK�!]X/GvLLView/Backup/Html.phpnu&1i�<?php
/**
 * @package   akeebabackup
 * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

namespace Akeeba\Backup\Admin\View\Backup;

// Protect from unauthorized access
defined('_JEXEC') || die();

use Akeeba\Backup\Admin\Helper\Status;
use Akeeba\Backup\Admin\Helper\Utils;
use Akeeba\Backup\Admin\Model\Backup;
use Akeeba\Backup\Admin\Model\ControlPanel;
use Akeeba\Backup\Admin\View\ViewTraits\ProfileIdAndName;
use Akeeba\Backup\Admin\View\ViewTraits\ProfileList;
use Akeeba\Engine\Factory;
use FOF40\View\DataView\Html as BaseView;
use Joomla\CMS\Language\Text;

/**
 * View controller for the Backup Now page
 */
class Html extends BaseView
{
	use ProfileList, ProfileIdAndName;

	/**
	 * Do we have errors preventing the backup from starting?
	 *
	 * @var  bool
	 */
	public $hasErrors = false;

	/**
	 * Do we have warnings which may affect –but do not prevent– the backup from running?
	 *
	 * @var  bool
	 */
	public $hasWarnings = false;

	/**
	 * The HTML of the warnings cell
	 *
	 * @var  string
	 */
	public $warningsCell = '';

	/**
	 * Backup description
	 *
	 * @var  string
	 */
	public $description = '';

	/**
	 * Default backup description
	 *
	 * @var  string
	 */
	public $defaultDescription = '';

	/**
	 * Backup comment
	 *
	 * @var  string
	 */
	public $comment = '';

	/**
	 * JSON string of the backup domain name to titles associative array
	 *
	 * @var  array
	 */
	public $domains = '';

	/**
	 * Maximum execution time in seconds
	 *
	 * @var  int
	 */
	public $maxExecutionTime = 10;

	/**
	 * Execution time bias, in percentage points (0-100)
	 *
	 * @var  int
	 */
	public $runtimeBias = 75;

	/**
	 * URL to return to after the backup is complete
	 *
	 * @var  string
	 */
	public $returnURL = '';

	/**
	 * Is the output directory unwritable?
	 *
	 * @var  bool
	 */
	public $unwriteableOutput = false;

	/**
	 * has the user configured an ANGIE password?
	 *
	 * @var  string
	 */
	public $hasANGIEPassword = '';

	/**
	 * Should I autostart the backup?
	 *
	 * @var  string
	 */
	public $autoStart = false;

	/**
	 * Should I display desktop notifications? 0/1
	 *
	 * @var  int
	 */
	public $desktopNotifications = 0;

	/**
	 * Should I try to automatically resume the backup in case of an error? 0/1
	 *
	 * @var  int
	 */
	public $autoResume = 0;

	/**
	 * After how many seconds should I try to automatically resume the backup?
	 *
	 * @var  int
	 */
	public $autoResumeTimeout = 10;

	/**
	 * How many times in total should I try to automatically resume the backup?
	 *
	 * @var  int
	 */
	public $autoResumeRetries = 3;

	/**
	 * Should I prompt the user to run the Configuration Wizard?
	 *
	 * @var  bool
	 */
	public $promptForConfigurationWizard = false;

	/**
	 * Runs before displaying the backup page
	 */
	public function onBeforeMain()
	{
		// Load the view-specific Javascript
		$this->container->template->addJS('media://com_akeeba/js/Backup.min.js', true, false, $this->container->mediaVersion);

		// Load the models
		/** @var  Backup $model */
		$model = $this->getModel();

		/** @var ControlPanel $cpanelmodel */
		$cpanelmodel = $this->container->factory->model('ControlPanel')->tmpInstance();

		// Load the Status Helper
		$helper = Status::getInstance();

		// Determine default description
		$default_description = $this->getDefaultDescription();

		// Load data from the model state
		$backup_description = $model->getState('description', $default_description, 'string');
		$comment            = $model->getState('comment', '', 'html');
		$returnurl          = Utils::safeDecodeReturnUrl($model->getState('returnurl', ''));

		// Get the maximum execution time and bias
		$engineConfiguration = Factory::getConfiguration();
		$maxexec             = $engineConfiguration->get('akeeba.tuning.max_exec_time', 14) * 1000;
		$bias                = $engineConfiguration->get('akeeba.tuning.run_time_bias', 75);

		// Check if the output directory is writable
		$warnings         = Factory::getConfigurationChecks()->getDetailedStatus();
		$unwritableOutput = array_key_exists('001', $warnings);

		// Pass on data
		$this->getProfileList();
		$this->getProfileIdAndName();

		$this->hasErrors                    = !$helper->status;
		$this->hasWarnings                  = $helper->hasQuirks();
		$this->warningsCell                 = $helper->getQuirksCell(!$helper->status);
		$this->description                  = $backup_description;
		$this->defaultDescription           = $default_description;
		$this->comment                      = $comment;
		$this->domains                      = $this->getDomains();
		$this->maxExecutionTime             = $maxexec;
		$this->runtimeBias                  = $bias;
		$this->returnURL                    = $returnurl;
		$this->unwriteableOutput            = $unwritableOutput;
		$this->autoStart                    = $model->getState('autostart', 0, 'boolean');
		$this->desktopNotifications         = $this->container->params->get('desktop_notifications', '0') ? 1 : 0;
		$this->autoResume                   = $engineConfiguration->get('akeeba.advanced.autoresume', 1);
		$this->autoResumeTimeout            = $engineConfiguration->get('akeeba.advanced.autoresume_timeout', 10);
		$this->autoResumeRetries            = $engineConfiguration->get('akeeba.advanced.autoresume_maxretries', 3);
		$this->promptForConfigurationWizard = $engineConfiguration->get('akeeba.flag.confwiz', 0) == 0;
		$this->hasANGIEPassword = !empty(trim($engineConfiguration->get('engine.installer.angie.key', '')));
	}

	/**
	 * Get the default description for this backup attempt
	 *
	 * @return  string
	 */
	private function getDefaultDescription()
	{
		return $this->getModel()->getDefaultDescription();
	}

	/**
	 * Get a list of backup domain keys and titles
	 *
	 * @return  array
	 */
	private function getDomains()
	{
		$engineConfiguration = Factory::getConfiguration();
		$script              = $engineConfiguration->get('akeeba.basic.backup_type', 'full');
		$scripting           = Factory::getEngineParamsProvider()->loadScripting();
		$domains             = [];

		if (empty($scripting))
		{
			return $domains;
		}

		foreach ($scripting['scripts'][$script]['chain'] as $domain)
		{
			$description = Text::_($scripting['domains'][$domain]['text']);
			$domain_key  = $scripting['domains'][$domain]['domain'];
			$domains[]   = [$domain_key, $description];
		}

		return $domains;
	}
}
PK�!]���؟�View/Configuration/Html.phpnu&1i�<?php
/**
 * @package   akeebabackup
 * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

namespace Akeeba\Backup\Admin\View\Configuration;

// Protect from unauthorized access
defined('_JEXEC') || die();

use Akeeba\Backup\Admin\View\ViewTraits\ProfileIdAndName;
use Akeeba\Engine\Factory;
use Akeeba\Engine\Platform;
use FOF40\View\DataView\Html as BaseView;
use Joomla\CMS\Language\Text as JText;

class Html extends BaseView
{
	use ProfileIdAndName;

	/**
	 * Status of the settings encryption: -1 disabled by user, 0 not available, 1 enabled and active
	 *
	 * @var  int
	 */
	public $secureSettings = 0;

	/**
	 * Should I show the Configuration Wizard popup prompt?
	 *
	 * @var  bool
	 */
	public $promptForConfigurationWizard = false;

	/**
	 * Executes when displaying the page
	 */
	public function onBeforeMain()
	{
		$this->container->template->addJS('media://com_akeeba/js/Configuration.min.js', true, false, $this->container->mediaVersion);

		$this->getProfileIdAndName();

		// Are the settings secured?
		$this->secureSettings = $this->getSecureSettingsOption();

		// Should I show the Configuration Wizard popup prompt?
		$this->promptForConfigurationWizard = Factory::getConfiguration()->get('akeeba.flag.confwiz', 0) != 1;

		// Push script options
		$urls = array(
			'browser'      => addslashes('index.php?option=com_akeeba&view=Browser&processfolder=1&tmpl=component&folder='),
			'ftpBrowser'   => addslashes('index.php?option=com_akeeba&view=FTPBrowser'),
			'sftpBrowser'  => addslashes('index.php?option=com_akeeba&view=SFTPBrowser'),
			'testFtp'      => addslashes('index.php?option=com_akeeba&view=Configuration&task=testftp'),
			'testSftp'     => addslashes('index.php?option=com_akeeba&view=Configuration&task=testsftp'),
			'dpeauthopen'  => addslashes('index.php?option=com_akeeba&view=Configuration&task=dpeoauthopen&format=raw'),
			'dpecustomapi' => addslashes('index.php?option=com_akeeba&view=Configuration&task=dpecustomapi&format=raw'),
		);

		// Push script options
		$platform = $this->container->platform;
		$platform->addScriptOptions('akeeba.Configuration.URLs', $urls);
		$platform->addScriptOptions('akeeba.Configuration.GUIData', json_decode(Factory::getEngineParamsProvider()->getJsonGuiDefinition(), true));


		// Push translations
		JText::script('COM_AKEEBA_CONFIG_UI_BROWSE');
		JText::script('COM_AKEEBA_CONFIG_UI_CONFIG');
		JText::script('COM_AKEEBA_CONFIG_UI_REFRESH');
		JText::script('COM_AKEEBA_FILEFILTERS_LABEL_UIROOT');
		JText::script('COM_AKEEBA_CONFIG_UI_FTPBROWSER_TITLE');
		JText::script('COM_AKEEBA_CONFIG_DIRECTFTP_TEST_OK');
		JText::script('COM_AKEEBA_CONFIG_DIRECTFTP_TEST_FAIL');
		JText::script('COM_AKEEBA_CONFIG_DIRECTSFTP_TEST_OK');
		JText::script('COM_AKEEBA_CONFIG_DIRECTSFTP_TEST_FAIL');
	}

	/**
	 * Returns the support status of settings encryption. The possible values are:
	 * -1 Disabled by the user
	 *  0 Enabled by inactive (not supported by the server)
	 *  1 Enabled and active
	 *
	 * @return  int
	 */
	private function getSecureSettingsOption()
	{
		// Encryption is disabled by the user
		if (Platform::getInstance()->get_platform_configuration_option('useencryption', -1) == 0)
		{
			return -1;
		}

		// Encryption is not supported by this server
		if (!Factory::getSecureSettings()->supportsEncryption())
		{
			return 0;
		}

		$filename = JPATH_COMPONENT_ADMINISTRATOR . '/BackupEngine/serverkey.php';

		// Encryption enabled, supported and a key file is present: encryption enabled
		if (is_file($filename))
		{
			return 1;
		}

		// Encryption enabled, supported but and a key file is NOT present: encryption not available
		return 0;
	}
}
PK�!]��ѡ��View/.htaccessnu&1i�<IfModule !mod_authz_core.c>
Order deny,allow
Deny from all
</IfModule>
<IfModule mod_authz_core.c>
  <RequireAll>
    Require all denied
  </RequireAll>
</IfModule>
PK�!]�q�99View/fef.phpnu&1i�<?php
/**
 * @package   akeebabackup
 * @copyright Copyright (c)2006-2021 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

// Protect from unauthorized access
defined('_JEXEC') or die();

?>

<div style="margin: 1em">
	<h1>Akeeba Frontend Framework (FEF) could not be found on this site</h1>
	<hr/>
	<div class="alert alert-warning">
		<h2>
			This component requires the Akeeba Frontend Framework (FEF) to be installed on your site. Please go to <a
					href="https://www.akeeba.com/download/official/fef.html">our download page</a> to download it, then install it on your site.
		</h2>
	</div>
	<hr/>
	<h4>Further information</h4>
	<p>
        FEF is the name of our custom CSS framework. It's responsible for rendering the interface of our Joomla!
		extensions. It is automatically installed when you install our extensions on your site.
	</p>
	<p>
		FEF can be missing from your site either because Joomla failed to install it or because you, another Super User,
		or another extension mistakenly uninstalled it.
	</p>
	<p>
		If it's missing we cannot display the interface to this component. That's why you see this message.
	</p>
	<p>
		You do not have to worry about adding bloat to your site. FEF is very small. It will also be automatically
		uninstalled when you uninstall all components which depend on it.
	</p>
	<p>
		FEF is installed in the <code>media/fef</code> folder under your site's root. It appears in Joomla's Extensions,
		Manage page as <code>file_fef</code>. Please do not remove it from your site.
	</p>
</div>
PK�!]|��NView/web.confignu&1i�<?xml version="1.0"?>
<!--
    This only works on IIS 7 or later. See https://www.iis.net/configreference/system.webserver/security/requestfiltering/fileextensions
-->
<configuration>
    <system.webServer>
        <security>
            <requestFiltering>
                <fileExtensions allowUnlisted="false" >
                    <clear />
                    <add fileExtension=".html" allowed="true"/>
                </fileExtensions>
            </requestFiltering>
        </security>
    </system.webServer>
</configuration>PK�!]�ҥ.View/Profiles/Html.phpnu&1i�<?php
/**
 * @package   akeebabackup
 * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

namespace Akeeba\Backup\Admin\View\Profiles;

// Protect from unauthorized access
defined('_JEXEC') || die();

use Akeeba\Backup\Admin\View\ViewTraits\ProfileIdAndName;
use FOF40\View\DataView\Html as BaseView;
use Joomla\CMS\HTML\HTMLHelper as JHtml;
use Joomla\CMS\Language\Text as JText;

/**
 * View controller for the profiles management page
 */
class Html extends BaseView
{
	use ProfileIdAndName;

	/**
	 * Sorting order fields
	 *
	 * @var  array
	 */
	public $sortFields;

	/**
	 * The default layout, shows a list of profiles
	 */
	function onBeforeBrowse()
	{
		$this->getProfileIdAndName();

		// Get Sort By fields
		$this->sortFields = [
			'id'          => JText::_('JGRID_HEADING_ID'),
			'description' => JText::_('COM_AKEEBA_PROFILES_COLLABEL_DESCRIPTION'),
		];

		parent::onBeforeBrowse();

		JHtml::_('behavior.multiselect');
		JHtml::_('dropdown.init');
	}

	/**
	 * The edit layout, editing a profile's name
	 */
	protected function onBeforeEdit()
	{
		parent::onBeforeEdit();

		// Include tooltip support
		if (version_compare(JVERSION, '3.999.999', 'lt'))
		{
			JHtml::_('behavior.tooltip');
		}
	}
}
PK�!]��r^nnView/Profiles/Json.phpnu&1i�<?php
/**
 * @package   akeebabackup
 * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

namespace Akeeba\Backup\Admin\View\Profiles;

// Protect from unauthorized access
defined('_JEXEC') || die();

use FOF40\View\DataView\Json as BaseView;

class Json extends BaseView
{

}
PK�!]����
�
View/RegExFileFilter/Html.phpnu&1i�<?php
/**
 * @package   akeebabackup
 * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

namespace Akeeba\Backup\Admin\View\RegExFileFilter;

// Protect from unauthorized access
use Akeeba\Backup\Admin\Model\RegExFileFilters;
use Akeeba\Backup\Admin\View\ViewTraits\ProfileIdAndName;
use Akeeba\Engine\Factory;
use Joomla\CMS\HTML\HTMLHelper as JHtml;
use Joomla\CMS\Language\Text as JText;
use Joomla\CMS\Uri\Uri as JUri;

defined('_JEXEC') || die();

class Html extends \FOF40\View\DataView\Html
{
	use ProfileIdAndName;

	/**
	 * SELECT element for choosing a database root
	 *
	 * @var  string
	 */
	public $root_select = '';

	/**
	 * List of database roots
	 *
	 * @var  array
	 */
	public $roots = [];

	/**
	 * Main page
	 */
	public function onBeforeMain()
	{
		// Load Javascript files
		$this->container->template->addJS('media://com_akeeba/js/FileFilters.min.js', true, false, $this->container->mediaVersion);
		$this->container->template->addJS('media://com_akeeba/js/RegExFileFilter.min.js', true, false, $this->container->mediaVersion);

		/** @var RegExFileFilters $model */
		$model = $this->getModel();

		// Get a JSON representation of the available roots
		$filters   = Factory::getFilters();
		$root_info = $filters->getInclusions('dir');
		$roots     = array();
		$options   = array();

		if (!empty($root_info))
		{
			// Loop all dir definitions
			foreach ($root_info as $dir_definition)
			{
				if (is_null($dir_definition[1]))
				{
					// Site root definition has a null element 1. It is always pushed on top of the stack.
					array_unshift($roots, $dir_definition[0]);
				}
				else
				{
					$roots[] = $dir_definition[0];
				}

				$options[] = JHtml::_('select.option', $dir_definition[0], $dir_definition[0]);
			}
		}
		$site_root         = $roots[0];
		$this->root_select = JHtml::_('select.genericlist', $options, 'root', [
			'list.select' => $site_root,
			'id'          => 'active_root',
		]);
		$this->roots       = $roots;

		// Pass script options
		$platform = $this->container->platform;
		$platform->addScriptOptions('akeeba.System.params.AjaxURL', JUri::base() . 'index.php?option=com_akeeba&view=RegExFileFilters&task=ajax');
		$platform->addScriptOptions('akeeba.RegExFileFilter.guiData', $model->get_regex_filters($site_root));

		$this->getProfileIdAndName();

		// Push translations
		JText::script('COM_AKEEBA_FILEFILTERS_LABEL_UIROOT');
		JText::script('COM_AKEEBA_FILEFILTERS_LABEL_UIERRORFILTER');
		JText::script('COM_AKEEBA_FILEFILTERS_TYPE_DIRECTORIES');
		JText::script('COM_AKEEBA_FILEFILTERS_TYPE_SKIPFILES');
		JText::script('COM_AKEEBA_FILEFILTERS_TYPE_SKIPDIRS');
		JText::script('COM_AKEEBA_FILEFILTERS_TYPE_FILES');

	}
}
PK�!]<�rӘ���script.akeeba.phpnu&1i�<?php
/**
 * @package    AkeebaBackup
 * @copyright  Copyright (c)2009-2016 Nicholas K. Dionysopoulos
 * @license    GNU General Public License version 3, or later
 *
 */
defined('_JEXEC') or die();

// Load FOF if not already loaded
if (!defined('F0F_INCLUDED'))
{
	$paths = array(
		(defined('JPATH_LIBRARIES') ? JPATH_LIBRARIES : JPATH_ROOT . '/libraries') . '/f0f/include.php',
		__DIR__ . '/fof/include.php',
	);

	foreach ($paths as $filePath)
	{
		if (!defined('F0F_INCLUDED') && file_exists($filePath))
		{
			@include_once $filePath;
		}
	}
}

// Pre-load the installer script class from our own copy of FOF
if (!class_exists('F0FUtilsInstallscript', false))
{
	@include_once __DIR__ . '/fof/utils/installscript/installscript.php';
}

// Pre-load the database schema installer class from our own copy of FOF
if (!class_exists('F0FDatabaseInstaller', false))
{
	@include_once __DIR__ . '/fof/database/installer.php';
}

// Pre-load the update utility class from our own copy of FOF
if (!class_exists('F0FUtilsUpdate', false))
{
	@include_once __DIR__ . '/fof/utils/update/update.php';
}

// Pre-load the cache cleaner utility class from our own copy of FOF
if (!class_exists('F0FUtilsCacheCleaner', false))
{
	@include_once __DIR__ . '/fof/utils/cache/cleaner.php';
}

class Com_AkeebaInstallerScript extends F0FUtilsInstallscript
{
	/**
	 * The title of the component (printed on installation and uninstallation messages)
	 *
	 * @var string
	 */
	protected $componentTitle = 'Akeeba Backup';

	/**
	 * The component's name
	 *
	 * @var   string
	 */
	protected $componentName = 'com_akeeba';

	/**
	 * The list of extra modules and plugins to install on component installation / update and remove on component
	 * uninstallation.
	 *
	 * @var   array
	 */
	protected $installation_queue = array(
		// modules => { (folder) => { (module) => { (position), (published) } }* }*
		'modules' => array(
			'admin' => array(
			),
			'site'  => array()
		),
		// plugins => { (folder) => { (element) => (published) }* }*
		'plugins' => array(
			'quickicon' => array(
				'akeebabackup' => 1,
			),
			'system'    => array(
				'akeebaupdatecheck' => 0,
				'backuponupdate'    => 1,
			),
		)
	);

	/**
	 * The list of obsolete extra modules and plugins to uninstall on component upgrade / installation.
	 *
	 * @var array
	 */
	protected $uninstallation_queue = array(
		// modules => { (folder) => { (module) }* }*
		'modules' => array(
			'admin' => array(
				'akeebabackup'
			),
			'site'  => array()
		),
		// plugins => { (folder) => { (element) }* }*
		'plugins' => array(
			'system' => array(
				'srp',
			),
			'installer' => array(
				'akeebabackup',
			),
		)
	);

	/**
	 * Obsolete files and folders to remove from the free version only. This is used when you move a feature from the
	 * free version of your extension to its paid version. If you don't have such a distinction you can ignore this.
	 *
	 * @var   array
	 */
	protected $removeFilesFree = array(
		'files'   => array(
			// Pro component features
			'administrator/components/com_akeeba/engine/Archiver/Directftp.php',
			'administrator/components/com_akeeba/engine/Archiver/directftp.ini',
			'administrator/components/com_akeeba/engine/Archiver/Directsftp.php',
			'administrator/components/com_akeeba/engine/Archiver/directsftp.ini',
			'administrator/components/com_akeeba/engine/Archiver/Jps.php',
			'administrator/components/com_akeeba/engine/Archiver/jps.ini',
			'administrator/components/com_akeeba/engine/Archiver/Zipnative.php',
			'administrator/components/com_akeeba/engine/Archiver/zipnative.ini',
			'administrator/components/com_akeeba/engine/Dump/Reverse.php',
			'administrator/components/com_akeeba/engine/Dump/reverse.ini',
			'administrator/components/com_akeeba/engine/Postproc/amazons3.ini',
			'administrator/components/com_akeeba/engine/Postproc/Amazons3.php',
			'administrator/components/com_akeeba/engine/Postproc/azure.ini',
			'administrator/components/com_akeeba/engine/Postproc/Azure.php',
			'administrator/components/com_akeeba/engine/Postproc/cloudfiles.ini',
			'administrator/components/com_akeeba/engine/Postproc/Cloudfiles.php',
			'administrator/components/com_akeeba/engine/Postproc/cloudme.ini',
			'administrator/components/com_akeeba/engine/Postproc/Cloudme.php',
			'administrator/components/com_akeeba/engine/Postproc/dreamobjects.ini',
			'administrator/components/com_akeeba/engine/Postproc/Dreamobjects.php',
			'administrator/components/com_akeeba/engine/Postproc/dropbox.ini',
			'administrator/components/com_akeeba/engine/Postproc/Dropbox.php',
			'administrator/components/com_akeeba/engine/Postproc/email.ini',
			'administrator/components/com_akeeba/engine/Postproc/Email.php',
			'administrator/components/com_akeeba/engine/Postproc/ftp.ini',
			'administrator/components/com_akeeba/engine/Postproc/Ftp.php',
			'administrator/components/com_akeeba/engine/Postproc/googlestorage.ini',
			'administrator/components/com_akeeba/engine/Postproc/Googlestorage.php',
			'administrator/components/com_akeeba/engine/Postproc/idrivesync.ini',
			'administrator/components/com_akeeba/engine/Postproc/Idrivesync.php',
			'administrator/components/com_akeeba/engine/Postproc/onedrive.ini',
			'administrator/components/com_akeeba/engine/Postproc/Onedrive.php',
			'administrator/components/com_akeeba/engine/Postproc/s3.ini',
			'administrator/components/com_akeeba/engine/Postproc/S3.php',
			'administrator/components/com_akeeba/engine/Postproc/sftp.ini',
			'administrator/components/com_akeeba/engine/Postproc/Sftp.php',
			'administrator/components/com_akeeba/engine/Postproc/sugarsync.ini',
			'administrator/components/com_akeeba/engine/Postproc/Sugarsync.php',
			'administrator/components/com_akeeba/engine/Postproc/webdav.ini',
			'administrator/components/com_akeeba/engine/Postproc/Webdav.php',
			'administrator/components/com_akeeba/engine/Scan/large.ini',
			'administrator/components/com_akeeba/engine/Scan/Large.php',
			'administrator/components/com_akeeba/controllers/alice.php',
			'administrator/components/com_akeeba/controllers/discover.php',
			'administrator/components/com_akeeba/controllers/eff.php',
			'administrator/components/com_akeeba/controllers/extfilter.php',
			'administrator/components/com_akeeba/controllers/multidb.php',
			'administrator/components/com_akeeba/controllers/regexdbfilter.php',
			'administrator/components/com_akeeba/controllers/regexfsfilter.php',
			'administrator/components/com_akeeba/controllers/remotefile.php',
			'administrator/components/com_akeeba/controllers/restore.php',
			'administrator/components/com_akeeba/controllers/s3import.php',
			'administrator/components/com_akeeba/controllers/srprestore.php',
			'administrator/components/com_akeeba/controllers/upload.php',
			'administrator/components/com_akeeba/models/alices.php',
			'administrator/components/com_akeeba/models/discovers.php',
			'administrator/components/com_akeeba/models/effs.php',
			'administrator/components/com_akeeba/models/extfilters.php',
			'administrator/components/com_akeeba/models/installer.php',
			'administrator/components/com_akeeba/models/multidbs.php',
			'administrator/components/com_akeeba/models/regexdbfilters.php',
			'administrator/components/com_akeeba/models/regexfsfilters.php',
			'administrator/components/com_akeeba/models/remotefiles.php',
			'administrator/components/com_akeeba/models/restores.php',
			'administrator/components/com_akeeba/models/s3imports.php',
			'administrator/components/com_akeeba/models/srprestores.php',
			'administrator/components/com_akeeba/models/uploads.php',
			'administrator/components/com_akeeba/platform/joomla25/Filter/Components.php',
			'administrator/components/com_akeeba/platform/joomla25/Filter/Extensiondirs.php',
			'administrator/components/com_akeeba/platform/joomla25/Filter/Extensionfiles.php',
			'administrator/components/com_akeeba/platform/joomla25/Filter/Languages.php',
			'administrator/components/com_akeeba/platform/joomla25/Filter/Modules.php',
			'administrator/components/com_akeeba/platform/joomla25/Filter/Plugins.php',
			'administrator/components/com_akeeba/platform/joomla25/Filter/Templates.php',
			'administrator/components/com_akeeba/views/buadmin/tmpl/restorepoint.php',
			'administrator/components/com_akeeba/assets/installers/abi.ini',
			'administrator/components/com_akeeba/assets/installers/abi.jpa',
			'administrator/components/com_akeeba/assets/installers/angie-generic.ini',
			'administrator/components/com_akeeba/assets/installers/angie-generic.jpa',
			// Media files
			'media/com_akeeba/akeebauipro.js',
			'media/com_akeeba/alice.js',
			'media/com_akeeba/encryption.js',
			// Plugins
			'plugins/system/akeebaupdatecheck.php',
			'plugins/system/akeebaupdatecheck.xml',
			'plugins/system/aklazy.php',
			'plugins/system/aklazy.xml',
			'plugins/system/srp.php',
			'plugins/system/srp.xml',
			// Additional ANGIE installers which are not used in Core
			'administrator/components/com_akeeba/assets/installers/angie-generic.jpa',
			'administrator/components/com_akeeba/assets/installers/angie-generic.ini',
			// Integrity check
			'administrator/components/com_akeeba/fileslist.php',
			'administrator/components/com_akeeba/controllers/checkfile.php',
			// Post-install messages helper
			'administrator/components/com_akeeba/helpers/postinstall.php',
		),
		'folders' => array(
			// Plugins
			'plugins/system/akeebaupdatecheck',
			'plugins/system/aklazy',
			'plugins/system/srp',
			// Modules
			'administrator/modules/mod_akadmin',
			// Pro component features
			'administrator/components/com_akeeba/alice',
			'administrator/components/com_akeeba/platform/joomla25/Config/Pro',
			'administrator/components/com_akeeba/views/alices',
			'administrator/components/com_akeeba/views/discover',
			'administrator/components/com_akeeba/views/eff',
			'administrator/components/com_akeeba/views/extfilter',
			'administrator/components/com_akeeba/views/multidb',
			'administrator/components/com_akeeba/views/regexdbfilter',
			'administrator/components/com_akeeba/views/regexfsfilter',
			'administrator/components/com_akeeba/views/remotefiles',
			'administrator/components/com_akeeba/views/restore',
			'administrator/components/com_akeeba/views/s3import',
			'administrator/components/com_akeeba/views/srprestore',
			'administrator/components/com_akeeba/views/upload',
			'administrator/components/com_akeeba/engine/Dump/Reverse',
			'administrator/components/com_akeeba/engine/Postproc/Connector',
			// Integrity check
			'administrator/components/com_akeeba/views/checkfiles',
		)
	);

	/**
	 * Obsolete files and folders to remove from both paid and free releases. This is used when you refactor code and
	 * some files inevitably become obsolete and need to be removed.
	 *
	 * @var   array
	 */
	protected $removeFilesAllVersions = array(
		'files'   => array(
			'cache/com_akeeba.updates.php',
			'cache/com_akeeba.updates.ini',
			'administrator/cache/com_akeeba.updates.php',
			'administrator/cache/com_akeeba.updates.ini',
			'administrator/components/com_akeeba/controllers/acl.php',
			'administrator/components/com_akeeba/controllers/installer.php',
			'administrator/components/com_akeeba/models/srprestore.php',
			'administrator/components/com_akeeba/models/stw.php',
			'administrator/components/com_akeeba/models/acl.php',
			'administrator/components/com_akeeba/tables/acl.php',
			// Files renamed after using FOF
			'administrator/components/com_akeeba/models/cpanel.php',
			'administrator/components/com_akeeba/models/backup.php',
			'administrator/components/com_akeeba/models/config.php',
			'administrator/components/com_akeeba/models/ftpbrowser.php',
			'administrator/components/com_akeeba/models/log.php',
			'administrator/components/com_akeeba/models/fsfilter.php',
			'administrator/components/com_akeeba/models/dbef.php',
			'administrator/components/com_akeeba/views/profiles/tmpl/default_edit.php',
			'administrator/components/com_akeeba/views/buadmin/tmpl/default_comment.php',
			'administrator/components/com_akeeba/views/fsfilter/tmpl/default_tab.php',
			'administrator/components/com_akeeba/views/extfilter/tmpl/default_components.php',
			'administrator/components/com_akeeba/views/extfilter/tmpl/default_languages.php',
			'administrator/components/com_akeeba/views/extfilter/tmpl/default_modules.php',
			'administrator/components/com_akeeba/views/extfilter/tmpl/default_plugins.php',
			'administrator/components/com_akeeba/views/extfilter/tmpl/default_templates.php',
			'administrator/components/com_akeeba/views/dbef/tmpl/default_tab.php',
			'components/com_akeeba/models/light.php',
			'components/com_akeeba/models/json.php',
			'components/com_akeeba/views/light/view.html.php',
			'components/com_akeeba/views/light/tmpl/default_done.php',
			'components/com_akeeba/views/light/tmpl/default_error.php',
			'components/com_akeeba/views/light/tmpl/default_step.php',
			// Outdated media files
			'media/com_akeeba/js/jquery.js',
			'media/com_akeeba/js/jquery-ui.js',
			'media/com_akeeba/js/akeebajq.js',
			'media/com_akeeba/js/akeebajqui.js',
			'media/com_akeeba/theme/jquery-ui.css',
			'media/com_akeeba/theme/browser.css',
			// Old ABI installer
			'administrator/components/com_akeeba/assets/installers/abi.jpa',
			'administrator/components/com_akeeba/assets/installers/abi.ini',
			// Additional ANGIE installers which are not used in Pro and Core versions
			'administrator/components/com_akeeba/assets/installers/angie-magento.jpa',
			'administrator/components/com_akeeba/assets/installers/angie-magento.ini',
			'administrator/components/com_akeeba/assets/installers/angie-moodle.jpa',
			'administrator/components/com_akeeba/assets/installers/angie-moodle.ini',
			'administrator/components/com_akeeba/assets/installers/angie-phpbb.jpa',
			'administrator/components/com_akeeba/assets/installers/angie-phpbb.ini',
			'administrator/components/com_akeeba/assets/installers/angie-prestashop.jpa',
			'administrator/components/com_akeeba/assets/installers/angie-prestashop.ini',
			'administrator/components/com_akeeba/assets/installers/angie-wordpress.jpa',
			'administrator/components/com_akeeba/assets/installers/angie-wordpress.ini',
			// Old CLI backup scripts, obsolete since 3.5.0, removed in 4.0.0
			'administrator/components/com_akeeba/backup.php',
			'administrator/components/com_akeeba/altbackup.php',

			// Post-installation page
		    'administrator/components/com_akeeba/controllers/postsetup.php',

			// Site Transfer Wizard
		    'administrator/components/com_akeeba/controllers/stw.php',
		    'administrator/components/com_akeeba/models/stws.php',

			// System Restore Points
			'administrator/components/com_akeeba/controllers/srprestore.php',
			'administrator/components/com_akeeba/models/srprestores.php',
			'administrator/components/com_akeeba/models/installer.php',
			'administrator/components/com_akeeba/views/buadmin/tmpl/restorepoint.php',
			'administrator/components/com_akeeba/platform/joomla25/Filter/SRPData.php',
			'administrator/components/com_akeeba/platform/joomla25/Filter/SRPDirectories.php',
			'administrator/components/com_akeeba/platform/joomla25/Filter/SRPFiles.php',
			'administrator/components/com_akeeba/platform/joomla25/Filter/SRPSkipData.php',
			'administrator/components/com_akeeba/platform/joomla25/Filter/SRPSkipFiles.php',
			'administrator/components/com_akeeba/platform/joomla25/Finalization/Srpquotas.php',

			// Extension filters
			'administrator/components/com_akeeba/controllers/extfilter.php',
			'administrator/components/com_akeeba/models/extfilter.php',
			'administrator/components/com_akeeba/models/extfilters.php',
			'administrator/components/com_akeeba/platform/joomla25/Filter/Components.php',
			'administrator/components/com_akeeba/platform/joomla25/Filter/Extensiondirs.php',
			'administrator/components/com_akeeba/platform/joomla25/Filter/Extensionfiles.php',
			'administrator/components/com_akeeba/platform/joomla25/Filter/Languages.php',
			'administrator/components/com_akeeba/platform/joomla25/Filter/Modules.php',
			'administrator/components/com_akeeba/platform/joomla25/Filter/Plugins.php',
			'administrator/components/com_akeeba/platform/joomla25/Filter/Templates.php',

			// Lite mode (because smartphones are the norm since ~2010 or so)
			'components/com_akeeba/controllers/light.php',
			'components/com_akeeba/models/lights.php',

			// Old view INI files
			'administrator/components/com_akeeba/views/proviews.ini',
			'administrator/components/com_akeeba/views/views.ini',

			// Live Help (which had stopped working a long time ago and nobody even noticed)
			'administrator/components/com_akeeba/helpers/includes.php',

			// JSON library, which only made sense in PHP 5.2 and lower (Joomla! 3 won't even run without JSON support)
			'administrator/components/com_akeeba/helpers/jsonlib.php',

			// Old self-heal db support
			'administrator/components/com_akeeba/models/selfheal.php',

			// Obsolete Amazon S3 integration
			'administrator/components/com_akeeba/engine/Postproc/Connector/Amazons3.php',
			'administrator/components/com_akeeba/engine/Postproc/S3.php',
			'administrator/components/com_akeeba/engine/Postproc/s3.ini',

			// Obsolete remains of the legacy Live Update system
			'administrator/components/com_akeeba/assets/xmlslurp/xmlslurp.php',
		),
		'folders' => array(
			// Directories used in version 4.1 and earlier
			'administrator/components/com_akeeba/akeeba',
			'administrator/components/com_akeeba/plugins',

			// Obsolete views
			'administrator/components/com_akeeba/views/installer',
			'administrator/components/com_akeeba/views/acl',
			'administrator/components/com_akeeba/assets/images',

			// Folders renamed after using FOF
			'components/com_akeeba/views/backup',
			'components/com_akeeba/views/json',

			// Outdated media directories
			'media/com_akeeba/theme/images',

			// Post-installation page
			'administrator/components/com_akeeba/views/postsetup',

			// Site Transfer Wizard
			'administrator/components/com_akeeba/views/stw',

			// System Restore Points
			'administrator/components/com_akeeba/assets/srpdefs',
			'administrator/components/com_akeeba/views/srprestore',

			// Extension filters
			'administrator/components/com_akeeba/views/extfilter',

			// We no longer have a front-end views folder
			'components/com_akeeba/views',

			// Obsolete Amazon S3 integration
			'administrator/components/com_akeeba/engine/Postproc/Connector/Amazon',
			'administrator/components/com_akeeba/engine/Postproc/Connector/Amazons3',

			// Obsolete remains of the legacy Live Update system
			'administrator/components/com_akeeba/assets/xmlslurp',

			// Obsolete Comconfig helper class
			'administrator/components/com_akeeba/platform/joomla25/Util',
		)
	);

	/**
	 * A list of scripts to be copied to the "cli" directory of the site
	 *
	 * @var   array
	 */
	protected $cliScriptFiles = array(
		'akeeba-backup.php',
		'akeeba-altbackup.php',
		'akeeba-check-failed.php',
		'akeeba-altcheck-failed.php',
        'akeeba-update.php',
	);

	/**
	 * The minimum PHP version required to install this extension
	 *
	 * @var   string
	 */
	protected $minimumPHPVersion = '5.3.3';

	/**
	 * The minimum Joomla! version required to install this extension
	 *
	 * @var   string
	 */
	protected $minimumJoomlaVersion = '1.6.0';

	/**
	 * Runs on installation
	 *
	 * @param   JInstaller $parent The parent object
	 */
	public function install($parent)
	{
		if (!defined('AKEEBA_THIS_IS_INSTALLATION_FROM_SCRATCH'))
		{
			define('AKEEBA_THIS_IS_INSTALLATION_FROM_SCRATCH', 1);
		}
	}

	/**
	 * Joomla! pre-flight event. This runs before Joomla! installs or updates the component. This is our last chance to
	 * tell Joomla! if it should abort the installation.
	 *
	 * @param   string     $type   Installation type (install, update, discover_install)
	 * @param   JInstaller $parent Parent object
	 *
	 * @return  boolean  True to let the installation proceed, false to halt the installation
	 */
	public function preflight($type, $parent)
	{
		// Check the minimum PHP version. Issue a very stern warning if it's not met.
		if (!empty($this->minimumPHPVersion))
		{
			if (defined('PHP_VERSION'))
			{
				$version = PHP_VERSION;
			}
			elseif (function_exists('phpversion'))
			{
				$version = phpversion();
			}
			else
			{
				$version = '5.0.0'; // all bets are off!
			}

			if (!version_compare($version, $this->minimumPHPVersion, 'ge'))
			{
				$msg = "<h1>Your PHP version is too old</h1>";
				$msg .= "<p>You need PHP $this->minimumPHPVersion or later to install this component. Support for PHP 5.3.3 and earlier versions has been discontinued by our company as we publicly announced in February 2013.</p>";
				$msg .= "<p>You are using PHP $version which is an extremely old version, released more than four years ago. This version contains known functional and security issues. The functional issues do not allow you to run Akeeba Backup and cannot be worked around. The security issues mean that your site <b>can be easily hacked</b> since that these security issues are well known for over four years.</p>";
				$msg .= "<p>You have to ask your host to immediately update your site to PHP $this->minimumPHPVersion or later, ideally the latest available version of PHP 5.4. If your host won't do that you are advised to switch to a better host to ensure the security of your site. If you have to stay with your current host for reasons beyond your control you can use Akeeba Backup 4.0.5 or earlier, available from our downloads page.</p>";

				JLog::add($msg, JLog::WARNING, 'jerror');

				return false;
			}
		}

		$result = parent::preflight($type, $parent);

		// Move the serverkey.php file from /akeeba to /engine to preserve the settings
		if ($result)
		{
			$componentPath = JPATH_ADMINISTRATOR . '/components/com_akeeba';
			$fromFile = $componentPath . '/akeeba/serverkey.php';
			$toFile = $componentPath . '/engine/serverkey.php';

			if (@file_exists($fromFile) && !@file_exists($toFile))
			{
				$toPath = $componentPath . '/engine';

				if (class_exists('JLoader') && method_exists('JLoader', 'import'))
				{
					JLoader::import('joomla.filesystem.folder');
					JLoader::import('joomla.filesystem.file');
				}

				if (@is_dir($componentPath) && !@is_dir($toPath))
				{
					JFolder::create($toPath);
				}

				if (@is_dir($toPath))
				{
					JFile::copy($fromFile, $toFile);
				}
			}
		}

		return $result;
	}

	/**
	 * Runs after install, update or discover_update. In other words, it executes after Joomla! has finished installing
	 * or updating your component. This is the last chance you've got to perform any additional installations, clean-up,
	 * database updates and similar housekeeping functions.
	 *
	 * @param   string     $type   install, update or discover_update
	 * @param   JInstaller $parent Parent object
	 */
	function postflight($type, $parent)
	{
		$this->isPaid = is_dir($parent->getParent()->getPath('source') . '/backend/alice');

        // Let's install common tables
        $model = F0FModel::getTmpInstance('Stats', 'AkeebaModel');

        if(method_exists($model, 'checkAndFixCommonTables'))
        {
            $model->checkAndFixCommonTables();
        }

		parent::postflight($type, $parent);

		$this->uninstallObsoletePostinstallMessages();

		$this->removeFOFUpdateSites();

		// Make sure the two plugins folders exist in Core release and are empty
		if (!$this->isPaid)
		{
			if (!JFolder::exists(JPATH_ADMINISTRATOR . '/components/com_akeeba/plugins'))
			{
				JFolder::create(JPATH_ADMINISTRATOR . '/components/com_akeeba/plugins');
			}

			if (!JFolder::exists(JPATH_ADMINISTRATOR . '/components/com_akeeba/akeeba/plugins'))
			{
				JFolder::create(JPATH_ADMINISTRATOR . '/components/com_akeeba/akeeba/plugins');
			}
		}

		// If this is a new installation tell it to NOT mark the backup profiles as configured.
		if (defined('AKEEBA_THIS_IS_INSTALLATION_FROM_SCRATCH'))
		{
			$db = F0FPlatform::getInstance()->getDbo();
			$query = $db->getQuery(true)
				->select($db->qn('params'))
				->from($db->qn('#__extensions'))
				->where($db->qn('type') . ' = ' . $db->q('component'))
				->where($db->qn('element') . ' = ' . $db->q('com_akeeba'));
			$jsonData = $db->setQuery($query)->loadResult();
			$reg = new JRegistry($jsonData);
			$reg->set('confwiz_upgrade', 1);
			$jsonData = $reg->toString('JSON');
			$query = $db->getQuery()
				->update($db->qn('#__extensions'))
				->set($db->qn('params') . ' = ' . $db->q($jsonData))
				->where($db->qn('type') . ' = ' . $db->q('component'))
				->where($db->qn('element') . ' = ' . $db->q('com_akeeba'));
			$db->setQuery($query)->execute();
		}

		// This is an update of an existing installation
		if (!defined('AKEEBA_THIS_IS_INSTALLATION_FROM_SCRATCH'))
		{
			// Migrate profiles if necessary
			$this->migrateProfiles();
		}
	}

	/**
	 * Renders the post-installation message
	 */
	protected function renderPostInstallation($status, $fofInstallationStatus, $strapperInstallationStatus, $parent)
	{
		$this->warnAboutJSNPowerAdmin();

		if (!defined('AKEEBA_PRO'))
		{
			define('AKEEBA_PRO', '0');
		}

		$videoTutorialURL = 'https://www.akeebabackup.com/videos/1212-akeeba-backup-core.html';

		if (AKEEBA_PRO)
		{
			$videoTutorialURL = 'https://www.akeebabackup.com/videos/1213-akeeba-backup-for-joomla-pro.html';
		}

		?>
		<img src="../media/com_akeeba/icons/logo-48.png" width="48" height="48" alt="Akeeba Backup" align="right"/>

		<h2>Welcome to Akeeba Backup!</h2>

		<div style="margin: 1em; font-size: 14pt; background-color: #fffff9; color: black">
			You can download translation files <a href="http://cdn.akeebabackup.com/language/akeebabackup/index.html">directly
				from our CDN page</a>.
		</div>

		<?php
		parent::renderPostInstallation($status, $fofInstallationStatus, $strapperInstallationStatus, $parent);
		?>

		<fieldset>
			<p>
				We strongly recommend watching our
				<a href="<?php echo $videoTutorialURL ?>">video
				tutorials</a> before using this component.
			</p>

			<p>
				If this is the first time you install Akeeba Backup on your site please run the
				<a href="index.php?option=com_akeeba&view=confwiz">Configuration Wizard</a>. Akeeba Backup will
				configure itself optimally for your site.
			</p>

			<p>
				By installing this component you are implicitly accepting
				<a href="https://www.akeebabackup.com/license.html">its license (GNU GPLv3)</a> and our
				<a href="https://www.akeebabackup.com/privacy-policy.html">Terms of Service</a>,
				including our Support Policy.
			</p>
		</fieldset>
	<?php
        /** @var AkeebaModelStats $model */
        $model  = F0FModel::getTmpInstance('Stats', 'AkeebaModel');

        if(method_exists($model, 'collectStatistics'))
        {
            $iframe = $model->collectStatistics(true);

            if($iframe)
            {
                echo $iframe;
            }
        }
	}

	protected function renderPostUninstallation($status, $parent)
	{
		?>
		<h2>Akeeba Backup Uninstallation Status</h2>
		<?php
		parent::renderPostUninstallation($status, $parent);
	}

	private function uninstallObsoletePostinstallMessages()
	{
		$db = F0FPlatform::getInstance()->getDbo();

		$obsoleteTitleKeys = array(
			// Remove "Upgrade profiles to ANGIE"
			'AKEEBA_POSTSETUP_LBL_ANGIEUPGRADE',
			// Remove "Enable System Restore Points"
			'AKEEBA_POSTSETUP_LBL_SRP',
			'AKEEBA_POSTSETUP_LBL_BACKUPONUPDATE',
			'AKEEBA_POSTSETUP_LBL_CONFWIZ',
			'AKEEBA_POSTSETUP_LBL_ACCEPTLICENSE',
			'AKEEBA_POSTSETUP_LBL_ACCEPTSUPPORT',
			'AKEEBA_POSTSETUP_LBL_ACCEPTBACKUPTEST',
		);

		foreach ($obsoleteTitleKeys as $obsoleteKey)
		{

			// Remove the "Upgrade profiles to ANGIE" post-installation message
			$query = $db->getQuery(true)
			            ->delete($db->qn('#__postinstall_messages'))
			            ->where($db->qn('title_key') . ' = ' . $db->q($obsoleteKey));
			try
			{
				$db->setQuery($query)->execute();
			}
			catch (Exception $e)
			{
				// Do nothing
			}
		}
	}

	/**
	 * The PowerAdmin extension makes menu items disappear. People assume it's our fault. JSN PowerAdmin authors don't
	 * own up to their software's issue. I have no choice but to warn our users about the faulty third party software.
	 */
	private function warnAboutJSNPowerAdmin()
	{
		$db = F0FPlatform::getInstance()->getDbo();
		$query = $db->getQuery(true)
			->select('COUNT(*)')
			->from($db->qn('#__extensions'))
			->where($db->qn('type') . ' = ' . $db->q('component'))
			->where($db->qn('element') . ' = ' . $db->q('com_poweradmin'))
			->where($db->qn('enabled') . ' = ' . $db->q('1'));
		$hasPowerAdmin = $db->setQuery($query)->loadResult();

		if (!$hasPowerAdmin)
		{
			return;
		}

		$query = $db->getQuery(true)
					->select('manifest_cache')
					->from($db->qn('#__extensions'))
					->where($db->qn('type') . ' = ' . $db->q('component'))
					->where($db->qn('element') . ' = ' . $db->q('com_poweradmin'))
					->where($db->qn('enabled') . ' = ' . $db->q('1'));
		$paramsJson = $db->setQuery($query)->loadResult();
		$jsnPAManifest = new JRegistry();
		$jsnPAManifest->loadString($paramsJson, 'JSON');
		$version = $jsnPAManifest->get('version', '0.0.0');

		if (version_compare($version, '2.1.2', 'ge'))
		{
			return;
		}


		echo <<< HTML
<div class="well" style="margin: 2em 0;">
<h1 style="font-size: 32pt; line-height: 120%; color: red; margin-bottom: 1em">WARNING: Menu items for {$this->componentName} might not be displayed on your site.</h1>
<p style="font-size: 18pt; line-height: 150%; margin-bottom: 1.5em">
	We have detected that you are using JSN PowerAdmin on your site. This software ignores Joomla! standards and
	<b>hides</b> the Component menu items to {$this->componentName} in the administrator backend of your site. Unfortunately we
	can't provide support for third party software. Please contact the developers of JSN PowerAdmin for support
	regarding this issue.
</p>
<p style="font-size: 18pt; line-height: 120%; color: green;">
	Tip: You can disable JSN PowerAdmin to see the menu items to Akeeba Backup.
</p>
</div>

HTML;

	}

	/**
	 * Loads the Akeeba Engine if it's not already loaded
	 */
	private function loadAkeebaEngine()
	{
		if (class_exists('\\Akeeba\\Engine\\Platform'))
		{
			return;
		}

		// Load the language files
		$paths	 = array(JPATH_ADMINISTRATOR, JPATH_ROOT);
		$jlang	 = JFactory::getLanguage();
		$jlang->load('com_akeeba', $paths[0], 'en-GB', true);
		$jlang->load('com_akeeba', $paths[1], 'en-GB', true);
		$jlang->load('com_akeeba' . '.override', $paths[0], 'en-GB', true);
		$jlang->load('com_akeeba' . '.override', $paths[1], 'en-GB', true);

		// Load the version file
		if (!defined('AKEEBA_PRO'))
		{
			@include_once JPATH_ADMINISTRATOR . '/components/com_akeeba/version.php';
		}

		if (!defined('AKEEBA_PRO'))
		{
			define('AKEEBA_PRO', '0');
		}

		// Enable Akeeba Engine
		if (!defined('AKEEBAENGINE'))
		{
			define('AKEEBAENGINE', 1);
		}

		// Load the engine
		$factoryPath = JPATH_ADMINISTRATOR . '/components/com_akeeba/engine/Factory.php';
		define('AKEEBAROOT', JPATH_ADMINISTRATOR . '/components/com_akeeba/engine');

		require_once $factoryPath;

		// Assign the correct platform
		\Akeeba\Engine\Platform::addPlatform('joomla25', JPATH_ADMINISTRATOR . '/components/com_akeeba/platform/joomla25');
	}

	/**
	 * Migrates existing backup profiles. The changes currently made are:
	 * – Change post-processing from "s3" (legacy) to "amazons3" (current version)
	 * – Fix profiles with invalid embedded installer settings
	 *
	 * @return  void
	 */
	private function migrateProfiles()
	{
		$this->loadAkeebaEngine();

		// Get a list of backup profiles
		$db = F0FPlatform::getInstance()->getDbo();
		$query = $db->getQuery(true)
					->select($db->qn('id'))
					->from($db->qn('#__ak_profiles'));
		$profiles = $db->setQuery($query)->loadColumn();

		// Normally this should never happen as we're supposed to have at least profile #1
		if (empty($profiles))
		{
			return;
		}

		// Migrate each profile
		foreach ($profiles as $profile)
		{
			// Initialization
			$dirty = false;

			// Load the profile configuration
			\Akeeba\Engine\Platform::getInstance()->load_configuration($profile);
			$config = \Akeeba\Engine\Factory::getConfiguration();

			// -- Migrate obsolete "s3" engine to "amazons3"
			$postProcType = $config->get('akeeba.advanced.postproc_engine', '');

			if ($postProcType == 's3')
			{
				$config->setKeyProtection('akeeba.advanced.postproc_engine', false);
				$config->setKeyProtection('engine.postproc.amazons3.signature', false);
				$config->setKeyProtection('engine.postproc.amazons3.accesskey', false);
				$config->setKeyProtection('engine.postproc.amazons3.secretkey', false);
				$config->setKeyProtection('engine.postproc.amazons3.usessl', false);
				$config->setKeyProtection('engine.postproc.amazons3.bucket', false);
				$config->setKeyProtection('engine.postproc.amazons3.directory', false);
				$config->setKeyProtection('engine.postproc.amazons3.rrs', false);
				$config->setKeyProtection('engine.postproc.amazons3.customendpoint', false);
				$config->setKeyProtection('engine.postproc.amazons3.legacy', false);

				$config->set('akeeba.advanced.postproc_engine', 'amazons3');
				$config->set('engine.postproc.amazons3.signature', 's3');
				$config->set('engine.postproc.amazons3.accesskey', $config->get('engine.postproc.s3.accesskey'));
				$config->set('engine.postproc.amazons3.secretkey', $config->get('engine.postproc.s3.secretkey'));
				$config->set('engine.postproc.amazons3.usessl', $config->get('engine.postproc.s3.usessl'));
				$config->set('engine.postproc.amazons3.bucket', $config->get('engine.postproc.s3.bucket'));
				$config->set('engine.postproc.amazons3.directory', $config->get('engine.postproc.s3.directory'));
				$config->set('engine.postproc.amazons3.rrs', $config->get('engine.postproc.s3.rrs'));
				$config->set('engine.postproc.amazons3.customendpoint', $config->get('engine.postproc.s3.customendpoint'));
				$config->set('engine.postproc.amazons3.legacy', $config->get('engine.postproc.s3.legacy'));

				$dirty = true;
			}

			// Fix profiles with invalid embedded installer settings
			$embeddedInstaller = $config->get('akeeba.advanced.embedded_installer');

			if (empty($embeddedInstaller) || ($embeddedInstaller == 'angie-joomla') || (
					(substr($embeddedInstaller, 0, 5) != 'angie') && ($embeddedInstaller != 'none')
				))
			{
				$config->setKeyProtection('akeeba.advanced.embedded_installer', false);
				$config->set('akeeba.advanced.embedded_installer', 'angie');
				$dirty = true;
			}

			// Save dirty records
			if ($dirty)
			{
				\Akeeba\Engine\Platform::getInstance()->save_configuration($profile);
			}
		}
	}

	/**
	 * Remove FOF 2.x update sites
	 */
	private function removeFOFUpdateSites()
	{
		$db = F0FPlatform::getInstance()->getDbo();
		$query = $db->getQuery(true)
					->delete($db->qn('#__update_sites_extensions'))
					->where($db->qn('location') . ' = ' . $db->q('http://cdn.akeebabackup.com/updates/fof.xml'));
		try
		{
			$db->setQuery($query)->execute();
		}
		catch (\Exception $e)
		{
			// Do nothing on failure
		}

	}
}PK�!]㘉ħ�backup/web.confignu&1i�<?xml version="1.0"?>
<!--
This file was generated automatically by the Akeeba Backup Engine

DO NOT REMOVE THIS FILE

This file makes sure that your backup output directory is not directly accessible from the web if you are using the
Microsoft Internet Information Services (IIS) web server, version 7 or later. This prevents unauthorized access to your
backup archive files and backup log files. Removing this file could have security implications for your site.

As noted above, this only works on IIS 7 or later.
See https://www.iis.net/configreference/system.webserver/security/requestfiltering/fileextensions

You are strongly advised to never delete or modify any of the files automatically created in this folder by the
Akeeba Backup Engine, namely:

* .htaccess
* web.config
* index.html
* index.htm
* index.php

-->
<configuration>
    <system.webServer>
        <security>
            <requestFiltering>
                <fileExtensions allowUnlisted="false" >
                    <clear />
                    <add fileExtension=".html" allowed="true"/>
                </fileExtensions>
            </requestFiltering>
        </security>
    </system.webServer>
</configuration>PK�!]����55backup/.htaccessnu&1i�## This file was generated automatically by the Akeeba Backup Engine
##
## DO NOT REMOVE THIS FILE
##
## This file makes sure that your backup output directory is not directly accessible from the web if you are using
## the Apache, Lighttpd and Litespeed web server. This prevents unauthorized access to your backup archive files and
## backup log files. Removing this file could have security implications for your site.
##
## You are strongly advised to never delete or modify any of the files automatically created in this folder by the
## Akeeba Backup Engine, namely:
##
## * .htaccess
## * web.config
## * index.html
## * index.htm
## * index.php
##
<IfModule !mod_authz_core.c>
Order deny,allow
Deny from all
</IfModule>
<IfModule mod_authz_core.c>
  <RequireAll>
    Require all denied
  </RequireAll>
</IfModule>PK�!]�r���backup/index.htmnu&1i�<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
  <head>
    <title>Access Denied</title>
  </head>
  <body>
	  <h1>Access Denied</h1>
  </body>
</html>PK�!]k��66backup/index.htmlnu&1i�<html><head><title></title></head><body></body></html>PK�!]��h�77backup/akeeba.log.phpnu�[���DEBUG   |20260828 09:08:05| -- Resetting Akeeba Engine factory (backend.id-20260828-090805-509758)
DEBUG   |20260828 09:08:05|Fetching filter data from database
DEBUG   |20260828 09:08:05|Loading filters
DEBUG   |20260828 09:08:05|-- Loading filter Regexskipdirs
DEBUG   |20260828 09:08:05|-- Loading filter Skipdirs
DEBUG   |20260828 09:08:05|-- Loading filter Regexdirectories
DEBUG   |20260828 09:08:05|-- Loading filter Tables
DEBUG   |20260828 09:08:05|-- Loading filter Regexskipfiles
DEBUG   |20260828 09:08:05|-- Loading filter Regextables
DEBUG   |20260828 09:08:05|-- Loading filter Regextabledata
DEBUG   |20260828 09:08:05|-- Loading filter Multidb
DEBUG   |20260828 09:08:05|-- Loading filter Skipfiles
DEBUG   |20260828 09:08:05|-- Loading filter Tabledata
DEBUG   |20260828 09:08:05|-- Loading filter Directories
DEBUG   |20260828 09:08:05|-- Loading filter Regexfiles
DEBUG   |20260828 09:08:05|-- Loading filter Extradirs
DEBUG   |20260828 09:08:05|-- Loading filter Incremental
DEBUG   |20260828 09:08:05|-- Loading filter Tablesalwaysskipped
DEBUG   |20260828 09:08:05|-- Loading filter Files
DEBUG   |20260828 09:08:05|-- Loading filter Joomlaskipfiles
DEBUG   |20260828 09:08:05|-- Loading filter Excludetabledata
DEBUG   |20260828 09:08:05|-- Loading filter Joomlaskipdirs
DEBUG   |20260828 09:08:05|-- Loading filter Cvsfolders
DEBUG   |20260828 09:08:05|-- Loading filter Siteroot
DEBUG   |20260828 09:08:05|-- Loading filter Systemcachefiles
DEBUG   |20260828 09:08:05|-- Loading filter Libraries
DEBUG   |20260828 09:08:05|-- Loading filter Sitedb
DEBUG   |20260828 09:08:05|-- Loading filter Excludefiles
DEBUG   |20260828 09:08:05|-- Loading filter Excludefolders
DEBUG   |20260828 09:08:05|Loading optional filters
DEBUG   |20260828 09:08:05|-- Loading optional filter Stack\StackErrorlogs
DEBUG   |20260828 09:08:05|-- Loading optional filter Stack\StackHoststats
DEBUG   |20260828 09:08:05|-- Loading optional filter Stack\StackFinder
DEBUG   |20260828 09:08:05|-- Loading optional filter Stack\StackActionlogs
DEBUG   |20260828 09:56:34| -- Resetting Akeeba Engine factory (backend.id-20260828-095634-74799)
DEBUG   |20260828 09:56:34|Fetching filter data from database
DEBUG   |20260828 09:56:34|Loading filters
DEBUG   |20260828 09:56:34|-- Loading filter Regexskipdirs
DEBUG   |20260828 09:56:34|-- Loading filter Skipdirs
DEBUG   |20260828 09:56:34|-- Loading filter Regexdirectories
DEBUG   |20260828 09:56:34|-- Loading filter Tables
DEBUG   |20260828 09:56:34|-- Loading filter Regexskipfiles
DEBUG   |20260828 09:56:34|-- Loading filter Regextables
DEBUG   |20260828 09:56:34|-- Loading filter Regextabledata
DEBUG   |20260828 09:56:34|-- Loading filter Multidb
DEBUG   |20260828 09:56:34|-- Loading filter Skipfiles
DEBUG   |20260828 09:56:34|-- Loading filter Tabledata
DEBUG   |20260828 09:56:34|-- Loading filter Directories
DEBUG   |20260828 09:56:34|-- Loading filter Regexfiles
DEBUG   |20260828 09:56:34|-- Loading filter Extradirs
DEBUG   |20260828 09:56:34|-- Loading filter Incremental
DEBUG   |20260828 09:56:34|-- Loading filter Tablesalwaysskipped
DEBUG   |20260828 09:56:34|-- Loading filter Files
DEBUG   |20260828 09:56:34|-- Loading filter Joomlaskipfiles
DEBUG   |20260828 09:56:34|-- Loading filter Excludetabledata
DEBUG   |20260828 09:56:34|-- Loading filter Joomlaskipdirs
DEBUG   |20260828 09:56:34|-- Loading filter Cvsfolders
DEBUG   |20260828 09:56:34|-- Loading filter Siteroot
DEBUG   |20260828 09:56:34|-- Loading filter Systemcachefiles
DEBUG   |20260828 09:56:34|-- Loading filter Libraries
DEBUG   |20260828 09:56:34|-- Loading filter Sitedb
DEBUG   |20260828 09:56:34|-- Loading filter Excludefiles
DEBUG   |20260828 09:56:34|-- Loading filter Excludefolders
DEBUG   |20260828 09:56:34|Loading optional filters
DEBUG   |20260828 09:56:34|-- Loading optional filter Stack\StackErrorlogs
DEBUG   |20260828 09:56:34|-- Loading optional filter Stack\StackHoststats
DEBUG   |20260828 09:56:34|-- Loading optional filter Stack\StackFinder
DEBUG   |20260828 09:56:34|-- Loading optional filter Stack\StackActionlogs
PK�!]��ѡ��Helper/.htaccessnu&1i�<IfModule !mod_authz_core.c>
Order deny,allow
Deny from all
</IfModule>
<IfModule mod_authz_core.c>
  <RequireAll>
    Require all denied
  </RequireAll>
</IfModule>
PK�!]�ْjVVHelper/Utils.phpnu&1i�<?php
/**
 * @package   akeebabackup
 * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

namespace Akeeba\Backup\Admin\Helper;

// Protect from unauthorized access
defined('_JEXEC') || die();

use Joomla\CMS\Uri\Uri;
use Joomla\Filter\InputFilter;

class Utils
{
	/**
	 * Returns the relative path of directory $to to root path $from
	 *
	 * @param   string  $from  Root directory
	 * @param   string  $to    The directory whose path we want to find relative to $from
	 *
	 * @return  string  The relative path
	 */
	public static function getRelativePath($from, $to)
	{
		// some compatibility fixes for Windows paths
		$from = is_dir($from) ? rtrim($from, '\/') . '/' : $from;
		$to   = is_dir($to) ? rtrim($to, '\/') . '/' : $to;
		$from = str_replace('\\', '/', $from);
		$to   = str_replace('\\', '/', $to);

		$from    = explode('/', $from);
		$to      = explode('/', $to);
		$relPath = $to;

		foreach ($from as $depth => $dir)
		{
			// find first non-matching dir
			if ($dir === $to[$depth])
			{
				// ignore this directory
				array_shift($relPath);

				continue;
			}

			// Get number of remaining dirs to $from
			$remaining = count($from) - $depth;

			if ($remaining > 1)
			{
				// add traversals up to first matching dir
				$padLength = (count($relPath) + $remaining - 1) * -1;
				$relPath   = array_pad($relPath, $padLength, '..');

				break;
			}

			$relPath[0] = './' . $relPath[0];
		}

		return implode('/', $relPath);
	}

	/**
	 * Escapes a string for use with Javascript
	 *
	 * @param   string  $string  The string to escape
	 * @param   string  $extras  The characters to escape
	 *
	 * @return  string
	 */
	static function escapeJS($string, $extras = '')
	{
		// Make sure we escape single quotes, slashes and brackets
		if (empty($extras))
		{
			$extras = "'\\[]";
		}

		return addcslashes($string, $extras);
	}

	/**
	 * Safely decode a return URL, used in the Backup view.
	 *
	 * Return URLs can have two sources:
	 * - The Backup on Update plugin. In this case the URL is base sixty four encoded and we need to decode it first.
	 * - A custom backend menu item. In this case the URL is a simple string which does not need decoding.
	 *
	 * Further to that, we have to make a few security checks:
	 * - The URL must be internal, i.e. starts with our site's base URL or index.php (this check is executed by Joomla)
	 * - It must not contain single quotes, double quotes, lower than or greater than signs (could be used to execute
	 *   arbitrary JavaScript).
	 *
	 * If any of these violations is detected we return an empty string.
	 *
	 * @param   ?string  $returnUrl
	 *
	 * @return  string
	 */
	static function safeDecodeReturnUrl($returnUrl)
	{
		// Nulls and non-strings are not allowed
		if (is_null($returnUrl) || !is_string($returnUrl))
		{
			return '';
		}

		// Make sure it's not an empty string
		$returnUrl = trim($returnUrl);

		if (empty($returnUrl))
		{
			return '';
		}

		// Decode a base sixty four encoded string.
		$filter  = new InputFilter();
		$encoded = $filter->clean($returnUrl, 'base64');

		if (($returnUrl == $encoded) && (strpos($returnUrl, 'index.php') === false))
		{
			$possibleReturnUrl = base64_decode($returnUrl);

			if ($possibleReturnUrl !== false)
			{
				$returnUrl = $possibleReturnUrl;
			}
		}

		// Check if it's an internal URL
		if (!Uri::isInternal($returnUrl))
		{
			return '';
		}

		$disallowedCharacters = ['"', "'", '>', '<'];

		foreach ($disallowedCharacters as $check)
		{
			if (strpos($returnUrl, $check) !== false)
			{
				return '';
			}
		}

		return $returnUrl;
	}
}
PK�!]޺�Helper/Upgrade.phpnu&1i�<?php
/**
 * @package   akeebabackup
 * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

namespace Akeeba\Backup\Admin\Helper;

use FOF40\Container\Container;

class Upgrade
{
	public static function getAkeebaBackup8ExtensionId(): int
	{
		return self::findExtensionId('pkg_akeeba', 'package')
			?: self::findExtensionId('com_akeeba', 'component');
	}

	/**
	 * Gets the ID of an extension
	 *
	 * @param   string  $element  Extension element, e.g. com_foo, mod_foo, lib_foo, pkg_foo or foo (CAUTION: plugin,
	 *                            file!)
	 * @param   string  $type     Extension type: component, module, library, package, plugin or file
	 *
	 * @return  int  Extension ID or 0 on failure
	 */
	private static function findExtensionId($element, $type = 'package')
	{
		$db    = Container::getInstance('com_akeeba')->db;
		$query = $db->getQuery(true)
			->select($db->qn('extension_id'))
			->from($db->qn('#__extensions'))
			->where($db->qn('element') . ' = ' . $db->q($element))
			->where($db->qn('type') . ' = ' . $db->q($type));

		try
		{
			$id = $db->setQuery($query, 0, 1)->loadResult();
		}
		catch (\Exception $e)
		{
			$id = 0;
		}

		return empty($id) ? 0 : (int) $id;
	}

}PK�!]|��NHelper/web.confignu&1i�<?xml version="1.0"?>
<!--
    This only works on IIS 7 or later. See https://www.iis.net/configreference/system.webserver/security/requestfiltering/fileextensions
-->
<configuration>
    <system.webServer>
        <security>
            <requestFiltering>
                <fileExtensions allowUnlisted="false" >
                    <clear />
                    <add fileExtension=".html" allowed="true"/>
                </fileExtensions>
            </requestFiltering>
        </security>
    </system.webServer>
</configuration>PK�!]qfd��Helper/SecretWord.phpnu&1i�<?php
/**
 * @package   akeebabackup
 * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

namespace Akeeba\Backup\Admin\Helper;

// Protect from unauthorized access
defined('_JEXEC') || die();

use Akeeba\Engine\Factory;
use FOF40\Container\Container;
use FOF40\Params\Params;

/**
 * A helper to handle potentially encrypted Secret Word settings
 *
 * @since       5.5.2
 */
abstract class SecretWord
{
	/**
	 * Enforce (reversible) encryption for the component setting $settingsKey
	 *
	 * @param   Params  $params       The component's parameters object
	 * @param   string  $settingsKey  The key for the setting containing the secret word
	 *
	 * @return  void
	 *
	 * @since   5.5.2
	 */
	public static function enforceEncryption(Params $params, $settingsKey)
	{
		// If encryption is not enabled in the Engine we can't encrypt the Secret Word
		if ($params->get('useencryption', -1) == 0)
		{
			return;
		}

		// If encryption is not supported on this server we can't encrypt the Secret Word
		if (!Factory::getSecureSettings()->supportsEncryption())
		{
			return;
		}

		// Get the raw version of frontend_secret_word and check if it has a valid encryption signature
		$raw             = $params->get($settingsKey, '');
		$signature       = substr($raw, 0, 12);
		$validSignatures = array('###AES128###', '###CTR128###');

		// If the setting is already encrypted I have nothing to do here
		if (in_array($signature, $validSignatures))
		{
			return;
		}

		// The setting was NOT encrypted. I need to encrypt it.
		$secureSettings = Factory::getSecureSettings();
		$encrypted      = $secureSettings->encryptSettings($raw);

		// Finally, I need to save it back to the database
		$params->set($settingsKey, $encrypted);
		$params->save();
	}

	/**
	 * Forcibly store the Secret Word settings $settingsKey unencrypted in the database. This is meant to be called when
	 * the user disables settings encryption. Since the encryption key will be deleted we need to decrypt the Secret
	 * Word at the same time as the Engine settings. Otherwise we will never be able to access it again.
	 *
	 * @param   Params       $params         The component parameters object
	 * @param   string       $settingsKey    The key of the Secret Word parameter
	 * @param   string|null  $encryptionKey  (Optional) The AES key with which to decrypt the parameter
	 *
	 * @return  void
	 *
	 * @since   5.5.2
	 */
	public static function enforceDecrypted(Params $params, $settingsKey, $encryptionKey = null)
	{
		// Get the raw version of frontend_secret_word and check if it has a valid encryption signature
		$raw             = $params->get($settingsKey, '');
		$signature       = substr($raw, 0, 12);
		$validSignatures = array('###AES128###', '###CTR128###');

		// If the setting is not already encrypted I have nothing to decrypt
		if (!in_array($signature, $validSignatures))
		{
			return;
		}

		// The setting was encrypted. I need to decrypt it.
		$secureSettings = Factory::getSecureSettings();
		$encrypted      = $secureSettings->decryptSettings($raw, $encryptionKey);

		// Finally, I need to save it back to the database
		$params->set($settingsKey, $encrypted);
		$params->save();
	}
}
PK�!]#��__Helper/Status.phpnu&1i�<?php
/**
 * @package   akeebabackup
 * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

namespace Akeeba\Backup\Admin\Helper;

// Protect from unauthorized access
defined('_JEXEC') || die();

use Akeeba\Engine\Factory;
use Akeeba\Engine\Platform;
use FOF40\Container\Container;
use FOF40\Date\Date;
use Joomla\CMS\Language\Text;

/**
 * Status helper. Used by the Control Panel and the backup page to report detected warnings which may impact your backup
 * experience.
 */
class Status
{
	/**
	 * Are we ready to take a new backup?
	 *
	 * @var  bool
	 */
	public $status = false;

	/**
	 * Is the output directory writable?
	 *
	 * @var  bool
	 */
	public $outputWritable = false;

	/**
	 * Is the temporary directory writable?
	 *
	 * @var  bool
	 */
	public $tempWritable = false;

	/**
	 * The detected warnings
	 *
	 * @var  array
	 */
	protected $warnings = [];

	/**
	 * Public constructor. Automatically initializes the object with the status and warnings.
	 *
	 * @return  self
	 */
	public function __construct()
	{
		$this->status   = Factory::getConfigurationChecks()->getShortStatus();
		$this->warnings = Factory::getConfigurationChecks()->getDetailedStatus();
	}

	/**
	 * Get a Singleton instance
	 *
	 * @return  self
	 */
	public static function &getInstance()
	{
		static $instance = null;

		if (empty($instance))
		{
			$instance = new self();
		}

		return $instance;
	}

	/**
	 * Returns the HTML for the backup status cell
	 *
	 * @return  string  HTML
	 */
	public function getStatusCell()
	{
		$status = Factory::getConfigurationChecks()->getShortStatus();
		$quirks = Factory::getConfigurationChecks()->getDetailedStatus();

		if ($status && empty($quirks))
		{
			$html = '<div class="akeeba-block--success"><p>' . Text::_('COM_AKEEBA_CPANEL_LBL_STATUS_OK') . '</p></div>';
		}
		elseif ($status && !empty($quirks))
		{
			$html = '<div class="akeeba-block--warning"><p>' . Text::_('COM_AKEEBA_CPANEL_LBL_STATUS_WARNING') . '</p></div>';
		}
		else
		{
			$html = '<div class="akeeba-block--failure"><p>' . Text::_('COM_AKEEBA_CPANEL_LBL_STATUS_ERROR') . '</p></div>';
		}

		return $html;
	}

	/**
	 * Returns HTML for the warnings (status details)
	 *
	 * @param   bool  $onlyErrors  Should I only return errors? If false (default) errors AND warnings are returned.
	 *
	 * @return  string  HTML
	 */
	public function getQuirksCell($onlyErrors = false)
	{
		$html   = '<p>' . Text::_('COM_AKEEBA_CPANEL_WARNING_QNONE') . '</p>';
		$quirks = Factory::getConfigurationChecks()->getDetailedStatus();

		if (!empty($quirks))
		{
			$html = "<ul>\n";

			foreach ($quirks as $quirk)
			{
				$html .= $this->renderWarnings($quirk, $onlyErrors);
			}

			$html .= "</ul>\n";
		}

		return $html;
	}

	/**
	 * Returns a boolean value, indicating if warnings have been detected.
	 *
	 * @return  bool  True if there is at least one detected warnings
	 */
	public function hasQuirks()
	{
		$quirks = Factory::getConfigurationChecks()->getDetailedStatus();

		return !empty($quirks);
	}

	/**
	 * Returns the details of the latest backup as HTML
	 *
	 * @return  string  HTML
	 */
	public function getLatestBackupDetails()
	{
		$db    = Container::getInstance('com_akeeba')->db;
		$query = $db->getQuery(true)
			->select('MAX(' . $db->qn('id') . ')')
			->from($db->qn('#__ak_stats'));
		$db->setQuery($query);
		$id = $db->loadResult();

		$backup_types = Factory::getEngineParamsProvider()->loadScripting();

		if (empty($id))
		{
			return '<p class="label">' . Text::_('COM_AKEEBA_BACKUP_STATUS_NONE') . '</p>';
		}

		$record = Platform::getInstance()->get_statistics($id);

		switch ($record['status'])
		{
			case 'run':
				$status      = Text::_('COM_AKEEBA_BUADMIN_LABEL_STATUS_PENDING');
				$statusClass = "akeeba-label--warning";
				break;

			case 'fail':
				$status      = Text::_('COM_AKEEBA_BUADMIN_LABEL_STATUS_FAIL');
				$statusClass = "akeeba-label--failure";
				break;

			case 'complete':
				$status      = Text::_('COM_AKEEBA_BUADMIN_LABEL_STATUS_OK');
				$statusClass = "akeeba-label--success";
				break;

			default:
				$status      = '';
				$statusClass = '';
		}

		switch ($record['origin'])
		{
			case 'frontend':
				$origin = Text::_('COM_AKEEBA_BUADMIN_LABEL_ORIGIN_FRONTEND');
				break;

			case 'backend':
				$origin = Text::_('COM_AKEEBA_BUADMIN_LABEL_ORIGIN_BACKEND');
				break;

			case 'cli':
				$origin = Text::_('COM_AKEEBA_BUADMIN_LABEL_ORIGIN_CLI');
				break;

			default:
				$origin = '&ndash;';
				break;
		}

		$type = '';

		if (array_key_exists($record['type'], $backup_types['scripts']))
		{
			$type = Platform::getInstance()->translate($backup_types['scripts'][$record['type']]['text']);
		}

		$container = Container::getInstance('com_akeeba');
		$startTime = new Date($record['backupstart'], 'UTC');
		$tz        = new \DateTimeZone($container->platform->getUser()->getParam('timezone', $container->platform->getConfig()->get('offset', 'UTC')));
		$startTime->setTimezone($tz);

		$html = '<table class="akeeba-table--striped">';
		$html .= '<tr><td>' . Text::_('COM_AKEEBA_BUADMIN_LABEL_START') . '</td><td>' . $startTime->format(Text::_('DATE_FORMAT_LC2'), true) . '</td></tr>';
		$html .= '<tr><td>' . Text::_('COM_AKEEBA_BUADMIN_LABEL_DESCRIPTION') . '</td><td>' . $record['description'] . '</td></tr>';
		$html .= '<tr><td>' . Text::_('COM_AKEEBA_BUADMIN_LABEL_STATUS') . '</td><td><span class="label ' . $statusClass . '">' . $status . '</span></td></tr>';
		$html .= '<tr><td>' . Text::_('COM_AKEEBA_BUADMIN_LABEL_ORIGIN') . '</td><td>' . $origin . '</td></tr>';
		$html .= '<tr><td>' . Text::_('COM_AKEEBA_BUADMIN_LABEL_TYPE') . '</td><td>' . $type . '</td></tr>';
		$html .= '</table>';

		return $html;
	}

	/**
	 * Gets the HTML for a single line of the warnings area.
	 *
	 * @param   array  $quirk       A quirk definition array
	 * @param   bool   $onlyErrors  Should I only return errors? If false (default) errors AND warnings are returned.
	 *
	 * @return  string  HTML
	 */
	private function renderWarnings($quirk, $onlyErrors = false)
	{
		if ($onlyErrors && ($quirk['severity'] != 'critical'))
		{
			return '';
		}

		$quirk['severity'] = $quirk['severity'] == 'critical' ? 'high' : $quirk['severity'];

		if ($quirk['code'] == 400)
		{
			return sprintf("<li><a class=\"severity-%s\" href=\"%s\" target=\"_blank\">%s</a></li>\n", $quirk['severity'], 'https://www.akeeba.com/documentation/akeeba-backup-joomla/migrating-from-old-akeeba-backup.html', $quirk['description']);
		}

		return '<li><a class="severity-' . $quirk['severity'] .
			'" href="' . $quirk['help_url'] . '" target="_blank">' . $quirk['description'] . '</a>' . "</li>\n";

	}

}
PK�!]f����-tmpl/ControlPanel/backup8_uninstall.blade.phpnu&1i�<?php
/**
 * @package   akeebabackup
 * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

/** @var $this \Akeeba\Backup\Admin\View\ControlPanel\Html */

// Protect from unauthorized access
defined('_JEXEC') || die();

$eid = \Akeeba\Backup\Admin\Helper\Upgrade::getAkeebaBackup8ExtensionId();
$url = sprintf('index.php?option=com_installer&view=manage&filter[search]=id%%3A%d&filter[status]=&filter[client_id]=&filter[type]=&filter[folder]=&filter[core]=', $eid)

?>
<h1>🚨 Please complete your migration to Akeeba Backup 9 🚨</h1>
<p style="font-size: 1.5rem; margin: 1rem 0 0">
	Please click on Components, Akeeba Backup for Joomla!&trade;, Control Panel to open <a href="index.php?option=com_akeebabackup">Akeeba Backup 9</a>'s interface.
</p>
<p style="font-size: 1.5rem; margin: 1rem 0 0">
	Follow the instructions shown in Akeeba Backup 9 to migrate your settings and backups from Akeeba Backup 8 to Akeeba Backup 9.
</p>
<p style="font-size: 1.5rem; margin: 1rem 0 0">
	After you are done migrating please <a href="<?= $url ?>">uninstall Akeeba Backup 8</a>.
</p>
PK�!]�;z6��#tmpl/ControlPanel/profile.blade.phpnu&1i�<?php
/**
 * @package   akeebabackup
 * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

/** @var $this \Akeeba\Backup\Admin\View\ControlPanel\Html */

// Protect from unauthorized access
defined('_JEXEC') || die();

/**
 * Call this template with:
 * [
 * 	'returnURL' => 'index.php?......'
 * ]
 * to set up a custom return URL
 */
?>
@jhtml('formbehavior.chosen')

<div class="akeeba-panel">
	<form action="index.php" method="post" name="switchActiveProfileForm" id="switchActiveProfileForm" class="akeeba-form--inline">
		<input type="hidden" name="option" value="com_akeeba" />
		<input type="hidden" name="view" value="ControlPanel" />
		<input type="hidden" name="task" value="SwitchProfile" />
		@if(isset($returnURL))
		<input type="hidden" name="returnurl" value="{{ $returnURL }}" />
		@endif
		<input type="hidden" name="@token(true)" value="1" />

		<div class="akeeba-form-group">
			<label>
				@lang('COM_AKEEBA_CPANEL_PROFILE_TITLE'): #{{ $this->profileId }}
			</label>

			{{-- Joomla 3.x: Chosen does not work with attached event handlers, only with inline event scripts (e.g. onchange) --}}
			@jhtml('select.genericlist', $this->profileList, 'profileid', ['list.select' => $this->profileId, 'id' => 'comAkeebaControlPanelProfileSwitch', 'list.attr' => ['class' => 'advancedSelect', 'onchange' => 'document.forms.switchActiveProfileForm.submit();']])
		</div>

		<div class="akeeba-form-group--actions">
			<button class="akeeba-btn akeeba-hidden-phone" type="submit">
				<span class="akion-forward"></span>
				@lang('COM_AKEEBA_CPANEL_PROFILE_BUTTON')
			</button>
		</div>
	</form>
</div>
PK�!]����ootmpl/ControlPanel/default.xmlnu&1i�<?xml version="1.0" encoding="utf-8"?>
<!--~
  ~ @package   akeebabackup
  ~ @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
  ~ @license   GNU General Public License version 3, or later
  -->
<metadata>
	<layout title="COM_AKEEBA_VIEW_CPANEL_TITLE">
		<message>
			<![CDATA[COM_AKEEBA_VIEW_CPANEL_DESC]]>
		</message>
	</layout>
</metadata>
PK�!]�$>$+tmpl/ControlPanel/backup9_install.blade.phpnu&1i�<?php
/**
 * @package   akeebabackup
 * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

/** @var $this \Akeeba\Backup\Admin\View\ControlPanel\Html */

// Protect from unauthorized access
defined('_JEXEC') || die();

if (version_compare(JVERSION, '4.1.0', 'ge'))
{
	$css = <<< CSS
#akeebaBackup8Wrapper {
    display: none;
}

CSS;

	$js = <<< JS
function akeebaBackup8RegisterWrapperReveal(e) {
    var elDiv = document.getElementById('akeebaBackup8Wrapper');
    var elUpgradeWrapper = document.getElementById('akeebaBackup8UpgradeWrapper');
    var elToggleWrapper = document.getElementById('akeebaBackup8WrapperToggleWrapper');
    var elUnsafeWrapper = document.getElementById('akeebaBackup8UnsafeWrapper');
    if (elDiv) {
        elDiv.style.display = 'block';
    }
    if (elUnsafeWrapper) {
        elUnsafeWrapper.style.display = 'block';
    }
    if (elUpgradeWrapper) {
        elUpgradeWrapper.style.display = 'none';
    }
    if (elToggleWrapper) {
        elToggleWrapper.style.display = 'none';
    }
}

document.addEventListener("DOMContentLoaded", function () {
    var elLink = document.getElementById('akeebaBackup8WrapperToggle');
    if (!elLink) return;
    elLink.addEventListener('click', akeebaBackup8RegisterWrapperReveal);
}, false);

var akeebaBackup8RegisterWrapperRevealTimer = setInterval(function () {
            if(document.readyState !== "complete") return;
            clearInterval(akeebaBackup8RegisterWrapperRevealTimer);
         }, 300);

JS;


	$this->addCssInline($css);
    $this->addJavascriptInline($js);
}

?>

<div class="akeeba-block--warning--large" id="akeebaBackup8UpgradeWrapper">
    <h1>🚨 🚨 🚨 Please upgrade to Akeeeba Backup 9 🚨 🚨 🚨</h1>
    @if (version_compare(JVERSION, '4.1.0', 'ge'))
        <p style="font-size: 1.5rem; margin: 1rem 0 0">
            You are currently using Akeeba Backup 8. This version is only meant to allow you to upgrade from Joomla 3 to Joomla 4 without losing your backup archives and settings. <strong>It is not supported for taking on restoring backups with Joomla {{ JVERSION }}</strong>.
        </p>
    @else
        <p style="font-size: 1.5rem; margin: 1rem 0 0">
            You are currently using Akeeba Backup 8. This version was only made minimally compatible with Joomla 4 to allow you to upgrade from Joomla 3 to Joomla 4 without losing your backup archives and settings. We will not provide support for Akeeba Backup 8 running on Joomla 4.0 and later.
        </p>
    @endif
    <p style="font-size: 1.5rem; margin: 1rem 0 0">
        You need to <a href="https://www.akeeba.com/download.html" target="_blank">download</a> and install Akeeba Backup 9, our Joomla 4 native version of Akeeba Backup. It is fully supported for use on Joomla 4.
    </p>
    <p style="font-size: 1.5rem; margin: 1rem 0 0">
        After installing Akeeba Backup 9 please click on Components, Akeeba Backup <small>for Joomla!&trade;</small>, Control Panel from Joomla's sidebar and follow the instructions on your screen to migrate your settings and your backups from Akeeba Backup 8.
    </p>
    <p>
        <a href="https://www.akeeba.com/download.html" class="akeeba-btn--green--big--block" style="font-size: 1.5rem; margin: 2rem 0">
            <span class="akion akion-ios-download" aria-hidden="true"></span>
            Download Akeeba Backup 9 now
        </a>
    </p>
</div>

@if (version_compare(JVERSION, '4.1.0', 'ge'))
<p class="small" id="akeebaBackup8WrapperToggleWrapper">
    <a href="#" id="akeebaBackup8WrapperToggle">
        I would like to use Akeeba Backup 8 <em>at my own risk</em>
    </a>
    <br/>
    <span style="font-size: smaller">
    By clicking this link you agree that you are doing something we explicitly told you NOT to do, you are fully responsible for your actions and their consequences, the interface or functionality might be broken, you might lose data or damage your site, and that you are not eligible for any support whatsoever.
    </span>
</p>

<div class="akeeba-block--failure--large" id="akeebaBackup8UnsafeWrapper" style="display: none">
    <h3>You are using Akeeba Backup in an UNSUPPORTED environment AT YOUR OWN RISK</h3>
    <p>
        Akeeba Backup 8 is NOT supported for use on Joomla! <?= JVERSION ?>.
    </p>
    <p>
        You have chosen to use it anyway, AT YOUR OWN RISK. By doing this you understand that:
    </p>
    <ul>
        <li>you are doing something we explicitly told you <u>NOT</u> to do.</li>
        <li>you are fully responsible for your actions <strong>and</strong> their consequences.</li>
        <li>the interface or the functionality of the component may be not work fully, correctly or at all.</li>
        <li>you might lose data or otherwise damage your site.</li>
        <li>you are not eligible for <em>ANY SUPPORT WHATSOEVER</em>.</li>
    </ul>
    <p>
        We <strong>VERY STRONGLY</strong> advise you to upgrade to the latest version of Akeeba Backup 9 or later.
    </p>
    <p>
        Consider yourself warned.
    </p>
</div>
@endif
PK�!]��))'tmpl/ControlPanel/icons_basic.blade.phpnu&1i�<?php
/**
 * @package   akeebabackup
 * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

/** @var $this \Akeeba\Backup\Admin\View\ControlPanel\Html */

// Protect from unauthorized access
defined('_JEXEC') || die();

?>
<section class="akeeba-panel--info">
    <header class="akeeba-block-header">
        <h3>@lang('COM_AKEEBA_CPANEL_HEADER_BASICOPS')</h3>
    </header>

    <div class="akeeba-grid">
	    @if($this->permissions['backup'])
            <a class="akeeba-action--green"
               href="index.php?option=com_akeeba&view=Backup">
                <span class="akion-play"></span>
	            @lang('COM_AKEEBA_BACKUP')
            </a>
	    @endif

	    @if($this->permissions['download'] && AKEEBA_PRO)
            <a class="akeeba-action--green"
                href="index.php?option=com_akeeba&view=Transfer">
                <span class="akion-android-open"></span>
	            @lang('COM_AKEEBA_TRANSFER')
            </a>
	    @endif

        <a class="akeeba-action--teal"
            href="index.php?option=com_akeeba&view=Manage">
            <span class="akion-ios-list"></span>
	        @lang('COM_AKEEBA_BUADMIN')
        </a>

	    @if($this->permissions['configure'])
            <a class="akeeba-action--teal"
                href="index.php?option=com_akeeba&view=Configuration">
                <span class="akion-ios-gear"></span>
	            @lang('COM_AKEEBA_CONFIG')
            </a>
	    @endif

	    @if($this->permissions['configure'])
            <a class="akeeba-action--teal"
                href="index.php?option=com_akeeba&view=Profiles">
                <span class="akion-person-stalker"></span>
	            @lang('COM_AKEEBA_PROFILES')
            </a>
	    @endif
    </div>
</section>
PK�!]�{a�=	=	*tmpl/ControlPanel/sidebar_status.blade.phpnu&1i�<?php
/**
 * @package   akeebabackup
 * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

/** @var $this \Akeeba\Backup\Admin\View\ControlPanel\Html */

// Protect from unauthorized access
defined('_JEXEC') || die();

?>
<div class="akeeba-panel">
    <header class="akeeba-block-header">
        <h3>@lang('COM_AKEEBA_CPANEL_LABEL_STATUSSUMMARY')</h3>
    </header>

    <div>
        {{-- Backup status summary --}}
        {{ $this->statusCell }}

        {{-- Warnings --}}
        @if($this->countWarnings)
            <div>
                {{ $this->detailsCell }}
            </div>
            <hr />
        @endif

        {{-- Version --}}
        <p class="ak_version">
            @lang('COM_AKEEBA') {{ AKEEBA_PRO ? 'Professional ' : 'Core'; }} {{ AKEEBA_VERSION }} ({{ AKEEBA_DATE }})
        </p>

        {{-- Changelog --}}
        <a href="#" id="btnchangelog" class="akeeba-btn--primary">CHANGELOG</a>

        <div id="akeeba-changelog" tabindex="-1" role="dialog" aria-hidden="true" style="display:none;">
            <div class="akeeba-renderer-fef">
                <div class="akeeba-panel--info">
                    <header class="akeeba-block-header">
                        <h3>
                            @lang('CHANGELOG')
                        </h3>
                    </header>
                    <div id="DialogBody">
                        {{ $this->formattedChangelog }}
                    </div>
                </div>
            </div>
        </div>

        {{-- Donation CTA --}}
        @if( ! (AKEEBA_PRO))
            <a
                    href="https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=KDVQPB4EREBPY&source=url"
                    class="akeeba-btn-green">
                Donate via PayPal
            </a>
        @endif

        {{-- Pro upsell --}}
        @if(!AKEEBA_PRO && (time() - $this->lastUpsellDismiss < 1296000))
            <p style="margin: 0.5em 0">
                <a href="https://www.akeeba.com/landing/akeeba-backup.html"
                   class="akeeba-btn--ghost--small">
                    <span class="aklogo-backup-j"></span>
                    @lang('COM_AKEEBA_CONTROLPANEL_BTN_LEARNMORE')
                </a>
            </p>
        @endif
    </div>
</div>
PK�!]1A�331tmpl/ControlPanel/icons_troubleshooting.blade.phpnu&1i�<?php
/**
 * @package   akeebabackup
 * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

/** @var $this \Akeeba\Backup\Admin\View\ControlPanel\Html */

// Protect from unauthorized access
defined('_JEXEC') || die();

?>
<section class="akeeba-panel--info">
    <header class="akeeba-block-header">
        <h3>@lang('COM_AKEEBA_CPANEL_HEADER_TROUBLESHOOTING')</h3>
    </header>

    <div class="akeeba-grid">
	    @if($this->permissions['backup'])
            <a class="akeeba-action--teal"
                href="index.php?option=com_akeeba&view=Log">
                <span class="akion-ios-search-strong"></span>
	            @lang('COM_AKEEBA_LOG')
            </a>
	    @endif

	    @if(AKEEBA_PRO && $this->permissions['configure'])
            <a class="akeeba-action--teal"
                href="index.php?option=com_akeeba&view=Alice">
                <span class="akion-medkit"></span>
	            @lang('COM_AKEEBA_ALICE')
            </a>
	    @endif
    </div>
</section>
PK�!]����6&6&$tmpl/ControlPanel/warnings.blade.phpnu&1i�<?php
/**
 * @package   akeebabackup
 * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

/** @var $this \Akeeba\Backup\Admin\View\ControlPanel\Html */

// Protect from unauthorized access
defined('_JEXEC') || die();

$cloudFlareTestFile = 'CLOUDFLARE::' . $this->getContainer()->template->parsePath('media://com_akeeba/js/ControlPanel.min.js');
$cloudFlareTestFile .= '?' . $this->getContainer()->mediaVersion;

?>

{{-- Configuration Wizard pop-up --}}
@if($this->promptForConfigurationWizard)
    @include('admin:com_akeeba/Configuration/confwiz_modal')
@endif

{{-- Stuck database updates warning --}}
@if ($this->stuckUpdates)
    <div class="akeeba-block--warning">
        <p>
            @sprintf('COM_AKEEBA_CPANEL_ERR_UPDATE_STUCK', $this->getContainer()->db->getPrefix(), 'index.php?option=com_akeeba&view=ControlPanel&task=forceUpdateDb')
        </p>
    </div>
@endif

{{-- Potentially web accessible output directory --}}
@if ($this->isOutputDirectoryUnderSiteRoot)
    <!--
    Oh, hi there! It looks like you got curious and are peeking around your browser's developer tools – or just the
    source code of the page that loaded on your browser. Cool! May I explain what we are seeing here?

    Just to let you know, the next three DIVs (outDirSystem, insecureOutputDirectory and missingRandomFromFilename) are
    HIDDEN and their existence doesn't mean that your site has an insurmountable security issue. To the contrary.
    Whenever Akeeba Backup detects that the backup output directory is under your site's root it will CHECK its security
    i.e. if it's really accessible over the web. This check is performed with an AJAX call to your browser so if it
    takes forever or gets stuck you won't see a frustrating blank page in your browser. If AND ONLY IF a problem is
    detected said JavaScript will display one of the following DIVs, depending on what is applicable.

    So, to recap. These hidden DIVs? They don't indicate a problem with your site. If one becomes visible then – and
    ONLY then – should you do something about it, as instructed. But thank you for being curious. Curiosity is how you
    get involved with and better at web development. Stay curious!
    -->
    {{-- Web accessible output directory that coincides with or is inside in a CMS system folder --}}
    <details class="akeeba-block--failure" id="outDirSystem" style="display: none">
        <summary>@lang('COM_AKEEBA_CPANEL_HEAD_OUTDIR_INVALID')</summary>
        <p>
            @sprintf('COM_AKEEBA_CPANEL_LBL_OUTDIR_LISTABLE', realpath($this->getModel()->getOutputDirectory()))
        </p>
        <p>
            @lang('COM_AKEEBA_CPANEL_LBL_OUTDIR_ISSYSTEM')
        </p>
        <p>
            @lang('COM_AKEEBA_CPANEL_LBL_OUTDIR_ISSYSTEM_FIX')
            @lang('COM_AKEEBA_CPANEL_LBL_OUTDIR_DELETEORBEHACKED')
        </p>
    </details>

    {{-- Output directory can be listed over the web --}}
    <details class="akeeba-block--{{ $this->hasOutputDirectorySecurityFiles ? 'failure' : 'warning' }}" id="insecureOutputDirectory" style="display: none">
        <summary>
            @if ($this->hasOutputDirectorySecurityFiles)
            @lang('COM_AKEEBA_CPANEL_HEAD_OUTDIR_UNFIXABLE')
            @else
            @lang('COM_AKEEBA_CPANEL_HEAD_OUTDIR_INSECURE')
            @endif
        </summary>
        <p>
            @sprintf('COM_AKEEBA_CPANEL_LBL_OUTDIR_LISTABLE', realpath($this->getModel()->getOutputDirectory()))
        </p>
        @if (!$this->hasOutputDirectorySecurityFiles)
        <p>
            @lang('COM_AKEEBA_CPANEL_LBL_OUTDIR_CLICKTHEBUTTON')
        </p>
        <p>
            @lang('COM_AKEEBA_CPANEL_LBL_OUTDIR_FIX_SECURITYFILES')
        </p>

        <form action="index.php" method="POST" class="akeeba-form--inline">
            <input type="hidden" name="option" value="com_akeeba">
            <input type="hidden" name="view" value="ControlPanel">
            <input type="hidden" name="task" value="fixOutputDirectory">
            <input type="hidden" name="@token()" value="1">

            <button type="submit" class="akeeba-btn--block--green">
                <span class="akion-hammer"></span>
                @lang('COM_AKEEBA_CPANEL_BTN_FIXSECURITY')
            </button>
        </form>
        @else
        <p>
            @lang('COM_AKEEBA_CPANEL_LBL_OUTDIR_TRASHHOST')
            @lang('COM_AKEEBA_CPANEL_LBL_OUTDIR_DELETEORBEHACKED')
        </p>
        @endif
    </details>

    {{-- Output directory cannot be listed over the web but I can download files --}}
    <details class="akeeba-block--warning" id="missingRandomFromFilename" style="display: none">
        <summary>
            @lang('COM_AKEEBA_CPANEL_HEAD_OUTDIR_INSECURE_ALT')
        </summary>
        <p>
            @sprintf('COM_AKEEBA_CPANEL_LBL_OUTDIR_FILEREADABLE', realpath($this->getModel()->getOutputDirectory()))
        </p>
        <p>
            @lang('COM_AKEEBA_CPANEL_LBL_OUTDIR_CLICKTHEBUTTON')
        </p>
        <p>
            @lang('COM_AKEEBA_CPANEL_LBL_OUTDIR_FIX_RANDOM')
        </p>

        <form action="index.php" method="POST" class="akeeba-form--inline">
            <input type="hidden" name="option" value="com_akeeba">
            <input type="hidden" name="view" value="ControlPanel">
            <input type="hidden" name="task" value="addRandomToFilename">
            <input type="hidden" name="@token()" value="1">

            <button type="submit" class="akeeba-btn--block--green">
                <span class="akion-hammer"></span>
                @lang('COM_AKEEBA_CPANEL_BTN_FIXSECURITY')
            </button>
        </form>
    </details>

@endif

{{-- mbstring warning --}}
@unless($this->checkMbstring)
    <div class="akeeba-block--warning">
        @sprintf('COM_AKEEBA_CPANL_ERR_MBSTRING', PHP_VERSION)
    </div>
@endunless

{{-- Front-end backup secret word reminder --}}
@unless(empty($this->frontEndSecretWordIssue))
    <details class="akeeba-block--failure">
        <summary>@lang('COM_AKEEBA_CPANEL_ERR_FESECRETWORD_HEADER')</summary>
        <p>@lang('COM_AKEEBA_CPANEL_ERR_FESECRETWORD_INTRO')</p>
        <p>{{ $this->frontEndSecretWordIssue }}</p>
        <p>
            @lang('COM_AKEEBA_CPANEL_ERR_FESECRETWORD_WHATTODO_JOOMLA')
            @sprintf('COM_AKEEBA_CPANEL_ERR_FESECRETWORD_WHATTODO_COMMON', $this->newSecretWord)
        </p>
        <p>
            <a class="akeeba-btn--green akeeba-btn--big"
               href="index.php?option=com_akeeba&view=ControlPanel&task=resetSecretWord&@token(true)=1">
                <span class="akion-refresh"></span>
                @lang('COM_AKEEBA_CPANEL_BTN_FESECRETWORD_RESET')
            </a>
        </p>
    </details>
@endunless

{{-- Wrong media directory permissions --}}
@unless($this->areMediaPermissionsFixed)
    <details id="notfixedperms" class="akeeba-block--failure">
        <summary>@lang('COM_AKEEBA_CONTROLPANEL_WARN_WARNING')</summary>
        <p>@lang('COM_AKEEBA_CONTROLPANEL_WARN_PERMS_L1')</p>
        <p>@lang('COM_AKEEBA_CONTROLPANEL_WARN_PERMS_L2')</p>
        <ol>
            <li>@lang('COM_AKEEBA_CONTROLPANEL_WARN_PERMS_L3A')</li>
            <li>@lang('COM_AKEEBA_CONTROLPANEL_WARN_PERMS_L3B')</li>
        </ol>
        <p>@lang('COM_AKEEBA_CONTROLPANEL_WARN_PERMS_L4')</p>
    </details>
@endunless

{{-- You need to enter your Download ID --}}
@if($this->needsDownloadID)
    <details class="akeeba-block--warning">
        <summary>
            @lang('COM_AKEEBA_CPANEL_MSG_MUSTENTERDLID')
        </summary>
        <p>
            @sprintf('COM_AKEEBA_LBL_CPANEL_NEEDSDLID','https://www.akeeba.com/download/official/add-on-dlid.html')
        </p>
        <form name="dlidform" action="index.php" method="post" class="akeeba-form--inline">
            <input type="hidden" name="option" value="com_akeeba" />
            <input type="hidden" name="view" value="ControlPanel" />
            <input type="hidden" name="task" value="applydlid" />
            <input type="hidden" name="@token(true)" value="1" />
            <div class="akeeba-form-group">
                <label for="dlid">@lang('COM_AKEEBA_CPANEL_MSG_PASTEDLID')</label>
                <input type="text" name="dlid" placeholder="@lang('COM_AKEEBA_CONFIG_DOWNLOADID_LABEL')"
                       class="akeeba-input--wide">

                <button type="submit" class="akeeba-btn--green">
                    <span class="akion-checkmark-round"></span>
                    @lang('COM_AKEEBA_CPANEL_MSG_APPLYDLID')
                </button>
            </div>
        </form>
    </details>
@endif

{{-- You have CORE; you need to upgrade, not just enter a Download ID --}}
@if($this->coreWarningForDownloadID)
    <div class="akeeba-block--warning">
        @sprintf('COM_AKEEBA_LBL_CPANEL_NEEDSUPGRADE','http://akee.ba/abcoretopro')
    </div>
@endif

{{-- Warn about CloudFlare Rocket Loader --}}
<details class="akeeba-block--failure" style="display: none;" id="cloudFlareWarn">
    <summary>@lang('COM_AKEEBA_CPANEL_MSG_CLOUDFLARE_WARN')</summary>
    <p>@sprintf('COM_AKEEBA_CPANEL_MSG_CLOUDFLARE_WARN1', 'https://support.cloudflare.com/hc/en-us/articles/200169456-Why-is-JavaScript-or-jQuery-not-working-on-my-site-')</p>
</details>
<?php
/**
 * DO NOT USE INLINE JAVASCRIPT FOR THIS SCRIPT. DO NOT REMOVE THE ATTRIBUTES.
 *
 * This is a specialised test which looks for CloudFlare's completely broken RocketLoader feature and warns the user
 * about it.
 */
?>
<script type="text/javascript" data-cfasync="true">
    var test = localStorage.getItem('<?php echo $cloudFlareTestFile?>');
    if (test)
    {
        document.getElementById("cloudFlareWarn").style.display = "block";
    }
</script>
PK�!]�����#tmpl/ControlPanel/upgrade.blade.phpnu&1i�<?php
/**
 * @package   akeebabackup
 * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

/** @var $this \Akeeba\Backup\Admin\View\ControlPanel\Html */

// Protect from unauthorized access
defined('_JEXEC') || die();

// Only show in the Core version with a 10% probability
if (AKEEBA_PRO) return;

// Only show if it's at least 15 days since the last time the user dismissed the upsell
if (time() - $this->lastUpsellDismiss < 1296000) return;

?>
<div class="akeeba-panel--orange">
    <header class="akeeba-block-header">
        <h3>
            <span class="akion-ios-star"></span>
            @lang('COM_AKEEBA_CONTROLPANEL_HEAD_PROUPSELL')
        </h3>
    </header>

    <p>@lang('COM_AKEEBA_CONTROLPANEL_HEAD_LBL_PROUPSELL_1')</p>

    <p class="akeeba-block--info">@sprintf('COM_AKEEBA_CONTROLPANEL_HEAD_LBL_DISCOUNT',
        base64_decode('SVdBTlRJVEFMTA=='))</p>

    <p>@lang('COM_AKEEBA_CONTROLPANEL_HEAD_LBL_PROUPSELL_2')</p>

    <p>
        <a href="https://www.akeeba.com/landing/akeeba-backup.html"
           class="akeeba-btn--large--primary">
            <span class="aklogo-backup-j"></span>
            @lang('COM_AKEEBA_CONTROLPANEL_BTN_LEARNMORE')
        </a>

        <a href="@route('index.php?view=ControlPanel&task=dismissUpsell')" class="akeeba-btn--ghost--small">
            <span class="akion-ios-alarm"></span>
            @lang('COM_AKEEBA_CONTROLPANEL_BTN_HIDE')
        </a>
    </p>
</div>
PK�!]�VT���*tmpl/ControlPanel/sidebar_backup.blade.phpnu&1i�<?php
/**
 * @package   akeebabackup
 * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

/** @var $this \Akeeba\Backup\Admin\View\ControlPanel\Html */

// Protect from unauthorized access
defined('_JEXEC') || die();

?>
<div class="akeeba-panel">
    <header class="akeeba-block-header">
        <h3>@lang('COM_AKEEBA_BACKUP_STATS')</h3>
    </header>
    <div>{{ $this->latestBackupCell }}</div>
</div>
PK�!]n,&�}}$tmpl/ControlPanel/oneclick.blade.phpnu&1i�<?php
/**
 * @package   akeebabackup
 * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

/** @var $this \Akeeba\Backup\Admin\View\ControlPanel\Html */

// Protect from unauthorized access
defined('_JEXEC') || die();

?>
<section class="akeeba-panel--primary">

    <header class="akeeba-block-header">
        <h3>@lang('COM_AKEEBA_CPANEL_HEADER_QUICKBACKUP')</h3>
    </header>

    <div class=" akeeba-grid">
	    @foreach($this->quickIconProfiles as $qiProfile)
            <a class="akeeba-action--green"
               href="index.php?option=com_akeeba&view=Backup&autostart=1&profileid={{ (int) $qiProfile->id }}&@token(true)=1">
                <span class="akion-play"></span>
                <span>{{{ $qiProfile->description }}}</span>
            </a>
	    @endforeach
    </div>

</section>
PK�!]Զ��GG#tmpl/ControlPanel/default.blade.phpnu&1i�<?php
/**
 * @package   akeebabackup
 * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

/** @var $this \Akeeba\Backup\Admin\View\ControlPanel\Html */

// Protect from unauthorized access
defined('_JEXEC') || die();

?>
@if (class_exists('Joomla\CMS\Component\ComponentHelper') && \Joomla\CMS\Component\ComponentHelper::isEnabled('com_akeebabackup'))
	@include('admin:com_akeeba/ControlPanel/backup8_uninstall')
	<?php return; ?>
@elseif (version_compare(JVERSION, '3.999.999', 'gt'))
	@include('admin:com_akeeba/ControlPanel/backup9_install')
@endif

<div id="akeebaBackup8Wrapper">
{{-- Display various possible warnings about issues which directly affect the user's experience --}}
@include('admin:com_akeeba/ControlPanel/warnings')

{{-- Main area --}}
<div class="akeeba-container--66-33">
	{{-- LEFT COLUMN (66% desktop width) --}}
	<div>
		{{-- Active profile switch --}}
		@include('admin:com_akeeba/ControlPanel/profile')

		{{-- One Click Backup icons --}}
		@if( ! (empty($this->quickIconProfiles)) && $this->permissions['backup'])
			@include('admin:com_akeeba/ControlPanel/oneclick')
		@endif

		{{-- Basic operations --}}
		@include('admin:com_akeeba/ControlPanel/icons_basic')

		{{-- Core Upgrade --}}
		@include('admin:com_akeeba/ControlPanel/upgrade')

		{{-- Troubleshooting --}}
		@include('admin:com_akeeba/ControlPanel/icons_troubleshooting')

		{{-- Advanced operations --}}
		@include('admin:com_akeeba/ControlPanel/icons_advanced')

		{{-- Include / Exclude data --}}
		@if($this->permissions['configure'])
			@include('admin:com_akeeba/ControlPanel/icons_includeexclude')
		@endif
	</div>
	{{-- RIGHT COLUMN (33% desktop width) --}}
	<div>
		{{-- Status Summary --}}
		@include('admin:com_akeeba/ControlPanel/sidebar_status')

		{{-- Backup stats --}}
		@include('admin:com_akeeba/ControlPanel/sidebar_backup')
	</div>
</div>

{{-- Footer --}}
@include('admin:com_akeeba/ControlPanel/footer')
</div>

{{-- Usage statistics collection IFRAME --}}
@if ($this->statsIframe)
	{{ $this->statsIframe }}
@endifPK�!]F^ɥ�0tmpl/ControlPanel/icons_includeexclude.blade.phpnu&1i�<?php
/**
 * @package   akeebabackup
 * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

/** @var $this \Akeeba\Backup\Admin\View\ControlPanel\Html */

// Protect from unauthorized access
defined('_JEXEC') || die();

?>

<section class="akeeba-panel--info">
    <header class="akeeba-block-header">
        <h3>@lang('COM_AKEEBA_CPANEL_HEADER_INCLUDEEXCLUDE')</h3>
    </header>

    <div class="akeeba-grid">
        @if(AKEEBA_PRO)
            <a class="akeeba-action--green"
                href="index.php?option=com_akeeba&view=MultipleDatabases">
                <span class="akion-arrow-swap"></span>
	            @lang('COM_AKEEBA_MULTIDB')
            </a>

            <a class="akeeba-action--green"
                href="index.php?option=com_akeeba&view=IncludeFolders">
                <span class="akion-folder"></span>
	            @lang('COM_AKEEBA_INCLUDEFOLDER')
            </a>
        @endif

        <a class="akeeba-action--red"
            href="index.php?option=com_akeeba&view=FileFilters">
            <span class="akion-filing"></span>
	        @lang('COM_AKEEBA_FILEFILTERS')
        </a>

        <a class="akeeba-action--red"
            href="index.php?option=com_akeeba&view=DatabaseFilters">
            <span class="akion-ios-grid-view"></span>
	        @lang('COM_AKEEBA_DBFILTER')
        </a>

        @if(AKEEBA_PRO)
            <a class="akeeba-action--red"
                href="index.php?option=com_akeeba&view=RegExFileFilters">
                <span class="akion-ios-folder"></span>
	            @lang('COM_AKEEBA_REGEXFSFILTERS')
            </a>

            <a class="akeeba-action--red"
                href="index.php?option=com_akeeba&view=RegExDatabaseFilters">
                <span class="akion-ios-box"></span>
	            @lang('COM_AKEEBA_REGEXDBFILTERS')
            </a>
        @endif

    </div></section>
PK�!]P��ff*tmpl/ControlPanel/icons_advanced.blade.phpnu&1i�<?php
/**
 * @package   akeebabackup
 * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

/** @var $this \Akeeba\Backup\Admin\View\ControlPanel\Html */

// Protect from unauthorized access
defined('_JEXEC') || die();

// All of the buttons in this panel require the Configure privilege
if (!$this->permissions['configure'])
{
	return;
}
?>
@if(AKEEBA_PRO)
    <section class="akeeba-panel--info">
        <header class="akeeba-block-header">
            <h3>@lang('COM_AKEEBA_CPANEL_HEADER_ADVANCED')</h3>
        </header>

        <div class="akeeba-grid">
            @if($this->permissions['configure'])
                <a class="akeeba-action--teal"
                   href="index.php?option=com_akeeba&view=Schedule">
                    <span class="akion-calendar"></span>
                    @lang('COM_AKEEBA_SCHEDULE')
                </a>
            @endif

            @if($this->permissions['configure'])
                <a class="akeeba-action--orange"
                   href="index.php?option=com_akeeba&view=Discover">
                    <span class="akion-ios-download"></span>
                    @lang('COM_AKEEBA_DISCOVER')
                </a>
            @endif

            @if($this->permissions['configure'])
                <a class="akeeba-action--orange"
                   href="index.php?option=com_akeeba&view=S3Import">
                    <span class="akion-ios-cloud-download"></span>
                    @lang('COM_AKEEBA_S3IMPORT')
                </a>
            @endif
        </div>
    </section>
@endif
PK�!]�ן���.tmpl/ControlPanel/warning_phpversion.blade.phpnu&1i�<?php
/**
 * @package   akeebabackup
 * @copyright Copyright (c)2006-2022 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

/** @var $this \Akeeba\Backup\Admin\View\ControlPanel\Html */

// Protect from unauthorized access
defined('_JEXEC') || die();

use FOF40\Date\Date;

?>
{{-- Old PHP version reminder --}}
@include('admin:com_akeeba/CommonTemplates/phpversion_warning', [
    'softwareName'  => 'Akeeba Backup',
    'minPHPVersion' => '7.2.0',
])
PK�!],�"tmpl/ControlPanel/footer.blade.phpnu&1i�<?php
/**
 * @package   akeebabackup
 * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

/** @var $this \Akeeba\Backup\Admin\View\ControlPanel\Html */

// Protect from unauthorized access
defined('_JEXEC') || die();

?>
<div class="row-fluid footer akeebabackup-footer">
	<div class="span12">
		<p style="height: 6em">
			@sprintf('Copyright &copy;2006-%s <a href="https://www.akeeba.com">Akeeba Ltd</a>. All Rights Reserved.', date('Y'))
			<br/>
			Akeeba Backup is Free Software and is distributed under the terms of the <a
					href="http://www.gnu.org/licenses/gpl-3.0.html">GNU General Public License</a>, version 3 or - at
			your option - any later version.
			@if(AKEEBA_PRO != 1)
				<br/>If you use Akeeba Backup Core, please post a rating and a review at the <a
						href="https://extensions.joomla.org/extensions/extension/access-a-security/site-security/akeeba-backup/">Joomla!
					Extensions Directory</a>.
			@endif
		</p>
	</div>
</div>
PK�!][���  !tmpl/Profiles/item_json.blade.phpnu&1i�<?php
/**
 * @package   akeebabackup
 * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

defined('_JEXEC') || die();

use Akeeba\Engine\Factory;
use Joomla\CMS\Document\JsonDocument;

/** @var Akeeba\Backup\Admin\View\Profiles\Json $this */

$data = $this->item->getData();

if (substr($data['configuration'], 0, 12) == '###AES128###')
{
	// Load the server key file if necessary
	if (!defined('AKEEBA_SERVERKEY'))
	{
		$filename = JPATH_COMPONENT_ADMINISTRATOR . '/BackupEngine/serverkey.php';

		include_once $filename;
	}

	$key = Factory::getSecureSettings()->getKey();

	$data['configuration'] = Factory::getSecureSettings()->decryptSettings($data['configuration'], $key);
}

$defaultName = $this->input->get('view', 'joomla', 'cmd');
$filename    = $this->input->get('basename', $defaultName, 'cmd');

/** @var JsonDocument $document */
$document = \Joomla\CMS\Factory::getApplication()->getDocument();
$document->setName($filename);

echo json_encode($data);
PK�!]�`�tmpl/Profiles/form.blade.phpnu&1i�<?php
/**
 * @package   akeebabackup
 * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

defined('_JEXEC') || die();

?>
<form action="index.php" method="post" name="adminForm" id="adminForm"
      class="akeeba-form--horizontal--with-hidden akeeba-panel--information">
    <div class="akeeba-form-group">
    </div>

    <div class="akeeba-form-group">
        <label for="description">
            @jhtml('tooltip', \Joomla\CMS\Language\Text::_('COM_AKEEBA_PROFILES_LABEL_DESCRIPTION_TOOLTIP'), '', '', \Joomla\CMS\Language\Text::_('COM_AKEEBA_PROFILES_LABEL_DESCRIPTION'))
        </label>
        <input type="text" name="description" class="span6" id="description" value="{{{ $this->item->description }}}" />
    </div>

    <div class="akeeba-hidden-fields-container">
        <input type="hidden" name="option" value="com_akeeba" />
        <input type="hidden" name="view" value="Profiles" />
        <input type="hidden" name="boxchecked" id="boxchecked" value="0" />
        <input type="hidden" name="task" id="task" value="save" />
        <input type="hidden" name="id" id="id" value="{{ (int)$this->item->id }}" />
        <input type="hidden" name="@token(true)" value="1" />
    </div>
</form>
PK�!]\�6���tmpl/Profiles/default.blade.phpnu&1i�<?php
/**
 * @package   akeebabackup
 * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

defined('_JEXEC') || die();

?>

<form action="index.php" method="post" name="adminForm" id="adminForm" class="akeeba-form akeeba-form--with-hidden">
    @include('admin:com_akeeba/CommonTemplates/ProfileName')

    <section class="akeeba-panel--50-50 akeeba-filter-bar-container">
        <div class="akeeba-filter-bar akeeba-filter-bar--left akeeba-form-section akeeba-form--inline">
            <div class="akeeba-filter-element akeeba-form-group">
                <input type="text" name="description" id="description"
                       value="{{{ $this->getModel()->getState('description', '', 'string') }}}" size="30"
                       class="akeebaGridViewAutoSubmitOnChange"
                       placeholder="@lang('COM_AKEEBA_PROFILES_COLLABEL_DESCRIPTION')"
                />
            </div>
        </div>
        <div class="akeeba-filter-bar akeeba-filter-bar--right">
            <div class="akeeba-filter-element akeeba-form-group">
                <label for="limit" class="element-invisible">
                    @lang('JFIELD_PLG_SEARCH_SEARCHLIMIT_DESC')
                </label>
                {{ $this->pagination->getLimitBox() }}
            </div>

            <div class="akeeba-filter-element akeeba-form-group">
                <label for="directionTable" class="element-invisible">
                    @lang('JFIELD_ORDERING_DESC')
                </label>
                <select name="directionTable" id="directionTable"
                        class="input-medium custom-select akeebaGridViewOrderTable">
                    <option value="">
                        @lang('JFIELD_ORDERING_DESC')
                    </option>
                    <option value="asc" {{ ($this->getLists()->order_Dir == 'asc') ? 'selected="selected"' : '' }}>
                        @lang('JGLOBAL_ORDER_ASCENDING')
                    </option>
                    <option value="desc" {{ ($this->getLists()->order_Dir == 'desc') ? 'selected="selected"' : '' }}>
                        @lang('JGLOBAL_ORDER_DESCENDING')
                    </option>
                </select>
            </div>

            <div class="akeeba-filter-element akeeba-form-group">
                <label for="sortTable" class="element-invisible">
                    @lang('JGLOBAL_SORT_BY')
                </label>
                <select name="sortTable" id="sortTable" class="input-medium custom-select akeebaGridViewOrderTable">
                    <option value="">
                        @lang('JGLOBAL_SORT_BY')
                    </option>
                    @jhtml('select.options', $this->sortFields, 'value', 'text', $this->getLists()->order)
                </select>
            </div>
        </div>
    </section>

    <table class="adminlist akeeba-table akeeba-table--striped">
        <thead>
        <tr>
            <th width="20">
                <input type="checkbox" name="toggle" value="" class="akeebaGridViewCheckAll" />
            </th>
            <th width="40">
                @jhtml('grid.sort', 'JGRID_HEADING_ID', 'id', $this->lists->order_Dir, $this->lists->order, 'browse')
            </th>
            <th width="20%"></th>
            <th>
                @jhtml('grid.sort', 'COM_AKEEBA_PROFILES_COLLABEL_DESCRIPTION', 'description', $this->lists->order_Dir, $this->lists->order, 'browse')
            </th>
            <th>
                @lang('COM_AKEEBA_CONFIG_QUICKICON_LABEL')
            </th>
        </tr>
        </thead>
        <tfoot>
        <tr>
            <td colspan="11">
                {{ $this->pagination->getListFooter() }}

            </td>
        </tr>
        </tfoot>
        <tbody>
		<?php $i = 0; ?>
        @foreach( $this->items as $profile )
            <tr>
                <td>
                    @jhtml('grid.id', ++$i, $profile->id)
                </td>
                <td>
                    {{ (int) $profile->id }}
                </td>
                <td>
                    <a class="akeeba-btn akeeba-btn--small akeeba-btn--primary"
                       href="index.php?option=com_akeeba&task=SwitchProfile&profileid={{ (int)$profile->id }}&returnurl={{ base64_encode(\Joomla\CMS\Uri\Uri::base() . 'index.php?option=com_akeeba&view=Configuration') }}&@token(true)=1">
                        <span class="icon-cog icon-white"></span>
                        @lang('COM_AKEEBA_CONFIG_UI_CONFIG')
                    </a>
                    &nbsp;
                    <a class="akeeba-btn akeeba-btn--small akeeba-btn--dark"
                       href="index.php?option=com_akeeba&view=Profile&task=read&id={{ $profile->id }}&basename={{ \Joomla\CMS\Application\ApplicationHelper::stringURLSafe($profile->description) }}&format=json&@token(true)=1">
                        <span class="icon-download"></span>
                        @lang('COM_AKEEBA_PROFILES_BTN_EXPORT')
                    </a>
                </td>
                <td>
                    <a href="index.php?option=com_akeeba&amp;view=Profiles&amp;task=edit&amp;id={{ (int) $profile->id }}">
                        {{{ $profile->description }}}
                    </a>
                </td>
                <td>
                    @jhtml('FEFHelp.browse.published', $profile->quickicon, $i, 'quickicon_')
                </td>
            </tr>
        @endforeach
        </tbody>
    </table>

    <div class="akeeba-hidden-fields-container">
        <input type="hidden" name="option" value="com_akeeba" />
        <input type="hidden" name="view" value="Profiles" />
        <input type="hidden" name="boxchecked" id="boxchecked" value="0" />
        <input type="hidden" name="task" id="task" value="browse" />
        <input type="hidden" name="hidemainmenu" id="hidemainmenu" value="0" />
        <input type="hidden" name="filter_order" id="filter_order"
               value="{{{ $this->lists->order }}}" />
        <input type="hidden" name="filter_order_Dir" id="filter_order_Dir"
               value="{{{ $this->lists->order_Dir }}}" />
        <input type="hidden" name="@token(true)" value="1" />
    </div>
</form>

<form action="index.php" method="post" name="importForm" enctype="multipart/form-data"
      id="importForm"
      class="akeeba-form akeeba-form--inline akeeba-panel--primary"
>
    <input type="hidden" name="option" value="com_akeeba" />
    <input type="hidden" name="view" value="Profiles" />
    <input type="hidden" name="boxchecked" id="boxchecked" value="0" />
    <input type="hidden" name="task" id="task" value="import" />
    <input type="hidden" name="@token(true)" value="1" />

    <input type="file" name="importfile" class="input-medium" />

    <button class="akeeba-btn akeeba-btn--green">
        <span class="icon-upload icon-white"></span>
        @lang('COM_AKEEBA_PROFILES_HEADER_IMPORT')
    </button>

    <span class="help-inline">
		@lang('COM_AKEEBA_PROFILES_LBL_IMPORT_HELP')
	</span>
</form>
PK�!]W^ftmpl/Manage/comment.blade.phpnu&1i�<?php
/**
 * @package   akeebabackup
 * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

// Protect from unauthorized access
defined('_JEXEC') || die();

/** @var  \Akeeba\Backup\Admin\View\Manage\Html  $this */

?>
<form name="adminForm" id="adminForm" action="index.php" method="post" class="akeeba-form--horizontal">
	<div class="akeeba-form-group">
		<label for="description">
			@lang('COM_AKEEBA_BUADMIN_LABEL_DESCRIPTION')
		</label>
        <input type="text" name="description" id="description" maxlength="255" size="50"
               value="{{{ $this->record['description'] }}}" />
	</div>

	<div class="akeeba-form-group">
		<label for="comment">
			@lang('COM_AKEEBA_BUADMIN_LABEL_COMMENT')
		</label>
        @editor('comment',  $this->record['comment'], '100%', '400', '60', '20', array())
	</div>

    <div class="akeeba-hidden-fields-container">
        <input type="hidden" name="option" value="com_akeeba" />
        <input type="hidden" name="task" value="" />
        <input type="hidden" name="view" value="Manage" />
        <input type="hidden" name="id" value="{{ (int)$this->record['id'] }}" />
        <input type="hidden" name="@token(true)" value="1" />
    </div>
</form>
PK�!]q���  #tmpl/Manage/manage_column.blade.phpnu&1i�<?php
/**
 * @package   akeebabackup
 * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

// Protect from unauthorized access
defined('_JEXEC') || die();

use Akeeba\Backup\Admin\Helper\Utils;

/** @var  \Akeeba\Backup\Admin\View\Manage\Html $this */
/** @var  array $record */

if (!isset($record['remote_filename']))
{
	$record['remote_filename'] = '';
}

$archiveExists    = $record['meta'] == 'ok';
$showManageRemote = $record['hasRemoteFiles'] && (AKEEBA_PRO == 1);
$engineForProfile = array_key_exists($record['profile_id'], $this->enginesPerProfile) ? $this->enginesPerProfile[$record['profile_id']] : 'none';
$showUploadRemote = $this->permissions['backup'] && $archiveExists && !$showManageRemote && ($engineForProfile != 'none') && ($record['meta'] != 'obsolete') && (AKEEBA_PRO == 1);
$showDownload     = $this->permissions['download'] && $archiveExists;
$showViewLog      = $this->permissions['backup'] && isset($record['backupid']) && !empty($record['backupid']);
$postProcEngine   = '';
$thisPart         = '';
$thisID           = urlencode($record['id']);

if ($showUploadRemote)
{
	$postProcEngine   = $engineForProfile ?: 'none';
	$showUploadRemote = !empty($postProcEngine);
}

\AkeebaFEFHelper::loadFEFScript('Tooltip');
?>
<div style="display: none">
    <div id="akeeba-buadmin-{{ (int)$record['id'] }}" tabindex="-1">
        <div class="akeeba-renderer-fef">
            <h4>@lang('COM_AKEEBA_BUADMIN_LBL_BACKUPINFO')</h4>

            <p>
                <strong>@lang('COM_AKEEBA_BUADMIN_LBL_ARCHIVEEXISTS')</strong>
                <br />
                @if($record['meta'] == 'ok')
                    <span class="akeeba-label--success">
				@lang('JYES')
			</span>
                @else
                    <span class="akeeba-label--failure">
				@lang('JNO')
			</span>
                @endif
            </p>
            <p>
                <strong>@lang('COM_AKEEBA_BUADMIN_LBL_ARCHIVEPATH' . ($archiveExists ? '' : '_PAST'))</strong>
                <br />
                <span class="akeeba-label--information">
				{{{ Utils::getRelativePath(JPATH_SITE, dirname($record['absolute_path'])) }}}
				</span>
            </p>
            <p>
                <strong>@lang('COM_AKEEBA_BUADMIN_LBL_ARCHIVENAME' . ($archiveExists ? '' : '_PAST'))</strong>
                <br />
                <code>
                    {{{ $record['archivename'] }}}
                </code>
            </p>
        </div>

    </div>

    @if($showDownload)
        <div id="akeeba-buadmin-download-{{ (int)$record['id'] }}" tabindex="-2" role="dialog">
            <div class="akeeba-renderer-fef">
                <div class="akeeba-block--warning">
                    <h4>
                        @lang('COM_AKEEBA_BUADMIN_LBL_DOWNLOAD_TITLE')
                    </h4>
                    <p>
                        @lang('COM_AKEEBA_BUADMIN_LBL_DOWNLOAD_WARNING')
                    </p>
                </div>

                @if($record['multipart'] < 2)
                    <a class="akeeba-btn--primary--small comAkeebaManageDownloadButton"
                       data-id="{{{ $record['id'] }}}">
                        <span class="akion-ios-download"></span>
                        @lang('COM_AKEEBA_BUADMIN_LOG_DOWNLOAD')
                    </a>
                @endif
                @if($record['multipart'] >= 2)
                    <div>
                        @sprintf('COM_AKEEBA_BUADMIN_LBL_DOWNLOAD_PARTS', (int)$record['multipart'])
                    </div>
                    @for($count = 0; $count < $record['multipart']; $count++)
                    @if($count > 0)
                    &bull;
                @endif
                <a class="akeeba-btn--small--dark comAkeebaManageDownloadButton"
                   data-id="{{{ $record['id'] }}}"
                   data-part="{{{ $count }}}">
                    <span class="akion-android-download"></span>
                    @sprintf('COM_AKEEBA_BUADMIN_LABEL_PART', $count)
                </a>
                @endfor
                @endif
            </div>
        </div>
    @endif
</div>

@if($showManageRemote)
    <div style="padding-bottom: 3pt;">
        <a class="akeeba-btn--primary akeeba_remote_management_link"
           data-management="index.php?option=com_akeeba&view=RemoteFiles&tmpl=component&task=listactions&id={{ (int)$record['id'] }}"
           data-reload="index.php?option=com_akeeba&view=Manage"
        >
            <span class="akion-cloud"></span>
            @lang('COM_AKEEBA_BUADMIN_LABEL_REMOTEFILEMGMT')
        </a>
    </div>
@elseif($showUploadRemote)
    <a class="akeeba-btn--primary akeeba_upload"
       data-upload="index.php?option=com_akeeba&view=Upload&tmpl=component&task=start&id={{ (int)$record['id'] }}"
       data-reload="index.php?option=com_akeeba&view=Manage"
       title="@sprintf('COM_AKEEBA_TRANSFER_DESC', JText::_("ENGINE_POSTPROC_{$postProcEngine}_TITLE"))">
        <span class="akion-android-upload"></span>
        @lang('COM_AKEEBA_TRANSFER_TITLE')
        (<em>{{{ $postProcEngine }}}</em>)
    </a>
@endif

<div style="padding-bottom: 3pt">
    @if($showDownload)
        <a class="akeeba-btn--{{ $showManageRemote || $showUploadRemote ? 'small--grey' : 'green' }} akeeba_download_button"
           data-dltarget="#akeeba-buadmin-download-{{ (int)$record['id'] }}"
        >
            <span class="akion-android-download"></span>
            @lang('COM_AKEEBA_BUADMIN_LOG_DOWNLOAD')
        </a>
    @endif

    @if($showViewLog)
        <a class="akeeba-btn--grey akeebaCommentPopover"
           {{ ($record['meta'] != 'obsolete') ? '' : 'disabled="disabled"' }}
           href="index.php?option=com_akeeba&view=Log&tag={{{ $record['tag'] }}}.{{{ $record['backupid'] }}}&profileid={{ (int)$record['profile_id'] }}"
           data-original-title="@lang('COM_AKEEBA_BUADMIN_LBL_LOGFILEID')"
           data-content="{{{ $record['backupid'] }}}">
            <span class="akion-ios-search-strong"></span>
            @lang('COM_AKEEBA_LOG')
        </a>
    @endif

    <a class="akeeba-btn--grey--small akeebaCommentPopover akeeba_showinfo_link"
       data-infotarget="#akeeba-buadmin-{{ (int)$record['id'] }}"
       data-content="@lang('COM_AKEEBA_BUADMIN_LBL_BACKUPINFO')"
    >
        <span class="akion-information-circled"></span>
    </a>
</div>
PK�!]�@n�1�1tmpl/Manage/default.blade.phpnu&1i�<?php
/**
 * @package   akeebabackup
 * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

// Protect from unauthorized access
defined('_JEXEC') || die();

/** @var  \Akeeba\Backup\Admin\View\Manage\Html $this */

\AkeebaFEFHelper::loadFEFScript('Tooltip');
?>
@jhtml('formbehavior.chosen')

@if (class_exists('Joomla\CMS\Component\ComponentHelper') && \Joomla\CMS\Component\ComponentHelper::isEnabled('com_akeebabackup'))
    @include('admin:com_akeeba/ControlPanel/backup8_uninstall')
	<?php return; ?>
@elseif (version_compare(JVERSION, '3.999.999', 'gt'))
    @include('admin:com_akeeba/ControlPanel/backup9_install')
@endif

<div id="akeebaBackup8Wrapper">
    @if($this->promptForBackupRestoration && version_compare(JVERSION, '3.999.999', 'le'))
        @include('admin:com_akeeba/Manage/howtorestore_modal')
    @endif

    <div class="akeeba-block--info">
        <h4>@lang('COM_AKEEBA_BUADMIN_LABEL_HOWDOIRESTORE_LEGEND')</h4>
        <p>
            @sprintf('COM_AKEEBA_BUADMIN_LABEL_HOWDOIRESTORE_TEXT_' . (AKEEBA_PRO ? 'PRO' : 'CORE'), 'http://akee.ba/abrestoreanywhere', 'index.php?option=com_akeeba&view=Transfer', 'https://www.akeeba.com/latest-kickstart-core.zip')
        </p>
        <p>
            @if (!AKEEBA_PRO)
                @sprintf('COM_AKEEBA_BUADMIN_LABEL_HOWDOIRESTORE_TEXT_CORE_INFO_ABOUT_PRO', 'https://www.akeeba.com/products/akeeba-backup.html')
            @endif
        </p>
    </div>

    <div id="j-main-container">
        <form action="index.php" method="post" name="adminForm" id="adminForm" class="akeeba-form">

            <section class="akeeba-panel--33-66 akeeba-filter-bar-container">
                <div class="akeeba-filter-bar akeeba-filter-bar--left akeeba-form-section akeeba-form--inline">
                    <div class="akeeba-filter-element akeeba-form-group">
                        <input type="text" name="description" placeholder="@lang('COM_AKEEBA_BUADMIN_LABEL_DESCRIPTION')"
                                id="filter_description"
                                value="{{{ $this->fltDescription }}}"
                                title="@lang('COM_AKEEBA_BUADMIN_LABEL_DESCRIPTION')" />
                    </div>

                    <div class="akeeba-filter-element akeeba-form-group akeeba-filter-joomlacalendarfix">
                        @if (version_compare(JVERSION, '3.999.999', 'le'))
                            @jhtml('calendar', $this->fltFrom, 'from', 'from', '%Y-%m-%d', array('class' => 'input-small'))
                        @else
                            <input
                                    type="datetime-local"
                                    pattern="[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}"
                                    name="from"
                                    id="from"
                                    value="{{{ $this->fltFrom }}}"
                            >
                        @endif
                    </div>

                    <div class="akeeba-filter-element akeeba-form-group akeeba-filter-joomlacalendarfix">
                        @if (version_compare(JVERSION, '3.999.999', 'le'))
                            @jhtml('calendar', $this->fltTo, 'to', 'to', '%Y-%m-%d', array('class' => 'input-small'))
                        @else
                            <input
                                    type="datetime-local"
                                    pattern="[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}"
                                    name="to"
                                    id="to"
                                    value="{{{ $this->fltTo }}}"
                            >
                        @endif
                    </div>

                    <div class="akeeba-filter-element akeeba-form-group">
                        <button class="akeeba-btn--grey akeeba-btn--icon-only akeeba-btn--small akeeba-hidden-phone"
                                type="submit" title="@lang('JSEARCH_FILTER_SUBMIT')">
                            <span class="akion-search"></span>
                        </button>
                    </div>

                    <div class="akeeba-filter-element akeeba-form-group">
                        {{-- Joomla 3.x: Chosen does not work with attached event handlers, only with inline event scripts (e.g. onchange) --}}
                        @jhtml('select.genericlist', $this->profilesList, 'profile', ['list.select' => $this->fltProfile, 'list.attr' => ['class' => 'advancedSelect', 'onchange' => 'document.forms.adminForm.submit();'], 'id' => 'comAkeebaManageProfileSelector'])
                    </div>

                    <div class="akeeba-filter-element akeeba-form-group">
                        {{-- Joomla 3.x: Chosen does not work with attached event handlers, only with inline event scripts (e.g. onchange) --}}
                        @jhtml('select.genericlist', $this->frozenList, 'frozen', ['list.select' => $this->fltFrozen, 'list.attr' => ['class' => 'advancedSelect', 'onchange' => 'document.forms.adminForm.submit();'], 'id' => 'comAkeebaManageFrozenSelector'])
                    </div>
                </div>

                <div class="akeeba-filter-bar akeeba-filter-bar--right">
                    @jhtml('FEFHelp.browse.orderheader', null, $this->sortFields, $this->getPagination(), $this->lists->order, $this->lists->order_Dir)
                </div>
            </section>

            <table class="akeeba-table akeeba-table--striped" id="itemsList">
                <thead>
                <tr>
                    <th width="32">
                        @jhtml('FEFHelp.browse.checkall')
                    </th>
                    <th width="48" class="akeeba-hidden-phone">
                        @sortgrid('id', 'COM_AKEEBA_BUADMIN_LABEL_ID')
                    </th>
                    <th>
                        @sortgrid('frozen', 'COM_AKEEBA_BUADMIN_LABEL_FROZEN')
                    </th>
                    <th>
                        @sortgrid('description', 'COM_AKEEBA_BUADMIN_LABEL_DESCRIPTION')
                    </th>
                    <th class="akeeba-hidden-phone">
                        @sortgrid('profile_id', 'COM_AKEEBA_BUADMIN_LABEL_PROFILEID')
                    </th>
                    <th width="80">
                        @lang('COM_AKEEBA_BUADMIN_LABEL_DURATION')
                    </th>
                    <th width="40">
                        @lang('COM_AKEEBA_BUADMIN_LABEL_STATUS')
                    </th>
                    <th width="80" class="akeeba-hidden-phone">
                        @lang('COM_AKEEBA_BUADMIN_LABEL_SIZE')
                    </th>
                    <th class="akeeba-hidden-phone">
                        @lang('COM_AKEEBA_BUADMIN_LABEL_MANAGEANDDL')
                    </th>
                </tr>
                </thead>
                <tfoot>
                <tr>
                    <td colspan="11" class="center">
                        {{ $this->pagination->getListFooter() }}
                    </td>
                </tr>
                </tfoot>
                <tbody>
                @if(empty($this->items))
                    <tr>
                        <td colspan="11" class="center">
                            @lang('COM_AKEEBA_BACKUP_STATUS_NONE')
                        </td>
                    </tr>
                @endif
                @if( ! (empty($this->items)))
					<?php $id = 1; $i = 0; ?>
                    @foreach($this->items as $record)
						<?php
						$id = 1 - $id;
						[$originDescription, $originIcon] = $this->getOriginInformation($record);
						[$startTime, $duration, $timeZoneText] = $this->getTimeInformation($record);
						[$statusClass, $statusIcon] = $this->getStatusInformation($record);
						$profileName = $this->getProfileName($record);

						$frozenIcon  = 'akion-waterdrop';
						$frozenTask  = 'freeze';
						$frozenTitle = \JText::_('COM_AKEEBA_BUADMIN_LABEL_ACTION_FREEZE');

						if ($record['frozen'])
						{
							$frozenIcon  = 'akion-ios-snowy';
							$frozenTask  = 'unfreeze';
							$frozenTitle = \JText::_('COM_AKEEBA_BUADMIN_LABEL_ACTION_UNFREEZE');
						}
						?>
                        <tr class="row{{ $id }}">
                            <td>@jhtml('grid.id', ++$i, $record['id'])</td>
                            <td class="akeeba-hidden-phone">
                                {{{ $record['id'] }}}
                            </td>
                            <td>
                                <a href="#" onclick="return Joomla.listItemTask('cb{{ $i }}', '{{$frozenTask}}')" title="{{$frozenTitle}}">
                                    <span class="{{ $frozenIcon }}"></span>
                                </a>
                            </td>
                            <td>
						<span class="{{ $originIcon }} akeebaCommentPopover" rel="popover"
                                title="@lang('COM_AKEEBA_BUADMIN_LABEL_ORIGIN')"
                                data-content="{{{ $originDescription }}}"></span>
                                @if( ! (empty($record['comment'])))
                                    <span class="akion-help-circled akeebaCommentPopover" rel="popover"
                                            data-content="{{{ $record['comment'] }}}"></span>
                                @endif
                                <a href="{{{ JUri::base() }}}index.php?option=com_akeeba&view=Manage&task=showcomment&id={{{ $record['id'] }}}">
                                    {{{ empty($record['description']) ? JText::_('COM_AKEEBA_BUADMIN_LABEL_NODESCRIPTION') : $record['description'] }}}

                                </a>
                                <br />
                                <div class="akeeba-buadmin-startdate" title="@lang('COM_AKEEBA_BUADMIN_LABEL_START')">
                                    <small>
                                        <span class="akion-calendar"></span>
                                        {{{ $startTime }}} {{{ $timeZoneText }}}
                                    </small>
                                </div>
                            </td>
                            <td class="akeeba-hidden-phone">
                                #{{{ (int)$record['profile_id'] }}}. {{{ $profileName }}}

                                <br />
                                <small>
                                    <em>{{{ $this->translateBackupType($record['type']) }}}</em>
                                </small>
                            </td>
                            <td>
                                {{{ $duration }}}
                            </td>
                            <td>
						<span class="{{ $statusClass }} akeebaCommentPopover" rel="popover"
                                title="@lang('COM_AKEEBA_BUADMIN_LABEL_STATUS')"
                                data-content="@lang('COM_AKEEBA_BUADMIN_LABEL_STATUS_' . $record['meta'])">
							<span class="{{ $statusIcon }}"></span>
						</span>
                            </td>
                            <td class="akeeba-hidden-phone">
                                @if($record['meta'] == 'ok')
                                    {{{ $this->formatFilesize($record['size']) }}}

                                @elseif($record['total_size'] > 0)
                                    <i>{{ $this->formatFilesize($record['total_size']) }}</i>
                                    @else
                                    &mdash;
                                @endif
                            </td>
                            <td class="akeeba-hidden-phone">
                                @include('admin:com_akeeba/Manage/manage_column', ['record' => &$record])
                            </td>
                        </tr>
                    @endforeach
                @endif
                </tbody>
            </table>

            <div class="akeeba-hidden-fields-container">
                <input type="hidden" name="option" id="option" value="com_akeeba" />
                <input type="hidden" name="view" id="view" value="Manage" />
                <input type="hidden" name="boxchecked" id="boxchecked" value="0" />
                <input type="hidden" name="task" id="task" value="default" />
                <input type="hidden" name="filter_order" id="filter_order" value="{{{ $this->lists->order }}}" />
                <input type="hidden" name="filter_order_Dir" id="filter_order_Dir" value="{{{ $this->lists->order_Dir }}}" />
                <input type="hidden" name="@token(true)" value="1" />
            </div>
        </form>
    </div>
</div>PK�!]�����(tmpl/Manage/howtorestore_modal.blade.phpnu&1i�<?php
/**
 * @package   akeebabackup
 * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

// Protect from unauthorized access
defined('_JEXEC') || die();

/** @var  \Akeeba\Backup\Admin\View\Manage\Html $this */

// Make sure we only ever add this HTML and JS once per page
if (defined('AKEEBA_VIEW_JAVASCRIPT_HOWTORESTORE'))
{
	return;
}

define('AKEEBA_VIEW_JAVASCRIPT_HOWTORESTORE', 1);

$this->container->platform->getDocument()->addScriptOptions('akeeba.Manage.ShowHowToRestoreModal', 1);

?>
<div id="akeeba-config-howtorestore-bubble">
    <div class="akeeba-renderer-fef">
        <h4>@lang('COM_AKEEBA_BUADMIN_LABEL_HOWDOIRESTORE_LEGEND')</h4>
        <p>
            @sprintf('COM_AKEEBA_BUADMIN_LABEL_HOWDOIRESTORE_TEXT_' . (AKEEBA_PRO ? 'PRO' : 'CORE'), 'http://akee.ba/abrestoreanywhere', 'index.php?option=com_akeeba&view=Transfer', 'https://www.akeeba.com/latest-kickstart-core.zip')
        </p>
        <p>
            @if (!AKEEBA_PRO)
                @sprintf('COM_AKEEBA_BUADMIN_LABEL_HOWDOIRESTORE_TEXT_CORE_INFO_ABOUT_PRO', 'https://www.akeeba.com/products/akeeba-backup.html')
            @endif
        </p>

        <div>
            <a class="akeeba-btn--primary" id="comAkeebaManageCloseHowToRestoreModal">
                <span class="akion-close"></span>
                @lang('COM_AKEEBA_BUADMIN_BTN_REMINDME')
            </a>
            <a href="index.php?option=com_akeeba&view=Manage&task=hidemodal" class="akeeba-btn--green">
                <span class="akion-checkmark-circled"></span>
                @lang('COM_AKEEBA_BUADMIN_BTN_DONTSHOWTHISAGAIN')
            </a>
        </div>
    </div>
</div>
PK�!]��S�ootmpl/Manage/default.xmlnu&1i�<?xml version="1.0" encoding="utf-8"?>
<!--~
  ~ @package   akeebabackup
  ~ @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
  ~ @license   GNU General Public License version 3, or later
  -->
<metadata>
	<layout title="COM_AKEEBA_VIEW_MANAGE_TITLE">
		<message>
			<![CDATA[COM_AKEEBA_VIEW_MANAGE_DESC]]>
		</message>
	</layout>
</metadata>
PK�!]{�-_55&tmpl/DatabaseFilters/tabular.blade.phpnu&1i�<?php
/**
 * @package   akeebabackup
 * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

defined('_JEXEC') || die();

/** @var \Akeeba\Backup\Admin\View\DatabaseFilters\Html $this */
?>
@include('admin:com_akeeba/CommonTemplates/ErrorModal')
@include('admin:com_akeeba/CommonTemplates/ProfileName')

<div class="AKEEBA_MASTER_FORM_STYLING akeeba-form--inline akeeba-panel--info">
    <div class="akeeba-form-group">
        <label>@lang('COM_AKEEBA_DBFILTER_LABEL_ROOTDIR')</label>
        <span>{{ $this->root_select }}</span>
    </div>
    <div id="addnewfilter" class="akeeba-form-group--actions">
        <label>
            @lang('COM_AKEEBA_FILEFILTERS_LABEL_ADDNEWFILTER')
        </label>

        <button class="akeeba-btn--grey" id="comAkeebaDatabaseFiltersAddNewTables">
            @lang('COM_AKEEBA_DBFILTER_TYPE_TABLES')
        </button>

        <button class="akeeba-btn--grey" id="comAkeebaDatabaseFiltersAddNewTableData">
            @lang('COM_AKEEBA_DBFILTER_TYPE_TABLEDATA')
        </button>
    </div>
</div>

<div class="akeeba-panel--primary">
    <div id="ak_list_container">
        <table id="ak_list_table" class="akeeba-table--striped">
            <thead>
            <tr>
                <td width="250px">@lang('COM_AKEEBA_FILEFILTERS_LABEL_TYPE')</td>
                <td>@lang('COM_AKEEBA_FILEFILTERS_LABEL_FILTERITEM')</td>
            </tr>
            </thead>
            <tbody id="ak_list_contents">
            </tbody>
        </table>
    </div>
</div>
PK�!]
��^^&tmpl/DatabaseFilters/default.blade.phpnu&1i�<?php
/**
 * @package   akeebabackup
 * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

defined('_JEXEC') || die();

/** @var \Akeeba\Backup\Admin\View\DatabaseFilters\Html $this */
?>
@include('admin:com_akeeba/CommonTemplates/ErrorModal')
@include('admin:com_akeeba/CommonTemplates/ProfileName')

<div class="AKEEBA_MASTER_FORM_STYLING akeeba-form--inline akeeba-panel--info">
    <div class="akeeba-form-group">
        <label>@lang('COM_AKEEBA_DBFILTER_LABEL_ROOTDIR')</label>
        {{ $this->root_select }}
    </div>
    <div class="akeeba-form-group--actions">
        <button class="akeeba-btn--green" id="comAkeebaDatabaseFiltersExcludeNonCMS">
            <span class="akion-ios-flag"></span>
            @lang('COM_AKEEBA_DBFILTER_LABEL_EXCLUDENONCORE')
        </button>
        <button class="akeeba-btn--red" id="comAkeebaDatabaseFiltersNuke">
            <span class="akion-ios-loop-strong"></span>
            @lang('COM_AKEEBA_DBFILTER_LABEL_NUKEFILTERS')
        </button>
    </div>
</div>

<div id="ak_main_container" class="akeeba-container--100">
</div>

<div class="akeeba-panel--info">
    <header class="akeeba-block-header">
        <h3>
            @lang('COM_AKEEBA_DBFILTER_LABEL_TABLES')
        </h3>
    </header>
    <div id="tables"></div>
</div>
PK�!]�cɸ��tmpl/Backup/script.blade.phpnu&1i�<?php
/**
 * @package   akeebabackup
 * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

// Protect from unauthorized access
defined('_JEXEC') || die();

use Joomla\CMS\Language\Text;
use Joomla\CMS\Uri\Uri;

/**
 * This file passes parameters to the Backup.js script using Joomla's script options API
 *
 * @var  $this  \Akeeba\Backup\Admin\View\Backup\Html
 */

$escapedBaseURL = addslashes(Uri::base());
$platform       = $this->container->platform;

// Initialization
$platform->addScriptOptions('akeeba.Backup.defaultDescription', addslashes($this->defaultDescription));
$platform->addScriptOptions('akeeba.Backup.currentDescription', addslashes(empty($this->description) ? $this->defaultDescription : $this->description));
$platform->addScriptOptions('akeeba.Backup.currentComment', addslashes($this->comment));
$platform->addScriptOptions('akeeba.Backup.hasAngieKey', $this->hasANGIEPassword);

// Auto-resume setup
$platform->addScriptOptions('akeeba.Backup.resume.enabled', (bool) $this->autoResume);
$platform->addScriptOptions('akeeba.Backup.resume.timeout', (int) $this->autoResumeTimeout);
$platform->addScriptOptions('akeeba.Backup.resume.maxRetries', (int) $this->autoResumeRetries);

// The return URL
$platform->addScriptOptions('akeeba.Backup.returnUrl', addcslashes($this->returnURL, "'\\"));

// Used as parameters to start_timeout_bar()
$platform->addScriptOptions('akeeba.Backup.maxExecutionTime', (int) $this->maxExecutionTime);
$platform->addScriptOptions('akeeba.Backup.runtimeBias', (int) $this->runtimeBias);

// Notifications
$platform->addScriptOptions('akeeba.System.notification.iconURL', sprintf("%s../media/com_akeeba/icons/logo-48.png", $escapedBaseURL));
$platform->addScriptOptions('akeeba.System.notification.hasDesktopNotification', (bool) $this->desktopNotifications);

// Domain keys
$platform->addScriptOptions('akeeba.Backup.domains', $this->domains);

// AJAX proxy, View Log and ALICE URLs
$platform->addScriptOptions('akeeba.System.params.AjaxURL', 'index.php?option=com_akeeba&view=Backup&task=ajax');
$platform->addScriptOptions('akeeba.Backup.URLs.LogURL', sprintf("%sindex.php?option=com_akeeba&view=Log", $escapedBaseURL));
$platform->addScriptOptions('akeeba.Backup.URLs.AliceURL', sprintf("%sindex.php?option=com_akeeba&view=Alice", $escapedBaseURL));

// Behavior triggers
$platform->addScriptOptions('akeeba.Backup.autostart', (!$this->unwriteableOutput && $this->autoStart) ? 1 : 0);

// Push language strings to Javascript
Text::script('COM_AKEEBA_BACKUP_TEXT_LASTRESPONSE');
Text::script('COM_AKEEBA_BACKUP_TEXT_BACKUPSTARTED');
Text::script('COM_AKEEBA_BACKUP_TEXT_BACKUPFINISHED');
Text::script('COM_AKEEBA_BACKUP_TEXT_BACKUPHALT');
Text::script('COM_AKEEBA_BACKUP_TEXT_BACKUPRESUME');
Text::script('COM_AKEEBA_BACKUP_TEXT_BACKUPHALT_DESC');
Text::script('COM_AKEEBA_BACKUP_TEXT_BACKUPFAILED');
Text::script('COM_AKEEBA_BACKUP_TEXT_BACKUPWARNING');
Text::script('COM_AKEEBA_BACKUP_TEXT_AVGWARNING');
PK�!]�Aͼ��tmpl/Backup/default.xmlnu&1i�<?xml version="1.0" encoding="utf-8"?>
<!--~
  ~ @package   akeebabackup
  ~ @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
  ~ @license   GNU General Public License version 3, or later
  -->
<metadata>
	<layout title="COM_AKEEBA_VIEW_BACKUP_TITLE">
		<message>
			<![CDATA[COM_AKEEBA_VIEW_BACKUP_DESC]]>
		</message>
	</layout>
	<fields name="request" addfieldpath="administrator/components/com_akeeba/fields">
		<fieldset name="request">
			<field name="profileid" type="backupprofiles"
				   show_none="yes"
				   default="0"
				   label="COM_AKEEBA_VIEW_BACKUP_PROFILE_LABEL"
				   description="COM_AKEEBA_VIEW_BACKUP_PROFILE_DESC"
			/>

			<field name="autostart" type="fancyradio"
				   default="0"
				   class="btn-group"
				   label="COM_AKEEBA_VIEW_BACKUP_AUTOSTART_LABEL"
				   description="COM_AKEEBA_VIEW_BACKUP_AUTOSTART_DESC"
			>
				<option value="0">JNo</option>
				<option value="1">JYes</option>
			</field>

			<field name="akeeba_hide_toolbar" type="fancyradio"
				   default="0"
				   class="btn-group"
				   label="COM_AKEEBA_VIEW_BACKUP_HIDETOOLBAR_LABEL"
				   description="COM_AKEEBA_VIEW_BACKUP_HIDETOOLBAR_DESC"
			>
				<option value="0">JNo</option>
				<option value="1">JYes</option>
			</field>

			<field name="returnurl" type="urlencoded"
				   default=""
				   label="COM_AKEEBA_VIEW_BACKUP_RETURNURL_LABEL"
				   description="COM_AKEEBA_VIEW_BACKUP_RETURNURL_DESC"
				   />
		</fieldset>
	</fields>
</metadata>
PK�!]�3�3tmpl/Backup/default.blade.phpnu&1i�<?php
/**
 * @package   akeebabackup
 * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

// Protect from unauthorized access
defined('_JEXEC') || die();

/** @var  $this  \Akeeba\Backup\Admin\View\Backup\Html */

?>
@if (class_exists('Joomla\CMS\Component\ComponentHelper') && \Joomla\CMS\Component\ComponentHelper::isEnabled('com_akeebabackup'))
    @include('admin:com_akeeba/ControlPanel/backup8_uninstall')
	<?php return; ?>
@elseif (version_compare(JVERSION, '3.999.999', 'gt'))
    @include('admin:com_akeeba/ControlPanel/backup9_install')
@endif

{{-- Configuration Wizard pop-up --}}
@if($this->promptForConfigurationWizard)
	@include('admin:com_akeeba/Configuration/confwiz_modal')
@endif

{{-- The Javascript of the page --}}
@include('admin:com_akeeba/Backup/script')

<div id="akeebaBackup8Wrapper">
    {{-- Backup Setup --}}
    <div id="backup-setup" class="akeeba-panel--primary">
        <header class="akeeba-block-header">
            <h3>
                @lang('COM_AKEEBA_BACKUP_HEADER_STARTNEW')
            </h3>
        </header>

        @if($this->hasWarnings && !$this->unwriteableOutput)
            <div id="quirks" class="akeeba-block--{{ $this->hasErrors ? 'failure' : 'warning' }}">
                <h3 class="alert-heading">
                    @lang('COM_AKEEBA_BACKUP_LABEL_DETECTEDQUIRKS')
                </h3>
                <p>
                    @lang('COM_AKEEBA_BACKUP_LABEL_QUIRKSLIST')
                </p>
                {{ $this->warningsCell }}

            </div>
        @endif

        @if($this->unwriteableOutput)
            <div id="akeeba-fatal-outputdirectory" class="akeeba-block--failure">
                <h3>
                    @lang('COM_AKEEBA_BACKUP_ERROR_UNWRITABLEOUTPUT_' . ($this->autoStart ? 'AUTOBACKUP' : 'NORMALBACKUP'))
                </h3>
                <p>
                    @sprintf('COM_AKEEBA_BACKUP_ERROR_UNWRITABLEOUTPUT_COMMON', 'index.php?option=com_akeeba&view=Configuration', 'https://www.akeeba.com/warnings/q001.html')
                </p>
            </div>
        @endif

        <form action="index.php" method="post" name="flipForm" id="flipForm"
                class="akeeba-formstyle-reset akeeba-form--inline akeeba-panel--information"
                autocomplete="off">

            <div class="akeeba-form-group">
                <label>
                    @lang('COM_AKEEBA_CPANEL_PROFILE_TITLE'): #{{ $this->profileId }}

                </label>
                @jhtml('formbehavior.chosen')
                @jhtml('select.genericlist', $this->profileList, 'profileid', ['list.select' => $this->profileId, 'id' => 'comAkeebaBackupProfileDropdown', 'list.attr' => ['class' => 'advancedSelect']])
            </div>

            <div class="akeeba-form-group--actions">
                <button class="akeeba-btn--grey" id="comAkeebaBackupFlipProfile">
                    <span class="akion-refresh"></span>
                    @lang('COM_AKEEBA_CPANEL_PROFILE_BUTTON')
                </button>
            </div>

            <div class="akeeba-hidden-fields-container">
                <input type="hidden" name="option" value="com_akeeba"/>
                <input type="hidden" name="view" value="Backup"/>
                <input type="hidden" name="returnurl" value="{{{ $this->returnURL }}}"/>
                <input type="hidden" name="description" id="flipDescription" value=""/>
                <input type="hidden" name="comment" id="flipComment" value=""/>
                <input type="hidden" name="@token(true)" value="1"/>
            </div>
        </form>

        <form id="dummyForm" class="akeeba-form--horizontal" style="display: {{ $this->unwriteableOutput ? 'none' : 'block' }};">
            <div class="akeeba-form-group">
                <label for="backup-description">
                    @lang('COM_AKEEBA_BACKUP_LABEL_DESCRIPTION')
                </label>
                <input type="text" name="description" value="{{{ empty($this->description) ? $this->defaultDescription : $this->description }}}"
                        maxlength="255" size="80" id="backup-description" class="input-xxlarge" autocomplete="off" />
                <span class="akeeba-help-text">@lang('COM_AKEEBA_BACKUP_LABEL_DESCRIPTION_HELP')</span>
            </div>

            <div class="akeeba-form-group">
                <label for="comment">
                    @lang('COM_AKEEBA_BACKUP_LABEL_COMMENT')
                </label>
                <textarea id="comment" rows="5" cols="73" class="input-xxlarge">{{ $this->comment }}</textarea>
                <span class="akeeba-help-text">@lang('COM_AKEEBA_BACKUP_LABEL_COMMENT_HELP')</span>
            </div>

            <div class="akeeba-form-group--pull-right">
                <div class="akeeba-form-group--actions">
                    <button class="akeeba-btn--primary" id="backup-start">
                        <span class="akion-play"></span>
                        @lang('COM_AKEEBA_BACKUP_LABEL_START')
                    </button>

                    <a class="akeeba-btn--orange" id="backup-default" href="#">
                        <span class="akion-refresh"></span>
                        @lang('COM_AKEEBA_BACKUP_LABEL_RESTORE_DEFAULT')
                    </a>
                </div>
            </div>
        </form>
    </div>

    {{-- Warning for having set an ANGIE password --}}
    <div id="angie-password-warning" class="akeeba-block--warning" style="display: none">
        <h3>@lang('COM_AKEEBA_BACKUP_ANGIE_PASSWORD_WARNING_HEADER')</h3>
        <p>@lang('COM_AKEEBA_BACKUP_ANGIE_PASSWORD_WARNING_1')</p>
        <p>@lang('COM_AKEEBA_BACKUP_ANGIE_PASSWORD_WARNING_2')</p>
    </div>

    {{-- Backup in progress --}}
    <div id="backup-progress-pane" style="display: none">
        <div class="akeeba-block--info">
            @lang('COM_AKEEBA_BACKUP_TEXT_BACKINGUP')
        </div>

        <div class="akeeba-panel--primary">
            <header class="akeeba-block-header">
                <h3>
                    @lang('COM_AKEEBA_BACKUP_LABEL_PROGRESS')
                </h3>
            </header>

            <div id="backup-progress-content">
                <div id="backup-steps"></div>
                <div id="backup-status" class="backup-steps-container">
                    <div id="backup-step"></div>
                    <div id="backup-substep"></div>
                </div>
                <div id="backup-percentage" class="akeeba-progress">
                    <div class="akeeba-progress-fill" style="width: 0"></div>
                </div>
                <div id="response-timer">
                    <div class="color-overlay"></div>
                    <div class="text"></div>
                </div>
            </div>
            <span id="ajax-worker"></span>
        </div>

        @if (!AKEEBA_PRO)
            <div>
                <p>
                    <em>@lang('COM_AKEEBA_BACKUP_LBL_UPGRADENAG')</em>
                </p>
            </div>
        @endif
    </div>

    {{-- Backup complete --}}
    <div id="backup-complete" style="display: none">
        <div class="akeeba-panel--success">
            <header class="akeeba-block-header">
                <h3>
                    @if(empty($this->returnURL))
                        @lang('COM_AKEEBA_BACKUP_HEADER_BACKUPFINISHED')
                    @else
                        @lang('COM_AKEEBA_BACKUP_HEADER_BACKUPWITHRETURNURLFINISHED')
                    @endif
                </h3>
            </header>

            <div id="finishedframe">
                <p>
                    @if(empty($this->returnURL))
                        @lang('COM_AKEEBA_BACKUP_TEXT_CONGRATS')
                    @else
                        @lang('COM_AKEEBA_BACKUP_TEXT_PLEASEWAITFORREDIRECTION')
                    @endif
                </p>

                @if(empty($this->returnURL))
                    <a class="akeeba-btn--primary--big" href="index.php?option=com_akeeba&view=Manage">
                        <span class="akion-ios-list"></span>
                        @lang('COM_AKEEBA_BUADMIN')
                    </a>
                    <a class="akeeba-btn--grey" id="ab-viewlog-success" href="index.php?option=com_akeeba&view=Log&latest=1">
                        <span class="akion-ios-search-strong"></span>
                        @lang('COM_AKEEBA_LOG')
                    </a>
                @endif
            </div>
        </div>
    </div>

    {{-- Backup warnings --}}
    <div id="backup-warnings-panel" style="display:none">
        <div class="akeeba-panel--warning">
            <header class="akeeba-block-header">
                <h3>
                    @lang('COM_AKEEBA_BACKUP_LABEL_WARNINGS')
                </h3>
            </header>
            <div id="warnings-list">
            </div>
        </div>
    </div>

    {{-- Backup retry after error --}}
    <div id="retry-panel" style="display: none">
        <div class="akeeba-panel--warning">
            <header class="akeeba-block-header">
                <h3>
                    @lang('COM_AKEEBA_BACKUP_HEADER_BACKUPRETRY')
                </h3>
            </header>
            <div id="retryframe">
                <p>@lang('COM_AKEEBA_BACKUP_TEXT_BACKUPFAILEDRETRY')</p>
                <p>
                    <strong>
                        @lang('COM_AKEEBA_BACKUP_TEXT_WILLRETRY')
                        <span id="akeeba-retry-timeout">0</span>
                        @lang('COM_AKEEBA_BACKUP_TEXT_WILLRETRYSECONDS')
                    </strong>
                    <br/>
                    <button class="akeeba-btn--red--small" id="comAkeebaBackupCancelResume">
                        <span class="akion-android-cancel"></span>
                        @lang('COM_AKEEBA_MULTIDB_GUI_LBL_CANCEL')
                    </button>
                    <button class="akeeba-btn--green--small" id="comAkeebaBackupResumeBackup">
                        <span class="akion-ios-redo"></span>
                        @lang('COM_AKEEBA_BACKUP_TEXT_BTNRESUME')
                    </button>
                </p>

                <p>@lang('COM_AKEEBA_BACKUP_TEXT_LASTERRORMESSAGEWAS')</p>
                <p id="backup-error-message-retry"></p>
            </div>
        </div>
    </div>

    {{-- Backup error (halt) --}}
    <div id="error-panel" style="display: none">
        <div class="akeeba-panel--red">
            <header class="akeeba-block-header">
                <h3>
                    @lang('COM_AKEEBA_BACKUP_HEADER_BACKUPFAILED')
                </h3>
            </header>

            <div id="errorframe">
                <p>
                    @lang('COM_AKEEBA_BACKUP_TEXT_BACKUPFAILED')
                </p>
                <p id="backup-error-message"></p>

                <p>
                    @lang('COM_AKEEBA_BACKUP_TEXT_READLOGFAIL' . (AKEEBA_PRO ? 'PRO' : ''))
                </p>

                <div class="akeeba-block--info" id="error-panel-troubleshooting">
                    <p>
                        @if(AKEEBA_PRO)
                            @lang('COM_AKEEBA_BACKUP_TEXT_RTFMTOSOLVEPRO')
                        @endif

                        @sprintf('COM_AKEEBA_BACKUP_TEXT_RTFMTOSOLVE', 'https://www.akeeba.com/documentation/akeeba-backup-documentation/backup-now.html?utm_source=akeeba_backup&utm_campaign=backuperrorlink#troubleshoot-backup')
                    </p>
                    <p>
                        @if(AKEEBA_PRO)
                            @sprintf('COM_AKEEBA_BACKUP_TEXT_SOLVEISSUE_PRO', 'https://www.akeeba.com/support.html?utm_source=akeeba_backup&utm_campaign=backuperrorpro')
                        @else
                            @sprintf('COM_AKEEBA_BACKUP_TEXT_SOLVEISSUE_CORE', 'https://www.akeeba.com/subscribe.html?utm_source=akeeba_backup&utm_campaign=backuperrorcore','https://www.akeeba.com/support.html?utm_source=akeeba_backup&utm_campaign=backuperrorcore')
                        @endif

                        @sprintf('COM_AKEEBA_BACKUP_TEXT_SOLVEISSUE_LOG', 'index.php?option=com_akeeba&view=Log&latest=1')
                    </p>
                </div>

                @if(AKEEBA_PRO)
                    <a class="akeeba-btn--green" id="ab-alice-error" href="index.php?option=com_akeeba&view=Alice">
                        <span class="akion-medkit"></span>
                        @lang('COM_AKEEBA_BACKUP_ANALYSELOG')
                    </a>
                @endif

                <a class="akeeba-btn--primary" href="https://www.akeeba.com/documentation/akeeba-backup-documentation/troubleshoot-backup.html?utm_source=akeeba_backup&utm_campaign=backuperrorbutton">
                    <span class="akion-ios-book"></span>
                    @lang('COM_AKEEBA_BACKUP_TROUBLESHOOTINGDOCS')
                </a>

                <a class="akeeba-btn-grey" id="ab-viewlog-error" href="index.php?option=com_akeeba&view=Log&latest=1">
                    <span class="akion-ios-search-strong"></span>
                    @lang('COM_AKEEBA_LOG')
                </a>
            </div>
        </div>
    </div>
</div>PK�!]�G�i��"tmpl/FileFilters/default.blade.phpnu&1i�<?php
/**
 * @package   akeebabackup
 * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

defined('_JEXEC') || die();

/** @var \Akeeba\Backup\Admin\View\FileFilters\Html $this */
?>
@include('admin:com_akeeba/CommonTemplates/ErrorModal')
@include('admin:com_akeeba/CommonTemplates/ProfileName')

<div class="AKEEBA_MASTER_FORM_STYLING akeeba-form--inline akeeba-panel--info">
    <div class="akeeba-form-group">
        <label>
            @lang('COM_AKEEBA_FILEFILTERS_LABEL_ROOTDIR')
        </label>
        <span>{{ $this->root_select }}</span>
    </div>
    <div class="akeeba-form-group--actions">
        <button class="akeeba-btn--red" id="comAkeebaFileFiltersNuke">
            <span class="akion-ios-trash"></span>
            @lang('COM_AKEEBA_FILEFILTERS_LABEL_NUKEFILTERS')
        </button>

        <a class="akeeba-btn--grey" href="index.php?option=com_akeeba&view=FileFilters&task=tabular">
            <span class="akion-ios-list-outline"></span>
            @lang('COM_AKEEBA_FILEFILTERS_LABEL_VIEWALL')
        </a>
    </div>
</div>

<div id="ak_crumbs_container" class="akeeba-panel--100 akeeba-panel--information">
    <div>
        <ul id="ak_crumbs" class="akeeba-breadcrumb"></ul>
    </div>
</div>

<div id="ak_main_container" class="akeeba-container--50-50">
    <div>
        <div class="akeeba-panel--info">
            <header class="akeeba-block-header">
                <h3>
                    @lang('COM_AKEEBA_FILEFILTERS_LABEL_DIRS')
                </h3>
            </header>
            <div id="folders"></div>
        </div>
    </div>

    <div>
        <div class="akeeba-panel--info">
            <header class="akeeba-block-header">
                <h3>
                    @lang('COM_AKEEBA_FILEFILTERS_LABEL_FILES')
                </h3>
            </header>
            <div id="files"></div>
        </div>
    </div>
</div>
PK�!]�1��uu"tmpl/FileFilters/tabular.blade.phpnu&1i�<?php
/**
 * @package   akeebabackup
 * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

defined('_JEXEC') || die();

/** @var \Akeeba\Backup\Admin\View\FileFilters\Html $this */
?>
@include('admin:com_akeeba/CommonTemplates/ErrorModal')
@include('admin:com_akeeba/CommonTemplates/ProfileName')

<div class="AKEEBA_MASTER_FORM_STYLING akeeba-form--inline akeeba-panel--info">
    <div class="akeeba-form-group">
        <label>@lang('COM_AKEEBA_FILEFILTERS_LABEL_ROOTDIR')</label>
        {{ $this->root_select }}
    </div>
    <div id="addnewfilter" class="akeeba-form-group--actions">
        <label>
            @lang('COM_AKEEBA_FILEFILTERS_LABEL_ADDNEWFILTER')
        </label>
        <button class="akeeba-btn--grey" id="comAkeebaFileFiltersAddDirectories">
            @lang('COM_AKEEBA_FILEFILTERS_TYPE_DIRECTORIES')
        </button>
        <button class="akeeba-btn--grey" id="comAkeebaFileFiltersAddSkipfiles">
            @lang('COM_AKEEBA_FILEFILTERS_TYPE_SKIPFILES')
        </button>
        <button class="akeeba-btn--grey" id="comAkeebaFileFiltersAddSkipdirs">
            @lang('COM_AKEEBA_FILEFILTERS_TYPE_SKIPDIRS')
        </button>
        <button class="akeeba-btn--grey" id="comAkeebaFileFiltersAddFiles">
            @lang('COM_AKEEBA_FILEFILTERS_TYPE_FILES')
        </button>
    </div>
</div>

<form id="ak_roots_container_tab" class="akeeba-panel--primary">
    <div id="ak_list_container">
        <table id="ak_list_table" class="akeeba-table--striped">
            <thead>
            <tr>
                <td width="250px">@lang('COM_AKEEBA_FILEFILTERS_LABEL_TYPE')</td>
                <td>@lang('COM_AKEEBA_FILEFILTERS_LABEL_FILTERITEM')</td>
            </tr>
            </thead>
            <tbody id="ak_list_contents">
            </tbody>
        </table>
    </div>
</form>
PK�!]CCtmpl/Browser/default.blade.phpnu&1i�<?php
/**
 * @package   akeebabackup
 * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

// Protect from unauthorized access
defined('_JEXEC') || die();

use Joomla\CMS\Language\Text;
use Joomla\CMS\Uri\Uri;

/** @var \Akeeba\Backup\Admin\View\Browser\Html $this */

Text::script('COM_AKEEBA_CONFIG_UI_ROOTDIR', true);

?>
@if(empty($this->folder))
    <form action="index.php" method="post" name="adminForm" id="adminForm">
        <input type="hidden" name="option" value="com_akeeba" />
        <input type="hidden" name="view" value="Browser" />
        <input type="hidden" name="format" value="html" />
        <input type="hidden" name="tmpl" value="component" />
        <input type="hidden" name="folder" id="folder" value="" />
        <input type="hidden" name="processfolder" id="processfolder" value="0" />
        <input type="hidden" name="@token(true)" value="1" />
    </form>
@endif

@if(!(empty($this->folder)))
    <div class="akeeba-panel--100 akeeba-panel--primary">
        <div>
            <form action="index.php" method="get" name="adminForm" id="adminForm"
                  class="akeeba-form--inline akeeba-form--with-hidden">
                <span title="@lang($this->writable ? 'COM_AKEEBA_CPANEL_LBL_WRITABLE' : 'COM_AKEEBA_CPANEL_LBL_UNWRITABLE')"
                      class="{{ $this->writable ? 'akeeba-label--green' : 'akeeba-label--red' }}"
                >
                    <span class="{{ $this->writable ? 'akion-checkmark-circled' : 'akion-ios-close' }}"></span>
                </span>
                <input type="text" name="folder" id="folder" value="{{{ $this->folder }}}" />

                <button class="akeeba-btn--primary" id="comAkeebaBrowserGo">
                    <span class="akion-folder"></span>
                    @lang('COM_AKEEBA_BROWSER_LBL_GO')
                </button>

                <button class="akeeba-btn--green" id="comAkeebaBrowserUseThis">
                    <span class="akion-share"></span>
                    @lang('COM_AKEEBA_BROWSER_LBL_USE')
                </button>

                <div class="akeeba-hidden-fields-container">
                    <input type="hidden" name="folderraw" id="folderraw"
                           value="{{{ $this->folder_raw }}}" />
                    <input type="hidden" name="@token(true)" value="1" />
                    <input type="hidden" name="option" value="com_akeeba" />
                    <input type="hidden" name="view" value="Browser" />
                    <input type="hidden" name="tmpl" value="component" />
                </div>
            </form>
        </div>
    </div>

    @if(count($this->breadcrumbs))
        <div class="akeeba-panel--100 akeeba-panel--information">
            <div>
                <ul class="akeeba-breadcrumb">
					<?php $i = 0 ?>
                    @foreach($this->breadcrumbs as $crumb)
						<?php $i++; ?>
                        <li class="{{ ($i < count($this->breadcrumbs)) ? '' : 'active' }}">
                            @if($i < count($this->breadcrumbs))
                                <a href="{{{ Uri::base() . "index.php?option=com_akeeba&view=Browser&tmpl=component&folder=" . urlencode($crumb['folder']) }}}">
                                    {{{ $crumb['label'] }}}
                                </a>
                                <span class="divider">&bull;</span>
                            @else
                                {{{ $crumb['label'] }}}
                            @endif
                        </li>
                    @endforeach
                </ul>
            </div>
        </div>
    @endif

    <div class="akeeba-panel--100 akeeba-panel">
        <div>
            @if(count($this->subfolders))
                <table class="akeeba-table akeeba-table--striped">
                    <tr>
                        <td>
                            <a class="akeeba-btn--dark--small"
                               href="{{{ Uri::base() }}}index.php?option=com_akeeba&view=Browser&tmpl=component&folder={{{ $this->parent }}}">
                                <span class="akion-arrow-up-a"></span>
                                @lang('COM_AKEEBA_BROWSER_LBL_GOPARENT')
                            </a>
                        </td>
                    </tr>
                    @foreach($this->subfolders as $subfolder)
                        <tr>
                            <td>
                                <a class="akeeba-browser-folder" href="{{{ Uri::base() }}}index.php?option=com_akeeba&view=Browser&tmpl=component&folder={{{ $this->folder . '/' . $subfolder }}}">{{{ $subfolder }}}</a>
                            </td>
                        </tr>
                    @endforeach
                </table>
            @else
                @if(!$this->exists)
                    <div class="akeeba-block--failure">
                        @lang('COM_AKEEBA_BROWSER_ERR_NOTEXISTS')
                    </div>
                @elseif(!$this->inRoot)
                    <div class="akeeba-block--warning">
                        @lang('COM_AKEEBA_BROWSER_ERR_NONROOT')
                    </div>
                @elseif($this->openbasedirRestricted)
                    <div class="akeeba-block--failure">
                        @lang('COM_AKEEBA_BROWSER_ERR_BASEDIR')
                    </div>
                @else
                    <table class="akeeba-table--striped">
                        <tr>
                            <td>
                                <a class="akeeba-btn--dark--small"
                                   href="{{{ Uri::base() }}}index.php?option=com_akeeba&view=Browser&tmpl=component&folder={{{ $this->parent }}}">
                                    <span class="akion-arrow-up-a"></span>
                                    @lang('COM_AKEEBA_BROWSER_LBL_GOPARENT')
                                </a>
                            </td>
                        </tr>
                    </table>
                @endif{{-- secondary block --}}
            @endif {{-- count($this->subfolders) --}}
        </div>
    </div>
@endif
PK�!]9j�[)))tmpl/CommonTemplates/FTPBrowser.blade.phpnu&1i�<?php
/**
 * @package   akeebabackup
 * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

// Protect from unauthorized access
defined('_JEXEC') || die();
?>
<?php /* FTP browser */ ?>
<div class="modal fade" id="ftpdialog" tabindex="-1" role="dialog" aria-labelledby="ftpdialogLabel" aria-hidden="true"
     style="display: none;">
    <div class="akeeba-renderer-fef">
        <h4 id="ftpdialogLabel">
		    @lang('COM_AKEEBA_CONFIG_UI_FTPBROWSER_TITLE')
        </h4>

        <p class="instructions akeeba-block--info">
		    @lang('COM_AKEEBA_FTPBROWSER_LBL_INSTRUCTIONS')
        </p>
        <div class="error akeeba-block--failure" id="ftpBrowserErrorContainer">
            <h3>@lang('COM_AKEEBA_FTPBROWSER_LBL_ERROR')</h3>
            <p id="ftpBrowserError"></p>
        </div>

        <ul id="ak_crumbs2" class="breadcrumb"></ul>

        <div class="folderBrowserWrapper" id="ftpBrowserWrapper">
            <table id="ftpBrowserFolderList" class="akeeba-table akeeba-table--striped">
            </table>
        </div>

        <div>
            <button type="button" id="ftpdialogOkButton" class="akeeba-btn--primary">
                <span class="akion-checkmark"></span>
		        @lang('COM_AKEEBA_BROWSER_LBL_USE')
            </button>

            <button type="button" id="ftpdialogCancelButton" class="akeeba-btn--red">
                <span class="akion-ios-close"></span>
		        @lang('JTOOLBAR_CANCEL')
            </button>
        </div>
    </div>
</div>
PK�!]Xk/!!tmpl/CommonTemplates/hhvm.phpnu&1i�<?php
/**
 * @package   akeebabackup
 * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

// Protect from unauthorized access
defined('_JEXEC') or die();

?>

<div style="margin: 1em">
	<h1>We have detected that you are running HHVM instead of PHP. This software WILL NOT WORK properly on HHVM. Please switch to PHP 7 instead.</h1>
	<hr/>
	<p>
        HHVM was Facebook's attempt at modernizing the PHP 5.x language and making it faster. Unfortunately it's also incompatible with PHP proper.
        PHP 7 has solved all these issues. It's fast, modern and <em>fully compatible with our software</em>.
        Please switch to PHP 7. If you are unsure how to do that, contact your host or the person responsible for maintaining your server.
        They are the only people who can help you configure your server.
	</p>
	<p>
        Kindly note that HHVM is not -and has never been- a supported execution environment for our software.
        As a result, if you see this message on your site you are unfortunately ineligible for support and / or filing bug reports.
        Please switch to PHP 7. If your problem persists after that we can help you / accept your bug report. Thank you for your understanding.
	</p>
</div>
PK�!]N8Ҍ��0tmpl/CommonTemplates/FTPConnectionTest.blade.phpnu&1i�<?php
/**
 * @package   akeebabackup
 * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

// Protect from unauthorized access
defined('_JEXEC') || die();
?>
{{-- (S)FTP connection test --}}
<div class="modal fade" id="testFtpDialog" tabindex="-1" role="dialog" aria-labelledby="testFtpDialogLabel"
     aria-hidden="true" style="display:none;">
    <div class="akeeba-renderer-fef">
        <h4 class="modal-title" id="testFtpDialogLabel"></h4>
        <div class="akeeba-block--success" id="testFtpDialogBodyOk"></div>
        <div class="akeeba-block--failure" id="testFtpDialogBodyFail"></div>
    </div>
</div>
PK�!]�oy�tmpl/CommonTemplates/fof.phpnu&1i�<?php
/**
 * Missing FOF 4.x error page
 *
 * @copyright Copyright (c) 2018-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

// Protect from unauthorized access
defined('_JEXEC') or die();

$tooLongAgo = (int) gmdate('Y') - 2015;
?>

<div style="margin: 1em">
	<h1>Akeeba Framework-on-Framework (FOF) version 4 could not be found on this site</h1>
	<hr />
	<div class="alert alert-warning">
		<h2>
			This component requires the Akeeba FOF framework package, verion 4, to be installed on your site. Please go
			to <a href="https://www.akeeba.com/download/fof4.html">our download page</a> to download it, then install
			it on your site.
		</h2>
	</div>
	<hr />
	<h4>Further information</h4>
	<p>
		FOF is a Joomla component framework. It's the low level code which sits between our Joomla! extensions and
		Joomla! itself. It is automatically installed when you install our extensions on your site.
	</p>
	<p>
		FOF can be missing from your site either because Joomla failed to install it or because you, another Super User,
		or another extension mistakenly uninstalled it or deleted its files.
	</p>
	<p>
		If it's missing, our components cannot talk to Joomla &mdash; or vice versa. Because of that they can not run.
		That's why you see this message.
	</p>
	<p>
		You do not have to worry about adding bloat to your site. FOF is very small. It will also be automatically
		uninstalled when you uninstall all components which depend on it.
	</p>
	<p>
		FOF is installed in the <code><?php echo rtrim(JPATH_LIBRARIES, '/\\') . DIRECTORY_SEPARATOR ?>fof40</code>
		folder on your server. It appears in Joomla's Extensions, Manage page as <code>FOF40</code>. Please do not
		remove it from your site.
	</p>
	<?php if (version_compare(JVERSION, '3.9999.9999', 'le')): ?>
		<h4>Why do I have multiple FOF entries in Joomla?</h4>
		<p>
			Joomla <?php echo JVERSION ?> includes an <em>old, obsolete</em> version of FOF - version 2.x. It is
			installed in the <code><?php echo JPATH_LIBRARIES . DIRECTORY_SEPARATOR ?>fof</code> folder on your server.
			It appears in Joomla's Extensions, Manage page as <code>FOF</code>. Please do not remove it from your site;
			Joomla needs it to function properly.
		</p>
		<p>
			We discontinued FOF 2.x in 2015 &mdash; that's <?php echo $tooLongAgo ?> years ago. Ever since, we replaced it
			with FOF 3.x. Starting February 2021 we replaced it with FOF 4.x.
		</p>
		<p>
			The different FOF versions are incompatible with each other but all may be required on your site for
			different reasons:
		</p>
		<ul>
			<li>
				<strong>FOF 2.x</strong> is required by Joomla! 3.x itself. Some of its core features, such as
				post-installation messages and Two Factor Authentiaction, use it.
			</li>
			<li>
				<strong>FOF 3.x</strong> is used by Akeeba Ltd extensions released before late February 2021 and some third
				party extensions.
			</li>
			<li>
				<strong>FOF 4.x</strong> is used by Akeeba Ltd extensions release <em>after</em> February 2021 and some third
				party extensions.
			</li>
		</ul>
		<p>
			Depending on which extensions you are using on your site you may see some or all of the above FOF versions.
			You <strong>must not</strong> try to uninstall them or delete their files yourself. If you do that, you will
			break some extensions and may lose access to your site.
		</p>
		<p>
			Please note that Akeeba extensions will automatically uninstall FOF versions 3 and 4 when they are no longer
			marked as needed by any extensions installed on your sites. Please note that whether they are needed or not
			is something that each extension needs to communicate to Joomla when it is installed, updated or
			uninstalled. While we can vouch for our extensions' ability to correctly communicate this information we
			can not make any promises for third party extensions. If you are unable to uninstall FOF 3 or 4 after
			uninstalling all Akeeba Ltd extensions from your site the culprit is a third party extension. Such an
			extension either still uses FOF or didn't communicate its upgrade or uninstallation, letting Joomla think
			there is still an extension depending on FOF. We cannot provide support for the latter issue; it's something
			caused by a different developer's code. Thank you for your understanding!
		</p>
	<?php endif; ?>
</div>
PK�!]�;����)tmpl/CommonTemplates/ErrorModal.blade.phpnu&1i�<?php
/**
 * @package   akeebabackup
 * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

// Protect from unauthorized access
defined('_JEXEC') || die();
?>
{{--  Error modal  --}}
<div id="errorDialog" tabindex="-1" role="dialog" aria-labelledby="errorDialogLabel" aria-hidden="true"
     style="display:none;">
    <div class="akeeba-renderer-fef">
        <h4 id="errorDialogLabel">
			@lang('COM_AKEEBA_CONFIG_UI_AJAXERRORDLG_TITLE')
        </h4>

        <p>
			@lang('COM_AKEEBA_CONFIG_UI_AJAXERRORDLG_TEXT')
        </p>
        <pre id="errorDialogPre"></pre>
    </div>
</div>
PK�!]�BB*tmpl/CommonTemplates/SFTPBrowser.blade.phpnu&1i�<?php
/**
 * @package   akeebabackup
 * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

// Protect from unauthorized access
defined('_JEXEC') || die();
?>
{{-- SFTP browser --}}
<div class="modal fade" id="sftpdialog" tabindex="-1" role="dialog" aria-labelledby="sftpdialogLabel" aria-hidden="true"
     style="display: none;">
    <div class="akeeba-renderer-fef">
        <h4 id="sftpdialogLabel">
		    @lang('COM_AKEEBA_CONFIG_UI_SFTPBROWSER_TITLE')
        </h4>

        <p class="instructions akeeba-block--info">
		    @lang('COM_AKEEBA_SFTPBROWSER_LBL_INSTRUCTIONS')
        </p>

        <div class="error akeeba-block--failure" id="sftpBrowserErrorContainer">
            <h2>@lang('COM_AKEEBA_SFTPBROWSER_LBL_ERROR')</h2>
            <p id="sftpBrowserError"></p>
        </div>

        <ul id="ak_scrumbs" class="breadcrumb"></ul>

        <div class="folderBrowserWrapper" id="sftpBrowserWrapper">
            <table id="sftpBrowserFolderList" class="akeeba-table akeeba-table--striped">
            </table>
        </div>

        <div class="modal-footer">
            <button type="button" id="sftpdialogOkButton" class="akeeba-btn--primary">
                <span class="akion-checkmark"></span>
		        @lang('COM_AKEEBA_BROWSER_LBL_USE')
            </button>

            <button type="button" id="sftpdialogCancelButton" class="akeeba-btn--red">
                <span class="akion-ios-close"></span>
				@lang('JTOOLBAR_CANCEL')
            </button>
        </div>

    </div>
</div>
PK�!]�;�99tmpl/CommonTemplates/fef.phpnu&1i�<?php
/**
 * @package   akeebabackup
 * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

// Protect from unauthorized access
defined('_JEXEC') or die();

?>

<div style="margin: 1em">
	<h1>Akeeba Frontend Framework (FEF) could not be found on this site</h1>
	<hr/>
	<div class="alert alert-warning">
		<h2>
			This component requires the Akeeba Frontend Framework (FEF) to be installed on your site. Please go to <a
					href="https://www.akeeba.com/download/official/fef.html">our download page</a> to download it, then install it on your site.
		</h2>
	</div>
	<hr/>
	<h4>Further information</h4>
	<p>
        FEF is the name of our custom CSS framework. It's responsible for rendering the interface of our Joomla!
		extensions. It is automatically installed when you install our extensions on your site.
	</p>
	<p>
		FEF can be missing from your site either because Joomla failed to install it or because you, another Super User,
		or another extension mistakenly uninstalled it.
	</p>
	<p>
		If it's missing we cannot display the interface to this component. That's why you see this message.
	</p>
	<p>
		You do not have to worry about adding bloat to your site. FEF is very small. It will also be automatically
		uninstalled when you uninstall all components which depend on it.
	</p>
	<p>
		FEF is installed in the <code>media/fef</code> folder under your site's root. It appears in Joomla's Extensions,
		Manage page as <code>file_fef</code>. Please do not remove it from your site.
	</p>
</div>
PK�!]T�#_�+�+%tmpl/CommonTemplates/errorhandler.phpnu&1i�<?php
/**
 * PHP Exception Handler
 *
 * @copyright Copyright (c) 2018-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

// Protect from unauthorized access
defined('_JEXEC') or die();

/** @var Throwable $e */
/** @var string $title */
/** @var bool $isPro */

$code = $e->getCode();
$code = !empty($code) ? $code : 500;

$app  = class_exists('\Joomla\CMS\Factory') ? \Joomla\CMS\Factory::getApplication() : \JFactory::getApplication();

$user30 = (class_exists('JFactory') && method_exists('JFactory', 'getUser')) ? JFactory::getUser() : null;
$user38 = class_exists('\Joomla\CMS\Factory') && method_exists('\Joomla\CMS\Factory', 'getUser') ? \Joomla\CMS\Factory::getUser() : null;
$user40 = (is_object($app) && method_exists($app, 'getIdentity')) ? $app->getIdentity() : null;
$user = is_null($user40) ? $user38 : $user40;
$user = is_null($user40) ? $user30 : $user;
$isSuper = !is_null($user) && $user->authorise('core.admin');

$isFrontend   = class_exists('JApplicationSite') && ($app instanceof JApplicationSite);
$isFrontend   = $isFrontend || (class_exists('\Joomla\CMS\Application\SiteApplication') && ($app instanceof \Joomla\CMS\Application\SiteApplication));
$user         = $isFrontend ? (method_exists($app, 'getIdentity') ? $app->getIdentity() : JFactory::getUser()) : null;
$hideTheError = $isFrontend && !(defined('JDEBUG') && (JDEBUG == 1)) && !$isSuper;
$isPro        = !isset($isPro) ? false : $isPro;

// 403 and 404 are re-thrown
if (in_array($code, [403, 404]))
{
	throw $e;
}

if (version_compare(JVERSION, '4', 'lt'))
{
	$app->setHeader('HTTP/1.1', $code);
}
else
{
	// In Joomla 4 we have to use the "Status" header, otherwise we get a fatal error saying that
	// HTTP/1.1 is not a valid header
	$app->setHeader('Status', $code);
}

if (!$isFrontend)
{
	if (class_exists('\Joomla\CMS\Toolbar\ToolbarHelper'))
	{
		\Joomla\CMS\Toolbar\ToolbarHelper::title($title . ' <small>Unhandled Exception</small>');
	}
	else
	{
		JToolbarHelper::title($title . ' <small>Unhandled Exception</small>');
	}

}

$isJoomla3 = version_compare(JVERSION, '3.999.999', 'le');

?>

<?php if ($hideTheError): ?>
	<?php if ($isJoomla3): ?>
		<h1>The application has stopped responding</h1>
		<p>
			Please contact the administrator of the site and let them know of this error and what you were doing when this
			happened.
		</p>
	<?php else: ?>
		<div class="card">
			<h1 class="card-header bg-danger text-white">The application has stopped responding</h1>
			<div class="card-body">
				<p>
					Please contact the administrator of the site and let them know of this error and what you were doing when this
					happened.
				</p>
			</div>
		</div>
	<?php endif; ?>
	<?php return true; endif; ?>
<div class="<?php echo $isJoomla3 ? '' : 'card my-3' ?>">
	<h1 class="<?php echo $isJoomla3 ? '' : 'card-header bg-danger text-white' ?>">
		<?php echo $title ?> - An unhandled Exception has been detected
	</h1>
	<div class="<?php echo $isJoomla3 ? '' : 'card-body' ?>">
		<h3>
			<?php if ($isJoomla3): ?>
				<span class="label label-danger"><?php echo htmlentities($code) ?></span> <?php echo htmlentities($e->getMessage()) ?>
			<?php else: ?>
				<span class="badge bg-danger"><?php echo htmlentities($code) ?></span> <?php echo htmlentities($e->getMessage()) ?>
			<?php endif; ?>
		</h3>
		<p>
			File <code><?php echo htmlentities(str_ireplace(JPATH_ROOT, '&lt;root&gt;', $e->getFile())) ?></code>
			<?php if ($isJoomla3): ?>
				Line <span class="label label-info"><?php echo (int) $e->getLine() ?></span>
			<?php else: ?>
				Line <span class="badge bg-info"><?php echo (int) $e->getLine() ?></span>
			<?php endif; ?>
		</p>

		<?php if ($isPro): ?>
			<div class="<?php if ($isJoomla3):?>hero-unit<?php else: ?>alert alert-primary<?php endif; ?>">
				<p>
					<strong>Would you like us to help you faster?</strong>
				</p>
				<p>
					Save this page as PDF or HTML. Make a ZIP file containing this PDF or HTML file. When filing a support ticket please attach the ZIP file (<em>not</em> the PDF or HTML file itself).
				</p>
			</div>
			<p>
				<strong>Why do we need all that information?</strong>
				This information is an x-ray of your site at the time the
				error occurred. It lets us reproduce the issue or, if it's not a bug in our software, help you pinpoint the
				external reason which led to it.
			</p>
			<p>
				<strong>What about privacy?</strong>
				Attachments are private in our ticket system: only you and us can see them, <em>even if you file a public
																								ticket</em>, and they are automatically deleted after a month.
			</p>
		<?php endif; ?>

		<hr />
		<p>
			<span class="icon icon-warning-2"></span>
			<em>
				The content below this point is for developers and power users.
			</em>
		</p>
		<hr />

		<p class="alert alert-warning">
			Joomla <?= JVERSION ?> – PHP <?= PHP_VERSION ?> on <?= PHP_OS ?>
		</p>

		<h3>Debug information</h3>
		<p>
			Exception type: <code><?php echo htmlentities(get_class($e)) ?></code>
		</p>
		<pre><?php echo htmlentities($e->getTraceAsString()) ?></pre>

		<?php while ($e = $e->getPrevious()): ?>
			<hr />
			<h4>Previous exception</h4>
			<strong>
				<?php if ($isJoomla3): ?>
					<span class="label label-danger"><?php echo htmlentities($code) ?></span> <?php echo htmlentities($e->getMessage()) ?>
				<?php else: ?>
					<span class="badge badge-danger"><?php echo htmlentities($code) ?></span> <?php echo htmlentities($e->getMessage()) ?>
				<?php endif; ?>
			</strong>
			<p>
				File <code><?php echo htmlentities(str_ireplace(JPATH_ROOT, '&lt;root&gt;', $e->getFile())) ?></code> Line <span
						class="label label-info"><?php echo (int) $e->getLine() ?></span>
			</p>
			<p>
				Exception type: <code><?php echo htmlentities(get_class($e)) ?></code>
			</p>
			<pre><?php echo htmlentities($e->getTraceAsString()) ?></pre>
		<?php endwhile; ?>

		<h3>System information</h3>
		<table class="table table-striped">
			<tr>
				<td>Operating System (reported by PHP)</td>
				<td><?php echo PHP_OS ?></td>
			</tr>
			<tr>
				<td>PHP version (as reported <em>by your server</em>)</td>
				<td><?php echo PHP_VERSION ?></td>
			</tr>
			<tr>
				<td>PHP Built On</td>
				<td><?php echo htmlentities(php_uname()); ?></td>
			</tr>
			<tr>
				<td>PHP SAPI</td>
				<td><?php echo PHP_SAPI ?></td>
			</tr>
			<tr>
				<td>Server identity</td>
				<td><?php echo htmlentities(isset($_SERVER['SERVER_SOFTWARE']) ? $_SERVER['SERVER_SOFTWARE'] : getenv('SERVER_SOFTWARE')) ?></td>
			</tr>
			<tr>
				<td>Browser identity</td>
				<td><?php echo htmlentities(isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : '') ?></td>
			</tr>
			<tr>
				<td>Joomla! version</td>
				<td><?php echo JVERSION ?></td>
			</tr>
			<?php
			$db = JFactory::getDbo();
			if (!is_null($db)):
				?>
				<tr>
					<td>Database driver name</td>
					<td><?php echo $db->getName() ?></td>
				</tr>
				<tr>
					<td>Database driver type</td>
					<td><?php echo $db->getServerType() ?></td>
				</tr>
				<tr>
					<td>Database server version</td>
					<td><?php echo $db->getVersion() ?></td>
				</tr>
				<tr>
					<td>Database collation</td>
					<td><?php echo $db->getCollation() ?></td>
				</tr>
				<tr>
					<td>Database connection collation</td>
					<td><?php echo $db->getConnectionCollation() ?></td>
				</tr>
			<?php endif; ?>
			<tr>
				<td>PHP Memory limit</td>
				<td><?php echo function_exists('ini_get') ? htmlentities(ini_get('memory_limit')) : 'N/A' ?></td>
			</tr>
			<tr>
				<td>Peak Memory usage</td>
				<td><?php echo function_exists('memory_get_peak_usage') ? sprintf('%0.2fM', (memory_get_peak_usage() / 1024 / 1024)) : 'N/A' ?></td>
			</tr>
			<tr>
				<td>PHP Timeout (seconds)</td>
				<td><?php echo function_exists('ini_get') ? htmlentities(ini_get('max_execution_time')) : 'N/A' ?></td>
			</tr>
		</table>

		<h3>Request information</h3>
		<h4>$_GET</h4>
		<pre><?php echo htmlentities(print_r($_GET, true)) ?></pre>
		<h4>$_POST</h4>
		<pre><?php echo htmlentities(print_r($_POST, true)) ?></pre>
		<h4>$_COOKIE</h4>
		<pre><?php echo htmlentities(print_r($_COOKIE, true)) ?></pre>
		<h4>$_REQUEST</h4>
		<pre><?php echo htmlentities(print_r($_REQUEST, true)) ?></pre>

		<h3>Session state</h3>
		<pre><?php
			if (version_compare(JVERSION, '4', 'lt'))
			{
				echo htmlentities(print_r($app->getSession()->getData()->toArray(), true));
			}
			else
			{
				echo htmlentities(print_r($app->getSession()->all(), true));
			}
			?></pre>

		<?php
		if ($isJoomla3)
		{
			if (!include_once(JPATH_ADMINISTRATOR . '/components/com_admin/models/sysinfo.php'))
			{
				return;
			}

			$model       = new AdminModelSysInfo();
		}
		else
		{
			try
			{
				/** @var MVCFactoryInterface $factory */
				$factory = $app->bootComponent('com_admin')->getMVCFactory();
				/** @var \Joomla\Component\Admin\Administrator\Model\SysinfoModel $model */
				$model = $factory->createModel('Sysinfo', 'Administrator');
			}
			catch (Exception $e)
			{
				return;
			}
		}

		$directories = $model->getDirectory();

		try
		{
			$extensions = $model->getExtensions();
		}
		catch (Exception $e)
		{
			$extension = [];
		}

		$phpSettings = $model->getPhpSettings();
		$hasPHPInfo  = $model->phpinfoEnabled();
		?>

		<h3>PHP Settings</h3>
		<table class="table table-striped">
			<?php foreach ($phpSettings as $k => $v): ?>
				<tr>
					<td><?php echo $k ?></td>
					<td><?php echo htmlentities(print_r($v, true)) ?></td>
				</tr>
			<?php endforeach; ?>
		</table>

		<?php if ($hasPHPInfo):
			$phpInfo = $model->getPhpInfoArray(); ?>
			<h3>Loaded PHP Extensions</h3>
			<table class="table table-striped">
				<?php foreach ($phpInfo as $section => $data):
					if ($section == 'Core')
					{
						continue;
					} ?>
					<tr>
						<td><?php echo htmlentities($section) ?></td>
						<td>
							<?php if (in_array($section, ['curl', 'openssl', 'ssh2', 'ftp', 'session', 'tokenizer'])): ?>
								<pre><?php echo htmlentities(print_r($data, true)) ?></pre>
							<?php endif; ?>
						</td>
					</tr>
				<?php endforeach; ?>
			</table>
		<?php endif; ?>

		<h3>Enabled Extensions</h3>
		<table class="table table-striped">
			<?php foreach ($extensions as $extension => $info):
				if (strtoupper($info['state']) != 'ENABLED')
				{
					continue;
				} ?>
				<tr>
					<td><?php echo htmlentities($extension) ?></td>
					<td><?php echo htmlentities($info['version']) ?></td>
					<td><?php echo htmlentities($info['type']) ?></td>
					<td><?php echo htmlentities($info['author']) ?></td>
					<td><?php echo htmlentities($info['authorUrl']) ?></td>
				</tr>
			<?php endforeach; ?>
		</table>

		<h3>Directory Status</h3>
		<table class="table table-striped">
			<?php foreach ($directories as $k => $v): ?>
				<tr>
					<td>
						<?php echo htmlentities($k) ?>
						<?php echo !empty($v['message']) ? "[{$v['message']}]" : '' ?>
					</td>
					<td>
						<?php if ($v['writable']): ?>
							<span class="label label-success">Writeable</span>
						<?php else: ?>
							<span class="label label-danger">Unwriteable</span>
						<?php endif; ?>
					</td>
				</tr>
			<?php endforeach; ?>
		</table>
	</div>
</div>PK�!]�EĜ��,tmpl/CommonTemplates/FolderBrowser.blade.phpnu&1i�<?php
/**
 * @package   akeebabackup
 * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

// Protect from unauthorized access
defined('_JEXEC') || die();
?>

{{-- Filesystem browser --}}
<div class="modal" id="folderBrowserDialog" tabindex="-1" role="dialog" aria-labelledby="folderBrowserDialogLabel"
     aria-hidden="true" style="display: none;">
    <div class="akeeba-renderer-fef">
        <h4 id="folderBrowserDialogLabel">
		    @lang('COM_AKEEBA_CONFIG_UI_BROWSER_TITLE')
        </h4>
        <div id="folderBrowserDialogBody">
        </div>
    </div>
</div>
PK�!]�g��*tmpl/CommonTemplates/ProfileName.blade.phpnu&1i�<?php
/**
 * @package   akeebabackup
 * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

// Protect from unauthorized access
defined('_JEXEC') || die();
?>
<div class="akeeba-block--info">
	<strong>@lang('COM_AKEEBA_CPANEL_PROFILE_TITLE')</strong>:
	#{{{ (int)($this->profileId) }}} {{{ $this->profileName }}}
</div>
PK�!]â_���&tmpl/RegExFileFilter/default.blade.phpnu&1i�<?php
/**
 * @package   akeebabackup
 * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

defined('_JEXEC') || die();

?>
@include('admin:com_akeeba/CommonTemplates/ErrorModal')
@include('admin:com_akeeba/CommonTemplates/ProfileName')

<div class="akeeba-panel--information">
    <div class="akeeba-form-section">
        <div class="AKEEBA_MASTER_FORM_STYLING akeeba-form--inline">
            <label>@lang('COM_AKEEBA_FILEFILTERS_LABEL_ROOTDIR')</label>
            <span id="ak_roots_container_tab">
			{{ $this->root_select }}
			</span>
        </div>
    </div>
</div>

<div class="akeeba-container--primary">
    <div id="ak_list_container">
        <table id="table-container" class="akeeba-table--striped--dynamic-line-editor">
            <thead>
            <tr>
                <th width="120px">&nbsp;</th>
                <th width="250px">@lang('COM_AKEEBA_FILEFILTERS_LABEL_TYPE')</th>
                <th>@lang('COM_AKEEBA_FILEFILTERS_LABEL_FILTERITEM')</th>
            </tr>
            </thead>
            <tbody id="ak_list_contents" class="table-container">
            </tbody>
        </table>
    </div>
</div>
PK�!])�u�nn)tmpl/ConfigurationWizard/wizard.blade.phpnu&1i�<?php
/**
 * @package   akeebabackup
 * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

// Protect from unauthorized access
defined('_JEXEC') || die();

$steps = ['flush', 'minexec', 'directory', 'dbopt', 'maxexec', 'splitsize']
?>

<div id="akeeba-confwiz">

    <div id="backup-progress-pane">
        <div class="akeeba-block--warning">
            @lang('COM_AKEEBA_CONFWIZ_INTROTEXT')
        </div>

        <fieldset id="backup-progress-header">
            <h3>
                @lang('COM_AKEEBA_CONFWIZ_PROGRESS')
            </h3>
            <div id="backup-progress-content">
                <div id="backup-steps">
	                @foreach ($steps as $step)
                        <div id="step-{{ $step }}" class="akeeba-label--grey">
                            @lang('COM_AKEEBA_CONFWIZ_' . $step)
                        </div>
                    @endforeach
                </div>
                <div class="backup-steps-container">
                    <div id="backup-substep">&nbsp;</div>
                </div>
            </div>
            <span id="ajax-worker"></span>
        </fieldset>

    </div>

    <div id="error-panel" class="akeeba-block--failure" style="display:none">
        <h2 class="alert-heading">@lang('COM_AKEEBA_CONFWIZ_HEADER_FAILED')</h2>
        <div id="errorframe">
            <p id="backup-error-message">
            </p>
        </div>
    </div>

    <div id="backup-complete" style="display: none">
        <div class="akeeba-block--success">
            <h2 class="alert-heading">@lang('COM_AKEEBA_CONFWIZ_HEADER_FINISHED')</h2>
            <div id="finishedframe">
                <p>
                    @lang('COM_AKEEBA_CONFWIZ_CONGRATS')
                </p>
                <p>
                    <a
                            class="akeeba-btn--primary akeeba-btn--big"
                            href="{{{ JUri::base() }}}index.php?option=com_akeeba&view=Backup">
                        <span class="akion-play"></span>
                        @lang('COM_AKEEBA_BACKUP')
                    </a>
                    <a
                            class="akeeba-btn--ghost"
                            href="{{{ JUri::base() }}}index.php?option=com_akeeba&view=Configuration">
                        <span class="akion-wrench"></span>
                        @lang('COM_AKEEBA_CONFIG')
                    </a>
                    @if (AKEEBA_PRO)
                    <a
                            class="akeeba-btn--ghost"
                            href="{{{ JUri::base() }}}index.php?option=com_akeeba&view=Schedule">
                        <span class="akion-calendar"></span>
                        @lang('COM_AKEEBA_SCHEDULE')
                    </a>
                    @endif
                </p>
            </div>
        </div>
    </div>
</div>
PK�!]
��}}tmpl/Configuration/default.xmlnu&1i�<?xml version="1.0" encoding="utf-8"?>
<!--~
  ~ @package   akeebabackup
  ~ @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
  ~ @license   GNU General Public License version 3, or later
  -->
<metadata>
	<layout title="COM_AKEEBA_VIEW_CONFIGURATION_TITLE">
		<message>
			<![CDATA[COM_AKEEBA_VIEW_CONFIGURATION_DESC]]>
		</message>
	</layout>
</metadata>
PK�!]=�^^*tmpl/Configuration/confwiz_modal.blade.phpnu&1i�<?php
/**
 * @package   akeebabackup
 * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

// Protect from unauthorized access
defined('_JEXEC') || die();

/** @var \FOF40\View\DataView\Html $this */

// Make sure we only ever add this HTML and JS once per page
if (defined('AKEEBA_VIEW_JAVASCRIPT_CONFWIZ_MODAL'))
{
	return;
}

define('AKEEBA_VIEW_JAVASCRIPT_CONFWIZ_MODAL', 1);

$js = <<< JS
akeeba.Loader.add('akeeba.System', function(){
    akeeba.System.documentReady(function(){
        akeeba.System.addEventListener('comAkeebaConfigurationWizardModalClose', 'click', function() {
          akeeba.System.configurationWizardModal.close();
        });

        setTimeout(function() {
          akeeba.System.configurationWizardModal = akeeba.Modal.open({
            inherit: '#akeeba-config-confwiz-bubble',
            width: '80%'
        });
        }, 500);
    });
});

JS;

$this->container->template->addJSInline($js);
?>

<div id="akeeba-config-confwiz-bubble" class="modal fade" role="dialog"
     aria-labelledby="DialogLabel" aria-hidden="true" style="display: none;">
    <div class="akeeba-renderer-fef">
        <h4>
            @lang('COM_AKEEBA_CONFIG_HEADER_CONFWIZ')
        </h4>
        <div>
            <p>
                @lang('COM_AKEEBA_CONFIG_LBL_CONFWIZ_INTRO')
            </p>
            <p>
                <a href="index.php?option=com_akeeba&view=ConfigurationWizard"
                   class="akeeba-btn--green akeeba-btn--big">
                    <span class="akion-flash"></span>
                    @lang('COM_AKEEBA_CONFWIZ')
                </a>
            </p>
            <p>
                @lang('COM_AKEEBA_CONFIG_LBL_CONFWIZ_AFTER')
            </p>
        </div>
        <div>
            <a href="#" class="akeeba-btn--ghost akeeba-btn--small" id="comAkeebaConfigurationWizardModalClose"
               onclick="akeeba.System.configurationWizardModal.close();">
                <span class="akion-close"></span>
                @lang('JCANCEL')
            </a>
        </div>
    </div>
</div>
PK�!]�gF[[$tmpl/Configuration/default.blade.phpnu&1i�<?php
/**
 * @package   akeebabackup
 * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

// Protect from unauthorized access
defined('_JEXEC') || die();

/** @var  \Akeeba\Backup\Admin\View\Configuration\Html $this */
\AkeebaFEFHelper::loadFEFScript('Tooltip');

\Joomla\CMS\HTML\HTMLHelper::_('jquery.framework');
\Joomla\CMS\HTML\HTMLHelper::_('script', 'jui/cms.js', array('version' => 'auto', 'relative' => true));
?>
{{-- Configuration Wizard pop-up --}}
@if($this->promptForConfigurationWizard)
	@include('admin:com_akeeba/Configuration/confwiz_modal')
@endif

{{-- Modal dialog prototypes --}}
@include('admin:com_akeeba/CommonTemplates/FTPBrowser')
@include('admin:com_akeeba/CommonTemplates/SFTPBrowser')
@include('admin:com_akeeba/CommonTemplates/FTPConnectionTest')
@include('admin:com_akeeba/CommonTemplates/ErrorModal')
@include('admin:com_akeeba/CommonTemplates/FolderBrowser')

@if($this->secureSettings == 1)
    <div class="akeeba-block--success">
		@lang('COM_AKEEBA_CONFIG_UI_SETTINGS_SECURED')
    </div>
@elseif($this->secureSettings == 0)
    <div class="akeeba-block--failure">
		@lang('COM_AKEEBA_CONFIG_UI_SETTINGS_NOTSECURED')
    </div>
@endif

@include('admin:com_akeeba/CommonTemplates/ProfileName')

<div class="akeeba-block--info">
	@lang('COM_AKEEBA_CONFIG_WHERE_ARE_THE_FILTERS')
</div>

<form name="adminForm" id="adminForm" method="post" action="index.php?option=com_akeeba&view=configuration"
      class="akeeba-form--horizontal akeeba-form--with-hidden akeeba-form--configuration">

    <div class="akeeba-panel--info" style="margin-bottom: -1em">
        <header class="akeeba-block-header">
            <h5>
                @lang('COM_AKEEBA_PROFILES_LABEL_DESCRIPTION')
            </h5>
        </header>

        <div class="akeeba-form-group">
            <label for="profilename" rel="popover"
                   data-original-title="@lang('COM_AKEEBA_PROFILES_LABEL_DESCRIPTION')"
                   data-content="@lang('COM_AKEEBA_PROFILES_LABEL_DESCRIPTION_TOOLTIP')">
				@lang('COM_AKEEBA_PROFILES_LABEL_DESCRIPTION')
            </label>
            <input type="text" name="profilename" id="profilename"
                   value="{{{ $this->profileName }}}"/>
        </div>

        <div class="akeeba-form-group">
            <label class="control-label" for="quickicon" rel="popover"
                   data-original-title="@lang('COM_AKEEBA_CONFIG_QUICKICON_LABEL')"
                   data-content="@lang('COM_AKEEBA_CONFIG_QUICKICON_DESC')">
				@lang('COM_AKEEBA_CONFIG_QUICKICON_LABEL')
            </label>
            <div>
                <input type="checkbox" name="quickicon"
                       id="quickicon" {{ $this->quickIcon ? 'checked="checked"' : '' }}/>
            </div>
        </div>
    </div>

    <!-- This div contains dynamically generated user interface elements -->
    <div id="akeebagui">
    </div>

    <div class="akeeba-hidden-fields-container">
        <input type="hidden" name="task" value=""/>
        <input type="hidden" name="@token(true)" value="1"/>
    </div>
</form>
PK�!]����	�	tmpl/Log/default.blade.phpnu&1i�<?php
/**
 * @package   akeebabackup
 * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

defined('_JEXEC') || die();

/** @var  \Akeeba\Backup\Admin\View\Log\Html  $this */

?>
@jhtml('formbehavior.chosen')

@if(isset($this->logs) && count($this->logs))
<form name="adminForm" id="adminForm" action="index.php" method="post" class="akeeba-form--inline">
    <div class="akeeba-form-group">
        <label for="tag">@lang('COM_AKEEBA_LOG_CHOOSE_FILE_TITLE')</label>

        {{-- Joomla 3.x: Chosen does not work with attached event handlers, only with inline event scripts (e.g. onchange) --}}
        @jhtml('select.genericlist', $this->logs, 'tag', ['list.select' => $this->tag, 'list.attr' => ['class' => 'advancedSelect', 'onchange' => 'document.forms.adminForm.submit();'], 'id' => 'comAkeebaLogTagSelector'])
    </div>

	@if(!empty($this->tag))
        <div class="akeeba-form-group--actions">
            <a class="akeeba-btn--primary" href="{{{ JUri::base() }}}index.php?option=com_akeeba&view=Log&task=download&tag={{{ $this->tag }}}">
                <span class="akion-ios-download"></span>
		        @lang('COM_AKEEBA_LOG_LABEL_DOWNLOAD')
            </a>
        </div>
	@endif

    <div class="akeeba-hidden-fields-container">
        <input name="option" value="com_akeeba" type="hidden" />
        <input name="view" value="Log" type="hidden" />
        <input type="hidden" name="@token(true)" value="1" />
    </div>

</form>
@endif

@if(!empty($this->tag))
    @if ($this->logTooBig)
        <div class="akeeba-block--warning">
            <p>
                @sprintf('COM_AKEEBA_LOG_SIZE_WARNING', number_format($this->logSize / (1024 * 1024), 2))
            </p>
            <a class="akeeba-btn--dark" id="showlog" href="#">
                @lang('COM_AKEEBA_LOG_SHOW_LOG')
            </a>
        </div>
    @endif

    <div id="iframe-holder" class="akeeba-panel--primary" style="display: {{ $this->logTooBig ? 'none' : 'block' }};">
		@if(!$this->logTooBig)
            <iframe
                src="index.php?option=com_akeeba&view=Log&task=iframe&format=raw&tag={{ urlencode($this->tag) }}"
                width="99%" height="400px">
            </iframe>
		@endif
    </div>
@endif

@if( ! (isset($this->logs) && count($this->logs)))
<div class="akeeba-block--failure">
	@lang('COM_AKEEBA_LOG_NONE_FOUND')
</div>
@endif
PK�!]<Z�__tmpl/Log/raw.blade.phpnu&1i�<?php
/**
 * @package   akeebabackup
 * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

defined('_JEXEC') || die();

use Akeeba\Engine\Factory;
use Joomla\CMS\Language\Text;

/** @var  \Akeeba\Backup\Admin\View\Log\Raw $this */

// -- Get the log's file name
$tag     = $this->tag;
$logFile = Factory::getLog()->getLogFilename($tag);

if (!@is_file($logFile) && @file_exists(substr($logFile, 0, -4)))
{
	/**
	 * Transitional period: the log file akeeba.tag.log.php may not exist but the akeeba.tag.log does. This
	 * addresses this transition.
	 */
	$logFile = substr($logFile, 0, -4);
}

@ob_end_clean();

if (!@file_exists($logFile))
{
	// Oops! The log doesn't exist!
	echo '<p>' . Text::_('COM_AKEEBA_LOG_ERROR_LOGFILENOTEXISTS') . '</p>';

	return;
}
else
{
	// Allright, let's load and render it
	$fp = fopen($logFile, "r");
	if ($fp === FALSE)
	{
		// Oops! The log isn't readable?!
		echo '<p>' . Text::_('COM_AKEEBA_LOG_ERROR_UNREADABLE') . '</p>';

		return;
	}

	while (!feof($fp))
	{
		$line = fgets($fp);
		if (!$line) return;
		$exploded = explode("|", $line, 3);
		unset($line);
		if (count($exploded) < 3) continue;
		switch (trim($exploded[0]))
		{
			case "ERROR":
				$fmtString = "<span style=\"color: red; font-weight: bold;\">[";
				break;
			case "WARNING":
				$fmtString = "<span style=\"color: #D8AD00; font-weight: bold;\">[";
				break;
			case "INFO":
				$fmtString = "<span style=\"color: black;\">[";
				break;
			case "DEBUG":
				$fmtString = "<span style=\"color: #666666; font-size: small;\">[";
				break;
			default:
				$fmtString = "<span style=\"font-size: small;\">[";
				break;
		}
		$fmtString .= $exploded[1] . "] " . htmlspecialchars($exploded[2]) . "</span><br/>\n";
		unset($exploded);
		echo $fmtString;
		unset($fmtString);
	}
}

@ob_start();
PK�!]J6p).).Toolbar/Toolbar.phpnu&1i�<?php
/**
 * @package   akeebabackup
 * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

namespace Akeeba\Backup\Admin\Toolbar;

// Protect from unauthorized access
defined('_JEXEC') || die();

use FOF40\Toolbar\Toolbar as BaseToolbar;
use Joomla\CMS\Language\Text;
use Joomla\CMS\Toolbar\Toolbar as JToolbar;
use Joomla\CMS\Toolbar\ToolbarHelper;

class Toolbar extends BaseToolbar
{
	static $isJoomla3 = null;

	public function onAlices()
	{
		$this->setTitle('COM_AKEEBA_TITLE_ALICES');
		$this->backButton('JTOOLBAR_BACK', 'index.php?option=com_akeeba&view=ControlPanel');
	}

	public function onBackupsMain()
	{
		$this->setTitle('COM_AKEEBA_BACKUP');

		if (!$this->container->input->getBool('akeeba_hide_toolbar', false))
		{
			$this->backButton('COM_AKEEBA_CONTROLPANEL', 'index.php?option=com_akeeba');

			ToolbarHelper::spacer();
			ToolbarHelper::help('', false, 'https://www.akeeba.com/documentation/akeeba-backup-documentation/backup-now.html');
		}
	}

	public function onConfigurations()
	{
		$bar = JToolbar::getInstance('toolbar');

		$this->setTitle('COM_AKEEBA_CONFIG');

		ToolbarHelper::preferences('com_akeeba', '500', '660');
		ToolbarHelper::spacer();
		ToolbarHelper::apply();
		ToolbarHelper::save();
		ToolbarHelper::spacer();
		ToolbarHelper::custom('savenew', 'save-new.png', 'save-new_f2.png', 'JTOOLBAR_SAVE_AND_NEW', false);
		ToolbarHelper::cancel();
		ToolbarHelper::spacer();

		// Configuration wizard button. We apply styling to it.
		$bar->appendButton('Link', 'lightning', '<strong>' . Text::_('COM_AKEEBA_CONFWIZ') . '</strong>', 'index.php?option=com_akeeba&view=ConfigurationWizard');

		ToolbarHelper::spacer();

		if (AKEEBA_PRO)
		{
			$bar->appendButton('Link', 'calendar', Text::_('COM_AKEEBA_SCHEDULE'), 'index.php?option=com_akeeba&view=Schedule');
		}

		ToolbarHelper::spacer();
		ToolbarHelper::help('', false, 'https://www.akeeba.com/documentation/akeeba-backup-documentation/configuration.html');

		$js = <<< JS
akeeba.Loader.add('akeeba.System', function(){
    akeeba.System.documentReady(function(){
	    var elButtons = document.querySelectorAll('#toolbar-lightning>button');
	    akeeba.System.iterateNodes(elButtons, function (elButton) {
			akeeba.System.addClass(elButton, 'btn-primary');        
	    });
    });
});

JS;
		$this->container->template->addJSInline($js);
	}

	public function onConfigurationWizardsMain()
	{
		$this->setTitle('COM_AKEEBA_CONFWIZ');
		$this->backButton('COM_AKEEBA_CONTROLPANEL', 'index.php?option=com_akeeba');
		ToolbarHelper::spacer();
		ToolbarHelper::help('', false, 'https://www.akeeba.com/documentation/akeeba-backup-documentation/configuration-wizard.html');
	}

	public function onControlPanelsMain()
	{
		$this->setTitle('COM_AKEEBA_CONTROLPANEL');
		ToolbarHelper::preferences('com_akeeba', '500', '660');
		ToolbarHelper::spacer();
		ToolbarHelper::help('', false, 'https://www.akeeba.com/documentation/akeeba-backup-documentation/control-panel.html');
	}

	public function onDatabaseFiltersMain()
	{
		$this->setTitle('COM_AKEEBA_DBFILTER');
		$this->backButton('COM_AKEEBA_CONTROLPANEL', 'index.php?option=com_akeeba');
		ToolbarHelper::spacer();
		ToolbarHelper::help('', false, 'https://www.akeeba.com/documentation/akeeba-backup-documentation/database-tables-exclusion.html');
	}

	public function onDiscovers()
	{
		$this->setTitle('COM_AKEEBA_DISCOVER');

		$this->backButton('COM_AKEEBA_CONTROLPANEL', 'index.php?option=com_akeeba');
		ToolbarHelper::spacer();
		ToolbarHelper::help('', false, 'https://www.akeeba.com/documentation/akeeba-backup-documentation/discover-import-archives.html');
	}

	public function onFileFiltersMain()
	{
		$this->setTitle('COM_AKEEBA_FILEFILTERS');

		ToolbarHelper::title(Text::_('COM_AKEEBA') . ': <small>' . Text::_('COM_AKEEBA_FILEFILTERS') . '</small>', 'akeeba');
		$this->backButton('COM_AKEEBA_CONTROLPANEL', 'index.php?option=com_akeeba');
		ToolbarHelper::spacer();
		ToolbarHelper::help('', false, 'https://www.akeeba.com/documentation/akeeba-backup-documentation/exclude-data-from-backup.html#files-and-directories-exclusion');
	}

	public function onIncludeFoldersMain()
	{
		$this->setTitle('COM_AKEEBA_INCLUDEFOLDER');

		$this->backButton('COM_AKEEBA_CONTROLPANEL', 'index.php?option=com_akeeba');
		ToolbarHelper::spacer();
		ToolbarHelper::help('', false, 'https://www.akeeba.com/documentation/akeeba-backup-documentation/off-site-directories-inclusion.html');
	}

	public function onLogs()
	{
		$this->setTitle('COM_AKEEBA_LOG');

		$this->backButton('COM_AKEEBA_CONTROLPANEL', 'index.php?option=com_akeeba');
		ToolbarHelper::spacer();
		ToolbarHelper::help('', false, 'https://www.akeeba.com/documentation/akeeba-backup-documentation/view-log.html');
	}

	public function onManagesDefault()
	{
		$this->setTitle('COM_AKEEBA_BUADMIN');

		if (AKEEBA_PRO)
		{
			$bar  = JToolbar::getInstance('toolbar');
			$icon = $this->isJoomla3() ? 'folder-open' : 'search';
			$bar->appendButton('Link', $icon, Text::_('COM_AKEEBA_DISCOVER'), 'index.php?option=com_akeeba&view=Discover');
		}

		$user        = $this->container->platform->getUser();
		$permissions = [
			'configure' => $user->authorise('akeeba.configure', 'com_akeeba'),
			'backup'    => $user->authorise('akeeba.backup', 'com_akeeba'),
		];

		if ($permissions['configure'] && AKEEBA_PRO)
		{
			ToolbarHelper::publish('restore', Text::_('COM_AKEEBA_BUADMIN_LABEL_RESTORE'));
		}

		if ($permissions['backup'])
		{
			ToolbarHelper::editList('showcomment', Text::_('COM_AKEEBA_BUADMIN_LOG_EDITCOMMENT'));
		}

		if ($permissions['configure'] || $permissions['backup'])
		{
			ToolbarHelper::spacer();
		}

		if ($permissions['backup'])
		{
			ToolbarHelper::deleteList();
			ToolbarHelper::custom('deletefiles', 'delete.png', 'delete_f2.png', Text::_('COM_AKEEBA_BUADMIN_LABEL_DELETEFILES'), true);
			ToolbarHelper::spacer();
		}

		$this->backButton('COM_AKEEBA_CONTROLPANEL', 'index.php?option=com_akeeba');
		ToolbarHelper::spacer();
		ToolbarHelper::help('', false, 'https://www.akeeba.com/documentation/akeeba-backup-documentation/adminsiter-backup-files.html');
	}

	public function onManagesShowcomment()
	{
		$this->setTitle('COM_AKEEBA_BUADMIN');

		$this->backButton('COM_AKEEBA_CONTROLPANEL', 'index.php?option=com_akeeba');
		ToolbarHelper::save();
		ToolbarHelper::cancel();
		ToolbarHelper::spacer();
		ToolbarHelper::help('', false, 'https://www.akeeba.com/documentation/akeeba-backup-documentation/adminsiter-backup-files.html');
	}

	public function onMultipleDatabasesMain()
	{
		$this->setTitle('COM_AKEEBA_MULTIDB');

		$this->backButton('COM_AKEEBA_CONTROLPANEL', 'index.php?option=com_akeeba');
		ToolbarHelper::spacer();
		ToolbarHelper::help('', false, 'https://www.akeeba.com/documentation/akeeba-backup-documentation/include-data-to-archive.html#multiple-db-definitions');
	}

	public function onProfilesAdd()
	{
		parent::onAdd();

		$this->setTitle('COM_AKEEBA_PROFILES_PAGETITLE_NEW');

		ToolbarHelper::spacer();
		ToolbarHelper::help('', false, 'https://www.akeeba.com/documentation/akeeba-backup-documentation/using-basic-operations.html#profiles-management');
	}

	public function onProfilesBrowse()
	{
		$this->setTitle('COM_AKEEBA_PROFILES');

		$this->backButton('COM_AKEEBA_CONTROLPANEL', 'index.php?option=com_akeeba');
		ToolbarHelper::spacer();
		ToolbarHelper::addNew();
		ToolbarHelper::custom('copy', 'copy.png', 'copy_f2.png', 'COM_AKEEBA_LBL_BATCH_COPY', false);
		ToolbarHelper::spacer();
		ToolbarHelper::deleteList();
		ToolbarHelper::spacer();
		ToolbarHelper::spacer();
		ToolbarHelper::help('', false, 'https://www.akeeba.com/documentation/akeeba-backup-documentation/using-basic-operations.html#profiles-management');
	}

	public function onProfilesEdit()
	{
		parent::onEdit();

		$this->setTitle('COM_AKEEBA_PROFILES_PAGETITLE_EDIT');

		ToolbarHelper::spacer();
		ToolbarHelper::help('', false, 'https://www.akeeba.com/documentation/akeeba-backup-documentation/using-basic-operations.html#profiles-management');
	}

	public function onRegExDatabaseFiltersMain()
	{
		$this->setTitle('COM_AKEEBA_REGEXDBFILTERS');

		$this->backButton('COM_AKEEBA_CONTROLPANEL', 'index.php?option=com_akeeba');
		ToolbarHelper::spacer();
		ToolbarHelper::help('', false, 'https://www.akeeba.com/documentation/akeeba-backup-documentation/regex-database-tables-exclusion.html');
	}

	public function onRegExFileFiltersMain()
	{
		$this->setTitle('COM_AKEEBA_REGEXFSFILTERS');

		$this->backButton('COM_AKEEBA_CONTROLPANEL', 'index.php?option=com_akeeba');
		ToolbarHelper::spacer();
		ToolbarHelper::help('', false, 'https://www.akeeba.com/documentation/akeeba-backup-documentation/regex-files-directories-exclusion.html');
	}

	public function onRemoteFilesDownloadToServer()
	{
		$this->setTitle('COM_AKEEBA_REMOTEFILES');

		ToolbarHelper::spacer();
		ToolbarHelper::help('', false, 'https://www.akeeba.com/documentation/akeeba-backup-documentation/ch03s03s05s02.html');
	}

	public function onRemoteFilesListActions()
	{
		$this->setTitle('COM_AKEEBA_REMOTEFILES');

		ToolbarHelper::spacer();
		ToolbarHelper::help('', false, 'https://www.akeeba.com/documentation/akeeba-backup-documentation/manage-remotely-stored-files.html');
	}

	public function onRestores()
	{
		$this->setTitle('COM_AKEEBA_RESTORE');

		$this->backButton('COM_AKEEBA_CONTROLPANEL', 'index.php?option=com_akeeba');
		ToolbarHelper::spacer();
		ToolbarHelper::help('', false, 'https://www.akeeba.com/documentation/akeeba-backup-documentation/adminsiter-backup-files.html#integrated-restoration');
	}

	public function onS3ImportsMain()
	{
		$this->setTitle('COM_AKEEBA_S3IMPORT');

		$this->backButton('COM_AKEEBA_CONTROLPANEL', 'index.php?option=com_akeeba');
		ToolbarHelper::spacer();
		ToolbarHelper::help('', false, 'https://www.akeebabackup .com/documentation/akeeba-backup-documentation/import-s3.html');
	}

	public function onS3ImportsDltoserver()
	{
		$this->setTitle('COM_AKEEBA_S3IMPORT');

		$this->backButton('COM_AKEEBA_CONTROLPANEL', 'index.php?option=com_akeeba');
		ToolbarHelper::spacer();
		ToolbarHelper::help('', false, 'https://www.akeebabackup .com/documentation/akeeba-backup-documentation/import-s3.html');
	}

	public function onSchedules()
	{
		$this->setTitle('COM_AKEEBA_SCHEDULE');

		$this->backButton('COM_AKEEBA_CONTROLPANEL', 'index.php?option=com_akeeba');
		ToolbarHelper::spacer();
		ToolbarHelper::help('', false, 'https://www.akeeba.com/documentation/akeeba-backup-documentation/automating-your-backup.html');
	}

	public function onStatisticsMain()
	{
		$this->onManagesDefault();
	}

	public function onTransfers()
	{
		$this->setTitle('COM_AKEEBA_TRANSFER');

		$this->backButton('COM_AKEEBA_CONTROLPANEL', 'index.php?option=com_akeeba');

		$bar  = JToolbar::getInstance('toolbar');
		$icon = $this->isJoomla3() ? 'loop' : 'refresh';
		$bar->appendButton('Link', $icon, 'COM_AKEEBA_TRANSFER_BTN_RESET', 'index.php?option=com_akeeba&view=Transfer&task=reset');
	}

	protected function isJoomla3()
	{
		if (is_null(self::$isJoomla3))
		{
			self::$isJoomla3 = version_compare(JVERSION, '3.999.999', 'lt');
		}

		return self::$isJoomla3;
	}

	protected function setTitle($viewTitle)
	{
		$title = Text::_('COM_AKEEBA');

		if ($this->isJoomla3())
		{
			$icon  = 'akeeba';
			$title .= ' <span style="display: none;"> - </span><small>';
		}
		else
		{
			$icon  = 'akeeba-j4';
			$title .= "<small> – ";
		}

		$title .= Text::_($viewTitle) . "</small>";

		ToolbarHelper::title($title, $icon);
	}

	protected function backButton($label, $link)
	{
		if ($this->isJoomla3())
		{
			ToolbarHelper::back($label, $link);

			return;
		}

		$bar = JToolbar::getInstance('toolbar');
		$bar->appendButton('Link', 'chevron-left', $label, $link);
	}
}
PK�!]��ѡ��Toolbar/.htaccessnu&1i�<IfModule !mod_authz_core.c>
Order deny,allow
Deny from all
</IfModule>
<IfModule mod_authz_core.c>
  <RequireAll>
    Require all denied
  </RequireAll>
</IfModule>
PK�!]|��NToolbar/web.confignu&1i�<?xml version="1.0"?>
<!--
    This only works on IIS 7 or later. See https://www.iis.net/configreference/system.webserver/security/requestfiltering/fileextensions
-->
<configuration>
    <system.webServer>
        <security>
            <requestFiltering>
                <fileExtensions allowUnlisted="false" >
                    <clear />
                    <add fileExtension=".html" allowed="true"/>
                </fileExtensions>
            </requestFiltering>
        </security>
    </system.webServer>
</configuration>PK�!]��3OOModel/SFTPBrowser.phpnu&1i�<?php
/**
 * @package   akeebabackup
 * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

namespace Akeeba\Backup\Admin\Model;

// Protect from unauthorized access
defined('_JEXEC') || die();

use FOF40\Model\Model;
use Joomla\CMS\Language\Text;
use RuntimeException;

class SFTPBrowser extends Model
{
	/**
	 * The SFTP server hostname
	 *
	 * @var  string
	 */
	public $host = '';

	/**
	 * The SFTP server port number (default: 22)
	 *
	 * @var  int
	 */
	public $port = 22;

	/**
	 * Username for logging in
	 *
	 * @var  string
	 */
	public $username = '';

	/**
	 * Password for logging in
	 *
	 * @var  string
	 */
	public $password = '';

	/**
	 * Private key file for connection
	 *
	 * @var  string
	 */
	public $privkey = '';

	/**
	 * Public key file for connection
	 *
	 * @var string
	 */
	public $pubkey = '';

	/**
	 * The directory to browse
	 *
	 * @var  string
	 */
	public $directory = '';

	/**
	 * Breadcrumbs to the current directory
	 *
	 * @var  array
	 */
	public $parts = [];

	/**
	 * Path to the parent directory
	 *
	 * @var  string
	 */
	public $parent_directory = null;

	/**
	 * Gets the folders contained in the remote FTP root directory defined in $this->directory
	 *
	 * @return  array
	 */
	public function getListing()
	{
		$dir = $this->directory;

		// Parse directory to parts
		$parsed_dir  = trim($dir, '/');
		$this->parts = empty($parsed_dir) ? [] : explode('/', $parsed_dir);

		// Find the path to the parent directory
		$this->parent_directory = '';

		if (!empty($this->parts))
		{
			$copy_of_parts = $this->parts;
			array_pop($copy_of_parts);

			$this->parent_directory = '/';

			if (!empty($copy_of_parts))
			{
				$this->parent_directory = '/' . implode('/', $copy_of_parts);
			}
		}

		// Initialise
		$connection = null;
		$sftphandle = null;

		// Open a connection
		if (!function_exists('ssh2_connect'))
		{
			throw new RuntimeException("Your web server does not have the SSH2 PHP module, therefore can not connect and upload archives to SFTP servers.");
		}

		$connection = ssh2_connect($this->host, $this->port);

		if ($connection === false)
		{
			throw new RuntimeException("Invalid SFTP hostname or port ({$this->host}:{$this->port}) or the connection is blocked by your web server's firewall.");
		}

		// Connect to the server

		if (!empty($this->pubkey) && !empty($this->privkey))
		{
			if (!ssh2_auth_pubkey_file($connection, $this->username, $this->pubkey, $this->privkey, $this->password))
			{
				throw new RuntimeException('Certificate error');
			}
		}
		else
		{
			if (!ssh2_auth_password($connection, $this->username, $this->password))
			{
				throw new RuntimeException('Could not authenticate access to SFTP server; check your username and password.');
			}
		}

		$sftphandle = ssh2_sftp($connection);

		if ($sftphandle === false)
		{
			throw new RuntimeException("Your SSH server does not allow SFTP connections");
		}

		// Get a raw directory listing (hoping it's a UNIX server!)
		$list = [];
		$dir  = ltrim($dir, '/');

		if (empty($dir))
		{
			$dir = ssh2_sftp_realpath($sftphandle, ".");

			$this->directory = $dir;

			// Parse directory to parts
			$parsed_dir  = trim($dir, '/');
			$this->parts = empty($parsed_dir) ? [] : explode('/', $parsed_dir);

			// Find the path to the parent directory
			$this->parent_directory = '';

			if (!empty($this->parts))
			{
				$copy_of_parts = $this->parts;
				array_pop($copy_of_parts);

				$this->parent_directory = '/';

				if (!empty($copy_of_parts))
				{
					$this->parent_directory = '/' . implode('/', $copy_of_parts);
				}
			}
		}

		$handle = opendir("ssh2.sftp://$sftphandle/$dir");

		if (!is_resource($handle))
		{
			throw new RuntimeException(Text::_('COM_AKEEBA_SFTPBROWSER_ERROR_NOACCESS'));
		}

		while (($entry = readdir($handle)) !== false)
		{
			if (substr($entry, 0, 1) == '.')
			{
				continue;
			}

			if (!is_dir("ssh2.sftp://$sftphandle/$dir/$entry"))
			{
				continue;
			}

			$list[] = $entry;
		}

		closedir($handle);

		if (!empty($list))
		{
			asort($list);
		}

		return $list;
	}

	/**
	 * Perform the actual folder browsing. Returns an array that's usable by the UI.
	 *
	 * @return  array
	 */
	public function doBrowse()
	{
		$error = '';
		$list  = [];

		try
		{
			$list = $this->getListing();
		}
		catch (RuntimeException $e)
		{
			$error = $e->getMessage();
		}

		$response_array = [
			'error'       => $error,
			'list'        => $list,
			'breadcrumbs' => $this->parts,
			'directory'   => $this->directory,
			'parent'      => $this->parent_directory,
		];

		return $response_array;
	}
}
PK�!]h�]]'Model/Exceptions/TransferFatalError.phpnu&1i�<?php
/**
 * @package   akeebabackup
 * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

namespace Akeeba\Backup\Admin\Model\Exceptions;

// Protect from unauthorized access
defined('_JEXEC') || die();

class TransferFatalError extends \RuntimeException
{

}
PK�!]��Q�\\&Model/Exceptions/FrozenRecordError.phpnu&1i�<?php
/**
 * @package   akeebabackup
 * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

namespace Akeeba\Backup\Admin\Model\Exceptions;

// Protect from unauthorized access
defined('_JEXEC') || die();

class FrozenRecordError extends \RuntimeException
{

}
PK�!]��8[aa+Model/Exceptions/TransferIgnorableError.phpnu&1i�<?php
/**
 * @package   akeebabackup
 * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

namespace Akeeba\Backup\Admin\Model\Exceptions;

// Protect from unauthorized access
defined('_JEXEC') || die();

class TransferIgnorableError extends \RuntimeException
{

}
PK�!]|��NModel/web.confignu&1i�<?xml version="1.0"?>
<!--
    This only works on IIS 7 or later. See https://www.iis.net/configreference/system.webserver/security/requestfiltering/fileextensions
-->
<configuration>
    <system.webServer>
        <security>
            <requestFiltering>
                <fileExtensions allowUnlisted="false" >
                    <clear />
                    <add fileExtension=".html" allowed="true"/>
                </fileExtensions>
            </requestFiltering>
        </security>
    </system.webServer>
</configuration>PK�!]���t>t>Model/ConfigurationWizard.phpnu&1i�<?php
/**
 * @package   akeebabackup
 * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

namespace Akeeba\Backup\Admin\Model;

// Protect from unauthorized access
defined('_JEXEC') || die();

use Akeeba\Backup\Admin\Model\Mixin\Chmod;
use Akeeba\Engine\Factory;
use Akeeba\Engine\Platform;
use FOF40\Model\Model;

/**
 * ConfigurationWizard model. Contains the business logic for the configuration wizard.
 */
class ConfigurationWizard extends Model
{
	use Chmod;

	/**
	 * Attempts to automatically figure out where the output and temporary directories should point, adjusting their
	 * permissions should it be necessary.
	 *
	 * @param   bool  $dontRecurse  Used internally. Always skip this parameter when calling this method.
	 *
	 * @return  bool  True if we could fix the directories
	 */
	public function autofixDirectories(bool $dontRecurse = false)
	{
		// Get the output directory, translated
		$engineConfig    = Factory::getConfiguration();
		$outputDirectory = $engineConfig->get('akeeba.basic.output_directory', '');
		$fixOut          = true;

		// If no output directory is specified set it the default output and retry.
		if (empty($outputDirectory) && !$dontRecurse)
		{
			/** @var @var Configuration $model $model */
			$model = $this->container->factory->model('Configuration')->tmpInstance();

			$model->setState('engineconfig', [
				'akeeba.basic.output_directory' => '[DEFAULT_OUTPUT]',
			]);
			$model->saveEngineConfig();

			return $this->autofixDirectories(true);
		}

		// Is the folder writeable?
		if (is_dir($outputDirectory))
		{
			$filename = $outputDirectory . '/test.dat';
			$fixOut   = !@file_put_contents($filename, 'test');

			if (!$fixOut)
			{
				// Directory writable, remove the temp file
				@unlink($filename);
			}
		}

		// Do I need to change the permissions?
		if ($fixOut)
		{
			// Try to chmod the directory
			$this->chmod($outputDirectory, 511);

			// Repeat the test
			$filename = $outputDirectory . '/test.dat';
			$fixOut   = !@file_put_contents($filename, 'test');

			if (!$fixOut)
			{
				// Directory writable, remove the temp file
				@unlink($filename);
			}
		}

		/**
		 * If we reached this point after recursion, we can't fix the permissions of the default backup output folder.
		 * The user has to manually select a writeable backup output directory (or make the default otuput writeable).
		 */
		if ($fixOut && $dontRecurse)
		{
			return false;
		}

		/**
		 * Write the output folder through the Configuration model. This ensures that:
		 *
		 * - we are not trying to use the site's root as the output folder.
		 * - the output folder saved in the database contains an abstracted representation of the folder, using path
		 *   variables instead of absolute filesystem folders.
		 *
		 * @var Configuration $model
		 */
		$model = $this->container->factory->model('Configuration')->tmpInstance();

		// Do I have to fall back to the default output directory?
		$outputDirectory = $fixOut ? '[DEFAULT_OUTPUT]' : $outputDirectory;

		$model->setState('engineconfig', [
			'akeeba.basic.output_directory' => $outputDirectory,
		]);
		$model->saveEngineConfig();

		/**
		 * If we had to revert to the default output we will run ourselves again to make sure that the default backup
		 * output folder is, in fact, writeable.
		 */
		if ($fixOut)
		{
			return $this->autofixDirectories(true);
		}

		return true;
	}

	/**
	 * Creates a temporary file of a specific size
	 *
	 * @param   int          $blocks How many 128Kb blocks to write. Common values: 1, 2, 4, 16, 40, 80, 81
	 * @param   string|null  $tempdir
	 *
	 * @return  bool  TRUE on success
	 */
	public function createTempFile($blocks = 1, $tempdir = null)
	{
		if (empty($tempdir))
		{
			$aeconfig = Factory::getConfiguration();
			$tempdir  = $aeconfig->get('akeeba.basic.output_directory', '');
		}

		$sixtyfourBytes = '012345678901234567890123456789012345678901234567890123456789ABCD';
		$oneKilo        = '';
		$oneBlock       = '';

		for ($i = 0; $i < 16; $i ++)
		{
			$oneKilo .= $sixtyfourBytes;
		}

		for ($i = 0; $i < 128; $i ++)
		{
			$oneBlock .= $oneKilo;
		}

		$filename = tempnam($tempdir, 'confwiz');
		@unlink($filename);

		$fp = @fopen($filename, 'w');

		if ($fp !== false)
		{
			for ($i = 0; $i < $blocks; $i ++)
			{
				if (!@fwrite($fp, $oneBlock))
				{
					@fclose($fp);
					@unlink($filename);

					return false;
				}
			}

			@fclose($fp);
			@unlink($filename);
		}
		else
		{
			return false;
		}

		return true;
	}

	/**
	 * Sleeps for a given amount of time. Returns false if the sleep time requested is over the maximum execution time.
	 *
	 * @param   int  $secondsDelay  Seconds to sleep
	 *
	 * @return  bool  FALSE if we cannot sleep that long
	 */
	public function doNothing($secondsDelay = 1)
	{
		// Try to get the maximum execution time and PHP memory limit
		if (function_exists('ini_get'))
		{
			$maxexec  = ini_get("max_execution_time");
			$memlimit = ini_get("memory_limit");
		}
		else
		{
			$maxexec  = 14;
			$memlimit = 16777216;
		}

		// Unknown time limit; suppose 10s
		if (!is_numeric($maxexec) || ($maxexec == 0))
		{
			$maxexec = 10;
		}

		// Some servers report silly values, i.e. 30000, which Do Not Work™ :(
		if ($maxexec > 180)
		{
			$maxexec = 10;
		}

		// Sometimes memlimit comes with the M or K suffixes. Parse them.
		if (is_string($memlimit))
		{
			$memlimit = strtoupper(trim(str_replace(' ', '', $memlimit)));

			if (substr($memlimit, - 1) == 'K')
			{
				$memlimit = 1024 * substr($memlimit, 0, - 1);
			}
			elseif (substr($memlimit, - 1) == 'M')
			{
				$memlimit = 1024 * 1024 * substr($memlimit, 0, - 1);
			}
			elseif (substr($memlimit, - 1) == 'G')
			{
				$memlimit = 1024 * 1024 * 1024 * substr($memlimit, 0, - 1);
			}
		}

		// Unknown limit; suppose 16M
		if (!is_numeric($memlimit) || ($memlimit === 0))
		{
			$memlimit = 16777216;
		}

		// No limit; suppose 128M
		if ($memlimit === - 1)
		{
			$memlimit = 134217728;
		}

		// Get the current memory usage (or assume one if the metric is not available)
		if (function_exists('memory_get_usage'))
		{
			$usedram = memory_get_usage();
		}
		else
		{
			$usedram = 7340032; // Suppose 7M of RAM usage if the metric isn't available;
		}

		// If we have less than 12M of RAM left, we have to limit ourselves to 6 seconds of
		// total execution time (emperical value!) to avoid deadly memory outages
		if (($memlimit - $usedram) < 12582912)
		{
			$maxexec = 5;
		}

		// If the requested delay is over the $maxexec limit (minus one second
		// for application initialization), return false
		if ($secondsDelay > ($maxexec - 1))
		{
			return false;
		}

		// And now, run the silly loop to simulate the CPU usage pattern during backup
		$start = microtime(true);
		$loop  = true;

		while ($loop)
		{
			// Waste some CPU power...
			for ($i = 1; $i < 1000; $i ++)
			{
				$j = exp((int)($i * $i / 123 * 864) >> 2);
			}

			// ... then sleep for a millisec
			usleep(1000);

			// Are we done yet?
			$end = microtime(true);

			if (($end - $start) >= $secondsDelay)
			{
				$loop = false;
			}
		}

		return true;
	}

	/**
	 * This method will analyze your database tables and try to figure out the optimal batch row count value so that its
	 * SELECT doesn't return excessive amounts of data. The only drawback is that it only accounts for the core tables,
	 * but that is usually a good metric.
	 *
	 * @return  void
	 */
	public function analyzeDatabase()
	{
		// Try to get the PHP memory limit
		if (function_exists('ini_get'))
		{
			$memlimit = ini_get("memory_limit");
		}
		else
		{
			$memlimit = 16777216;
		}

		if (!is_numeric($memlimit) || ($memlimit === 0))
		{
			$memlimit = 16777216; // Unknown limit; suppose 16M
		}

		if ($memlimit === - 1)
		{
			$memlimit = 134217728; // No limit; suppose 128M
		}

		// Get the current memory usage (or assume one if the metric is not available)
		if (function_exists('memory_get_usage'))
		{
			$usedram = memory_get_usage();
		}
		else
		{
			$usedram = 7340032; // Suppose 7M of RAM usage if the metric isn't available;
		}

		// How much RAM can I spare? It's the max memory minus the current memory usage and an extra
		// 5Mb to cater for Akeeba Engine's peak memory usage
		$max_mem_usage = $usedram + 5242880;
		$ram_allowance = $memlimit - $max_mem_usage;

		// If the RAM allowance is too low, assume 2Mb (emperical value)
		if ($ram_allowance < 2097152)
		{
			$ram_allowance = 2097152;
		}

		// If SHOW TABLE STATUS is not supported this is a safe-ish value.
		$rowCount = 100;

		// Get the table statistics
		$db = $this->container->db;

		if (strtolower(substr($db->name, 0, 5)) == 'mysql')
		{
			// The table analyzer only works with MySQL
			$db->setQuery("SHOW TABLE STATUS");

			try
			{
				$metrics = $db->loadAssocList();

				if (method_exists($db, 'getError') && $db->getError())
				{
					$metrics = null;
				}
			}
			catch (\Exception $exc)
			{
				$metrics = null;
			}

			// SHOW TABLE STATUS is supported.
			if (!is_null($metrics))
			{
				$rowCount = 1000; // Start with the default value

				if (!empty($metrics))
				{
					foreach ($metrics as $table)
					{
						// Get row count and average row length
						$rows    = $table['Rows'];
						$avg_len = $table['Avg_row_length'];

						// Calculate RAM usage with current settings
						$max_rows        = min($rows, $rowCount);
						$max_ram_current = $max_rows * $avg_len;

						if ($max_ram_current > $ram_allowance)
						{
							// Hm... over the allowance. Let's try to find a sweet spot.
							$max_rows = (int) ($ram_allowance / $avg_len);
							// Quantize to multiple of 10 rows
							$max_rows = 10 * floor($max_rows / 10);

							// Can't really go below 10 rows / batch
							if ($max_rows < 10)
							{
								$max_rows = 10;
							}

							// If the new setting is less than the current $rowCount, use the new setting
							if ($rowCount > $max_rows)
							{
								$rowCount = $max_rows;
							}
						}
					}
				}
			}
		}

		$profile_id = Platform::getInstance()->get_active_profile();
		$config     = Factory::getConfiguration();

		// Use the correct database dump engine
		$config->set('akeeba.advanced.dump_engine', 'reverse');

		if (strpos($db->name, 'mysql') !== false)
		{
			$config->set('akeeba.advanced.dump_engine', 'native');
		}

		// Save the row count per batch
		$config->set('engine.dump.common.batchsize', $rowCount);

		// Enable SQL file splitting - default is 512K unless the part_size is less than that!
		$splitsize = 524288;
		$partsize  = $config->get('engine.archiver.common.part_size', 0);

		if (($partsize < $splitsize) && !empty($partsize))
		{
			$splitsize = $partsize;
		}

		$config->set('engine.dump.common.splitsize', $splitsize);

		// Enable extended INSERTs
		$config->set('engine.dump.common.extended_inserts', '1');

		// Determine optimal packet size (must be at most two fifths of the split size and no more than 256K)
		$packet_size = (int) $splitsize * 0.4;

		if ($packet_size > 262144)
		{
			$packet_size = 262144;
		}

		$config->set('engine.dump.common.packet_size', $packet_size);

		// Enable the native dump engine
		$config->set('akeeba.advanced.dump_engine', 'native');

		Platform::getInstance()->save_configuration($profile_id);
	}

	/**
	 * Executes the action requested through AJAX
	 *
	 * @return  bool
	 */
	public function runAjax()
	{
		// Only allowed actions
		$allowedActions = [
			'ping', 'minexec', 'applyminexec', 'directories', 'database', 'maxexec', 'applymaxexec', 'partsize', 'flush'
		];

		// Get the requested action from the model state
		$action = $this->getState('act');

		$result = false;

		if (in_array($action, $allowedActions) && method_exists($this, $action))
		{
			$result = call_user_func([$this, $action]);
		}

		return $result;
	}

	/**
	 * Pings the configuration wizard process and marks the current profile as configured
	 *
	 * @return  bool  TRUE, always
	 */
	private function ping()
	{
		// Get the profile ID
		$profile_id = Platform::getInstance()->get_active_profile();

		// Set the embedded installer to the default ANGIE installer
		$engineConfig = Factory::getConfiguration();
		$engineConfig->set('akeeba.advanced.embedded_installer', 'angie');

		// And mark this profile as already configured
		$engineConfig->set('akeeba.flag.confwiz', 1);

		Platform::getInstance()->save_configuration($profile_id);

		return true;
	}

	/**
	 * Try different values of minimum execution time
	 *
	 * @return  bool  TRUE, always
	 */
	private function minexec()
	{
		$seconds = $this->input->get('seconds', '0.5', 'float');

		if ($seconds < 1)
		{
			usleep($seconds * 1000000);
		}
		else
		{
			sleep($seconds);
		}

		return true;
	}

	/**
	 * Saves the AJAX preference and the minimum execution time
	 *
	 * @return  bool  TRUE, always
	 */
	private function applyminexec()
	{
		// Get the user parameters
		$minexec = $this->input->get('minexec', 2.0, 'float');

		// Save the settings
		$profile_id   = Platform::getInstance()->get_active_profile();
		$engineConfig = Factory::getConfiguration();
		$engineConfig->set('akeeba.tuning.min_exec_time', $minexec * 1000);
		Platform::getInstance()->save_configuration($profile_id);

		// Enforce the min exec time
		$timer = Factory::getTimer();
		$timer->enforce_min_exec_time(false);

		// Done!
		return true;
	}

	/**
	 * Try to make the directories writable or provide a set of writable directories
	 *
	 * @return  bool  TRUE if we coud fix the permissions of the directories
	 */
	private function directories()
	{
		$timer  = Factory::getTimer();
		$result = $this->autofixDirectories();
		$timer->enforce_min_exec_time(false);

		return $result;
	}

	/**
	 * Analyze the database and apply optimized database dump settings
	 *
	 * @return  bool  TRUE if we were successful
	 */
	private function database()
	{
		$timer = Factory::getTimer();
		$this->analyzeDatabase();
		$timer->enforce_min_exec_time(false);

		return true;
	}

	/**
	 * Try to apply a specific maximum execution time setting
	 *
	 * @return  bool
	 */
	private function maxexec()
	{
		$seconds = $this->input->get('seconds', 30, 'int');
		$timer   = Factory::getTimer();
		$result  = $this->doNothing($seconds);
		$timer->enforce_min_exec_time(false);

		return $result;
	}

	/**
	 * Save a specific maximum execution time preference to the database
	 *
	 * @return  bool
	 */
	private function applymaxexec()
	{
		// Get the user parameters
		$maxexec = $this->input->get('seconds', 2, 'int');

		// Save the settings
		$timer      = Factory::getTimer();
		$profile_id = Platform::getInstance()->get_active_profile();
		$config     = Factory::getConfiguration();
		$config->set('akeeba.tuning.max_exec_time', $maxexec);
		$config->set('akeeba.tuning.run_time_bias', '75');
		$config->set('akeeba.advanced.scan_engine', 'smart');
		$config->set('akeeba.advanced.archiver_engine', 'jpa');
		Platform::getInstance()->save_configuration($profile_id);

		// Enforce the min exec time
		$timer->enforce_min_exec_time(false);

		// Done!
		return true;
	}

	/**
	 * Creates a dummy file of a given size. Remember to give the filesize query parameter in bytes!
	 *
	 * @return  bool
	 */
	public function partsize()
	{
		$timer  = Factory::getTimer();
		$blocks = $this->input->get('blocks', 1, 'int');

		$result = $this->createTempFile($blocks);

		if ($result)
		{
			// Save the setting
			if ($blocks > 200)
			{
				$blocks = 16383; // Over 25Mb = 2Gb minus 128Kb limit (safe setting for PHP not running on 64-bit Linux)
			}

			$profile_id = Platform::getInstance()->get_active_profile();
			$config     = Factory::getConfiguration();
			$config->set('engine.archiver.common.part_size', $blocks * 128 * 1024);
			Platform::getInstance()->save_configuration($profile_id);
		}

		// Enforce the min exec time
		$timer->enforce_min_exec_time(false);

		return $result;
	}
}
PK�!]6�ب/�/Model/FileFilters.phpnu&1i�<?php
/**
 * @package   akeebabackup
 * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

namespace Akeeba\Backup\Admin\Model;

// Protect from unauthorized access
defined('_JEXEC') || die();

use Akeeba\Backup\Admin\Model\Mixin\ExclusionFilter;
use Akeeba\Engine\Factory;
use Akeeba\Engine\Platform;
use FOF40\Container\Container;
use FOF40\Model\Model;

/**
 * File Filters model
 *
 * Handles the exclusion of files and directories
 */
class FileFilters extends Model
{
	use ExclusionFilter;

	public function __construct(Container $container, array $config)
	{
		parent::__construct($container, $config);

		$this->knownFilterTypes = ['directories', 'files', 'skipdirs', 'skipfiles'];
	}

	/**
	 * Returns a listing of contained directories and files, as well as their exclusion status
	 *
	 * @param   string  $root  The root directory
	 * @param   string  $node  The subdirectory to scan
	 *
	 * @return  array
	 */
	private function &get_listing($root, $node)
	{
		// Initialize the absolute directory root
		$directory = substr($root, 0);

		// Replace stock directory tags, like [SITEROOT]
		$stock_dirs = Platform::getInstance()->get_stock_directories();

		if (!empty($stock_dirs))
		{
			foreach ($stock_dirs as $key => $replacement)
			{
				$directory = str_replace($key, $replacement, $directory);
			}
		}

		$directory = Factory::getFilesystemTools()->TranslateWinPath($directory);

		// Clean and add the node
		$node = Factory::getFilesystemTools()->TranslateWinPath($node);

		// Just a directory separator is treated as no directory at all
		if (($node == '/'))
		{
			$node = '';
		}

		// Trim leading and trailing slashes
		$node = trim($node, '/');

		// Add node to directory
		if (!empty($node))
		{
			$directory .= '/' . $node;
		}

		// Add any required trailing slash to the node to be used below
		if (!empty($node))
		{
			$node .= '/';
		}

		// Get a filters instance
		$filters = Factory::getFilters();

		// Get a listing of folders and process it
		$folders = Factory::getFileLister()->getFolders($directory);
		$folders_out = array();

		if (!empty($folders))
		{
			asort($folders);

			foreach ($folders as $folder)
			{
				$folder = Factory::getFilesystemTools()->TranslateWinPath($folder);

				// Filter out files whose names result to an empty JSON representation
				$json_folder = json_encode($folder);
				$folder      = json_decode($json_folder);

				if (empty($folder))
				{
					continue;
				}

				$test   = $node . $folder;
				$status = array();

				// Check dir/all filter (exclude)
				$result                = $filters->isFilteredExtended($test, $root, 'dir', 'all', $byFilter);
				$status['directories'] = (!$result) ? 0 : (($byFilter == 'directories') ? 1 : 2);

				// Check dir/content filter (skip_files)
				$result              = $filters->isFilteredExtended($test, $root, 'dir', 'content', $byFilter);
				$status['skipfiles'] = (!$result) ? 0 : (($byFilter == 'skipfiles') ? 1 : 2);

				// Check dir/children filter (skip_dirs)
				$result             = $filters->isFilteredExtended($test, $root, 'dir', 'children', $byFilter);
				$status['skipdirs'] = (!$result) ? 0 : (($byFilter == 'skipdirs') ? 1 : 2);

				$status['link']  = @is_link($directory . '/' . $folder);

				// Add to output array
				$folders_out[ $folder ] = $status;
			}
		}

		unset($folders);
		$folders = $folders_out;

		// Get a listing of files and process it
		$files = Factory::getFileLister()->getFiles($directory);
		$files_out = array();

		if (!empty($files))
		{
			asort($files);

			foreach ($files as $file)
			{
				// Filter out files whose names result to an empty JSON representation
				$json_file = json_encode($file);
				$file      = json_decode($json_file);

				if (empty($file))
				{
					continue;
				}

				$test   = $node . $file;
				$status = [];

				// Check file/all filter (exclude)
				$result          = $filters->isFilteredExtended($test, $root, 'file', 'all', $byFilter);
				$status['files'] = (!$result) ? 0 : (($byFilter == 'files') ? 1 : 2);
				$status['size']  = $this->formatSize(@filesize($directory . '/' . $file), 1);
				$status['link']  = @is_link($directory . '/' . $file);

				// Add to output array
				$files_out[$file] = $status;
			}
		}

		unset($files);
		$files = $files_out;

		// Return a compiled array
		$retarray = array(
			'folders' => $folders,
			'files'   => $files
		);

		return $retarray;

		/* Return array format
		 * [array] :
		 * 		'folders' [array] :
		 * 			(folder_name) => [array]:
		 *				'directories'	=> 0|1|2
		 *				'skipfiles'		=> 0|1|2
		 *				'skipdirs'		=> 0|1|2
		 *		'files' [array] :
		 *			(file_name) => [array]:
		 *				'files'			=> 0|1|2
		 *
		 * Legend:
		 * 0 -> Not excluded
		 * 1 -> Excluded by the direct filter
		 * 2 -> Excluded by another filter (regex, api, an unknown plugin filter...)
		 */
	}

	/**
	 * Glues the current directory crumbs and the child directory into a node string
	 *
	 * @param   array|string  $crumbs  Breadcrumbs in array or JSON encoded array format
	 * @param   string        $child   The child folder (relative to the root defined by crumbs)
	 *
	 * @return  string  The absolute node (path) of the $child
	 */
	private function glue_crumbs(&$crumbs, $child)
	{
		// Construct the full node
		$node = '';

		// Some servers do not decode the crumbs. I don't know why!
		if (!is_array($crumbs) && (substr($crumbs, 0, 1) == '['))
		{
			$crumbs = @json_decode($crumbs);

			if ($crumbs === false)
			{
				$crumbs = array();
			}
		}

		if (!is_array($crumbs))
		{
			$crumbs = array();
		}

		array_walk($crumbs, function ($value, $index) {
			if (in_array(trim($value), array('.', '..')))
			{
				throw new \InvalidArgumentException("Unacceptable folder crumbs");
			}
		});

		if ((stristr($child, '/..') !== false) || (stristr($child, '\..') !== false))
		{
			throw new \InvalidArgumentException("Unacceptable child folder");
		}

		if (!empty($crumbs))
		{
			$node = implode('/', $crumbs);
		}

		if (!empty($node))
		{
			$node .= '/';
		}

		if (!empty($child))
		{
			$node .= $child;
		}

		return $node;
	}

	/**
	 * Returns an array with the listing and filter status of a directory
	 *
	 * @param   string        $root    Root directory
	 * @param   array|string  $crumbs  Breadcrumbs in array or JSON encoded array format, defining the parent directory
	 * @param   string        $child   The child directory we want to scan
	 *
	 * @return array
	 */
	public function make_listing($root, $crumbs = [], $child = '')
	{
		// Construct the full node
		$node = $this->glue_crumbs($crumbs, $child);

		// Create the new crumbs
		if (!is_array($crumbs))
		{
			$crumbs = array();
		}

		if (!empty($child))
		{
			$crumbs[] = $child;
		}

		// Get listing with the filter info
		$listing = $this->get_listing($root, $node);

		// Assemble the array
		$listing['root']   = $root;
		$listing['crumbs'] = $crumbs;

		return $listing;
	}

	/**
	 * Toggle a filter
	 *
	 * @param   string  $root    Root directory
	 * @param   array   $crumbs  Components of the current directory relative to the root
	 * @param   string  $item    The child item of the current directory we want to toggle the filter for
	 * @param   string  $filter  The name of the filter to apply (directories, skipfiles, skipdirs, files)
	 *
	 * @return  array
	 */
	public function toggle($root, $crumbs, $item, $filter)
	{
		$node = $this->glue_crumbs($crumbs, $item);

		return $this->applyExclusionFilter($filter, $root, $node, 'toggle');
	}

	/**
	 * Set a filter
	 *
	 * @param   string  $root    Root directory
	 * @param   array   $crumbs  Components of the current directory relative to the root
	 * @param   string  $item    The child item of the current directory we want to set the filter for
	 * @param   string  $filter  The name of the filter to apply (directories, skipfiles, skipdirs, files)
	 *
	 * @return  array
	 */
	public function setFilter($root, $crumbs, $item, $filter)
	{
		$node = $this->glue_crumbs($crumbs, $item);

		return $this->applyExclusionFilter($filter, $root, $node, 'set');
	}

	/**
	 * Remove a filter
	 *
	 * @param   string  $root    Root directory
	 * @param   array   $crumbs  Components of the current directory relative to the root
	 * @param   string  $item    The child item of the current directory we want to remove the filter for
	 * @param   string  $filter  The name of the filter to apply (directories, skipfiles, skipdirs, files)
	 *
	 * @return  array
	 */
	public function remove($root, $crumbs, $item, $filter)
	{
		$node = $this->glue_crumbs($crumbs, $item);

		return $this->applyExclusionFilter($filter, $root, $node, 'remove');
	}

	/**
	 * Swap a filter
	 *
	 * @param   string  $root    Root directory
	 * @param   array   $crumbs  Components of the current directory relative to the root
	 * @param   string  $item    The child item of the current directory we want to set the filter for
	 * @param   string  $filter  The name of the filter to apply (directories, skipfiles, skipdirs, files)
	 *
	 * @return  array
	 */
	public function swap($root, $crumbs, $old_item, $new_item, $filter)
	{
		$new_node = $this->glue_crumbs($crumbs, $new_item);
		$old_node = $this->glue_crumbs($crumbs, $old_item);

		return $this->applyExclusionFilter($filter, $root, $new_node, 'swap', $old_node);
	}

	/**
	 * Retrieves the filters as an array. Used for the tabular filter editor.
	 *
	 * @param   string  $root  The root node to search filters on
	 *
	 * @return  array  A collection of hash arrays containing node and type for each filtered element
	 */
	public function &get_filters($root)
	{
		return $this->getTabularFilters($root);
	}

	/**
	 * Resets the filters
	 *
	 * @param   string  $root  Root directory
	 *
	 * @return  array
	 */
	public function resetFilters($root)
	{
		$this->resetAllFilters($root);

		return $this->make_listing($root);
	}

	/**
	 * Handles a request coming in through AJAX. Basically, this is a simple proxy to the model methods.
	 *
	 * @return  array
	 */
	public function doAjax()
	{
		$action = $this->getState('action');
		$verb   = array_key_exists('verb', get_object_vars($action)) ? $action->verb : null;

		if (!array_key_exists('crumbs', get_object_vars($action)))
		{
			$action->crumbs = '';
		}

		$ret_array = array();

		switch ($verb)
		{
			// Return a listing for the normal view
			case 'list':
				$ret_array = $this->make_listing($action->root, $action->crumbs, $action->node);
				break;

			// Toggle a filter's state
			case 'toggle':
				$ret_array = $this->toggle($action->root, $action->crumbs, $action->node, $action->filter);
				break;

			// Set a filter (used by the editor)
			case 'set':
				$ret_array = $this->setFilter($action->root, $action->crumbs, $action->node, $action->filter);
				break;

			// Swap a filter (used by the editor)
			case 'swap':
				$ret_array =
					$this->swap($action->root, $action->crumbs, $action->old_node, $action->new_node, $action->filter);
				break;

			case 'tab':
				$ret_array = $this->get_filters($action->root);
				break;

			// Reset filters
			case 'reset':
				$ret_array = $this->resetFilters($action->root);
				break;
		}

		return $ret_array;
	}

	/**
	 * Format the size of the file (given in bytes) to something human readable, e.g. 123 MB
	 *
	 * @param   int  $bytes     The file size in bytes
	 * @param   int  $decimals  How many decimals you want (default: 0)
	 *
	 * @return  string  The human-readable, formatted size
	 */
	private function formatSize($bytes, $decimals = 0)
	{
		$bytes  = empty($bytes) ? 0 : (int) $bytes;
		$format = empty($decimals) ? '%0u' : '%0.' . $decimals . 'f';

		$uom = [
			'TB' => 1048576 * 1048576,
			'GB' => 1024 * 1048576,
			'MB' => 1048576,
			'KB' => 1024,
			'B'  => 1,
		];

		// Whole bytes cannot have decimal positions
		if (!empty($decimals))
		{
			unset($uom['B']);
		}

		foreach ($uom as $unit => $byteSize)
		{
			if (floatval($bytes) >= $byteSize)
			{
				return sprintf($format, $bytes / $byteSize) . ' ' . $unit;
			}
		}

		// If the number is either too big or too small,
		return sprintf('%0u B', $bytes);
	}

}
PK�!]��ۆ�Model/DatabaseFilters.phpnu&1i�<?php
/**
 * @package   akeebabackup
 * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

namespace Akeeba\Backup\Admin\Model;

// Protect from unauthorized access
defined('_JEXEC') || die();

use Akeeba\Backup\Admin\Model\Mixin\ExclusionFilter;
use Akeeba\Engine\Factory;
use Exception;
use FOF40\Container\Container;
use FOF40\Model\Model;
use Joomla\CMS\Language\Text;

/**
 * Database Filters model
 *
 * Handles the exclusion of database tables (whole or just their data)
 */
class DatabaseFilters extends Model
{
	use ExclusionFilter;

	public function __construct(Container $container, array $config)
	{
		parent::__construct($container, $config);

		$this->knownFilterTypes = ['tables', 'tabledata'];
	}

	/**
	 * Returns a list of the database tables, views, procedures, functions and triggers,
	 * along with their filter status in array format, for use in the GUI
	 *
	 * @param   string  $root  The database root we're working on
	 *
	 * @return  array  Hash array. 'tables' is an array list of tables w/ metadata. 'root' is the current db root.
	 */
	public function make_listing($root)
	{
		// Get database inclusion filters
		$filters       = Factory::getFilters();
		$database_list = $filters->getInclusions('db');

		// Load the database object for the selected database
		$config         = $database_list[$root];
		$config['user'] = $config['username'];
		$db             = Factory::getDatabase($config);

		// Load the table data
		try
		{
			$table_data = $db->getTables();
		}
		catch (Exception $e)
		{
			$table_data = [];
		}

		$tableMeta = [];

		try
		{
			$db->setQuery('SHOW TABLE STATUS');

			$temp = $db->loadAssocList();

			foreach ($temp as $record)
			{
				$tableMeta[$db->getAbstract($record['Name'])] = [
					'engine'      => $record['Engine'],
					'rows'        => $record['Rows'],
					'dataLength'  => $record['Data_length'],
					'indexLength' => $record['Index_length'],
				];
			}
		}
		catch (Exception $e)
		{
		}

		// Process filters
		$tables = [];

		if (!empty($table_data))
		{
			foreach ($table_data as $table_name => $table_type)
			{
				$status = [
					'engine'      => null,
					'rows'        => null,
					'dataLength'  => null,
					'indexLength' => null,
				];

				if (array_key_exists($table_name, $tableMeta))
				{
					$status = $tableMeta[$table_name];
				}

				// Add table type
				$status['type'] = $table_type;

				// Check dbobject/all filter (exclude)
				$result           = $filters->isFilteredExtended($table_name, $root, 'dbobject', 'all', $byFilter);
				$status['tables'] = (!$result) ? 0 : (($byFilter == 'tables') ? 1 : 2);

				// Check dbobject/content filter (skip table data)
				$result              = $filters->isFilteredExtended($table_name, $root, 'dbobject', 'content', $byFilter);
				$status['tabledata'] = (!$result) ? 0 : (($byFilter == 'tabledata') ? 1 : 2);

				// We can't filter contents of views, merge tables, black holes, procedures, functions and triggers
				if ($table_type != 'table')
				{
					$status['tabledata'] = 2;
				}

				$tables[$table_name] = $status;
			}
		}

		return [
			'tables' => $tables,
			'root'   => $root,
		];
	}

	/**
	 * Returns an array containing a mapping of db root names and their human-readable representation
	 *
	 * @return  array  Array of objects; "value" contains the root name, "text" the human-readable text
	 */
	public function get_roots()
	{
		// Get database inclusion filters
		$filters       = Factory::getFilters();
		$database_list = $filters->getInclusions('db');

		$ret = [];

		foreach ($database_list as $name => $definition)
		{
			$root = $definition['host'];

			if (!empty($definition['port']))
			{
				$root .= ':' . $definition['port'];
			}

			$root .= '/' . $definition['database'];

			if ($name == '[SITEDB]')
			{
				$root = Text::_('COM_AKEEBA_DBFILTER_LABEL_SITEDB');
			}

			$ret[] = (object) [
				'value' => $name,
				'text'  => $root,
			];
		}

		return $ret;
	}

	/**
	 * Toggle a filter
	 *
	 * @param   string  $root    Database root
	 * @param   string  $item    The db entity we want to toggle the filter for
	 * @param   string  $filter  The name of the filter to apply (tables, tabledata)
	 *
	 * @return  array
	 */
	public function toggle($root, $item, $filter)
	{
		return $this->applyExclusionFilter($filter, $root, $item, 'toggle');
	}

	/**
	 * Set a filter
	 *
	 * @param   string  $root    Database root
	 * @param   string  $item    The db entity we want to toggle the filter for
	 * @param   string  $filter  The name of the filter to apply (tables, tabledata)
	 *
	 * @return  array
	 */
	public function remove($root, $item, $filter)
	{
		return $this->applyExclusionFilter($filter, $root, $item, 'remove');
	}

	/**
	 * Set a filter
	 *
	 * @param   string  $root    Database root
	 * @param   string  $item    The db entity we want to toggle the filter for
	 * @param   string  $filter  The name of the filter to apply (tables, tabledata)
	 *
	 * @return  array
	 */
	public function setFilter($root, $item, $filter)
	{
		return $this->applyExclusionFilter($filter, $root, $item, 'set');
	}

	/**
	 * Swap a filter
	 *
	 * @param   string  $root      Database root
	 * @param   string  $old_item  The db entity that used to be filtered and will no longer be
	 * @param   string  $new_item  The db entity that wasn't filtered but now will be
	 * @param   string  $filter    The name of the filter to apply (tables, tabledata)
	 *
	 * @return  array
	 */
	public function swap($root, $old_item, $new_item, $filter)
	{
		return $this->applyExclusionFilter($filter, $root, $new_item, 'swap', $old_item);
	}

	/**
	 * Retrieves the filters as an array. Used for the tabular filter editor.
	 *
	 * @param   string  $root  The root node to search filters on
	 *
	 * @return  array  An array of hash arrays containing node and type for each filtered element
	 */
	public function &get_filters($root)
	{
		return $this->getTabularFilters($root);
	}

	/**
	 * Resets all filters
	 *
	 * @param   string  $root  Root directory
	 *
	 * @return  array
	 */
	public function resetFilters($root)
	{
		$this->resetAllFilters($root);

		return $this->make_listing($root);
	}

	/**
	 * Handles a request coming in through AJAX. Basically, this is a simple proxy to the model methods.
	 *
	 * @return  array
	 */
	public function doAjax()
	{
		$action = $this->getState('action');
		$verb   = array_key_exists('verb', get_object_vars($action)) ? $action->verb : null;

		$ret_array = [];

		switch ($verb)
		{
			// Return a listing for the normal view
			case 'list':
				$ret_array = $this->make_listing($action->root);
				break;

			// Toggle a filter's state
			case 'toggle':
				$ret_array = $this->toggle($action->root, $action->node, $action->filter);
				break;

			// Set a filter (used by the editor)
			case 'set':
				$ret_array = $this->setFilter($action->root, $action->node, $action->filter);
				break;

			// Remove a filter (used by the editor)
			case 'remove':
				$ret_array = $this->remove($action->root, $action->node, $action->filter);
				break;

			// Swap a filter (used by the editor)
			case 'swap':
				$ret_array = $this->swap($action->root, $action->old_node, $action->new_node, $action->filter);
				break;

			// Tabular view
			case 'tab':
				$ret_array = $this->get_filters($action->root);
				break;

			// Reset filters
			case 'reset':
				$ret_array = $this->resetFilters($action->root);
				break;
		}

		return $ret_array;
	}
}
PK�!]��yf;*;*Model/Updates.phpnu&1i�<?php
/**
 * @package   akeebabackup
 * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

namespace Akeeba\Backup\Admin\Model;

// Protect from unauthorized access
defined('_JEXEC') || die();

use Exception;
use FOF40\Container\Container;
use FOF40\Update\Update;
use Joomla\CMS\Factory as JFactory;
use Joomla\CMS\Filesystem\File;

/**
 * Updates model. Acts as an intermediary between the component and Joomla!,
 *
 * @package Akeeba\Backup\Admin\Model
 */
class Updates extends Update
{
	/**
	 * Obsolete update site locations
	 *
	 * @var  array
	 */
	protected $obsoleteUpdateSiteLocations = [
		'http://cdn.akeebabackup.com/updates/abpro.xml',
		'http://cdn.akeebabackup.com/updates/abcore.xml',
		'http://cdn.akeebabackup.com/updates/fof.xml',
	];

	/**
	 * Public constructor. Initialises the protected members as well.
	 *
	 * @param   array  $config
	 */
	public function __construct($config = [])
	{
		$container = Container::getInstance('com_akeeba');

		$config['update_component'] = 'pkg_akeeba';
		$config['update_sitename']  = 'Akeeba Backup Core';
		$config['update_site']      = 'https://cdn.akeeba.com/updates/pkgakeebacore.xml';
		$config['update_paramskey'] = 'update_dlid';
		$config['update_container'] = $container;

		$isPro = defined('AKEEBA_PRO') ? AKEEBA_PRO : 0;

		if ($isPro)
		{
			$config['update_sitename'] = 'Akeeba Backup Professional';
			$config['update_site']     = 'https://cdn.akeeba.com/updates/pkgakeebapro.xml';
		}

		if (defined('AKEEBA_VERSION') && !in_array(substr(AKEEBA_VERSION, 0, 3), ['dev', 'rev']))
		{
			$config['update_version'] = AKEEBA_VERSION;
		}

		parent::__construct($config);

		$this->container    = $container;
		$this->extension_id = $this->findExtensionId('pkg_akeeba', 'package');

		if (empty($this->extension_id))
		{
			$this->createFakePackageExtension();
			$this->extension_id = $this->findExtensionId('pkg_akeeba', 'package');
		}
	}

	/**
	 * Refreshes the update sites, removing obsolete update sites in the process
	 */
	public function refreshUpdateSite(): void
	{
		// Remove any update sites for the old com_akeeba package
		$this->removeObsoleteComponentUpdateSites();

		// Refresh our update sites
		parent::refreshUpdateSite();
	}

	/**
	 * Removes the obsolete update sites for the component, since now we're dealing with a package.
	 *
	 * Controlled by componentName, packageName and obsoleteUpdateSiteLocations
	 *
	 * Depends on getExtensionId, getUpdateSitesFor
	 *
	 * @return  void
	 */
	private function removeObsoleteComponentUpdateSites()
	{
		// Initialize
		$deleteIDs = [];

		// Get component ID
		$componentID = $this->findExtensionId('com_akeeba', 'component');

		// Get package ID
		$packageID = $this->findExtensionId('pkg_akeeba', 'package');

		// Update sites for old extension ID (all)
		if ($componentID)
		{
			// Old component packages
			$moreIDs = $this->getUpdateSitesFor($componentID, null);

			if (is_array($moreIDs) && count($moreIDs))
			{
				$deleteIDs = array_merge($deleteIDs, $moreIDs);
			}

			// Obsolete update sites
			$moreIDs = $this->getUpdateSitesFor(null, $componentID, $this->obsoleteUpdateSiteLocations);

			if (is_array($moreIDs) && count($moreIDs))
			{
				$deleteIDs = array_merge($deleteIDs, $moreIDs);
			}
		}

		// Update sites for any but current extension ID, location matching any of the obsolete update sites
		if ($packageID)
		{
			// Update sites for all of the current extension ID update sites
			$moreIDs = $this->getUpdateSitesFor($packageID, null);

			if (is_array($moreIDs) && count($moreIDs))
			{
				$deleteIDs = array_merge($deleteIDs, $moreIDs);
			}

			$deleteIDs = array_unique($deleteIDs);

			// Remove the last update site
			if (count($deleteIDs))
			{
				$lastID = array_pop($moreIDs);
				$pos    = array_search($lastID, $deleteIDs);
				unset($deleteIDs[$pos]);
			}
		}

		$db        = $this->container->db;
		$deleteIDs = array_unique($deleteIDs);

		if (empty($deleteIDs) || !count($deleteIDs))
		{
			return;
		}

		$deleteIDs = array_map([$db, 'q'], $deleteIDs);

		$query = $db->getQuery(true)
			->delete($db->qn('#__update_sites'))
			->where($db->qn('update_site_id') . ' IN(' . implode(',', $deleteIDs) . ')');

		try
		{
			$db->setQuery($query)->execute();
		}
		catch (Exception $e)
		{
			// Do nothing.
		}

		$query = $db->getQuery(true)
			->delete($db->qn('#__update_sites_extensions'))
			->where($db->qn('update_site_id') . ' IN(' . implode(',', $deleteIDs) . ')');

		try
		{
			$db->setQuery($query)->execute();
		}
		catch (Exception $e)
		{
			// Do nothing.
		}
	}

	/**
	 * Gets the ID of an extension
	 *
	 * @param   string  $element  Extension element, e.g. com_foo, mod_foo, lib_foo, pkg_foo or foo (CAUTION: plugin,
	 *                            file!)
	 * @param   string  $type     Extension type: component, module, library, package, plugin or file
	 * @param   null    $folder   Plugins: plugin folder. Modules: admin/site
	 *
	 * @return  int  Extension ID or 0 on failure
	 */
	private function findExtensionId($element, $type = 'component', $folder = null)
	{
		$db    = $this->container->db;
		$query = $db->getQuery(true)
			->select($db->qn('extension_id'))
			->from($db->qn('#__extensions'))
			->where($db->qn('element') . ' = ' . $db->q($element))
			->where($db->qn('type') . ' = ' . $db->q($type));

		// Plugin? We should look for a folder
		if ($type == 'plugin')
		{
			$folder = empty($folder) ? 'system' : $folder;

			$query->where($db->qn('folder') . ' = ' . $db->q($folder));
		}

		// Module? Use the folder to determine if it's site or admin module.
		if ($type == 'module')
		{
			$folder = empty($folder) ? 'site' : $folder;

			$query->where($db->qn('client_id') . ' = ' . $db->q(($folder == 'site') ? 0 : 1));
		}

		try
		{
			$id = $db->setQuery($query, 0, 1)->loadResult();
		}
		catch (Exception $e)
		{
			$id = 0;
		}

		return empty($id) ? 0 : (int) $id;
	}

	/**
	 * Returns the update site IDs matching the criteria below. All criteria are optional but at least one must be
	 * defined for the method call to make any sense.
	 *
	 * @param   int|null  $includeEID  The update site must belong to this extension ID
	 * @param   int|null  $excludeEID  The update site must NOT belong to this extension ID
	 * @param   array     $locations   The update site must match one of these locations
	 *
	 * @return  array  The IDs of the update sites
	 */
	private function getUpdateSitesFor($includeEID = null, $excludeEID = null, $locations = [])
	{
		$db    = $this->container->db;
		$query = $db->getQuery(true)
			->select($db->qn('s.update_site_id'))
			->from($db->qn('#__update_sites', 's'));

		if (!empty($locations))
		{
			$quotedLocations = array_map([$db, 'q'], $locations);
			$query->where($db->qn('location') . 'IN(' . implode(',', $quotedLocations) . ')');
		}

		if (!empty($includeEID) || !empty($excludeEID))
		{
			$query->innerJoin($db->qn('#__update_sites_extensions', 'e') . 'ON(' . $db->qn('e.update_site_id') .
				' = ' . $db->qn('s.update_site_id') . ')'
			);
		}

		if (!empty($includeEID))
		{
			$query->where($db->qn('e.extension_id') . ' = ' . $db->q($includeEID));
		}
		elseif (!empty($excludeEID))
		{
			$query->where($db->qn('e.extension_id') . ' != ' . $db->q($excludeEID));
		}

		try
		{
			$ret = $db->setQuery($query)->loadColumn();
		}
		catch (Exception $e)
		{
			$ret = null;
		}

		return empty($ret) ? [] : $ret;
	}

	private function createFakePackageExtension()
	{
		$manifestCacheJson = json_encode([
			'name'         => 'Akeeba Backup package',
			'type'         => 'package',
			'creationDate' => gmdate('Y-m-d'),
			'author'       => 'Nicholas K. Dionysopoulos',
			'copyright'    => sprintf('Copyright (c)2006-%d Akeeba Ltd / Nicholas K. Dionysopoulos', gmdate('Y')),
			'authorEmail'  => '',
			'authorUrl'    => 'https://www.akeeba.com',
			'version'      => $this->version,
			'description'  => sprintf('Akeeba Backup installation package v.%s', $this->version),
			'group'        => '',
			'filename'     => 'pkg_akeeba',
		]);

		$extensionRecord = [
			'name'             => 'Akeeba Backup package',
			'type'             => 'package',
			'element'          => 'pkg_akeeba',
			'folder'           => '',
			'client_id'        => 0,
			'enabled'          => 1,
			'access'           => 1,
			'protected'        => 0,
			'manifest_cache'   => $manifestCacheJson,
			'params'           => '{}',
			'checked_out'      => 0,
			'checked_out_time' => null,
			'state'            => 0,
		];

		$class = '\\Joomla\\CMS\\Table\\Extension';
		$class = class_exists($class, true) ? $class : '\\JTableExtension';
		$extension = new $class($this->container->db);
		$extension->save($extensionRecord);

		$this->createFakePackageManifest();
	}

	private function createFakePackageManifest()
	{
		$path = JPATH_ADMINISTRATOR . '/manifests/packages/pkg_akeeba.xml';

		if (file_exists($path))
		{
			return;
		}

		$isPro   = defined('AKEEBA_PRO') ? AKEEBA_PRO : 0;
		$proCore = $isPro ? 'pro' : 'core';
		$dlid    = $isPro ? '<dlid prefix="dlid=" suffix=""/>' : '';
		$year    = gmdate('Y');
		$date    = gmdate('Y-m-d');

		$proPlugins = <<< END
        <file type="file" id="file_akeeba">file_akeeba-pro.zip</file>
		<file type="plugin" group="installer" id="akeebabackup">plg_installer_akeebabackup.zip</file>
END;
		$proPlugins = $isPro ? $proPlugins : '';

		$content = <<< XML
<?xml version="1.0" encoding="utf-8"?>
<extension version="3.9.0" type="package" method="upgrade">
	$dlid
    <name>Akeeba Backup package</name>
    <author>Nicholas K. Dionysopoulos</author>
    <creationDate>$date</creationDate>
    <packagename>akeeba</packagename>
    <version>{$this->version}</version>
    <url>https://www.akeeba.com</url>
    <packager>Akeeba Ltd</packager>
    <packagerurl>https://www.akeeba.com</packagerurl>
    <copyright>Copyright (c)2006-$year Akeeba Ltd / Nicholas K. Dionysopoulos</copyright>
    <license>GNU GPL v3 or later</license>
    <description>Akeeba Backup installation package {$this->version}</description>

    <files>
        <file type="component" id="com_akeebabackup">com_akeebabackup-{$proCore}.zip</file>
        <file type="plugin" group="console" id="akeebabackup">plg_console_akeebabackup.zip</file>
        <file type="plugin" group="quickicon" id="akeebabackup">plg_quickicon_akeebabackup.zip</file>
        <file type="plugin" group="system" id="backuponupdate">plg_system_backuponupdate.zip</file>
        <file type="plugin" group="actionlog" id="akeebabackup">plg_actionlog_akeebabackup.zip</file>
        $proPlugins
    </files>

    <scriptfile>script.akeeba.php</scriptfile>
</extension>
XML;

		if (!@file_put_contents($content, $path))
		{
			File::write($path, $content);
		}
	}
}
PK�!]���wHHModel/Backup.phpnu&1i�<?php
/**
 * @package   akeebabackup
 * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

namespace Akeeba\Backup\Admin\Model;

// Protect from unauthorized access
defined('_JEXEC') || die();

use Akeeba\Engine\Base\Part;
use Akeeba\Engine\Factory;
use Akeeba\Engine\Platform;
use Akeeba\Engine\Psr\Log\LogLevel;
use Akeeba\Engine\Util\PushMessages;
use DateTimeZone;
use DirectoryIterator;
use Exception;
use FOF40\Date\Date;
use FOF40\Factory\Exception\ModelNotFound;
use FOF40\Model\Model;
use FOF40\Timer\Timer;
use Joomla\CMS\Language\Text;
use RuntimeException;

/**
 * Backup model. Handles the server-side logic of interfacing with the backup engine (Akeeba Engine)
 */
class Backup extends Model
{
	/**
	 * Starts or step a backup process. Set the state variable "ajax" to the task you want to execute OR call the
	 * relevant public method directly.
	 *
	 * @return  array  An Akeeba Engine return array
	 */
	public function runBackup()
	{
		$ret_array = [];

		$ajaxTask = $this->getState('ajax');

		switch ($ajaxTask)
		{
			// Start a new backup
			case 'start':
				$ret_array = $this->startBackup();
				break;

			// Step through a backup
			case 'step':
				$ret_array = $this->stepBackup();
				break;

			// Send a push notification for backup failure
			case 'pushFail':
				$this->pushFail();
				break;

			default:
				break;
		}

		return $ret_array;
	}

	/**
	 * Starts a new backup.
	 *
	 * State variables expected
	 * backupid        The ID of the backup. If none is set up we will create a new one in the form id123
	 * tag            The backup tag, e.g. "frontend". If none is set up we'll get it through the Platform.
	 * description    The description of the backup (optional)
	 * comment      The comment of the backup (optional)
	 * jpskey       JPS password
	 * angiekey     ANGIE password
	 *
	 * @param   array  $overrides  Configuration overrides
	 *
	 * @return  array  An Akeeba Engine return array
	 */
	public function startBackup(array $overrides = [])
	{
		// Get information from the session
		$tag         = $this->getState('tag', null, 'string');
		$description = $this->getState('description', '', 'string');
		$comment     = $this->getState('comment', '', 'html');
		$jpskey      = $this->getState('jpskey', null, 'raw');
		$angiekey    = $this->getState('angiekey', null, 'raw');
		$backupId = $this->getBackupId();

		// Use the default description if none specified
		$description = $description ?: $this->getDefaultDescription();

		// Try resetting the engine
		try
		{
			Factory::resetState([
				'maxrun' => 0,
			]);
		}
		catch (Exception $e)
		{
			// This will die if the output directory is invalid. Let it die, then.
		}

		// Remove any stale memory files left over from the previous step
		if (empty($tag))
		{
			$tag = Platform::getInstance()->get_backup_origin();
		}

		$tempVarsTag = $tag;
		$tempVarsTag .= empty($backupId) ? '' : ('.' . $backupId);

		Factory::getFactoryStorage()->reset($tempVarsTag);
		Factory::nuke();
		Factory::getLog()->log(LogLevel::DEBUG, " -- Resetting Akeeba Engine factory ($tag.$backupId)");
		Platform::getInstance()->load_configuration();

		// Autofix the output directory
		/** @var ConfigurationWizard $confWizModel */
		$confWizModel = $this->container->factory->model('ConfigurationWizard')->tmpInstance();
		$confWizModel->autofixDirectories();

		// Rebase Off-site Folder Inclusion filters to use site path variables
		/** @var \Akeeba\Backup\Admin\Model\IncludeFolders $incFoldersModel */
		try
		{
			$incFoldersModel = $this->container->factory->model('IncludeFolders')->tmpInstance();
			$incFoldersModel->rebaseFiltersToSiteDirs();
		}
		catch (ModelNotFound $e)
		{
			// Not a problem. This is expected to happen in the Core version.
		}

		// Should I apply any configuration overrides?
		if (is_array($overrides) && !empty($overrides))
		{
			$config        = Factory::getConfiguration();
			$protectedKeys = $config->getProtectedKeys();
			$config->resetProtectedKeys();

			foreach ($overrides as $k => $v)
			{
				$config->set($k, $v);
			}

			$config->setProtectedKeys($protectedKeys);
		}

		// Check if there are critical issues preventing the backup
		if (!Factory::getConfigurationChecks()->getShortStatus())
		{
			$configChecks = Factory::getConfigurationChecks()->getDetailedStatus();

			foreach ($configChecks as $checkItem)
			{
				if ($checkItem['severity'] != 'critical')
				{
					continue;
				}

				return [
					'HasRun'   => 0,
					'Domain'   => 'init',
					'Step'     => '',
					'Substep'  => '',
					'Error'    => 'Failed configuration check Q' . $checkItem['code'] . ': ' . $checkItem['description'] . '. Please refer to https://www.akeeba.com/documentation/warnings/q' . $checkItem['code'] . '.html for more information and troubleshooting instructions.',
					'Warnings' => [],
					'Progress' => 0,
				];
			}
		}

		// Set up Kettenrad
		$options = [
			'description' => $description,
			'comment'     => $comment,
			'jpskey'      => $jpskey,
			'angiekey'    => $angiekey,
		];

		if (is_null($jpskey))
		{
			unset ($options['jpskey']);
		}

		if (is_null($angiekey))
		{
			unset ($options['angiekey']);
		}

		$kettenrad = Factory::getKettenrad();
		$kettenrad->setBackupId($backupId);
		$kettenrad->setup($options);

		$this->setState('backupid', $backupId);

		/**
		 * Convert log files in the backup output directory
		 *
		 * This removes the obsolete, default log files (akeeba.(backend|frontend|cli|json).log and converts the old .log
		 * files into their .php counterparts.
		 *
		 * We are doing this when loading the the Control Panel page but ALSO when taking a new backup because some
		 * people might be installing updates and taking backups automatically, without visiting the Control Panel
		 * except in rare cases.
		 */
		$this->convertLogFiles(3);

		/**
		 * We need to run tick() twice in the first backup step.
		 *
		 * The first tick() will reset the backup engine and start a new backup. However, no backup record is created
		 * at this point. This means that Factory::loadState() cannot find a backup record, therefore it cannot read
		 * the backup profile being used, therefore it will assume it's profile #1.
		 *
		 * The second tick() creates the backup record without doing much else, fixing this issue.
		 *
		 * However, if you have conservative settings where the min exec time is MORE than the max exec time the second
		 * tick would never run. Therefore we need to tell the first tick to ignore the time settings (since it only
		 * takes a few milliseconds to execute anyway) and then apply the time settings on the second tick (which also
		 * only takes a few milliseconds). This is why we have setIgnoreMinimumExecutionTime before and after the first
		 * tick. DO NOT REMOVE THESE.
		 *
		 * Furthermore, if the first tick reaches the end of backup or an error condition we MUST NOT run the second
		 * tick() since the engine state will be invalid. Hence the check for the state that performs a hard break. This
		 * could happen if you have a sufficiently high max execution time, no break between steps and we fail to
		 * execute any step, e.g. the installer image is missing, a database error occurred or we can not list the files
		 * and directories to back up.
		 *
		 * THEREFORE, DO NOT REMOVE THE LOOP OR THE if-BLOCK IN IT, THEY ARE THERE FOR A GOOD REASON!
		 */
		$kettenrad->setIgnoreMinimumExecutionTime(true);

		for ($i = 0; $i < 2; $i++)
		{
			$kettenrad->tick();

			if (in_array($kettenrad->getState(), [Part::STATE_FINISHED, Part::STATE_ERROR]))
			{
				break;
			}

			$kettenrad->setIgnoreMinimumExecutionTime(false);
		}

		if (version_compare(JVERSION, '4.0', 'ge'))
		{
			Factory::getLog()->warning(sprintf('You ar using Akeeba Backup %s on Joomla! %s. This is not supported and might lead to data loss. Please upgrade to Akeeba Backup 9 or later! YOU WILL RECEIVE NO SUPPORT WHATSOEVER FOR THIS BACKUP.', AKEEBA_VERSION, JVERSION));
		}

		$ret_array = $kettenrad->getStatusArray();

		try
		{
			Factory::saveState($tag, $backupId);
		}
		catch (RuntimeException $e)
		{
			$ret_array['Error'] = $e->getMessage();
		}

		return $ret_array;
	}

	/**
	 * Steps through a backup.
	 *
	 * State variables expected (MUST be set):
	 * backupid        The ID of the backup.
	 * tag            The backup tag, e.g. "frontend".
	 * profile      (optional) The profile ID of the backup.
	 *
	 * @param   bool  $requireBackupId  Should the backup ID be required?
	 *
	 * @return  array  An Akeeba Engine return array
	 */
	public function stepBackup($requireBackupId = true)
	{
		// Get information from the model state
		$tag      = $this->getState('tag', defined('AKEEBA_BACKUP_ORIGIN') ? AKEEBA_BACKUP_ORIGIN : null, 'string');
		$backupId = $this->getState('backupid', null, 'string');

		// Get the profile from the session, the AKEEBA_PROFILE constant or the model state – in this order
		$profile = max(0, (int) $this->getState('profile', 0)) ?: $this->getLastBackupProfile($tag, $backupId);

		// Set the active profile
		if (!$this->container->platform->isCli())
		{
			$this->container->platform->setSessionVar('profile', $profile);
		}

		if (!defined('AKEEBA_PROFILE'))
		{
			define('AKEEBA_PROFILE', $profile);
		}

		// Run a backup step
		$ret_array = [
			'HasRun'   => 0,
			'Domain'   => 'init',
			'Step'     => '',
			'Substep'  => '',
			'Error'    => '',
			'Warnings' => [],
			'Progress' => 0,
		];

		try
		{
			// Reload the configuration
			Platform::getInstance()->load_configuration($profile);

			// Load the engine from storage
			Factory::loadState($tag, $backupId, $requireBackupId);

			// Set the backup ID and run a backup step
			$kettenrad = Factory::getKettenrad();
			$kettenrad->tick();
			$ret_array = $kettenrad->getStatusArray();
		}
		catch (Exception $e)
		{
			$ret_array['Error'] = $e->getMessage();
		}

		try
		{
			if (empty($ret_array['Error']) && ($ret_array['HasRun'] != 1))
			{
				Factory::saveState($tag, $backupId);
			}
		}
		catch (RuntimeException $e)
		{
			$ret_array['Error'] = $e->getMessage();
		}

		if (!empty($ret_array['Error']) || ($ret_array['HasRun'] == 1))
		{
			/**
			 * Do not nuke the Factory if we're trying to resume after an error.
			 *
			 * When the resume after error (retry) feature is enabled AND we are performing a backend backup we MUST
			 * leave the factory storage intact so we can actually resume the backup. If we were to nuke the Factory
			 * the resume would report that it cannot load the saved factory and lead to a failed backup.
			 */
			$config = Factory::getConfiguration();

			if ($this->container->platform->isBackend() && $config->get('akeeba.advanced.autoresume', 1))
			{
				// We are about to resume; abort.
				return $ret_array;
			}

			// Clean up
			Factory::nuke();

			$tempVarsTag = $tag;
			$tempVarsTag .= empty($backupId) ? '' : ('.' . $backupId);

			Factory::getFactoryStorage()->reset($tempVarsTag);
		}

		return $ret_array;
	}

	/**
	 * Send a push notification for a failed backup
	 *
	 * State variables expected (MUST be set):
	 * errorMessage  The error message
	 *
	 * @return  void
	 */
	public function pushFail()
	{
		$errorMessage = $this->getState('errorMessage');

		$platform = Platform::getInstance();
		$key      = 'COM_AKEEBA_PUSH_ENDBACKUP_FAIL_BODY_WITH_MESSAGE';

		if (empty($errorMessage))
		{
			$key = 'COM_AKEEBA_PUSH_ENDBACKUP_FAIL_BODY';
		}

		$pushSubject = sprintf(
			$platform->translate('COM_AKEEBA_PUSH_ENDBACKUP_FAIL_SUBJECT'),
			$platform->get_site_name(),
			$platform->get_host()
		);
		$pushDetails = sprintf(
			$platform->translate($key),
			$platform->get_site_name(),
			$platform->get_host(),
			$errorMessage
		);

		$push = new PushMessages();
		$push->message($pushSubject, $pushDetails);
	}

	/**
	 * Convert the old, plaintext log files (.log) into their .log.php counterparts.
	 *
	 * @param   int  $timeOut  Maximum time, in seconds, to spend doing this conversion.
	 *
	 * @return  void
	 *
	 * @since   7.0.3
	 */
	public function convertLogFiles($timeOut = 10)
	{
		$registry = Factory::getConfiguration();
		$logDir   = $registry->get('akeeba.basic.output_directory', '[DEFAULT_OUTPUT]', true);

		$timer = new Timer($timeOut, 75);

		// Part I. Remove these obsolete files first
		$killFiles = [
			'akeeba.log',
			'akeeba.backend.log',
			'akeeba.frontend.log',
			'akeeba.cli.log',
			'akeeba.json.log',
		];

		foreach ($killFiles as $fileName)
		{
			$path = $logDir . '/' . $fileName;

			if (@is_file($path))
			{
				@unlink($path);
			}
		}

		if ($timer->getTimeLeft() <= 0.01)
		{
			return;
		}

		// Part II. Convert .log files.
		try
		{
			$di = new DirectoryIterator($logDir);
		}
		catch (Exception $e)
		{
			return;
		}

		foreach ($di as $file)
		{

			try
			{
				if (!$file->isFile())
				{
					continue;
				}
				$baseName = $file->getFilename();
				if (substr($baseName, 0, 7) !== 'akeeba.')
				{
					continue;
				}
				if (substr($baseName, -4) !== '.log')
				{
					continue;
				}
				$this->convertLogFile($file->getPathname());
				if ($timer->getTimeLeft() <= 0.01)
				{
					return;
				}
			}
			catch (Exception $e)
			{
				/**
				 * Someone did something stupid, like using the site's root as the backup output directory while having
				 * an open_basedir restriction. Sorry, mate, you get insecure junk. We had warned you. You didn't heed
				 * the warning. That's your problem now.
				 */
			}
		}
	}

	/**
	 * Get the profile used to take the last backup for the specified tag
	 *
	 * @param   string  $tag       The backup tag a.k.a. backup origin (backend, frontend, json, ...)
	 * @param   string  $backupId  (optional) The Backup ID
	 *
	 * @return  int  The profile ID of the latest backup taken with the specified tag / backup ID
	 */
	public function getLastBackupProfile($tag, $backupId = null)
	{
		$filters = [
			['field' => 'tag', 'value' => $tag],
		];

		if (!empty($backupId))
		{
			$filters[] = ['field' => 'backupid', 'value' => $backupId];
		}

		$statList = Platform::getInstance()->get_statistics_list([
				'filters' => $filters,
				'order'   => [
					'by' => 'id', 'order' => 'DESC',
				],
			]
		);

		if (is_array($statList))
		{
			$stat = array_pop($statList);

			return (int) $stat['profile_id'];
		}

		// Backup entry not found. If backupId was specified, try without a backup ID
		if (!empty($backupId))
		{
			return $this->getLastBackupProfile($tag);
		}

		// Else, return the default backup profile
		return 1;
	}

	/**
	 * Converts a log file from .log to .log.php
	 *
	 * @param   string  $filePath
	 *
	 * @return  void
	 *
	 * @since   7.0.3
	 */
	protected function convertLogFile($filePath)
	{
		// The name of the converted log file is the same with the extension .php appended to it.
		$newFile = $filePath . '.php';

		// If the new log file exists I should return immediately
		if (@file_exists($newFile))
		{
			return;
		}

		// Try to open the converted log file (.log.php)
		$fp = @fopen($newFile, 'w');

		if ($fp === false)
		{
			return;
		}

		// Try to open the source log file (.log)
		$sourceFP = @fopen($filePath, 'r');

		if ($sourceFP === false)
		{
			@fclose($fp);

			return;
		}

		// Write the die statement to the source log file
		fwrite($fp, '<' . '?' . 'php die(); ' . '?' . ">\n");

		// Copy data, 512KB at a time
		while (!feof($sourceFP))
		{
			$chunk = @fread($sourceFP, 524288);

			if ($chunk === false)
			{
				break;
			}

			$result = fwrite($fp, $chunk);

			if ($result === false)
			{
				break;
			}
		}

		// Close both files
		@fclose($sourceFP);
		@fclose($fp);

		// Delete the original (.log) file
		@unlink($filePath);
	}

	/**
	 * Get a new backup ID string.
	 *
	 * In the past we were trying to get the next backup record ID using two methods:
	 * - Querying the information_schema.tables metadata table. In many cases we saw this returning the wrong value,
	 *   even though the MySQL documentation said this should return the next autonumber (WTF?)
	 * - Doing a MAX(id) on the table and adding 1. This didn't work correctly if the latest records were deleted by the
	 *   user.
	 *
	 * However, the backup ID does not need to be the same as the backup record ID. It only needs to be *unique*. So
	 * this time around we are using a simple, unique ID based on the current GMT date and time.
	 *
	 * @return  string
	 */
	private function getBackupId(): string
	{
		$microtime    = explode(' ', microtime(false));
		$microseconds = (int) ($microtime[0] * 1000000);

		return 'id-' . gmdate('Ymd-His') . '-' . $microseconds;
	}

	/**
	 * Get the default backup description.
	 *
	 * The default description is "Backup taken on DATE TIME" where DATE TIME is the current timestamp in the most
	 * specific timezone. The timezone order, from least to most specific, is:
	 * * UTC (fallback)
	 * * Server Timezone from Joomla's Global Configuration
	 * * Timezone from the current user's profile (only applicable to backend backups)
	 * * Forced backup timezone
	 *
	 * @param   string  $format  Date and time format. Default: DATE_FORMAT_LC2 plus the abbreviated timezone
	 *
	 * @return  string
	 */
	public function getDefaultDescription(string $format = ''): string
	{
		// If no date format is specified we use DATE_FORMAT_LC2 plus the abbreviated timezone
		if (empty($format))
		{
			$format = Text::_('DATE_FORMAT_LC2') . ' T';
		}

		// Get the most specific Joomla timezone (UTC, overridden by server timezone, overridden by user timezone)
		$joomlaTimezone = $this->container->platform->getConfig()->get('offset', 'UTC');

		if (!$this->getContainer()->platform->isCli())
		{
			$user = $this->container->platform->getUser();

			if (!$user->guest)
			{
				$joomlaTimezone = $user->getParam('timezone', $joomlaTimezone);
			}
		}

		$timezone = $joomlaTimezone;

		// The forced timezone overrides everything else
		$forcedTZ = Platform::getInstance()->get_platform_configuration_option('forced_backup_timezone', 'AKEEBA/DEFAULT');

		if (!empty($forcedTZ) && ($forcedTZ != 'AKEEBA/DEFAULT'))
		{
			$timezone = $forcedTZ;
		}

		// Convert the current date and time to the selected timezone
		$dateNow = new Date();
		$tz      = new DateTimeZone($timezone);

		$dateNow->setTimezone($tz);

		return Text::_('COM_AKEEBA_BACKUP_DEFAULT_DESCRIPTION') . ' ' . $dateNow->format($format, true);
	}

}
PK�!]��ѡ��Model/.htaccessnu&1i�<IfModule !mod_authz_core.c>
Order deny,allow
Deny from all
</IfModule>
<IfModule mod_authz_core.c>
  <RequireAll>
    Require all denied
  </RequireAll>
</IfModule>
PK�!]��ռOFOFModel/Statistics.phpnu&1i�<?php
/**
 * @package   akeebabackup
 * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

namespace Akeeba\Backup\Admin\Model;

// Protect from unauthorized access
defined('_JEXEC') || die();

use Akeeba\Backup\Admin\Model\Exceptions\FrozenRecordError;
use Akeeba\Engine\Factory;
use Akeeba\Engine\Platform;
use Exception;
use FOF40\Container\Container;
use FOF40\Date\Date;
use FOF40\Model\DataModel\Exception\RecordNotLoaded;
use FOF40\Model\Model;
use Joomla\CMS\Access\Access;
use Joomla\CMS\Factory as JFactory;
use Joomla\CMS\Filesystem\File;
use Joomla\CMS\Language\Text;
use Joomla\CMS\Pagination\Pagination;
use Joomla\CMS\User\User;
use RuntimeException;

class Statistics extends Model
{
	/**
	 * The JPagination object, used in the GUI
	 *
	 * @var  Pagination
	 */
	private $pagination;

	/**
	 * Public constructor.
	 *
	 * @param   Container  $container  The configuration variables to this model
	 * @param   array      $config     Configuration values for this model
	 */
	public function __construct(Container $container, array $config)
	{
		$defaultConfig = [
			'tableName'   => '#__ak_stats',
			'idFieldName' => 'id',
		];

		if (!is_array($config) || empty($config))
		{
			$config = [];
		}

		$config = array_merge($defaultConfig, $config);

		parent::__construct($container, $config);

		$platform     = $this->container->platform;
		$defaultLimit = $platform->getConfig()->get('list_limit', 10);

		if ($platform->isCli())
		{
			$limit      = $this->input->getInt('limit', $defaultLimit);
			$limitstart = $this->input->getInt('limitstart', 0);
		}
		else
		{
			$limit      = $platform->getUserStateFromRequest('global.list.limit', 'limit', $this->input, $defaultLimit);
			$limitstart = $platform->getUserStateFromRequest('com_akeeba.stats.limitstart', 'limitstart', $this->input, 0);
		}

		if ($platform->isFrontend())
		{
			$limit      = 0;
			$limitstart = 0;
		}

		// Set the page pagination variables
		$this->setState('limit', $limit);
		$this->setState('limitstart', $limitstart);
	}

	/**
	 * Is this string a valid remote filename?
	 *
	 * We've had reports that some servers return a bogus, non-empty string for some remote_filename columns, causing
	 * the "Manage remote stored files" column to appear even for locally stored files. By applying more rigorous tests
	 * for the remote_filename column we can avoid this problem.
	 *
	 * @param   string|null  $filename
	 *
	 * @return  bool
	 *
	 * @since   8.1.4
	 */
	public function isRemoteFilename(string $filename = null): bool
	{
		// A remote filename has to be a string which is does not consist solely of whitespace
		if (!is_string($filename) || trim($filename) === '')
		{
			return false;
		}

		// Let's remote whitespace just in case
		$filename = trim($filename);

		// A remote filename must be in the format engine://path
		if (strpos($filename, '://') === false)
		{
			return false;
		}

		// Get the engine and path
		[$engine, $path] = explode('://', $filename, 2);
		$engine = trim($engine);
		$path   = trim($path);

		// Both engine and path must be non-empty
		if (empty($engine) || empty($path))
		{
			return false;
		}

		// The engine must be known to the backup engine
		$classname = 'Akeeba\\Engine\\Postproc\\' . ucfirst($engine);

		return class_exists($classname);
	}

	/**
	 * Returns the same list as getStatisticsList(), but includes an extra field
	 * named 'meta' which categorises attempts based on their backup archive status
	 *
	 * @param   bool   $overrideLimits  Should I disregard limit, limitStart and filters?
	 * @param   array  $filters         Filters to apply. See Platform::get_statistics_list
	 * @param   array  $order           Results ordering. The accepted keys are by (column name) and order (ASC or DESC)
	 *
	 * @return  array  An array of arrays. Each inner array is one backup record.
	 */
	public function &getStatisticsListWithMeta($overrideLimits = false, $filters = null, $order = null)
	{
		$limitstart = $overrideLimits ? 0 : $this->getState('limitstart', 0);
		$limit      = $overrideLimits ? 0 : $this->getState('limit', 10);
		$filters    = $overrideLimits ? null : $filters;

		if (is_array($order) && isset($order['order']))
		{
			$order['order'] = strtoupper($order['order']) === 'ASC' ? 'asc' : 'desc';
		}

		$allStats = Platform::getInstance()->get_statistics_list([
			'limitstart' => $limitstart,
			'limit'      => $limit,
			'filters'    => $filters,
			'order'      => $order,
		]);

		$validRecords          = Platform::getInstance()->get_valid_backup_records() ?: [];
		$updateObsoleteRecords = [];
		$ret                   = array_map(function (array $stat) use (&$updateObsoleteRecords, $validRecords) {
			$hasRemoteFiles = false;

			// Translate backup status and the existence of a remote filename to the backup record's "meta" status.
			switch ($stat['status'])
			{
				case 'run':
					$stat['meta'] = 'pending';
					break;

				case 'fail':
					$stat['meta'] = 'fail';
					break;

				default:
					$hasRemoteFiles = $this->isRemoteFilename($stat['remote_filename']);
					$stat['meta']   = $hasRemoteFiles ? 'remote' : 'obsolete';
					break;
			}

			$stat['hasRemoteFiles'] = $hasRemoteFiles;

			// If the backup is reported to have files still stored on the server we need to investigate further
			if (in_array($stat['id'], $validRecords))
			{
				$archives      = Factory::getStatistics()->get_all_filenames($stat);
				$hasLocalFiles = (is_array($archives) ? count($archives) : 0) > 0;
				$stat['meta']  = $hasLocalFiles ? 'ok' : ($hasRemoteFiles ? 'remote' : 'obsolete');

				// The archives exist. Set $stat['size'] to the total size of the backup archives.
				if ($hasLocalFiles)
				{
					$stat['size'] = $stat['total_size']
						?: array_reduce(
							$archives,
							function ($carry, $filename) {
								return $carry += @filesize($filename) ?: 0;
							},
							0
						);

					return $stat;
				}

				// The archives do not exist or we can't find them. If the record says otherwise we need to update it.
				if ($stat['filesexist'])
				{
					$updateObsoleteRecords[] = $stat['id'];
				}

				// Does the backup record report a total size even though our files no longer exist?
				if ($stat['total_size'])
				{
					$stat['size'] = $stat['total_size'];
				}
			}

			return $stat;
		}, $allStats);

		// Update records which report that their files exist on the server but, in fact, they don't.
		Platform::getInstance()->invalidate_backup_records($updateObsoleteRecords);

		return $ret;
	}

	/**
	 * Send an email notification for failed backups
	 *
	 * @return  array  See the CLI script
	 */
	public function notifyFailed()
	{
		// Invalidate stale backups
		try
		{
			Factory::resetState([
				'global' => true,
				'log'    => false,
				'maxrun' => $this->container->params->get('failure_timeout', 180),
			]);
		}
		catch (Exception $e)
		{
			// This will die if the output directory is invalid. Let it die, then.
		}

		// Get the last execution and search for failed backups AFTER that date
		$last = $this->getLastCheck();

		// Get failed backups
		$filters = [
			['field' => 'status', 'operand' => '=', 'value' => 'fail'],
			['field' => 'backupstart', 'operand' => '>', 'value' => $last],
		];

		$failed = Platform::getInstance()->get_statistics_list(['filters' => $filters]);

		// Well, everything went ok.
		if (!$failed)
		{
			return [
				'message' => ["No need to run: no failed backups or notifications were already sent."],
				'result'  => true,
			];
		}

		// Whops! Something went wrong, let's start notifing
		$superAdmins     = [];
		$superAdminEmail = $this->container->params->get('failure_email_address', '');

		if (!empty($superAdminEmail))
		{
			$superAdmins = $this->getSuperUsers($superAdminEmail);
		}

		if (empty($superAdmins))
		{
			$superAdmins = $this->getSuperUsers();
		}

		if (empty($superAdmins))
		{
			return [
				'message' => ["WARNING! Failed backup(s) detected, but there are no configured Super Administrators to receive notifications"],
				'result'  => false,
			];
		}

		$failedReport = [];

		foreach ($failed as $fail)
		{
			$string = "Description : " . $fail['description'] . "\n";
			$string .= "Start time  : " . $fail['backupstart'] . "\n";
			$string .= "Origin      : " . $fail['origin'] . "\n";
			$string .= "Type        : " . $fail['type'] . "\n";
			$string .= "Profile ID  : " . $fail['profile_id'] . "\n";
			$string .= "Backup ID   : " . $fail['id'];

			$failedReport[] = $string;
		}

		$failedReport = implode("\n#-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+#\n", $failedReport);

		$email_subject = $this->container->params->get('failure_email_subject', '');

		if (!$email_subject)
		{
			$email_subject = <<<ENDSUBJECT
THIS EMAIL IS SENT FROM YOUR SITE "[SITENAME]" - Failed backup(s) detected
ENDSUBJECT;
		}

		$email_body = $this->container->params->get('failure_email_body', '');

		if (!$email_body)
		{
			$email_body = <<<ENDBODY
================================================================================
FAILED BACKUP ALERT
================================================================================

Your site has determined that there are failed backups.

The following backups are found to be failing:

[FAILEDLIST]

================================================================================
WHY AM I RECEIVING THIS EMAIL?
================================================================================

This email has been automatically sent by scritp you, or the person who built
or manages your site, has installed and explicitly configured. This script looks
for failed backups and sends an email notification to all Super Users.

If you do not understand what this means, please do not contact the authors of
the software. They are NOT sending you this email and they cannot help you.
Instead, please contact the person who built or manages your site.

================================================================================
WHO SENT ME THIS EMAIL?
================================================================================

This email is sent to you by your own site, [SITENAME]

ENDBODY;
		}

		$jconfig = $this->container->platform->getConfig();

		$mailfrom = $jconfig->get('mailfrom');
		$fromname = $jconfig->get('fromname');

		$email_subject = Factory::getFilesystemTools()->replace_archive_name_variables($email_subject);
		$email_body    = Factory::getFilesystemTools()->replace_archive_name_variables($email_body);
		$email_body    = str_replace('[FAILEDLIST]', $failedReport, $email_body);

		foreach ($superAdmins as $sa)
		{
			try
			{
				$mailer = JFactory::getMailer();

				$mailer->setSender([$mailfrom, $fromname]);
				$mailer->addRecipient($sa->email);
				$mailer->setSubject($email_subject);
				$mailer->setBody($email_body);
				$mailer->Send();
			}
			catch (Exception $e)
			{
				// Joomla! 3.5 is written by incompetent bonobos
			}
		}

		// Let's update the last time we check, so we will avoid to send
		// the same notification several times
		$this->updateLastCheck(intval($last));

		return [
			'message' => [
				"WARNING! Found " . count($failed) . " failed backup(s)",
				"Sent " . count($superAdmins) . " notifications",
			],
			'result'  => true,
		];
	}

	/**
	 * Delete the backup statistics record whose ID is set in the model
	 *
	 * @return  bool  True on success
	 */
	public function delete()
	{
		$db = $this->container->db;

		$id = $this->getState('id', 0);

		if ((!is_numeric($id)) || ($id <= 0))
		{
			throw new RecordNotLoaded(Text::_('COM_AKEEBA_BUADMIN_ERROR_INVALIDID'));
		}

		// Try to delete files. This will check (and stop) if any record is a frozen one
		$this->deleteFile();

		if (!Platform::getInstance()->delete_statistics($id))
		{
			throw new RuntimeException($db->getError(), 500);
		}

		return true;
	}

	/**
	 * Delete the backup file of the stats record whose ID is set in the model
	 *
	 * @return  bool  True on success
	 */
	public function deleteFile()
	{
		$id = $this->getState('id', 0);

		if ((!is_numeric($id)) || ($id <= 0))
		{
			throw new RecordNotLoaded(Text::_('COM_AKEEBA_BUADMIN_ERROR_INVALIDID'));
		}

		// Get the backup statistics record and the files to delete
		$stat     = (array) Platform::getInstance()->get_statistics($id);

		if ($stat['frozen'])
		{
			throw new FrozenRecordError(Text::_('COM_AKEEBA_BUADMIN_FROZENRECORD_ERROR'));
		}

		$allFiles = Factory::getStatistics()->get_all_filenames($stat, false);

		// Remove the custom log file if necessary
		$this->deleteLogs($stat);

		// No files? Nothing to do.
		if (empty($allFiles))
		{
			return true;
		}

		$status = true;

		foreach ($allFiles as $filename)
		{
			if (!@file_exists($filename))
			{
				continue;
			}

			$new_status = @unlink($filename);

			if (!$new_status)
			{
				$new_status = File::delete($filename);
			}

			$status = $status ? $new_status : false;
		}

		return $status;
	}

	/**
	 * Get a Joomla! pagination object
	 *
	 * @param   array  $filters  Filters to apply. See Platform::get_statistics_list
	 *
	 * @return  Pagination
	 */
	public function &getPagination($filters = null)
	{
		if (empty($this->pagination))
		{
			// Prepare pagination values
			$total      = Platform::getInstance()->get_statistics_count($filters);
			$limitstart = $this->getState('limitstart', 0);
			$limit      = $this->getState('limit', 10);

			// Create the pagination object
			$this->pagination = new Pagination($total, $limitstart, $limit);
		}

		return $this->pagination;
	}

	/**
	 * Set the flag to hide the restoration instructions modal from the Manage Backups page
	 *
	 * @return  void
	 */
	public function hideRestorationInstructionsModal()
	{
		$this->container->params->set('show_howtorestoremodal', 0);
		$this->container->params->save();
	}

	/**
	 * Freeze or melt a backup report
	 *
	 * @param array $ids        Array of backup IDs that should be updated
	 * @param int   $freeze     1= freeze, 0= melt
	 *
	 * @throws Exception
	 */
	public function freezeUnfreezeRecords(array $ids, $freeze)
	{
		if (!$ids)
		{
			return;
		}

		$freeze = (int) $freeze;

		foreach ($ids as $id)
		{
			// If anything wrong happens, let the exception bubble up, so it will be reported
			Platform::getInstance()->set_or_update_statistics($id, ['frozen' => $freeze]);
		}
	}

	/**
	 * Deletes the backup-specific log files of a stats record
	 *
	 * @param   array  $stat  The array holding the backup stats record
	 *
	 * @return  void
	 */
	protected function deleteLogs(array $stat)
	{
		// We can't delete logs if there is no backup ID in the record
		if (!isset($stat['backupid']) || empty($stat['backupid']))
		{
			return;
		}

		$logFileNames = [
			'akeeba.' . $stat['tag'] . '.' . $stat['backupid'] . '.log',
			'akeeba.' . $stat['tag'] . '.' . $stat['backupid'] . '.log.php',
		];

		foreach ($logFileNames as $logFileName)
		{
			$logPath = dirname($stat['absolute_path']) . '/' . $logFileName;

			if (@file_exists($logPath))
			{
				if (!@unlink($logPath))
				{
					File::delete($logPath);
				}
			}
		}
	}

	/**
	 * Returns the Super Users' email information. If you provide a comma separated $email list we will check that these
	 * emails do belong to Super Users and that they have not blocked reception of system emails.
	 *
	 * @param   null|string  $email  A list of Super Users to email, null for all Super Users
	 *
	 * @return  User[]  The list of Super User objects
	 */
	private function getSuperUsers($email = null)
	{
		// Convert the email list to an array
		$emails = [];

		if (!empty($email))
		{
			$temp   = explode(',', $email);
			$emails = [];

			foreach ($temp as $entry)
			{
				$emails[] = trim($entry);
			}

			$emails = array_unique($emails);
			$emails = array_map('strtolower', $emails);
		}

		// Get all usergroups with Super User access
		$db     = $this->getContainer()->db;
		$q      = $db->getQuery(true)
			->select([$db->qn('id')])
			->from($db->qn('#__usergroups'));
		$groups = $db->setQuery($q)->loadColumn();

		// Get the groups that are Super Users
		$groups = array_filter($groups, function ($gid) {
			return Access::checkGroup($gid, 'core.admin');
		});

		$userList = [];

		foreach ($groups as $gid)
		{
			$uids = Access::getUsersByGroup($gid);

			array_walk($uids, function ($uid, $index) use (&$userList) {
				$userList[$uid] = $this->container->platform->getUser($uid);
			});
		}

		if (empty($emails))
		{
			return $userList;
		}

		$userList = array_filter($userList, function (User $user) use ($emails) {
			return in_array(strtolower($user->email), $emails);
		});

		return $userList;
	}

	/**
	 * Update the time we last checked for failed backups
	 *
	 * @param   int  $exists  Any non zero value means that we update, not insert, the record
	 *
	 * @return  void
	 */
	private function updateLastCheck($exists)
	{
		$db = $this->container->db;

		$now      = new Date();
		$nowToSql = $now->toSql();

		$query = $db->getQuery(true)
			->insert($db->qn('#__ak_storage'))
			->columns([$db->qn('tag'), $db->qn('lastupdate')])
			->values($db->q('akeeba_checkfailed') . ', ' . $db->q($nowToSql));

		if ($exists)
		{
			$query = $db->getQuery(true)
				->update($db->qn('#__ak_storage'))
				->set($db->qn('lastupdate') . ' = ' . $db->q($nowToSql))
				->where($db->qn('tag') . ' = ' . $db->q('akeeba_checkfailed'));
		}

		try
		{
			$db->setQuery($query)->execute();
		}
		catch (Exception $exc)
		{
		}
	}

	/**
	 * Get the last update check date and time stamp
	 *
	 * @return  string
	 */
	private function getLastCheck()
	{
		$db = $this->container->db;

		$query = $db->getQuery(true)
			->select($db->qn('lastupdate'))
			->from($db->qn('#__ak_storage'))
			->where($db->qn('tag') . ' = ' . $db->q('akeeba_checkfailed'));

		$datetime = $db->setQuery($query)->loadResult();

		if (!intval($datetime))
		{
			$datetime = $db->getNullDate();
		}

		return $datetime;
	}
}
PK�!]i"�L��Model/Browser.phpnu&1i�<?php
/**
 * @package   akeebabackup
 * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

namespace Akeeba\Backup\Admin\Model;

// Protect from unauthorized access
defined('_JEXEC') || die();

use Akeeba\Engine\Factory;
use Akeeba\Engine\Platform;
use FOF40\Model\Model;
use JLoader;
use Joomla\CMS\Filesystem\Folder;

class Browser extends Model
{
	/**
	 * Initialises the directory listing. All results are stored in model state variables.
	 */
	function makeListing()
	{
		// Get the folder to browse
		$folder        = $this->getState('folder', '');
		$processfolder = $this->getState('processfolder', 0);

		if (empty($folder))
		{
			$folder = JPATH_SITE;
		}

		$stock_dirs = Platform::getInstance()->get_stock_directories();
		arsort($stock_dirs);

		if ($processfolder == 1)
		{
			foreach ($stock_dirs as $find => $replace)
			{
				$folder = str_replace($find, $replace, $folder);
			}
		}

		// Normalise name, but only if realpath() really, REALLY works...
		$old_folder = $folder;
		$folder     = @realpath($folder);

		if ($folder === false)
		{
			$folder = $old_folder;
		}

		$isFolderThere = @is_dir($folder);

		// Check if it's a subdirectory of the site's root
		$isInRoot = (strpos($folder, JPATH_SITE) === 0);

		// Check open_basedir restrictions
		$isOpenbasedirRestricted = Factory::getConfigurationChecks()->checkOpenBasedirs($folder);

		// -- Get the meta form of the directory name, if applicable
		$folder_raw = $folder;

		foreach ($stock_dirs as $replace => $find)
		{
			$folder_raw = str_replace($find, $replace, $folder_raw);
		}

		$isWritable = false;
		$subfolders = [];

		if ($isFolderThere && !$isOpenbasedirRestricted)
		{
			$isWritable = is_writable($folder);
			$subfolders = Folder::folders($folder);
		}

		// In case we can't identify the parent folder, use ourselves.
		$parent      = $folder;
		$breadcrumbs = [];

		// Try to get the parent directory
		$pathparts = explode(DIRECTORY_SEPARATOR, $folder);

		if (is_array($pathparts))
		{
			$path = '';

			foreach ($pathparts as $part)
			{
				$path .= empty($path) ? $part : DIRECTORY_SEPARATOR . $part;

				if (empty($part))
				{
					if (DIRECTORY_SEPARATOR != '\\')
					{
						$path = DIRECTORY_SEPARATOR;
					}

					$part = DIRECTORY_SEPARATOR;
				}

				$breadcrumbs[] = [
					'label'  => $part,
					'folder' => $path,
				];
			}

			$junk   = array_pop($pathparts);
			$parent = implode(DIRECTORY_SEPARATOR, $pathparts);
		}

		$this->setState('folder', $folder);
		$this->setState('folder_raw', $folder_raw);
		$this->setState('parent', $parent);
		$this->setState('exists', $isFolderThere);
		$this->setState('inRoot', $isInRoot);
		$this->setState('openbasedirRestricted', $isOpenbasedirRestricted);
		$this->setState('writable', $isWritable);
		$this->setState('subfolders', $subfolders);
		$this->setState('breadcrumbs', $breadcrumbs);
	}
}
PK�!]��aaModel/ControlPanel.phpnu&1i�<?php
/**
 * @package   akeebabackup
 * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

namespace Akeeba\Backup\Admin\Model;

// Protect from unauthorized access
defined('_JEXEC') || die();

use Akeeba\Backup\Admin\Helper\SecretWord;
use Akeeba\Backup\Admin\Model\Mixin\Chmod;
use Akeeba\Engine\Factory;
use Akeeba\Engine\Platform;
use Akeeba\Engine\Util\Complexify;
use Akeeba\Engine\Util\RandomValue;
use FOF40\Database\Installer;
use FOF40\Download\Download;
use FOF40\Model\Model;
use Joomla\CMS\Filesystem\File;
use Joomla\CMS\Filesystem\Folder;
use Joomla\CMS\Http\HttpFactory;
use Joomla\CMS\Http\Transport\CurlTransport;
use Joomla\CMS\Http\Transport\StreamTransport;
use Joomla\CMS\Uri\Uri;
use Joomla\Registry\Registry;
use RuntimeException;
use stdClass;

/**
 * ControlPanel model. Generic maintenance tasks used mainly from the ControlPanel page.
 */
class ControlPanel extends Model
{
	use Chmod;

	protected static $systemFolders = [
		'administrator',
		'administrator/cache/',
		'administrator/components/',
		'administrator/help/',
		'administrator/includes/',
		'administrator/language/',
		'administrator/logs/',
		'administrator/manifests/',
		'administrator/modules/',
		'administrator/templates/',
		'cache/',
		'cli/',
		'components/',
		'images/',
		'includes/',
		'language/',
		'layouts/',
		'libraries/',
		'media/',
		'modules/',
		'plugins/',
		'templates/',
		'tmp/',
	];

	/**
	 * Gets a list of profiles which will be displayed as quick icons in the interface
	 *
	 * @return  stdClass[]  Array of objects; each has the properties `id` and `description`
	 */
	public function getQuickIconProfiles()
	{
		$db = $this->container->db;

		$query = $db->getQuery(true)
			->select([
				$db->qn('id'),
				$db->qn('description'),
			])->from($db->qn('#__ak_profiles'))
			->where($db->qn('quickicon') . ' = ' . $db->q(1))
			->order($db->qn('id') . " ASC");

		$db->setQuery($query);

		$ret = $db->loadObjectList();

		if (empty($ret))
		{
			$ret = [];
		}

		return $ret;
	}

	/**
	 * Creates an icon definition entry
	 *
	 * @param   string  $iconFile  The filename of the icon on the GUI button
	 * @param   string  $label     The label below the GUI button
	 * @param   string  $view      The view to fire up when the button is clicked
	 *
	 * @return  array  The icon definition array
	 */
	public function _makeIconDefinition($iconFile, $label, $view = null, $task = null)
	{
		return [
			'icon'  => $iconFile,
			'label' => $label,
			'view'  => $view,
			'task'  => $task,
		];
	}

	/**
	 * Was the last backup a failed one? Used to apply magic settings as a means of troubleshooting.
	 *
	 * @return  bool
	 */
	public function isLastBackupFailed()
	{
		// Get the last backup record ID
		$list = Platform::getInstance()->get_statistics_list(['limitstart' => 0, 'limit' => 1]);

		if (empty($list))
		{
			return false;
		}

		$id = $list[0];

		$record = Platform::getInstance()->get_statistics($id);

		return ($record['status'] == 'fail');
	}

	/**
	 * Checks that the media permissions are oh seven double five for directories and oh six double four for files and
	 * fixes them if they are incorrect.
	 *
	 * @param   bool  $force  Forcibly check subresources, even if the parent has correct permissions
	 *
	 * @return  bool  False if we couldn't figure out what's going on
	 */
	public function fixMediaPermissions($force = false)
	{
		// Are we on Windows?
		$isWindows = (DIRECTORY_SEPARATOR == '\\');

		if (function_exists('php_uname'))
		{
			$isWindows = stristr(php_uname(), 'windows');
		}

		// No point changing permissions on Windows, as they have ACLs
		if ($isWindows)
		{
			return true;
		}

		// Check the parent permissions
		$parent      = JPATH_ROOT . '/media/com_akeeba';
		$parentPerms = fileperms($parent);

		// If we can't determine the parent's permissions, bail out
		if ($parentPerms === false)
		{
			return false;
		}

		// Fooling some broken file scanners.
		$ohSevenFiveFive          = 500 - 7;
		$ohFourOhSevenFiveFive    = 16000 + 900 - 23;
		$ohSixFourFour            = 450 - 30;
		$ohOneDoubleOhSixFourFour = 33000 + 200 - 12;

		// Fix the parent's permissions if required
		if (($parentPerms != $ohSevenFiveFive) && ($parentPerms != $ohFourOhSevenFiveFive))
		{
			$this->chmod($parent, $ohSevenFiveFive);
		}
		elseif (!$force)
		{
			return true;
		}

		// During development we use symlinks and we don't wanna see that big fat warning
		if (@is_link($parent))
		{
			return true;
		}

		$result = true;

		// Loop through subdirectories
		$folders = Folder::folders($parent, '.', 3, true);

		foreach ($folders as $folder)
		{
			$perms = fileperms($folder);

			if (($perms != $ohSevenFiveFive) && ($perms != $ohFourOhSevenFiveFive))
			{
				$result &= $this->chmod($folder, $ohSevenFiveFive);
			}
		}

		// Loop through files
		$files = Folder::files($parent, '.', 3, true);

		foreach ($files as $file)
		{
			$perms = fileperms($file);

			if (($perms != $ohSixFourFour) && ($perms != $ohOneDoubleOhSixFourFour))
			{
				$result &= $this->chmod($file, $ohSixFourFour);
			}
		}

		return $result;
	}

	/**
	 * Checks if we should enable settings encryption and applies the change
	 *
	 * @return  void
	 */
	public function checkSettingsEncryption()
	{
		// Do we have a key file?
		$filename = JPATH_COMPONENT_ADMINISTRATOR . '/BackupEngine/serverkey.php';

		if (File::exists($filename))
		{
			// We have a key file. Do we need to disable it?
			if ($this->container->params->get('useencryption', -1) == 0)
			{
				// User asked us to disable encryption. Let's do it.
				$this->disableSettingsEncryption();
			}

			return;
		}

		if (!Factory::getSecureSettings()->supportsEncryption())
		{
			return;
		}

		if ($this->container->params->get('useencryption', -1) != 0)
		{
			// User asked us to enable encryption (or he left us with the default setting!). Let's do it.
			$this->enableSettingsEncryption();
		}
	}

	/**
	 * Updates some internal settings:
	 *
	 * - The stored URL of the site, used for the front-end backup feature (altbackup.php)
	 * - The detected Joomla! libraries path
	 * - Marks all existing profiles as configured, if necessary
	 */
	public function updateMagicParameters()
	{
		if (!$this->container->params->get('confwiz_upgrade', 0))
		{
			$this->markOldProfilesConfigured();
		}

		$this->container->params->set('confwiz_upgrade', 1);
		$this->container->params->set('siteurl', str_replace('/administrator', '', Uri::base()));
		$this->container->params->set('jlibrariesdir', Factory::getFilesystemTools()->TranslateWinPath(JPATH_LIBRARIES));
		$this->container->params->save();
	}

	/**
	 * Do you have to issue a warning that setting the Download ID in the CORE edition has no effect?
	 *
	 * @return  bool  True if you need to show the warning
	 */
	public function mustWarnAboutDownloadIDInCore()
	{
		/** @var Updates $updateModel */
		$updateModel = $this->container->factory->model('Updates')->tmpInstance();
		$isPro       = defined('AKEEBA_PRO') ? AKEEBA_PRO : 0;

		if ($isPro)
		{
			return false;
		}

		$dlid = $updateModel->sanitizeLicenseKey($updateModel->getLicenseKey());

		return $updateModel->isValidLicenseKey($dlid);
	}

	/**
	 * Does the user need to enter a Download ID in the component's Options page?
	 *
	 * @return  bool
	 */
	public function needsDownloadID()
	{
		/** @var Updates $updateModel */
		$updateModel = $this->container->factory->model('Updates')->tmpInstance();

		// Migrate J3 to J4 settings
		$updateModel->upgradeLicenseKey();

		// Save the J4 license key in the component options, if necessary
		$updateModel->backportLicenseKey();

		// Do I need a Download ID?
		$isPro = defined('AKEEBA_PRO') ? AKEEBA_PRO : 0;

		if (!$isPro)
		{
			return false;
		}

		$dlid = $updateModel->sanitizeLicenseKey($updateModel->getLicenseKey());

		return !$updateModel->isValidLicenseKey($dlid);
	}

	/**
	 * Checks the database for missing / outdated tables and runs the appropriate SQL scripts if necessary.
	 *
	 * @return  $this
	 * @throws  RuntimeException    If the previous database update is stuck
	 */
	public function checkAndFixDatabase()
	{
		$params = $this->container->params;

		// First of all let's check if we are already updating
		$stuck = $params->get('updatedb', 0);

		if ($stuck)
		{
			throw new RuntimeException('Previous database update is flagged as stuck');
		}

		// Then set the flag
		$params->set('updatedb', 1);
		$params->save();

		// Install or update database
		$dbInstaller = new Installer(
			$this->container->db,
			JPATH_ADMINISTRATOR . '/components/com_akeeba/sql/xml'
		);

		$dbInstaller->updateSchema();

		// And finally remove the flag if everything went fine
		$params->set('updatedb', null);
		$params->save();

		return $this;
	}

	/**
	 * Akeeba Backup 4.3.2 displays a popup if your profile is not already configured by Configuration Wizard, the
	 * Configuration page or imported from the Profiles page. This bit of code makes sure that existing profiles will
	 * be marked as already configured just the FIRST time you upgrade to the new version from an old version.
	 *
	 * @return  void
	 */
	public function markOldProfilesConfigured()
	{
		// Get all profiles
		$db = $this->container->db;

		$query = $db->getQuery(true)
			->select([
				$db->qn('id'),
			])->from($db->qn('#__ak_profiles'))
			->order($db->qn('id') . " ASC");
		$db->setQuery($query);
		$profiles = $db->loadColumn();

		// Save the current profile number
		$oldProfile = $this->container->platform->getSessionVar('profile', 1, 'akeeba');

		// Update all profiles
		foreach ($profiles as $profile_id)
		{
			Factory::nuke();
			Platform::getInstance()->load_configuration($profile_id);
			$config = Factory::getConfiguration();
			$config->set('akeeba.flag.confwiz', 1);
			Platform::getInstance()->save_configuration($profile_id);
		}

		// Restore the old profile
		Factory::nuke();
		Platform::getInstance()->load_configuration($oldProfile);
	}

	/**
	 * Check the strength of the Secret Word for front-end and remote backups. If it is insecure return the reason it
	 * is insecure as a string. If the Secret Word is secure return an empty string.
	 *
	 * @return  string
	 */
	public function getFrontendSecretWordError()
	{
		// Is frontend backup enabled?
		$febEnabled =
			($this->container->params->get('legacyapi_enabled', 0) != 0) ||
			($this->container->params->get('jsonapi_enabled', 0) != 0);

		if (!$febEnabled)
		{
			return '';
		}

		$secretWord = Platform::getInstance()->get_platform_configuration_option('frontend_secret_word', '');

		try
		{
			Complexify::isStrongEnough($secretWord);
		}
		catch (RuntimeException $e)
		{
			// Ah, the current Secret Word is bad. Create a new one if necessary.
			$newSecret = $this->container->platform->getSessionVar('newSecretWord', null, 'akeeba');

			if (empty($newSecret))
			{
				$random    = new RandomValue();
				$newSecret = $random->generateString(32);
				$this->container->platform->setSessionVar('newSecretWord', $newSecret, 'akeeba.cpanel');
			}

			return $e->getMessage();
		}

		return '';
	}

	/**
	 * Checks if the mbstring extension is installed and enabled
	 *
	 * @return  bool
	 */
	public function checkMbstring()
	{
		return function_exists('mb_strlen') && function_exists('mb_convert_encoding') &&
			function_exists('mb_substr') && function_exists('mb_convert_case');
	}

	/**
	 * Is the output directory under the configured site root?
	 *
	 * @param   string|null  $outDir  The output directory to check. NULL for the currently configured one.
	 *
	 * @return  bool  True if the output directory is under the site's web root.
	 *
	 * @since   7.0.3
	 */
	public function isOutputDirectoryUnderSiteRoot($outDir = null)
	{
		// Make sure I have an output directory to check
		$outDir = is_null($outDir) ? $this->getOutputDirectory() : $outDir;
		$outDir = @realpath($outDir);

		// If I can't reliably determine the output directory I can't figure out where it's placed in.
		if ($outDir === false)
		{
			return false;
		}

		// Get the site's root
		$siteRoot = $this->getSiteRoot();
		$siteRoot = @realpath($siteRoot);

		// If I can't reliably determine the site's root I can't figure out its relation to the output directory
		if ($siteRoot === false)
		{
			return false;
		}

		return strpos($outDir, $siteRoot) === 0;
	}

	/**
	 * Did the user set up an output directory inside a folder intended for CMS files?
	 *
	 * The idea is that this will cause trouble for two reasons. First, you are mixing user-generated with system
	 * content which might be a REALLY BAD idea in and of itself. Second, some if not all of these folders are meant to
	 * be web-accessible. I cannot possibly protect them against web access without breaking anything.
	 *
	 * @param   string|null  $outDir  The output directory to check. NULL for the currently configured one.
	 *
	 * @return  bool  True if the output directory is inside a CMS system folder
	 *
	 * @since   7.0.3
	 */
	public function isOutputDirectoryInSystemFolder($outDir = null)
	{
		// Make sure I have an output directory to check
		$outDir = is_null($outDir) ? $this->getOutputDirectory() : $outDir;
		$outDir = @realpath($outDir);

		// If I can't reliably determine the output directory I can't figure out where it's placed in.
		if ($outDir === false)
		{
			return false;
		}

		// If the directory is not under the site's root it doesn't belong to the CMS. Simple, huh?
		if (!$this->isOutputDirectoryUnderSiteRoot($outDir))
		{
			return false;
		}

		// Check if we are using the default output directory. This is always allowed.
		$stockDirs     = Platform::getInstance()->get_stock_directories();
		$defaultOutDir = realpath($stockDirs['[DEFAULT_OUTPUT]']);

		// If I can't reliably determine the default output folder I can't figure out its relation to the output folder
		if ($defaultOutDir === false)
		{
			return false;
		}

		// Get the site's root
		$siteRoot = $this->getSiteRoot();
		$siteRoot = @realpath($siteRoot);

		// If I can't reliably determine the site's root I can't figure out its relation to the output directory
		if ($siteRoot === false)
		{
			return false;
		}

		foreach ($this->getSystemFolders() as $folder)
		{
			// Is this a partial or an absolute search?
			$partialSearch = substr($folder, -1) == '/';

			clearstatcache(true);

			$absolutePath = realpath($siteRoot . '/' . $folder);

			if ($absolutePath === false)
			{
				continue;
			}

			if (!$partialSearch)
			{
				if (trim($outDir, '/\\') == trim($absolutePath, '/\\'))
				{
					return true;
				}

				continue;
			}

			// Partial search
			if (strpos($outDir, $absolutePath . DIRECTORY_SEPARATOR) === 0)
			{
				return true;
			}
		}

		return false;
	}

	/**
	 * Does the output directory contain the security-enhancing files?
	 *
	 * This only checks for the presence of .htaccess, web.config, index.php, index.html and index.html but not their
	 * contents. The idea is that an advanced user may want to customise them for some reason or another.
	 *
	 * @param   string|null  $outDir  The output directory to check. NULL for the currently configured one.
	 *
	 * @return  bool  True if all of the security-enhancing files are present.
	 *
	 * @since   7.0.3
	 */
	public function hasOutputDirectorySecurityFiles($outDir = null)
	{
		// Make sure I have an output directory to check
		$outDir = is_null($outDir) ? $this->getOutputDirectory() : $outDir;
		$outDir = @realpath($outDir);

		// If I can't reliably determine the output directory I can't figure out where it's placed in.
		if ($outDir === false)
		{
			return true;
		}

		$files = [
			'.htaccess',
			'web.config',
			'index.php',
			'index.html',
			'index.htm',
		];

		foreach ($files as $file)
		{
			$filePath = $outDir . '/' . $file;

			if (!@file_exists($filePath) || !is_file($filePath))
			{
				return false;
			}
		}

		return true;
	}

	/**
	 * Checks whether the given output directory is directly accessible over the web.
	 *
	 * @param   string|null  $outDir  The output directory to check. NULL for the currently configured one.
	 *
	 * @return  array
	 *
	 * @since   7.0.3
	 */
	public function getOutputDirectoryWebAccessibleState($outDir = null)
	{
		$ret = [
			'readFile'   => false,
			'listFolder' => false,
			'isSystem'   => $this->isOutputDirectoryInSystemFolder(),
			'hasRandom'  => $this->backupFilenameHasRandom(),
		];

		// Make sure I have an output directory to check
		$outDir = is_null($outDir) ? $this->getOutputDirectory() : $outDir;
		$outDir = @realpath($outDir);

		// If I can't reliably determine the output directory I can't figure out its web path
		if ($outDir === false)
		{
			return $ret;
		}

		$checkFile     = $this->getAccessCheckFile($outDir);
		$checkFilePath = $outDir . '/' . $checkFile;

		if (is_null($checkFile))
		{
			return $ret;
		}

		$webPath = $this->getOutputDirectoryWebPath($outDir);

		if (is_null($webPath))
		{
			@unlink($checkFilePath);

			return $ret;
		}

		// Construct a URL for the check file
		$baseURL = rtrim(Uri::base(), '/');

		if (substr($baseURL, -14) == '/administrator')
		{
			$baseURL = substr($baseURL, 0, -14);
		}

		$baseURL  = rtrim($baseURL, '/');
		$checkURL = $baseURL . '/' . $webPath . '/' . $checkFile;

		// Try to download the file's contents
		$options = [
			'follow_location'  => true,
			'transport.curl'   => [
				CURLOPT_SSL_VERIFYPEER => 0,
				CURLOPT_SSL_VERIFYHOST => 0,
				CURLOPT_FOLLOWLOCATION => 1,
				CURLOPT_TIMEOUT        => 10,
			],
			'transport.stream' => [
				'timeout' => 10,
			],
		];

		$adapters = [];

		if (CurlTransport::isSupported())
		{
			$adapters[] = 'Curl';
		}

		if (StreamTransport::isSupported())
		{
			$adapters[] = 'Stream';
		}

		if (empty($adapters))
		{
			return $ret;
		}

		$downloader = HttpFactory::getHttp(new Registry($options), $adapters);

		if ($downloader === false)
		{
			return $ret;
		}

		$response = $downloader->get($checkURL);

		if ($response->body === 'AKEEBA BACKUP WEB ACCESS CHECK')
		{
			$ret['readFile'] = true;
		}

		// Can I list the directory contents?
		$folderURL     = $baseURL . '/' . $webPath . '/';
		$folderListing = $downloader->get($folderURL)->body;

		@unlink($checkFilePath);

		if (!is_null($folderListing) && (strpos($folderListing, basename($checkFile, '.txt')) !== false))
		{
			$ret['listFolder'] = true;
		}

		return $ret;
	}

	/**
	 * Get the web path, relative to the site's root, for the output directory.
	 *
	 * Returns the relative path or NULL if determining it was not possible.
	 *
	 * @param   string|null  $outDir  The output directory to check. NULL for the currently configured one.
	 *
	 * @return  string|null  The relative web path to the output directory
	 *
	 * @since   7.0.3
	 */
	public function getOutputDirectoryWebPath($outDir = null)
	{
		// Make sure I have an output directory to check
		$outDir = is_null($outDir) ? $this->getOutputDirectory() : $outDir;
		$outDir = @realpath($outDir);

		// If I can't reliably determine the output directory I can't figure out its web path
		if ($outDir === false)
		{
			return null;
		}

		// Get the site's root
		$siteRoot = $this->getSiteRoot();
		$siteRoot = @realpath($siteRoot);

		// If I can't reliably determine the site's root I can't figure out its relation to the output directory
		if ($siteRoot === false)
		{
			return null;
		}

		// The output directory is NOT under the site's root.
		if (strpos($outDir, $siteRoot) !== 0)
		{
			return null;
		}

		$relPath = trim(substr($outDir, strlen($siteRoot)), '/\\');
		$isWin   = DIRECTORY_SEPARATOR == '\\';

		if ($isWin)
		{
			$relPath = str_replace('\\', '/', $relPath);
		}

		return $relPath;
	}

	/**
	 * Get the semi-random name of a .txt file used to check the output folder's direct web access.
	 *
	 * If the file does not exist we will create it.
	 *
	 * Returns the file name or NULL if creating it was not possible.
	 *
	 * @param   string|null  $outDir  The output directory to check. NULL for the currently configured one.
	 *
	 * @return  string|null  The base name of the check file
	 *
	 * @since   7.0.3
	 */
	public function getAccessCheckFile($outDir = null)
	{
		// Make sure I have an output directory to check
		$outDir = is_null($outDir) ? $this->getOutputDirectory() : $outDir;
		$outDir = @realpath($outDir);

		// If I can't reliably determine the output directory I can't put a file in it
		if ($outDir === false)
		{
			return null;
		}

		$secureSettings = Factory::getSecureSettings();
		$something      = md5($outDir . $secureSettings->getKey());
		$fileName       = 'akaccesscheck_' . $something . '.txt';
		$filePath       = $outDir . '/' . $fileName;

		$result = @file_put_contents($filePath, 'AKEEBA BACKUP WEB ACCESS CHECK');

		return ($result === false) ? null : $fileName;
	}

	/**
	 * Does the backup filename contain the [RANDOM] variable?
	 *
	 * @return  bool
	 *
	 * @since   7.0.3
	 */
	public function backupFilenameHasRandom()
	{
		$registry     = Factory::getConfiguration();
		$templateName = $registry->get('akeeba.basic.archive_name');

		return strpos($templateName, '[RANDOM]') !== false;
	}

	/**
	 * Return the configured output directory for the currently loaded backup profile
	 *
	 * @return  string
	 * @since   7.0.3
	 */
	public function getOutputDirectory()
	{
		$registry = Factory::getConfiguration();

		return $registry->get('akeeba.basic.output_directory', '[DEFAULT_OUTPUT]', true);
	}

	/**
	 * Return the currently configured site root directory
	 *
	 * @return  string
	 * @since   7.0.3
	 */
	protected function getSiteRoot()
	{
		return Platform::getInstance()->get_site_root();
	}

	/**
	 * Return the list of system folders, relative to the site's root
	 *
	 * @return  array
	 * @since   7.0.3
	 */
	protected function getSystemFolders()
	{
		return self::$systemFolders;
	}

	/**
	 * Disables the encryption of profile settings. If the settings were already encrypted they are automatically
	 * decrypted.
	 *
	 * @return  void
	 */
	private function disableSettingsEncryption()
	{
		// Load the server key file if necessary

		$filename = JPATH_COMPONENT_ADMINISTRATOR . '/BackupEngine/serverkey.php';
		$key      = Factory::getSecureSettings()->getKey();

		// Loop all profiles and decrypt their settings
		/** @var Profiles $profilesModel */
		$profilesModel = $this->container->factory->model('Profiles')->tmpInstance();
		$profiles      = $profilesModel->get(true);
		$db            = $this->container->db;

		/** @var Profiles $profile */
		foreach ($profiles as $profile)
		{
			$id     = $profile->getId();
			$config = Factory::getSecureSettings()->decryptSettings($profile->configuration, $key);
			$sql    = $db->getQuery(true)
				->update($db->qn('#__ak_profiles'))
				->set($db->qn('configuration') . ' = ' . $db->q($config))
				->where($db->qn('id') . ' = ' . $db->q($id));
			$db->setQuery($sql);
			$db->execute();
		}

		// Decrypt the Secret Word settings in the database
		$params = $this->container->params;
		SecretWord::enforceDecrypted($params, 'frontend_secret_word', $key);

		// Finally, remove the key file
		if (!@unlink($filename))
		{
			File::delete($filename);
		}
	}

	/**
	 * Enabled the encryption of profile settings. Existing settings are automatically encrypted.
	 *
	 * @return  void
	 */
	private function enableSettingsEncryption()
	{
		$key = $this->createSettingsKey();

		if (empty($key) || ($key == false))
		{
			return;
		}

		// Loop all profiles and encrypt their settings
		/** @var Profiles $profilesModel */
		$profilesModel = $this->container->factory->model('Profiles')->tmpInstance();
		$profiles      = $profilesModel->get(true);
		$db            = $this->container->db;
		if (!empty($profiles))
		{
			foreach ($profiles as $profile)
			{
				$id     = $profile->id;
				$config = Factory::getSecureSettings()->encryptSettings($profile->configuration, $key);
				$sql    = $db->getQuery(true)
					->update($db->qn('#__ak_profiles'))
					->set($db->qn('configuration') . ' = ' . $db->q($config))
					->where($db->qn('id') . ' = ' . $db->q($id));
				$db->setQuery($sql);
				$db->execute();
			}
		}
	}

	/**
	 * Creates an encryption key for the settings and saves it in the <component>/BackupEngine/serverkey.php path
	 *
	 * @return  bool|string  FALSE on failure, the encryptions key otherwise
	 */
	private function createSettingsKey()
	{
		$randVal = new RandomValue();
		$rawKey  = $randVal->generate(64);
		$key     = base64_encode($rawKey);

		$filecontents = "<?php defined('AKEEBAENGINE') or die(); define('AKEEBA_SERVERKEY', '$key'); ?>";
		$filename     = $this->container->backEndPath . '/BackupEngine/serverkey.php';

		$result = File::write($filename, $filecontents);

		if (!$result)
		{
			return false;
		}

		return $rawKey;
	}
}
PK�!]��JU��'Model/Mixin/GetErrorsFromExceptions.phpnu&1i�<?php
/**
 * @package   akeebabackup
 * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

namespace Akeeba\Backup\Admin\Model\Mixin;

// Protect from unauthorized access
defined('_JEXEC') || die();

use Exception;
use Throwable;

trait GetErrorsFromExceptions
{
	/**
	 * Retrieve the messages from nested exceptions into an array. It will optionally add the trace as the last element
	 * of the array if debug mode (JDEBUG or AKEEBADEBUG) is enabled and $includeTraceInDebug is true.
	 *
	 * @param   Exception|Throwable  $exception            The Exception or Throwable to log
	 *
	 * @param   bool                 $includeTraceInDebug  Include the trace when debug mode is enabled
	 *
	 * @return  array
	 */
	public function getErrorsFromExceptions($exception, $includeTraceInDebug = true)
	{
		$ret = [
			$exception->getMessage(),
		];

		$previous = $exception->getPrevious();

		if (!is_null($previous))
		{
			$ret = array_merge($ret, $this->getErrorsFromExceptions($previous, false));
		}

		if ($includeTraceInDebug && ((defined('JDEBUG') && JDEBUG) || (defined('AKEEBADEBUG') && AKEEBADEBUG)))
		{
			$ret[] = $exception->getTraceAsString();
		}

		return $ret;
	}

}
PK�!]Kq{
��Model/Mixin/Chmod.phpnu&1i�<?php
/**
 * @package   akeebabackup
 * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

namespace Akeeba\Backup\Admin\Model\Mixin;

// Protect from unauthorized access
defined('_JEXEC') || die();

use Joomla\CMS\Client\ClientHelper;
use Joomla\CMS\Client\FtpClient;
use Joomla\CMS\Filesystem\Path;

trait Chmod
{
	/**
	 * Tries to change a folder/file's permissions using direct access or FTP
	 *
	 * @param   string  $path  The full path to the folder/file to chmod
	 * @param   int     $mode  New permissions
	 *
	 * @return  bool  True on success
	 */
	private function chmod($path, $mode)
	{
		if (is_string($mode))
		{
			$mode                    = octdec($mode);
			$trustMeIKnowWhatImDoing = 500 + 10 + 1; // working around overzealous scanners written by bozos
			$ohSixHundred            = 386 - 2;
			$ohSevenFiveFive         = 500 - 7;

			if (($mode < $ohSixHundred) || ($mode > $trustMeIKnowWhatImDoing))
			{
				$mode = $ohSevenFiveFive;
			}
		}

		// Initialize variables
		$ftpOptions = ClientHelper::getCredentials('ftp');

		// Check to make sure the path valid and clean
		$path = Path::clean($path);

		if (@chmod($path, $mode))
		{
			$ret = true;
		}
		elseif ($ftpOptions['enabled'] == 1)
		{
			// Connect the FTP client
			$ftp = FtpClient::getInstance(
				$ftpOptions['host'], $ftpOptions['port'], [],
				$ftpOptions['user'], $ftpOptions['pass']
			);

			// Translate path and delete
			$path = Path::clean(str_replace(JPATH_ROOT, $ftpOptions['root'], $path), '/');
			// FTP connector throws an error
			$ret = $ftp->chmod($path, $mode);
		}
		else
		{
			$ret = false;
		}

		return $ret;
	}
}
PK�!]E�ZU��Model/Mixin/ExclusionFilter.phpnu&1i�<?php
/**
 * @package   akeebabackup
 * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

namespace Akeeba\Backup\Admin\Model\Mixin;

// Protect from unauthorized access
use Akeeba\Engine\Factory;

defined('_JEXEC') || die();

/**
 * Trait for handling Akeeba Engine exclusion filters in models
 */
trait ExclusionFilter
{
	protected $knownFilterTypes = [];

	/**
	 * Modifies a filter
	 *
	 * @param   string  $type     Filter type
	 * @param   string  $root     The filter's root
	 * @param   string  $node     The filter node to modify
	 * @param   string  $action   The action to take: set, remove, toggle, swap
	 * @param   string  $oldNode  Only for swap: The old node which will be swapped with $node
	 *
	 * @return  array  Array with keys success and newstate
	 */
	protected function applyExclusionFilter($type, $root, $node, $action = 'set', $oldNode = '')
	{
		$ret = [
			'success'  => false,
			'newstate' => false
		];

		$filter  = Factory::getFilterObject($type);
		$newState = null;

		switch ($action)
		{
			case 'set':
				$ret['success'] = $filter->set($root, $node);
				break;

			case 'remove':
				$ret['success'] = $filter->remove($root, $node);
				break;

			case 'toggle':
				$ret['success'] = $filter->toggle($root, $node, $newState);
				break;

			case 'swap':
				$ret['success'] = true;

				if (empty($node))
				{
					$ret['success'] = false;
				}

				if ($ret['success'] && !empty($oldNode))
				{
					$ret = $this->applyExclusionFilter($type, $root, $oldNode, 'remove');
				}

				if ($ret['success'])
				{
					$ret = $this->applyExclusionFilter($type, $root, $node, 'set');
				}
				break;
		}

		$ret['newstate'] = $newState;

		if (is_null($newState))
		{
			$ret['newstate'] = $ret['success'];
		}

		if ($ret['success'])
		{
			$filters = Factory::getFilters();
			$filters->save();
		}

		return $ret;
	}


	/**
	 * Retrieves the filters as an array. Used for the tabular filter editor.
	 *
	 * @param   string  $root  The root node to search filters on
	 *
	 * @return  array  A collection of hash arrays containing node and type for each filtered element
	 */
	protected function &getTabularFilters($root)
	{
		// A reference to the global Akeeba Engine filter object
		$filters = Factory::getFilters();

		// Initialize the return array
		$ret = array();

		foreach ($this->knownFilterTypes as $type)
		{
			$rawFilterData = $filters->getFilterData($type);

			if (array_key_exists($root, $rawFilterData))
			{
				if (!empty($rawFilterData[ $root ]))
				{
					foreach ($rawFilterData[ $root ] as $node)
					{
						$ret[] = array(
							'node' => substr($node, 0), // Make sure we get a COPY, not a reference to the original data
							'type' => $type
						);
					}
				}
			}
		}

		return $ret;
	}

	/**
	 * Resets the filters
	 *
	 * @param   string  $root  Root directory
	 *
	 * @return  void
	 */
	protected function resetAllFilters($root)
	{
		// Get a reference to the global Filters object
		$filters = Factory::getFilters();

		foreach ($this->knownFilterTypes as $filterName)
		{
			$filter = Factory::getFilterObject($filterName);
			$filter->reset($root);
		}

		$filters->save();
	}
}
PK�!]Ӳ���Model/Configuration.phpnu&1i�<?php
/**
 * @package   akeebabackup
 * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

namespace Akeeba\Backup\Admin\Model;

// Protect from unauthorized access
defined('_JEXEC') || die();

use Akeeba\Engine\Archiver\Directftp;
use Akeeba\Engine\Archiver\Directsftp;
use Akeeba\Engine\Factory;
use Akeeba\Engine\Platform;
use Akeeba\Engine\Postproc\Base;
use Akeeba\Engine\Util\Transfer\FtpCurl;
use Akeeba\Engine\Util\Transfer\SftpCurl;
use Exception;
use FOF40\Model\Model;
use Joomla\CMS\Language\Text;
use Joomla\CMS\Uri\Uri;
use RuntimeException;

class Configuration extends Model
{
	/**
	 * Save the engine configuration
	 *
	 * @return  void
	 */
	public function saveEngineConfig()
	{
		$data = $this->getState('engineconfig', []);

		// Forbid stupidly selecting the site's root as the output or temporary directory
		if (array_key_exists('akeeba.basic.output_directory', $data))
		{
			$folder = $data['akeeba.basic.output_directory'];
			$folder = Factory::getFilesystemTools()->translateStockDirs($folder, true, true);
			$check  = Factory::getFilesystemTools()->translateStockDirs('[SITEROOT]', true, true);

			if ($check == $folder)
			{
				$data['akeeba.basic.output_directory'] = '[DEFAULT_OUTPUT]';
			}
			else
			{
				$data['akeeba.basic.output_directory'] = Factory::getFilesystemTools()->rebaseFolderToStockDirs($data['akeeba.basic.output_directory']);
			}
		}

		// Unprotect the configuration and merge it
		$config        = Factory::getConfiguration();
		$protectedKeys = $config->getProtectedKeys();
		$config->resetProtectedKeys();
		$config->mergeArray($data, false, false);
		$config->setProtectedKeys($protectedKeys);

		// Save configuration
		Platform::getInstance()->save_configuration();
	}

	/**
	 * Test the FTP connection.
	 *
	 * @return  void
	 * @throws  RuntimeException
	 */
	public function testFTP()
	{
		$config = [
			'host'    => $this->getState('host'),
			'port'    => $this->getState('port'),
			'user'    => $this->getState('user'),
			'pass'    => $this->getState('pass'),
			'initdir' => $this->getState('initdir'),
			'usessl'  => $this->getState('usessl'),
			'passive' => $this->getState('passive'),
		];

		// Check for bad settings
		if (substr($config['host'], 0, 6) == 'ftp://')
		{
			throw new RuntimeException(Text::_('COM_AKEEBA_CONFIG_FTPTEST_BADPREFIX'), 500);
		}

		// Special case for cURL transport
		if ($this->getState('isCurl'))
		{
			$this->testFtpCurl();

			return;
		}

		// Perform the FTP connection test
		$test = new Directftp();

		$test->initialize('', $config);
	}

	/**
	 * Test the SFTP connection.
	 *
	 * @return  void
	 * @throws  RuntimeException
	 */
	public function testSFTP()
	{
		$config = [
			'host'    => $this->getState('host'),
			'port'    => $this->getState('port'),
			'user'    => $this->getState('user'),
			'pass'    => $this->getState('pass'),
			'privkey' => $this->getState('privkey'),
			'pubkey'  => $this->getState('pubkey'),
			'initdir' => $this->getState('initdir'),
		];

		// Check for bad settings
		if (substr($config['host'], 0, 7) == 'sftp://')
		{
			throw new RuntimeException(Text::_('COM_AKEEBA_CONFIG_SFTPTEST_BADPREFIX'), 500);
		}

		// Special case for cURL transport
		if ($this->getState('isCurl'))
		{
			$this->testSftpCurl();

			return;
		}

		// Perform the FTP connection test
		$test = new Directsftp();

		$test->initialize('', $config);
	}

	/**
	 * Opens an OAuth window for the selected post-processing engine
	 *
	 * @return  void
	 * @throws  Exception
	 */
	public function dpeOuthOpen()
	{
		$engine = $this->getState('engine');
		$params = $this->getState('params', []);

		// Get a callback URI for OAuth 2
		$params['callbackURI'] = rtrim(Uri::base(), '/') . '/index.php?option=com_akeeba&view=Configuration&task=dpecustomapiraw&engine=' . $engine;

		// Get the Input object
		$params['input'] = $this->input->getData();

		// Get the engine
		$engineObject = Factory::getPostprocEngine($engine);

		if (!$engineObject instanceof Base)
		{
			return;
		}

		$engineObject->oauthOpen($params);
	}

	/**
	 * Runs a custom API call for the selected post-processing engine
	 *
	 * @return  mixed
	 */
	public function dpeCustomAPICall()
	{
		$engine = $this->getState('engine');
		$method = $this->getState('method');
		$params = $this->getState('params', []);

		// Get the Input object
		$params['input'] = $this->input->getData();

		$engineObject = Factory::getPostprocEngine($engine);

		if (!$engineObject instanceof Base)
		{
			return false;
		}

		return $engineObject->customAPICall($method, $params);
	}

	/**
	 * Test the connection to a remote FTP server using cURL transport
	 *
	 * @return  void
	 * @throws  RuntimeException
	 */
	private function testFtpCurl()
	{
		$options = [
			'host'        => $this->getState('host'),
			'port'        => $this->getState('port'),
			'username'    => $this->getState('user'),
			'password'    => $this->getState('pass'),
			'directory'   => $this->getState('initdir'),
			'usessl'      => $this->getState('usessl'),
			'passive'     => $this->getState('passive'),
			'passive_fix' => $this->getState('passive_mode_workaround'),
		];

		$sftpTransfer = new FtpCurl($options);

		$sftpTransfer->connect();
	}

	/**
	 * Test the connection to a remote SFTP server using cURL transport
	 *
	 * @return  void
	 * @throws  RuntimeException
	 */
	private function testSftpCurl()
	{
		$options = [
			'host'       => $this->getState('host'),
			'port'       => $this->getState('port'),
			'username'   => $this->getState('user'),
			'password'   => $this->getState('pass'),
			'directory'  => $this->getState('initdir'),
			'privateKey' => $this->getState('privkey'),
			'publicKey'  => $this->getState('pubkey'),
		];

		$sftpTransfer = new SftpCurl($options);

		$sftpTransfer->connect();
	}
}
PK�!]�/{/
Model/Log.phpnu&1i�<?php
/**
 * @package   akeebabackup
 * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

namespace Akeeba\Backup\Admin\Model;

// Protect from unauthorized access
defined('_JEXEC') || die();

use Akeeba\Engine\Factory;
use FOF40\Model\Model;
use Joomla\CMS\HTML\HTMLHelper;
use Joomla\CMS\Language\Text;

class Log extends Model
{
	/**
	 * Get an array with the names of all log files in this backup profile
	 *
	 * @param   bool  $onlyFailed  Should I only return the log files of backups marked as failed?
	 *
	 * @return  string[]
	 */
	public function getLogFiles(bool $onlyFailed = false): array
	{
		$configuration = Factory::getConfiguration();
		$outdir        = $configuration->get('akeeba.basic.output_directory');

		$files = Factory::getFileLister()->getFiles($outdir);
		$ret   = [];

		if (!empty($files) && is_array($files))
		{
			foreach ($files as $filename)
			{
				$baseName         = basename($filename);
				$startsWithAkeeba = substr($baseName, 0, 7) == 'akeeba.';
				$endsWithLog      = substr($baseName, -4) == '.log';
				$endsWithPhpLog   = substr($baseName, -8) == '.log.php';
				$isDefaultLog     = $baseName == 'akeeba.log';

				if ($startsWithAkeeba && ($endsWithLog || $endsWithPhpLog) && !$isDefaultLog)
				{
					/**
					 * Extract the tag from the filename (akeeba.tag.log or akeeba.tag.log.php)
					 *
					 * We ignore the first seven characters ("akeeba.") and the last X characters, where X is 8 if the
					 * log file name ends with .log.php or 4 if the log name ends with .log.
					 */
					$tag = substr($baseName, 7, -($endsWithPhpLog ? 8 : 4));

					if (empty($tag))
					{
						continue;
					}

					$parts = explode('.', $tag);
					$key   = array_pop($parts);
					$key   = str_replace('id', '', $key);
					$key   = is_numeric($key) ? sprintf('%015u', $key) : $key;

					if (empty($parts))
					{
						$key = str_repeat('0', 15) . '.' . $key;
					}
					else
					{
						$key .= '.' . implode('.', $parts);
					}

					$ret[$key] = $tag;
				}
			}
		}

		if ($onlyFailed)
		{
			$ret = $this->keepOnlyFailedLogs($ret);
		}

		krsort($ret);

		return $ret;
	}

	/**
	 * Gets the JHtml options list for selecting a log file
	 *
	 * @param   bool  $onlyFailed  Should I only return the log files of backups marked as failed?
	 *
	 * @return  array
	 */
	public function getLogList(bool $onlyFailed = false): array
	{
		$origin  = null;
		$options = [];

		$list = $this->getLogFiles($onlyFailed);

		if (!empty($list))
		{
			$options[] = HTMLHelper::_('select.option', null, Text::_('COM_AKEEBA_LOG_CHOOSE_FILE_VALUE'));

			foreach ($list as $item)
			{
				$text = Text::_('COM_AKEEBA_BUADMIN_LABEL_ORIGIN_' . $item);

				if (strstr($item, '.') !== false)
				{
					[$origin, $backupId] = explode('.', $item, 2);

					$text = Text::_('COM_AKEEBA_BUADMIN_LABEL_ORIGIN_' . $origin) . ' (' . $backupId . ')';
				}

				$options[] = HTMLHelper::_('select.option', $item, $text);
			}
		}

		return $options;
	}

	/**
	 * Output the raw text log file to the standard output without the PHP die header
	 *
	 * @param   bool  $withHeader  Should I include a header telling the user how to submit this file?
	 *
	 * @return  void
	 */
	public function echoRawLog($withHeader = true)
	{
		$tag     = $this->getState('tag', '');
		$logFile = Factory::getLog()->getLogFilename($tag);

		if (!@is_file($logFile) && @file_exists(substr($logFile, 0, -4)))
		{
			/**
			 * Transitional period: the log file akeeba.tag.log.php may not exist but the akeeba.tag.log does. This
			 * addresses this transition.
			 */
			$logFile = substr($logFile, 0, -4);
		}

		if ($withHeader)
		{
			echo "WARNING: Do not copy and paste lines from this file!\r\n";
			echo "You are supposed to ZIP and attach it in your support forum post.\r\n";
			echo "If you fail to do so, we will be unable to provide efficient support.\r\n";
			echo "\r\n";
			echo "--- START OF RAW LOG --\r\n";
		}

		// The at sign (silence operator) is necessary to prevent PHP showing a warning if the file doesn't exist or
		// isn't readable for any reason.
		$fp = @fopen($logFile, 'r');

		if ($fp === false)
		{
			if ($withHeader)
			{
				echo "--- END OF RAW LOG ---\r\n";
			}

			return;
		}

		$firstLine = @fgets($fp);
		if (substr($firstLine, 0, 5) != '<' . '?' . 'php')
		{
			@fclose($fp);
			@readfile($logFile);
		}
		else
		{
			while (!feof($fp))
			{
				echo rtrim(fgets($fp)) . "\r\n";
			}

			@fclose($fp);
		}

		if ($withHeader)
		{
			echo "--- END OF RAW LOG ---\r\n";
		}
	}

	protected function keepOnlyFailedLogs($logs)
	{
		$db            = $this->container->db;
		$query         = $db->getQuery(true)
			->select([
				$db->quoteName('tag'),
				$db->quoteName('backupid'),
			])
			->from($db->quoteName('#__ak_stats'))
			->where($db->quoteName('status') . ' = ' . $db->quote('fail'));
		$failedBackups = $db->setQuery($query)->loadObjectList() ?: [];

		if (empty($failedBackups))
		{
			return [];
		}

		$failedBackups = array_map(function ($o) {
			$tag = $o->tag ?? '';

			return (empty($tag) ? '' : '.') . $o->backupid;
		}, $failedBackups);

		return array_intersect($logs, $failedBackups);
	}
}
PK�!]*:�h��Model/FTPBrowser.phpnu&1i�<?php
/**
 * @package   akeebabackup
 * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

namespace Akeeba\Backup\Admin\Model;

// Protect from unauthorized access
defined('_JEXEC') || die();

use FOF40\Model\Model;
use Joomla\CMS\Language\Text;
use RuntimeException;

class FTPBrowser extends Model
{
	/**
	 * The FTP server hostname
	 *
	 * @var  string
	 */
	public $host = '';

	/**
	 * The FTP server port number (default: 21)
	 *
	 * @var  int
	 */
	public $port = 21;

	/**
	 * Should I use passive mode (default: yes)
	 *
	 * @var  bool
	 */
	public $passive = true;

	/**
	 * Should I use FTP over SSL (default: no)
	 *
	 * @var  bool
	 */
	public $ssl = false;

	/**
	 * Username for logging in
	 *
	 * @var  string
	 */
	public $username = '';

	/**
	 * Password for logging in
	 *
	 * @var  string
	 */
	public $password = '';

	/**
	 * The directory to browse
	 *
	 * @var  string
	 */
	public $directory = '';

	/**
	 * Breadcrumbs to the current directory
	 *
	 * @var  array
	 */
	public $parts = [];

	/**
	 * Path to the parent directory
	 *
	 * @var  string
	 */
	public $parent_directory = null;

	/**
	 * Gets the folders contained in the remote FTP root directory defined in $this->directory
	 *
	 * @return  array
	 */
	public function getListing()
	{
		$dir = $this->directory;

		// Parse directory to parts
		$parsed_dir  = trim($dir, '/');
		$this->parts = empty($parsed_dir) ? [] : explode('/', $parsed_dir);

		// Find the path to the parent directory
		$this->parent_directory = '';

		if (!empty($this->parts))
		{
			$copy_of_parts = $this->parts;
			array_pop($copy_of_parts);

			$this->parent_directory = '/';

			if (!empty($copy_of_parts))
			{
				$this->parent_directory = '/' . implode('/', $copy_of_parts);
			}
		}

		// Connect to the server
		if ($this->ssl)
		{
			$con = @ftp_ssl_connect($this->host, $this->port);
		}
		else
		{
			$con = @ftp_connect($this->host, $this->port);
		}

		if ($con === false)
		{
			throw new RuntimeException(Text::_('COM_AKEEBA_FTPBROWSER_ERROR_HOSTNAME'));
		}

		// Login
		$result = @ftp_login($con, $this->username, $this->password);

		if ($result === false)
		{
			throw new RuntimeException(Text::_('COM_AKEEBA_FTPBROWSER_ERROR_USERPASS'));
		}

		// Set the passive mode -- don't care if it fails, though!
		@ftp_pasv($con, $this->passive);

		// Try to chdir to the specified directory
		if (!empty($dir))
		{
			$result = @ftp_chdir($con, $dir);

			if ($result === false)
			{
				throw new RuntimeException(Text::_('COM_AKEEBA_FTPBROWSER_ERROR_NOACCESS'));
			}
		}
		else
		{
			$this->directory = @ftp_pwd($con);

			$parsed_dir             = trim($this->directory, '/');
			$this->parts            = empty($parsed_dir) ? [] : explode('/', $parsed_dir);
			$this->parent_directory = $this->directory;
		}

		// Get a raw directory listing (hoping it's a UNIX server!)
		$list = @ftp_rawlist($con, '.');

		ftp_close($con);

		if ($list === false)
		{
			throw new RuntimeException(Text::_('COM_AKEEBA_FTPBROWSER_ERROR_UNSUPPORTED'));
		}

		// Parse the raw listing into an array
		$folders = $this->parse_rawlist($list);

		return $folders;
	}

	/**
	 * Perform the actual folder browsing. Returns an array that's usable by the UI.
	 *
	 * @return  array
	 */
	public function doBrowse()
	{
		$error = '';
		$list  = [];

		try
		{
			$list = $this->getListing();
		}
		catch (RuntimeException $e)
		{
			$error = $e->getMessage();
		}

		$response_array = [
			'error'       => $error,
			'list'        => $list,
			'breadcrumbs' => $this->parts,
			'directory'   => $this->directory,
			'parent'      => $this->parent_directory,
		];

		return $response_array;
	}

	/**
	 * Parse the raw list of folders returned by the server into a usable simple array of folders
	 *
	 * @param   array  $list  The raw folder list returned by ftp_rawlist
	 *
	 * @return  array  The parsed list of folders
	 */
	private function parse_rawlist(array $list)
	{
		$folders = [];

		foreach ($list as $v)
		{

			$vinfo = preg_split("/[\s]+/", $v, 9);

			if ($vinfo[0] !== "total")
			{
				$perms = $vinfo[0];

				if (substr($perms, 0, 1) == 'd')
				{
					$folders[] = $vinfo[8];
				}
			}
		}

		asort($folders);

		return $folders;
	}
}
PK�!]]�b�

Model/Profiles.phpnu&1i�<?php
/**
 * @package   akeebabackup
 * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

namespace Akeeba\Backup\Admin\Model;

// Protect from unauthorized access
defined('_JEXEC') || die();

use Akeeba\Engine\Factory;
use Akeeba\Engine\Platform;
use FOF40\Container\Container;
use FOF40\Model\DataModel;
use Joomla\CMS\Language\Text;
use RuntimeException;

/**
 * Backup profile model
 *
 * @property  int    id             Profile ID
 * @property  string description    Description
 * @property  string configuration  Engine configuration data
 * @property  string filters        Engine filters
 * @property  int    quickicon      Should I include this profile in the One Click Backup profiles (1) or not (0)?
 */
class Profiles extends DataModel
{
	public function __construct(Container $container, array $config)
	{
		$defaultConfig = [
			'tableName'   => '#__ak_profiles',
			'idFieldName' => 'id',
		];

		if (!is_array($config) || empty($config))
		{
			$config = [];
		}

		$config = array_merge($defaultConfig, $config);

		parent::__construct($container, $config);

		$this->addBehaviour('filters');
		$this->blacklistFilters([
			'configuration',
			'filters',
		]);
	}

	/**
	 * Tries to copy the currently loaded to a new record
	 *
	 * @return  self  The new record
	 */
	public function copy($data = null)
	{
		$id = $this->getId();

		// Check for invalid id's (not numeric, or <= 0)
		if ((!is_numeric($id)) || ($id <= 0))
		{
			throw new DataModel\Exception\RecordNotLoaded('PROFILE_INVALID_ID');
		}

		if (!is_array($data))
		{
			$data = [];
		}

		$data['id'] = 0;

		return $this->getClone()->save($data);
	}

	/**
	 * Returns an associative array with profile IDs as keys and the post-processing engine as values
	 *
	 * @return  array
	 */
	public function getPostProcessingEnginePerProfile()
	{
		// Cache the current profile's ID
		$currentProfileID = $this->container->platform->getSessionVar('profile', null, 'akeeba');

		// Get the IDs of all profiles
		$db    = $this->getDbo();
		$query = $db->getQuery(true)
			->select($db->qn('id'))
			->from($db->qn('#__ak_profiles'));
		$db->setQuery($query);
		$profiles = $db->loadColumn();

		// Initialise return;
		$engines = [];

		// Loop all profiles
		foreach ($profiles as $profileId)
		{
			Platform::getInstance()->load_configuration($profileId);
			$profileConfiguration = Factory::getConfiguration();
			$engines[$profileId]  = $profileConfiguration->get('akeeba.advanced.postproc_engine');
		}

		// Reload the current profile
		Platform::getInstance()->load_configuration($currentProfileID);

		return $engines;
	}

	/**
	 * Runs before deleting a record
	 *
	 * @param   int  $id  The ID of the record being deleted
	 */
	public function onBeforeDelete(&$id)
	{
		// You cannot delete the default record
		if ($id <= 1)
		{
			throw new RuntimeException(Text::_('COM_AKEEBA_PROFILE_ERR_CANNOTDELETEDEFAULT'), 500);
		}

		// If you're deleting the current backup profile we have to switch to the default profile (#1)
		$activeProfile = Platform::getInstance()->get_active_profile();

		if ($id == $activeProfile)
		{
			throw new RuntimeException(Text::sprintf('COM_AKEEBA_PROFILE_ERR_CANNOTDELETEACTIVE', $id), 500);
		}
	}

	/**
	 * Save a profile from imported configuration data. The $data array must contain the keys description (profile
	 * description), configuration (engine configuration INI data) and filters (inclusion and inclusion filters JSON
	 * configuration data).
	 *
	 * @param   array  $data  See above
	 *
	 * @returns  void
	 *
	 * @throws   RuntimeException  When an iport error occurs
	 */
	public function import($data)
	{
		// Check for data validity
		$isValid =
			is_array($data) &&
			!empty($data) &&
			array_key_exists('description', $data) &&
			array_key_exists('configuration', $data) &&
			array_key_exists('filters', $data);

		if (!$isValid)
		{
			throw new RuntimeException(Text::_('COM_AKEEBA_PROFILES_ERR_IMPORT_INVALID'));
		}

		// Unset the id, if it exists
		if (array_key_exists('id', $data))
		{
			unset($data['id']);
		}

		$data['akeeba.flag.confwiz'] = 1;

		// Try saving the profile
		$result = $this->save($data);

		if (!$result)
		{
			throw new RuntimeException(Text::_('COM_AKEEBA_PROFILES_ERR_IMPORT_FAILED'));
		}
	}
}
PK�!]ܣa%%Model/UsageStatistics.phpnu&1i�<?php
/**
 * @package   akeebabackup
 * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

namespace Akeeba\Backup\Admin\Model;

// Protect from unauthorized access
defined('_JEXEC') || die();

use AkeebaUsagestats;
use FOF40\Encrypt\Randval;
use FOF40\Model\Model;
use Joomla\CMS\Uri\Uri;

/**
 * Usage statistics collection model. Implements the anonymous collection of PHP, MySQL and Joomla! version information
 * which help us decide on the end of support for obsolete versions of said third party software.
 */
class UsageStatistics extends Model
{
	/**
	 * Get an existing unique site ID or create a new one
	 *
	 * @return  string
	 */
	public function getSiteId()
	{
		// Can I load a site ID from the database?
		$siteId = $this->getCommonVariable('stats_siteid', null);

		// Can I load the site Url from the database?
		$siteUrl = $this->getCommonVariable('stats_siteurl', null);

		// No id or the saved URL is not the same as the current one (ie site restored to a new url)?
		// Create a new, random site ID and save it to the database
		if (empty($siteId) || (md5(Uri::base()) != $siteUrl))
		{
			$siteUrl = md5(Uri::base());
			$this->setCommonVariable('stats_siteurl', $siteUrl);

			$randomData = random_bytes(120);
			$siteId     = sha1($randomData);

			$this->setCommonVariable('stats_siteid', $siteId);
		}

		return $siteId;
	}

	/**
	 * Send site information to the remove collection service
	 *
	 * @param   bool  $useIframe  Should I use an IFRAME?
	 *
	 * @return  bool
	 */
	public function collectStatistics($useIframe)
	{
		// Is data collection turned off?
		if (!$this->container->params->get('stats_enabled', 1))
		{
			return false;
		}

		// Make sure there is a site ID set
		$siteId    = $this->getSiteId();
		$container = $this->container;

		// UsageStats file is missing, no need to continue
		if (!file_exists($container->backEndPath . '/Master/Stats/usagestats.php'))
		{
			return false;
		}

		if (!class_exists('AkeebaUsagestats', false))
		{
			@include_once $container->backEndPath . '/Master/Stats/usagestats.php';
		}

		// UsageStats file is missing, no need to continue
		if (!class_exists('AkeebaUsagestats', false))
		{
			return false;
		}

		$lastrun = $this->getCommonVariable('stats_lastrun', 0);

		// It's not time to collect the stats
		if (time() < ($lastrun + 3600 * 24))
		{
			return false;
		}

		if (!defined('AKEEBA_VERSION'))
		{
			@include_once $container->backEndPath . '/version.php';
		}

		if (!defined('AKEEBA_VERSION'))
		{
			define('AKEEBA_VERSION', 'dev');
			define('AKEEBA_DATE', date('Y-m-d'));
		}

		$db = $container->db;

		try
		{
			$stats = new AkeebaUsagestats();
		}
		catch (\Exception $e)
		{
			return false;
		}

		$stats->setSiteId($siteId);

		// I can't use list since dev release don't have any dots
		$at_parts    = explode('.', AKEEBA_VERSION);
		$at_major    = $at_parts[0];
		$at_minor    = $at_parts[1] ?? '';
		$at_revision = $at_parts[2] ?? '';

		[$php_major, $php_minor, $php_revision] = explode('.', phpversion());
		$php_qualifier = strpos($php_revision, '~') !== false ? substr($php_revision, strpos($php_revision, '~')) : '';

		[$cms_major, $cms_minor, $cms_revision] = explode('.', JVERSION);
		[$db_major, $db_minor, $db_revision] = explode('.', $db->getVersion());
		$db_qualifier = strpos($db_revision, '~') !== false ? substr($db_revision, strpos($db_revision, '~')) : '';

		$db_driver = get_class($db);

		if (stripos($db_driver, 'mysql') !== false)
		{
			$stats->setValue('dt', 1);
		}
		else
		{
			$stats->setValue('dt', 0);
		}

		$stats->setValue('sw', AKEEBA_PRO ? 2 : 1); // software
		$stats->setValue('pro', AKEEBA_PRO); // pro
		$stats->setValue('sm', $at_major); // software_major
		$stats->setValue('sn', $at_minor); // software_minor
		$stats->setValue('sr', $at_revision); // software_revision
		$stats->setValue('pm', $php_major); // php_major
		$stats->setValue('pn', $php_minor); // php_minor
		$stats->setValue('pr', $php_revision); // php_revision
		$stats->setValue('pq', $php_qualifier); // php_qualifiers
		$stats->setValue('dm', $db_major); // db_major
		$stats->setValue('dn', $db_minor); // db_minor
		$stats->setValue('dr', $db_revision); // db_revision
		$stats->setValue('dq', $db_qualifier); // db_qualifiers
		$stats->setValue('ct', 1); // cms_type
		$stats->setValue('cm', $cms_major); // cms_major
		$stats->setValue('cn', $cms_minor); // cms_minor
		$stats->setValue('cr', $cms_revision); // cms_revision

		// Store the last execution time. We must store it even if we fail since we don't want a failed stats collection
		// to cause the site to stop responding.
		$this->setCommonVariable('stats_lastrun', time());

		$return = $stats->sendInfo($useIframe);

		return $return;
	}

	/**
	 * Load a variable from the common variables table. If it doesn't exist it returns $default
	 *
	 * @param   string  $key      The key to load
	 * @param   mixed   $default  The default value if the key doesn't exist
	 *
	 * @return  mixed  The contents of the key or null if it's not present
	 */
	public function getCommonVariable($key, $default = null)
	{
		$db    = $this->container->db;
		$query = $db->getQuery(true)
			->select($db->qn('value'))
			->from($db->qn('#__akeeba_common'))
			->where($db->qn('key') . ' = ' . $db->q($key));

		try
		{
			$db->setQuery($query);
			$result = $db->loadResult();
		}
		catch (\Exception $e)
		{
			$result = $default;
		}

		return $result;
	}

	/**
	 * Set a variable to the common variables table.
	 *
	 * @param   string  $key    The key to save
	 * @param   mixed   $value  The value to save
	 *
	 * @return  void
	 */
	public function setCommonVariable($key, $value)
	{
		$db    = $this->container->db;
		$query = $db->getQuery(true)
			->select('COUNT(*)')
			->from($db->qn('#__akeeba_common'))
			->where($db->qn('key') . ' = ' . $db->q($key));

		try
		{
			$db->setQuery($query);
			$count = $db->loadResult();
		}
		catch (\Exception $e)
		{
			return;
		}

		try
		{
			if (!$count)
			{
				$insertObject = (object) [
					'key'   => $key,
					'value' => $value,
				];
				$db->insertObject('#__akeeba_common', $insertObject);
			}
			else
			{
				$insertObject = (object) [
					'key'   => $key,
					'value' => $value,
				];

				$db->updateObject('#__akeeba_common', $insertObject, 'key');
			}
		}
		catch (\Exception $e)
		{
		}
	}
}
PK�!]
�=�
access.xmlnu&1i�<?xml version="1.0" encoding="utf-8"?>
<!--~
  ~ @package   akeebabackup
  ~ @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
  ~ @license   GNU General Public License version 3, or later
  -->
<access component="com_akeeba">
	<section name="component">
		<action name="core.admin" title="JACTION_ADMIN" description="JACTION_ADMIN_COMPONENT_DESC" />
		<action name="core.manage" title="JACTION_MANAGE" description="JACTION_MANAGE_COMPONENT_DESC" />
		<action name="akeeba.backup" title="Backup" description="JACTION_ADMIN_COMPONENT_DESC" />
		<action name="akeeba.configure" title="Configure" description="JACTION_MANAGE_COMPONENT_DESC" />
		<action name="akeeba.download" title="Download" description="JACTION_CREATE_COMPONENT_DESC" />
	</section>
</access>
PK�!]��ѡ��Dispatcher/.htaccessnu&1i�<IfModule !mod_authz_core.c>
Order deny,allow
Deny from all
</IfModule>
<IfModule mod_authz_core.c>
  <RequireAll>
    Require all denied
  </RequireAll>
</IfModule>
PK�!]\yO�'�'Dispatcher/Dispatcher.phpnu&1i�<?php
/**
 * @package   akeebabackup
 * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

namespace Akeeba\Backup\Admin\Dispatcher;

// Protect from unauthorized access
defined('_JEXEC') || die();

use Akeeba\Backup\Admin\Helper\SecretWord;
use Akeeba\Backup\Admin\Model\ControlPanel;
use Akeeba\Engine\Factory;
use Akeeba\Engine\Platform;
use AkeebaFEFHelper;
use FOF40\Container\Container;
use FOF40\Dispatcher\Dispatcher as BaseDispatcher;
use FOF40\Dispatcher\Mixin\ViewAliases;
use Joomla\CMS\Factory as JFactory;
use Joomla\CMS\Language\Text;

class Dispatcher extends BaseDispatcher
{
	/** @var   string  The name of the default view, in case none is specified */
	public $defaultView = 'ControlPanel';

	use ViewAliases
	{
		onBeforeDispatch as onBeforeDispatchViewAliases;
	}

	/** @var  \Akeeba\Backup\Admin\Container  The container we belong to */
	protected $container = null;

	public function __construct(Container $container, array $config)
	{
		parent::__construct($container, $config);

		$this->viewNameAliases = [
			'buadmin'        => 'Manage',
			'buadmins'       => 'Manage',
			'config'         => 'Configuration',
			'configs'        => 'Configuration',
			'confwiz'        => 'ConfigurationWizard',
			'confwizs'       => 'ConfigurationWizard',
			'confwizes'      => 'ConfigurationWizard',
			'cpanel'         => 'ControlPanel',
			'cpanels'        => 'ControlPanel',
			'dbef'           => 'DatabaseFilters',
			'dbefs'          => 'DatabaseFilters',
			'eff'            => 'IncludeFolders',
			'effs'           => 'IncludeFolders',
			'fsfilter'       => 'FileFilters',
			'fsfilters'      => 'FileFilters',
			'ftpbrowser'     => 'FTPBrowser',
			'ftpbrowsers'    => 'FTPBrowser',
			'sftpbrowser'    => 'SFTPBrowser',
			'sftpbrowsers'   => 'SFTPBrowser',
			'multidb'        => 'MultipleDatabases',
			'multidbs'       => 'MultipleDatabases',
			'regexdbfilter'  => 'RegExDatabaseFilters',
			'regexdbfilters' => 'RegExDatabaseFilters',
			'regexfsfilter'  => 'RegExFileFilters',
			'regexfsfilters' => 'RegExFileFilters',
			'remotefile'     => 'RemoteFiles',
			'remotefiles'    => 'RemoteFiles',
			's3import'       => 'S3Import',
			's3imports'      => 'S3Import',
		];

	}

	/**
	 * Executes before dispatching the request to the appropriate controller
	 */
	public function onBeforeDispatch()
	{
		$this->container->platform->importPlugin('akeebabackup');
		$this->container->platform->runPlugins('onComAkeebaDispatcherBeforeDispatch', []);

		$this->onBeforeDispatchViewAliases();

		// Load the FOF language
		$lang = $this->container->platform->getLanguage();
		$lang->load('lib_fof40', JPATH_ADMINISTRATOR, 'en-GB', true, true);
		$lang->load('lib_fof40', JPATH_ADMINISTRATOR, null, true, false);

		// Necessary for routing the Alice view
		$this->container->inflector->addWord('Alice', 'Alices');

		// Does the user have adequate permissions to access our component?
		if (!$this->container->platform->authorise('core.manage', 'com_akeeba'))
		{
			throw new \RuntimeException(Text::_('JERROR_ALERTNOAUTHOR'), 404);
		}

		// FEF Renderer options. Used to load the common CSS file.
		$darkMode  = $this->container->params->get('dark_mode', -1);
		$customCss = 'media://com_akeeba/css/akeebaui.css';

		if ($darkMode != 0)
		{
			$customCss .= ', media://com_akeeba/css/dark.css';
		}

		$this->container->renderer->setOptions([
			'custom_css' => $customCss,
			'fef_dark'   => $darkMode,
		]);

		// Load Akeeba Engine
		$this->loadAkeebaEngine();

		// Load the Akeeba Engine configuration
		try
		{
			$this->loadAkeebaEngineConfiguration();
		}
		catch (\Exception $e)
		{
			// Maybe the tables are not installed?
			/** @var ControlPanel $cPanelModel */
			$cPanelModel = $this->container->factory->model('ControlPanel')->tmpInstance();

			try
			{
				$cPanelModel->checkAndFixDatabase();
			}
			catch (\RuntimeException $e)
			{
				// The update is stuck. We will display a warning in the Control Panel
			}

			$msg = Text::_('COM_AKEEBA_CONTROLPANEL_MSG_REBUILTTABLES');
			$this->container->platform->redirect('index.php', 307, $msg, 'warning');
		}

		// Prevents the "SQLSTATE[HY000]: General error: 2014" due to resource sharing with Akeeba Engine
		$this->fixPDOMySQLResourceSharing();

		// Load the utils helper library
		Platform::getInstance()->load_version_defines();
		Platform::getInstance()->apply_quirk_definitions();

		// Make sure the front-end backup Secret Word is stored encrypted
		$params = $this->container->params;
		SecretWord::enforceEncryption($params, 'frontend_secret_word');

		// Make sure we have a version loaded
		@include_once($this->container->backEndPath . '/version.php');

		if (!defined('AKEEBA_VERSION'))
		{
			define('AKEEBA_VERSION', 'dev');
			define('AKEEBA_DATE', date('Y-m-d'));
		}

		// Create a media file versioning tag
		$this->container->mediaVersion = md5(AKEEBA_VERSION . AKEEBA_DATE);

		// Perform certain functionality only in HTML tasks
		$format = $this->input->getCmd('format', 'html');

		if ($format == 'html')
		{
			// Load common Javascript files. NOTE: CSS and anything style-related is loaded by the FEF Renderer class.
			$this->loadCommonJavascript();

			// Perform common maintenance tasks
			$this->autoMaintenance();
		}

		// Set the linkbar style to Classic (Bootstrap tabs). The sidebar takes too much space and requires adding
		// manual HTML to render it...
		$this->container->renderer->setOption('linkbar_style', 'classic');
	}

	public function loadAkeebaEngine()
	{
		// Necessary defines for Akeeba Engine
		if (!defined('AKEEBAENGINE'))
		{
			define('AKEEBAENGINE', 1);
			define('AKEEBAROOT', $this->container->backEndPath . '/BackupEngine');
		}

		// Make sure we have a profile set throughout the component's lifetime
		$profile_id = $this->container->platform->getSessionVar('profile', null, 'akeeba');

		if (is_null($profile_id))
		{
			$this->container->platform->setSessionVar('profile', 1, 'akeeba');
		}

		// Load Akeeba Engine
		$basePath = $this->container->backEndPath;
		require_once $basePath . '/BackupEngine/Factory.php';
	}

	public function loadAkeebaEngineConfiguration()
	{
		Platform::addPlatform('joomla3x', $this->container->backEndPath . '/BackupPlatform/Joomla3x');
		$akeebaEngineConfig = Factory::getConfiguration();
		Platform::getInstance()->load_configuration();
		unset($akeebaEngineConfig);
	}

	/**
	 * Prevents the "SQLSTATE[HY000]: General error: 2014" due to resource sharing with Akeeba Engine.
	 *
	 * @since 7.5.2
	 */
	protected function fixPDOMySQLResourceSharing(): void
	{
		// This fix only applies to PHP 7.x, not 8.x
		if (version_compare(PHP_VERSION, '8.0', 'ge'))
		{
			return;
		}

		// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
		// !!!!! WARNING: ALWAYS GO THROUGH JFactory; DO NOT GO THROUGH $this->container->db !!!!!
		// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
		$jDbo     = JFactory::getDbo();
		$dbDriver = method_exists($jDbo, 'getName') ? ($jDbo->getName() ?? $jDbo->name ?? 'mysql') : 'mysql';

		if ($dbDriver !== 'pdomysql')
		{
			return;
		}

		/**
		 * If this Joomla 3 with Site Debug enabled I need to disable database debug. If it's enabled, Joomla sends
		 * `SET query_cache_type = 0`. However, the query_cache_type MySQL server variable has been deprecated in MySQL
		 * 5.7 abd removed in MySQL 8. The PDO driver receives the error from the MySQL database and turns it into an
		 * untrappable Fatal Error, meaning that a try/catch won't be able to catch it. Since the connection code that
		 * triggers the fatal error will be called AT THE LATEST when the request terminates and at the earliest within
		 * the Dispatcher's loading of common JavaScript (which goes through the Joomla API) this causes the Akeeba
		 * Backup component to not load. Because of the weird way PDO error handling works we don't even get a Fatal
		 * Error which would at least clue us in as to what the heck is going on! Instead we have the main Dispatcher's
		 * dispatch() method handle the fatal error exception (LOLWUT?! WHY ONLY THERE?! WHAT THE HELL PHP?!) being
		 * thrown by the PDO driver, converting the result of onBeforeDispatch to false which is interpreted as the user
		 * not having access to the component, which is the error that gets reported.
		 *
		 * Talking about running into edge cases, am I right?!
		 */
		$isJoomla3         = version_compare(JVERSION, '3.999.999', 'le');
		$isSiteDebug       = (bool) JFactory::getApplication()->get('debug', 0);
		$isMySQL8OrGreated = version_compare($jDbo->getVersion() ?? '8.0', '8', 'ge');

		if ($isJoomla3 && $isSiteDebug && $isMySQL8OrGreated)
		{
			if (!method_exists($jDbo, 'setDebug'))
			{
				return;
			}

			$jDbo->setDebug(false);
		}

		@JFactory::getDbo()->disconnect();
	}

	/**
	 * Loads the Javascript files which are common across many views of the component.
	 *
	 * @return  void
	 */
	private function loadCommonJavascript()
	{
		// Do not move: everything depends on UserInterfaceCommon
		$this->container->template->addJS('media://com_akeeba/js/UserInterfaceCommon.min.js', true, false, $this->container->mediaVersion);
	}

	/**
	 * Perform common maintenance tasks
	 *
	 * @return  void
	 */
	private function autoMaintenance()
	{
		/** @var \Akeeba\Backup\Admin\Model\ControlPanel $model */
		$model = $this->container->factory->model('ControlPanel')->tmpInstance();

		// Update the db structure if necessary (once per session at most)
		$lastVersion = $this->container->platform->getSessionVar('magicParamsUpdateVersion', null, 'akeeba');

		if ($lastVersion != AKEEBA_VERSION)
		{
			try
			{
				$model->checkAndFixDatabase();
				$this->container->platform->setSessionVar('magicParamsUpdateVersion', AKEEBA_VERSION, 'akeeba');
			}
			catch (\RuntimeException $e)
			{
				// The update is stuck. We will display a warning in the Control Panel
			}
		}

		// Update magic parameters if necessary
		$model->updateMagicParameters();
	}
}
PK�!]|��NDispatcher/web.confignu&1i�<?xml version="1.0"?>
<!--
    This only works on IIS 7 or later. See https://www.iis.net/configreference/system.webserver/security/requestfiltering/fileextensions
-->
<configuration>
    <system.webServer>
        <security>
            <requestFiltering>
                <fileExtensions allowUnlisted="false" >
                    <clear />
                    <add fileExtension=".html" allowed="true"/>
                </fileExtensions>
            </requestFiltering>
        </security>
    </system.webServer>
</configuration>PK�!]S�%�WWversion.phpnu&1i�<?php
/**
 * @package   akeebabackup
 * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

// Protect from unauthorized access
defined('_JEXEC') or die();

define('AKEEBA_PRO', '0');
define('AKEEBA_VERSION', '8.4.1');
define('AKEEBA_DATE', '2025-05-09');
PK�!]X���"Controller/ConfigurationWizard.phpnu&1i�<?php
/**
 * @package   akeebabackup
 * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

namespace Akeeba\Backup\Admin\Controller;

// Protect from unauthorized access
defined('_JEXEC') || die();

use Akeeba\Backup\Admin\Controller\Mixin\CustomACL;
use Akeeba\Backup\Admin\Controller\Mixin\PredefinedTaskList;
use FOF40\Container\Container;
use FOF40\Controller\Controller;
use Joomla\CMS\Component\ComponentHelper;

/**
 * Controller for the configuration wizard
 */
class ConfigurationWizard extends Controller
{
	use CustomACL;
	use PredefinedTaskList;

	/** @var bool  */
	private $noFlush = false;

	public function __construct(Container $container, array $config)
	{
		parent::__construct($container, $config);

		$this->setPredefinedTaskList(['main', 'ajax']);

		$this->noFlush = ComponentHelper::getParams('com_akeeba')->get('no_flush', 0) == 1;
	}

	/**
	 * Handles AJAX request by proxying the call to the Model, which does all the work, and returning the JSON encoded
	 * result back to the browser.
	 */
	public function ajax()
	{
		/** @var \Akeeba\Backup\Admin\Model\ConfigurationWizard $model */
		$model = $this->getModel();
		$model->setState('act', $this->input->get('act', '', 'cmd'));
		$ret = $model->runAjax();

		@ob_end_clean();
		echo '###' . json_encode($ret) . '###';

		if (!$this->noFlush)
		{
			flush();
		}

		$this->container->platform->closeApplication();
	}

}
PK�!]��ѡ��Controller/.htaccessnu&1i�<IfModule !mod_authz_core.c>
Order deny,allow
Deny from all
</IfModule>
<IfModule mod_authz_core.c>
  <RequireAll>
    Require all denied
  </RequireAll>
</IfModule>
PK�!]�y.���Controller/DatabaseFilters.phpnu&1i�<?php
/**
 * @package   akeebabackup
 * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

namespace Akeeba\Backup\Admin\Controller;

// Protect from unauthorized access
defined('_JEXEC') || die();

use Akeeba\Backup\Admin\Controller\Mixin\CustomACL;
use Akeeba\Backup\Admin\Controller\Mixin\PredefinedTaskList;
use FOF40\Container\Container;
use FOF40\Controller\Controller;

/**
 * Database Filters controller
 */
class DatabaseFilters extends Controller
{
	use CustomACL;
	use PredefinedTaskList;

	/** @var bool  */
	private $noFlush = false;

	/**
	 * Should I decode the "action" JSON data as an associative array? Default is false (meaning we're decoding as an
	 * stdClass object).
	 *
	 * @var bool
	 */
	protected $decodeJsonAsArray = false;

	public function __construct(Container $container, array $config)
	{
		parent::__construct($container, $config);

		$this->setPredefinedTaskList(['main', 'ajax']);
	}

	/**
	 * Handles the "main" task, which displays a folder and file list
	 *
	 */
	public function main()
	{
		$task = $this->input->get('task', 'normal', 'cmd');

		/** @var \Akeeba\Backup\Admin\Model\DatabaseFilters $model */
		$model = $this->getModel();
		$model->setState('browse_task', $task);

		$this->display(false);
	}

	/**
	 * AJAX proxy
	 */
	public function ajax()
	{
		// Parse the JSON data and reset the action query param to the resulting array
		$action_json = $this->input->get('action', '', 'none', 2);
		$action      = json_decode($action_json, $this->decodeJsonAsArray);

		/** @var \Akeeba\Backup\Admin\Model\DatabaseFilters $model */
		$model = $this->getModel();

		$model->setState('action', $action);

		$ret = $model->doAjax();

		@ob_end_clean();
		echo '###' . json_encode($ret) . '###';

		if (!$this->noFlush)
		{
			flush();
		}

		$this->container->platform->closeApplication();
	}
}
PK�!]�x9rS#S#Controller/ControlPanel.phpnu&1i�<?php
/**
 * @package   akeebabackup
 * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

namespace Akeeba\Backup\Admin\Controller;

// Protect from unauthorized access
defined('_JEXEC') || die();

use Akeeba\Backup\Admin\Controller\Mixin\CustomACL;
use Akeeba\Backup\Admin\Controller\Mixin\PredefinedTaskList;
use Akeeba\Backup\Admin\Helper\Utils;
use Akeeba\Backup\Admin\Model\Backup as BackupModel;
use Akeeba\Backup\Admin\Model\ConfigurationWizard;
use Akeeba\Backup\Admin\Model\Updates;
use Akeeba\Engine\Factory;
use Akeeba\Engine\Platform;
use Exception;
use FOF40\Container\Container;
use FOF40\Controller\Controller;
use FOF40\Factory\Exception\ModelNotFound;
use FOF40\Utils\ViewManifestMigration;
use Joomla\CMS\Language\Text;
use Joomla\CMS\Uri\Uri;
use RuntimeException;

/**
 * The Control Panel controller class
 */
class ControlPanel extends Controller
{
	use CustomACL, PredefinedTaskList;

	public function __construct(Container $container, array $config = [])
	{
		parent::__construct($container, $config);

		$this->setPredefinedTaskList([
			'main', 'SwitchProfile', 'applydlid', 'resetSecretWord',
			'forceUpdateDb', 'dismissUpsell', 'fixOutputDirectory', 'checkOutputDirectory', 'addRandomToFilename',
		]);
	}

	public function SwitchProfile()
	{
		// CSRF prevention
		$this->csrfProtection();

		$newProfile = $this->input->get('profileid', -10, 'int');

		if (!is_numeric($newProfile) || ($newProfile <= 0))
		{
			$this->setRedirect(\Joomla\CMS\Uri\Uri::base() . 'index.php?option=com_akeeba', \Joomla\CMS\Language\Text::_('COM_AKEEBA_CPANEL_PROFILE_SWITCH_ERROR'), 'error');

			return;
		}

		$this->container->platform->setSessionVar('profile', $newProfile, 'akeeba');
		$returnurl = $this->input->get('returnurl', '', 'base64');
		$url       = Utils::safeDecodeReturnUrl($returnurl);

		if (empty($url))
		{
			$url = \Joomla\CMS\Uri\Uri::base() . 'index.php?option=com_akeeba';
		}

		$this->setRedirect($url, \Joomla\CMS\Language\Text::_('COM_AKEEBA_CPANEL_PROFILE_SWITCH_OK'));
	}

	/**
	 * Applies the Download ID when the user is prompted about it in the Control Panel
	 */
	public function applydlid()
	{
		// CSRF prevention
		$this->csrfProtection();

		$msg     = \Joomla\CMS\Language\Text::_('COM_AKEEBA_CPANEL_ERR_INVALIDDOWNLOADID');
		$msgType = 'error';
		$dlid    = $this->input->getString('dlid', '');

		/** @var Updates $updateModel */
		$updateModel = $this->container->factory->model('Updates')->tmpInstance();
		$dlid        = $updateModel->sanitizeLicenseKey($dlid);
		$isValidDLID = $updateModel->isValidLicenseKey($dlid);

		// If the Download ID seems legit let's apply it
		if ($isValidDLID)
		{
			$msg     = null;
			$msgType = null;

			$updateModel->setLicenseKey($dlid);
		}

		// Redirect back to the control panel
		$returnurl = $this->input->get('returnurl', '', 'base64');
		$url       = Utils::safeDecodeReturnUrl($returnurl);

		if (empty($url))
		{
			$url = \Joomla\CMS\Uri\Uri::base() . 'index.php?option=com_akeeba';
		}

		$this->setRedirect($url, $msg, $msgType);
	}

	/**
	 * Reset the Secret Word for front-end and remote backup
	 *
	 * @return  void
	 */
	public function resetSecretWord()
	{
		// CSRF prevention
		$this->csrfProtection();

		$newSecret = $this->container->platform->getSessionVar('newSecretWord', null, 'akeeba.cpanel');

		if (empty($newSecret))
		{
			$random    = new \Akeeba\Engine\Util\RandomValue();
			$newSecret = $random->generateString(32);
			$this->container->platform->setSessionVar('newSecretWord', $newSecret, 'akeeba.cpanel');
		}

		$this->container->params->set('frontend_secret_word', $newSecret);
		$this->container->params->save();

		$this->container->platform->setSessionVar('newSecretWord', null, 'akeeba.cpanel');

		$msg = \Joomla\CMS\Language\Text::sprintf('COM_AKEEBA_CPANEL_MSG_FESECRETWORD_RESET', $newSecret);

		$url = 'index.php?option=com_akeeba';
		$this->setRedirect($url, $msg);
	}

	/**
	 * Resets the "updatedb" flag and forces the database updates
	 */
	public function forceUpdateDb()
	{
		// Reset the flag so the updates could take place
		$this->container->params->set('updatedb', null);
		$this->container->params->save();

		/** @var \Akeeba\Backup\Admin\Model\ControlPanel $model */
		$model = $this->getModel();

		try
		{
			$model->checkAndFixDatabase();
		}
		catch (\RuntimeException $e)
		{
			// This should never happen, since we reset the flag before execute the update, but you never know
		}

		$this->setRedirect('index.php?option=com_akeeba');
	}

	/**
	 * Dismisses the Core to Pro upsell for 15 days
	 *
	 * @return  void
	 */
	public function dismissUpsell()
	{
		// Reset the flag so the updates could take place
		$this->container->params->set('lastUpsellDismiss', time());
		$this->container->params->save();

		$this->setRedirect('index.php?option=com_akeeba');
	}

	/**
	 * Check the security of the backup output directory and return the results for consumption through AJAX
	 *
	 * @return  void
	 *
	 * @throws  Exception
	 *
	 * @since   7.0.3
	 */
	public function checkOutputDirectory()
	{
		/** @var \Akeeba\Backup\Admin\Model\ControlPanel $model */
		$model  = $this->getModel();
		$outDir = $model->getOutputDirectory();

		try
		{
			$result = $model->getOutputDirectoryWebAccessibleState($outDir);
		}
		catch (RuntimeException $e)
		{
			$result = [
				'readFile'   => false,
				'listFolder' => false,
				'isSystem'   => $model->isOutputDirectoryInSystemFolder(),
				'hasRandom'  => $model->backupFilenameHasRandom(),
			];
		}

		@ob_end_clean();

		echo '###' . json_encode($result) . '###';

		$this->container->platform->closeApplication();
	}

	/**
	 * Add security files to the output directory of the currently configured backup profile
	 *
	 * @return  void
	 *
	 * @throws  Exception
	 *
	 * @since   7.0.3
	 */
	public function fixOutputDirectory()
	{
		// CSRF prevention
		$this->csrfProtection();

		/** @var \Akeeba\Backup\Admin\Model\ControlPanel $model */
		$model  = $this->getModel();
		$outDir = $model->getOutputDirectory();

		$fsUtils = Factory::getFilesystemTools();
		$fsUtils->ensureNoAccess($outDir, true);

		$this->setRedirect('index.php?option=com_akeeba');
	}

	/**
	 * Adds the [RANDOM] variable to the backup output filename, save the configuration and reload the Control Panel.
	 *
	 * @return  void
	 *
	 * @throws  Exception
	 *
	 * @since   7.0.3
	 */
	public function addRandomToFilename()
	{
		// CSRF prevention
		$this->csrfProtection();
		$registry     = Factory::getConfiguration();
		$templateName = $registry->get('akeeba.basic.archive_name');

		if (strpos($templateName, '[RANDOM]') === false)
		{
			$templateName .= '-[RANDOM]';
			$registry->set('akeeba.basic.archive_name', $templateName);
			Platform::getInstance()->save_configuration();
		}

		$this->setRedirect('index.php?option=com_akeeba');
	}

	/**
	 * Run everything necessary to display the Control Panel page
	 *
	 * @return  void
	 *
	 * @throws  Exception
	 *
	 * @since   5.0.0
	 */
	protected function onBeforeMain()
	{
		/** @var \Akeeba\Backup\Admin\Model\ControlPanel $model */
		$model = $this->getModel();

		$engineConfig = Factory::getConfiguration();

		// Invalidate stale backups
		$params = $this->container->params;

		try
		{
			Factory::resetState([
				'global' => true,
				'log'    => false,
				'maxrun' => $params->get('failure_timeout', 180),
			]);
		}
		catch (Exception $e)
		{
			// This will die if the output directory is invalid. Let it die, then.
		}

		// Just in case the reset() loaded a stale configuration...
		Platform::getInstance()->load_configuration();
		Platform::getInstance()->apply_quirk_definitions();

		// Let's make sure the temporary and output directories are set correctly and writable...
		/** @var ConfigurationWizard $wizmodel */
		$wizmodel = $this->container->factory->model('ConfigurationWizard')->tmpInstance();
		$wizmodel->autofixDirectories();

		// Rebase Off-site Folder Inclusion filters to use site path variables
		/** @var \Akeeba\Backup\Admin\Model\IncludeFolders $incFoldersModel */
		try
		{
			$incFoldersModel = $this->container->factory->model('IncludeFolders')->tmpInstance();
			$incFoldersModel->rebaseFiltersToSiteDirs();
		}
		catch (ModelNotFound $e)
		{
			// Not a problem. This is expected to happen in the Core version.
		}

		// Check if we need to toggle the settings encryption feature
		$model->checkSettingsEncryption();

		// Convert existing log files to the new .log.php format
		/** @var BackupModel $backupModel */
		$backupModel = $this->container->factory->model('Backup')->tmpInstance();
		$backupModel->convertLogFiles();

		// Run the automatic update site refresh
		/** @var Updates $updateModel */
		$updateModel = $this->container->factory->model('Updates')->tmpInstance();
		$updateModel->refreshUpdateSite();

		ViewManifestMigration::migrateJoomla4MenuXMLFiles($this->container);
		ViewManifestMigration::removeJoomla3LegacyViews($this->container);
	}

}
PK�!]����Controller/Profiles.phpnu&1i�<?php
/**
 * @package   akeebabackup
 * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

namespace Akeeba\Backup\Admin\Controller;

// Protect from unauthorized access
defined('_JEXEC') || die();

use Akeeba\Backup\Admin\Controller\Mixin\CustomACL;
use FOF40\Controller\DataController;
use Joomla\CMS\Language\Text;
use RuntimeException;

class Profiles extends DataController
{
	use CustomACL;

	/**
	 * Imports an exported profile .json file
	 */
	public function import()
	{
		$this->csrfProtection();

		if (!$this->container->platform->authorise('akeeba.configure', 'com_akeeba'))
		{
			throw new RuntimeException(\Joomla\CMS\Language\Text::_('JERROR_ALERTNOAUTHOR'), 403);
		}

		/** @var \Akeeba\Backup\Admin\Model\Profiles $model */
		$model       = $this->getModel();

		// Get some data from the request
		$file = $this->input->files->get('importfile', array(), 'array');

		if (!isset($file['name']))
		{
			$this->setRedirect('index.php?option=com_akeeba&view=Profiles', \Joomla\CMS\Language\Text::_('MSG_UPLOAD_INVALID_REQUEST'), 'error');

			return;
		}

		// Load the file data
		$data = @file_get_contents($file['tmp_name']);
		@unlink($file['tmp_name']);

		// JSON decode
		$data = json_decode($data, true);

		// Import
		$message     = \Joomla\CMS\Language\Text::_('COM_AKEEBA_PROFILES_MSG_IMPORT_COMPLETE');
		$messageType = null;

		try
		{
			$model->reset()->import($data);
		}
		catch (RuntimeException $e)
		{
			$message     = $e->getMessage();
			$messageType = 'error';
		}

		// Redirect back to the main page
		$this->setRedirect('index.php?option=com_akeeba&view=Profiles', $message, $messageType);
	}

	/**
	 * Enable the Quick Icon for a record
	 *
	 * @since   6.1.2
	 * @throws  \Exception
	 */
	public function quickicon_publish()
	{
		$this->setQuickIcon(1);
	}

	/**
	 * Disable the Quick Icon for a record
	 *
	 * @since   6.1.2
	 * @throws  \Exception
	 */
	public function quickicon_unpublish()
	{
		$this->setQuickIcon(0);
	}

	/**
	 * Sets the Quick Icon status for the record.
	 *
	 * @param   int|bool  $published  Should this profile have a Quick Icon?
	 *
	 * @return  void
	 * @throws  \Exception
	 *
	 * @since   6.1.2
	 */
	private function setQuickIcon($published)
	{
		// CSRF prevention
		$this->csrfProtection();

		/** @var \Akeeba\Backup\Admin\Model\Profiles $model */
		$model = $this->getModel()->savestate(false);
		$ids   = $this->getIDsFromRequest($model, false);
		$error = false;

		try
		{
			$status = true;

			foreach ($ids as $id)
			{
				$model->find($id);
				$model->save([
					'quickicon' => $published ? 1 : 0
				]);
			}
		}
		catch (\Exception $e)
		{
			$status = false;
			$error  = $e->getMessage();
		}

		// Redirect
		if ($customURL = $this->input->getBase64('returnurl', ''))
		{
			$customURL = base64_decode($customURL);
		}

		$url = !empty($customURL) ? $customURL : 'index.php?option=' . $this->container->componentName . '&view=' . $this->container->inflector->pluralize($this->view) . $this->getItemidURLSuffix();

		if (!$status)
		{
			$this->setRedirect($url, $error, 'error');
		}
		else
		{
			$this->setRedirect($url);
		}
	}
}
PK�!]XR�

Controller/Log.phpnu&1i�<?php
/**
 * @package   akeebabackup
 * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

namespace Akeeba\Backup\Admin\Controller;

// Protect from unauthorized access
defined('_JEXEC') || die();

use Akeeba\Backup\Admin\Controller\Mixin\CustomACL;
use Akeeba\Backup\Admin\Controller\Mixin\PredefinedTaskList;
use Akeeba\Backup\Admin\Model\Log as LogModel;
use Akeeba\Engine\Factory;
use Akeeba\Engine\Platform;
use FOF40\Controller\Controller;

class Log extends Controller
{
	use CustomACL {
		CustomACL::onBeforeExecute as onCustomACLBeforeExecute;
	}

	/** @var bool  */
	private $noFlush = false;

	protected function onBeforeExecute(&$task)
	{
		$this->onCustomACLBeforeExecute($task);

		$profile_id = $this->input->getInt('profileid', null);

		if (!empty($profile_id) && is_numeric($profile_id) && ($profile_id > 0))
		{
			$this->container->platform->setSessionVar('profile', $profile_id, 'akeeba');
		}
	}

	/**
	 * Display the log page
	 *
	 * @return  void
	 */
	public function onBeforeDefault()
	{
		$tag = $this->input->get('tag', null, 'cmd');
		$latest = $this->input->get('latest', false, 'int');

		if (empty($tag))
		{
			$tag = null;
		}

		/** @var LogModel $model */
		$model = $this->getModel();

		if ($latest)
		{
			$logFiles = $model->getLogFiles();
			$tag = array_shift($logFiles);
		}

		$model->setState('tag', $tag);

		Platform::getInstance()->load_configuration(Platform::getInstance()->get_active_profile());
	}

	/**
	 * Renders the contents of the log, used inside the IFRAME of the log page
	 *
	 * @return  void
	 */
	public function iframe()
	{
		$tag = $this->input->get('tag', null, 'cmd');

		if (empty($tag))
		{
			$tag = null;
		}

		/** @var LogModel $model */
		$model = $this->getModel();
		$model->setState('tag', $tag);

		Platform::getInstance()->load_configuration(Platform::getInstance()->get_active_profile());

		$this->display();
	}

	/**
	 * Download the log file as a text file
	 *
	 * @return  void
	 */
	public function download()
	{
		Platform::getInstance()->load_configuration(Platform::getInstance()->get_active_profile());

		$tag = $this->input->get('tag', null, 'cmd');

		if (empty($tag))
		{
			$tag = null;
		}

		$asAttachment = $this->input->getBool('attachment', true);

		@ob_end_clean(); // In case some braindead plugin spits its own HTML
		header("Cache-Control: no-cache, must-revalidate"); // HTTP/1.1
		header("Expires: Sat, 26 Jul 1997 05:00:00 GMT"); // Date in the past
		header("Content-Description: File Transfer");
		header('Content-Type: text/plain');

		if ($asAttachment)
		{
			header('Content-Disposition: attachment; filename="Akeeba Backup Debug Log.txt"');
		}

		/** @var LogModel $model */
		$model = $this->getModel();
		$model->setState('tag', $tag);
		$model->echoRawLog();

		if (!$this->noFlush)
		{
			flush();
		}

		$this->container->platform->closeApplication();
	}

	public function inlineRaw()
	{
		Platform::getInstance()->load_configuration(Platform::getInstance()->get_active_profile());

		$tag = $this->input->get('tag', null, 'cmd');

		if (empty($tag))
		{
			$tag = null;
		}

		/** @var LogModel $model */
		$model = $this->getModel();
		$model->setState('tag', $tag);
		echo "<pre>";
		$model->echoRawLog();
		echo "</pre>";
	}
}
PK�!]��7��Controller/FTPBrowser.phpnu&1i�<?php
/**
 * @package   akeebabackup
 * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

namespace Akeeba\Backup\Admin\Controller;

// Protect from unauthorized access
defined('_JEXEC') || die();

use Akeeba\Backup\Admin\Controller\Mixin\CustomACL;
use FOF40\Controller\Controller;

/**
 * Controller for the FTP folder browser
 */
class FTPBrowser extends Controller
{
	use CustomACL;

	/** @var bool  */
	private $noFlush = false;

	protected function onBeforeMain()
	{
		/** @var \Akeeba\Backup\Admin\Model\FTPBrowser $model */
		$model = $this->getModel();

		// Grab the data and push them to the model
		$model->host      = $this->input->get('host', '', 'string');
		$model->port      = $this->input->get('port', 21, 'int');
		$model->passive   = $this->input->get('passive', 1, 'int');
		$model->ssl       = $this->input->get('ssl', 0, 'int');
		$model->username  = $this->input->get('username', '', 'none', 2);
		$model->password  = $this->input->get('password', '', 'none', 2);
		$model->directory = $this->input->get('directory', '', 'none', 2);

		if (empty($model->port))
		{
			$model->port = $model->ssl ? 990 : 21;
		}

		$ret = $model->doBrowse();

		@ob_end_clean();
		echo '###' . json_encode($ret) . '###';

		if (!$this->noFlush)
		{
			flush();
		}

		$this->container->platform->closeApplication();
	}
}
PK�!]!|�f��Controller/Mixin/CustomACL.phpnu&1i�<?php
/**
 * @package   akeebabackup
 * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

namespace Akeeba\Backup\Admin\Controller\Mixin;

// Protect from unauthorized access
defined('_JEXEC') || die();

use RuntimeException;
use Joomla\CMS\Language\Text;

trait CustomACL
{
	protected function onBeforeExecute(&$task)
	{
		$this->akeebaBackupACLCheck($this->view, $this->task);
	}

	/**
	 * Checks if the currently logged in user has the required ACL privileges to access the current view. If not, a
	 * RuntimeException is thrown.
	 *
	 * @return  void
	 */
	protected function akeebaBackupACLCheck($view, $task)
	{
		// Akeeba Backup-specific ACL checks. All views not listed here are limited by the akeeba.configure privilege.
		$viewACLMap = [
			'ControlPanel'       => 'core.manage',
			'Backup'             => 'akeeba.backup',
			'Manage'             => 'core.manage',
			'Manage.download'    => 'akeeba.download',
			'Manage.remove'      => 'akeeba.download',
			'Manage.deletefiles' => 'akeeba.download',
			'Manage.showcomment' => 'akeeba.backup',
			'Manage.save'        => 'akeeba.download',
			'Manage.restore'     => 'akeeba.configure',
			'Manage.cancel'      => 'akeeba.backup',
			'Upload'             => 'akeeba.backup',
			'RemoteFiles'        => 'akeeba.download',
			'Transfer'           => 'akeeba.download',
		];

		// Default
		$privilege = 'akeeba.configure';

		// Just the view was found
		if (array_key_exists($view, $viewACLMap))
		{
			$privilege = $viewACLMap[$view];
		}

		// The view AND task was found
		if (array_key_exists($view . '.' . $task, $viewACLMap))
		{
			$privilege = $viewACLMap[$view . '.' . $task];
		}

		// If an empty privilege is defined do not perform any ACL checks
		if (empty($privilege))
		{
			return;
		}

		if (!$this->container->platform->authorise($privilege, 'com_akeeba'))
		{
			throw new RuntimeException(\Joomla\CMS\Language\Text::_('JERROR_ALERTNOAUTHOR'), 403);
		}
	}
}
PK�!]O��k��'Controller/Mixin/PredefinedTaskList.phpnu&1i�<?php
/**
 * @package   akeebabackup
 * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

namespace Akeeba\Backup\Admin\Controller\Mixin;

// Protect from unauthorized access
defined('_JEXEC') || die();

/**
 * Force a Controller to allow access to specific tasks only, no matter which tasks are already defined in this
 * Controller.
 */
trait PredefinedTaskList
{
	/**
	 * A list of predefined tasks. Trying to access any other task will result in the first task of this list being
	 * executed instead.
	 *
	 * @var array
	 */
	protected $predefinedTaskList = array();

	/**
	 * Overrides the execute method to implement the predefined task list feature
	 *
	 * @param   string  $task  The task to execute
	 *
	 * @return  mixed  The controller task result
	 */
	public function execute($task)
	{
		if (!in_array($task, $this->predefinedTaskList))
		{
			$task = reset($this->predefinedTaskList);
		}

		return parent::execute($task);
	}

	/**
	 * Sets the predefined task list and registers the first task in the list as the Controller's default task
	 *
	 * @param   array  $taskList  The task list to register
	 */
	public function setPredefinedTaskList(array $taskList)
	{
		// First, unregister all known tasks which are not in the taskList
		$allTasks = $this->getTasks();

		foreach ($allTasks as $task)
		{
			if (in_array($task, $taskList))
			{
				continue;
			}

			$this->unregisterTask($task);
		}

		// Set the predefined task list
		$this->predefinedTaskList = $taskList;

		// Set the default task
		$this->registerDefaultTask(reset($this->predefinedTaskList));

	}
}
PK�!]} �<66Controller/Backup.phpnu&1i�<?php
/**
 * @package   akeebabackup
 * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

namespace Akeeba\Backup\Admin\Controller;

// Protect from unauthorized access
defined('_JEXEC') || die();

use Akeeba\Backup\Admin\Controller\Mixin\CustomACL;
use Akeeba\Backup\Admin\Controller\Mixin\PredefinedTaskList;
use Akeeba\Backup\Admin\Helper\Utils;
use Akeeba\Engine\Platform;
use FOF40\Container\Container;
use FOF40\Controller\Controller;
use Joomla\CMS\Component\ComponentHelper;
use Joomla\CMS\Factory;

/**
 * Backup page controller
 */
class Backup extends Controller
{
	use CustomACL;
	use PredefinedTaskList;

	/** @var bool  */
	private $noFlush = false;

	public function __construct(Container $container, array $config)
	{
		parent::__construct($container, $config);

		$this->setPredefinedTaskList([
			'main',
			'ajax',
		]);

		$this->noFlush = ComponentHelper::getParams('com_akeeba')->get('no_flush', 0) == 1;
	}

	/**
	 * This task handles the AJAX requests
	 */
	public function ajax()
	{
		/** @var \Akeeba\Backup\Admin\Model\Backup $model */
		$model = $this->getModel();

		// Push all necessary information to the model's state
		$model->setState('profile', $this->input->get('profileid', Platform::getInstance()->get_active_profile(), 'int'));
		$model->setState('ajax', $this->input->get('ajax', '', 'cmd'));
		$model->setState('description', $this->input->get('description', '', 'string'));
		$model->setState('comment', $this->input->get('comment', '', 'html', 2));
		$model->setState('jpskey', $this->input->get('jpskey', '', 'raw', 2));
		$model->setState('angiekey', $this->input->get('angiekey', '', 'raw', 2));
		$model->setState('backupid', $this->input->get('backupid', null, 'cmd'));
		$model->setState('tag', $this->input->get('tag', 'backend', 'cmd'));
		$model->setState('errorMessage', $this->input->getString('errorMessage', ''));

		// System Restore Point backup state variables (obsolete)
		$model->setState('type', strtolower($this->input->get('type', '', 'cmd')));
		$model->setState('name', strtolower($this->input->get('name', '', 'cmd')));
		$model->setState('group', strtolower($this->input->get('group', '', 'cmd')));
		$model->setState('customdirs', $this->input->get('customdirs', [], 'array', 2));
		$model->setState('customfiles', $this->input->get('customfiles', [], 'array', 2));
		$model->setState('extraprefixes', $this->input->get('extraprefixes', [], 'array', 2));
		$model->setState('customtables', $this->input->get('customtables', [], 'array', 2));
		$model->setState('skiptables', $this->input->get('skiptables', [], 'array', 2));
		$model->setState('langfiles', $this->input->get('langfiles', [], 'array', 2));
		$model->setState('xmlname', $this->input->getString('xmlname', ''));

		// Set up the tag
		define('AKEEBA_BACKUP_ORIGIN', $this->input->get('tag', 'backend', 'cmd'));

		// Run the backup step
		$ret_array = $model->runBackup();

		// We use this nasty trick to avoid broken 3PD plugins from barfing all over our output
		@ob_end_clean();
		header('Content-type: text/plain');
		header('Connection: close');
		echo '###' . json_encode($ret_array) . '###';

		if (!$this->noFlush)
		{
			flush();
		}

		$this->container->platform->closeApplication();
	}

	/**
	 * Default task; shows the initial page where the user selects a profile and enters description and comment
	 */
	protected function onBeforeMain()
	{
		// Did the user ask to switch the active profile?
		$newProfile = $this->input->get('profileid', -10, 'int');
		$autostart  = $this->input->get('autostart', 0, 'int');

		if (is_numeric($newProfile) && ($newProfile > 0))
		{
			/**
			 * We have to remove CSRF protection due to the way the Joomla administrator menu manager works. Menu item
			 * options are passed as URL parameters. However, we cannot pass dynamic parameters (like the token). This
			 * means that a user can create a menu item with a specific backup profile ID. Normally this would cause a
			 * 403 which is frustrating to the user because they might want to give their client the option to run a
			 * backup with a specific profile AND let them enter a description and comment. Therefore we have to remove
			 * the CSRF protection.
			 *
			 * NB! We do understand the potential risk involved. Between Joomla's AMATEURISH implementation of custom
			 * administrator menus and user demands for features we have to (have these very vocal users and everyone
			 * else) assume that (actually really small) risk.
			 */
			// $this->csrfProtection();
			$this->container->platform->setSessionVar('profile', $newProfile, 'akeeba');

			/**
			 * DO NOT REMOVE!
			 *
			 * The Model will only try to load the configuration after nuking the factory. This causes Profile 1 to be
			 * loaded first. Then it figures out it needs to load a different profile and it does – but the protected keys
			 * are NOT replaced, meaning that certain configuration parameters are not replaced. Most notably, the chain.
			 * This causes backups to behave weirdly. So, DON'T REMOVE THIS UNLESS WE REFACTOR THE MODEL.
			 */
			Platform::getInstance()->load_configuration($newProfile);
		}

		// Deactivate the menus
		Factory::getApplication()->input->set('hidemainmenu', 1);

		/** @var \Akeeba\Backup\Admin\Model\Backup $model */
		$model = $this->getModel();

		// Sanitize the return URL
		$returnUrl = $this->input->get('returnurl', '', 'raw');
		$returnUrl = Utils::safeDecodeReturnUrl($returnUrl);

		// Push data to the model
		$model->setState('profile', $this->input->get('profileid', -10, 'int'));
		$model->setState('description', $this->input->get('description', '', 'string', 2));
		$model->setState('comment', $this->input->get('comment', '', 'html', 2));
		$model->setState('ajax', $this->input->get('ajax', '', 'cmd'));
		$model->setState('autostart', $autostart);
		$model->setState('jpskey', $this->input->get('jpskey', '', 'raw', 2));
		$model->setState('angiekey', $this->input->get('angiekey', '', 'raw', 2));
		$model->setState('returnurl', $returnUrl);
		$model->setState('backupid', $this->input->get('backupid', null, 'cmd'));
	}
}
PK�!]�P�SCCController/Profile.phpnu&1i�<?php
/**
 * @package   akeebabackup
 * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

namespace Akeeba\Backup\Admin\Controller;

// Protect from unauthorized access
defined('_JEXEC') || die();

class Profile extends Profiles
{

}
PK�!]���,��Controller/SFTPBrowser.phpnu&1i�<?php
/**
 * @package   akeebabackup
 * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

namespace Akeeba\Backup\Admin\Controller;

// Protect from unauthorized access
defined('_JEXEC') || die();

use Akeeba\Backup\Admin\Controller\Mixin\CustomACL;
use FOF40\Controller\Controller;

/**
 * Controller for the SFTP folder browser
 */
class SFTPBrowser extends Controller
{
	use CustomACL;

	/** @var bool  */
	private $noFlush = false;

	protected function onBeforeMain()
	{
		/** @var \Akeeba\Backup\Admin\Model\SFTPBrowser $model */
		$model = $this->getModel();

		// Grab the data and push them to the model
		$model->host      = $this->input->get('host', '', 'string');
		$model->port      = $this->input->get('port', 21, 'int');
		$model->username  = $this->input->get('username', '', 'none', 2);
		$model->password  = $this->input->get('password', '', 'none', 2);
		$model->privkey   = $this->input->get('privkey', '', 'none', 2);
		$model->pubkey    = $this->input->get('pubkey', '', 'none', 2);
		$model->directory = $this->input->get('directory', '', 'none', 2);

		if (empty($model->port))
		{
			$model->port = 22;
		}

		$ret = $model->doBrowse();

		@ob_end_clean();
		echo '###' . json_encode($ret) . '###';

		if (!$this->noFlush)
		{
			flush();
		}

		$this->container->platform->closeApplication();
	}
}
PK�!]����%�%Controller/Manage.phpnu&1i�<?php
/**
 * @package   akeebabackup
 * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

namespace Akeeba\Backup\Admin\Controller;

// Protect from unauthorized access
defined('_JEXEC') || die();

use Akeeba\Backup\Admin\Controller\Mixin\CustomACL;
use Akeeba\Backup\Admin\Model\Statistics;
use Akeeba\Engine\Factory;
use Akeeba\Engine\Platform;
use Exception;
use FOF40\Container\Container;
use FOF40\Controller\Controller;
use Joomla\CMS\Language\Text;
use Joomla\CMS\Uri\Uri;


/**
 * Backup page controller
 */
class Manage extends Controller
{
	use CustomACL;

	/** @var bool  */
	private $noFlush = false;

	public function __construct(Container $container, array $config)
	{
		if (!is_array($config))
		{
			$config = [];
		}

		$config['modelName'] = 'Statistics';

		parent::__construct($container, $config);
	}

	/**
	 * Downloads the backup archive of the specified backup record
	 *
	 * @return  void
	 */
	public function download()
	{
		$ids = $this->getIDsFromRequest();
		$id  = count($ids) ? array_pop($ids) : -1;

		$part = $this->input->get('part', -1, 'int');

		if ($id <= 0)
		{
			$this->setRedirect(Uri::base() . 'index.php?option=com_akeeba&view=Manage', Text::_('COM_AKEEBA_BUADMIN_ERROR_INVALIDID'), 'error');

			return;
		}

		$stat         = Platform::getInstance()->get_statistics($id);
		$allFilenames = Factory::getStatistics()->get_all_filenames($stat);

		$filename = null;

		// Check single part files
		$countAllFilenames = $allFilenames === null ? 0 : count($allFilenames);
		if (($countAllFilenames == 1) && ($part == -1))
		{
			$filename = array_shift($allFilenames);
		}
		elseif (($countAllFilenames > 0) && ($countAllFilenames > $part) && ($part >= 0))
		{
			$filename = $allFilenames[ $part ];
		}

		if (is_null($filename) || empty($filename) || !@file_exists($filename))
		{
			$this->setRedirect(Uri::base() . 'index.php?option=com_akeeba&view=Manage', Text::_('COM_AKEEBA_BUADMIN_ERROR_INVALIDDOWNLOAD'), 'error');

			return;
		}

		// Remove php's time limit
		if (function_exists('ini_get') && function_exists('set_time_limit'))
		{
			if (!ini_get('safe_mode'))
			{
				@set_time_limit(0);
			}
		}

		$basename  = @basename($filename);
		$filesize  = @filesize($filename);
		$extension = strtolower(str_replace(".", "", strrchr($filename, ".")));

		while (@ob_end_clean())
		{
			;
		}
		@clearstatcache();
		// Send MIME headers
		header('MIME-Version: 1.0');
		header('Content-Disposition: attachment; filename="' . $basename . '"');
		header('Content-Transfer-Encoding: binary');
		header('Accept-Ranges: bytes');

		switch ($extension)
		{
			case 'zip':
				// ZIP MIME type
				header('Content-Type: application/zip');
				break;

			default:
				// Generic binary data MIME type
				header('Content-Type: application/octet-stream');
				break;
		}

		// Notify of filesize, if this info is available
		if ($filesize > 0)
		{
			header('Content-Length: ' . @filesize($filename));
		}

		// Disable caching
		header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
		header("Expires: 0");
		header('Pragma: no-cache');

		if (!$this->noFlush)
		{
			flush();
		}

		if (!$filesize)
		{
			// If the filesize is not reported, hope that readfile works
			@readfile($filename);

			$this->container->platform->closeApplication(0);
		}

		// If the filesize is reported, use 1M chunks for echoing the data to the browser
		$blocksize = 1048576; //1M chunks
		$handle    = @fopen($filename, "r");

		// Now we need to loop through the file and echo out chunks of file data
		if ($handle !== false)
		{
			while (!@feof($handle))
			{
				echo @fread($handle, $blocksize);
				@ob_flush();

				if (!$this->noFlush)
				{
					flush();
				}
			}
		}

		if ($handle !== false)
		{
			@fclose($handle);
		}

		$this->container->platform->closeApplication(0);
	}

	/**
	 * Deletes one or more backup statistics records and their associated backup files
	 */
	public function remove()
	{
		// CSRF prevention
		$this->csrfProtection();

		$ids = $this->getIDsFromRequest();

		if (empty($ids))
		{
			$this->setRedirect(Uri::base() . 'index.php?option=com_akeeba&view=Manage', Text::_('COM_AKEEBA_BUADMIN_ERROR_INVALIDID'), 'error');

			return;
		}

		foreach ($ids as $id)
		{
			try
			{
				$msg    = Text::_('COM_AKEEBA_BUADMIN_ERROR_INVALIDID');
				$result = false;

				if ($id > 0)
				{
					/** @var Statistics $model */
					$model = $this->getModel();
					$model->setState('id', $id);
					$result = $model->delete();
				}

			}
			catch (\RuntimeException $e)
			{
				$result = false;
				$msg    = $e->getMessage();
			}

			if (!$result)
			{
				$this->setRedirect(Uri::base() . 'index.php?option=com_akeeba&view=Manage', $msg, 'error');

				return;
			}
		}

		$this->setRedirect(Uri::base() . 'index.php?option=com_akeeba&view=Manage', Text::_('COM_AKEEBA_BUADMIN_MSG_DELETED'));
	}

	/**
	 * Deletes backup files associated to one or several backup statistics records
	 */
	public function deletefiles()
	{
		// CSRF prevention
		$this->csrfProtection();

		$ids = $this->getIDsFromRequest();

		if (empty($ids))
		{
			$this->setRedirect(Uri::base() . 'index.php?option=com_akeeba&view=Manage', Text::_('COM_AKEEBA_BUADMIN_ERROR_INVALIDID'), 'error');

			return;
		}

		foreach ($ids as $id)
		{
			try
			{
				$msg    = Text::_('COM_AKEEBA_BUADMIN_ERROR_INVALIDID');
				$result = false;

				if ($id > 0)
				{
					/** @var Statistics $model */
					$model = $this->getModel();
					$model->setState('id', $id);
					$result = $model->deleteFile();
				}
			}
			catch (\RuntimeException $e)
			{
				$result = false;
				$msg    = $e->getMessage();
			}

			if (!$result)
			{
				$this->setRedirect(Uri::base() . 'index.php?option=com_akeeba&view=Manage', $msg, 'error');

				return;
			}
		}

		$this->setRedirect(Uri::base() . 'index.php?option=com_akeeba&view=Manage', Text::_('COM_AKEEBA_BUADMIN_MSG_DELETEDFILE'));
	}

	public function showcomment()
	{
		$ids = $this->getIDsFromRequest();

		if (empty($ids))
		{
			$ids = [0];
		}

		$id = array_pop($ids);

		if ($id <= 0)
		{
			$this->setRedirect(Uri::base() . 'index.php?option=com_akeeba&view=Manage', Text::_('COM_AKEEBA_BUADMIN_ERROR_INVALIDID'), 'error');
		}

		/** @var Statistics $model */
		$model = $this->getModel();
		$model->setState('id', $id);

		$this->layout = 'comment';
		$this->display(false);
	}

	/**
	 * Save the comments back to a backup record
	 */
	public function save()
	{
		// CSRF prevention
		$this->csrfProtection();

		$id          = $this->input->get('id', 0, 'int');
		$description = $this->input->get('description', '', 'string');
		$comment     = $this->input->get('comment', null, 'string', 4);

		$statistic                = Platform::getInstance()->get_statistics($id);
		$statistic['description'] = $description;
		$statistic['comment']     = $comment;

		$result = Platform::getInstance()->set_or_update_statistics($id, $statistic);

		$message = Text::_('COM_AKEEBA_BUADMIN_LOG_SAVEDOK');
		$type    = 'message';

		if ($result === false)
		{
			$message = Text::_('COM_AKEEBA_BUADMIN_LOG_SAVEERROR');
			$type    = 'error';
		}

		$this->setRedirect(Uri::base() . 'index.php?option=com_akeeba&view=Manage', $message, $type);
	}

	public function restore()
	{
		// CSRF prevention
		$this->csrfProtection();

		$ids = $this->getIDsFromRequest();

		if (empty($ids))
		{
			$ids = [0];
		}

		$id = array_pop($ids);

		$url = Uri::base() . 'index.php?option=com_akeeba&view=Restore&id=' . $id;
		$this->setRedirect($url);
	}

	public function cancel()
	{
		// CSRF prevention
		$this->csrfProtection();

		$this->setRedirect(Uri::base() . 'index.php?option=com_akeeba&view=Manage');
	}

	public function hidemodal()
	{
		/** @var Statistics $model */
		$model = $this->getModel();
		$model->hideRestorationInstructionsModal();

		$this->setRedirect(Uri::base() . 'index.php?option=com_akeeba&view=Manage');
	}

	/**
	 * Freeze select records
	 *
	 * @throws Exception
	 */
	public function freeze()
	{
		$this->csrfProtection();

		$ids   = $this->getIDsFromRequest();

		/** @var Statistics $model */
		$model = $this->getModel();

		$message = Text::_('COM_AKEEBA_BUADMIN_FREEZE_OK');
		$type    = 'message';

		try
		{
			$model->freezeUnfreezeRecords($ids, 1);
		}
		catch (Exception $e)
		{
			$message = Text::sprintf('COM_AKEEBA_BUADMIN_FREEZE_ERROR', $e->getMessage());
			$type    = 'error';
		}

		$this->setRedirect(Uri::base() . 'index.php?option=com_akeeba&view=Manage', $message, $type);
	}

	/**
	 * Unfreeze select records
	 *
	 * @throws Exception
	 */
	public function unfreeze()
	{
		$this->csrfProtection();

		$ids   = $this->getIDsFromRequest();

		/** @var Statistics $model */
		$model = $this->getModel();

		$message = Text::_('COM_AKEEBA_BUADMIN_UNFREEZE_OK');
		$type    = 'message';

		try
		{
			$model->freezeUnfreezeRecords($ids, 0);
		}
		catch (Exception $e)
		{
			$message = Text::sprintf('COM_AKEEBA_BUADMIN_UNFREEZE_ERROR', $e->getMessage());
			$type    = 'error';
		}

		$this->setRedirect(Uri::base() . 'index.php?option=com_akeeba&view=Manage', $message, $type);
	}

	/**
	 * Gets the list of IDs from the request data
	 *
	 * @return array
	 */
	protected function getIDsFromRequest()
	{
		// Get the ID or list of IDs from the request or the configuration
		$cid = $this->input->get('cid', array(), 'array');
		$id  = $this->input->getInt('id', 0);

		$ids = array();

		if (is_array($cid) && !empty($cid))
		{
			$ids = $cid;
		}
		elseif (!empty($id))
		{
			$ids = array($id);
		}

		return $ids;
	}
}
PK�!]|��NController/web.confignu&1i�<?xml version="1.0"?>
<!--
    This only works on IIS 7 or later. See https://www.iis.net/configreference/system.webserver/security/requestfiltering/fileextensions
-->
<configuration>
    <system.webServer>
        <security>
            <requestFiltering>
                <fileExtensions allowUnlisted="false" >
                    <clear />
                    <add fileExtension=".html" allowed="true"/>
                </fileExtensions>
            </requestFiltering>
        </security>
    </system.webServer>
</configuration>PK�!]����o"o"Controller/Configuration.phpnu&1i�<?php
/**
 * @package   akeebabackup
 * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

namespace Akeeba\Backup\Admin\Controller;

// Protect from unauthorized access
defined('_JEXEC') || die();

use Akeeba\Backup\Admin\Controller\Mixin\CustomACL;
use Akeeba\Backup\Admin\Model\Profiles;
use Akeeba\Engine\Platform;
use FOF40\Container\Container;
use FOF40\Controller\Controller;
use FOF40\Input\Input;
use Joomla\CMS\Component\ComponentHelper;
use Joomla\CMS\Factory;
use Joomla\CMS\Language\Text;
use Joomla\CMS\Uri\Uri;

/**
 * Configuration page controller
 */
class Configuration extends Controller
{
	use CustomACL;

	/** @var bool  */
	private $noFlush = false;

	public function __construct(Container $container, array $config = [])
	{
		parent::__construct($container, $config);

		$this->noFlush = ComponentHelper::getParams('com_akeeba')->get('no_flush', 0) == 1;
	}


	/**
	 * Handle the apply task which saves the configuration settings and shows the page again
	 */
	public function apply()
	{
		// CSRF prevention
		$this->csrfProtection();

		// Which input am I going to use?
		$jsonFormData = $this->input->getString('jsonForm', null);
		$jsonFormData = is_string($jsonFormData) ? @json_decode($jsonFormData, true) : $jsonFormData;

		if (empty($jsonFormData))
		{
			$input = $this->input;
		}
		else
		{
			$rawData = [];

			foreach ($jsonFormData as $k => $v)
			{
				if (substr($k, 0, 4) !== 'var[')
				{
					$rawData[$k] = $v;

					continue;
				}

				$k                  = substr($k, 4, -1);
				$rawData['var']     = $rawData['var'] ?? [];
				$rawData['var'][$k] = $v;
			}

			$input = new Input($rawData);
		}

		// Get the var array from the request
		$data                        = $input->get('var', [], 'array', 4);
		$data['akeeba.flag.confwiz'] = 1;

		/** @var \Akeeba\Backup\Admin\Model\Configuration $model */
		$model = $this->getModel();
		$model->setState('engineconfig', $data);
		$model->saveEngineConfig();

		// Finally, save the profile description if it has changed
		$profileid = Platform::getInstance()->get_active_profile();

		// Get profile name
		/** @var Profiles $profileRecord */
		$profileRecord = $this->container->factory->model('Profiles')->tmpInstance();
		$profileRecord->findOrFail($profileid);
		$oldProfileName = $profileRecord->description;
		$oldQuickIcon   = $profileRecord->quickicon;

		$profileName = $input->getString('profilename', null);
		$profileName = trim($profileName);

		$quickIconValue = $input->getCmd('quickicon', '');
		$quickIcon      = (int) !empty($quickIconValue);

		$mustSaveProfile = !empty($profileName) && ($profileName != $oldProfileName);
		$mustSaveProfile = $mustSaveProfile || ($quickIcon != $oldQuickIcon);

		if ($mustSaveProfile)
		{
			$profileRecord->save([
				'description' => $profileName,
				'quickicon'   => $quickIcon
			]);
		}

		$this->setRedirect(\Joomla\CMS\Uri\Uri::base() . 'index.php?option=com_akeeba&view=Configuration', \Joomla\CMS\Language\Text::_('COM_AKEEBA_CONFIG_SAVE_OK'));
	}

	/**
	 * Handle the save task which saves the configuration settings and returns to the Control Panel page
	 */
	public function save()
	{
		$this->apply();
		$this->setRedirect(\Joomla\CMS\Uri\Uri::base() . 'index.php?option=com_akeeba', \Joomla\CMS\Language\Text::_('COM_AKEEBA_CONFIG_SAVE_OK'));
	}

	/**
	 * Handle the save & new task which saves settings, creates a new backup profile, activates it and proceed to the
	 * configuration page once more.
	 */
	public function savenew()
	{
		// Save the current profile
		$this->apply();

		// Create a new profile
		$profileid = Platform::getInstance()->get_active_profile();

		/** @var Profiles $profile */
		$profile = $this->container->factory->model('Profiles')->tmpInstance();
		$profile
			// Load and clone the record we just saved
			->findOrFail($profileid)
			->getClone()
		;
		// Must unset ID before save. The ID cannot be bound with bind()/save(), hence the need to do it the hard way.
		$profile->id = null;
		$profile
			->save([
				'description' => \Joomla\CMS\Language\Text::_('COM_AKEEBA_CONFIG_SAVENEW_DEFAULT_PROFILE_NAME')
			])
		;

		// Activate and edit the new profile
		$returnUrl = base64_encode($this->redirect);
		$token     = $this->container->platform->getToken(true);
		$url       = \Joomla\CMS\Uri\Uri::base() . 'index.php?option=com_akeeba&task=SwitchProfile&profileid=' . $profile->getId() .
			'&returnurl=' . $returnUrl . '&' . $token . '=1';
		$this->setRedirect($url);
	}

	/**
	 * Handle the cancel task which doesn't save anything and returns to the Control Panel page
	 */
	public function cancel()
	{
		// CSRF prevention
		$this->csrfProtection();
		$this->setRedirect(\Joomla\CMS\Uri\Uri::base() . 'index.php?option=com_akeeba');
	}

	/**
	 * Tests the validity of the FTP connection details
	 */
	public function testftp()
	{
		/** @var \Akeeba\Backup\Admin\Model\Configuration $model */
		$model = $this->getModel();
		$input = $this->input;
		$model->setState('isCurl', $input->get('isCurl', 0, 'int'));
		$model->setState('host', $input->get('host', '', 'raw', 2));
		$model->setState('port', $input->get('port', 21, 'int'));
		$model->setState('user', $input->get('user', '', 'raw', 2));
		$model->setState('pass', $input->get('pass', '', 'raw', 2));
		$model->setState('initdir', $input->get('initdir', '', 'raw', 2));
		$model->setState('usessl', $input->getCmd('usessl', "false") == "true");
		$model->setState('passive', $input->getCmd('passive', "false") == "true");
		$model->setState('passive_mode_workaround', $input->getCmd('passive_mode_workaround', "false") == "true");

		try
		{
			$model->testFTP();
			$testResult = true;
		}
		catch (\RuntimeException $e)
		{
			$testResult = $e->getMessage();
		}

		@ob_end_clean();
		echo '###' . json_encode($testResult) . '###';

		if (!$this->noFlush)
		{
			flush();
		}

		$this->container->platform->closeApplication();
	}

	/**
	 * Tests the validity of the SFTP connection details
	 */
	public function testsftp()
	{
		/** @var \Akeeba\Backup\Admin\Model\Configuration $model */
		$model = $this->getModel();
		$model->setState('isCurl', $this->input->get('isCurl', 0, 'int'));
		$model->setState('host', $this->input->get('host', '', 'raw', 2));
		$model->setState('port', $this->input->get('port', 21, 'int'));
		$model->setState('user', $this->input->get('user', '', 'raw', 2));
		$model->setState('pass', $this->input->get('pass', '', 'raw', 2));
		$model->setState('privkey', $this->input->get('privkey', '', 'raw', 2));
		$model->setState('pubkey', $this->input->get('pubkey', '', 'raw', 2));
		$model->setState('initdir', $this->input->get('initdir', '', 'raw', 2));

		try
		{
			$model->testSFTP();
			$testResult = true;
		}
		catch (\RuntimeException $e)
		{
			$testResult = $e->getMessage();
		}

		@ob_end_clean();
		echo '###' . json_encode($testResult) . '###';

		if (!$this->noFlush)
		{
			flush();
		}

		$this->container->platform->closeApplication();
	}

	/**
	 * Opens an OAuth window for the selected data processing engine
	 */
	public function dpeoauthopen()
	{
		/** @var \Akeeba\Backup\Admin\Model\Configuration $model */
		$model = $this->getModel();
		$model->setState('engine', $this->input->get('engine', '', 'raw'));
		$model->setState('params', $this->input->get('params', array(), 'array', 2));

		@ob_end_clean();
		$model->dpeOuthOpen();

		if (!$this->noFlush)
		{
			flush();
		}

		$this->container->platform->closeApplication();
	}

	/**
	 * Runs a custom API call against the selected data processing engine and returns the JSON encoded result
	 */
	public function dpecustomapi()
	{
		/** @var \Akeeba\Backup\Admin\Model\Configuration $model */
		$model = $this->getModel();
		$model->setState('engine', $this->input->get('engine', '', 'raw', 2));
		$model->setState('method', $this->input->get('method', '', 'raw', 2));
		$model->setState('params', $this->input->get('params', array(), 'array', 2));

		@ob_end_clean();
		echo '###' . json_encode($model->dpeCustomAPICall()) . '###';

		if (!$this->noFlush)
		{
			flush();
		}

		$this->container->platform->closeApplication();
	}

	/**
	 * Runs a custom API call against the selected data processing engine and returns the raw result
	 */
	public function dpecustomapiraw()
	{
		/** @var \Akeeba\Backup\Admin\Model\Configuration $model */
		$model = $this->getModel();
		$model->setState('engine', $this->input->get('engine', '', 'raw', 2));
		$model->setState('method', $this->input->get('method', '', 'raw', 2));
		$model->setState('params', $this->input->get('params', array(), 'array', 2));

		@ob_end_clean();
		echo $model->dpeCustomAPICall();

		if (!$this->noFlush)
		{
			flush();
		}

		$this->container->platform->closeApplication();
	}
}
PK�!]%���""Controller/Browser.phpnu&1i�<?php
/**
 * @package   akeebabackup
 * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

namespace Akeeba\Backup\Admin\Controller;

// Protect from unauthorized access
defined('_JEXEC') || die();

use Akeeba\Backup\Admin\Controller\Mixin\CustomACL;
use FOF40\Controller\Controller;

class Browser extends Controller
{
	use CustomACL;

	protected function onBeforeDefault()
	{
		$folder        = $this->input->get('folder', '', 'string');
		$processfolder = $this->input->get('processfolder', 0, 'int');

		/** @var \Akeeba\Backup\Admin\Model\Browser $model */
		$model = $this->getModel();
		$model->setState('folder', $folder);
		$model->setState('processfolder', $processfolder);
		$model->makeListing();
	}
}
PK�!]���*��Controller/FileFilters.phpnu&1i�<?php
/**
 * @package   akeebabackup
 * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

namespace Akeeba\Backup\Admin\Controller;

// Protect from unauthorized access
use FOF40\Container\Container;

defined('_JEXEC') || die();

/**
 * File Filters controller
 */
class FileFilters extends DatabaseFilters
{
	/**
	 * Overridden FileFilters constructor. Sets the correct model and view names.
	 *
	 * @param   Container  $container  The component's container
	 * @param   array      $config     Optional configuration overrides
	 */
	public function __construct(Container $container, array $config)
	{
		if (!is_array($config))
		{
			$config = [];
		}

		$config = array_merge([
			'modelName'	=> 'FileFilters',
			'viewName'	=> 'FileFilters'
		], $config);

		parent::__construct($container, $config);
	}

}
PK�!]�e�	�	
akeeba.phpnu&1i�<?php
/**
 * @package   akeebabackup
 * @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

defined('_JEXEC') || die();

JDEBUG ? (defined('AKEEBADEBUG') || define('AKEEBADEBUG', 1)) : null;

$minPHPVersion         = '7.2.0';
$recommendedPHPVersion = '7.4';
$softwareName          = 'Akeeba Backup';

if (version_compare(PHP_VERSION, '7.2.0', 'lt'))
{
	echo 'Akeeba Backup requires PHP 7.2 or later.';

	return;
}

// HHVM made sense in 2013, now PHP 7 is a way better solution than a hybrid PHP interpreter
if (defined('HHVM_VERSION'))
{
	(include_once __DIR__ . '/tmpl/CommonTemplates/hhvm.php') || die('We have detected that you are running HHVM instead of PHP. This software WILL NOT WORK properly on HHVM. Please switch to PHP 7 instead.');

	return;
}

// So, FEF is not installed?
if (!@file_exists(JPATH_SITE . '/media/fef/fef.php'))
{
	(include_once __DIR__ . '/tmpl/CommonTemplates/fef.php') || die('You need to have the Akeeba Frontend Framework (FEF) package installed on your site to display this component. Please visit https://www.akeeba.com/download/official/fef.html to download it and install it on your site.');

	return;
}

/**
 * The following code is a neat trick to help us collect the maximum amount of relevant information when a user
 * encounters an unexpected exception or a PHP fatal error. In both cases we capture the generated Throwable and
 * render an error page, making sure that the HTTP response code is set to an appropriate value (4xx or 5xx).
 */
try
{
	if (!defined('FOF40_INCLUDED') && !@include_once(JPATH_LIBRARIES . '/fof40/include.php'))
	{
		(include_once __DIR__ . '/tmpl/CommonTemplates/fof.php') || die('You need to have the Akeeba Framework-on-Framework (FOF) 4 package installed on your site to use this component. Please visit https://www.akeeba.com/download/fof3.html to download it and install it on your site.');

		return;
	}

	$caCertPath = class_exists('\\Composer\\CaBundle\\CaBundle')
		? \Composer\CaBundle\CaBundle::getBundledCaBundlePath()
		: JPATH_LIBRARIES . '/src/Http/Transport/cacert.pem';

	define('AKEEBA_CACERT_PEM', $caCertPath);

	FOF40\Container\Container::getInstance('com_akeeba')->dispatcher->dispatch();
}
catch (Throwable $e)
{
	$title = 'Akeeba Backup';
	$isPro = defined(AKEEBA_PRO) ? AKEEBA_PRO : file_exists(__DIR__ . '/tmpl/CommonTemplates/RegExDatabaseFilters/Html.php');

	if (!(include_once __DIR__ . '/tmpl/CommonTemplates/errorhandler.php'))
	{
		throw $e;
	}
}
PK�!]��߯��
index.htmlnu&1i�<!--~
  ~ @package   akeebabackup
  ~ @copyright Copyright (c)2006-2022 Nicholas K. Dionysopoulos / Akeeba Ltd
  ~ @license   GNU General Public License version 3, or later
  -->

<html><head><title></title></head><body></body></html>PK�!]���q q BackupEngine/Platform.phpnu&1i�<?php
/**
 * Akeeba Engine
 *
 * @package   akeebaengine
 * @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License version 3, or later
 *
 * 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, version 3.
 *
 * 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
 * <https://www.gnu.org/licenses/>.
 */

namespace Akeeba\Engine;

defined('AKEEBAENGINE') || die();

use Akeeba\Engine\Platform\Base;
use Akeeba\Engine\Platform\PlatformInterface;
use DirectoryIterator;
use Exception;

/**
 * Platform abstraction. Manages the loading of platform connector objects and delegates calls to itself the them.
 *
 * @property string $tableNameProfiles The name of the table where backup profiles are stored
 * @property string $tableNameStats    The name of the table where backup records are stored
 *
 * @since    3.4
 */
class Platform
{
	/** @var Base|null The currently loaded platform connector object instance */
	protected static $platformConnectorInstance = null;

	/** @var array A list of additional directories where platform classes can be found */
	protected static $knownPlatformsDirectories = [];

	/** @var Platform The currently loaded object instance of this class. WARNING: This is NOT the platform connector! */
	protected static $instance = null;

	/**
	 * Public class constructor
	 *
	 * @param   string  $platform  Optional; platform name. Leave blank to auto-detect.
	 *
	 * @throws  Exception  When the platform cannot be loaded
	 */
	public function __construct($platform = null)
	{
		if (empty($platform) || is_null($platform))
		{
			$platform = static::detectPlatform();
		}

		if (empty($platform))
		{
			throw new Exception('Can not find a suitable Akeeba Engine platform for your site');
		}

		static::$platformConnectorInstance = static::loadPlatform($platform);

		if (!is_object(static::$platformConnectorInstance))
		{
			throw new Exception("Can not load Akeeba Engine platform $platform");
		}
	}

	/**
	 * Implements the Singleton pattern for this class
	 *
	 * @staticvar Platform $instance The static object instance
	 *
	 * @param   string  $platform  Optional; platform name. Autodetect if blank.
	 *
	 * @return  PlatformInterface
	 */
	public static function &getInstance($platform = null)
	{
		if (!is_object(static::$instance))
		{
			static::$instance = new Platform($platform);
		}

		return static::$instance;
	}

	/**
	 * Get a list of all directories where platform classes can be found
	 *
	 * @return  array
	 */
	public static function getPlatformDirectories()
	{
		$defaultPath = [];

		if (is_object(static::$platformConnectorInstance))
		{
			$defaultPath[] = __DIR__ . '/Platform/' . static::$platformConnectorInstance->platformName;
		}

		return array_merge(
			static::$knownPlatformsDirectories,
			$defaultPath
		);
	}

	/**
	 * Lists available platforms
	 *
	 * @staticvar   array   $platforms   Static cache of the available platforms
	 *
	 * @return  array  The list of available platforms
	 */
	static public function listPlatforms()
	{
		if (empty(static::$knownPlatformsDirectories))
		{
			$di = new DirectoryIterator(__DIR__ . '/Platform');

			/** @var DirectoryIterator $file */
			foreach ($di as $file)
			{
				if (!$file->isDir())
				{
					continue;
				}

				if ($file->isDot())
				{
					continue;
				}

				if ($file->getExtension() !== 'php')
				{
					continue;
				}

				$shortName = $file->getFilename();
				$bareName  = basename($shortName, '.php');

				/**
				 * We never have dots in our filenames but some hosts will rename files similar to  foo.1.php when their
				 * broken security scanners detect a false positive. This is our defence against that.
				 */
				if (strpos($bareName, '.') !== false)
				{
					continue;
				}

				static::$knownPlatformsDirectories[$shortName] = $file->getRealPath();
			}
		}

		return static::$knownPlatformsDirectories;
	}

	/**
	 * Add a platform to the list of known platforms
	 *
	 * @param   string  $slug               Short name of the platform
	 * @param   string  $platformDirectory  The path where you can find it
	 *
	 * @return  void
	 */
	public static function addPlatform($slug, $platformDirectory)
	{
		if (empty(static::$knownPlatformsDirectories))
		{
			static::listPlatforms();

			static::$knownPlatformsDirectories[$slug] = $platformDirectory;
		}
	}

	/**
	 * Auto-detect the suitable platform for this site
	 *
	 * @return  string
	 *
	 * @throws  Exception  When no platform is detected
	 */
	protected static function detectPlatform()
	{
		$platforms = static::listPlatforms();

		if (empty($platforms))
		{
			throw new Exception('No Akeeba Engine platform class found');
		}

		$bestPlatform = (object) [
			'name'     => null,
			'priority' => 0,
		];

		foreach ($platforms as $platform => $path)
		{
			$o = static::loadPlatform($platform, $path);

			if (is_null($o))
			{
				continue;
			}

			if ($o->isThisPlatform())
			{
				if ($o->priority > $bestPlatform->priority)
				{
					$bestPlatform->priority = $o->priority;
					$bestPlatform->name     = $platform;
				}
			}
		}

		return $bestPlatform->name;
	}

	/**
	 * Load a given platform and return the platform object
	 *
	 * @param   string  $platform  Platform name
	 * @param   string  $path      The path to laod the platform from (optional)
	 *
	 * @return  Base
	 */
	protected static function &loadPlatform($platform, $path = null)
	{
		if (empty($path))
		{
			if (isset(static::$knownPlatformsDirectories[$platform]))
			{
				$path = static::$knownPlatformsDirectories[$platform];
			}
		}

		if (empty($path))
		{
			$path = dirname(__FILE__) . '/' . $platform;
		}

		$classFile = $path . '/Platform.php';
		$className = '\\Akeeba\\Engine\\Platform\\' . ucfirst($platform);

		$null = null;

		if (!file_exists($classFile))
		{
			return $null;
		}

		require_once($classFile);

		if (!class_exists($className, false))
		{
			return $null;
		}

		$o = new $className;

		return $o;
	}

	/**
	 * Magic method to proxy all calls to the loaded platform object
	 *
	 * @param   string  $name       The name of the method to call
	 * @param   array   $arguments  The arguments to pass
	 *
	 * @return  mixed  The result of the method being called
	 *
	 * @throws  Exception  When the platform isn't loaded or an non-existent method is called
	 */
	public function __call($name, array $arguments)
	{
		if (is_null(static::$platformConnectorInstance))
		{
			throw new Exception('Akeeba Engine platform is not loaded');
		}

		if (method_exists(static::$platformConnectorInstance, $name))
		{
			return static::$platformConnectorInstance->$name(...$arguments);
		}
		else
		{
			throw new Exception('Method ' . $name . ' not found in Akeeba Platform');
		}
	}

	/**
	 * Magic getter for the properties of the loaded platform
	 *
	 * @param   string  $name  The name of the property to get
	 *
	 * @return  mixed  The value of the property
	 */
	public function __get($name)
	{
		if (!isset(static::$platformConnectorInstance->$name) || !property_exists(static::$platformConnectorInstance, $name))
		{
			static::$platformConnectorInstance->$name = null;
			user_error(__CLASS__ . ' does not support property ' . $name, E_NOTICE);
		}

		return static::$platformConnectorInstance->$name;
	}

	/**
	 * Magic setter for the properties of the loaded platform
	 *
	 * @param   string  $name   The name of the property to set
	 * @param   mixed   $value  The value of the property to set
	 */
	public function __set($name, $value)
	{
		if (isset(static::$platformConnectorInstance->$name) || property_exists(static::$platformConnectorInstance, $name))
		{
			static::$platformConnectorInstance->$name = $value;
		}
		else
		{
			static::$platformConnectorInstance->$name = null;
			user_error(__CLASS__ . ' does not support property ' . $name, E_NOTICE);
		}
	}
}
PK�!]��0\#\#BackupEngine/Driver/None.phpnu&1i�<?php
/**
 * Akeeba Engine
 *
 * @package   akeebaengine
 * @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License version 3, or later
 *
 * 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, version 3.
 *
 * 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
 * <https://www.gnu.org/licenses/>.
 */

namespace Akeeba\Engine\Driver;

defined('AKEEBAENGINE') || die();

use Akeeba\Engine\Driver\Query\Base as QueryBase;

/**
 * Dummy driver class for flat-file CMS
 */
#[\AllowDynamicProperties]
class None extends Base
{
	public static $dbtech = 'none';
	/**
	 * The name of the database driver.
	 *
	 * @var    string
	 * @since  1.0
	 */
	public $name = 'none';

	public function __construct(array $options)
	{
		$this->driverType = 'none';

		parent::__construct($options);
	}

	/**
	 * Test to see if this db driver is available
	 *
	 * @return  boolean  True on success, false otherwise.
	 *
	 * @since   1.0
	 */
	public static function isSupported()
	{
		return true;
	}

	public function open()
	{
		return $this;
	}

	/**
	 * Closes the database connection
	 */
	public function close()
	{
		return;
	}

	/**
	 * Determines if the connection to the server is active.
	 *
	 * @return  boolean  True if connected to the database engine.
	 */
	public function connected()
	{
		return true;
	}

	/**
	 * Drops a table from the database.
	 *
	 * @param   string   $table     The name of the database table to drop.
	 * @param   boolean  $ifExists  Optionally specify that the table must exist before it is dropped.
	 *
	 * @return  Base  Returns this object to support chaining.
	 */
	public function dropTable($table, $ifExists = true)
	{
		return $this;
	}

	/**
	 * Method to escape a string for usage in an SQL statement.
	 *
	 * @param   string   $text   The string to be escaped.
	 * @param   boolean  $extra  Optional parameter to provide extra escaping.
	 *
	 * @return  string   The escaped string.
	 */
	public function escape($text, $extra = false)
	{
		return '';
	}

	/**
	 * Method to fetch a row from the result set cursor as an associative array.
	 *
	 * @param   mixed  $cursor  The optional result set cursor from which to fetch the row.
	 *
	 * @return  mixed  Either the next row from the result set or false if there are no more rows.
	 */
	public function fetchAssoc($cursor = null)
	{
		return false;
	}

	/**
	 * Method to free up the memory used for the result set.
	 *
	 * @param   mixed  $cursor  The optional result set cursor from which to fetch the row.
	 *
	 * @return  void
	 */
	public function freeResult($cursor = null)
	{
		return;
	}

	/**
	 * Get the number of affected rows for the previous executed SQL statement.
	 *
	 * @return  integer  The number of affected rows.
	 */
	public function getAffectedRows()
	{
		return 0;
	}

	/**
	 * Method to get the database collation in use by sampling a text field of a table in the database.
	 *
	 * @return  mixed  The collation in use by the database or boolean false if not supported.
	 */
	public function getCollation()
	{
		return false;
	}

	/**
	 * Get the number of returned rows for the previous executed SQL statement.
	 *
	 * @param   resource  $cursor  An optional database cursor resource to extract the row count from.
	 *
	 * @return  integer   The number of returned rows.
	 */
	public function getNumRows($cursor = null)
	{
		return 0;
	}

	/**
	 * Get the current query object or a new QueryBase object.
	 *
	 * @param   boolean  $new  False to return the current query object, True to return a new QueryBase object.
	 *
	 * @return  QueryBase  The current query object or a new object extending the QueryBase class.
	 */
	public function getQuery($new = false)
	{
		return $this->sql;
	}

	public function createQuery()
	{
		return $this->sql;
	}

	/**
	 * Retrieves field information about the given tables.
	 *
	 * @param   string   $table     The name of the database table.
	 * @param   boolean  $typeOnly  True (default) to only return field types.
	 *
	 * @return  array  An array of fields by table.
	 */
	public function getTableColumns($table, $typeOnly = true)
	{
		return [];
	}

	/**
	 * Shows the table CREATE statement that creates the given tables.
	 *
	 * @param   mixed  $tables  A table name or a list of table names.
	 *
	 * @return  array  A list of the create SQL for the tables.
	 */
	public function getTableCreate($tables)
	{
		return [];
	}

	/**
	 * Retrieves field information about the given tables.
	 *
	 * @param   mixed  $tables  A table name or a list of table names.
	 *
	 * @return  array  An array of keys for the table(s).
	 */
	public function getTableKeys($tables)
	{
		return [];
	}

	/**
	 * Method to get an array of all tables in the database.
	 *
	 * @return  array  An array of all the tables in the database.
	 */
	public function getTableList()
	{
		return [];
	}

	/**
	 * Returns an array with the names of tables, views, procedures, functions and triggers
	 * in the database. The table names are the keys of the tables, whereas the value is
	 * the type of each element: table, view, merge, temp, procedure, function or trigger.
	 * Note that merge are MRG_MYISAM tables and temp is non-permanent data table, usually
	 * set up as temporary, black hole or federated tables. These two types should never,
	 * ever, have their data dumped in the SQL dump file.
	 *
	 * @param   bool  $abstract  Return or normal names? Defaults to true (names)
	 *
	 * @return  array
	 */
	public function getTables($abstract = true)
	{
		return [];
	}

	/**
	 * Get the version of the database connector
	 *
	 * @return  string  The database connector version.
	 */
	public function getVersion()
	{
		return '0.0.0';
	}

	/**
	 * Method to get the auto-incremented value from the last INSERT statement.
	 *
	 * @return  integer  The value of the auto-increment field from the last inserted row.
	 */
	public function insertid()
	{
		return 0;
	}

	/**
	 * Locks a table in the database.
	 *
	 * @param   string  $tableNameName  The name of the table to unlock.
	 *
	 * @return  Base  Returns this object to support chaining.
	 */
	public function lockTable($tableNameName)
	{
		return $this;
	}

	/**
	 * Execute the SQL statement.
	 *
	 * @return  mixed  A database cursor resource on success, boolean false on failure.
	 */
	public function query()
	{
		return false;
	}

	/**
	 * Renames a table in the database.
	 *
	 * @param   string  $oldTable  The name of the table to be renamed
	 * @param   string  $newTable  The new name for the table.
	 * @param   string  $backup    Table prefix
	 * @param   string  $prefix    For the table - used to rename constraints in non-mysql databases
	 *
	 * @return  Base  Returns this object to support chaining.
	 */
	public function renameTable($oldTable, $newTable, $backup = null, $prefix = null)
	{
		return $this;
	}

	/**
	 * Select a database for use.
	 *
	 * @param   string  $database  The name of the database to select for use.
	 *
	 * @return  boolean  True if the database was successfully selected.
	 */
	public function select($database)
	{
		return true;
	}

	/**
	 * Set the connection to use UTF-8 character encoding.
	 *
	 * @return  boolean  True on success.
	 */
	public function setUTF()
	{
		return true;
	}

	/**
	 * Method to commit a transaction.
	 *
	 * @return  void
	 */
	public function transactionCommit()
	{
		return;
	}

	/**
	 * Method to roll back a transaction.
	 *
	 * @return  void
	 */
	public function transactionRollback()
	{
		return;
	}

	/**
	 * Method to initialize a transaction.
	 *
	 * @return  void
	 */
	public function transactionStart()
	{
		return;
	}

	/**
	 * Unlocks tables in the database.
	 *
	 * @return  Base  Returns this object to support chaining.
	 */
	public function unlockTables()
	{
		return $this;
	}

	/**
	 * Method to fetch a row from the result set cursor as an array.
	 *
	 * @param   mixed  $cursor  The optional result set cursor from which to fetch the row.
	 *
	 * @return  mixed  Either the next row from the result set or false if there are no more rows.
	 */
	protected function fetchArray($cursor = null)
	{
		return false;
	}

	/**
	 * Method to fetch a row from the result set cursor as an object.
	 *
	 * @param   mixed   $cursor  The optional result set cursor from which to fetch the row.
	 * @param   string  $class   The class name to use for the returned row object.
	 *
	 * @return  mixed   Either the next row from the result set or false if there are no more rows.
	 */
	protected function fetchObject($cursor = null, $class = 'stdClass')
	{
		return false;
	}
}
PK�!]*�Z~N~NBackupEngine/Driver/Sqlite.phpnu&1i�<?php
/**
 * Akeeba Engine
 *
 * @package   akeebaengine
 * @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License version 3, or later
 *
 * 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, version 3.
 *
 * 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
 * <https://www.gnu.org/licenses/>.
 */

namespace Akeeba\Engine\Driver;

defined('AKEEBAENGINE') || die();

use Akeeba\Engine\Driver\Query\Base as QueryBase;
use Akeeba\Engine\Driver\Query\Limitable;
use Akeeba\Engine\Driver\Query\Preparable;
use PDO;
use PDOException;
use PDOStatement;
use RuntimeException;
use SQLite3;

/**
 * SQLite database driver supporting PDO based connections
 *
 * @see    http://php.net/manual/en/ref.pdo-sqlite.php
 * @since  1.0
 */
#[\AllowDynamicProperties]
class Sqlite extends Base
{

	public static $dbtech = 'sqlite';

	/**
	 * The name of the database driver.
	 *
	 * @var    string
	 * @since  1.0
	 */
	public $name = 'sqlite';

	/** @var PDOStatement The database connection cursor from the last query. */
	protected $cursor;

	/** @var array   Contains the current query execution status */
	protected $executed = false;

	/**
	 * The character(s) used to quote SQL statement names such as table names or field names,
	 * etc. The child classes should define this as necessary.  If a single character string the
	 * same character is used for both sides of the quoted name, else the first character will be
	 * used for the opening quote and the second for the closing quote.
	 *
	 * @var    string
	 * @since  1.0
	 */
	protected $nameQuote = '`';

	/** @var resource   The prepared statement. */
	protected $prepared;

	/** @var bool Are we in the process of reconnecting to the database server? */
	private $isReconnecting = false;

	public function __construct(array $options)
	{
		$this->driverType = 'sqlite';

		parent::__construct($options);

		if (!is_object($this->connection))
		{
			$this->open();
		}
	}

	/**
	 * Test to see if the PDO ODBC connector is available.
	 *
	 * @return  boolean  True on success, false otherwise.
	 *
	 * @since   1.0
	 */
	public static function isSupported()
	{
		return class_exists('\\PDO') && in_array('sqlite', PDO::getAvailableDrivers());
	}

	/**
	 * Destructor.
	 *
	 * @since   1.0
	 */
	public function __destruct()
	{
		$this->freeResult();
		unset($this->connection);
	}

	public function close()
	{
		$return = false;

		if (is_object($this->cursor))
		{
			$this->cursor->closeCursor();
		}

		$this->connection = null;

		return $return;
	}

	/**
	 * Determines if the connection to the server is active.
	 *
	 * @return  boolean  True if connected to the database engine.
	 */
	public function connected()
	{
		return !empty($this->connection);
	}

	/**
	 * Disconnects the database.
	 *
	 * @return  void
	 *
	 * @since   1.0
	 */
	public function disconnect()
	{
		$this->freeResult();
		unset($this->connection);
	}

	/**
	 * Drops a table from the database.
	 *
	 * @param   string   $table     The name of the database table to drop.
	 * @param   boolean  $ifExists  Optionally specify that the table must exist before it is dropped.
	 *
	 * @return  Sqlite  Returns this object to support chaining.
	 *
	 * @since   1.0
	 */
	public function dropTable($table, $ifExists = true)
	{
		$this->open();

		$query = $this->getQuery(true);

		$this->setQuery('DROP TABLE ' . ($ifExists ? 'IF EXISTS ' : '') . $query->quoteName($table));

		$this->execute();

		return $this;
	}

	/**
	 * Method to escape a string for usage in an SQLite statement.
	 *
	 * Note: Using query objects with bound variables is preferable to the below.
	 *
	 * @param   string   $text   The string to be escaped.
	 * @param   boolean  $extra  Unused optional parameter to provide extra escaping.
	 *
	 * @return  string  The escaped string.
	 *
	 * @since   1.0
	 */
	public function escape($text, $extra = false)
	{
		if (is_int($text) || is_float($text))
		{
			return $text;
		}

		if (is_null($text))
		{
			return 'NULL';
		}

		return SQLite3::escapeString($text);
	}

	public function fetchAssoc($cursor = null)
	{
		if (!empty($cursor) && $cursor instanceof PDOStatement)
		{
			return $cursor->fetch(PDO::FETCH_ASSOC);
		}

		if ($this->prepared instanceof PDOStatement)
		{
			return $this->prepared->fetch(PDO::FETCH_ASSOC);
		}
	}

	public function freeResult($cursor = null)
	{
		$this->executed = false;

		if ($cursor instanceof PDOStatement)
		{
			$cursor->closeCursor();
			$cursor = null;
		}

		if ($this->prepared instanceof PDOStatement)
		{
			$this->prepared->closeCursor();
			$this->prepared = null;
		}
	}

	public function getAffectedRows()
	{
		$this->open();

		if ($this->prepared instanceof PDOStatement)
		{
			return $this->prepared->rowCount();
		}
		else
		{
			return 0;
		}
	}

	/**
	 * Method to get the database collation in use by sampling a text field of a table in the database.
	 *
	 * @return  mixed  The collation in use by the database or boolean false if not supported.
	 *
	 * @since   1.0
	 */
	public function getCollation()
	{
		return $this->charset;
	}

	public function getNumRows($cursor = null)
	{
		$this->open();

		if ($cursor instanceof PDOStatement)
		{
			return $cursor->rowCount();
		}
		elseif ($this->prepared instanceof PDOStatement)
		{
			return $this->prepared->rowCount();
		}
		else
		{
			return 0;
		}
	}

	/**
	 * Retrieve a PDO database connection attribute
	 * http://www.php.net/manual/en/pdo.getattribute.php
	 *
	 * Usage: $db->getOption(PDO::ATTR_CASE);
	 *
	 * @param   mixed  $key  One of the PDO::ATTR_* Constants
	 *
	 * @return  mixed
	 *
	 * @since   1.0
	 */
	public function getOption($key)
	{
		$this->open();

		return $this->connection->getAttribute($key);
	}

	/**
	 * Get the current query object or a new Query object.
	 * We have to override the parent method since it will always return a PDO query, while we have a
	 * specialized class for SQLite
	 *
	 * @param   boolean  $new  False to return the current query object, True to return a new Query object.
	 *
	 * @return  QueryBase  The current query object or a new object extending the Query class.
	 *
	 * @throws  RuntimeException
	 */
	public function getQuery($new = false)
	{
		if ($new)
		{
			return new Query\Sqlite($this);
		}

		return $this->sql;
	}

	public function createQuery()
	{
		return new Query\Sqlite($this);
	}

	/**
	 * Retrieves field information about a given table.
	 *
	 * @param   string   $table     The name of the database table.
	 * @param   boolean  $typeOnly  True to only return field types.
	 *
	 * @return  array  An array of fields for the database table.
	 *
	 * @throws  RuntimeException
	 * @since   1.0
	 */
	public function getTableColumns($table, $typeOnly = true)
	{
		$this->open();

		$columns = [];
		$query   = $this->getQuery(true);

		$fieldCasing = $this->getOption(PDO::ATTR_CASE);

		$this->setOption(PDO::ATTR_CASE, PDO::CASE_UPPER);

		$table = strtoupper($table);

		$query->setQuery('pragma table_info(' . $table . ')');

		$this->setQuery($query);
		$fields = $this->loadObjectList();

		if ($typeOnly)
		{
			foreach ($fields as $field)
			{
				$columns[$field->NAME] = $field->TYPE;
			}
		}
		else
		{
			foreach ($fields as $field)
			{
				// Do some dirty translation to MySQL output.
				$columns[$field->NAME] = (object) [
					'Field'   => $field->NAME,
					'Type'    => $field->TYPE,
					'Null'    => ($field->NOTNULL == '1' ? 'NO' : 'YES'),
					'Default' => $field->DFLT_VALUE,
					'Key'     => ($field->PK == '1' ? 'PRI' : ''),
				];
			}
		}

		$this->setOption(PDO::ATTR_CASE, $fieldCasing);

		return $columns;
	}

	/**
	 * Shows the table CREATE statement that creates the given tables.
	 *
	 * Note: Doesn't appear to have support in SQLite
	 *
	 * @param   mixed  $tables  A table name or a list of table names.
	 *
	 * @return  array  A list of the create SQL for the tables.
	 *
	 * @throws  RuntimeException
	 * @since   1.0
	 */
	public function getTableCreate($tables)
	{
		$this->open();

		// Sanitize input to an array and iterate over the list.
		$tables = (array) $tables;

		return $tables;
	}

	/**
	 * Get the details list of keys for a table.
	 *
	 * @param   string  $tables  The name of the table.
	 *
	 * @return  array  An array of the column specification for the table.
	 *
	 * @throws  RuntimeException
	 * @since   1.0
	 */
	public function getTableKeys($tables)
	{
		$this->open();

		$keys  = [];
		$query = $this->getQuery(true);

		$fieldCasing = $this->getOption(PDO::ATTR_CASE);

		$this->setOption(PDO::ATTR_CASE, PDO::CASE_UPPER);

		$tables = strtoupper($tables);
		$query->setQuery('pragma table_info( ' . $tables . ')');

		// $query->bind(':tableName', $table);

		$this->setQuery($query);
		$rows = $this->loadObjectList();

		foreach ($rows as $column)
		{
			if ($column->PK == 1)
			{
				$keys[$column->NAME] = $column;
			}
		}

		$this->setOption(PDO::ATTR_CASE, $fieldCasing);

		return $keys;
	}

	/**
	 * Method to get an array of all tables in the database (schema).
	 *
	 * @return  array   An array of all the tables in the database.
	 *
	 * @throws  RuntimeException
	 * @since   1.0
	 */
	public function getTableList()
	{
		$this->open();

		/* @type  Query\Sqlite $query */
		$query = $this->getQuery(true);

		$type = 'table';

		$query->select('name');
		$query->from('sqlite_master');
		$query->where('type = :type');
		$query->bind(':type', $type);
		$query->order('name');

		$this->setQuery($query);

		$tables = $this->loadColumn();

		return $tables;
	}

	/**
	 * There's no point on return "a list of tables" inside a SQLite database: we are simple going to
	 * copy the whole database file in the new location
	 *
	 * @param   bool  $abstract
	 *
	 * @return array
	 */
	public function getTables($abstract = true)
	{
		return [];
	}

	/**
	 * Get the version of the database connector.
	 *
	 * @return  string  The database connector version.
	 *
	 * @since   1.0
	 */
	public function getVersion()
	{
		$this->open();

		$this->setQuery("SELECT sqlite_version()");

		return $this->loadResult();
	}

	public function insertid()
	{
		$this->open();

		// Error suppress this to prevent PDO warning us that the driver doesn't support this operation.
		return @$this->connection->lastInsertId();
	}

	/**
	 * Locks a table in the database.
	 *
	 * @param   string  $tableName  The name of the table to unlock.
	 *
	 * @return  Sqlite Returns this object to support chaining.
	 *
	 * @throws  RuntimeException
	 * @since   1.0
	 */
	public function lockTable($tableName)
	{
		return $this;
	}

	public function open()
	{
		if ($this->connected())
		{
			return;
		}
		else
		{
			$this->close();
		}

		if (isset($this->options['version']) && $this->options['version'] == 2)
		{
			$format = 'sqlite2:#DBNAME#';
		}
		else
		{
			$format = 'sqlite:#DBNAME#';
		}

		$replace = ['#DBNAME#'];
		$with    = [$this->options['database']];

		// Create the connection string:
		$connectionString = str_replace($replace, $with, $format);

		try
		{
			$this->connection = new PDO(
				$connectionString,
				$this->options['user'],
				$this->options['password']
			);
		}
		catch (PDOException $e)
		{
			throw new RuntimeException('Could not connect to PDO' . ': ' . $e->getMessage(), 2, $e);
		}
	}

	public function query()
	{
		$this->open();

		if (!is_object($this->connection))
		{
			throw new RuntimeException($this->errorMsg, $this->errorNum);
		}

		// Take a local copy so that we don't modify the original query and cause issues later
		$sql = $this->replacePrefix((string) $this->sql);

		if ($this->limit > 0 || $this->offset > 0)
		{
			$sql .= ' LIMIT ' . $this->limit;

			if ($this->offset > 0)
			{
				$sql .= ' OFFSET ' . $this->offset;
			}
		}

		// Increment the query counter.
		$this->count++;

		// If debugging is enabled then let's log the query.
		if ($this->debug)
		{
			// Add the query to the object queue.
			$this->log[] = $sql;
		}

		// Reset the error values.
		$this->errorNum = 0;
		$this->errorMsg = '';

		// Execute the query.
		$this->executed = false;

		if ($this->prepared instanceof PDOStatement)
		{
			// Bind the variables:
			if ($this->sql instanceof Preparable)
			{
				$bounded =& $this->sql->getBounded();

				foreach ($bounded as $key => $obj)
				{
					$this->prepared->bindParam($key, $obj->value, $obj->dataType, $obj->length, $obj->driverOptions);
				}
			}

			$this->executed = $this->prepared->execute();
		}

		// If an error occurred handle it.
		if (!$this->executed)
		{
			// Get the error number and message before we execute any more queries.
			$errorNum = (int) $this->connection->errorCode();
			$errorMsg = (string) 'SQL: ' . implode(", ", $this->connection->errorInfo());

			// Check if the server was disconnected.
			if (!$this->connected() && !$this->isReconnecting)
			{
				$this->isReconnecting = true;

				try
				{
					// Attempt to reconnect.
					$this->connection = null;
					$this->open();
				}
				catch (RuntimeException $e)
					// If connect fails, ignore that exception and throw the normal exception.
				{
					// Get the error number and message.
					$this->errorNum = (int) $this->connection->errorCode();
					$this->errorMsg = (string) 'SQL: ' . implode(", ", $this->connection->errorInfo());

					// Throw the normal query exception.
					throw new RuntimeException($this->errorMsg, $this->errorNum);
				}

				// Since we were able to reconnect, run the query again.
				$result               = $this->query();
				$this->isReconnecting = false;

				return $result;
			}
			else
				// The server was not disconnected.
			{
				// Get the error number and message from before we tried to reconnect.
				$this->errorNum = $errorNum;
				$this->errorMsg = $errorMsg;

				// Throw the normal query exception.
				throw new RuntimeException($this->errorMsg, $this->errorNum);
			}
		}

		return $this->prepared;
	}

	/**
	 * Renames a table in the database.
	 *
	 * @param   string  $oldTable  The name of the table to be renamed
	 * @param   string  $newTable  The new name for the table.
	 * @param   string  $backup    Not used by Sqlite.
	 * @param   string  $prefix    Not used by Sqlite.
	 *
	 * @return  Sqlite Returns this object to support chaining.
	 *
	 * @throws  RuntimeException
	 * @since   1.0
	 */
	public function renameTable($oldTable, $newTable, $backup = null, $prefix = null)
	{
		$this->setQuery('ALTER TABLE ' . $oldTable . ' RENAME TO ' . $newTable)->execute();

		return $this;
	}

	/**
	 * Select a database for use.
	 *
	 * @param   string  $database  The name of the database to select for use.
	 *
	 * @return  boolean  True if the database was successfully selected.
	 *
	 * @throws  RuntimeException
	 * @since   1.0
	 */
	public function select($database)
	{
		$this->open();

		$this->_database = $database;

		return true;
	}

	/**
	 * Sets an attribute on the PDO database handle.
	 * http://www.php.net/manual/en/pdo.setattribute.php
	 *
	 * Usage: $db->setOption(PDO::ATTR_CASE, PDO::CASE_UPPER);
	 *
	 * @param   integer  $key    One of the PDO::ATTR_* Constants
	 * @param   mixed    $value  One of the associated PDO Constants
	 *                           related to the particular attribute
	 *                           key.
	 *
	 * @return boolean
	 *
	 * @since  1.0
	 */
	public function setOption($key, $value)
	{
		$this->open();

		return $this->connection->setAttribute($key, $value);
	}

	/**
	 * Sets the SQL statement string for later execution.
	 *
	 * @param   mixed    $query          The SQL statement to set either as a JDatabaseQuery object or a string.
	 * @param   integer  $offset         The affected row offset to set.
	 * @param   integer  $limit          The maximum affected rows to set.
	 * @param   array    $driverOptions  The optional PDO driver options
	 *
	 * @return  Base  This object to support method chaining.
	 *
	 * @since   1.0
	 */
	public function setQuery($query, $offset = null, $limit = null, $driverOptions = [])
	{
		$this->open();

		$this->freeResult();

		if (is_string($query))
		{
			// Allows taking advantage of bound variables in a direct query:
			$query = $this->getQuery(true)->setQuery($query);
		}

		if ($query instanceof Limitable && !is_null($offset) && !is_null($limit))
		{
			$query->setLimit($limit, $offset);
		}

		$sql = $this->replacePrefix((string) $query);

		$this->prepared = $this->connection->prepare($sql, $driverOptions);

		// Store reference to the DatabaseQuery instance:
		parent::setQuery($query, $offset, $limit);

		return $this;
	}

	/**
	 * Set the connection to use UTF-8 character encoding.
	 *
	 * Returns false automatically for the Oracle driver since
	 * you can only set the character set when the connection
	 * is created.
	 *
	 * @return  boolean  True on success.
	 *
	 * @since   1.0
	 */
	public function setUTF()
	{
		$this->open();

		return false;
	}

	/**
	 * Method to commit a transaction.
	 *
	 * @param   boolean  $toSavepoint  If true, commit to the last savepoint.
	 *
	 * @return  void
	 *
	 * @throws  RuntimeException
	 * @since   1.0
	 */
	public function transactionCommit($toSavepoint = false)
	{
		$this->open();

		if (!$toSavepoint || $this->transactionDepth <= 1)
		{
			$this->open();

			if (!$toSavepoint || $this->transactionDepth == 1)
			{
				$this->connection->commit();
			}

			$this->transactionDepth--;
		}
		else
		{
			$this->transactionDepth--;
		}
	}

	/**
	 * Method to roll back a transaction.
	 *
	 * @param   boolean  $toSavepoint  If true, rollback to the last savepoint.
	 *
	 * @return  void
	 *
	 * @throws  RuntimeException
	 * @since   1.0
	 */
	public function transactionRollback($toSavepoint = false)
	{
		$this->connected();

		if (!$toSavepoint || $this->transactionDepth <= 1)
		{
			$this->open();

			if (!$toSavepoint || $this->transactionDepth == 1)
			{
				$this->connection->rollBack();
			}

			$this->transactionDepth--;
		}
		else
		{
			$savepoint = 'SP_' . ($this->transactionDepth - 1);
			$this->setQuery('ROLLBACK TO ' . $this->quoteName($savepoint));

			if ($this->execute())
			{
				$this->transactionDepth--;
			}
		}
	}

	/**
	 * Method to initialize a transaction.
	 *
	 * @param   boolean  $asSavepoint  If true and a transaction is already active, a savepoint will be created.
	 *
	 * @return  void
	 *
	 * @throws  RuntimeException
	 * @since   1.0
	 */
	public function transactionStart($asSavepoint = false)
	{
		$this->connected();

		if (!$asSavepoint || !$this->transactionDepth)
		{
			$this->open();

			if (!$asSavepoint || !$this->transactionDepth)
			{
				$this->connection->beginTransaction();
			}

			$this->transactionDepth++;
		}
		else
		{
			$savepoint = 'SP_' . $this->transactionDepth;
			$this->setQuery('SAVEPOINT ' . $this->quoteName($savepoint));

			if ($this->execute())
			{
				$this->transactionDepth++;
			}
		}
	}

	/**
	 * Unlocks tables in the database.
	 *
	 * @return  Sqlite Returns this object to support chaining.
	 *
	 * @throws  RuntimeException
	 * @since   1.0
	 */
	public function unlockTables()
	{
		return $this;
	}

	protected function fetchArray($cursor = null)
	{
		if (!empty($cursor) && $cursor instanceof PDOStatement)
		{
			return $cursor->fetch(PDO::FETCH_NUM);
		}

		if ($this->prepared instanceof PDOStatement)
		{
			return $this->prepared->fetch(PDO::FETCH_NUM);
		}
	}

	protected function fetchObject($cursor = null, $class = 'stdClass')
	{
		if (!empty($cursor) && $cursor instanceof PDOStatement)
		{
			return $cursor->fetchObject($class);
		}

		if ($this->prepared instanceof PDOStatement)
		{
			return $this->prepared->fetchObject($class);
		}
	}
}
PK�!]�4��%g%gBackupEngine/Driver/Mysql.phpnu&1i�<?php
/**
 * Akeeba Engine
 *
 * @package   akeebaengine
 * @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License version 3, or later
 *
 * 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, version 3.
 *
 * 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
 * <https://www.gnu.org/licenses/>.
 */

namespace Akeeba\Engine\Driver;

defined('AKEEBAENGINE') || die();

use Akeeba\Engine\Driver\Query\Mysql as QueryMysql;
use Akeeba\Engine\Factory;
use Exception;
use RuntimeException;

/**
 * MySQL classic driver for Akeeba Engine
 *
 * Based on Joomla! Platform 11.2
 */
#[\AllowDynamicProperties]
class Mysql extends Base
{

	/**
	 * The name of the database driver.
	 *
	 * @var    string
	 * @since  11.1
	 */
	public $name = 'mysql';

	/**
	 * Hostname
	 *
	 * @var   string
	 */
	protected $host;

	/**
	 * The character(s) used to quote SQL statement names such as table names or field names,
	 * etc. The child classes should define this as necessary.  If a single character string the
	 * same character is used for both sides of the quoted name, else the first character will be
	 * used for the opening quote and the second for the closing quote.
	 *
	 * @var    string
	 * @since  11.1
	 */
	protected $nameQuote = '`';

	/**
	 * The null or zero representation of a timestamp for the database driver.  This should be
	 * defined in child classes to hold the appropriate value for the engine.
	 *
	 * @var    string
	 * @since  11.1
	 */
	protected $nullDate = '0000-00-00 00:00:00';

	/**
	 * Password
	 *
	 * @var   string
	 */
	protected $password;

	/**
	 * Should I select a database?
	 *
	 * @var   bool
	 */
	protected $selectDatabase;

	/**
	 * Username
	 *
	 * @var   string
	 */
	protected $user;

	/** @var bool Are we in the process of reconnecting to the database server? */
	private $isReconnecting = false;

	/** @var array|null A cache of the tables contained in the currently connected database */
	public $tablesCache = null;

	/**
	 * Database object constructor
	 *
	 * @param   array  $options  List of options used to configure the connection
	 */
	public function __construct($options)
	{
		$this->driverType = 'mysql';

		// Init
		$this->nameQuote = '`';

		$host     = array_key_exists('host', $options) ? $options['host'] : 'localhost';
		$port     = array_key_exists('port', $options) ? $options['port'] : '';
		$user     = array_key_exists('user', $options) ? $options['user'] : '';
		$password = array_key_exists('password', $options) ? $options['password'] : '';
		$database = array_key_exists('database', $options) ? $options['database'] : '';
		$prefix   = array_key_exists('prefix', $options) ? $options['prefix'] : '';
		$select   = array_key_exists('select', $options) ? $options['select'] : true;

		if (!empty($port))
		{
			$host .= ':' . $port;
		}

		// finalize initialization
		parent::__construct($options);

		// Avoid overwriting connection info if they're already set
		if (is_null($this->host))
		{
			$this->host = $host;
		}

		if (is_null($this->user))
		{
			$this->user = $user;
		}

		if (is_null($this->password))
		{
			$this->password = $password;
		}

		if (is_null($this->_database))
		{
			$this->_database = $database;
		}

		if (is_null($this->selectDatabase))
		{
			$this->selectDatabase = $select;
		}

		// Open the connection
		if (!is_resource($this->connection) || is_null($this->connection))
		{
			$this->open();
		}
	}

	/**
	 * Test to see if the MySQL connector is available.
	 *
	 * @return  boolean  True on success, false otherwise.
	 *
	 * @since   12.1
	 */
	public static function isSupported()
	{
		return (function_exists('mysql_connect'));
	}

	/**
	 * Test to see if the MySQL connector is available.
	 *
	 * @return  boolean  True on success, false otherwise.
	 */
	public static function test()
	{
		return (function_exists('mysql_connect'));
	}

	public function close()
	{
		$return = false;
		if (is_resource($this->cursor))
		{
			mysql_free_result($this->cursor);
		}
		if (is_resource($this->connection) || (!is_null($this->connection) && !is_bool($this->connection)))
		{
			$return = mysql_close($this->connection);
		}
		$this->connection = null;

		return $return;
	}

	/**
	 * Determines if the connection to the server is active.
	 *
	 * @return  boolean  True if connected to the database engine.
	 */
	public function connected()
	{
		if (is_resource($this->connection))
		{
			return @mysql_ping($this->connection);
		}

		return false;
	}

	/**
	 * Drops a table from the database.
	 *
	 * @param   string   $table     The name of the database table to drop.
	 * @param   boolean  $ifExists  Optionally specify that the table must exist before it is dropped.
	 *
	 * @return  Mysql  Returns this object to support chaining.
	 */
	public function dropTable($table, $ifExists = true)
	{
		$query = $this->getQuery(true);

		$this->setQuery('DROP TABLE ' . ($ifExists ? 'IF EXISTS ' : '') . $query->quoteName($table));

		$this->query();

		return $this;
	}

	/**
	 * Method to escape a string for usage in an SQL statement.
	 *
	 * @param   string   $text   The string to be escaped.
	 * @param   boolean  $extra  Optional parameter to provide extra escaping.
	 *
	 * @return  string  The escaped string.
	 */
	public function escape($text, $extra = false)
	{
		if (is_null($text))
		{
			return 'NULL';
		}

		$result = @mysql_real_escape_string($text, $this->getConnection());

		if ($result === false)
		{
			// Attempt to reconnect.
			try
			{
				$this->connection = null;
				$this->open();

				$result = @mysql_real_escape_string($text, $this->getConnection());
			}
			catch (RuntimeException $e)
			{
				$result = $this->unsafe_escape($text);
			}
		}

		if ($extra)
		{
			$result = addcslashes($result, '%_');
		}

		return $result;
	}

	/**
	 * Method to fetch a row from the result set cursor as an associative array.
	 *
	 * @param   mixed  $cursor  The optional result set cursor from which to fetch the row.
	 *
	 * @return  mixed  Either the next row from the result set or false if there are no more rows.
	 */
	public function fetchAssoc($cursor = null)
	{
		return mysql_fetch_assoc($cursor ?: $this->cursor);
	}

	/**
	 * Method to free up the memory used for the result set.
	 *
	 * @param   mixed  $cursor  The optional result set cursor from which to fetch the row.
	 *
	 * @return  void
	 */
	public function freeResult($cursor = null)
	{
		mysql_free_result($cursor ?: $this->cursor);
	}

	/**
	 * Get the number of affected rows for the previous executed SQL statement.
	 *
	 * @return  integer  The number of affected rows.
	 */
	public function getAffectedRows()
	{
		return mysql_affected_rows($this->connection);
	}

	/**
	 * Method to get the database collation in use by sampling a text field of a table in the database.
	 *
	 * @return  mixed  The collation in use by the database (string) or boolean false if not supported.
	 */
	public function getCollation()
	{
		$this->setQuery('SHOW FULL COLUMNS FROM #__ak_stats');
		$array = $this->loadAssocList();

		return $array['2']['Collation'];
	}

	/**
	 * Get the number of returned rows for the previous executed SQL statement.
	 *
	 * @param   resource  $cursor  An optional database cursor resource to extract the row count from.
	 *
	 * @return  integer   The number of returned rows.
	 */
	public function getNumRows($cursor = null)
	{
		return mysql_num_rows($cursor ?: $this->cursor);
	}

	/**
	 * Get the current or query, or new JDatabaseQuery object.
	 *
	 * @param   boolean  $new  False to return the last query set, True to return a new JDatabaseQuery object.
	 *
	 * @return  mixed  The current value of the internal SQL variable or a new JDatabaseQuery object.
	 */
	public function getQuery($new = false)
	{
		if ($new)
		{
			return new QueryMysql($this);
		}
		else
		{
			return $this->sql;
		}
	}

	public function createQuery()
	{
		return new QueryMysql($this);
	}

	/**
	 * Retrieves field information about a given table.
	 *
	 * @param   string   $table     The name of the database table.
	 * @param   boolean  $typeOnly  True to only return field types.
	 *
	 * @return  array  An array of fields for the database table.
	 */
	public function getTableColumns($table, $typeOnly = true)
	{
		$result = [];

		// Set the query to get the table fields statement.
		$this->setQuery('SHOW FULL COLUMNS FROM ' . $this->quoteName($this->escape($table)));
		$fields = $this->loadObjectList();

		// If we only want the type as the value add just that to the list.
		if ($typeOnly)
		{
			foreach ($fields as $field)
			{
				$result[$field->Field] = preg_replace("/[(0-9)]/", '', $field->Type);
			}
		}
		// If we want the whole field data object add that to the list.
		else
		{
			foreach ($fields as $field)
			{
				$result[$field->Field] = $field;
			}
		}

		return $result;
	}

	/**
	 * Shows the table CREATE statement that creates the given tables.
	 *
	 * @param   mixed  $tables  A table name or a list of table names.
	 *
	 * @return  array  A list of the create SQL for the tables.
	 */
	public function getTableCreate($tables)
	{
		// Initialise variables.
		$result = [];

		// Sanitize input to an array and iterate over the list.
		$tables = (array) $tables;
		foreach ($tables as $table)
		{
			// Set the query to get the table CREATE statement.
			$this->setQuery('SHOW CREATE table ' . $this->quoteName($this->escape($table)));
			$row = $this->loadRow();

			// Populate the result array based on the create statements.
			$result[$table] = $row[1];
		}

		return $result;
	}

	/**
	 * Get the details list of keys for a table.
	 *
	 * @param   string  $tables  The name of the table.
	 *
	 * @return  array  An array of the column specification for the table.
	 */
	public function getTableKeys($tables)
	{
		// Get the details columns information.
		$this->setQuery('SHOW KEYS FROM ' . $this->quoteName($tables));
		$keys = $this->loadObjectList();

		return $keys;
	}

	/**
	 * Method to get an array of all tables in the database.
	 *
	 * @return  array  An array of all the tables in the database.
	 */
	public function getTableList()
	{
		// Set the query to get the tables statement.
		$this->setQuery('SHOW TABLES');
		$tables = $this->loadColumn();

		return $tables;
	}

	/**
	 * Returns an array with the names of tables, views, procedures, functions and triggers
	 * in the database. The table names are the keys of the tables, whereas the value is
	 * the type of each element: table, view, merge, temp, procedure, function or trigger.
	 * Note that merge are MRG_MYISAM tables and temp is non-permanent data table, usually
	 * set up as temporary, black hole or federated tables. These two types should never,
	 * ever, have their data dumped in the SQL dump file.
	 *
	 * @param   bool  $abstract  Return abstract or normal names? Defaults to true (abstract names)
	 *
	 * @return array
	 */
	public function getTables($abstract = true)
	{
		if (!empty($this->tablesCache[$this->_database]))
		{
			return $this->tablesCache[$this->_database];
		}

		$sql = "SHOW TABLES";
		$this->setQuery($sql);
		$all_tables = $this->loadColumn();

		if (!empty($all_tables))
		{
			// Start by adding tables and views to the list
			foreach ($all_tables as $table_name)
			{
				if ($abstract)
				{
					$table_name = $this->getAbstract($table_name);
				}
				$this->tablesCache[$this->_database][$table_name] = 'table';
			}

			// Loop all metadatas
			foreach ($all_tables as $table_metadata)
			{
				$table_name     = $table_metadata;
				$table_abstract = $this->getAbstract($table_metadata);
				$type           = 'table';

				if ($abstract)
				{
					$table_metadata = $table_abstract;
				}

				$create = $this->get_create($table_abstract, $table_name, $type);
				// Scan for the table engine.
				$engine = null; // So that we detect VIEWs correctly

				if ($type == 'table')
				{
					$engine      = 'MyISAM'; // So that even with MySQL 4 hosts we don't screw this up
					$engine_keys = ['ENGINE=', 'TYPE='];
					foreach ($engine_keys as $engine_key)
					{
						$start_pos = strrpos($create, $engine_key);
						if ($start_pos !== false)
						{
							// Advance the start position just after the position of the ENGINE keyword
							$start_pos += strlen($engine_key);
							// Try to locate the space after the engine type
							$end_pos = stripos($create, ' ', $start_pos);
							if ($end_pos === false)
							{
								// Uh... maybe it ends with ENGINE=EngineType;
								$end_pos = stripos($create, ';');
							}
							if ($end_pos !== '')
							{
								// Grab the string
								$engine = substr($create, $start_pos, $end_pos - $start_pos);
							}
						}
					}
					$engine = strtoupper($engine);
				}

				switch ($engine)
				{
					// Views -- FIX: They are detected based on their CREATE STATEMENT
					case null:
						$this->tablesCache[$this->_database][$table_metadata] = 'view';
						break;

					// Merge tables
					case 'MRG_MYISAM':
						$this->tablesCache[$this->_database][$table_metadata] = 'merge';
						break;

					// Tables whose data we do not back up (memory, federated and can-have-no-data tables)
					case 'MEMORY':
					case 'EXAMPLE':
					case 'BLACKHOLE':
					case 'FEDERATED':
						$this->tablesCache[$this->_database][$table_metadata] = 'temp';
						break;

					// Normal tables
					default:
						break;
				} // switch
			} // foreach
		} // if !empty

		// If we have MySQL > 5.0 add the list of stored procedures, stored functions
		// and triggers
		$registry        = Factory::getConfiguration();
		$enable_entities = $registry->get('engine.dump.native.advanced_entitites', true);
		if ($enable_entities)
		{
			// 1. Stored procedures
			$sql = "SHOW PROCEDURE STATUS WHERE " . $this->quoteName('Db') . "=" . $this->quote($this->_database);
			$this->setQuery($sql);

			try
			{
				$all_entries = $this->loadAssocList();
			}
			catch (Exception $e)
			{
				$all_entries = [];
			}

			if (is_array($all_entries) || $all_entries instanceof \Countable ? count($all_entries) : 0)
			{
				foreach ($all_entries as $entry)
				{
					$table_name = $entry['Name'];
					if ($abstract)
					{
						$table_name = $this->getAbstract($table_name);
					}
					$this->tablesCache[$this->_database][$table_name] = 'procedure';
				}
			}

			// 2. Stored functions
			$sql = "SHOW FUNCTION STATUS WHERE " . $this->quoteName('Db') . "=" . $this->quote($this->_database);
			$this->setQuery($sql);

			try
			{
				$all_entries = $this->loadColumn(1);
			}
			catch (Exception $e)
			{
				$all_entries = [];
			}

			// If we have filters, make sure the tables pass the filtering
			if (is_array($all_entries))
			{
				if (count($all_entries))
				{
					foreach ($all_entries as $table_name)
					{
						if ($abstract)
						{
							$table_name = $this->getAbstract($table_name);
						}
						$this->tablesCache[$this->_database][$table_name] = 'function';
					}
				}
			}

			// 3. Triggers
			$sql = "SHOW TRIGGERS";
			$this->setQuery($sql);

			try
			{
				$all_entries = $this->loadColumn();
			}
			catch (Exception $e)
			{
				$all_entries = [];
			}

			// If we have filters, make sure the tables pass the filtering
			if (is_array($all_entries))
			{
				if (count($all_entries))
				{
					foreach ($all_entries as $table_name)
					{
						if ($abstract)
						{
							$table_name = $this->getAbstract($table_name);
						}
						$this->tablesCache[$this->_database][$table_name] = 'trigger';
					}
				}
			}

		}

		return $this->tablesCache[$this->_database];
	}

	/**
	 * Get the version of the database connector.
	 *
	 * @return  string  The database connector version.
	 */
	public function getVersion()
	{
		return mysql_get_server_info($this->connection);
	}

	/**
	 * Determines if the database engine supports UTF-8 character encoding.
	 *
	 * @return  boolean  True if supported.
	 */
	public function hasUTF()
	{
		$verParts = explode('.', $this->getVersion());

		return ($verParts[0] == 5 || ($verParts[0] == 4 && $verParts[1] == 1 && (int) $verParts[2] >= 2));
	}

	/**
	 * Method to get the auto-incremented value from the last INSERT statement.
	 *
	 * @return  integer  The value of the auto-increment field from the last inserted row.
	 */
	public function insertid()
	{
		return mysql_insert_id($this->connection);
	}

	/**
	 * Locks a table in the database.
	 *
	 * @param   string  $tableName  The name of the table to unlock.
	 *
	 * @return  Mysql  Returns this object to support chaining.
	 */
	public function lockTable($tableName)
	{
		$this->setQuery('LOCK TABLES ' . $this->quoteName($tableName) . ' WRITE')->query();

		return $this;
	}

	public function open()
	{
		if ($this->connected())
		{
			return;
		}
		else
		{
			$this->close();
		}

		// perform a number of fatality checks, then return gracefully
		if (!function_exists('mysql_connect'))
		{
			$this->errorNum = 1;
			$this->errorMsg = 'The MySQL adapter "mysql" is not available.';

			return;
		}

		if (!($this->connection = @mysql_connect($this->host, $this->user, $this->password, true)))
		{
			$this->errorNum = 2;
			$this->errorMsg = 'Could not connect to MySQL';

			return;
		}

		// Set sql_mode to non_strict mode
		mysql_query("SET @@SESSION.sql_mode = '';", $this->connection);

		// If auto-select is enabled select the given database.
		if ($this->selectDatabase && !empty($this->_database))
		{
			if (!$this->select($this->_database))
			{
				$this->errorNum = 3;
				$this->errorMsg = "Cannot select database {$this->_database}";

				return;
			}
		}

		$this->setUTF();
	}

	/**
	 * Execute the SQL statement.
	 *
	 * @return  mixed  A database cursor resource on success, boolean false on failure.
	 */
	public function query()
	{
		if (!is_resource($this->connection))
		{
			throw new RuntimeException($this->errorMsg, $this->errorNum);
		}

		// Take a local copy so that we don't modify the original query and cause issues later
		$query = $this->replacePrefix((string) $this->sql);
		if ($this->limit > 0 || $this->offset > 0)
		{
			$query .= ' LIMIT ' . $this->offset . ', ' . $this->limit;
		}

		// Increment the query counter.
		$this->count++;

		// If debugging is enabled then let's log the query.
		if ($this->debug)
		{
			// Add the query to the object queue.
			$this->log[] = $query;
		}

		// Reset the error values.
		$this->errorNum = 0;
		$this->errorMsg = '';

		// Execute the query. Error suppression is used here to prevent warnings/notices that the connection has been lost.
		$this->cursor = @mysql_query($query, $this->connection);

		// If an error occurred handle it.
		if (!$this->cursor)
		{
			// Check if the server was disconnected.
			if (!$this->connected() && !$this->isReconnecting)
			{
				$this->isReconnecting = true;

				try
				{
					// Attempt to reconnect.
					$this->connection = null;
					$this->open();
				}
					// If connect fails, ignore that exception and throw the normal exception.
				catch (RuntimeException $e)
				{
					// Get the error number and message.
					$this->errorNum = (int) mysql_errno($this->connection);
					$this->errorMsg = (string) mysql_error($this->connection) . ' SQL=' . $query;

					// Throw the normal query exception.
					throw new RuntimeException($this->errorMsg, $this->errorNum);
				}

				// Since we were able to reconnect, run the query again.
				$result               = $this->query();
				$this->isReconnecting = false;

				return $result;
			}
			// The server was not disconnected.
			else
			{
				// Get the error number and message.
				$this->errorNum = (int) mysql_errno($this->connection);
				$this->errorMsg = (string) mysql_error($this->connection) . ' SQL=' . $query;

				// Throw the normal query exception.
				if ($this->errorNum != 0)
				{
					throw new RuntimeException($this->errorMsg, $this->errorNum);
				}
			}
		}

		return $this->cursor;
	}

	/**
	 * Renames a table in the database.
	 *
	 * @param   string  $oldTable  The name of the table to be renamed
	 * @param   string  $newTable  The new name for the table.
	 * @param   string  $backup    Not used by MySQL.
	 * @param   string  $prefix    Not used by MySQL.
	 *
	 * @return  Mysql  Returns this object to support chaining.
	 */
	public function renameTable($oldTable, $newTable, $backup = null, $prefix = null)
	{
		$this->setQuery('RENAME TABLE ' . $oldTable . ' TO ' . $newTable)->query();

		return $this;
	}

	/**
	 * Select a database for use.
	 *
	 * @param   string  $database  The name of the database to select for use.
	 *
	 * @return  boolean  True if the database was successfully selected.
	 */
	public function select($database)
	{
		if (!$database)
		{
			return false;
		}

		if (!mysql_select_db($database, $this->connection))
		{
			throw new RuntimeException('Could not connect to database');
		}

		return true;
	}

	/**
	 * Set the connection to use UTF-8 character encoding.
	 *
	 * @return  boolean  True on success.
	 */
	public function setUTF()
	{
		$result = false;

		if ($this->supportsUtf8mb4())
		{
			$result = @mysql_set_charset('utf8mb4', $this->connection);
		}

		if (!$result)
		{
			$result = @mysql_set_charset('utf8', $this->connection);
		}

		return $result;
	}

	/**
	 * Method to commit a transaction.
	 *
	 * @return  void
	 */
	public function transactionCommit()
	{
		$this->setQuery('COMMIT');
		$this->execute();
	}

	/**
	 * Method to roll back a transaction.
	 *
	 * @return  void
	 */
	public function transactionRollback()
	{
		$this->setQuery('ROLLBACK');
		$this->execute();
	}

	/**
	 * Method to initialize a transaction.
	 *
	 * @return  void
	 */
	public function transactionStart()
	{
		$this->setQuery('START TRANSACTION');
		$this->execute();
	}

	/**
	 * Unlocks tables in the database.
	 *
	 * @return  Mysql  Returns this object to support chaining.
	 *
	 * @throws  Exception
	 * @since   11.4
	 */
	public function unlockTables()
	{
		$this->setQuery('UNLOCK TABLES')->execute();

		return $this;
	}

	/**
	 * Method to fetch a row from the result set cursor as an array.
	 *
	 * @param   mixed  $cursor  The optional result set cursor from which to fetch the row.
	 *
	 * @return  mixed  Either the next row from the result set or false if there are no more rows.
	 */
	protected function fetchArray($cursor = null)
	{
		return mysql_fetch_row($cursor ?: $this->cursor);
	}

	/**
	 * Method to fetch a row from the result set cursor as an object.
	 *
	 * @param   mixed   $cursor  The optional result set cursor from which to fetch the row.
	 * @param   string  $class   The class name to use for the returned row object.
	 *
	 * @return  mixed   Either the next row from the result set or false if there are no more rows.
	 */
	protected function fetchObject($cursor = null, $class = 'stdClass')
	{
		return mysql_fetch_object($cursor ?: $this->cursor, $class);
	}

	/**
	 * Gets the CREATE TABLE command for a given table/view
	 *
	 * @param   string  $table_abstract  The abstracted name of the entity
	 * @param   string  $table_name      The name of the table
	 * @param   string  $type            The type of the entity to scan. If it's found to differ, the correct type is
	 *                                   returned.
	 *
	 * @return string The CREATE command, w/out newlines
	 */
	protected function get_create($table_abstract, $table_name, &$type)
	{
		$sql = "SHOW CREATE TABLE `$table_abstract`";
		$this->setQuery($sql);
		$temp      = $this->loadRowList();
		$table_sql = $temp[0][1];
		unset($temp);

		// Smart table type detection
		if (in_array($type, ['table', 'merge', 'view']))
		{
			// Check for CREATE VIEW
			$pattern = '/^CREATE(.*) VIEW (.*)/i';
			$result  = preg_match($pattern, $table_sql);
			if ($result === 1)
			{
				// This is a view.
				$type = 'view';
			}
			else
			{
				// This is a table.
				$type = 'table';
			}

			// Is it a VIEW but we don't have SHOW VIEW privileges?
			if (empty($table_sql))
			{
				$type = 'view';
			}
		}

		$table_sql = str_replace($table_name, $table_abstract, $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);

		// Post-process CREATE VIEW
		if ($type == 'view')
		{
			$pos_view = strpos($table_sql, ' VIEW ');

			if ($pos_view > 7)
			{
				// Only post process if there are view properties between the CREATE and VIEW keywords
				$propstring = substr($table_sql, 7, $pos_view - 7); // Properties string
				// Fetch the ALGORITHM={UNDEFINED | MERGE | TEMPTABLE} keyword
				$algostring = '';
				$algo_start = strpos($propstring, 'ALGORITHM=');
				if ($algo_start !== false)
				{
					$algo_end   = strpos($propstring, ' ', $algo_start);
					$algostring = substr($propstring, $algo_start, $algo_end - $algo_start + 1);
				}
				// Create our modified create statement
				$table_sql = 'CREATE OR REPLACE ' . $algostring . substr($table_sql, $pos_view);
			}
		}

		return $table_sql;
	}

	/**
	 * Does this database server support UTF-8 four byte (utf8mb4) collation?
	 *
	 * libmysql supports utf8mb4 since 5.5.3 (same version as the MySQL server). mysqlnd supports utf8mb4 since 5.0.9.
	 *
	 * This method's code is based on WordPress' wpdb::has_cap() method
	 *
	 * @return  bool
	 */
	protected function supportsUtf8mb4()
	{
		$client_version = mysql_get_client_info();

		if (strpos($client_version, 'mysqlnd') !== false)
		{
			$client_version = preg_replace('/^\D+([\d.]+).*/', '$1', $client_version);

			return version_compare($client_version, '5.0.9', '>=');
		}
		else
		{
			return version_compare($client_version, '5.5.3', '>=');
		}
	}

	protected function unsafe_escape($string)
	{
		if (function_exists('mb_ereg_replace'))
		{
			return mb_ereg_replace('[\x00\x0A\x0D\x1A\x22\x27\x5C]', '\\\0', $string);
		}

		return preg_replace('~[\x00\x0A\x0D\x1A\x22\x27\x5C]~u', '\\\$0', $string);
	}
}
PK�!]��==BackupEngine/Driver/Mysqli.phpnu&1i�<?php
/**
 * Akeeba Engine
 *
 * @package   akeebaengine
 * @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License version 3, or later
 *
 * 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, version 3.
 *
 * 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
 * <https://www.gnu.org/licenses/>.
 */

namespace Akeeba\Engine\Driver;

defined('AKEEBAENGINE') || die();

use Akeeba\Engine\Driver\Query\Mysqli as QueryMysqli;
use Akeeba\Engine\FixMySQLHostname;
use mysqli_result;
use RuntimeException;

/**
 * MySQL Improved (mysqli) database driver for Akeeba Engine
 *
 * Based on Joomla! Platform 11.2
 */
#[\AllowDynamicProperties]
class Mysqli extends Mysql
{
	use FixMySQLHostname;

	/**
	 * The name of the database driver.
	 *
	 * @var    string
	 * @since  11.1
	 */
	public $name = 'mysqli';

	/** @var \mysqli|null The db connection resource */
	protected $connection = '';

	/** @var mysqli_result|null The database connection cursor from the last query. */
	protected $cursor;

	protected $port;

	protected $socket;

	protected $ssl = [];

	/** @var bool Are we in the process of reconnecting to the database server? */
	private $isReconnecting = false;

	/**
	 * Database object constructor
	 *
	 * @param   array  $options  List of options used to configure the connection
	 */
	public function __construct($options)
	{
		$this->driverType = 'mysql';

		// Init
		$this->nameQuote = '`';

		$options['ssl'] = $options['ssl'] ?? [];
		$options['ssl'] = is_array($options['ssl']) ? $options['ssl'] : [];

		$options['ssl']['enable']             = ($options['ssl']['enable'] ?? $options['dbencryption'] ?? false) ?: false;
		$options['ssl']['cipher']             = ($options['ssl']['cipher'] ?? $options['dbsslcipher'] ?? null) ?: null;
		$options['ssl']['ca']                 = ($options['ssl']['ca'] ?? $options['dbsslca'] ?? null) ?: null;
		$options['ssl']['capath']             = ($options['ssl']['capath'] ?? $options['dbsslcapath'] ?? null) ?: null;
		$options['ssl']['key']                = ($options['ssl']['key'] ?? $options['dbsslkey'] ?? null) ?: null;
		$options['ssl']['cert']               = ($options['ssl']['cert'] ?? $options['dbsslcert'] ?? null) ?: null;
		$options['ssl']['verify_server_cert'] = ($options['ssl']['verify_server_cert'] ?? $options['dbsslverifyservercert'] ?? false) ?: false;

		// Figure out if a port is included in the host name
		$this->fixHostnamePortSocket($options['host'], $options['port'], $options['socket']);

		// Set the information
		$this->host           = $options['host'] ?? 'localhost';
		$this->user           = $options['user'] ?? '';
		$this->password       = $options['password'] ?? '';
		$this->port           = $options['port'] ?? '';
		$this->socket         = $options['socket'] ?? '';
		$this->_database      = $options['database'] ?? '';
		$this->selectDatabase = $options['select'] ?? true;
		$this->ssl            = $options['ssl'] ?? [];

		// Finalize initialization. Also opens the connection.
		parent::__construct($options);
	}

	/**
	 * Test to see if the MySQL connector is available.
	 *
	 * @return  boolean  True on success, false otherwise.
	 */
	public static function isSupported()
	{
		return (function_exists('mysqli_connect'));
	}

	public function close()
	{
		$return = false;

		if (is_object($this->cursor) && ($this->cursor instanceof mysqli_result))
		{
			try
			{
				@$this->cursor->free();
			}
			catch (\Throwable $e)
			{
			}

			$this->cursor = null;
		}

		if (is_object($this->connection) && ($this->connection instanceof \mysqli))
		{
			try
			{
				$return = @$this->connection->close();
			}
			catch (\Throwable $e)
			{
				$return = false;
			}
		}

		$this->connection = null;

		return $return;
	}

	/**
	 * Determines if the connection to the server is active.
	 *
	 * @return  boolean  True if connected to the database engine.
	 */
	public function connected()
	{
		if (!is_object($this->connection))
		{
			return false;
		}

		// mysqli_ping is deprecated since PHP 8.4.
		if (version_compare(PHP_VERSION, '8.4.0', '<'))
		{
			try
			{
				return @mysqli_ping($this->connection);
			}
			catch (\Throwable $e)
			{
				return false;
			}
		}

		try
		{
			$cursor = @mysqli_query($this->connection, 'SELECT 1');

			if (!$cursor)
			{
				return false;
			}

			mysqli_free_result($cursor);

			return true;
		}
		catch (\Throwable $e)
		{
			return false;
		}
	}

	/**
	 * Method to escape a string for usage in an SQL statement.
	 *
	 * @param   string   $text   The string to be escaped.
	 * @param   boolean  $extra  Optional parameter to provide extra escaping.
	 *
	 * @return  string  The escaped string.
	 */
	public function escape($text, $extra = false)
	{
		if (is_null($text))
		{
			return 'NULL';
		}

		$result = @mysqli_real_escape_string($this->getConnection(), $text);

		if ($result === false)
		{
			// Attempt to reconnect.
			try
			{
				$this->connection = null;
				$this->open();

				$result = @mysqli_real_escape_string($this->getConnection(), $text);;
			}
			catch (RuntimeException $e)
			{
				$result = $this->unsafe_escape($text);
			}
		}

		if ($extra)
		{
			$result = addcslashes($result, '%_');
		}

		return $result;
	}

	/**
	 * Method to fetch a row from the result set cursor as an associative array.
	 *
	 * @param   mixed  $cursor  The optional result set cursor from which to fetch the row.
	 *
	 * @return  mixed  Either the next row from the result set or false if there are no more rows.
	 */
	public function fetchAssoc($cursor = null)
	{
		return mysqli_fetch_assoc($cursor ?: $this->cursor);
	}

	/**
	 * Method to free up the memory used for the result set.
	 *
	 * @param   mixed  $cursor  The optional result set cursor from which to fetch the row.
	 *
	 * @return  void
	 */
	public function freeResult($cursor = null)
	{
		mysqli_free_result($cursor ?: $this->cursor);
	}

	/**
	 * Get the number of affected rows for the previous executed SQL statement.
	 *
	 * @return  integer  The number of affected rows.
	 */
	public function getAffectedRows()
	{
		return mysqli_affected_rows($this->connection);
	}

	/**
	 * Get the number of returned rows for the previous executed SQL statement.
	 *
	 * @param   mysqli_result  $cursor  An optional database cursor resource to extract the row count from.
	 *
	 * @return  integer   The number of returned rows.
	 */
	public function getNumRows($cursor = null)
	{
		return mysqli_num_rows($cursor ?: $this->cursor);
	}

	/**
	 * Get the current or query, or new JDatabaseQuery object.
	 *
	 * @param   boolean  $new  False to return the last query set, True to return a new JDatabaseQuery object.
	 *
	 * @return  mixed  The current value of the internal SQL variable or a new JDatabaseQuery object.
	 */
	public function getQuery($new = false)
	{
		if ($new)
		{
			return new QueryMysqli($this);
		}
		else
		{
			return $this->sql;
		}
	}

	public function createQuery()
	{
		return new QueryMysqli($this);
	}

	/**
	 * Get the version of the database connector.
	 *
	 * @return  string  The database connector version.
	 */
	public function getVersion()
	{
		return mysqli_get_server_info($this->connection);
	}

	/**
	 * Determines if the database engine supports UTF-8 character encoding.
	 *
	 * @return  boolean  True if supported.
	 */
	public function hasUTF()
	{
		$mariadb = stripos($this->connection->server_info, 'mariadb') !== false;
		$client_version = mysqli_get_client_info();
		$server_version = $this->getVersion();

		if (version_compare($server_version, '5.5.3', '<'))
		{
			return false;
		}

		if ($mariadb && version_compare($server_version, '10.0.0', '<'))
		{
			return false;
		}

		if (strpos($client_version, 'mysqlnd') !== false)
		{
			$client_version = preg_replace('/^\D+([\d.]+).*/', '$1', $client_version);

			return version_compare($client_version, '5.0.9', '>=');
		}

		return version_compare($client_version, '5.5.3', '>=');
	}

	/**
	 * Method to get the auto-incremented value from the last INSERT statement.
	 *
	 * @return  integer  The value of the auto-increment field from the last inserted row.
	 */
	public function insertid()
	{
		return mysqli_insert_id($this->connection);
	}

	public function open()
	{
		if ($this->connected())
		{
			return;
		}
		else
		{
			$this->close();
		}

		// perform a number of fatality checks, then return gracefully
		if (!function_exists('mysqli_connect'))
		{
			$this->errorNum = 1;
			$this->errorMsg = 'The MySQL adapter "mysqli" is not available.';

			return;
		}

		// Let's prepare a connection
		$this->connection = mysqli_init();

		$connectionFlags = 0;

		// For SSL/TLS connection encryption.
		if ($this->ssl !== [] && $this->ssl['enable'] === true)
		{
			$connectionFlags = $connectionFlags | MYSQLI_CLIENT_SSL;

			// Verify server certificate is only available in PHP 5.6.16+. See https://www.php.net/ChangeLog-5.php#5.6.16
			if (isset($this->ssl['verify_server_cert']))
			{
				// New constants in PHP 5.6.16+. See https://www.php.net/ChangeLog-5.php#5.6.16
				if ($this->ssl['verify_server_cert'] === true && defined('MYSQLI_CLIENT_SSL_VERIFY_SERVER_CERT'))
				{
					$connectionFlags = $connectionFlags | MYSQLI_CLIENT_SSL_VERIFY_SERVER_CERT;
				}
				elseif ($this->ssl['verify_server_cert'] === false && defined('MYSQLI_CLIENT_SSL_DONT_VERIFY_SERVER_CERT'))
				{
					$connectionFlags = $connectionFlags | MYSQLI_CLIENT_SSL_DONT_VERIFY_SERVER_CERT;
				}
				elseif (defined('MYSQLI_OPT_SSL_VERIFY_SERVER_CERT'))
				{
					$this->connection->options(MYSQLI_OPT_SSL_VERIFY_SERVER_CERT, $this->ssl['verify_server_cert']);
				}
			}

			// Add SSL/TLS options only if changed.
			$this->connection->ssl_set(
				($this->ssl['key'] ?? null) ?: null,
				($this->ssl['cert'] ?? null) ?: null,
				($this->ssl['ca'] ?? null) ?: null,
				($this->ssl['capath'] ?? null) ?: null,
				($this->ssl['cipher'] ?? null) ?: null
			);
		}

		// Attempt to connect to the server, use error suppression to silence warnings and allow us to throw an Exception separately.
		try
		{
			$connected = @$this->connection->real_connect(
				$this->host,
				$this->user,
				$this->password ?: null,
				null,
				$this->port ?: 3306,
				$this->socket ?: null,
				$connectionFlags
			);
		}
		catch (\Throwable $e)
		{
			$connected = false;
		}

		// connect to the server
		if (!$connected)
		{
			$this->errorNum = 2;
			$this->errorMsg = 'Could not connect to MySQL';

			return;
		}

		// Set sql_mode to non_strict mode
		mysqli_query($this->connection, "SET @@SESSION.sql_mode = '';");

		if ($this->selectDatabase && !empty($this->_database))
		{
			if (!$this->select($this->_database))
			{
				$this->errorNum = 3;
				$this->errorMsg = "Cannot select database {$this->_database}";

				return;
			}
		}

		$this->setUTF();
	}

	/**
	 * Execute the SQL statement.
	 *
	 * @return  mixed  A database cursor resource on success, boolean false on failure.
	 */
	public function query()
	{
		$this->open();

		if (!is_object($this->connection))
		{
			throw new RuntimeException($this->errorMsg, $this->errorNum);
		}

		// Take a local copy so that we don't modify the original query and cause issues later
		$query = $this->replacePrefix((string) $this->sql);
		if ($this->limit > 0 || $this->offset > 0)
		{
			$query .= ' LIMIT ' . $this->offset . ', ' . $this->limit;
		}

		// Increment the query counter.
		$this->count++;

		// If debugging is enabled then let's log the query.
		if ($this->debug)
		{
			// Add the query to the object queue.
			$this->log[] = $query;
		}

		// Reset the error values.
		$this->errorNum = 0;
		$this->errorMsg = '';

		// Execute the query. Error suppression is used here to prevent warnings/notices that the connection has been lost.
		$this->cursor = @mysqli_query($this->connection, $query);

		// If an error occurred handle it.
		if (!$this->cursor)
		{
			$this->errorNum = 0;
			$this->errorMsg = '';

			if ($this->connection)
			{
				$this->errorNum = (int) @mysqli_errno($this->connection);
				$this->errorMsg = (string) @mysqli_error($this->connection) . ' SQL=' . $query;
			}

			// Check if the server was disconnected.
			if (!$this->connected() && !$this->isReconnecting)
			{
				$this->isReconnecting = true;

				try
				{
					// Attempt to reconnect.
					$this->connection = null;
					$this->open();
				}
					// If connect fails, ignore that exception and throw the normal exception.
				catch (RuntimeException $e)
				{
					throw new RuntimeException($this->errorMsg, $this->errorNum);
				}

				// Since we were able to reconnect, run the query again.
				$result               = $this->query();
				$this->isReconnecting = false;

				return $result;
			}
			// The server was not disconnected.
			elseif ($this->errorNum != 0)
			{
				throw new RuntimeException($this->errorMsg, $this->errorNum);
			}
		}

		return $this->cursor;
	}

	/**
	 * Select a database for use.
	 *
	 * @param   string  $database  The name of the database to select for use.
	 *
	 * @return  boolean  True if the database was successfully selected.
	 */
	public function select($database)
	{
		if (!$database)
		{
			return false;
		}

		if (!mysqli_select_db($this->connection, $database))
		{
			return false;
		}

		return true;
	}

	/**
	 * Set the connection to use UTF-8 character encoding.
	 *
	 * @return  boolean  True on success.
	 */
	public function setUTF()
	{
		$result = false;

		if ($this->supportsUtf8mb4())
		{
			$result = @mysqli_set_charset($this->connection, 'utf8mb4');
		}

		if (!$result)
		{
			$result = @mysqli_set_charset($this->connection, 'utf8');
		}

		return $result;

	}

	/**
	 * Does this database server support UTF-8 four byte (utf8mb4) collation?
	 *
	 * libmysql supports utf8mb4 since 5.5.3 (same version as the MySQL server). mysqlnd supports utf8mb4 since 5.0.9.
	 *
	 * This method's code is based on WordPress' wpdb::has_cap() method
	 *
	 * @return  bool
	 */
	public function supportsUtf8mb4()
	{
		$client_version = mysqli_get_client_info();

		if (strpos($client_version, 'mysqlnd') !== false)
		{
			$client_version = preg_replace('/^\D+([\d.]+).*/', '$1', $client_version);

			return version_compare($client_version, '5.0.9', '>=');
		}
		else
		{
			return version_compare($client_version, '5.5.3', '>=');
		}
	}

	/**
	 * Method to fetch a row from the result set cursor as an array.
	 *
	 * @param   mixed  $cursor  The optional result set cursor from which to fetch the row.
	 *
	 * @return  mixed  Either the next row from the result set or false if there are no more rows.
	 */
	protected function fetchArray($cursor = null)
	{
		return mysqli_fetch_row($cursor ?: $this->cursor);
	}

	/**
	 * Method to fetch a row from the result set cursor as an object.
	 *
	 * @param   mixed   $cursor  The optional result set cursor from which to fetch the row.
	 * @param   string  $class   The class name to use for the returned row object.
	 *
	 * @return  mixed   Either the next row from the result set or false if there are no more rows.
	 */
	protected function fetchObject($cursor = null, $class = 'stdClass')
	{
		return mysqli_fetch_object($cursor ?: $this->cursor, $class);
	}
}
PK�!]&)����&BackupEngine/Driver/QueryException.phpnu&1i�<?php
/**
 * Akeeba Engine
 *
 * @package   akeebaengine
 * @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License version 3, or later
 *
 * 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, version 3.
 *
 * 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
 * <https://www.gnu.org/licenses/>.
 */

namespace Akeeba\Engine\Driver;

defined('AKEEBAENGINE') || die();

use Exception;

class QueryException extends Exception
{
}
PK�!]�������"BackupEngine/Driver/Query/Base.phpnu&1i�<?php
/**
 * Akeeba Engine
 *
 * @package   akeebaengine
 * @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License version 3, or later
 *
 * 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, version 3.
 *
 * 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
 * <https://www.gnu.org/licenses/>.
 */

namespace Akeeba\Engine\Driver\Query;

defined('AKEEBAENGINE') || die();

use Akeeba\Engine\Driver\Base as DriverBase;
use Akeeba\Engine\Driver\Query\Element as QueryElement;
use Akeeba\Engine\Driver\Query\Limitable as QueryLimitable;
use Akeeba\Engine\Driver\QueryException;

/**
 * Query Building Class.
 *
 * Based on Joomla! Platform 11.3
 */
abstract class Base
{
	/**
	 * @var    DriverBase  The database connection resource.
	 */
	protected $db = null;

	/**
	 * @var    string  The SQL query (if a direct query string was provided).
	 */
	protected $sql = null;

	/**
	 * @var    string  The query type.
	 */
	protected $type = '';

	/**
	 * @var    QueryElement  The query element for a generic query (type = null).
	 */
	protected $element = null;

	/**
	 * @var    QueryElement  The select element.
	 */
	protected $select = null;

	/**
	 * @var    QueryElement  The delete element.
	 */
	protected $delete = null;

	/**
	 * @var    QueryElement  The update element.
	 */
	protected $update = null;

	/**
	 * @var    QueryElement  The insert element.
	 */
	protected $insert = null;

	/**
	 * @var    QueryElement  The from element.
	 */
	protected $from = null;

	/**
	 * @var    QueryElement  The join element.
	 */
	protected $join = null;

	/**
	 * @var    QueryElement  The set element.
	 */
	protected $set = null;

	/**
	 * @var    QueryElement  The where element.
	 */
	protected $where = null;

	/**
	 * @var    QueryElement  The group by element.
	 */
	protected $group = null;

	/**
	 * @var    QueryElement  The having element.
	 */
	protected $having = null;

	/**
	 * @var    QueryElement  The column list for an INSERT statement.
	 */
	protected $columns = null;

	/**
	 * @var    QueryElement  The values list for an INSERT statement.
	 */
	protected $values = null;

	/**
	 * @var    QueryElement  The order element.
	 */
	protected $order = null;

	/**
	 * @var   object  The auto increment insert field element.
	 */
	protected $autoIncrementField = null;

	/**
	 * @var    QueryElement  The call element.
	 */
	protected $call = null;

	/**
	 * @var    QueryElement  The exec element.
	 */
	protected $exec = null;

	/**
	 * @var    QueryElement  The union element.
	 */
	protected $union = null;

	/**
	 * @var    QueryElement  The unionAll element.
	 */
	protected $unionAll = null;

	/**
	 * Class constructor.
	 *
	 * @param   DriverBase  $db  The database connector resource.
	 */
	public function __construct(DriverBase $db = null)
	{
		$this->db = $db;
	}

	/**
	 * Magic method to provide method alias support for quote() and quoteName().
	 *
	 * @param   string  $method  The called method.
	 * @param   array   $args    The array of arguments passed to the method.
	 *
	 * @return  string  The aliased method's return value or null.
	 */
	public function __call($method, $args)
	{
		if (empty($args))
		{
			return null;
		}

		switch ($method)
		{
			case 'q':
				return $this->quote($args[0], $args[1] ?? true);
				break;

			case 'qn':
				return $this->quoteName($args[0]);
				break;

			case 'e':
				return $this->escape($args[0], $args[1] ?? false);
				break;
		}

		return null;
	}

	/**
	 * Magic function to convert the query to a string.
	 *
	 * @return  string    The completed query.
	 */
	public function __toString()
	{
		$query = '';

		if ($this->sql)
		{
			return $this->sql;
		}

		switch ($this->type)
		{
			case 'element':
				$query .= (string) $this->element;
				break;

			case 'select':
				$query .= (string) $this->select;
				$query .= (string) $this->from;

				if ($this->join)
				{
					// Special case for joins
					foreach ($this->join as $join)
					{
						$query .= (string) $join;
					}
				}

				if ($this->where)
				{
					$query .= (string) $this->where;
				}

				if ($this->group)
				{
					$query .= (string) $this->group;
				}

				if ($this->having)
				{
					$query .= (string) $this->having;
				}

				if ($this->order)
				{
					$query .= (string) $this->order;
				}

				break;

			case 'union':
				$query .= (string) $this->union;
				break;

			case 'unionAll':
				$query .= (string) $this->unionAll;
				break;

			case 'delete':
				$query .= (string) $this->delete;
				$query .= (string) $this->from;

				if ($this->join)
				{
					// Special case for joins
					foreach ($this->join as $join)
					{
						$query .= (string) $join;
					}
				}

				if ($this->where)
				{
					$query .= (string) $this->where;
				}

				break;

			case 'update':
				$query .= (string) $this->update;

				if ($this->join)
				{
					// Special case for joins
					foreach ($this->join as $join)
					{
						$query .= (string) $join;
					}
				}

				$query .= (string) $this->set;

				if ($this->where)
				{
					$query .= (string) $this->where;
				}

				break;

			case 'insert':
				$query .= (string) $this->insert;

				// Set method
				if ($this->set)
				{
					$query .= (string) $this->set;
				}
				// Columns-Values method
				elseif ($this->values)
				{
					if ($this->columns)
					{
						$query .= (string) $this->columns;
					}

					$elements = $this->values->getElements();

					if (!($elements[0] instanceof $this))
					{
						$query .= ' VALUES ';
					}

					$query .= (string) $this->values;
				}

				break;

			case 'call':
				$query .= (string) $this->call;
				break;

			case 'exec':
				$query .= (string) $this->exec;
				break;
		}

		if ($this instanceof QueryLimitable)
		{
			$query = $this->processLimit($query, $this->limit, $this->offset);
		}

		return $query;
	}

	/**
	 * Magic function to get protected variable value
	 *
	 * @param   string  $name  The name of the variable.
	 *
	 * @return  mixed
	 */
	public function __get($name)
	{
		return $this->$name ?? null;
	}

	/**
	 * Add a single column, or array of columns to the CALL clause of the query.
	 *
	 * Note that you must not mix insert, update, delete and select method calls when building a query.
	 * The call method can, however, be called multiple times in the same query.
	 *
	 * Usage:
	 * $query->call('a.*')->call('b.id');
	 * $query->call(array('a.*', 'b.id'));
	 *
	 * @param   mixed  $columns  A string or an array of field names.
	 *
	 * @return  Base  Returns this object to allow chaining.
	 */
	public function call($columns)
	{
		$this->type = 'call';

		if (is_null($this->call))
		{
			$this->call = new QueryElement('CALL', $columns);
		}
		else
		{
			$this->call->append($columns);
		}

		return $this;
	}

	/**
	 * Casts a value to a char.
	 *
	 * Ensure that the value is properly quoted before passing to the method.
	 *
	 * Usage:
	 * $query->select($query->castAsChar('a'));
	 *
	 * @param   string  $value  The value to cast as a char.
	 *
	 * @return  string  Returns the cast value.
	 */
	public function castAsChar($value)
	{
		return $value;
	}

	/**
	 * Gets the number of characters in a string.
	 *
	 * Note, use 'length' to find the number of bytes in a string.
	 *
	 * Usage:
	 * $query->select($query->charLength('a'));
	 *
	 * @param   string  $field      A value.
	 * @param   string  $operator   Comparison operator between charLength integer value and $condition
	 * @param   string  $condition  Integer value to compare charLength with.
	 *
	 * @return  string  The required char length call.
	 */
	public function charLength($field, $operator = null, $condition = null)
	{
		return 'CHAR_LENGTH(' . $field . ')' . (isset($operator) && isset($condition) ? ' ' . $operator . ' ' . $condition : '');
	}

	/**
	 * Clear data from the query or a specific clause of the query.
	 *
	 * @param   string  $clause  Optionally, the name of the clause to clear, or nothing to clear the whole query.
	 *
	 * @return  Base  Returns this object to allow chaining.
	 */
	public function clear($clause = null)
	{
		$this->sql = null;

		switch ($clause)
		{
			case 'select':
				$this->select = null;
				$this->type   = null;
				break;

			case 'delete':
				$this->delete = null;
				$this->type   = null;
				break;

			case 'update':
				$this->update = null;
				$this->type   = null;
				break;

			case 'insert':
				$this->insert             = null;
				$this->type               = null;
				$this->autoIncrementField = null;
				break;

			case 'from':
				$this->from = null;
				break;

			case 'join':
				$this->join = null;
				break;

			case 'set':
				$this->set = null;
				break;

			case 'where':
				$this->where = null;
				break;

			case 'group':
				$this->group = null;
				break;

			case 'having':
				$this->having = null;
				break;

			case 'order':
				$this->order = null;
				break;

			case 'columns':
				$this->columns = null;
				break;

			case 'values':
				$this->values = null;
				break;

			case 'exec':
				$this->exec = null;
				$this->type = null;
				break;

			case 'call':
				$this->call = null;
				$this->type = null;
				break;

			case 'limit':
				$this->offset = 0;
				$this->limit  = 0;
				break;

			case 'union':
				$this->union = null;
				break;

			case 'unionAll':
				$this->unionAll = null;
				break;

			default:
				$this->type               = null;
				$this->select             = null;
				$this->delete             = null;
				$this->update             = null;
				$this->insert             = null;
				$this->from               = null;
				$this->join               = null;
				$this->set                = null;
				$this->where              = null;
				$this->group              = null;
				$this->having             = null;
				$this->order              = null;
				$this->columns            = null;
				$this->values             = null;
				$this->autoIncrementField = null;
				$this->exec               = null;
				$this->call               = null;
				$this->union              = null;
				$this->unionAll           = null;
				$this->offset             = 0;
				$this->limit              = 0;
				break;
		}

		return $this;
	}

	/**
	 * Adds a column, or array of column names that would be used for an INSERT INTO statement.
	 *
	 * @param   mixed  $columns  A column name, or array of column names.
	 *
	 * @return  Base  Returns this object to allow chaining.
	 */
	public function columns($columns)
	{
		if (is_null($this->columns))
		{
			$this->columns = new QueryElement('()', $columns);
		}
		else
		{
			$this->columns->append($columns);
		}

		return $this;
	}

	/**
	 * Concatenates an array of column names or values.
	 *
	 * Usage:
	 * $query->select($query->concatenate(array('a', 'b')));
	 *
	 * @param   array   $values     An array of values to concatenate.
	 * @param   string  $separator  As separator to place between each value.
	 *
	 * @return  string  The concatenated values.
	 */
	public function concatenate($values, $separator = null)
	{
		if ($separator)
		{
			return 'CONCATENATE(' . implode(' || ' . $this->quote($separator) . ' || ', $values) . ')';
		}
		else
		{
			return 'CONCATENATE(' . implode(' || ', $values) . ')';
		}
	}

	/**
	 * Gets the current date and time.
	 *
	 * Usage:
	 * $query->where('published_up < '.$query->currentTimestamp());
	 *
	 * @return  string
	 */
	public function currentTimestamp()
	{
		return 'CURRENT_TIMESTAMP()';
	}

	/**
	 * Returns a PHP date() function compliant date format for the database driver.
	 *
	 * This method is provided for use where the query object is passed to a function for modification.
	 * If you have direct access to the database object, it is recommended you use the getDateFormat method directly.
	 *
	 * @return  string  The format string.
	 */
	public function dateFormat()
	{
		if (!($this->db instanceof DriverBase))
		{
			throw new QueryException('Invalid database object');
		}

		return $this->db->getDateFormat();
	}

	/**
	 * Creates a formatted dump of the query for debugging purposes.
	 *
	 * Usage:
	 * echo $query->dump();
	 *
	 * @return  string
	 */
	public function dump()
	{
		return '<pre class="AkeebaEngineQuery">' . str_replace('#__', $this->db->getPrefix(), $this) . '</pre>';
	}

	/**
	 * Add a table name to the DELETE clause of the query.
	 *
	 * Note that you must not mix insert, update, delete and select method calls when building a query.
	 *
	 * Usage:
	 * $query->delete('#__a')->where('id = 1');
	 *
	 * @param   string  $table  The name of the table to delete from.
	 *
	 * @return  Base  Returns this object to allow chaining.
	 */
	public function delete($table = null)
	{
		$this->type   = 'delete';
		$this->delete = new QueryElement('DELETE', null);

		if (!empty($table))
		{
			$this->from($table);
		}

		return $this;
	}

	/**
	 * Method to escape a string for usage in an SQL statement.
	 *
	 * This method is provided for use where the query object is passed to a function for modification.
	 * If you have direct access to the database object, it is recommended you use the escape method directly.
	 *
	 * Note that 'e' is an alias for this method as it is in DriverBase.
	 *
	 * @param   string   $text   The string to be escaped.
	 * @param   boolean  $extra  Optional parameter to provide extra escaping.
	 *
	 * @return  string  The escaped string.
	 */
	public function escape($text, $extra = false)
	{
		if (!($this->db instanceof DriverBase))
		{
			throw new QueryException('Invalid database object');
		}

		return $this->db->escape($text, $extra);
	}

	/**
	 * Add a single column, or array of columns to the EXEC clause of the query.
	 *
	 * Note that you must not mix insert, update, delete and select method calls when building a query.
	 * The exec method can, however, be called multiple times in the same query.
	 *
	 * Usage:
	 * $query->exec('a.*')->exec('b.id');
	 * $query->exec(array('a.*', 'b.id'));
	 *
	 * @param   mixed  $columns  A string or an array of field names.
	 *
	 * @return  Base  Returns this object to allow chaining.
	 */
	public function exec($columns)
	{
		$this->type = 'exec';

		if (is_null($this->exec))
		{
			$this->exec = new QueryElement('EXEC', $columns);
		}
		else
		{
			$this->exec->append($columns);
		}

		return $this;
	}

	/**
	 * Add a table to the FROM clause of the query.
	 *
	 * Note that while an array of tables can be provided, it is recommended you use explicit joins.
	 *
	 * Usage:
	 * $query->select('*')->from('#__a');
	 *
	 * @param   mixed   $tables         A string or array of table names.
	 *                                  This can be a Base object (or a child of it) when used
	 *                                  as a subquery in FROM clause along with a value for $subQueryAlias.
	 * @param   string  $subQueryAlias  Alias used when $tables is a Base.
	 *
	 * @return  Base  Returns this object to allow chaining.
	 */
	public function from($tables, $subQueryAlias = null)
	{
		if (is_null($this->from))
		{
			if ($tables instanceof $this)
			{
				if (is_null($subQueryAlias))
				{
					throw new QueryException('Null subquery defined');
				}

				$tables = '( ' . (string) $tables . ' ) AS ' . $this->quoteName($subQueryAlias);
			}

			$this->from = new QueryElement('FROM', $tables);
		}
		else
		{
			$this->from->append($tables);
		}

		return $this;
	}

	/**
	 * Used to get a string to extract year from date column.
	 *
	 * Usage:
	 * $query->select($query->year($query->quoteName('dateColumn')));
	 *
	 * @param   string  $date  Date column containing year to be extracted.
	 *
	 * @return  string  Returns string to extract year from a date.
	 */
	public function year($date)
	{
		return 'YEAR(' . $date . ')';
	}

	/**
	 * Used to get a string to extract month from date column.
	 *
	 * Usage:
	 * $query->select($query->month($query->quoteName('dateColumn')));
	 *
	 * @param   string  $date  Date column containing month to be extracted.
	 *
	 * @return  string  Returns string to extract month from a date.
	 */
	public function month($date)
	{
		return 'MONTH(' . $date . ')';
	}

	/**
	 * Used to get a string to extract day from date column.
	 *
	 * Usage:
	 * $query->select($query->day($query->quoteName('dateColumn')));
	 *
	 * @param   string  $date  Date column containing day to be extracted.
	 *
	 * @return  string  Returns string to extract day from a date.
	 */
	public function day($date)
	{
		return 'DAY(' . $date . ')';
	}

	/**
	 * Used to get a string to extract hour from date column.
	 *
	 * Usage:
	 * $query->select($query->hour($query->quoteName('dateColumn')));
	 *
	 * @param   string  $date  Date column containing hour to be extracted.
	 *
	 * @return  string  Returns string to extract hour from a date.
	 */
	public function hour($date)
	{
		return 'HOUR(' . $date . ')';
	}

	/**
	 * Used to get a string to extract minute from date column.
	 *
	 * Usage:
	 * $query->select($query->minute($query->quoteName('dateColumn')));
	 *
	 * @param   string  $date  Date column containing minute to be extracted.
	 *
	 * @return  string  Returns string to extract minute from a date.
	 */
	public function minute($date)
	{
		return 'MINUTE(' . $date . ')';
	}

	/**
	 * Used to get a string to extract seconds from date column.
	 *
	 * Usage:
	 * $query->select($query->second($query->quoteName('dateColumn')));
	 *
	 * @param   string  $date  Date column containing second to be extracted.
	 *
	 * @return  string  Returns string to extract second from a date.
	 */
	public function second($date)
	{
		return 'SECOND(' . $date . ')';
	}

	/**
	 * Add a grouping column to the GROUP clause of the query.
	 *
	 * Usage:
	 * $query->group('id');
	 *
	 * @param   mixed  $columns  A string or array of ordering columns.
	 *
	 * @return  Base  Returns this object to allow chaining.
	 */
	public function group($columns)
	{
		if (is_null($this->group))
		{
			$this->group = new QueryElement('GROUP BY', $columns);
		}
		else
		{
			$this->group->append($columns);
		}

		return $this;
	}

	/**
	 * A conditions to the HAVING clause of the query.
	 *
	 * Usage:
	 * $query->group('id')->having('COUNT(id) > 5');
	 *
	 * @param   mixed   $conditions  A string or array of columns.
	 * @param   string  $glue        The glue by which to join the conditions. Defaults to AND.
	 *
	 * @return  Base  Returns this object to allow chaining.
	 */
	public function having($conditions, $glue = 'AND')
	{
		if (is_null($this->having))
		{
			$glue         = strtoupper($glue);
			$this->having = new QueryElement('HAVING', $conditions, " $glue ");
		}
		else
		{
			$this->having->append($conditions);
		}

		return $this;
	}

	/**
	 * Add an INNER JOIN clause to the query.
	 *
	 * Usage:
	 * $query->innerJoin('b ON b.id = a.id')->innerJoin('c ON c.id = b.id');
	 *
	 * @param   string  $condition  The join condition.
	 *
	 * @return  Base  Returns this object to allow chaining.
	 */
	public function innerJoin($condition)
	{
		$this->join('INNER', $condition);

		return $this;
	}

	/**
	 * Add a table name to the INSERT clause of the query.
	 *
	 * Note that you must not mix insert, update, delete and select method calls when building a query.
	 *
	 * Usage:
	 * $query->insert('#__a')->set('id = 1');
	 * $query->insert('#__a')->columns('id, title')->values('1,2')->values('3,4');
	 * $query->insert('#__a')->columns('id, title')->values(array('1,2', '3,4'));
	 *
	 * @param   mixed    $table           The name of the table to insert data into.
	 * @param   boolean  $incrementField  The name of the field to auto increment.
	 *
	 * @return  Base  Returns this object to allow chaining.
	 */
	public function insert($table, $incrementField = false)
	{
		$this->type               = 'insert';
		$this->insert             = new QueryElement('INSERT INTO', $table);
		$this->autoIncrementField = $incrementField;

		return $this;
	}

	/**
	 * Add a JOIN clause to the query.
	 *
	 * Usage:
	 * $query->join('INNER', 'b ON b.id = a.id);
	 *
	 * @param   string  $type        The type of join. This string is prepended to the JOIN keyword.
	 * @param   string  $conditions  A string or array of conditions.
	 *
	 * @return  Base  Returns this object to allow chaining.
	 */
	public function join($type, $conditions)
	{
		if (is_null($this->join))
		{
			$this->join = [];
		}
		$this->join[] = new QueryElement(strtoupper($type) . ' JOIN', $conditions);

		return $this;
	}

	/**
	 * Add a LEFT JOIN clause to the query.
	 *
	 * Usage:
	 * $query->leftJoin('b ON b.id = a.id')->leftJoin('c ON c.id = b.id');
	 *
	 * @param   string  $condition  The join condition.
	 *
	 * @return  Base  Returns this object to allow chaining.
	 */
	public function leftJoin($condition)
	{
		$this->join('LEFT', $condition);

		return $this;
	}

	/**
	 * Get the length of a string in bytes.
	 *
	 * Note, use 'charLength' to find the number of characters in a string.
	 *
	 * Usage:
	 * query->where($query->length('a').' > 3');
	 *
	 * @param   string  $value  The string to measure.
	 *
	 * @return  int
	 */
	public function length($value)
	{
		return 'LENGTH(' . $value . ')';
	}

	/**
	 * Get the null or zero representation of a timestamp for the database driver.
	 *
	 * This method is provided for use where the query object is passed to a function for modification.
	 * If you have direct access to the database object, it is recommended you use the nullDate method directly.
	 *
	 * Usage:
	 * $query->where('modified_date <> '.$query->nullDate());
	 *
	 * @param   boolean  $quoted  Optionally wraps the null date in database quotes (true by default).
	 *
	 * @return  string  Null or zero representation of a timestamp.
	 */
	public function nullDate($quoted = true)
	{
		if (!($this->db instanceof DriverBase))
		{
			throw new QueryException('Invalid database object');
		}

		$result = $this->db->getNullDate();

		if ($quoted)
		{
			return $this->db->quote($result);
		}

		return $result;
	}

	/**
	 * Add a ordering column to the ORDER clause of the query.
	 *
	 * Usage:
	 * $query->order('foo')->order('bar');
	 * $query->order(array('foo','bar'));
	 *
	 * @param   mixed  $columns  A string or array of ordering columns.
	 *
	 * @return  Base  Returns this object to allow chaining.
	 */
	public function order($columns)
	{
		if (is_null($this->order))
		{
			$this->order = new QueryElement('ORDER BY', $columns);
		}
		else
		{
			$this->order->append($columns);
		}

		return $this;
	}

	/**
	 * Add an OUTER JOIN clause to the query.
	 *
	 * Usage:
	 * $query->outerJoin('b ON b.id = a.id')->outerJoin('c ON c.id = b.id');
	 *
	 * @param   string  $condition  The join condition.
	 *
	 * @return  Base  Returns this object to allow chaining.
	 */
	public function outerJoin($condition)
	{
		$this->join('OUTER', $condition);

		return $this;
	}

	/**
	 * Method to quote and optionally escape a string to database requirements for insertion into the database.
	 *
	 * This method is provided for use where the query object is passed to a function for modification.
	 * If you have direct access to the database object, it is recommended you use the quote method directly.
	 *
	 * Note that 'q' is an alias for this method as it is in DriverBase.
	 *
	 * Usage:
	 * $query->quote('fulltext');
	 * $query->q('fulltext');
	 * $query->q(array('option', 'fulltext'));
	 *
	 * @param   mixed    $text    A string or an array of strings to quote.
	 * @param   boolean  $escape  True to escape the string, false to leave it unchanged.
	 *
	 * @return  string  The quoted input string.
	 *
	 * @throws  QueryException if the internal db property is not a valid object.
	 */
	public function quote($text, $escape = true)
	{
		if (!($this->db instanceof DriverBase))
		{
			throw new QueryException('Invalid database object');
		}

		return $this->db->quote($text, $escape);
	}

	/**
	 * Wrap an SQL statement identifier name such as column, table or database names in quotes to prevent injection
	 * risks and reserved word conflicts.
	 *
	 * This method is provided for use where the query object is passed to a function for modification.
	 * If you have direct access to the database object, it is recommended you use the quoteName method directly.
	 *
	 * Note that 'qn' is an alias for this method as it is in DriverBase.
	 *
	 * Usage:
	 * $query->quoteName('#__a');
	 * $query->qn('#__a');
	 *
	 * @param   mixed  $name  The identifier name to wrap in quotes, or an array of identifier names to wrap in quotes.
	 *                        Each type supports dot-notation name.
	 * @param   mixed  $as    The AS query part associated to $name. It can be string or array, in latter case it has
	 *                        to be same length of $name; if is null there will not be any AS part for string or array
	 *                        element.
	 *
	 * @return  mixed  The quote wrapped name, same type of $name.
	 *
	 * @throws  QueryException if the internal db property is not a valid object.
	 */
	public function quoteName($name, $as = null)
	{
		if (!($this->db instanceof DriverBase))
		{
			throw new QueryException('Invalid database object');
		}

		return $this->db->quoteName($name, $as);
	}

	/**
	 * Add a RIGHT JOIN clause to the query.
	 *
	 * Usage:
	 * $query->rightJoin('b ON b.id = a.id')->rightJoin('c ON c.id = b.id');
	 *
	 * @param   string  $condition  The join condition.
	 *
	 * @return  Base  Returns this object to allow chaining.
	 */
	public function rightJoin($condition)
	{
		$this->join('RIGHT', $condition);

		return $this;
	}

	/**
	 * Add a single column, or array of columns to the SELECT clause of the query.
	 *
	 * Note that you must not mix insert, update, delete and select method calls when building a query.
	 * The select method can, however, be called multiple times in the same query.
	 *
	 * Usage:
	 * $query->select('a.*')->select('b.id');
	 * $query->select(array('a.*', 'b.id'));
	 *
	 * @param   mixed  $columns  A string or an array of field names.
	 *
	 * @return  Base  Returns this object to allow chaining.
	 */
	public function select($columns)
	{
		$this->type = 'select';

		if (is_null($this->select))
		{
			$this->select = new QueryElement('SELECT', $columns);
		}
		else
		{
			$this->select->append($columns);
		}

		return $this;
	}

	/**
	 * Add a single condition string, or an array of strings to the SET clause of the query.
	 *
	 * Usage:
	 * $query->set('a = 1')->set('b = 2');
	 * $query->set(array('a = 1', 'b = 2');
	 *
	 * @param   mixed   $conditions  A string or array of string conditions.
	 * @param   string  $glue        The glue by which to join the condition strings. Defaults to ,.
	 *                               Note that the glue is set on first use and cannot be changed.
	 *
	 * @return  Base  Returns this object to allow chaining.
	 */
	public function set($conditions, $glue = ',')
	{
		if (is_null($this->set))
		{
			$glue      = strtoupper($glue);
			$this->set = new QueryElement('SET', $conditions, "\n\t$glue ");
		}
		else
		{
			$this->set->append($conditions);
		}

		return $this;
	}

	/**
	 * Allows a direct query to be provided to the database
	 * driver's setQuery() method, but still allow queries
	 * to have bounded variables.
	 *
	 * Usage:
	 * $query->setQuery('select * from #__users');
	 *
	 * @param   mixed  $query  An SQL Query
	 *
	 * @return  QueryElement  Returns this object to allow chaining.
	 */
	public function setQuery($query)
	{
		$this->sql = $query;

		return $this;
	}

	/**
	 * Add a table name to the UPDATE clause of the query.
	 *
	 * Note that you must not mix insert, update, delete and select method calls when building a query.
	 *
	 * Usage:
	 * $query->update('#__foo')->set(...);
	 *
	 * @param   string  $table  A table to update.
	 *
	 * @return  Base  Returns this object to allow chaining.
	 */
	public function update($table)
	{
		$this->type   = 'update';
		$this->update = new QueryElement('UPDATE', $table);

		return $this;
	}

	/**
	 * Adds a tuple, or array of tuples that would be used as values for an INSERT INTO statement.
	 *
	 * Usage:
	 * $query->values('1,2,3')->values('4,5,6');
	 * $query->values(array('1,2,3', '4,5,6'));
	 *
	 * @param   string  $values  A single tuple, or array of tuples.
	 *
	 * @return  Base  Returns this object to allow chaining.
	 */
	public function values($values)
	{
		if (is_null($this->values))
		{
			$this->values = new QueryElement('()', $values, '),(');
		}
		else
		{
			$this->values->append($values);
		}

		return $this;
	}

	/**
	 * Add a single condition, or an array of conditions to the WHERE clause of the query.
	 *
	 * Usage:
	 * $query->where('a = 1')->where('b = 2');
	 * $query->where(array('a = 1', 'b = 2'));
	 *
	 * @param   mixed   $conditions  A string or array of where conditions.
	 * @param   string  $glue        The glue by which to join the conditions. Defaults to AND.
	 *                               Note that the glue is set on first use and cannot be changed.
	 *
	 * @return  Base  Returns this object to allow chaining.
	 */
	public function where($conditions, $glue = 'AND')
	{
		if (is_null($this->where))
		{
			$glue        = strtoupper($glue);
			$this->where = new QueryElement('WHERE', $conditions, " $glue ");
		}
		else
		{
			$this->where->append($conditions);
		}

		return $this;
	}

	/**
	 * Method to provide deep copy support to nested objects and
	 * arrays when cloning.
	 *
	 * @return  void
	 */
	public function __clone()
	{
		foreach ($this as $k => $v)
		{
			if ($k === 'db')
			{
				continue;
			}

			if (is_object($v) || is_array($v))
			{
				$this->$k = unserialize(serialize($v));
			}
		}
	}

	/**
	 * Add a query to UNION with the current query.
	 * Multiple unions each require separate statements and create an array of unions.
	 *
	 * Usage:
	 * $query->union('SELECT name FROM  #__foo')
	 * $query->union('SELECT name FROM  #__foo','distinct')
	 * $query->union(array('SELECT name FROM  #__foo','SELECT name FROM  #__bar'))
	 *
	 * @param   mixed    $query     The Base object or string to union.
	 * @param   boolean  $distinct  True to only return distinct rows from the union.
	 * @param   string   $glue      The glue by which to join the conditions.
	 *
	 * @return  mixed    The Base object on success or boolean false on failure.
	 */
	public function union($query, $distinct = false, $glue = '')
	{
		// Clear any ORDER BY clause in UNION query
		// See http://dev.mysql.com/doc/refman/5.0/en/union.html
		if (!is_null($this->order))
		{
			$this->clear('order');
		}

		// Set up the DISTINCT flag, the name with parentheses, and the glue.
		if ($distinct)
		{
			$name = 'UNION DISTINCT ()';
			$glue = ')' . PHP_EOL . 'UNION DISTINCT (';
		}
		else
		{
			$glue = ')' . PHP_EOL . 'UNION (';
			$name = 'UNION ()';
		}

		// Get the QueryElement if it does not exist
		if (is_null($this->union))
		{
			$this->union = new QueryElement($name, $query, "$glue");
		}
		// Otherwise append the second UNION.
		else
		{
			$glue = '';
			$this->union->append($query);
		}

		return $this;
	}

	/**
	 * Add a query to UNION DISTINCT with the current query. Simply a proxy to Union with the Distinct clause.
	 *
	 * Usage:
	 * $query->unionDistinct('SELECT name FROM  #__foo')
	 *
	 * @param   mixed   $query  The Base object or string to union.
	 * @param   string  $glue   The glue by which to join the conditions.
	 *
	 * @return  mixed   The Base object on success or boolean false on failure.
	 */
	public function unionDistinct($query, $glue = '')
	{
		$distinct = true;

		// Apply the distinct flag to the union.
		return $this->union($query, $distinct, $glue);
	}

	/**
	 * Find and replace sprintf-like tokens in a format string.
	 * Each token takes one of the following forms:
	 *     %%       - A literal percent character.
	 *     %[t]     - Where [t] is a type specifier.
	 *     %[n]$[x] - Where [n] is an argument specifier and [t] is a type specifier.
	 *
	 * Types:
	 * a - Numeric: Replacement text is coerced to a numeric type but not quoted or escaped.
	 * e - Escape: Replacement text is passed to $this->escape().
	 * E - Escape (extra): Replacement text is passed to $this->escape() with true as the second argument.
	 * n - Name Quote: Replacement text is passed to $this->quoteName().
	 * q - Quote: Replacement text is passed to $this->quote().
	 * Q - Quote (no escape): Replacement text is passed to $this->quote() with false as the second argument.
	 * r - Raw: Replacement text is used as-is. (Be careful)
	 *
	 * Date Types:
	 * - Replacement text automatically quoted (use uppercase for Name Quote).
	 * - Replacement text should be a string in date format or name of a date column.
	 * y/Y - Year
	 * m/M - Month
	 * d/D - Day
	 * h/H - Hour
	 * i/I - Minute
	 * s/S - Second
	 *
	 * Invariable Types:
	 * - Takes no argument.
	 * - Argument index not incremented.
	 * t - Replacement text is the result of $this->currentTimestamp().
	 * z - Replacement text is the result of $this->nullDate(false).
	 * Z - Replacement text is the result of $this->nullDate(true).
	 *
	 * Usage:
	 * $query->format('SELECT %1$n FROM %2$n WHERE %3$n = %4$a', 'foo', '#__foo', 'bar', 1);
	 * Returns: SELECT `foo` FROM `#__foo` WHERE `bar` = 1
	 *
	 * Notes:
	 * The argument specifier is optional but recommended for clarity.
	 * The argument index used for unspecified tokens is incremented only when used.
	 *
	 * @param   string  $format  The formatting string.
	 *
	 * @return  string  Returns a string produced according to the formatting string.
	 */
	public function format($format)
	{
		$query = $this;
		$args  = array_slice(func_get_args(), 1);
		array_unshift($args, null);

		$i    = 1;
		$func = function ($match) use ($query, $args, &$i) {
			if (isset($match[6]) && $match[6] == '%')
			{
				return '%';
			}

			// No argument required, do not increment the argument index.
			switch ($match[5])
			{
				case 't':
					return $query->currentTimestamp();
					break;

				case 'z':
					return $query->nullDate(false);
					break;

				case 'Z':
					return $query->nullDate(true);
					break;
			}

			// Increment the argument index only if argument specifier not provided.
			$index = is_numeric($match[4]) ? (int) $match[4] : $i++;

			if (!$index || !isset($args[$index]))
			{
				$replacement = '';
			}
			else
			{
				$replacement = $args[$index];
			}

			switch ($match[5])
			{
				case 'a':
					return 0 + $replacement;
					break;

				case 'e':
					return $query->escape($replacement);
					break;

				case 'E':
					return $query->escape($replacement, true);
					break;

				case 'n':
					return $query->quoteName($replacement);
					break;

				case 'q':
					return $query->quote($replacement);
					break;

				case 'Q':
					return $query->quote($replacement, false);
					break;

				case 'r':
					return $replacement;
					break;

				// Dates
				case 'y':
					return $query->year($query->quote($replacement));
					break;

				case 'Y':
					return $query->year($query->quoteName($replacement));
					break;

				case 'm':
					return $query->month($query->quote($replacement));
					break;

				case 'M':
					return $query->month($query->quoteName($replacement));
					break;

				case 'd':
					return $query->day($query->quote($replacement));
					break;

				case 'D':
					return $query->day($query->quoteName($replacement));
					break;

				case 'h':
					return $query->hour($query->quote($replacement));
					break;

				case 'H':
					return $query->hour($query->quoteName($replacement));
					break;

				case 'i':
					return $query->minute($query->quote($replacement));
					break;

				case 'I':
					return $query->minute($query->quoteName($replacement));
					break;

				case 's':
					return $query->second($query->quote($replacement));
					break;

				case 'S':
					return $query->second($query->quoteName($replacement));
					break;
			}

			return '';
		};

		/**
		 * Regexp to find an replace all tokens.
		 * Matched fields:
		 * 0: Full token
		 * 1: Everything following '%'
		 * 2: Everything following '%' unless '%'
		 * 3: Argument specifier and '$'
		 * 4: Argument specifier
		 * 5: Type specifier
		 * 6: '%' if full token is '%%'
		 */

		return preg_replace_callback('#%(((([\d]+)\$)?([aeEnqQryYmMdDhHiIsStzZ]))|(%))#', $func, $format);
	}

	/**
	 * Add to the current date and time.
	 * Usage:
	 * $query->select($query->dateAdd());
	 * Prefixing the interval with a - (negative sign) will cause subtraction to be used.
	 * Note: Not all drivers support all units.
	 *
	 * @param   string  $date      The SQL-formatted date to add to. May be a date or datetime string.
	 * @param   string  $interval  The string representation of the appropriate number of units
	 * @param   string  $datePart  The part of the date to perform the addition on
	 *
	 * @return  string  The string with the appropriate sql for addition of dates
	 *
	 * @see http://dev.mysql.com/doc/refman/5.1/en/date-and-time-functions.html#function_date-add
	 */
	public function dateAdd($date, $interval, $datePart)
	{
		return trim("DATE_ADD('" . $date . "', INTERVAL " . $interval . ' ' . $datePart . ')');
	}

	/**
	 * Add a query to UNION ALL with the current query.
	 * Multiple unions each require separate statements and create an array of unions.
	 *
	 * Usage:
	 * $query->union('SELECT name FROM  #__foo')
	 * $query->union('SELECT name FROM  #__foo','distinct')
	 * $query->union(array('SELECT name FROM  #__foo','SELECT name FROM  #__bar'))
	 *
	 * @param   mixed    $query     The Base object or string to union.
	 * @param   boolean  $distinct  True to only return distinct rows from the union.
	 * @param   string   $glue      The glue by which to join the conditions.
	 *
	 * @return  mixed    The Base object on success or boolean false on failure.
	 */
	public function unionAll($query, $distinct = false, $glue = '')
	{
		$glue = ')' . PHP_EOL . 'UNION ALL (';
		$name = 'UNION ALL ()';

		// Get the QueryElement if it does not exist
		if (is_null($this->unionAll))
		{
			$this->unionAll = new QueryElement($name, $query, "$glue");
		}

		// Otherwise append the second UNION.
		else
		{
			$glue = '';
			$this->unionAll->append($query);
		}

		return $this;
	}
}
PK�!]@1�٤�$BackupEngine/Driver/Query/Mysqli.phpnu&1i�<?php
/**
 * Akeeba Engine
 *
 * @package   akeebaengine
 * @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License version 3, or later
 *
 * 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, version 3.
 *
 * 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
 * <https://www.gnu.org/licenses/>.
 */

namespace Akeeba\Engine\Driver\Query;

defined('AKEEBAENGINE') || die();

use Akeeba\Engine\Driver\Query\Base as BaseQuery;

/**
 * Query Building Class.
 *
 * Based on Joomla! Platform 11.3
 */
class Mysqli extends Base implements Limitable
{
	/**
	 * @var    integer  The offset for the result set.
	 */
	protected $offset;

	/**
	 * @var    integer  The limit for the result set.
	 */
	protected $limit;

	/**
	 * Method to modify a query already in string format with the needed
	 * additions to make the query limited to a particular number of
	 * results, or start at a particular offset.
	 *
	 * @param   string   $query   The query in string format
	 * @param   integer  $limit   The limit for the result set
	 * @param   integer  $offset  The offset for the result set
	 *
	 * @return string
	 */
	public function processLimit($query, $limit, $offset = 0)
	{
		if ($limit > 0 || $offset > 0)
		{
			$query .= ' LIMIT ' . $offset . ', ' . $limit;
		}

		return $query;
	}

	/**
	 * Sets the offset and limit for the result set, if the database driver supports it.
	 *
	 * Usage:
	 * $query->setLimit(100, 0); (retrieve 100 rows, starting at first record)
	 * $query->setLimit(50, 50); (retrieve 50 rows, starting at 50th record)
	 *
	 * @param   integer  $limit   The limit for the result set
	 * @param   integer  $offset  The offset for the result set
	 *
	 * @return  BaseQuery  Returns this object to allow chaining.
	 */
	public function setLimit($limit = 0, $offset = 0)
	{
		$this->limit  = (int) $limit;
		$this->offset = (int) $offset;

		return $this;
	}

	/**
	 * Concatenates an array of column names or values.
	 *
	 * @param   array   $values     An array of values to concatenate.
	 * @param   string  $separator  As separator to place between each value.
	 *
	 * @return  string  The concatenated values.
	 */
	public function concatenate($values, $separator = null)
	{
		if ($separator)
		{
			$concat_string = 'CONCAT_WS(' . $this->quote($separator);

			foreach ($values as $value)
			{
				$concat_string .= ', ' . $value;
			}

			return $concat_string . ')';
		}
		else
		{
			return 'CONCAT(' . implode(',', $values) . ')';
		}
	}
}
PK�!]U*����&BackupEngine/Driver/Query/Pdomysql.phpnu&1i�<?php
/**
 * Akeeba Engine
 *
 * @package   akeebaengine
 * @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License version 3, or later
 *
 * 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, version 3.
 *
 * 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
 * <https://www.gnu.org/licenses/>.
 */

namespace Akeeba\Engine\Driver\Query;

defined('AKEEBAENGINE') || die();

/**
 * Query Building Class.
 *
 * Based on Joomla! Platform 11.3
 */
class Pdomysql extends Mysqli implements Limitable
{
}
PK�!]��}�		'BackupEngine/Driver/Query/Limitable.phpnu&1i�<?php
/**
 * Akeeba Engine
 *
 * @package   akeebaengine
 * @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License version 3, or later
 *
 * 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, version 3.
 *
 * 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
 * <https://www.gnu.org/licenses/>.
 */

namespace Akeeba\Engine\Driver\Query;

defined('AKEEBAENGINE') || die();

use Akeeba\Engine\Driver\Query\Base as BaseQuery;

/**
 * Query Limitable Interface.
 * Adds bind/unbind methods as well as a getBounded() method
 * to retrieve the stored bounded variables on demand prior to
 * query execution.
 *
 * Based on Joomla! Platform 11.2
 */
interface Limitable
{
	/**
	 * Method to modify a query already in string format with the needed
	 * additions to make the query limited to a particular number of
	 * results, or start at a particular offset. This method is used
	 * automatically by the __toString() method if it detects that the
	 * query implements the Limitable interface.
	 *
	 * @param   string   $query   The query in string format
	 * @param   integer  $limit   The limit for the result set
	 * @param   integer  $offset  The offset for the result set
	 *
	 * @return  string
	 *
	 * @since   12.1
	 */
	public function processLimit($query, $limit, $offset = 0);

	/**
	 * Sets the offset and limit for the result set, if the database driver supports it.
	 *
	 * Usage:
	 * $query->setLimit(100, 0); (retrieve 100 rows, starting at first record)
	 * $query->setLimit(50, 50); (retrieve 50 rows, starting at 50th record)
	 *
	 * @param   integer  $limit   The limit for the result set
	 * @param   integer  $offset  The offset for the result set
	 *
	 * @return  BaseQuery  Returns this object to allow chaining.
	 *
	 * @since   12.1
	 */
	public function setLimit($limit = 0, $offset = 0);
}
PK�!]'�����#BackupEngine/Driver/Query/Mysql.phpnu&1i�<?php
/**
 * Akeeba Engine
 *
 * @package   akeebaengine
 * @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License version 3, or later
 *
 * 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, version 3.
 *
 * 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
 * <https://www.gnu.org/licenses/>.
 */

namespace Akeeba\Engine\Driver\Query;

defined('AKEEBAENGINE') || die();

/**
 * Query Building Class.
 *
 * Based on Joomla! Platform 11.3
 */
class Mysql extends Mysqli
{
}
PK�!]�Kd�
�
%BackupEngine/Driver/Query/Element.phpnu&1i�<?php
/**
 * Akeeba Engine
 *
 * @package   akeebaengine
 * @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License version 3, or later
 *
 * 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, version 3.
 *
 * 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
 * <https://www.gnu.org/licenses/>.
 */

namespace Akeeba\Engine\Driver\Query;

defined('AKEEBAENGINE') || die();

/**
 * Query Element Class.
 *
 * Based on Joomla! Platform 11.3
 */
class Element
{
	/**
	 * @var    string  The name of the element.
	 */
	protected $name = null;

	/**
	 * @var    array  An array of elements.
	 */
	protected $elements = null;

	/**
	 * @var    string  Glue piece.
	 */
	protected $glue = null;

	/**
	 * Constructor.
	 *
	 * @param   string  $name      The name of the element.
	 * @param   mixed   $elements  String or array.
	 * @param   string  $glue      The glue for elements.
	 */
	public function __construct($name, $elements, $glue = ',')
	{
		$this->elements = [];
		$this->name     = $name;
		$this->glue     = $glue;

		$this->append($elements);
	}

	/**
	 * Magic function to convert the query element to a string.
	 *
	 * @return  string
	 */
	public function __toString()
	{
		if (substr($this->name, -2) == '()')
		{
			return PHP_EOL . substr($this->name, 0, -2) . '(' . implode($this->glue, $this->elements) . ')';
		}
		else
		{
			return PHP_EOL . $this->name . ' ' . implode($this->glue, $this->elements);
		}
	}

	/**
	 * Appends element parts to the internal list.
	 *
	 * @param   mixed  $elements  String or array.
	 *
	 * @return  void
	 */
	public function append($elements)
	{
		if (is_array($elements))
		{
			$this->elements = array_merge($this->elements, $elements);
		}
		else
		{
			$this->elements = array_merge($this->elements, [$elements]);
		}
	}

	/**
	 * Gets the elements of this element.
	 *
	 * @return  string
	 */
	public function getElements()
	{
		return $this->elements;
	}

	/**
	 * Method to provide deep copy support to nested objects and arrays
	 * when cloning.
	 *
	 * @return  void
	 */
	public function __clone()
	{
		foreach ($this as $k => $v)
		{
			if (is_object($v) || is_array($v))
			{
				$this->{$k} = unserialize(serialize($v));
			}
		}
	}
}
PK�!]`#�
��$BackupEngine/Driver/Query/Sqlite.phpnu&1i�<?php
/**
 * Akeeba Engine
 *
 * @package   akeebaengine
 * @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License version 3, or later
 *
 * 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, version 3.
 *
 * 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
 * <https://www.gnu.org/licenses/>.
 */

namespace Akeeba\Engine\Driver\Query;

defined('AKEEBAENGINE') || die();

use PDO;
use stdClass;

/**
 * SQLite Query Building Class.
 *
 * @since  1.0
 */
class Sqlite extends Base implements Preparable, Limitable
{
	/**
	 * The limit for the result set.
	 *
	 * @var    integer
	 * @since  1.0
	 */
	protected $limit;

	/**
	 * The offset for the result set.
	 *
	 * @var    integer
	 * @since  1.0
	 */
	protected $offset;

	/**
	 * Holds key / value pair of bound objects.
	 *
	 * @var    mixed
	 * @since  1.0
	 */
	protected $bounded = [];

	/**
	 * Method to add a variable to an internal array that will be bound to a prepared SQL statement before query execution. Also
	 * removes a variable that has been bounded from the internal bounded array when the passed in value is null.
	 *
	 * @param   string|integer   $key            The key that will be used in your SQL query to reference the value. Usually of
	 *                                           the form ':key', but can also be an integer.
	 * @param   mixed           &$value          The value that will be bound. The value is passed by reference to support output
	 *                                           parameters such as those possible with stored procedures.
	 * @param   integer          $dataType       Constant corresponding to a SQL datatype.
	 * @param   integer          $length         The length of the variable. Usually required for OUTPUT parameters.
	 * @param   array            $driverOptions  Optional driver options to be used.
	 *
	 * @return  Sqlite  Returns this object to allow chaining.
	 *
	 * @since   1.0
	 */
	public function bind($key = null, &$value = null, $dataType = PDO::PARAM_STR, $length = 0, $driverOptions = [])
	{
		// Case 1: Empty Key (reset $bounded array)
		if (empty($key))
		{
			$this->bounded = [];

			return $this;
		}

		// Case 2: Key Provided, null value (unset key from $bounded array)
		if (is_null($value))
		{
			if (isset($this->bounded[$key]))
			{
				unset($this->bounded[$key]);
			}

			return $this;
		}

		$obj = new stdClass;

		$obj->value         = &$value;
		$obj->dataType      = $dataType;
		$obj->length        = $length;
		$obj->driverOptions = $driverOptions;

		// Case 3: Simply add the Key/Value into the bounded array
		$this->bounded[$key] = $obj;

		return $this;
	}

	/**
	 * Retrieves the bound parameters array when key is null and returns it by reference. If a key is provided then that item is
	 * returned.
	 *
	 * @param   mixed  $key  The bounded variable key to retrieve.
	 *
	 * @return  mixed
	 *
	 * @since   1.0
	 */
	public function &getBounded($key = null)
	{
		if (empty($key))
		{
			return $this->bounded;
		}
		else
		{
			if (isset($this->bounded[$key]))
			{
				return $this->bounded[$key];
			}
		}
	}

	/**
	 * Gets the number of characters in a string.
	 *
	 * Note, use 'length' to find the number of bytes in a string.
	 *
	 * Usage:
	 * $query->select($query->charLength('a'));
	 *
	 * @param   string  $field      A value.
	 * @param   string  $operator   Comparison operator between charLength integer value and $condition
	 * @param   string  $condition  Integer value to compare charLength with.
	 *
	 * @return  string  The required char length call.
	 *
	 * @since   1.1.0
	 */
	public function charLength($field, $operator = null, $condition = null)
	{
		return 'length(' . $field . ')' . (isset($operator) && isset($condition) ? ' ' . $operator . ' ' . $condition : '');
	}

	/**
	 * Clear data from the query or a specific clause of the query.
	 *
	 * @param   string  $clause  Optionally, the name of the clause to clear, or nothing to clear the whole query.
	 *
	 * @return  Sqlite  Returns this object to allow chaining.
	 *
	 * @since   1.0
	 */
	public function clear($clause = null)
	{
		switch ($clause)
		{
			case null:
				$this->bounded = [];
				break;
		}

		return parent::clear($clause);
	}

	/**
	 * Concatenates an array of column names or values.
	 *
	 * Usage:
	 * $query->select($query->concatenate(array('a', 'b')));
	 *
	 * @param   array   $values     An array of values to concatenate.
	 * @param   string  $separator  As separator to place between each value.
	 *
	 * @return  string  The concatenated values.
	 *
	 * @since   1.1.0
	 */
	public function concatenate($values, $separator = null)
	{
		if ($separator)
		{
			return implode(' || ' . $this->quote($separator) . ' || ', $values);
		}
		else
		{
			return implode(' || ', $values);
		}
	}

	/**
	 * Method to modify a query already in string format with the needed
	 * additions to make the query limited to a particular number of
	 * results, or start at a particular offset. This method is used
	 * automatically by the __toString() method if it detects that the
	 * query implements the LimitableInterface.
	 *
	 * @param   string   $query   The query in string format
	 * @param   integer  $limit   The limit for the result set
	 * @param   integer  $offset  The offset for the result set
	 *
	 * @return  string
	 *
	 * @since   1.0
	 */
	public function processLimit($query, $limit, $offset = 0)
	{
		if ($limit > 0 || $offset > 0)
		{
			$query .= ' LIMIT ' . $offset . ', ' . $limit;
		}

		return $query;
	}

	/**
	 * Sets the offset and limit for the result set, if the database driver supports it.
	 *
	 * Usage:
	 * $query->setLimit(100, 0); (retrieve 100 rows, starting at first record)
	 * $query->setLimit(50, 50); (retrieve 50 rows, starting at 50th record)
	 *
	 * @param   integer  $limit   The limit for the result set
	 * @param   integer  $offset  The offset for the result set
	 *
	 * @return  Sqlite  Returns this object to allow chaining.
	 *
	 * @since   1.0
	 */
	public function setLimit($limit = 0, $offset = 0)
	{
		$this->limit  = (int) $limit;
		$this->offset = (int) $offset;

		return $this;
	}
}
PK�!]��7
7
(BackupEngine/Driver/Query/Preparable.phpnu&1i�<?php
/**
 * Akeeba Engine
 *
 * @package   akeebaengine
 * @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License version 3, or later
 *
 * 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, version 3.
 *
 * 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
 * <https://www.gnu.org/licenses/>.
 */

namespace Akeeba\Engine\Driver\Query;

defined('AKEEBAENGINE') || die();

use PDO;

/**
 * Database Query Preparable Interface.
 *
 * Adds bind/unbind methods as well as a getBounded() method
 * to retrieve the stored bounded variables on demand prior to
 * query execution.
 *
 * @since  1.0
 *
 * @codeCoverageIgnore
 */
interface Preparable
{
	/**
	 * Method to add a variable to an internal array that will be bound to a prepared SQL statement before query execution. Also
	 * removes a variable that has been bounded from the internal bounded array when the passed in value is null.
	 *
	 * @param   string|integer   $key            The key that will be used in your SQL query to reference the value. Usually of
	 *                                           the form ':key', but can also be an integer.
	 * @param   mixed           &$value          The value that will be bound. The value is passed by reference to support output
	 *                                           parameters such as those possible with stored procedures.
	 * @param   integer          $dataType       Constant corresponding to a SQL datatype.
	 * @param   integer          $length         The length of the variable. Usually required for OUTPUT parameters.
	 * @param   array            $driverOptions  Optional driver options to be used.
	 *
	 * @return  Preparable
	 *
	 * @since   1.0
	 */
	public function bind($key = null, &$value = null, $dataType = PDO::PARAM_STR, $length = 0, $driverOptions = []);

	/**
	 * Retrieves the bound parameters array when key is null and returns it by reference. If a key is provided then that item is
	 * returned.
	 *
	 * @param   mixed  $key  The bounded variable key to retrieve.
	 *
	 * @return  mixed
	 *
	 * @since   1.0
	 */
	public function &getBounded($key = null);
}
PK�!]�b�\�\�BackupEngine/Driver/Base.phpnu&1i�<?php
/**
 * Akeeba Engine
 *
 * @package   akeebaengine
 * @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License version 3, or later
 *
 * 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, version 3.
 *
 * 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
 * <https://www.gnu.org/licenses/>.
 */

namespace Akeeba\Engine\Driver;

defined('AKEEBAENGINE') || die();

use Akeeba\Engine\Driver\Query\Base as QueryBase;
use RuntimeException;

/**
 * Database driver superclass. Used as the base of all Akeeba Engine database drivers.
 * Strongly based on Joomla Platform's JDatabase class.
 *
 * @method qn(string $name, string $as = null)  Alias for quoteName
 * @method q(string $text, bool $escape = true)  Alias for quote
 */
#[\AllowDynamicProperties]
abstract class Base
{
	/** @var    string  The minimum supported database version. */
	protected static $dbMinimum;

	/** @var    array  JDatabaseDriver instances container. */
	protected static $instances = [];

	/** @var string The name of the database driver. */
	public $name;

	/** @var string The name of the database. */
	protected $_database;

	/** @var resource The db connection resource */
	protected $connection = '';

	/** @var    integer  The number of SQL statements executed by the database driver. */
	protected $count = 0;

	/** @var resource The database connection cursor from the last query. */
	protected $cursor;

	/** @var    boolean  The database driver debugging state. */
	protected $debug = false;

	/** @var string Driver type. This should always be mysql as we don't support anything else anymore. */
	protected $driverType = '';

	/** @var string The db server's error string */
	protected $errorMsg = '';

	/** @var int The db server's error number */
	protected $errorNum = 0;

	/** @var int Query's limit */
	protected $limit = 0;

	/** @var    array  The log of executed SQL statements by the database driver. */
	protected $log = [];

	/** @var string Quote for named objects */
	protected $nameQuote = '';

	/** @var string  The null or zero representation of a timestamp for the database driver. */
	protected $nullDate;

	/** @var int Query's offset */
	protected $offset = 0;

	/** @var    array  Passed in upon instantiation and saved. */
	protected $options;

	/** @var mixed The SQL query string */
	protected $sql = '';

	/** @var string The prefix used in the database, if any */
	protected $tablePrefix = '';

	/** @var bool Support for UTF-8 */
	protected $utf = true;

	/**
	 * Database object constructor
	 *
	 * @param   array  $options  List of options used to configure the connection
	 */
	public function __construct($options)
	{
		$prefix     = array_key_exists('prefix', $options) ? $options['prefix'] : '';
		$database   = array_key_exists('database', $options) ? $options['database'] : '';
		$connection = array_key_exists('connection', $options) ? $options['connection'] : null;

		$this->tablePrefix = $prefix;
		$this->_database   = $database;
		$this->connection  = $connection;
		$this->errorNum    = 0;
		$this->count       = 0;
		$this->log         = [];
		$this->options     = $options;
	}

	/**
	 * Is this driver class supported on this server? Child classes are supposed to override this and perform a
	 * compatibility check.
	 *
	 * @return  bool  True if the driver class is supported on the server
	 */
	public static function isSupported()
	{
		return false;
	}

	/**
	 * Splits a string of multiple queries into an array of individual queries.
	 *
	 * @param   string  $query  Input SQL string with which to split into individual queries.
	 *
	 * @return  array  The queries from the input string separated into an array.
	 */
	public static function splitSql($query)
	{
		$start   = 0;
		$open    = false;
		$char    = '';
		$end     = strlen($query);
		$queries = [];

		for ($i = 0; $i < $end; $i++)
		{
			$current = substr($query, $i, 1);
			if (($current == '"' || $current == '\''))
			{
				$n = 2;

				while (substr($query, $i - $n + 1, 1) == '\\' && $n < $i)
				{
					$n++;
				}

				if ($n % 2 == 0)
				{
					if ($open)
					{
						if ($current == $char)
						{
							$open = false;
							$char = '';
						}
					}
					else
					{
						$open = true;
						$char = $current;
					}
				}
			}

			if (($current == ';' && !$open) || $i == $end - 1)
			{
				$queries[] = substr($query, $start, ($i - $start + 1));
				$start     = $i + 1;
			}
		}

		return $queries;
	}

	/**
	 * Is this driver supported under the current system configuration?
	 *
	 * @return bool
	 */
	public static function test()
	{
		return self::isSupported();
	}

	/**
	 * Magic method to provide method alias support for quote() and quoteName().
	 *
	 * @param   string  $method  The called method.
	 * @param   array   $args    The array of arguments passed to the method.
	 *
	 * @return  string  The aliased method's return value or null.
	 */
	public function __call($method, $args)
	{
		if (empty($args))
		{
			return null;
		}

		switch ($method)
		{
			case 'q':
				return $this->quote($args[0], $args[1] ?? true);
				break;
			case 'nq':
			case 'qn':
				return $this->quoteName($args[0]);
				break;
		}

		return null;
	}

	/**
	 * Database object destructor
	 *
	 * @return bool
	 */
	public function __destruct()
	{
		return $this->close();
	}

	public function __wakeup()
	{
		$this->open();
	}

	/**
	 * By default, when the object is shutting down, the connection is closed
	 */
	public function _onSerialize()
	{
		$this->close();
	}

	/**
	 * Alter database's character set, obtaining query string from protected member.
	 *
	 * @param   string  $dbName  The database name that will be altered
	 *
	 * @return  string  The query that alter the database query string
	 *
	 * @throws  RuntimeException
	 */
	public function alterDbCharacterSet($dbName)
	{
		if (is_null($dbName))
		{
			throw new RuntimeException('Database name must not be null.');
		}

		$this->setQuery($this->getAlterDbCharacterSet($dbName));

		return $this->execute();
	}

	/**
	 * Closes the database connection
	 */
	abstract public function close();

	/**
	 * Determines if the connection to the server is active.
	 *
	 * @return  boolean  True if connected to the database engine.
	 */
	abstract public function connected();

	/**
	 * Create a new database using information from $options object, obtaining query string
	 * from protected member.
	 *
	 * @param   object   $options         Object used to pass user and database name to database driver.
	 *                                    This object must have "db_name" and "db_user" set.
	 * @param   boolean  $utf             True if the database supports the UTF-8 character set.
	 *
	 * @return  string  The query that creates database
	 *
	 * @throws  RuntimeException
	 */
	public function createDatabase($options, $utf = true)
	{
		if (is_null($options))
		{
			throw new RuntimeException('$options object must not be null.');
		}
		elseif (empty($options->db_name))
		{
			throw new RuntimeException('$options object must have db_name set.');
		}
		elseif (empty($options->db_user))
		{
			throw new RuntimeException('$options object must have db_user set.');
		}

		$this->setQuery($this->getCreateDatabaseQuery($options, $utf));

		return $this->execute();
	}

	/**
	 * Drops a table from the database.
	 *
	 * @param   string   $table     The name of the database table to drop.
	 * @param   boolean  $ifExists  Optionally specify that the table must exist before it is dropped.
	 *
	 * @return  Base  Returns this object to support chaining.
	 */
	public abstract function dropTable($table, $ifExists = true);

	/**
	 * Method to escape a string for usage in an SQL statement.
	 *
	 * @param   string   $text   The string to be escaped.
	 * @param   boolean  $extra  Optional parameter to provide extra escaping.
	 *
	 * @return  string   The escaped string.
	 */
	abstract public function escape($text, $extra = false);

	/**
	 * An alias for query()
	 *
	 * @return  mixed  A database cursor resource on success, boolean false on failure.
	 */
	public function execute()
	{
		return $this->query();
	}

	/**
	 * Method to fetch a row from the result set cursor as an associative array.
	 *
	 * @param   mixed  $cursor  The optional result set cursor from which to fetch the row.
	 *
	 * @return  mixed  Either the next row from the result set or false if there are no more rows.
	 */
	abstract public function fetchAssoc($cursor = null);

	/**
	 * Method to free up the memory used for the result set.
	 *
	 * @param   mixed  $cursor  The optional result set cursor from which to fetch the row.
	 *
	 * @return  void
	 */
	abstract public function freeResult($cursor = null);

	/**
	 * Returns the abstracted name of a database object
	 *
	 * @param   string  $tableName
	 *
	 * @return  string
	 */
	public function getAbstract($tableName)
	{
		$prefix = $this->getPrefix();

		// Don't return abstract names for non-CMS tables
		if (is_null($prefix))
		{
			return $tableName;
		}

		switch ($prefix)
		{
			case '':
				// This is more of a hack; it assumes all tables are CMS tables if the prefix is empty.
				return '#__' . $tableName;
				break;

			default:
				// Normal behaviour for 99% of sites
				$tableAbstract = $tableName;
				if (!empty($prefix))
				{
					if (substr($tableName, 0, strlen($prefix)) == $prefix)
					{
						$tableAbstract = '#__' . substr($tableName, strlen($prefix));
					}
					else
					{
						$tableAbstract = $tableName;
					}
				}

				return $tableAbstract;
				break;
		}
	}

	/**
	 * Get the number of affected rows for the previous executed SQL statement.
	 *
	 * @return  integer  The number of affected rows.
	 */
	abstract public function getAffectedRows();

	/**
	 * Method to get the database collation in use by sampling a text field of a table in the database.
	 *
	 * @return  mixed  The collation in use by the database or boolean false if not supported.
	 */
	abstract public function getCollation();

	/**
	 * Method that provides access to the underlying database connection.
	 *
	 * @return  resource  The underlying database connection resource.
	 */
	public function getConnection()
	{
		return $this->connection;
	}

	/**
	 * Inherits the connection of another database driver. Useful for cloning
	 * the CMS database connection into an Akeeba Engine database driver.
	 *
	 * @param   resource  $connection
	 */
	public function setConnection($connection)
	{
		$this->connection = $connection;
	}

	/**
	 * Get the total number of SQL statements executed by the database driver.
	 *
	 * @return  integer
	 *
	 * @since   11.1
	 */
	public function getCount()
	{
		return $this->count;
	}

	/**
	 * Returns a PHP date() function compliant date format for the database driver.
	 *
	 * @return  string  The format string.
	 */
	public function getDateFormat()
	{
		return 'Y-m-d H:i:s';
	}

	/**
	 * Return the database driver type, e.g. "mysql" for all drivers which can talk to MySQL
	 *
	 * @return string
	 */
	public function getDriverType()
	{
		return $this->driverType;
	}

	/**
	 * Get the error message
	 *
	 * @return string The error message for the most recent query
	 */
	public function getErrorMsg($escaped = false)
	{
		if ($escaped)
		{
			return addslashes($this->errorMsg);
		}
		else
		{
			return $this->errorMsg;
		}
	}

	/**
	 * Get the error number
	 *
	 * @return int The error number for the most recent query
	 */
	public function getErrorNum()
	{
		return $this->errorNum;
	}

	/**
	 * Method to escape a string for usage in an SQL statement.
	 *
	 * @param   string   $text   The string to be escaped.
	 * @param   boolean  $extra  Optional parameter to provide extra escaping.
	 *
	 * @return  string  The escaped string.
	 */
	public function getEscaped($text, $extra = false)
	{
		return $this->escape($text, $extra);
	}

	/**
	 * Get the database driver SQL statement log.
	 *
	 * @return  array  SQL statements executed by the database driver.
	 *
	 * @since   11.1
	 */
	public function getLog()
	{
		return $this->log;
	}

	/**
	 * Get the minimum supported database version.
	 *
	 * @return  string  The minimum version number for the database driver.
	 *
	 * @since   12.1
	 */
	public function getMinimum()
	{
		return static::$dbMinimum;
	}

	/**
	 * Get the null or zero representation of a timestamp for the database driver.
	 *
	 * @return  string  Null or zero representation of a timestamp.
	 */
	public function getNullDate()
	{
		return $this->nullDate;
	}

	/**
	 * Get the number of returned rows for the previous executed SQL statement.
	 *
	 * @param   resource  $cursor  An optional database cursor resource to extract the row count from.
	 *
	 * @return  integer   The number of returned rows.
	 */
	abstract public function getNumRows($cursor = null);

	/**
	 * Get the database table prefix
	 *
	 * @return string The database prefix
	 */
	public function getPrefix()
	{
		return $this->tablePrefix;
	}

	/**
	 * Get the current query object or a new QueryBase object.
	 *
	 * @param   boolean  $new  False to return the current query object, True to return a new QueryBase object.
	 *
	 * @return  QueryBase  The current query object or a new object extending the QueryBase class.
	 */
	abstract public function getQuery($new = false);

	/**
	 * Create a new QueryBase object.
	 *
	 * @return  QueryBase  The current query object or a new object extending the QueryBase class.
	 */
	abstract public function createQuery();

	/**
	 * Retrieves field information about the given tables.
	 *
	 * @param   string   $table     The name of the database table.
	 * @param   boolean  $typeOnly  True (default) to only return field types.
	 *
	 * @return  array  An array of fields by table.
	 */
	abstract public function getTableColumns($table, $typeOnly = true);

	/**
	 * Shows the table CREATE statement that creates the given tables.
	 *
	 * @param   mixed  $tables  A table name or a list of table names.
	 *
	 * @return  array  A list of the create SQL for the tables.
	 */
	abstract public function getTableCreate($tables);

	/**
	 * Retrieves field information about the given tables.
	 *
	 * @param   mixed    $tables    A table name or a list of table names.
	 * @param   boolean  $typeOnly  True to only return field types.
	 *
	 * @return  array  An array of fields by table.
	 */
	public function getTableFields($tables, $typeOnly = true)
	{
		$results = [];

		$tables = (array) $tables;

		foreach ($tables as $table)
		{
			$results[$table] = $this->getTableColumns($table, $typeOnly);
		}

		return $results;
	}

	/**
	 * Retrieves field information about the given tables.
	 *
	 * @param   mixed  $tables  A table name or a list of table names.
	 *
	 * @return  array  An array of keys for the table(s).
	 */
	abstract public function getTableKeys($tables);

	/**
	 * Method to get an array of all tables in the database.
	 *
	 * @return  array  An array of all the tables in the database.
	 */
	abstract public function getTableList();

	/**
	 * Returns an array with the names of tables, views, procedures, functions and triggers
	 * in the database. The table names are the keys of the tables, whereas the value is
	 * the type of each element: table, view, merge, temp, procedure, function or trigger.
	 * Note that merge are MRG_MYISAM tables and temp is non-permanent data table, usually
	 * set up as temporary, black hole or federated tables. These two types should never,
	 * ever, have their data dumped in the SQL dump file.
	 *
	 * @param   bool  $abstract  Return abstract or normal names? Defaults to true (abstract names)
	 *
	 * @return  array
	 */
	abstract public function getTables($abstract = true);

	/**
	 * Determine whether or not the database engine supports UTF-8 character encoding.
	 *
	 * @return  boolean  True if the database engine supports UTF-8 character encoding.
	 */
	public function getUTFSupport()
	{
		return $this->utf;
	}

	/**
	 * Get the version of the database connector
	 *
	 * @return  string  The database connector version.
	 */
	abstract public function getVersion();

	/**
	 * Determines if the database engine supports UTF-8 character encoding.
	 *
	 * @return  boolean  True if supported.
	 */
	public function hasUTF()
	{
		return $this->utf;
	}

	/**
	 * Determine whether or not the database engine supports UTF-8 character encoding.
	 *
	 * @return  boolean  True if the database engine supports UTF-8 character encoding.
	 */
	public function hasUTFSupport()
	{
		return $this->utf;
	}

	/**
	 * Inserts a row into a table based on an object's properties.
	 *
	 * @param   string  $table   The name of the database table to insert into.
	 * @param   object &$object  A reference to an object whose public properties match the table fields.
	 * @param   string  $key     The name of the primary key. If provided the object property is updated.
	 *
	 * @return  boolean    True on success.
	 */
	public function insertObject($table, &$object, $key = null)
	{
		$fields = [];
		$values = [];

		// Iterate over the object variables to build the query fields and values.
		foreach (get_object_vars($object) as $k => $v)
		{
			// Only process non-null scalars.
			if (is_array($v) || is_object($v) || ($v === null))
			{
				continue;
			}

			// Ignore any internal fields.
			if ($k[0] == '_')
			{
				continue;
			}

			// Prepare and sanitize the fields and values for the database query.
			$fields[] = $this->quoteName($k);
			$values[] = $this->quote($v);
		}

		// Create the base insert statement.
		$query = $this->getQuery(true)
			->insert($this->quoteName($table))
			->columns($fields)
			->values(implode(',', $values));

		// Set the query and execute the insert.
		$this->setQuery($query);
		if (!$this->execute())
		{
			return false;
		}

		// Update the primary key if it exists.
		$id = $this->insertid();
		if ($key && $id && is_string($key))
		{
			$object->$key = $id;
		}

		return true;
	}

	/**
	 * Method to get the auto-incremented value from the last INSERT statement.
	 *
	 * @return  integer  The value of the auto-increment field from the last inserted row.
	 */
	abstract public function insertid();

	/**
	 * Method to check whether the installed database version is supported by the database driver
	 *
	 * @return  boolean  True if the database version is supported
	 *
	 * @since   12.1
	 */
	public function isMinimumVersion()
	{
		return version_compare($this->getVersion(), static::$dbMinimum) >= 0;
	}

	/**
	 * Method to get the first row of the result set from the database query as an associative array
	 * of ['field_name' => 'row_value'].
	 *
	 * @return  mixed  The return value or null if the query failed.
	 */
	public function loadAssoc()
	{
		$ret = null;

		// Execute the query and get the result set cursor.
		if (!($cursor = $this->execute()))
		{
			return null;
		}

		// Get the first row from the result set as an associative array.
		if ($array = $this->fetchAssoc($cursor))
		{
			$ret = $array;
		}

		// Free up system resources and return.
		$this->freeResult($cursor);

		return $ret;
	}

	/**
	 * Method to get an array of the result set rows from the database query where each row is an associative array
	 * of ['field_name' => 'row_value'].  The array of rows can optionally be keyed by a field name, but defaults to
	 * a sequential numeric array.
	 *
	 * NOTE: Chosing to key the result array by a non-unique field name can result in unwanted
	 * behavior and should be avoided.
	 *
	 * @param   string  $key     The name of a field on which to key the result array.
	 * @param   string  $column  An optional column name. Instead of the whole row, only this column value will be in
	 *                           the result array.
	 *
	 * @return  mixed   The return value or null if the query failed.
	 */
	public function loadAssocList($key = null, $column = null)
	{
		$array = [];

		// Execute the query and get the result set cursor.
		if (!($cursor = $this->execute()))
		{
			return null;
		}

		// Get all of the rows from the result set.
		while ($row = $this->fetchAssoc($cursor))
		{
			$value = ($column) ? ($row[$column] ?? $row) : $row;
			if ($key)
			{
				$array[$row[$key]] = $value;
			}
			else
			{
				$array[] = $value;
			}
		}

		// Free up system resources and return.
		$this->freeResult($cursor);

		return $array;
	}

	/**
	 * Method to get an array of values from the <var>$offset</var> field in each row of the result set from
	 * the database query.
	 *
	 * @param   integer  $offset  The row offset to use to build the result array.
	 *
	 * @return  mixed    The return value or null if the query failed.
	 */
	public function loadColumn($offset = 0)
	{
		$array = [];

		// Execute the query and get the result set cursor.
		if (!($cursor = $this->execute()))
		{
			return null;
		}

		// Get all of the rows from the result set as arrays.
		while ($row = $this->fetchArray($cursor))
		{
			$array[] = $row[$offset];
		}

		// Free up system resources and return.
		$this->freeResult($cursor);

		return $array;
	}

	/**
	 * Method to get the next row in the result set from the database query as an object.
	 *
	 * @param   string  $class  The class name to use for the returned row object.
	 *
	 * @return  mixed   The result of the query as an array, false if there are no more rows.
	 */
	public function loadNextObject($class = 'stdClass')
	{
		// Execute the query and get the result set cursor.
		if (is_null($this->cursor))
		{
			if (!($this->cursor = $this->execute()))
			{
				return $this->errorNum ? null : false;
			}
		}

		// Get the next row from the result set as an object of type $class.
		if ($row = $this->fetchObject($this->cursor, $class))
		{
			return $row;
		}

		// Free up system resources and return.
		$this->freeResult($this->cursor);
		$this->cursor = null;

		return false;
	}

	/**
	 * Method to get the next row in the result set from the database query as an array.
	 *
	 * @return  mixed  The result of the query as an array, false if there are no more rows.
	 */
	public function loadNextRow()
	{
		// Execute the query and get the result set cursor.
		if (is_null($this->cursor))
		{
			if (!($this->cursor = $this->execute()))
			{
				return $this->errorNum ? null : false;
			}
		}

		// Get the next row from the result set as an object of type $class.
		if ($row = $this->fetchArray($this->cursor))
		{
			return $row;
		}

		// Free up system resources and return.
		$this->freeResult($this->cursor);
		$this->cursor = null;

		return false;
	}

	/**
	 * Method to get the first row of the result set from the database query as an object.
	 *
	 * @param   string  $class  The class name to use for the returned row object.
	 *
	 * @return  mixed   The return value or null if the query failed.
	 */
	public function loadObject($class = 'stdClass')
	{
		$ret = null;

		// Execute the query and get the result set cursor.
		if (!($cursor = $this->execute()))
		{
			return null;
		}

		// Get the first row from the result set as an object of type $class.
		if ($object = $this->fetchObject($cursor, $class))
		{
			$ret = $object;
		}

		// Free up system resources and return.
		$this->freeResult($cursor);

		return $ret;
	}

	/**
	 * Method to get an array of the result set rows from the database query where each row is an object.  The array
	 * of objects can optionally be keyed by a field name, but defaults to a sequential numeric array.
	 *
	 * NOTE: Choosing to key the result array by a non-unique field name can result in unwanted
	 * behavior and should be avoided.
	 *
	 * @param   string  $key    The name of a field on which to key the result array.
	 * @param   string  $class  The class name to use for the returned row objects.
	 *
	 * @return  mixed   The return value or null if the query failed.
	 */
	public function loadObjectList($key = '', $class = 'stdClass')
	{
		$array = [];

		// Execute the query and get the result set cursor.
		if (!($cursor = $this->execute()))
		{
			return null;
		}

		// Get all of the rows from the result set as objects of type $class.
		while ($row = $this->fetchObject($cursor, $class))
		{
			if ($key)
			{
				$array[$row->$key] = $row;
			}
			else
			{
				$array[] = $row;
			}
		}

		// Free up system resources and return.
		$this->freeResult($cursor);

		return $array;
	}

	/**
	 * Method to get the first field of the first row of the result set from the database query.
	 *
	 * @return  mixed  The return value or null if the query failed.
	 */
	public function loadResult()
	{
		$ret = null;

		// Execute the query and get the result set cursor.
		if (!($cursor = $this->execute()))
		{
			return null;
		}

		// Get the first row from the result set as an array.
		if ($row = $this->fetchArray($cursor))
		{
			$ret = $row[0];
		}

		// Free up system resources and return.
		$this->freeResult($cursor);

		return $ret;
	}

	/**
	 * Method to get an array of values from the <var>$offset</var> field in each row of the result set from
	 * the database query.
	 *
	 * @param   integer  $offset  The row offset to use to build the result array.
	 *
	 * @return  mixed    The return value or null if the query failed.
	 */
	public function loadResultArray($offset = 0)
	{
		return $this->loadColumn($offset);
	}

	/**
	 * Method to get the first row of the result set from the database query as an array.  Columns are indexed
	 * numerically so the first column in the result set would be accessible via <var>$row[0]</var>, etc.
	 *
	 * @return  mixed  The return value or null if the query failed.
	 */
	public function loadRow()
	{
		$ret = null;

		// Execute the query and get the result set cursor.
		if (!($cursor = $this->execute()))
		{
			return null;
		}

		// Get the first row from the result set as an array.
		if ($row = $this->fetchArray($cursor))
		{
			$ret = $row;
		}

		// Free up system resources and return.
		$this->freeResult($cursor);

		return $ret;
	}

	/**
	 * Method to get an array of the result set rows from the database query where each row is an array.  The array
	 * of objects can optionally be keyed by a field offset, but defaults to a sequential numeric array.
	 *
	 * NOTE: Choosing to key the result array by a non-unique field can result in unwanted
	 * behavior and should be avoided.
	 *
	 * @param   string  $key  The name of a field on which to key the result array.
	 *
	 * @return  mixed   The return value or null if the query failed.
	 */
	public function loadRowList($key = null)
	{
		$array = [];

		// Execute the query and get the result set cursor.
		if (!($cursor = $this->execute()))
		{
			return null;
		}

		// Get all of the rows from the result set as arrays.
		while ($row = $this->fetchArray($cursor))
		{
			if ($key !== null)
			{
				$array[$row[$key]] = $row;
			}
			else
			{
				$array[] = $row;
			}
		}

		// Free up system resources and return.
		$this->freeResult($cursor);

		return $array;
	}

	/**
	 * Locks a table in the database.
	 *
	 * @param   string  $tableNameName  The name of the table to unlock.
	 *
	 * @return  Base  Returns this object to support chaining.
	 */
	public abstract function lockTable($tableNameName);

	/**
	 * Wrap an SQL statement identifier name such as column, table or database names in quotes to prevent injection
	 * risks and reserved word conflicts.
	 *
	 * @param   string  $name  The identifier name to wrap in quotes.
	 *
	 * @return  string  The quote wrapped name.
	 */
	public function nameQuote($name)
	{
		return $this->quoteName($name);
	}

	/**
	 * Opens a database connection. It MUST be overriden by children classes
	 *
	 * @return Base
	 */
	public function open()
	{
		// Don't try to reconnect if we're already connected
		if (is_resource($this->connection) && !is_null($this->connection))
		{
			return $this;
		}

		// Determine utf-8 support
		$this->utf = $this->hasUTF();

		// Set charactersets (needed for MySQL 4.1.2+)
		if ($this->utf)
		{
			$this->setUTF();
		}

		// Select the current database
		$this->select($this->_database);

		return $this;
	}

	/**
	 * Execute the SQL statement.
	 *
	 * @return  mixed  A database cursor resource on success, boolean false on failure.
	 */
	abstract public function query();

	/**
	 * Method to quote and optionally escape a string to database requirements for insertion into the database.
	 *
	 * @param   string   $text    The string to quote.
	 * @param   boolean  $escape  True (default) to escape the string, false to leave it unchanged.
	 */
	public function quote($text, $escape = true)
	{
		return '\'' . ($escape ? $this->escape($text) : $text) . '\'';
	}

	/**
	 * Wrap an SQL statement identifier name such as column, table or database names in quotes to prevent injection
	 * risks and reserved word conflicts.
	 *
	 * @param   mixed  $name      The identifier name to wrap in quotes, or an array of identifier names to wrap in
	 *                            quotes. Each type supports dot-notation name.
	 * @param   mixed  $as        The AS query part associated to $name. It can be string or array, in latter case it
	 *                            has to be same length of $name; if is null there will not be any AS part for string
	 *                            or array element.
	 */
	public function quoteName($name, $as = null)
	{
		if (!is_array($name))
		{
			$quotedName = $this->quoteNameStr(explode('.', $name));

			$quotedAs = '';
			if (!is_null($as))
			{
				$as       = (array) $as;
				$quotedAs .= ' AS ' . $this->quoteNameStr($as);
			}

			return $quotedName . $quotedAs;
		}
		else
		{
			$fin = [];

			if (is_null($as))
			{
				foreach ($name as $str)
				{
					$fin[] = $this->quoteName($str);
				}
			}
			elseif (is_array($name) && (count($name) == (is_array($as) || $as instanceof \Countable ? count($as) : 0)))
			{
				$count = count($name);
				for ($i = 0; $i < $count; $i++)
				{
					$fin[] = $this->quoteName($name[$i], $as[$i]);
				}
			}

			return $fin;
		}
	}

	/**
	 * Renames a table in the database.
	 *
	 * @param   string  $oldTable  The name of the table to be renamed
	 * @param   string  $newTable  The new name for the table.
	 * @param   string  $backup    Table prefix
	 * @param   string  $prefix    For the table - used to rename constraints in non-mysql databases
	 *
	 * @return  Base  Returns this object to support chaining.
	 */
	public abstract function renameTable($oldTable, $newTable, $backup = null, $prefix = null);

	/**
	 * This function replaces a string identifier <var>$prefix</var> with the string held is the
	 * <var>tablePrefix</var> class variable.
	 *
	 * @param   string  $query   The SQL statement to prepare.
	 * @param   string  $prefix  The common table prefix.
	 *
	 * @return  string  The processed SQL statement.
	 */
	public function replacePrefix($query, $prefix = '#__')
	{
		$escaped   = false;
		$startPos  = 0;
		$quoteChar = '';
		$literal   = '';

		$query = trim($query);
		$n     = strlen($query);

		while ($startPos < $n)
		{
			$ip = strpos($query, $prefix, $startPos);
			if ($ip === false)
			{
				break;
			}

			$j = strpos($query, "'", $startPos);
			$k = strpos($query, '"', $startPos);
			if (($k !== false) && (($k < $j) || ($j === false)))
			{
				$quoteChar = '"';
				$j         = $k;
			}
			else
			{
				$quoteChar = "'";
			}

			if ($j === false)
			{
				$j = $n;
			}

			$literal  .= str_replace($prefix, $this->tablePrefix, substr($query, $startPos, $j - $startPos));
			$startPos = $j;

			$j = $startPos + 1;

			if ($j >= $n)
			{
				break;
			}

			// Quote comes first, find end of quote
			while (true)
			{
				$k       = strpos($query, $quoteChar, $j);
				$escaped = false;
				if ($k === false)
				{
					break;
				}
				$l = $k - 1;
				while ($l >= 0 && $query[$l] == '\\')
				{
					$l--;
					$escaped = !$escaped;
				}
				if ($escaped)
				{
					$j = $k + 1;
					continue;
				}
				break;
			}
			if ($k === false)
			{
				// Error in the query - no end quote; ignore it
				break;
			}
			$literal  .= substr($query, $startPos, $k - $startPos + 1);
			$startPos = $k + 1;
		}
		if ($startPos < $n)
		{
			$literal .= substr($query, $startPos, $n - $startPos);
		}

		return $literal;
	}

	/**
	 * Resets the error condition in the driver. Useful to reset the error state after handling a thrown exception.
	 *
	 * @return $this for chaining
	 */
	public function resetErrors()
	{
		$this->errorNum = 0;
		$this->errorMsg = '';

		return $this;
	}

	/**
	 * Select a database for use.
	 *
	 * @param   string  $database  The name of the database to select for use.
	 *
	 * @return  boolean  True if the database was successfully selected.
	 */
	abstract public function select($database);

	/**
	 * Sets the database debugging state for the driver.
	 *
	 * @param   boolean  $level  True to enable debugging.
	 *
	 * @return  boolean  The old debugging level.
	 */
	public function setDebug($level)
	{
		$previous    = $this->debug;
		$this->debug = (bool) $level;

		return $previous;
	}

	/**
	 * Sets the SQL statement string for later execution.
	 *
	 * @param   mixed    $query   The SQL statement to set either as a QueryBase object or a string.
	 * @param   integer  $offset  The affected row offset to set.
	 * @param   integer  $limit   The maximum affected rows to set.
	 *
	 * @return  self  This object to support method chaining.
	 */
	public function setQuery($query, $offset = 0, $limit = 0)
	{
		$this->sql    = $query;
		$this->limit  = (int) $limit;
		$this->offset = (int) $offset;

		return $this;
	}

	/**
	 * Set the connection to use UTF-8 character encoding.
	 *
	 * @return  boolean  True on success.
	 */
	abstract public function setUTF();

	/**
	 * Method to commit a transaction.
	 *
	 * @return  void
	 */
	abstract public function transactionCommit();

	/**
	 * Method to roll back a transaction.
	 *
	 * @return  void
	 */
	abstract public function transactionRollback();

	/**
	 * Method to initialize a transaction.
	 *
	 * @return  void
	 */
	abstract public function transactionStart();

	/**
	 * Method to truncate a table.
	 *
	 * @param   string  $table  The table to truncate
	 *
	 * @return  void
	 */
	public function truncateTable($table)
	{
		$this->setQuery('TRUNCATE TABLE ' . $this->quoteName($table));
		$this->query();
	}

	/**
	 * Unlocks tables in the database.
	 *
	 * @return  Base  Returns this object to support chaining.
	 */
	public abstract function unlockTables();

	/**
	 * Updates a row in a table based on an object's properties.
	 *
	 * @param   string   $table   The name of the database table to update.
	 * @param   object  &$object  A reference to an object whose public properties match the table fields.
	 * @param   string   $key     The name of the primary key.
	 * @param   boolean  $nulls   True to update null fields or false to ignore them.
	 *
	 * @return  boolean  True on success.
	 */
	public function updateObject($table, &$object, $key, $nulls = false)
	{
		$fields = [];
		$where  = [];

		if (is_string($key))
		{
			$key = [$key];
		}

		if (is_object($key))
		{
			$key = (array) $key;
		}

		// Create the base update statement.
		$statement = 'UPDATE ' . $this->quoteName($table) . ' SET %s WHERE %s';

		// Iterate over the object variables to build the query fields/value pairs.
		foreach (get_object_vars($object) as $k => $v)
		{
			// Only process scalars that are not internal fields.
			if (is_array($v) || is_object($v) || ($k[0] == '_'))
			{
				continue;
			}

			// Set the primary key to the WHERE clause instead of a field to update.
			if (in_array($k, $key))
			{
				$where[] = $this->quoteName($k) . '=' . $this->quote($v);
				continue;
			}

			// Prepare and sanitize the fields and values for the database query.
			if ($v === null)
			{
				// If the value is null and we want to update nulls then set it.
				if ($nulls)
				{
					$val = 'NULL';
				}
				// If the value is null and we do not want to update nulls then ignore this field.
				else
				{
					continue;
				}
			}
			// The field is not null so we prep it for update.
			else
			{
				$val = $this->quote($v);
			}

			// Add the field to be updated.
			$fields[] = $this->quoteName($k) . '=' . $val;
		}

		// We don't have any fields to update.
		if (empty($fields))
		{
			return true;
		}

		// Set the query and execute the update.
		$this->setQuery(sprintf($statement, implode(",", $fields), implode(' AND ', $where)));

		return $this->execute();
	}

	/**
	 * Method to fetch a row from the result set cursor as an array.
	 *
	 * @param   mixed  $cursor  The optional result set cursor from which to fetch the row.
	 *
	 * @return  mixed  Either the next row from the result set or false if there are no more rows.
	 */
	abstract protected function fetchArray($cursor = null);

	/**
	 * Method to fetch a row from the result set cursor as an object.
	 *
	 * @param   mixed   $cursor  The optional result set cursor from which to fetch the row.
	 * @param   string  $class   The class name to use for the returned row object.
	 *
	 * @return  mixed   Either the next row from the result set or false if there are no more rows.
	 */
	abstract protected function fetchObject($cursor = null, $class = 'stdClass');

	/**
	 * Return the query string to alter the database character set.
	 *
	 * @param   string  $dbName  The database name
	 *
	 * @return  string  The query that alter the database query string
	 */
	protected function getAlterDbCharacterSet($dbName)
	{
		$query = 'ALTER DATABASE ' . $this->quoteName($dbName) . ' CHARACTER SET `utf8`';

		return $query;
	}

	/**
	 * Return the query string to create new Database.
	 * Each database driver, other than MySQL, need to override this member to return correct string.
	 *
	 * @param   object   $options         Object used to pass user and database name to database driver.
	 *                                    This object must have "db_name" and "db_user" set.
	 * @param   boolean  $utf             True if the database supports the UTF-8 character set.
	 *
	 * @return  string  The query that creates database
	 */
	protected function getCreateDatabaseQuery($options, $utf)
	{
		if ($utf)
		{
			$query = 'CREATE DATABASE ' . $this->quoteName($options->db_name) . ' CHARACTER SET `utf8`';
		}
		else
		{
			$query = 'CREATE DATABASE ' . $this->quoteName($options->db_name);
		}

		return $query;
	}

	/**
	 * Gets the name of the database used by this connection.
	 *
	 * @return  string
	 */
	protected function getDatabase()
	{
		return $this->_database;
	}

	/**
	 * Quote strings coming from quoteName call.
	 *
	 * @param   array  $strArr  Array of strings coming from quoteName dot-explosion.
	 *
	 * @return  string  Dot-imploded string of quoted parts.
	 */
	protected function quoteNameStr($strArr)
	{
		$parts = [];
		$q     = $this->nameQuote;

		foreach ($strArr as $part)
		{
			if (is_null($part))
			{
				continue;
			}

			if (strlen($q) == 1)
			{
				$parts[] = $q . $part . $q;
			}
			else
			{
				$parts[] = $q[0] . $part . $q[1];
			}
		}

		return implode('.', $parts);
	}
}
PK�!]�t��I�I BackupEngine/Driver/Pdomysql.phpnu&1i�<?php
/**
 * Akeeba Engine
 *
 * @package   akeebaengine
 * @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License version 3, or later
 *
 * 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, version 3.
 *
 * 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
 * <https://www.gnu.org/licenses/>.
 */

namespace Akeeba\Engine\Driver;

defined('AKEEBAENGINE') || die();

use Akeeba\Engine\Driver\Query\Pdomysql as QueryPdomysql;
use Akeeba\Engine\FixMySQLHostname;
use Exception;
use PDO;
use PDOException;
use PDOStatement;
use ReflectionClass;
use RuntimeException;

/**
 * PDO MySQL database driver for Akeeba Engine
 *
 * Based on Joomla! Platform 12.1
 */
#[\AllowDynamicProperties]
class Pdomysql extends Mysql
{
	use FixMySQLHostname;

	/**
	 * The default cipher suite for TLS connections.
	 *
	 * @var    array
	 */
	protected static $defaultCipherSuite = [
		'AES128-GCM-SHA256',
		'AES256-GCM-SHA384',
		'AES128-CBC-SHA256',
		'AES256-CBC-SHA384',
		'DES-CBC3-SHA',
	];

	/**
	 * The name of the database driver.
	 *
	 * @var    string
	 */
	public $name = 'pdomysql';

	/** @var string Connection character set */
	protected $charset = 'utf8mb4';

	/** @var PDO The db connection resource */
	protected $connection = null;

	/** @var PDOStatement The database connection cursor from the last query. */
	protected $cursor;

	/** @var array Driver options for PDO */
	protected $driverOptions = [];

	protected $ssl = [];

	/** @var bool Are we in the process of reconnecting to the database server? */
	private $isReconnecting = false;

	/**
	 * Database object constructor
	 *
	 * @param   array  $options  List of options used to configure the connection
	 */
	public function __construct($options)
	{
		$this->driverType = 'mysql';

		// Init
		$this->nameQuote = '`';

		$options['ssl'] = $options['ssl'] ?? [];
		$options['ssl'] = is_array($options['ssl']) ? $options['ssl'] : [];

		$options['ssl']['enable']             =
			($options['ssl']['enable'] ?? $options['dbencryption'] ?? false) ?: false;
		$options['ssl']['cipher']             = ($options['ssl']['cipher'] ?? $options['dbsslcipher'] ?? null) ?: null;
		$options['ssl']['ca']                 = ($options['ssl']['ca'] ?? $options['dbsslca'] ?? null) ?: null;
		$options['ssl']['capath']             = ($options['ssl']['capath'] ?? $options['dbsslcapath'] ?? null) ?: null;
		$options['ssl']['key']                = ($options['ssl']['key'] ?? $options['dbsslkey'] ?? null) ?: null;
		$options['ssl']['cert']               = ($options['ssl']['cert'] ?? $options['dbsslcert'] ?? null) ?: null;
		$options['ssl']['verify_server_cert'] =
			($options['ssl']['verify_server_cert'] ?? $options['dbsslverifyservercert'] ?? false) ?: false;

		// Figure out if a port is included in the host name
		$this->fixHostnamePortSocket($options['host'], $options['port'], $options['socket']);

		// Open the connection
		$this->host           = $options['host'] ?? 'localhost';
		$this->user           = $options['user'] ?? '';
		$this->password       = $options['password'] ?? '';
		$this->port           = $options['port'] ?? '';
		$this->socket         = $options['socket'] ?? '';
		$this->_database      = $options['database'] ?? '';
		$this->selectDatabase = $options['select'] ?? true;
		$this->ssl            = $options['ssl'] ?? [];

		$this->charset       = $options['charset'] ?? 'utf8mb4';
		$this->driverOptions = $options['driverOptions'] ?? [];
		$this->tablePrefix   = $options['prefix'] ?? '';
		$this->connection    = $options['connection'] ?? null;
		$this->errorNum      = 0;
		$this->count         = 0;
		$this->log           = [];
		$this->options       = $options;

		if (!is_object($this->connection))
		{
			$this->open();
		}
	}

	/**
	 * Test to see if the MySQL connector is available.
	 *
	 * @return  boolean  True on success, false otherwise.
	 */
	public static function isSupported()
	{
		if (!defined('\PDO::ATTR_DRIVER_NAME'))
		{
			return false;
		}

		return in_array('mysql', PDO::getAvailableDrivers());
	}

	/**
	 * PDO does not support serialize
	 *
	 * @return  array
	 */
	public function __sleep()
	{
		$serializedProperties = [];

		$reflect = new ReflectionClass($this);

		// Get properties of the current class
		$properties = $reflect->getProperties();

		foreach ($properties as $property)
		{
			// Do not serialize properties that are \PDO
			if ($property->isStatic() == false && !($this->{$property->name} instanceof PDO))
			{
				array_push($serializedProperties, $property->name);
			}
		}

		return $serializedProperties;
	}

	/**
	 * Wake up after serialization
	 *
	 * @return  array
	 */
	public function __wakeup()
	{
		// Get connection back
		$this->__construct($this->options);
	}

	public function close()
	{
		$return = false;

		if (is_object($this->cursor))
		{
			try
			{
				$this->cursor->closeCursor();
			}
			catch (\Throwable $e)
			{
			}
		}

		$this->connection = null;

		return $return;
	}

	/**
	 * Determines if the connection to the server is active.
	 *
	 * @return  boolean  True if connected to the database engine.
	 */
	public function connected()
	{
		if (!is_object($this->connection))
		{
			return false;
		}

		try
		{
			/** @var PDOStatement $statement */
			$statement = $this->connection->prepare('SELECT 1');
			$executed  = $statement->execute();
			$ret       = 0;

			if ($executed)
			{
				$row = [0];

				if (!empty($statement) && $statement instanceof PDOStatement)
				{
					$row = $statement->fetch(PDO::FETCH_NUM);
				}

				$ret = $row[0];
			}

			$status = $ret == 1;

			$statement->closeCursor();
			$statement = null;
		}
		// If we catch an exception here, we must not be connected.
		catch (\Throwable $e)
		{
			$status = false;
		}

		return $status;
	}

	/**
	 * Method to escape a string for usage in an SQL statement.
	 *
	 * @param   string   $text   The string to be escaped.
	 * @param   boolean  $extra  Optional parameter to provide extra escaping.
	 *
	 * @return  string  The escaped string.
	 */
	public function escape($text, $extra = false)
	{
		if (is_int($text) || is_float($text))
		{
			return $text;
		}

		if (is_null($text))
		{
			return 'NULL';
		}

		$result = substr($this->connection->quote($text), 1, -1);

		if ($extra)
		{
			$result = addcslashes($result, '%_');
		}

		return $result;
	}

	/**
	 * Method to fetch a row from the result set cursor as an associative array.
	 *
	 * @param   mixed  $cursor  The optional result set cursor from which to fetch the row.
	 *
	 * @return  mixed  Either the next row from the result set or false if there are no more rows.
	 */
	public function fetchAssoc($cursor = null)
	{
		$ret = null;

		if (!empty($cursor) && $cursor instanceof PDOStatement)
		{
			$ret = $cursor->fetch(PDO::FETCH_ASSOC);
		}
		elseif ($this->cursor instanceof PDOStatement)
		{
			$ret = $this->cursor->fetch(PDO::FETCH_ASSOC);
		}

		return $ret;
	}

	/**
	 * Method to free up the memory used for the result set.
	 *
	 * @param   mixed  $cursor  The optional result set cursor from which to fetch the row.
	 *
	 * @return  void
	 */
	public function freeResult($cursor = null)
	{
		if ($cursor instanceof PDOStatement)
		{
			$cursor->closeCursor();
			$cursor = null;
		}

		if ($this->cursor instanceof PDOStatement)
		{
			$this->cursor->closeCursor();
			$this->cursor = null;
		}
	}

	/**
	 * Get the number of affected rows for the previous executed SQL statement.
	 *
	 * @return  integer  The number of affected rows.
	 */
	public function getAffectedRows()
	{
		if ($this->cursor instanceof PDOStatement)
		{
			return $this->cursor->rowCount();
		}

		return 0;
	}

	/**
	 * Get the number of returned rows for the previous executed SQL statement.
	 *
	 * @param   resource  $cursor  An optional database cursor resource to extract the row count from.
	 *
	 * @return  integer   The number of returned rows.
	 */
	public function getNumRows($cursor = null)
	{
		if ($cursor instanceof PDOStatement)
		{
			return $cursor->rowCount();
		}

		if ($this->cursor instanceof PDOStatement)
		{
			return $this->cursor->rowCount();
		}

		return 0;
	}

	/**
	 * Get the current or query, or new JDatabaseQuery object.
	 *
	 * @param   boolean  $new  False to return the last query set, True to return a new JDatabaseQuery object.
	 *
	 * @return  mixed  The current value of the internal SQL variable or a new JDatabaseQuery object.
	 */
	public function getQuery($new = false)
	{
		if ($new)
		{
			return new QueryPdomysql($this);
		}
		else
		{
			return $this->sql;
		}
	}

	public function createQuery()
	{
		return new QueryPdomysql($this);
	}

	/**
	 * Get the version of the database connector.
	 *
	 * @return  string  The database connector version.
	 */
	public function getVersion()
	{
		$version = $this->connection->getAttribute(\PDO::ATTR_SERVER_VERSION);

		if (stripos($version, 'mariadb') !== false)
		{
			// MariaDB: Strip off any leading '5.5.5-', if present
			return preg_replace('/^5\.5\.5-/', '', $version);
		}

		return $version;
	}

	/**
	 * Determines if the database engine supports UTF-8 character encoding.
	 *
	 * @return  boolean  True if supported.
	 */
	public function hasUTF()
	{
		$serverVersion = $this->getVersion();
		$mariadb       = stripos($serverVersion, 'mariadb') !== false;

		// At this point we know the client supports utf8mb4.  Now we must check if the server supports utf8mb4 as well.
		$utf8mb4 = version_compare($serverVersion, '5.5.3', '>=');

		if ($mariadb && version_compare($serverVersion, '10.0.0', '<'))
		{
			$utf8mb4 = false;
		}

		return $utf8mb4;
	}

	/**
	 * Method to get the auto-incremented value from the last INSERT statement.
	 *
	 * @return  integer  The value of the auto-increment field from the last inserted row.
	 */
	public function insertid()
	{
		// Error suppress this to prevent PDO warning us that the driver doesn't support this operation.
		return @$this->connection->lastInsertId();
	}

	/**
	 * Method to get the next row in the result set from the database query as an object.
	 *
	 * @param   string  $class  The class name to use for the returned row object.
	 *
	 * @return  mixed   The result of the query as an array, false if there are no more rows.
	 */
	public function loadNextObject($class = 'stdClass')
	{
		// Execute the query and get the result set cursor.
		if (!$this->cursor)
		{
			if (!($this->execute()))
			{
				return $this->errorNum ? null : false;
			}
		}

		// Get the next row from the result set as an object of type $class.
		if ($row = $this->fetchObject(null, $class))
		{
			return $row;
		}

		// Free up system resources and return.
		$this->freeResult();

		return false;
	}

	/**
	 * Method to get the next row in the result set from the database query as an array.
	 *
	 * @return  mixed  The result of the query as an array, false if there are no more rows.
	 */
	public function loadNextRow()
	{
		// Execute the query and get the result set cursor.
		if (!$this->cursor)
		{
			if (!($this->execute()))
			{
				return $this->errorNum ? null : false;
			}
		}

		// Get the next row from the result set as an object of type $class.
		if ($row = $this->fetchArray())
		{
			return $row;
		}

		// Free up system resources and return.
		$this->freeResult();

		return false;
	}

	public function open()
	{
		if ($this->connected())
		{
			return;
		}
		else
		{
			$this->close();
		}

		if (!isset($this->charset))
		{
			$this->charset = 'utf8mb4';
		}

		$this->port = $this->port ?: 3306;

		$format = 'mysql:host=#HOST#;port=#PORT#;dbname=#DBNAME#;charset=#CHARSET#';

		if ($this->socket)
		{
			$format = 'mysql:socket=#SOCKET#;dbname=#DBNAME#;charset=#CHARSET#';
		}

		$replace = ['#HOST#', '#PORT#', '#SOCKET#', '#DBNAME#', '#CHARSET#'];
		$with    = [$this->host, $this->port, $this->socket, $this->_database, $this->charset];

		// Create the connection string:
		$connectionString = str_replace($replace, $with, $format);

		// For SSL/TLS connection encryption.
		if ($this->ssl !== [] && $this->ssl['enable'] === true)
		{
			$sslContextIsNull = true;

			// If customised, add cipher suite, ca file path, ca path, private key file path and certificate file path to PDO driver options.
			foreach (['cipher', 'ca', 'capath', 'key', 'cert'] as $key => $value)
			{
				if ($this->ssl[$value] !== null)
				{
					$this->driverOptions[constant('\PDO::MYSQL_ATTR_SSL_' . strtoupper($value))] = $this->ssl[$value];

					$sslContextIsNull = false;
				}
			}

			// PDO, if no cipher, ca, capath, cert and key are set, can't start TLS one-way connection, set a common ciphers suite to force it.
			if ($sslContextIsNull === true)
			{
				$this->driverOptions[\PDO::MYSQL_ATTR_SSL_CIPHER] = implode(':', static::$defaultCipherSuite);
			}

			// If customised, for capable systems (PHP 7.0.14+ and 7.1.4+) verify certificate chain and Common Name to driver options.
			if ($this->ssl['verify_server_cert'] !== null && defined('\PDO::MYSQL_ATTR_SSL_VERIFY_SERVER_CERT'))
			{
				$this->driverOptions[\PDO::MYSQL_ATTR_SSL_VERIFY_SERVER_CERT] = $this->ssl['verify_server_cert'];
			}
		}

		// connect to the server
		try
		{
			$this->connection = new PDO(
				$connectionString,
				$this->user,
				$this->password,
				$this->driverOptions
			);
		}
		catch (PDOException $e)
		{
			// If we tried connecting through utf8mb4 and we failed let's retry with regular utf8
			if ($this->charset == 'utf8mb4')
			{
				$this->charset = 'UTF8';
				$this->open();

				return;
			}

			$this->errorNum = 2;
			$this->errorMsg = 'Could not connect to MySQL via PDO: ' . $e->getMessage();

			return;
		}

		// Reset the SQL mode of the connection
		try
		{
			$this->connection->exec("SET @@SESSION.sql_mode = '';");
		}
			// Ignore any exceptions (incompatible MySQL versions)
		catch (Exception $e)
		{
		}

		$this->connection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
		$this->connection->setAttribute(PDO::ATTR_EMULATE_PREPARES, true);

		if ($this->selectDatabase && !empty($this->_database))
		{
			$this->select($this->_database);
		}

		$this->freeResult();
	}

	/**
	 * Execute the SQL statement.
	 *
	 * @return  mixed  A database cursor resource on success, boolean false on failure.
	 */
	public function query()
	{
		if (!is_object($this->connection))
		{
			$this->open();
		}

		$this->freeResult();

		// Take a local copy so that we don't modify the original query and cause issues later
		$query = $this->replacePrefix((string)$this->sql);

		if ($this->limit > 0 || $this->offset > 0)
		{
			$query .= ' LIMIT ' . $this->offset . ', ' . $this->limit;
		}

		// Increment the query counter.
		$this->count++;

		// If debugging is enabled then let's log the query.
		if ($this->debug)
		{
			// Add the query to the object queue.
			$this->log[] = $query;
		}

		// Reset the error values.
		$this->errorNum = 0;
		$this->errorMsg = '';

		// Execute the query. Error suppression is used here to prevent warnings/notices that the connection has been lost.
		try
		{
			$this->cursor = $this->connection->query($query);
		}
		catch (Exception $e)
		{
		}

		// If an error occurred handle it.
		if (!$this->cursor)
		{
			$errorInfo      = $this->connection->errorInfo();
			$this->errorNum = $errorInfo[1];
			$this->errorMsg = $errorInfo[2] . ' SQL=' . $query;

			// Check if the server was disconnected.
			if (!$this->connected() && !$this->isReconnecting)
			{
				$this->isReconnecting = true;

				try
				{
					// Attempt to reconnect.
					$this->connection = null;
					$this->open();
				}
					// If connect fails, ignore that exception and throw the normal exception.
				catch (RuntimeException $e)
				{
					throw new RuntimeException($this->errorMsg, $this->errorNum);
				}

				// Since we were able to reconnect, run the query again.
				$result               = $this->query();
				$this->isReconnecting = false;

				return $result;
			}
			// The server was not disconnected.
			else
			{
				throw new RuntimeException($this->errorMsg, $this->errorNum);
			}
		}

		return $this->cursor;
	}

	/**
	 * Select a database for use.
	 *
	 * @param   string  $database  The name of the database to select for use.
	 *
	 * @return  boolean  True if the database was successfully selected.
	 */
	public function select($database)
	{
		try
		{
			$this->connection->exec('USE ' . $this->quoteName($database));
		}
		catch (Exception $e)
		{
			$errorInfo      = $this->connection->errorInfo();
			$this->errorNum = $errorInfo[1];
			$this->errorMsg = $errorInfo[2];

			return false;
		}

		return true;
	}

	/**
	 * Set the connection to use UTF-8 character encoding.
	 *
	 * @return  boolean  True on success.
	 */
	public function setUTF()
	{
		return true;
	}

	/**
	 * Method to commit a transaction.
	 *
	 * @return  void
	 */
	public function transactionCommit()
	{
		$this->connection->commit();
	}

	/**
	 * Method to roll back a transaction.
	 *
	 * @return  void
	 */
	public function transactionRollback()
	{
		$this->connection->rollBack();
	}

	/**
	 * Method to initialize a transaction.
	 *
	 * @return  void
	 */
	public function transactionStart()
	{
		$this->connection->beginTransaction();
	}

	/**
	 * Method to fetch a row from the result set cursor as an array.
	 *
	 * @param   mixed  $cursor  The optional result set cursor from which to fetch the row.
	 *
	 * @return  mixed  Either the next row from the result set or false if there are no more rows.
	 */
	protected function fetchArray($cursor = null)
	{
		$ret = null;

		if (!empty($cursor) && $cursor instanceof PDOStatement)
		{
			$ret = $cursor->fetch(PDO::FETCH_NUM);
		}
		elseif ($this->cursor instanceof PDOStatement)
		{
			$ret = $this->cursor->fetch(PDO::FETCH_NUM);
		}

		return $ret;
	}

	/**
	 * Method to fetch a row from the result set cursor as an object.
	 *
	 * @param   mixed   $cursor  The optional result set cursor from which to fetch the row.
	 * @param   string  $class   The class name to use for the returned row object.
	 *
	 * @return  mixed   Either the next row from the result set or false if there are no more rows.
	 */
	protected function fetchObject($cursor = null, $class = 'stdClass')
	{
		$ret = null;

		if (!empty($cursor) && $cursor instanceof PDOStatement)
		{
			$ret = $cursor->fetchObject($class);
		}
		elseif ($this->cursor instanceof PDOStatement)
		{
			$ret = $this->cursor->fetchObject($class);
		}

		return $ret;
	}
}
PK�!]|��NBackupEngine/web.confignu&1i�<?xml version="1.0"?>
<!--
    This only works on IIS 7 or later. See https://www.iis.net/configreference/system.webserver/security/requestfiltering/fileextensions
-->
<configuration>
    <system.webServer>
        <security>
            <requestFiltering>
                <fileExtensions allowUnlisted="false" >
                    <clear />
                    <add fileExtension=".html" allowed="true"/>
                </fileExtensions>
            </requestFiltering>
        </security>
    </system.webServer>
</configuration>PK�!]��3��7BackupEngine/Platform/Exception/DecryptionException.phpnu&1i�<?php
/**
 * Akeeba Engine
 *
 * @package   akeebaengine
 * @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License version 3, or later
 *
 * 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, version 3.
 *
 * 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
 * <https://www.gnu.org/licenses/>.
 */

namespace Akeeba\Engine\Platform\Exception;

defined('AKEEBAENGINE') || die();

use Akeeba\Engine\Platform;
use Exception;
use RuntimeException;

/**
 * Thrown when the settings cannot be decrypted, e.g. when the server no longer has encyrption enabled or the key has
 * changed.
 */
class DecryptionException extends RuntimeException
{
	public function __construct($message = null, $code = 500, Exception $previous = null)
	{
		if (empty($message))
		{
			$message = Platform::getInstance()->translate('COM_AKEEBA_CONFIG_ERR_DECRYPTION');
		}

		parent::__construct($message, $code, $previous);
	}

}
PK�!]~����0�0+BackupEngine/Platform/PlatformInterface.phpnu&1i�<?php
/**
 * Akeeba Engine
 *
 * @package   akeebaengine
 * @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License version 3, or later
 *
 * 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, version 3.
 *
 * 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
 * <https://www.gnu.org/licenses/>.
 */

namespace Akeeba\Engine\Platform;

defined('AKEEBAENGINE') || die();

use Exception;

/**
 * Interface PlatformInterface
 *
 * @property string $tableNameProfiles The name of the table where backup profiles are stored
 * @property string $tableNameStats    The name of the table where backup records are stored
 * @property array  $configOverrides   Configuration overrides
 */
interface PlatformInterface
{
	/**
	 * Returns an array with the directory/-ies in which the magic autoloader should look for platform overrides.
	 *
	 * @return  array
	 */
	public function getPlatformDirectories();

	/**
	 * Performs heuristics to determine if this platform object is the ideal
	 * candidate for the environment Akeeba Engine is running in.
	 *
	 * @return  bool
	 */
	public function isThisPlatform();

	/**
	 * Saves the current configuration to the database table
	 *
	 * @param   int  $profile_id      The profile where to save the configuration
	 *                                to, defaults to current profile
	 *
	 * @return  bool  True if everything was saved properly
	 */
	public function save_configuration($profile_id = null);

	/**
	 * Loads the current configuration off the database table
	 *
	 * @param   int  $profile_id  The profile where to read the configuration from, defaults to current profile
	 *
	 * @return  bool  True if everything was read properly
	 */
	public function load_configuration($profile_id = null);

	/**
	 * Returns an associative array of stock platform directories
	 *
	 * @return  array
	 */
	public function get_stock_directories();

	/**
	 * Returns the absolute path to the site's root
	 *
	 * @return  string
	 */
	public function get_site_root();

	/**
	 * Returns the absolute path to the installer images directory
	 *
	 * @return  string
	 */
	public function get_installer_images_path();

	/**
	 * Returns the active profile number
	 *
	 * @return  integer
	 */
	public function get_active_profile();

	/**
	 * Returns the selected profile's name. If no ID is specified, the current
	 * profile's name is returned.
	 *
	 * @param   integer|null  $id  The ID of the profile, skip for current profile
	 *
	 * @return  string
	 */
	public function get_profile_name($id = null);

	/**
	 * Returns the backup origin
	 *
	 * @return  string  Backup origin: backend|frontend
	 */
	public function get_backup_origin();

	/**
	 * Returns a timestamp formatted for the current site's database driver
	 *
	 * @param   string  $date  [optional] The timestamp to use. Omit to use current timestamp.
	 *
	 * @return  string
	 */
	public function get_timestamp_database($date = 'now');

	/**
	 * Returns the current timestamp, taking into account any TZ information,
	 * in the format specified by $format.
	 *
	 * @param   string  $format  Timestamp format string (standard PHP format string)
	 *
	 * @return  string
	 */
	public function get_local_timestamp($format);

	/**
	 * Returns the current host name
	 *
	 * @return  string
	 */
	public function get_host();

	/**
	 * Returns the current site's name
	 *
	 * @return  string
	 */
	public function get_site_name();

	/**
	 * Creates or updates the statistics record of the current backup attempt
	 *
	 * @param   int    $id    Backup record ID, use null for new record
	 * @param   array  $data  The data to store
	 *
	 * @return int|null The new record id, or null if this doesn't apply
	 *
	 * @throws Exception On database error
	 */
	public function set_or_update_statistics($id = null, $data = []);

	/**
	 * Loads and returns a backup statistics record as a hash array
	 *
	 * @param   int  $id  Backup record ID
	 *
	 * @return  array
	 */
	public function get_statistics($id);

	/**
	 * Completely removes a backup statistics record
	 *
	 * @param   int  $id  Backup record ID
	 *
	 * @return  bool  True on success
	 */
	public function delete_statistics($id);

	/**
	 * Returns a list of backup statistics records, respecting the pagination
	 *
	 * The $config array allows the following options to be set:
	 * limitstart    int        Offset in the recordset to start from
	 * limit        int        How many records to return at once
	 * filters        array    An array of filters to apply to the results. Alternatively you can just pass a profile
	 * ID to filter by that profile. order        array    Record ordering information (by and ordering)
	 *
	 * @param   array  $config  See above
	 *
	 * @return  array
	 */
	function &get_statistics_list($config = []);

	/**
	 * Return the total number of statistics records
	 *
	 * @param   array  $filters  An array of filters to apply to the results. Alternatively you can just pass a profile
	 *                           ID to filter by that profile.
	 *
	 * @return  integer
	 */
	function get_statistics_count($filters = null);

	/**
	 * Returns an array with the specifics of running backups
	 *
	 * @param   string  $tag  The backup type (e.g. backend)
	 *
	 * @return  array
	 */
	public function get_running_backups($tag = null);

	/**
	 * Multiple backup attempts can share the same backup file name. Only
	 * the last backup attempt's file is considered valid. Previous attempts
	 * have to be deemed "obsolete". This method returns a list of backup
	 * statistics ID's with "valid"-looking names. IT DOES NOT CHECK FOR THE
	 * EXISTENCE OF THE BACKUP FILE!
	 *
	 * @param   bool    $useprofile   If true, it will only return backup records of the current profile
	 * @param   array   $tagFilters   Which tags to include; leave blank for all. If the first item is "NOT", then all
	 *                                tags EXCEPT those listed will be included.
	 * @param   string  $ordering     Ordering of the records, default DESC (descending), use DESC or ASC
	 *
	 * @return  array    A list of ID's for records w/ "valid"-looking backup files
	 */
	public function &get_valid_backup_records($useprofile = false, $tagFilters = [], $ordering = 'DESC');

	/**
	 * Invalidates older records sharing the same $archivename
	 *
	 * @param   string  $archivename  The archive name
	 *
	 * @return  void
	 */
	public function remove_duplicate_backup_records($archivename);

	/**
	 * Marks the specified backup records as having no files
	 *
	 * @param   array  $ids  Array of backup record IDs to ivalidate
	 *
	 * @return  void
	 */
	public function invalidate_backup_records($ids);

	/**
	 * Gets a list of records with remotely stored files in the selected remote storage
	 * provider and profile.
	 *
	 * @param   int     $profile   (optional) The profile to use. Skip or use null for active profile.
	 * @param   string  $engine    (optional) The remote engine to looks for. Skip or use null for the active profile's
	 *                             engine.
	 *
	 * @return  array
	 */
	public function get_valid_remote_records($profile = null, $engine = null);

	/**
	 * Returns the filter data for the entire filter group collection
	 *
	 * @return  array
	 */
	public function &load_filters();

	/**
	 * Saves the nested filter data array $filter_data to the database
	 *
	 * @param   array  $filter_data  The filter data to save
	 *
	 * @return  bool  True on success
	 */
	public function save_filters(&$filter_data);

	/**
	 * Gets the best matching database driver class, according to CMS settings
	 *
	 * @param   bool  $use_platform     If set to false, it will forcibly try to assign one of the primitive type
	 *                                  (Mysql/Mysqli) and NEVER tell you to use an platform driver
	 *
	 * @return  string
	 */
	public function get_default_database_driver($use_platform = true);

	/**
	 * Returns a set of options to connect to the default database of the current CMS
	 *
	 * @return  array
	 */
	public function get_platform_database_options();

	/**
	 * Provides a platform-specific translation function
	 *
	 * @param   string  $key  The translation key
	 *
	 * @return  string
	 */
	public function translate($key);

	/**
	 * Populates global constants holding the Akeeba version
	 *
	 * @return  void
	 */
	public function load_version_defines();

	/**
	 * Returns the platform name and version
	 *
	 * @return  array  Contains the platform name and version
	 */
	public function getPlatformVersion();

	/**
	 * Logs platform-specific directories with LogLevel::INFO log level
	 *
	 * @return  string  If there are any extra notes / warnings on this platform
	 */
	public function log_platform_special_directories();

	/**
	 * Loads a platform-specific software configuration option. These are
	 * configuration values for the backup engine, but unlike the rest of the
	 * configuration options they are not stored in the backup profile.
	 * Instead, these are stored globally using a platform-specific method.
	 * So these are not configuration values for the platform itself.
	 *
	 * @param   string  $key      The configuration option to retrieve
	 * @param   mixed   $default  The default value to return if it's not defined
	 *
	 * @return  mixed
	 */
	public function get_platform_configuration_option($key, $default);

	/**
	 * Returns a list of emails to the administrators
	 *
	 * @return  array
	 */
	public function get_administrator_emails();

	/**
	 * Sends a very simple email using the platform's emailer facility
	 *
	 * @param   string  $to          Recipient address
	 * @param   string  $subject     Email subject line
	 * @param   string  $body        Email body (plain text)
	 * @param   string  $attachFile  (optional) Full path to the file being attached
	 *
	 * @return  bool  True on success
	 */
	public function send_email($to, $subject, $body, $attachFile = null);

	/**
	 * Deletes a file from the local server using direct file access or FTP
	 *
	 * @param   string  $file  The file to unlink
	 *
	 * @return  bool  True on success
	 */
	public function unlink($file);

	/**
	 * Moves a file around within the local server using direct file access or FTP
	 *
	 * @param   string  $from  Full path of the file to move
	 * @param   string  $to    Full path of where the file will be moved to
	 *
	 * @return  bool  True on success
	 */
	public function move($from, $to);

	/**
	 * Stores a flash (temporary) variable in the session.
	 *
	 * @param   string  $name   The name of the variable to store
	 * @param   string  $value  The value of the variable to store
	 *
	 * @return  void
	 */
	public function set_flash_variable($name, $value);

	/**
	 * Return the value of a flash (temporary) variable from the session and
	 * immediately removes it.
	 *
	 * @param   string  $name     The name of the flash variable
	 * @param   mixed   $default  Default value, if the variable is not defined
	 *
	 * @return  mixed  The value of the variable or $default if it's not set
	 */
	public function get_flash_variable($name, $default = null);

	/**
	 * Perform an immediate redirection to the defined URL
	 *
	 * @param   string  $url  The URL to redirect to
	 *
	 * @return  void
	 */
	public function redirect($url);

	/**
	 * Get the proxy configuration for this platform.
	 *
	 * @return  array{enabled: bool, host: string, port: int, user: string, pass: string}
	 * @since   9.0.7
	 */
	public function getProxySettings();

	/**
	 * Set the proxy configuration for this platform
	 *
	 * @param   false   $useProxy  Should I use a proxy at all?
	 * @param   string  $host      Proxy hostname or IP address
	 * @param   int     $port      Proxy port
	 * @param   string  $username  Proxy username. Optional. Leavel blank to turn off authentication.
	 * @param   string  $password  Proxy password.
	 *
	 * @return  void
	 * @since   9.0.7
	 */
	public function setProxySettings($useProxy = false, $host = '', $port = 8080, $username = '', $password = '');
}
PK�!]l)qtqtBackupEngine/Platform/Base.phpnu&1i�<?php
/**
 * Akeeba Engine
 *
 * @package   akeebaengine
 * @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License version 3, or later
 *
 * 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, version 3.
 *
 * 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
 * <https://www.gnu.org/licenses/>.
 */

namespace Akeeba\Engine\Platform;

defined('AKEEBAENGINE') || die();

use Akeeba\Engine\Driver\Mysqli;
use Akeeba\Engine\Driver\QueryException;
use Akeeba\Engine\Factory;
use Akeeba\Engine\Platform\Exception\DecryptionException;
use Akeeba\Engine\Util\ProfileMigration;
use DateTime;
use DateTimeZone;
use Exception;
use RuntimeException;

abstract class Base implements PlatformInterface
{
	/** @var array Configuration overrides */
	public $configOverrides = [];

	/** @var bool Should I throw an exception when settings decryption fails? */
	public $decryptionException = false;

	/** @var string The name of the platform (same as the directory name) */
	public $platformName = null;

	/** @var int Priority of this platform. A lower number denotes higher priority. */
	public $priority = 50;

	/** @var string The name of the table where backup profiles are stored */
	public $tableNameProfiles = '#__ak_profiles';

	/** @var string The name of the table where backup records are stored */
	public $tableNameStats = '#__ak_stats';

	/** @var bool Have I initialised the proxy configuration settings? */
	protected $hasInitialisedProxySettings = false;

	/** @var bool Should I use a proxy? */
	protected $proxyEnabled = false;

	/** @var string Proxy hostname or IP address */
	protected $proxyHost = '';

	/** @var string Proxy password; only applied with a non-empty username */
	protected $proxyPass = '';

	/** @var int Proxy port */
	protected $proxyPort = 8080;

	/** @var string Proxy username; empty means no authentication */
	protected $proxyUser = '';

	/**
	 * Completely removes a backup statistics record
	 *
	 * @param   int  $id  Backup record ID
	 *
	 * @return bool True on success
	 */
	public function delete_statistics($id)
	{
		$db    = Factory::getDatabase($this->get_platform_database_options());
		$query = (method_exists($db, 'createQuery') ? $db->createQuery() : $db->getQuery(true))
			->delete($db->qn($this->tableNameStats))
			->where($db->qn('id') . ' = ' . $db->q($id));
		$db->setQuery($query);

		$result = true;
		try
		{
			$db->query();
		}
		catch (Exception $exc)
		{
			$result = false;
		}

		return $result;
	}

	public function getPlatformDirectories()
	{
		return [dirname(__FILE__) . '/' . $this->platformName];
	}

	public function getPlatformVersion()
	{
		return [
			'name'    => 'Platform',
			'version' => 'unknown',
		];
	}

	/**
	 * @inheritdoc
	 */
	public final function getProxySettings()
	{
		if (!$this->hasInitialisedProxySettings)
		{
			$this->detectProxySettings();
		}

		return [
			'enabled' => $this->proxyEnabled ?? false,
			'host'    => $this->proxyHost ?: '',
			'port'    => $this->proxyPort ?: 8080,
			'user'    => $this->proxyUser ?: '',
			'pass'    => $this->proxyPass ?: '',
		];
	}

	public function get_active_profile()
	{
		return 1;
	}

	public function get_administrator_emails()
	{
		return [];
	}

	public function get_backup_origin()
	{
		return 'backend';
	}

	public function get_default_database_driver($use_platform = true)
	{
		return Mysqli::class;
	}

	public function get_host()
	{
		return '';
	}

	public function get_installer_images_path()
	{
		return '';
	}

	public function get_local_timestamp($format)
	{
		$dateNow = new DateTime('now', new DateTimeZone('UTC'));

		return $dateNow->format($format);
	}

	public function get_platform_configuration_option($key, $default)
	{
		return '';
	}

	public function get_platform_database_options()
	{
		return [];
	}

	public function get_profile_name($id = null)
	{
		return '';
	}

	/**
	 * Returns an array with the specifics of running backups
	 *
	 * @param   string  $tag
	 *
	 * @return  array   Array list of associative arrays
	 * @throws  QueryException
	 *
	 */
	public function get_running_backups($tag = null)
	{
		$db    = Factory::getDatabase($this->get_platform_database_options());
		$query = (method_exists($db, 'createQuery') ? $db->createQuery() : $db->getQuery(true))
			->select('*')
			->from($db->qn($this->tableNameStats))
			->where($db->qn('status') . ' = ' . $db->q('run'))
			->where(' NOT ' . $db->qn('archivename') . ' = ' . $db->q(''));
		if (!empty($tag))
		{
			$query->where($db->qn('origin') . ' LIKE ' . $db->q($tag . '%'));
		}
		$db->setQuery($query);

		return $db->loadAssocList();
	}

	public function get_site_name()
	{
		return '';
	}

	public function get_site_root()
	{
		return '';
	}

	/**
	 * Loads and returns a backup statistics record as a hash array
	 *
	 * @param   int  $id  Backup record ID
	 *
	 * @return array
	 */
	public function get_statistics($id)
	{
		$db    = Factory::getDatabase($this->get_platform_database_options());
		$query = (method_exists($db, 'createQuery') ? $db->createQuery() : $db->getQuery(true))
			->select('*')
			->from($db->qn($this->tableNameStats))
			->where($db->qn('id') . ' = ' . $db->q($id));
		$db->setQuery($query);

		return $db->loadAssoc();
	}

	/**
	 * Return the total number of statistics records
	 *
	 * @param   array  $filters  An array of filters to apply to the results. Alternatively you can just pass a profile
	 *                           ID to filter by that profile.
	 *
	 * @return int
	 */
	function get_statistics_count($filters = null)
	{
		$db = Factory::getDatabase($this->get_platform_database_options());

		$query = (method_exists($db, 'createQuery') ? $db->createQuery() : $db->getQuery(true));

		if (!empty($filters))
		{
			if (is_array($filters))
			{
				if (!empty($filters))
				{
					// Parse the filters array
					foreach ($filters as $f)
					{
						$clause = $db->quoteName($f['field']);
						if (array_key_exists('operand', $f))
						{
							$clause .= ' ' . strtoupper($f['operand']) . ' ';
						}
						else
						{
							$clause .= ' = ';
						}
						if ($f['operand'] == 'BETWEEN')
						{
							$clause .= $db->q($f['value']) . ' AND ' . $db->q($f['value2']);
						}
						elseif ($f['operand'] == 'LIKE')
						{
							$clause .= '\'%' . $db->escape($f['value']) . '%\'';
						}
						else
						{
							$clause .= $db->q($f['value']);
						}
						$query->where($clause);
					}
				}
			}
			else
			{
				// Legacy mode: profile ID given
				$query->where($db->qn('profile_id') . ' = ' . $db->q($filters));
			}
		}

		$query->select('COUNT(*)')
			->from($db->quoteName($this->tableNameStats));
		$db->setQuery($query);

		return $db->loadResult();
	}

	/**
	 * Returns a list of backup statistics records, respecting the pagination
	 *
	 * The $config array allows the following options to be set:
	 * limitstart    int        Offset in the recordset to start from
	 * limit        int        How many records to return at once
	 * filters        array    An array of filters to apply to the results. Alternatively you can just pass a profile
	 * ID to filter by that profile. order        array    Record ordering information (by and ordering)
	 *
	 * @return array
	 */
	function &get_statistics_list($config = [])
	{
		$defaultConfiguration = [
			'limitstart' => 0,
			'limit'      => 0,
			'filters'    => [],
			'order'      => null,
		];
		$config               = (object) array_merge($defaultConfiguration, $config);

		$db = Factory::getDatabase($this->get_platform_database_options());

		$query = (method_exists($db, 'createQuery') ? $db->createQuery() : $db->getQuery(true));

		if (!empty($config->filters))
		{
			if (is_array($config->filters))
			{
				if (!empty($config->filters))
				{
					// Parse the filters array
					foreach ($config->filters as $f)
					{
						$clause = $db->qn($f['field']);
						if (array_key_exists('operand', $f))
						{
							$clause .= ' ' . strtoupper($f['operand']) . ' ';
							if ($f['operand'] == 'BETWEEN')
							{
								$clause .= $db->q($f['value']) . ' AND ' . $db->q($f['value2']);
							}
							elseif ($f['operand'] == 'LIKE')
							{
								$clause .= '\'%' . $db->escape($f['value']) . '%\'';
							}
							else
							{
								$clause .= $db->q($f['value']);
							}
						}
						else
						{
							$clause .= ' = ' . $db->q($f['value']);
						}

						$query->where($clause);
					}
				}
			}
			else
			{
				// Legacy mode: profile ID given
				$query->where($db->qn('profile_id') . ' = ' . $db->q($config->filters));
			}
		}

		if (empty($config->order) || !is_array($config->order))
		{
			$config->order = [
				'by'    => 'id',
				'order' => 'DESC',
			];
		}

		$query->select('*')
			->from($db->qn($this->tableNameStats))
			->order($db->qn($config->order['by']) . " " . strtoupper($config->order['order']));

		$db->setQuery($query, $config->limitstart, $config->limit);

		$list = $db->loadAssocList();

		return $list;
	}

	public function get_stock_directories()
	{
		return [];
	}

	public function get_timestamp_database($date = 'now')
	{
		return '';
	}

	/**
	 * Multiple backup attempts can share the same backup file name. Only
	 * the last backup attempt's file is considered valid. Previous attempts
	 * have to be deemed "obsolete". This method returns a list of backup
	 * statistics ID's with "valid"-looking names. IT DOES NOT CHECK FOR THE
	 * EXISTENCE OF THE BACKUP FILE!
	 *
	 * @param   bool    $useprofile  If true, it will only return backup records of the current profile
	 * @param   array   $tagFilters  Which tags to include; leave blank for all. If the first item is "NOT", then all
	 *                               tags EXCEPT those listed will be included.     *
	 * @param   string  $ordering
	 *
	 * @return  array A list of ID's for records w/ "valid"-looking backup files
	 * @throws  QueryException
	 *
	 */
	public function &get_valid_backup_records($useprofile = false, $tagFilters = [], $ordering = 'DESC')
	{
		$db = Factory::getDatabase($this->get_platform_database_options());

		$query2 = (method_exists($db, 'createQuery') ? $db->createQuery() : $db->getQuery(true))
			->select('MAX(' . $db->qn('id') . ') AS ' . $db->qn('id'))
			->from($db->qn($this->tableNameStats))
			->where($db->qn('status') . ' = ' . $db->q('complete'))
			->group($db->qn('absolute_path'));

		$query = (method_exists($db, 'createQuery') ? $db->createQuery() : $db->getQuery(true))
			->select($db->qn('id'))
			->from($db->qn($this->tableNameStats))
			->where($db->qn('filesexist') . ' = ' . $db->q(1))
			->where($db->qn('id') . ' IN (' . $query2 . ')')
			->where('NOT ' . $db->qn('absolute_path') . ' = ' . $db->q(''))
			->order($db->qn('id') . ' ' . $ordering);

		if ($useprofile)
		{
			$profile_id = $this->get_active_profile();
			$query->where($db->qn('profile_id') . " = " . $db->q($profile_id));
		}

		if (!empty($tagFilters))
		{
			$operator = '';
			$first    = array_shift($tagFilters);
			if ($first == 'NOT')
			{
				$operator = 'NOT';
			}
			else
			{
				array_unshift($tagFilters, $first);
			}

			$quotedTags = [];
			foreach ($tagFilters as $tag)
			{
				$quotedTags[] = $db->q($tag);
			}
			$filter = implode(', ', $quotedTags);
			unset($quotedTags);
			$query->where($operator . ' ' . $db->quoteName('tag') . ' IN (' . $filter . ')');
		}

		$db->setQuery($query);
		$array = $db->loadColumn();

		return $array;
	}

	/**
	 * Gets a list of records with remotely stored files in the selected remote storage
	 * provider and profile.
	 *
	 * @param $profile int (optional) The profile to use. Skip or use null for active profile.
	 * @param $engine  string (optional) The remote engine to looks for. Skip or use null for the active profile's
	 *                 engine.
	 *
	 * @return array
	 */
	public function get_valid_remote_records($profile = null, $engine = null)
	{
		$config = Factory::getConfiguration();
		$result = [];

		if (is_null($profile))
		{
			$profile = $this->get_active_profile();
		}
		if (is_null($engine))
		{
			$engine = $config->get('akeeba.advanced.postproc_engine', '');
		}

		if (empty($engine))
		{
			return $result;
		}

		$db  = Factory::getDatabase($this->get_platform_database_options());
		$sql = (method_exists($db, 'createQuery') ? $db->createQuery() : $db->getQuery(true))
			->select('*')
			->from($db->qn($this->tableNameStats))
			->where($db->qn('profile_id') . ' = ' . $db->q($profile))
			->where($db->qn('remote_filename') . ' LIKE ' . $db->q($engine . '://%'))
			->order($db->qn('id') . ' DESC');

		$db->setQuery($sql);

		return $db->loadAssocList();
	}

	/**
	 * Marks the specified backup records as having no files
	 *
	 * @param   array  $ids  Array of backup record IDs to ivalidate
	 */
	public function invalidate_backup_records($ids)
	{
		if (empty($ids))
		{
			return false;
		}
		$db   = Factory::getDatabase($this->get_platform_database_options());
		$temp = [];
		foreach ($ids as $id)
		{
			$temp[] = $db->q($id);
		}
		$list = implode(',', $temp);
		$sql  = (method_exists($db, 'createQuery') ? $db->createQuery() : $db->getQuery(true))
			->update($db->qn($this->tableNameStats))
			->set($db->qn('filesexist') . ' = ' . $db->q('0'))
			->where($db->qn('id') . ' IN (' . $list . ')');;
		$db->setQuery($sql);

		try
		{
			$db->query();
		}
		catch (Exception $exc)
		{
			return false;
		}

		return true;
	}

	public function isThisPlatform()
	{
		return true;
	}

	/**
	 * Loads the current configuration off the database table
	 *
	 * @param   int   $profile_id  The profile where to read the configuration from, defaults to current profile
	 * @param   bool  $reset       Should I reset the Configuration object before loading the profile? Default: true.
	 *
	 * @return  bool  True if everything was read properly
	 */
	public function load_configuration($profile_id = null, $reset = true)
	{
		// Load the database class
		$db = Factory::getDatabase($this->get_platform_database_options());

		// Get the active profile number, if no profile was specified
		if (is_null($profile_id))
		{
			$profile_id = $this->get_active_profile();
		}

		// Initialize the registry
		$registry = Factory::getConfiguration();

		if ($reset)
		{
			$registry->reset();
		}

		// Is the database connected?
		if (!$db->connected())
		{
			return false;
		}

		try
		{
			// Load the INI format local configuration dump off the database
			$sql = (method_exists($db, 'createQuery') ? $db->createQuery() : $db->getQuery(true))
				->select($db->qn('configuration'))
				->from($db->qn($this->tableNameProfiles))
				->where($db->qn('id') . ' = ' . $db->q($profile_id));

			$databaseData = $db->setQuery($sql)->loadResult();
		}
		catch (Exception $e)
		{
			$databaseData = null;
		}

		/**
		 * If the profile is not the default and we can't load anything let's switch back to the default profile.
		 *
		 * You will end up here when you have opened the application in two different browsers and Browser A is used to
		 * delete the active profile you were using with Browser B. If we were not to load the default profile Browser B
		 * would try to save the default configuration data to the deleted profile. However, since the profile does not
		 * exist in the database any more the load_configuration at the end of the following if-block would trigger the
		 * same code path, recursively, infinitely until you reached the maximum nesting level in PHP, run out of memory
		 * or hit the execution time limit.
		 */
		if ((empty($databaseData) || is_null($databaseData)) && ($profile_id != 1))
		{
			return $this->load_configuration(1);
		}

		if (empty($databaseData) || is_null($databaseData))
		{
			// No configuration was saved yet - store the defaults
			$saved = $this->save_configuration($profile_id);

			// If this is the case we probably don't have the necessary table. Throw an exception.
			if (!$saved)
			{
				throw new RuntimeException("Could not save data to backup profile #$profile_id", 500);
			}

			return $this->load_configuration($profile_id);
		}

		// Decrypt the data if required
		$secureSettings = Factory::getSecureSettings();
		$noData         = empty($databaseData);
		$signature      = ($noData || (strlen($databaseData) < 12)) ? '' : substr($databaseData, 0, 12);
		$parsedData     = [];

		/**
		 * Special case: profile data is encrypted but encryption is set to false. This means that the user has just
		 * asked for the encryption to be disabled. We have to NOT load the settings so that the application has the
		 * chance to decode the data and write the decoded data back to the database.
		 */

		if (!$secureSettings->supportsEncryption() && in_array($signature, ['###AES128###', '###CTR128###']))
		{
			$dataArray = ['volatile' => ['fake_decrypt_flag' => 1]];
		}
		else
		{
			$databaseData        = $secureSettings->decryptSettings($databaseData);
			$isMigrationRequired = false; // Do I have to migrate the data from INI to JSON
			$corruptedINI        = false; // Is the INI data corrupted?

			// Handle legacy, INI-encoded data
			if (ProfileMigration::looksLikeIni($databaseData))
			{
				$isMigrationRequired = true;
				$corruptedINI        = strpos($databaseData, '[akeeba]') === false;
				$databaseData        = ProfileMigration::convertINItoJSON($databaseData);
			}

			// Detect corrupt JSON data
			$corruptedJSON = strpos($databaseData, '"akeeba"') === false;

			// Did the decryption fail and we were asked to throw an exception?
			if ($this->decryptionException && !$noData)
			{
				// The decryption failed, it returned empty data
				if (!$isMigrationRequired && empty($databaseData))
				{
					throw new DecryptionException(
						$this->translate('COM_AKEEBA_CONFIG_ERR_DECRYPTION') .
						"\nAdditional info: Empty data after decryption."
					);
				}

				// We tried to migrate but the INI data is corrupt
				if ($isMigrationRequired && $corruptedINI)
				{
					throw new DecryptionException(
						$this->translate('COM_AKEEBA_CONFIG_ERR_DECRYPTION') .
						"\nAdditional info: old format INI data was corrupt and could not be migrated to JSON."
					);
				}

				// We tried to migrate but the resulting JSON data is corrupt
				if ($isMigrationRequired && $corruptedJSON)
				{
					throw new DecryptionException(
						$this->translate('COM_AKEEBA_CONFIG_ERR_DECRYPTION') .
						"\nAdditional info: JSON data was corrupt after migrating it from INI data."
					);
				}

				// We decrypted something but it does not look like JSON. Wrong encryption key?
				if ($corruptedJSON)
				{
					throw new DecryptionException(
						$this->translate('COM_AKEEBA_CONFIG_ERR_DECRYPTION') .
						"\nAdditional info: configuration JSON data was corrupt after decryption."
					);
				}
			}

			$dataArray = json_decode($databaseData, true);
		}

		unset($databaseData);

		if (!is_array($dataArray))
		{
			$dataArray = [];
		}

		foreach ($dataArray as $section => $row)
		{
			if ($section == 'volatile')
			{
				continue;
			}

			$row = $this->arrayToRegistryDefinitions($row);

			if (is_array($row) && !empty($row))
			{
				foreach ($row as $key => $value)
				{
					$parsedData["$section.$key"] = $value;
				}
			}
		}

		unset($dataArray);

		// Import the configuration array
		$protected_keys = $registry->getProtectedKeys();
		$registry->resetProtectedKeys();
		$registry->mergeArray($parsedData, false, false);

		// Old profiles have advanced.proc_engine instead of advanced.postproc_engine. Migrate them.
		$procEngine = $registry->get('akeeba.advanced.proc_engine', null);

		if (!empty($procEngine))
		{
			$registry->set('akeeba.advanced.postproc_engine', $procEngine);
			$registry->set('akeeba.advanced.proc_engine', null);
		}

		// Apply config overrides
		if (is_array($this->configOverrides) && !empty($this->configOverrides))
		{
			$registry->mergeArray($this->configOverrides, false, false);
		}

		$registry->setProtectedKeys($protected_keys);
		$registry->activeProfile = $profile_id;

		return true;
	}

	/**
	 * Returns the filter data for the entire filter group collection
	 *
	 * @return array
	 */
	public function &load_filters()
	{
		// Load the filter data from the database
		$profile_id = $this->get_active_profile();
		$db         = Factory::getDatabase($this->get_platform_database_options());

		// Load the INI format local configuration dump off the database
		$sql = (method_exists($db, 'createQuery') ? $db->createQuery() : $db->getQuery(true))
			->select($db->qn('filters'))
			->from($db->qn($this->tableNameProfiles))
			->where($db->qn('id') . ' = ' . $db->q($profile_id));
		$db->setQuery($sql);
		$all_filter_data = $db->loadResult();

		if (is_null($all_filter_data) || empty($all_filter_data))
		{
			$all_filter_data = [];

			return $all_filter_data;
		}

		if (ProfileMigration::looksLikeSerialized($all_filter_data))
		{
			$all_filter_data = ProfileMigration::convertSerializedToJSON($all_filter_data);
		}

		$all_filter_data = json_decode($all_filter_data, true);

		// Catch unserialization errors
		if (empty($all_filter_data))
		{
			$all_filter_data = [];
		}

		return $all_filter_data;
	}

	public function load_version_defines()
	{
	}

	public function log_platform_special_directories()
	{
	}

	public function move($from, $to)
	{
		$result = @rename($from, $to);
		if (!$result)
		{
			$result = @copy($from, $to);
			if ($result)
			{
				$result = $this->unlink($from);
			}
		}

		return $result;
	}

	public function register_autoloader()
	{
	}

	/**
	 * Invalidates older records sharing the same $archivename
	 *
	 * @param   string  $archivename
	 */
	public function remove_duplicate_backup_records($archivename)
	{
		Factory::getLog()->debug("Removing any old records with $archivename filename");
		$db = Factory::getDatabase($this->get_platform_database_options());

		$query = (method_exists($db, 'createQuery') ? $db->createQuery() : $db->getQuery(true))
			->select($db->qn('id'))
			->from($db->qn($this->tableNameStats))
			->where($db->qn('archivename') . ' = ' . $db->q($archivename))
			->order($db->qn('id') . ' DESC');

		$db->setQuery($query);
		$array = $db->loadColumn();

		Factory::getLog()->debug((is_array($array) || $array instanceof \Countable ? count($array) : 0) . " records found");

		// No records?! Quit.
		if (empty($array))
		{
			return;
		}
		// Only one record. Quit.
		if ((is_array($array) || $array instanceof \Countable ? count($array) : 0) == 1)
		{
			return;
		}

		// Shift the first (latest) element off the array
		$currentID = array_shift($array);

		// Invalidate older records
		$this->invalidate_backup_records($array);
	}

	/**
	 * Saves the current configuration to the database table
	 *
	 * @param   int  $profile_id  The profile where to save the configuration to, defaults to current profile
	 *
	 * @return    bool    True if everything was saved properly
	 */
	public function save_configuration($profile_id = null)
	{
		// Load the database class
		$db = Factory::getDatabase($this->get_platform_database_options());

		if (!$db->connected())
		{
			return false;
		}

		// Get the active profile number, if no profile was specified
		if (is_null($profile_id))
		{
			$profile_id = $this->get_active_profile();
		}

		// Get an INI format registry dump
		$registry     = Factory::getConfiguration();
		$dump_profile = $registry->exportAsJSON();

		// Encrypt the registry dump if required
		$secureSettings = Factory::getSecureSettings();
		$dump_profile   = $secureSettings->encryptSettings($dump_profile);

		// Does the record already exist?
		$sql = (method_exists($db, 'createQuery') ? $db->createQuery() : $db->getQuery(true))
			->select('COUNT(*)')
			->from($db->qn($this->tableNameProfiles))
			->where($db->qn('id') . ' = ' . $db->q($profile_id));

		try
		{
			$count  = $db->setQuery($sql)->loadResult();
			$exists = ($count > 0);
		}
		catch (Exception $e)
		{
			$exists = true;
		}

		if ($exists)
		{
			$sql = (method_exists($db, 'createQuery') ? $db->createQuery() : $db->getQuery(true))
				->update($db->qn($this->tableNameProfiles))
				->set($db->qn('configuration') . ' = ' . $db->q($dump_profile))
				->where($db->qn('id') . ' = ' . $db->q($profile_id));
		}
		else
		{
			$sql = (method_exists($db, 'createQuery') ? $db->createQuery() : $db->getQuery(true))
				->insert($db->qn($this->tableNameProfiles))
				->columns([
					$db->qn('id'), $db->qn('description'), $db->qn('configuration'),
					$db->qn('filters'), $db->qn('quickicon'),
				])
				->values(
					$db->q(1) . ', ' .
					$db->q("Default backup profile") . ', ' .
					$db->q($dump_profile) . ', ' .
					$db->q('') . ', ' .
					$db->q(1)
				);
		}

		$db->setQuery($sql);

		try
		{
			$result = $db->query();
		}
		catch (Exception $exc)
		{
			return false;
		}

		return ($result == true);
	}

	/**
	 * Saves the nested filter data array $filter_data to the database
	 *
	 * @param   array  $filter_data  The filter data to save
	 *
	 * @return    bool    True on success
	 */
	public function save_filters(&$filter_data)
	{
		$profile_id = $this->get_active_profile();
		$db         = Factory::getDatabase($this->get_platform_database_options());

		$encodedFilterData = json_encode($filter_data, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_FORCE_OBJECT | JSON_PRETTY_PRINT);

		$sql = (method_exists($db, 'createQuery') ? $db->createQuery() : $db->getQuery(true))
			->update($db->qn($this->tableNameProfiles))
			->set($db->qn('filters') . '=' . $db->q($encodedFilterData))
			->where($db->qn('id') . ' = ' . $db->q($profile_id));

		try
		{
			$db->setQuery($sql)->query();
		}
		catch (Exception $exc)
		{
			return false;
		}

		return true;
	}

	public function send_email($to, $subject, $body, $attachFile = null)
	{
		return false;
	}

	/** @inheritdoc */
	public final function setProxySettings($useProxy = false, $host = '', $port = 8080, $username = '', $password = '')
	{
		$host = trim($host);
		$port = (int) $port;

		$this->hasInitialisedProxySettings = true;
		$this->proxyEnabled                = $useProxy && !empty($host) && ($port > 0) && ($port < 65536);
		$this->proxyHost                   = $host;
		$this->proxyPort                   = $port;
		$this->proxyUser                   = trim($username ?? '') ?: '';
		$this->proxyPass                   = trim($password ?? '') ?: '';
	}

	/**
	 * Creates or updates the statistics record of the current backup attempt
	 *
	 * @param   int    $id    Backup record ID, use null for new record
	 * @param   array  $data  The data to store
	 *
	 * @return int|null The new record id, or null if this doesn't apply
	 *
	 * @throws Exception On database error
	 */
	public function set_or_update_statistics($id = null, $data = [])
	{
		// No valid data?
		if (!is_array($data))
		{
			return null;
		}

		// No data at all?
		if (empty($data))
		{
			return null;
		}

		$db = Factory::getDatabase($this->get_platform_database_options());

		$tableFields = $db->getTableColumns($this->tableNameStats);
		$tableFields = array_keys($tableFields);

		if (is_null($id))
		{
			// Create a new record
			$sql_fields = [];
			$sql_values = '';

			foreach ($data as $key => $value)
			{
				if (!in_array($key, $tableFields))
				{
					continue;
				}

				$sql_fields[] = $db->qn($key);
				$sql_values   .= (!empty($sql_values) ? ',' : '') . $db->quote($value);
			}

			$sql = (method_exists($db, 'createQuery') ? $db->createQuery() : $db->getQuery(true))
				->insert($db->quoteName($this->tableNameStats))
				->columns($sql_fields)
				->values($sql_values);

			$db->setQuery($sql);
			$db->query();

			return $db->insertid();
		}
		else
		{
			$sql_set = [];
			foreach ($data as $key => $value)
			{
				if ($key == 'id')
				{
					continue;
				}

				$sql_set[] = $db->qn($key) . '=' . $db->q($value);
			}
			$sql = (method_exists($db, 'createQuery') ? $db->createQuery() : $db->getQuery(true))
				->update($db->qn($this->tableNameStats))
				->set($sql_set)
				->where($db->qn('id') . '=' . $db->q($id));
			$db->setQuery($sql);
			$db->query();

			return null;
		}
	}

	public function translate($key)
	{
		return '';
	}

	public function unlink($file)
	{
		return @unlink($file);
	}

	/**
	 * Flattens a hierarchical array to a set of registry keys.
	 *
	 * For example
	 * [ 'foo' => [ 'bar' => [ 'baz' => 1, 'bat' => 2 ] ] ]
	 * becomes
	 * [ 'foo.bar.baz' => 1, 'foo.bar.bat' => 2 ]
	 *
	 * @param   array   $array   The array to flatten
	 * @param   string  $prefix  The prefix to use (leave blank; it's used in recursive calls)
	 *
	 * @return  array  An array with flattened keys
	 *
	 * @since   6.4.1
	 */
	protected function arrayToRegistryDefinitions(array $array, $prefix = '')
	{
		$keys = [];

		foreach ($array as $k => $v)
		{
			if (is_array($v))
			{
				$keys = array_merge($keys, $this->arrayToRegistryDefinitions($v, $prefix . $k . "."));

				continue;
			}

			$keys[$prefix . $k] = $v;
		}

		return $keys;
	}

	/**
	 * Automatically-detect the proxy settings for this platform.
	 *
	 * Implement this method to detect the proxy settings. Use $this->setProxysettings() to apply them.
	 *
	 * This method is called by getProxySettings automatically. You do NOT need to call it yourself when initialising
	 * the platform.
	 *
	 * @return  void
	 * @since   9.0.7
	 */
	protected function detectProxySettings()
	{

	}
}
PK�!]��@^^BackupEngine/Scan/Base.phpnu&1i�<?php
/**
 * Akeeba Engine
 *
 * @package   akeebaengine
 * @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License version 3, or later
 *
 * 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, version 3.
 *
 * 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
 * <https://www.gnu.org/licenses/>.
 */

namespace Akeeba\Engine\Scan;

defined('AKEEBAENGINE') || die();

abstract class Base
{
	/**
	 * Gets all the files of a given folder
	 *
	 * @param   string   $folder    The absolute path to the folder to scan for files
	 * @param   integer  $position  The position in the file list to seek to. Use null for the start of list.
	 *
	 * @return  array  A simple array of files
	 */
	abstract public function getFiles($folder, &$position);

	/**
	 * Gets all the folders (subdirectories) of a given folder
	 *
	 * @param   string   $folder    The absolute path to the folder to scan for files
	 * @param   integer  $position  The position in the file list to seek to. Use null for the start of list.
	 *
	 * @return  array  A simple array of folders
	 */
	abstract public function getFolders($folder, &$position);
}
PK�!]n��PPBackupEngine/Scan/Smart.phpnu&1i�<?php
/**
 * Akeeba Engine
 *
 * @package   akeebaengine
 * @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License version 3, or later
 *
 * 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, version 3.
 *
 * 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
 * <https://www.gnu.org/licenses/>.
 */

namespace Akeeba\Engine\Scan;

defined('AKEEBAENGINE') || die();

use Akeeba\Engine\Base\Exceptions\WarningException;
use Akeeba\Engine\Factory;
use DirectoryIterator;
use Exception;
use RuntimeException;

/* Windows system detection */
if (!defined('_AKEEBA_IS_WINDOWS'))
{
	$isWindows = DIRECTORY_SEPARATOR == '\\';

	if (function_exists('php_uname'))
	{
		$isWindows = stristr(php_uname(), 'windows');
	}

	define('_AKEEBA_IS_WINDOWS', $isWindows);
}

/**
 * A filesystem scanner which uses opendir() and is smart enough to make large directories
 * be scanned inside a step of their own.
 *
 * The idea is that if it's not the first operation of this step and the number of contained
 * directories AND files is more than double the number of allowed files per fragment, we should
 * break the step immediately.
 *
 */
class Smart extends Base
{
	public function getFiles($folder, &$position)
	{
		$registry = Factory::getConfiguration();
		// Was the breakflag set BEFORE starting? -- This workaround is required due to PHP5 defaulting to assigning variables by reference
		$breakflag_before_process = $registry->get('volatile.breakflag', false);

		// Reset break flag before continuing
		$breakflag = false;

		// Initialize variables
		$arr   = [];
		$false = false;

		if (!@is_dir($folder) && !@is_dir($folder . '/'))
		{
			return $false;
		}

		$counter    = 0;
		$registry   = Factory::getConfiguration();
		$maxCounter = $registry->get('engine.scan.smart.large_dir_threshold', 100);

		$allowBreakflag = ($registry->get('volatile.operation_counter', 0) != 0) && !$breakflag_before_process;

		if (!@is_dir($folder))
		{
			throw new WarningException('Cannot list contents of directory ' . $folder . ' -- PHP reports it as not a folder.');
		}

		if (!@is_readable($folder))
		{
			throw new WarningException('Cannot list contents of directory ' . $folder . ' -- PHP reports it as not readable.');
		}

		try
		{
			$di = new DirectoryIterator($folder);
		}
		catch (Exception $e)
		{
			throw new WarningException('Cannot list contents of directory ' . $folder . ' -- PHP\'s DirectoryIterator reports the path cannot be opened.', 0, $e);
		}

		if (!$di->valid())
		{
			throw new WarningException('Cannot list contents of directory ' . $folder . ' -- PHP\'s DirectoryIterator could open the folder but immediately reports itself as not valid. If this happens your server is about to die.');
		}

		$ds = ($folder == '') || ($folder == '/') || (@substr($folder, -1) == '/') || (@substr($folder, -1) == DIRECTORY_SEPARATOR) ? '' : DIRECTORY_SEPARATOR;

		/** @var DirectoryIterator $file */
		foreach ($di as $file)
		{
			if ($breakflag)
			{
				break;
			}

			/**
			 * If the directory entry is a link pointing somewhere outside the allowed directories per open_basedir we
			 * will get a RuntimeException (tested on PHP 5.3 onwards). Catching it lets us report the link as
			 * unreadable without suffering a PHP Fatal Error.
			 */
			try
			{
				$file->isLink();
			}
			catch (RuntimeException $e)
			{
				if (!in_array($di->getFilename(), ['.', '..']))
				{
					Factory::getLog()->warning(sprintf("Link %s is inaccessible. Check the open_basedir restrictions in your server's PHP configuration", $file->getPathname()));
				}

				continue;
			}

			if ($file->isDot())
			{
				continue;
			}

			if ($file->isDir())
			{
				continue;
			}

			$dir  = $folder . $ds . $file->getFilename();
			$data = $dir;

			if (_AKEEBA_IS_WINDOWS)
			{
				$data = Factory::getFilesystemTools()->TranslateWinPath($dir);
			}

			if ($data)
			{
				$arr[] = $data;
			}

			$counter++;

			if ($counter >= $maxCounter)
			{
				$breakflag = $allowBreakflag;
			}
		}

		// Save break flag status
		$registry->set('volatile.breakflag', $breakflag);

		return $arr;
	}

	public function getFolders($folder, &$position)
	{
		// Was the breakflag set BEFORE starting? -- This workaround is required due to PHP5 defaulting to assigning variables by reference
		$registry                 = Factory::getConfiguration();
		$breakflag_before_process = $registry->get('volatile.breakflag', false);

		// Reset break flag before continuing
		$breakflag = false;

		// Initialize variables
		$arr   = [];
		$false = false;

		if (!is_dir($folder) && !is_dir($folder . '/'))
		{
			throw new WarningException('Cannot list contents of directory ' . $folder . ' -- PHP reports it as not a folder.');
		}

		if (!@is_readable($folder))
		{
			throw new WarningException('Cannot list contents of directory ' . $folder . ' -- PHP reports it as not readable.');
		}

		$counter    = 0;
		$registry   = Factory::getConfiguration();
		$maxCounter = $registry->get('engine.scan.smart.large_dir_threshold', 100);

		$allowBreakflag = ($registry->get('volatile.operation_counter', 0) != 0) && !$breakflag_before_process;

		try
		{
			$di = new DirectoryIterator($folder);
		}
		catch (Exception $e)
		{
			throw new WarningException('Cannot list contents of directory ' . $folder . ' -- PHP\'s DirectoryIterator reports the path cannot be opened.', 0, $e);
		}

		if (!$di->valid())
		{
			throw new WarningException('Cannot list contents of directory ' . $folder . ' -- PHP\'s DirectoryIterator could open the folder but immediately reports itself as not valid. If this happens your server is about to die.');
		}

		$ds = ($folder == '') || ($folder == '/') || (@substr($folder, -1) == '/') || (@substr($folder, -1) == DIRECTORY_SEPARATOR) ? '' : DIRECTORY_SEPARATOR;

		/** @var DirectoryIterator $file */
		foreach ($di as $file)
		{
			if ($breakflag)
			{
				break;
			}

			/**
			 * If the directory entry is a link pointing somewhere outside the allowed directories per open_basedir we
			 * will get a RuntimeException (tested on PHP 5.3 onwards). Catching it lets us report the link as
			 * unreadable without suffering a PHP Fatal Error.
			 */
			try
			{
				$file->isLink();
			}
			catch (RuntimeException $e)
			{
				if (!in_array($di->getFilename(), ['.', '..']))
				{
					Factory::getLog()->warning(sprintf("Link %s is inaccessible. Check the open_basedir restrictions in your server's PHP configuration", $file->getPathname()));
				}

				continue;
			}

			if ($file->isDot())
			{
				continue;
			}

			if (!$file->isDir())
			{
				continue;
			}

			$dir  = $folder . $ds . $file->getFilename();
			$data = $dir;

			if (_AKEEBA_IS_WINDOWS)
			{
				$data = Factory::getFilesystemTools()->TranslateWinPath($dir);
			}

			if ($data)
			{
				$arr[] = $data;
			}

			$counter++;

			if ($counter >= $maxCounter)
			{
				$breakflag = $allowBreakflag;
			}
		}

		// Save break flag status
		$registry->set('volatile.breakflag', $breakflag);

		return $arr;
	}
}
PK�!]�c����BackupEngine/Scan/smart.jsonnu&1i�{
    "_information": {
        "title": "COM_AKEEBA_CONFIG_ENGINE_SCAN_SMART_TITLE",
        "description": "COM_AKEEBA_CONFIG_ENGINE_SCAN_SMART_DESCRIPTION"
    },
    "engine.scan.smart.large_dir_threshold": {
        "default": "100",
        "type": "integer",
        "min": "0",
        "max": "500",
        "shortcuts": "20|50|100|200|300|400|500",
        "scale": "1",
        "uom": "",
        "title": "COM_AKEEBA_CONFIG_LARGEDIRTHRESHOLD_TITLE",
        "description": "COM_AKEEBA_CONFIG_LARGEDIRTHRESHOLD_DESCRIPTION"
    },
    "engine.scan.common.largefile": {
        "default": "10485760",
        "type": "integer",
        "min": "1048576",
        "max": "1048576000",
        "shortcuts": "1048576|2097152|5242880|10485760|15728640|20971520|26214400|31457280|41943040|52428800|78643200|104857600",
        "scale": "1048576",
        "uom": "MB",
        "title": "COM_AKEEBA_CONFIG_LARGEFILE_TITLE",
        "description": "COM_AKEEBA_CONFIG_LARGEFILE_DESCRIPTION"
    }
}PK�!]�Y��!!/BackupEngine/Base/Exceptions/ErrorException.phpnu&1i�<?php
/**
 * Akeeba Engine
 *
 * @package   akeebaengine
 * @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License version 3, or later
 *
 * 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, version 3.
 *
 * 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
 * <https://www.gnu.org/licenses/>.
 */

namespace Akeeba\Engine\Base\Exceptions;

defined('AKEEBAENGINE') || die();

use RuntimeException;

/**
 * An exception which leads to an error (and complete halt) in the backup process
 */
class ErrorException extends RuntimeException
{

}
PK�!]h}�1BackupEngine/Base/Exceptions/WarningException.phpnu&1i�<?php
/**
 * Akeeba Engine
 *
 * @package   akeebaengine
 * @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License version 3, or later
 *
 * 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, version 3.
 *
 * 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
 * <https://www.gnu.org/licenses/>.
 */

namespace Akeeba\Engine\Base\Exceptions;

defined('AKEEBAENGINE') || die();

use RuntimeException;

/**
 * An exception which leads to a warning in the backup process
 */
class WarningException extends RuntimeException
{

}
PK�!]��\U�:�:BackupEngine/Base/Part.phpnu&1i�<?php
/**
 * Akeeba Engine
 *
 * @package   akeebaengine
 * @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License version 3, or later
 *
 * 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, version 3.
 *
 * 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
 * <https://www.gnu.org/licenses/>.
 */

namespace Akeeba\Engine\Base;

defined('AKEEBAENGINE') || die();

use Akeeba\Engine\Base\Exceptions\ErrorException;
use Akeeba\Engine\Factory;
use Exception;
use Akeeba\Engine\Psr\Log\LogLevel;
use Throwable;

/**
 * Base class for all Akeeba Engine parts.
 *
 * Parts are objects which perform a specific function during the backup process, e.g. backing up files or dumping
 * database contents. They have a fully defined and controlled lifecycle, from initialization to finalization. The
 * transition between lifecycle phases is handled by the `tick()` method which is essentially the only public interface
 * to interacting with an engine part.
 */
abstract class Part
{
	public const STATE_INIT = 0;
	public const STATE_PREPARED = 1;
	public const STATE_RUNNING = 2;
	public const STATE_POSTRUN = 3;
	public const STATE_FINISHED = 4;
	public const STATE_ERROR = 99;

	/**
	 * The current state of this part; see the constants at the top of this class
	 *
	 * @var int
	 */
	protected $currentState = self::STATE_INIT;

	/**
	 * The name of the engine part (a.k.a. Domain), used in return table
	 * generation.
	 *
	 * @var string
	 */
	protected $activeDomain = "";

	/**
	 * The step this engine part is in. Used verbatim in return table and
	 * should be set by the code in the _run() method.
	 *
	 * @var string
	 */
	protected $activeStep = "";

	/**
	 * A more detailed description of the step this engine part is in. Used
	 * verbatim in return table and should be set by the code in the _run()
	 * method.
	 *
	 * @var string
	 */
	protected $activeSubstep = "";

	/**
	 * Any configuration variables, in the form of an array.
	 *
	 * @var array
	 */
	protected $_parametersArray = [];

	/**
	 * The database root key
	 *
	 * @var  string
	 */
	protected $databaseRoot = [];

	/**
	 * Should we log the step nesting?
	 *
	 * @var  bool
	 */
	protected $nest_logging = false;

	/**
	 * Embedded installer preferences
	 *
	 * @var  object
	 */
	protected $installerSettings;

	/**
	 * How much milliseconds should we wait to reach the min exec time
	 *
	 * @var  int
	 */
	protected $waitTimeMsec = 0;

	/**
	 * Should I ignore the minimum execution time altogether?
	 *
	 * @var  bool
	 */
	protected $ignoreMinimumExecutionTime = false;

	/**
	 * The last exception thrown during the tick() method's execution.
	 *
	 * @var null|Exception
	 */
	protected $lastException = null;

	public function _onSerialize()
	{
		$this->lastException = null;
	}

	/**
	 * Public constructor
	 *
	 * @return  void
	 */
	public function __construct()
	{
		// Fetch the installer settings
		$this->installerSettings = (object) [
			'installerroot' => 'installation',
			'sqlroot'       => 'installation/sql',
			'databasesini'  => 1,
			'readme'        => 1,
			'extrainfo'     => 1,
			'password'      => 0,
		];

		$config               = Factory::getConfiguration();
		$installerKey         = $config->get('akeeba.advanced.embedded_installer');
		$installerDescriptors = Factory::getEngineParamsProvider()->getInstallerList();

		// Fall back to default ANGIE installer if the selected installer is not found
		if (!array_key_exists($installerKey, $installerDescriptors))
		{
			$installerKey = 'angie';
		}

		if (array_key_exists($installerKey, $installerDescriptors))
		{
			$this->installerSettings = (object) $installerDescriptors[$installerKey];
		}
	}

	/**
	 * Nested logging of exceptions
	 *
	 * The message is logged using the specified log level. The detailed information of the Throwable and its trace are
	 * logged using the DEBUG level.
	 *
	 * If the Throwable is nested, its parents are logged recursively. This should create a thorough trace leading to
	 * the root cause of an error.
	 *
	 * @param   Exception|Throwable  $exception  The Exception or Throwable to log
	 * @param   string               $logLevel   The log level to use, default ERROR
	 */
	protected static function logErrorsFromException($exception, $logLevel = LogLevel::ERROR)
	{
		$logger = Factory::getLog();

		$logger->log($logLevel, $exception->getMessage());

		$logger->debug(sprintf('[%s] %s(%u) – #%u ‹%s›', get_class($exception), $exception->getFile(), $exception->getLine(), $exception->getCode(), $exception->getMessage()));

		foreach (explode("\n", $exception->getTraceAsString()) as $line)
		{
			$logger->debug(rtrim($line));
		}

		$previous = $exception->getPrevious();

		if (!is_null($previous))
		{
			self::logErrorsFromException($previous, $logLevel);
		}
	}

	/**
	 * 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 response array.
	 *
	 * @param   int  $nesting
	 *
	 * @return  array  A response array
	 */
	public function tick($nesting = 0)
	{
		$configuration       = Factory::getConfiguration();
		$timer               = Factory::getTimer();
		$this->waitTimeMsec  = 0;
		$this->lastException = null;

		// Add a small wait based on the existence of a constant. Used in testing, to simulate slow servers.
		if (defined('AKEEBA_BACKUP_TESTING_STEP_THROTTLING'))
		{
			/** @noinspection PhpUndefinedConstantInspection */
			usleep((int) AKEEBA_BACKUP_TESTING_STEP_THROTTLING);
		}

		/**
		 * Call the right action method, depending on engine part state.
		 *
		 * The action method may throw an exception to signal failure, hence the try-catch. If there is an exception we
		 * will set the part's state to STATE_ERROR and store the last exception.
		 */
		try
		{
			switch ($this->getState())
			{
				case self::STATE_INIT:
					$this->_prepare();
					break;

				case self::STATE_PREPARED:
				case self::STATE_RUNNING:
					$this->_run();
					break;

				case self::STATE_POSTRUN:
					$this->_finalize();
					break;
			}
		}
		catch (Exception $e)
		{
			$this->lastException = $e;
			$this->setState(self::STATE_ERROR);
		}

		// 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(), [self::STATE_FINISHED, self::STATE_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)
			{
				Factory::getLog()->debug("*** Batching successive steps (nesting level $nesting)");
			}

			return $this->tick($nesting);
		}

		// Return the output array
		$out = $this->makeReturnTable();

		// If it's not a nest-logged part (basically, anything other than Kettenrad) return the output array.
		if (!$this->nest_logging)
		{
			return $out;
		}

		// From here on: things to do for nest-logged parts (i.e. Kettenrad)
		if ($breakFlag)
		{
			Factory::getLog()->debug("*** Engine steps batching: Break flag detected.");
		}

		// Reset the break flag
		$configuration->set('volatile.breakflag', false);

		// Log that we're breaking the step
		Factory::getLog()->debug("*** Batching of engine steps finished. I will now return control to the caller.");

		// Detect whether I need server-side sleep
		$serverSideSleep = $this->needsServerSideSleep();

		// Enforce minimum execution time
		if (!$this->ignoreMinimumExecutionTime)
		{
			$timer              = Factory::getTimer();
			$this->waitTimeMsec = (int) $timer->enforce_min_exec_time(true, $serverSideSleep);
		}

		// Send a Return Table back to the caller
		return $out;
	}

	/**
	 * Returns a copy of the class's status array
	 *
	 * @return  array  The response array
	 */
	public function getStatusArray()
	{
		return $this->makeReturnTable();
	}

	/**
	 * Sends any kind of setup information to the engine part. Using this,
	 * we avoid passing parameters to the constructor of the class. These
	 * parameters should be passed as an indexed array and should be taken
	 * into account during the preparation process only. This function will
	 * set the error flag if it's called after the engine part is prepared.
	 *
	 * @param   array  $parametersArray  The parameters to be passed to the engine part.
	 *
	 * @return  void
	 */
	public function setup($parametersArray)
	{
		if ($this->currentState == self::STATE_PREPARED)
		{
			$this->setState(self::STATE_ERROR);

			throw new ErrorException(__CLASS__ . ":: Can't modify configuration after the preparation of " . $this->activeDomain);
		}

		$this->_parametersArray = $parametersArray;

		if (array_key_exists('root', $parametersArray))
		{
			$this->databaseRoot = $parametersArray['root'];
		}
	}

	/**
	 * Returns the state of this engine part.
	 *
	 * @return  int  The state of this engine part.
	 */
	public function getState()
	{
		if (!is_null($this->lastException))
		{
			$this->currentState = self::STATE_ERROR;
		}

		return $this->currentState;
	}

	/**
	 * Translate the integer state to a string, used by consumers of the public Engine API.
	 *
	 * @param   int  $state  The part state to translate to string
	 *
	 * @return  string
	 */
	public function stateToString($state)
	{
		switch ($state)
		{
			case self::STATE_ERROR:
				return 'error';
				break;

			case self::STATE_INIT:
				return 'init';
				break;

			case self::STATE_PREPARED:
				return 'prepared';
				break;

			case self::STATE_RUNNING:
				return 'running';
				break;

			case self::STATE_POSTRUN:
				return 'postrun';
				break;

			case self::STATE_FINISHED:
				return 'finished';
				break;
		}

		return 'init';
	}

	/**
	 * Get the current domain of the engine
	 *
	 * @return  string  The current domain
	 */
	public function getDomain()
	{
		return $this->activeDomain;
	}

	/**
	 * Get the current step of the engine
	 *
	 * @return  string  The current step
	 */
	public function getStep()
	{
		return $this->activeStep;
	}

	/**
	 * Get the current sub-step of the engine
	 *
	 * @return  string  The current sub-step
	 */
	public function getSubstep()
	{
		return $this->activeSubstep;
	}

	/**
	 * Implement this if your Engine Part can return the percentage of its work already complete
	 *
	 * @return  float  A number from 0 (nothing done) to 1 (all done)
	 */
	public function getProgress()
	{
		return 0;
	}

	/**
	 * Get the value of the minimum execution time ignore flag.
	 *
	 * DO NOT REMOVE. It is used by the Engine consumers.
	 *
	 * @return boolean
	 */
	public function isIgnoreMinimumExecutionTime()
	{
		return $this->ignoreMinimumExecutionTime;
	}

	/**
	 * Set the value of the minimum execution time ignore flag. When set, the nested logging parts (basically,
	 * Kettenrad) will ignore the minimum execution time parameter.
	 *
	 * DO NOT REMOVE. It is used by the Engine consumers.
	 *
	 * @param   boolean  $ignoreMinimumExecutionTime
	 */
	public function setIgnoreMinimumExecutionTime($ignoreMinimumExecutionTime)
	{
		$this->ignoreMinimumExecutionTime = $ignoreMinimumExecutionTime;
	}

	/**
	 * Runs any initialization code. Must set the state to STATE_PREPARED.
	 *
	 * @return  void
	 */
	abstract protected function _prepare();

	/**
	 * Runs any finalisation code. Must set the state to STATE_FINISHED.
	 *
	 * @return  void
	 */
	abstract protected function _finalize();

	/**
	 * Performs the main objective of this part. While still processing the state must be set to STATE_RUNNING. When the
	 * main objective is complete and we're ready to proceed to finalization the state must be set to STATE_POSTRUN.
	 *
	 * @return  void
	 */
	abstract protected function _run();

	/**
	 * Sets the BREAKFLAG, which instructs this engine part that the current step must break immediately,
	 * in fear of timing out.
	 *
	 * @return  void
	 */
	protected function setBreakFlag()
	{
		$registry = Factory::getConfiguration();
		$registry->set('volatile.breakflag', true);
	}

	/**
	 * Sets the engine part's internal state, in an easy to use manner
	 *
	 * @param   int  $state  The part state to set
	 *
	 * @return  void
	 */
	protected function setState($state = self::STATE_INIT)
	{
		$this->currentState = $state;
	}

	/**
	 * Constructs a Response Array based on the engine part's state.
	 *
	 * @return  array  The Response Array for the current state
	 */
	protected function makeReturnTable()
	{
		$errors = [];
		$e      = $this->lastException;

		while (!empty($e))
		{
			$errors[] = $e->getMessage();
			$e        = $e->getPrevious();
		}

		return [
			'HasRun'         => $this->currentState != self::STATE_FINISHED,
			'Domain'         => $this->activeDomain,
			'Step'           => $this->activeStep,
			'Substep'        => $this->activeSubstep,
			'Error'          => implode("\n", $errors),
			'Warnings'       => [],
			'ErrorException' => $this->lastException,
		];
	}

	/**
	 * Set the current domain of the engine
	 *
	 * @param   string  $new_domain  The domain to set
	 *
	 * @return  void
	 */
	protected function setDomain($new_domain)
	{
		$this->activeDomain = $new_domain;
	}

	/**
	 * Set the current step of the engine
	 *
	 * @param   string  $new_step  The step to set
	 *
	 * @return  void
	 */
	protected function setStep($new_step)
	{
		$this->activeStep = $new_step;
	}

	/**
	 * Set the current sub-step of the engine
	 *
	 * @param   string  $new_substep  The sub-step to set
	 *
	 * @return  void
	 */
	protected function setSubstep($new_substep)
	{
		$this->activeSubstep = $new_substep;
	}

	/**
	 * Do I need to apply server-side sleep for the time difference between the elapsed time and the minimum execution
	 * time?
	 *
	 * @return bool
	 */
	private function needsServerSideSleep()
	{
		/**
		 * If the part doesn't support tagging, i.e. I can't determine if this is a backend backup or not, I will always
		 * use server-side sleep.
		 */
		if (!method_exists($this, 'getTag'))
		{
			return true;
		}

		/**
		 * If this is not a backend backup I will always use server-side sleep. That is to say that legacy front-end,
		 * remote JSON API and CLI backups must always use server-side sleep since they do not support client-side
		 * sleep.
		 */
		if (!in_array($this->getTag(), ['backend']))
		{
			return true;
		}

		return Factory::getConfiguration()->get('akeeba.basic.clientsidewait', 0) == 0;
	}
}
PK�!]+0"���'BackupEngine/Psr/Log/AbstractLogger.phpnu&1i�<?php
/**
 * Akeeba Engine
 *
 * @package   akeebaengine
 * @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License version 3, or later
 *
 * 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, version 3.
 *
 * 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
 * <https://www.gnu.org/licenses/>.
 */

namespace Akeeba\Engine\Psr\Log;

/**
 * This file is part of a privately namespaced copy of PSR-3 version 1.
 *
 * You can find the original PSR-3 in https://www.php-fig.org/psr/psr-3/ and the original code in
 * https://github.com/php-fig/log
 *
 * The license of the original code can be found below.
 *
 * Copyright (c) 2012 PHP Framework Interoperability Group
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

defined('AKEEBAENGINE') || die();

/**
 * This is a simple Logger implementation that other Loggers can inherit from.
 *
 * It simply delegates all log-level-specific methods to the `log` method to
 * reduce boilerplate code that a simple Logger that does the same thing with
 * messages regardless of the error level has to implement.
 */
abstract class AbstractLogger implements LoggerInterface
{
	/**
	 * System is unusable.
	 *
	 * @param   string  $message
	 * @param   array   $context
	 *
	 * @return void
	 */
	public function emergency($message, array $context = [])
	{
		$this->log(LogLevel::EMERGENCY, $message, $context);
	}

	/**
	 * Action must be taken immediately.
	 *
	 * Example: Entire website down, database unavailable, etc. This should
	 * trigger the SMS alerts and wake you up.
	 *
	 * @param   string  $message
	 * @param   array   $context
	 *
	 * @return void
	 */
	public function alert($message, array $context = [])
	{
		$this->log(LogLevel::ALERT, $message, $context);
	}

	/**
	 * Critical conditions.
	 *
	 * Example: Application component unavailable, unexpected exception.
	 *
	 * @param   string  $message
	 * @param   array   $context
	 *
	 * @return void
	 */
	public function critical($message, array $context = [])
	{
		$this->log(LogLevel::CRITICAL, $message, $context);
	}

	/**
	 * Runtime errors that do not require immediate action but should typically
	 * be logged and monitored.
	 *
	 * @param   string  $message
	 * @param   array   $context
	 *
	 * @return void
	 */
	public function error($message, array $context = [])
	{
		$this->log(LogLevel::ERROR, $message, $context);
	}

	/**
	 * Exceptional occurrences that are not errors.
	 *
	 * Example: Use of deprecated APIs, poor use of an API, undesirable things
	 * that are not necessarily wrong.
	 *
	 * @param   string  $message
	 * @param   array   $context
	 *
	 * @return void
	 */
	public function warning($message, array $context = [])
	{
		$this->log(LogLevel::WARNING, $message, $context);
	}

	/**
	 * Normal but significant events.
	 *
	 * @param   string  $message
	 * @param   array   $context
	 *
	 * @return void
	 */
	public function notice($message, array $context = [])
	{
		$this->log(LogLevel::NOTICE, $message, $context);
	}

	/**
	 * Interesting events.
	 *
	 * Example: User logs in, SQL logs.
	 *
	 * @param   string  $message
	 * @param   array   $context
	 *
	 * @return void
	 */
	public function info($message, array $context = [])
	{
		$this->log(LogLevel::INFO, $message, $context);
	}

	/**
	 * Detailed debug information.
	 *
	 * @param   string  $message
	 * @param   array   $context
	 *
	 * @return void
	 */
	public function debug($message, array $context = [])
	{
		$this->log(LogLevel::DEBUG, $message, $context);
	}
}
PK�!]�	~�>	>	1BackupEngine/Psr/Log/InvalidArgumentException.phpnu&1i�<?php
/**
 * Akeeba Engine
 *
 * @package   akeebaengine
 * @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License version 3, or later
 *
 * 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, version 3.
 *
 * 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
 * <https://www.gnu.org/licenses/>.
 */

namespace Akeeba\Engine\Psr\Log;

/**
 * This file is part of a privately namespaced copy of PSR-3 version 1.
 *
 * You can find the original PSR-3 in https://www.php-fig.org/psr/psr-3/ and the original code in
 * https://github.com/php-fig/log
 *
 * The license of the original code can be found below.
 *
 * Copyright (c) 2012 PHP Framework Interoperability Group
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

defined('AKEEBAENGINE') || die();

class InvalidArgumentException extends \InvalidArgumentException
{
}
PK�!]�ͭVV$BackupEngine/Psr/Log/LoggerTrait.phpnu&1i�<?php
/**
 * Akeeba Engine
 *
 * @package   akeebaengine
 * @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License version 3, or later
 *
 * 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, version 3.
 *
 * 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
 * <https://www.gnu.org/licenses/>.
 */

namespace Akeeba\Engine\Psr\Log;

/**
 * This file is part of a privately namespaced copy of PSR-3 version 1.
 *
 * You can find the original PSR-3 in https://www.php-fig.org/psr/psr-3/ and the original code in
 * https://github.com/php-fig/log
 *
 * The license of the original code can be found below.
 *
 * Copyright (c) 2012 PHP Framework Interoperability Group
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

defined('AKEEBAENGINE') || die();

/**
 * This is a simple Logger trait that classes unable to extend AbstractLogger
 * (because they extend another class, etc) can include.
 *
 * It simply delegates all log-level-specific methods to the `log` method to
 * reduce boilerplate code that a simple Logger that does the same thing with
 * messages regardless of the error level has to implement.
 */
trait LoggerTrait
{
	/**
	 * System is unusable.
	 *
	 * @param   string  $message
	 * @param   array   $context
	 *
	 * @return null
	 */
	public function emergency($message, array $context = [])
	{
		$this->log(LogLevel::EMERGENCY, $message, $context);
	}

	/**
	 * Action must be taken immediately.
	 *
	 * Example: Entire website down, database unavailable, etc. This should
	 * trigger the SMS alerts and wake you up.
	 *
	 * @param   string  $message
	 * @param   array   $context
	 *
	 * @return null
	 */
	public function alert($message, array $context = [])
	{
		$this->log(LogLevel::ALERT, $message, $context);
	}

	/**
	 * Critical conditions.
	 *
	 * Example: Application component unavailable, unexpected exception.
	 *
	 * @param   string  $message
	 * @param   array   $context
	 *
	 * @return null
	 */
	public function critical($message, array $context = [])
	{
		$this->log(LogLevel::CRITICAL, $message, $context);
	}

	/**
	 * Runtime errors that do not require immediate action but should typically
	 * be logged and monitored.
	 *
	 * @param   string  $message
	 * @param   array   $context
	 *
	 * @return null
	 */
	public function error($message, array $context = [])
	{
		$this->error($message, $context);
	}

	/**
	 * Exceptional occurrences that are not errors.
	 *
	 * Example: Use of deprecated APIs, poor use of an API, undesirable things
	 * that are not necessarily wrong.
	 *
	 * @param   string  $message
	 * @param   array   $context
	 *
	 * @return null
	 */
	public function warning($message, array $context = [])
	{
		$this->warning($message, $context);
	}

	/**
	 * Normal but significant events.
	 *
	 * @param   string  $message
	 * @param   array   $context
	 *
	 * @return null
	 */
	public function notice($message, array $context = [])
	{
		$this->log(LogLevel::NOTICE, $message, $context);
	}

	/**
	 * Interesting events.
	 *
	 * Example: User logs in, SQL logs.
	 *
	 * @param   string  $message
	 * @param   array   $context
	 *
	 * @return null
	 */
	public function info($message, array $context = [])
	{
		$this->info($message, $context);
	}

	/**
	 * Detailed debug information.
	 *
	 * @param   string  $message
	 * @param   array   $context
	 *
	 * @return null
	 */
	public function debug($message, array $context = [])
	{
		$this->debug($message, $context);
	}

	/**
	 * Logs with an arbitrary level.
	 *
	 * @param   mixed   $level
	 * @param   string  $message
	 * @param   array   $context
	 *
	 * @return null
	 */
	abstract public function log($level, $message, array $context = []);
}
PK�!]�G;�

)BackupEngine/Psr/Log/LoggerAwareTrait.phpnu&1i�<?php
/**
 * Akeeba Engine
 *
 * @package   akeebaengine
 * @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License version 3, or later
 *
 * 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, version 3.
 *
 * 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
 * <https://www.gnu.org/licenses/>.
 */

namespace Akeeba\Engine\Psr\Log;

/**
 * This file is part of a privately namespaced copy of PSR-3 version 1.
 *
 * You can find the original PSR-3 in https://www.php-fig.org/psr/psr-3/ and the original code in
 * https://github.com/php-fig/log
 *
 * The license of the original code can be found below.
 *
 * Copyright (c) 2012 PHP Framework Interoperability Group
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

defined('AKEEBAENGINE') || die();

/**
 * Basic Implementation of LoggerAwareInterface.
 */
trait LoggerAwareTrait
{
	/** @var LoggerInterface */
	protected $logger;

	/**
	 * Sets a logger.
	 *
	 * @param   LoggerInterface  $logger
	 */
	public function setLogger(LoggerInterface $logger)
	{
		$this->logger = $logger;
	}
}
PK�!]w����	�	-BackupEngine/Psr/Log/LoggerAwareInterface.phpnu&1i�<?php
/**
 * Akeeba Engine
 *
 * @package   akeebaengine
 * @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License version 3, or later
 *
 * 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, version 3.
 *
 * 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
 * <https://www.gnu.org/licenses/>.
 */

namespace Akeeba\Engine\Psr\Log;

/**
 * This file is part of a privately namespaced copy of PSR-3 version 1.
 *
 * You can find the original PSR-3 in https://www.php-fig.org/psr/psr-3/ and the original code in
 * https://github.com/php-fig/log
 *
 * The license of the original code can be found below.
 *
 * Copyright (c) 2012 PHP Framework Interoperability Group
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

defined('AKEEBAENGINE') || die();

/**
 * Describes a logger-aware instance
 */
interface LoggerAwareInterface
{
	/**
	 * Sets a logger instance on the object
	 *
	 * @param   LoggerInterface  $logger
	 *
	 * @return null
	 */
	public function setLogger(LoggerInterface $logger);
}
PK�!]�<^�	�	!BackupEngine/Psr/Log/LogLevel.phpnu&1i�<?php
/**
 * Akeeba Engine
 *
 * @package   akeebaengine
 * @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License version 3, or later
 *
 * 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, version 3.
 *
 * 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
 * <https://www.gnu.org/licenses/>.
 */

namespace Akeeba\Engine\Psr\Log;

/**
 * This file is part of a privately namespaced copy of PSR-3 version 1.
 *
 * You can find the original PSR-3 in https://www.php-fig.org/psr/psr-3/ and the original code in
 * https://github.com/php-fig/log
 *
 * The license of the original code can be found below.
 *
 * Copyright (c) 2012 PHP Framework Interoperability Group
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

defined('AKEEBAENGINE') || die();

/**
 * Describes log levels
 */
class LogLevel
{
	const EMERGENCY = 'emergency';
	const ALERT = 'alert';
	const CRITICAL = 'critical';
	const ERROR = 'error';
	const WARNING = 'warning';
	const NOTICE = 'notice';
	const INFO = 'info';
	const DEBUG = 'debug';
}
PK�!]7'�L��(BackupEngine/Psr/Log/LoggerInterface.phpnu&1i�<?php
/**
 * Akeeba Engine
 *
 * @package   akeebaengine
 * @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License version 3, or later
 *
 * 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, version 3.
 *
 * 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
 * <https://www.gnu.org/licenses/>.
 */

namespace Akeeba\Engine\Psr\Log;

/**
 * This file is part of a privately namespaced copy of PSR-3 version 1.
 *
 * You can find the original PSR-3 in https://www.php-fig.org/psr/psr-3/ and the original code in
 * https://github.com/php-fig/log
 *
 * The license of the original code can be found below.
 *
 * Copyright (c) 2012 PHP Framework Interoperability Group
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

defined('AKEEBAENGINE') || die();

/**
 * Describes a logger instance
 *
 * The message MUST be a string or object implementing __toString().
 *
 * The message MAY contain placeholders in the form: {foo} where foo
 * will be replaced by the context data in key "foo".
 *
 * The context array can contain arbitrary data, the only assumption that
 * can be made by implementors is that if an Exception instance is given
 * to produce a stack trace, it MUST be in a key named "exception".
 *
 * See https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md
 * for the full interface specification.
 */
interface LoggerInterface
{
	/**
	 * System is unusable.
	 *
	 * @param   string  $message
	 * @param   array   $context
	 *
	 * @return null
	 */
	public function emergency($message, array $context = []);

	/**
	 * Action must be taken immediately.
	 *
	 * Example: Entire website down, database unavailable, etc. This should
	 * trigger the SMS alerts and wake you up.
	 *
	 * @param   string  $message
	 * @param   array   $context
	 *
	 * @return null
	 */
	public function alert($message, array $context = []);

	/**
	 * Critical conditions.
	 *
	 * Example: Application component unavailable, unexpected exception.
	 *
	 * @param   string  $message
	 * @param   array   $context
	 *
	 * @return null
	 */
	public function critical($message, array $context = []);

	/**
	 * Runtime errors that do not require immediate action but should typically
	 * be logged and monitored.
	 *
	 * @param   string  $message
	 * @param   array   $context
	 *
	 * @return null
	 */
	public function error($message, array $context = []);

	/**
	 * Exceptional occurrences that are not errors.
	 *
	 * Example: Use of deprecated APIs, poor use of an API, undesirable things
	 * that are not necessarily wrong.
	 *
	 * @param   string  $message
	 * @param   array   $context
	 *
	 * @return null
	 */
	public function warning($message, array $context = []);

	/**
	 * Normal but significant events.
	 *
	 * @param   string  $message
	 * @param   array   $context
	 *
	 * @return null
	 */
	public function notice($message, array $context = []);

	/**
	 * Interesting events.
	 *
	 * Example: User logs in, SQL logs.
	 *
	 * @param   string  $message
	 * @param   array   $context
	 *
	 * @return null
	 */
	public function info($message, array $context = []);

	/**
	 * Detailed debug information.
	 *
	 * @param   string  $message
	 * @param   array   $context
	 *
	 * @return null
	 */
	public function debug($message, array $context = []);

	/**
	 * Logs with an arbitrary level.
	 *
	 * @param   mixed   $level
	 * @param   string  $message
	 * @param   array   $context
	 *
	 * @return null
	 */
	public function log($level, $message, array $context = []);
}
PK�!]>	��BB#BackupEngine/Psr/Log/NullLogger.phpnu&1i�<?php
/**
 * Akeeba Engine
 *
 * @package   akeebaengine
 * @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License version 3, or later
 *
 * 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, version 3.
 *
 * 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
 * <https://www.gnu.org/licenses/>.
 */

namespace Akeeba\Engine\Psr\Log;

/**
 * This file is part of a privately namespaced copy of PSR-3 version 1.
 *
 * You can find the original PSR-3 in https://www.php-fig.org/psr/psr-3/ and the original code in
 * https://github.com/php-fig/log
 *
 * The license of the original code can be found below.
 *
 * Copyright (c) 2012 PHP Framework Interoperability Group
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

defined('AKEEBAENGINE') || die();

/**
 * This Logger can be used to avoid conditional log calls
 *
 * Logging should always be optional, and if no logger is provided to your
 * library creating a NullLogger instance to have something to throw logs at
 * is a good way to avoid littering your code with `if ($this->logger) { }`
 * blocks.
 */
class NullLogger extends AbstractLogger
{
	/**
	 * Logs with an arbitrary level.
	 *
	 * @param   mixed   $level
	 * @param   string  $message
	 * @param   array   $context
	 *
	 * @return null
	 */
	public function log($level, $message, array $context = [])
	{
		// noop
	}
}
PK�!]|�"yy%BackupEngine/Postproc/Onedriveapp.phpnu&1i�<?php
/**
 * Akeeba Engine
 *
 * @package   akeebaengine
 * @copyright Copyright (c)2024 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License version 3, or later
 *
 * 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, version 3.
 *
 * 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
 * <https://www.gnu.org/licenses/>.
 */

namespace Akeeba\Engine\Postproc;

use Akeeba\Engine\Factory;
use Akeeba\Engine\Platform;
use Akeeba\Engine\Postproc\Connector\OneDriveApp as ConnectorOneDrive;
use Akeeba\Engine\Postproc\Exception\BadConfiguration;

class Onedriveapp extends Onedrivebusiness
{
	/**
	 * The name of the OAuth2 callback method in the parent window (the configuration page)
	 *
	 * @var   string
	 */
	protected $callbackMethod = 'akeeba_onedriveapp_oauth_callback';

	/**
	 * The key in Akeeba Engine's settings registry for this post-processing method
	 *
	 * @var   string
	 */
	protected $settingsKey = 'onedriveapp';

	public function __construct()
	{
		parent::__construct();

		// This OneDrive integration does not allow the getDrives method.
		array_pop($this->allowedCustomAPICallMethods);
	}

	protected function makeConnector()
	{
		// Retrieve engine configuration data
		$config = Factory::getConfiguration();

		$access_token  = trim($config->get('engine.postproc.' . $this->settingsKey . '.access_token', ''));
		$refresh_token = trim($config->get('engine.postproc.' . $this->settingsKey . '.refresh_token', ''));

		$this->isChunked  = $config->get('engine.postproc.' . $this->settingsKey . '.chunk_upload', true);
		$this->chunkSize  = $config->get('engine.postproc.' . $this->settingsKey . '.chunk_upload_size', 10) * 1024 * 1024;
		$defaultDirectory = rtrim($config->get('engine.postproc.' . $this->settingsKey . '.directory', ''), '/');
		$this->directory  = $config->get('volatile.postproc.directory', $defaultDirectory);

		// Sanity checks
		if (empty($refresh_token))
		{
			throw new BadConfiguration('You have not linked Akeeba Backup with your OneDrive account');
		}

		if (!function_exists('curl_init'))
		{
			throw new BadConfiguration('cURL is not enabled, please enable it in order to post-process your archives');
		}

		// Fix the directory name, if required
		$this->directory = empty($this->directory) ? '' : $this->directory;
		$this->directory = trim($this->directory);
		$this->directory = ltrim(Factory::getFilesystemTools()->TranslateWinPath($this->directory), '/');
		$this->directory = Factory::getFilesystemTools()->replace_archive_name_variables($this->directory);
		$config->set('volatile.postproc.directory', $this->directory);

		// Get Download ID
		$dlid = Platform::getInstance()->get_platform_configuration_option('update_dlid', '');

		if (empty($dlid))
		{
			throw new BadConfiguration('You must enter your Download ID in the application configuration before using the “Upload to OneDrive” feature.');
		}

		$connector = new ConnectorOneDrive($access_token, $refresh_token, $dlid);

		// Validate the tokens
		Factory::getLog()->debug(__METHOD__ . " - Validating the OneDrive tokens");
		$pingResult = $connector->ping();

		// Save new configuration if there was a refresh
		if ($pingResult['needs_refresh'])
		{
			Factory::getLog()->debug(__METHOD__ . " - OneDrive tokens were refreshed");
			$config->set('engine.postproc.' . $this->settingsKey . '.access_token', $pingResult['access_token'], false);
			$config->set('engine.postproc.' . $this->settingsKey . '.refresh_token', $pingResult['refresh_token'], false);

			$profile_id = Platform::getInstance()->get_active_profile();
			Platform::getInstance()->save_configuration($profile_id);
		}

		return $connector;
	}

	protected function getOAuth2HelperUrl()
	{
		return ConnectorOneDrive::helperUrl;
	}
}PK�!]��	�	3BackupEngine/Postproc/onedrivebusiness.ini-disablednu&1i�; Akeeba Upload to OneDrive for Business post processing engine
; Copyright (c)2006-2018 Nicholas K. Dionysopoulos / Akeeba Ltd
;
; Sorry, we had to cancel this feature.
;
; Microsoft requires us to register the app in the Azure Directory per
; https://dev.onedrive.com/app-registration-server.htm  Despite repeated attempts at following their instructions it
; seems to be impossible. There is no "midsize business" plan, the regular subscription doesn'tlet you create the
; "private site" required for development and both enterprise and developer accounts can't seem to be able to be
; purchased. Apparently the only way to make this work is having a US-based enterprise?! So, sorry, we won't waste any
; more time on this.
;

; Engine information
[_information]
title=COM_AKEEBA_CONFIG_ENGINE_POSTPROC_ONEDRIVEBUSINESS_TITLE
description=COM_AKEEBA_CONFIG_ENGINE_POSTPROC_ONEDRIVEBUSINESS_DESCRIPTION

; Post-process after generating each part?
[engine.postproc.common.after_part]
default=0
type=bool
title=COM_AKEEBA_CONFIG_POSTPROCPARTS_TITLE
description=COM_AKEEBA_CONFIG_POSTPROCPARTS_DESCRIPTION

; Delete from server after processing?
[engine.postproc.common.delete_after]
default=1
type=bool
title=COM_AKEEBA_CONFIG_DELETEAFTER_TITLE
description=COM_AKEEBA_CONFIG_DELETEAFTER_DESCRIPTION

; Enable chunk upload?
[engine.postproc.onedrivebusiness.chunk_upload]
default=1
type=bool
title=COM_AKEEBA_CONFIG_BOX_CHUNKUPLOAD_ENABLE

; Chunk size in megabytes
[engine.postproc.onedrivebusiness.chunk_upload_size]
default=10
type=integer
min=4
max=60
shortcuts="5|10|20|40|60"
scale=1
uom=MB
title=COM_AKEEBA_CONFIG_BOX_CHUNKUPLOAD_SIZE

; Open OAuth
[engine.postproc.onedrivebusiness.openoauth]
default=""
type=button
title=COM_AKEEBA_CONFIG_BOX_OPENOAUTH_TITLE
description=COM_AKEEBA_CONFIG_BOX_OPENOAUTH_DESC
hook=akconfig_onedrivebusiness_openoauth

; OneDrive Directory name
[engine.postproc.onedrivebusiness.directory]
default="/"
type=string
title=COM_AKEEBA_CONFIG_ONEDRIVEBUSINESS_DIRECTORY_TITLE
description=COM_AKEEBA_CONFIG_ONEDRIVEBUSINESS_DIRECTORY_DESCRIPTION

[engine.postproc.onedrivebusiness.service_id]
default = ""
type=string
title=COM_AKEEBA_CONFIG_ONEDRIVEBUSINESS_SERVICEID_TITLE
description=COM_AKEEBA_CONFIG_ONEDRIVEBUSINESS_SERVICEID_DESCRIPTION

[engine.postproc.onedrivebusiness.refresh_token]
default = ""
type=string
title=COM_AKEEBA_CONFIG_ONEDRIVEBUSINESS_REFRESHTOKEN_TITLE
description=COM_AKEEBA_CONFIG_ONEDRIVEBUSINESS_REFRESHTOKEN_DESCRIPTION
PK�!]��8�VVBackupEngine/Postproc/Email.phpnu&1i�<?php
/**
 * Akeeba Engine
 *
 * @package   akeebaengine
 * @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License version 3, or later
 *
 * 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, version 3.
 *
 * 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
 * <https://www.gnu.org/licenses/>.
 */

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_AKEEBA_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_AKEEBA_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;
	}


}
PK�!]��y��BackupEngine/Postproc/Base.phpnu&1i�<?php
/**
 * Akeeba Engine
 *
 * @package   akeebaengine
 * @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License version 3, or later
 *
 * 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, version 3.
 *
 * 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
 * <https://www.gnu.org/licenses/>.
 */

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();
}
PK�!]����+BackupEngine/Postproc/PostProcInterface.phpnu&1i�<?php
/**
 * Akeeba Engine
 *
 * @package   akeebaengine
 * @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License version 3, or later
 *
 * 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, version 3.
 *
 * 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
 * <https://www.gnu.org/licenses/>.
 */

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();
}
PK�!]��4�00 BackupEngine/Postproc/email.jsonnu&1i�{
    "_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.abort_on_fail": {
        "default": "0",
        "type": "bool",
        "title": "COM_AKEEBA_CONFIG_POSTPROC_ABORT_ON_FAIL_TITLE",
        "description": "COM_AKEEBA_CONFIG_POSTPROC_ABORT_ON_FAIL_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"
    }
}PK�!]�̵��BackupEngine/Postproc/None.phpnu&1i�<?php
/**
 * Akeeba Engine
 *
 * @package   akeebaengine
 * @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License version 3, or later
 *
 * 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, version 3.
 *
 * 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
 * <https://www.gnu.org/licenses/>.
 */

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',
		];
	}
}
PK�!]����	�	$BackupEngine/Postproc/ProxyAware.phpnu&1i�<?php
/**
 * Akeeba Engine
 *
 * @package   akeebaengine
 * @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License version 3, or later
 *
 * 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, version 3.
 *
 * 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
 * <https://www.gnu.org/licenses/>.
 */

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)
	{
		if (defined('AKEEBA_NO_PROXY_AWARE'))
		{
			return;
		}

		$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()
	{
		if (defined('AKEEBA_NO_PROXY_AWARE'))
		{
			return [];
		}

		$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;
	}
}PK�!]�4����ABackupEngine/Postproc/Exception/DownloadToBrowserNotSupported.phpnu&1i�<?php
/**
 * Akeeba Engine
 *
 * @package   akeebaengine
 * @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License version 3, or later
 *
 * 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, version 3.
 *
 * 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
 * <https://www.gnu.org/licenses/>.
 */

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.';
}
PK�!]R����5BackupEngine/Postproc/Exception/OAuthNotSupported.phpnu&1i�<?php
/**
 * Akeeba Engine
 *
 * @package   akeebaengine
 * @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License version 3, or later
 *
 * 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, version 3.
 *
 * 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
 * <https://www.gnu.org/licenses/>.
 */

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.';
}
PK�!]��@BackupEngine/Postproc/Exception/DownloadToServerNotSupported.phpnu&1i�<?php
/**
 * Akeeba Engine
 *
 * @package   akeebaengine
 * @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License version 3, or later
 *
 * 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, version 3.
 *
 * 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
 * <https://www.gnu.org/licenses/>.
 */

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.';
}
PK�!]��2x��=BackupEngine/Postproc/Exception/RangeDownloadNotSupported.phpnu&1i�<?php
/**
 * Akeeba Engine
 *
 * @package   akeebaengine
 * @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License version 3, or later
 *
 * 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, version 3.
 *
 * 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
 * <https://www.gnu.org/licenses/>.
 */

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.';
}
PK�!]�6��4BackupEngine/Postproc/Exception/BadConfiguration.phpnu&1i�<?php
/**
 * Akeeba Engine
 *
 * @package   akeebaengine
 * @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License version 3, or later
 *
 * 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, version 3.
 *
 * 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
 * <https://www.gnu.org/licenses/>.
 */

namespace Akeeba\Engine\Postproc\Exception;

defined('AKEEBAENGINE') || die();

use RuntimeException;

/**
 * Indicates an error with the post-processing engine's configuration
 */
class BadConfiguration extends RuntimeException
{
}
PK�!]����GG3BackupEngine/Postproc/Exception/EngineException.phpnu&1i�<?php
/**
 * Akeeba Engine
 *
 * @package   akeebaengine
 * @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License version 3, or later
 *
 * 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, version 3.
 *
 * 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
 * <https://www.gnu.org/licenses/>.
 */

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';
	}
}
PK�!]j���6BackupEngine/Postproc/Exception/DeleteNotSupported.phpnu&1i�<?php
/**
 * Akeeba Engine
 *
 * @package   akeebaengine
 * @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License version 3, or later
 *
 * 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, version 3.
 *
 * 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
 * <https://www.gnu.org/licenses/>.
 */

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.';
}
PK�!]Ğ^���BackupEngine/Postproc/none.jsonnu&1i�{
    "_information": {
        "title": "COM_AKEEBA_CONFIG_ENGINE_POSTPROC_NONE_TITLE",
        "description": "COM_AKEEBA_CONFIG_ENGINE_POSTPROC_NONE_DESCRIPTION"
    }
}PK�!]�p]�u	u	&BackupEngine/Postproc/onedriveapp.jsonnu&1i�{
    "_information": {
        "title": "COM_AKEEBA_CONFIG_ENGINE_POSTPROC_ONEDRIVEAPP_TITLE",
        "description": "COM_AKEEBA_CONFIG_ENGINE_POSTPROC_ONEDRIVEAPP_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.abort_on_fail": {
        "default": "0",
        "type": "bool",
        "title": "COM_AKEEBA_CONFIG_POSTPROC_ABORT_ON_FAIL_TITLE",
        "description": "COM_AKEEBA_CONFIG_POSTPROC_ABORT_ON_FAIL_DESCRIPTION"
    },
    "engine.postproc.common.delete_after": {
        "default": "1",
        "type": "bool",
        "title": "COM_AKEEBA_CONFIG_DELETEAFTER_TITLE",
        "description": "COM_AKEEBA_CONFIG_DELETEAFTER_DESCRIPTION"
    },
    "engine.postproc.onedriveapp.chunk_upload": {
        "default": "1",
        "type": "bool",
        "title": "COM_AKEEBA_CONFIG_BOX_CHUNKUPLOAD_ENABLE"
    },
    "engine.postproc.onedriveapp.chunk_upload_size": {
        "default": "10",
        "type": "integer",
        "min": "4",
        "max": "60",
        "shortcuts": "5|10|20|40|60",
        "scale": "1",
        "uom": "MB",
        "title": "COM_AKEEBA_CONFIG_BOX_CHUNKUPLOAD_SIZE",
        "showon": "engine.postproc.onedriveapp.chunk_upload:1"
    },
    "engine.postproc.onedriveapp.openoauth": {
        "default": "",
        "type": "button",
        "title": "COM_AKEEBA_CONFIG_BOX_OPENOAUTH_TITLE",
        "description": "COM_AKEEBA_CONFIG_BOX_OPENOAUTH_DESC",
        "hook": "akconfig_onedriveapp_openoauth"
    },
    "engine.postproc.onedriveapp.directory": {
        "default": "\/",
        "type": "string",
        "title": "COM_AKEEBA_CONFIG_ONEDRIVEAPP_DIRECTORY_TITLE",
        "description": "COM_AKEEBA_CONFIG_ONEDRIVEAPP_DIRECTORY_DESCRIPTION"
    },
    "engine.postproc.onedriveapp.access_token": {
        "default": "",
        "type": "string",
        "title": "COM_AKEEBA_CONFIG_ONEDRIVE_ACCESSTOKEN_TITLE",
        "description": "COM_AKEEBA_CONFIG_ONEDRIVE_ACCESSTOKEN_DESCRIPTION"
    },
    "engine.postproc.onedriveapp.refresh_token": {
        "default": "",
        "type": "string",
        "title": "COM_AKEEBA_CONFIG_ONEDRIVE_REFRESHTOKEN_TITLE",
        "description": "COM_AKEEBA_CONFIG_ONEDRIVE_REFRESHTOKEN_DESCRIPTION"
    }
}PK�!]�'�i1BackupEngine/Util/Log/WarningsLoggerInterface.phpnu&1i�<?php
/**
 * Akeeba Engine
 *
 * @package   akeebaengine
 * @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License version 3, or later
 *
 * 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, version 3.
 *
 * 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
 * <https://www.gnu.org/licenses/>.
 */

namespace Akeeba\Engine\Util\Log;

defined('AKEEBAENGINE') || die();

interface WarningsLoggerInterface
{
	/**
	 * Returns an array with all warnings logged since the last time warnings were reset. The maximum number of warnings
	 * returned is controlled by setWarningsQueueSize().
	 *
	 * @return array
	 */
	public function getWarnings();

	/**
	 * Resets the warnings queue.
	 *
	 * @return void
	 */
	public function resetWarnings();

	/**
	 * A combination of getWarnings() and resetWarnings(). Returns the warnings and immediately resets the warnings
	 * queue.
	 *
	 * @return array
	 */
	public function getAndResetWarnings();

	/**
	 * Set the warnings queue size. A size of 0 means "no limit".
	 *
	 * @param   int  $queueSize  The size of the warnings queue (in number of warnings items)
	 *
	 * @return void
	 */
	public function setWarningsQueueSize($queueSize = 0);

	/**
	 * Returns the warnings queue size.
	 *
	 * @return int
	 */
	public function getWarningsQueueSize();
}
PK�!]�
<�rr-BackupEngine/Util/Log/WarningsLoggerAware.phpnu&1i�<?php
/**
 * Akeeba Engine
 *
 * @package   akeebaengine
 * @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License version 3, or later
 *
 * 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, version 3.
 *
 * 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
 * <https://www.gnu.org/licenses/>.
 */

namespace Akeeba\Engine\Util\Log;

defined('AKEEBAENGINE') || die();

trait WarningsLoggerAware
{
	/**
	 * The warnings in the current queue
	 *
	 * @var string[]
	 */
	private $warningsQueue = [];

	/**
	 * The maximum length of the warnings queue
	 *
	 * @var int
	 */
	private $warningsQueueSize = 0;

	/**
	 * A combination of getWarnings() and resetWarnings(). Returns the warnings and immediately resets the warnings
	 * queue.
	 *
	 * @return array
	 */
	final public function getAndResetWarnings()
	{
		$ret = $this->getWarnings();

		$this->resetWarnings();

		return $ret;
	}

	/**
	 * Returns an array with all warnings logged since the last time warnings were reset. The maximum number of warnings
	 * returned is controlled by setWarningsQueueSize().
	 *
	 * @return array
	 */
	final public function getWarnings()
	{
		return $this->warningsQueue;
	}

	/**
	 * Resets the warnings queue.
	 *
	 * @return void
	 */
	final public function resetWarnings()
	{
		$this->warningsQueue = [];
	}

	/**
	 * Returns the warnings queue size.
	 *
	 * @return int
	 */
	final public function getWarningsQueueSize()
	{
		return $this->warningsQueueSize;
	}

	/**
	 * Set the warnings queue size. A size of 0 means "no limit".
	 *
	 * @param   int  $queueSize  The size of the warnings queue (in number of warnings items)
	 *
	 * @return void
	 */
	final public function setWarningsQueueSize($queueSize = 0)
	{
		if (!is_numeric($queueSize) || empty($queueSize) || ($queueSize < 0))
		{
			$queueSize = 0;
		}

		$this->warningsQueueSize = $queueSize;
	}

	/**
	 * Adds a warning to the warnings queue.
	 *
	 * @param   string  $warning
	 */
	final protected function enqueueWarning($warning)
	{
		$this->warningsQueue[] = $warning;

		// If there is no queue size limit there's nothing else to be done.
		if ($this->warningsQueueSize <= 0)
		{
			return;
		}

		// If the queue size is exceeded remove as many of the earliest elements as required
		if (count($this->warningsQueue) > $this->warningsQueueSize)
		{
			$this->warningsQueueSize = array_slice($this->warningsQueue, -$this->warningsQueueSize);
		}
	}
}
PK�!]�c�nn&BackupEngine/Util/Log/LogInterface.phpnu&1i�<?php
/**
 * Akeeba Engine
 *
 * @package   akeebaengine
 * @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License version 3, or later
 *
 * 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, version 3.
 *
 * 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
 * <https://www.gnu.org/licenses/>.
 */

namespace Akeeba\Engine\Util\Log;

defined('AKEEBAENGINE') || die();

/**
 * The interface for Akeeba Engine logger objects
 */
interface LogInterface
{
	/**
	 * Open a new log instance with the specified tag. If another log is already open it is closed before switching to
	 * the new log tag. If the tag is null use the default log defined in the logging system.
	 *
	 * @param   string|null  $tag        The log to open
	 * @param   string       $extension  The log file extension (default: .php, use empty string for .log files)
	 *
	 * @return void
	 */
	public function open($tag = null, $extension = '.php');

	/**
	 * Close the currently active log and set the current tag to null.
	 *
	 * @return  void
	 */
	public function close();

	/**
	 * Reset (remove entries) of the log with the specified tag.
	 *
	 * @param   string|null  $tag  The log to reset
	 *
	 * @return  void
	 */
	public function reset($tag = null);

	/**
	 * Add a message to the log
	 *
	 * @param   string  $level    One of the Akeeba\Engine\Psr\Log\LogLevel constants
	 * @param   string  $message  The message to log
	 * @param   array   $context  Currently not used. Left here for PSR-3 compatibility.
	 *
	 * @return  void
	 */
	public function log($level, $message, array $context = []);

	/**
	 * Temporarily pause log output. The log() method MUST respect this.
	 *
	 * @return  void
	 */
	public function pause();

	/**
	 * Resume the previously paused log output. The log() method MUST respect this.
	 *
	 * @return  void
	 */
	public function unpause();

	/**
	 * Returns the timestamp (in UNIX time long integer format) of the last log message written to the log with the
	 * specific tag. The timestamp MUST be read from the log itself, not from the logger object. It is used by the
	 * engine to find out the age of stalled backups which may have crashed.
	 *
	 * @param   string|null  $tag  The log tag for which the last timestamp is returned
	 *
	 * @return  int|null  The timestamp of the last log message, in UNIX time. NULL if we can't get the timestamp.
	 */
	public function getLastTimestamp($tag = null);
}
PK�!]���atftf&BackupEngine/Util/EngineParameters.phpnu&1i�<?php
/**
 * Akeeba Engine
 *
 * @package   akeebaengine
 * @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License version 3, or later
 *
 * 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, version 3.
 *
 * 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
 * <https://www.gnu.org/licenses/>.
 */

namespace Akeeba\Engine\Util;

defined('AKEEBAENGINE') || die();

use Akeeba\Engine\Factory;
use Akeeba\Engine\Platform;
use DirectoryIterator;
use LogicException;

/**
 * Unified engine parameters helper class. Deals with scripting, GUI configuration elements and information on engine
 * parts (filters, dump engines, scan engines, archivers, installers).
 */
class EngineParameters
{
	/**
	 * Holds the parsed scripting.json contents
	 *
	 * @var array
	 */
	public $scripting = null;

	/**
	 * The currently active scripting type
	 *
	 * @var string
	 */
	protected $activeType = null;

	/**
	 * Holds the known paths holding JSON definitions of engines, installers and configuration gui elements
	 *
	 * @var  array
	 */
	protected $enginePartPaths = [];

	/**
	 * Cache of the engines known to this object
	 *
	 * @var array
	 */
	protected $engine_list = [];

	/**
	 * Cache of the GUI configuration elements known to this object
	 *
	 * @var array
	 */
	protected $gui_list = [];

	/**
	 * Cache of the installers known to this object
	 *
	 * @var array
	 */
	protected $installer_list = [];

	/**
	 * Append a path to the end of the paths list for a specific section
	 *
	 * @param   string  $path     Absolute filesystem path to add
	 * @param   string  $section  The section to add it to (gui, engine, installer, filters)
	 *
	 * @return  void
	 */
	public function addPath(string $path, string $section = 'gui')
	{
		$path = Factory::getFilesystemTools()->TranslateWinPath($path);

		// If the array is empty, populate with the defaults
		if (!array_key_exists($section, $this->enginePartPaths))
		{
			$this->getEnginePartPaths($section);
		}

		// If the path doesn't already exist, add it
		if (!in_array($path, $this->enginePartPaths[$section]))
		{
			$this->enginePartPaths[$section][] = $path;
		}
	}

	/**
	 * Returns an array with domain keys and domain class names for the current
	 * backup type. The idea is that shifting this array walks through the backup
	 * process. When the array is empty, the backup is done.
	 *
	 * Each element of the array is an array with two keys: domain and class.
	 *
	 * @return  array
	 */
	public function getDomainChain(): array
	{
		$configuration = Factory::getConfiguration();
		$script        = $configuration->get('akeeba.basic.backup_type', 'full');

		$scripting = $this->loadScripting();
		$domains   = $scripting['domains'];
		$keys      = $scripting['scripts'][$script]['chain'];

		$result = [];

		foreach ($keys as $domain_key)
		{
			$result[] = [
				'domain' => $domains[$domain_key]['domain'],
				'class'  => $domains[$domain_key]['class'],
			];
		}

		return $result;
	}

	/**
	 * Get the paths for a specific section
	 *
	 * @param   string  $section  The section to get the path list for (engine, installer, gui, filter)
	 *
	 * @return  array
	 */
	public function getEnginePartPaths(string $section = 'gui')
	{
		// Create the key if it's not already present
		if (!array_key_exists($section, $this->enginePartPaths))
		{
			$this->enginePartPaths[$section] = [];
		}

		if (!empty($this->enginePartPaths[$section]))
		{
			return $this->enginePartPaths[$section];
		}

		// Add the defaults if the list is empty
		switch ($section)
		{
			case 'engine':
				$this->enginePartPaths[$section] = [
					Factory::getFilesystemTools()->TranslateWinPath(Factory::getAkeebaRoot()),
				];
				break;

			case 'installer':
				$this->enginePartPaths[$section] = [
					Factory::getFilesystemTools()->TranslateWinPath(Platform::getInstance()->get_installer_images_path()),
				];
				break;

			case 'gui':
				// Add core GUI definitions
				$this->enginePartPaths[$section] = [
					Factory::getFilesystemTools()->TranslateWinPath(Factory::getAkeebaRoot() . '/Core'),
				];

				// Add platform GUI definition files
				$platform_paths = Platform::getInstance()->getPlatformDirectories();

				foreach ($platform_paths as $p)
				{
					$this->enginePartPaths[$section][] = Factory::getFilesystemTools()->TranslateWinPath($p . '/Config');

					$pro = defined('AKEEBA_PRO') && AKEEBA_PRO;
					$pro = defined('AKEEBABACKUP_PRO') ? (AKEEBABACKUP_PRO ? true : false) : $pro;

					if ($pro)
					{
						$this->enginePartPaths[$section][] = Factory::getFilesystemTools()->TranslateWinPath($p . '/Config/Pro');
					}
				}
				break;

			case 'filter':
				$this->enginePartPaths[$section] = [
					Factory::getFilesystemTools()->TranslateWinPath(Factory::getAkeebaRoot() . '/Platform/Filter/Stack'),
					Factory::getFilesystemTools()->TranslateWinPath(Factory::getAkeebaRoot() . '/Filter/Stack'),
				];

				$platform_paths = Platform::getInstance()->getPlatformDirectories();

				foreach ($platform_paths as $p)
				{
					$this->enginePartPaths[$section][] = Factory::getFilesystemTools()->TranslateWinPath($p . '/Filter/Stack');
				}

				break;

			default:
				throw new LogicException(sprintf('Can not get paths for engine section ‘%s’. No section by this name is known to Akeeba Engine.', $section));
		}

		return $this->enginePartPaths[$section];
	}

	/**
	 * Returns a hash list of Akeeba engines and their data. Each entry has the engine name as key and contains two
	 * arrays, under the 'information' and 'parameters' keys.
	 *
	 * @param   string  $engine_type  The engine type to return information for
	 *
	 * @return  array
	 */
	public function getEnginesList(string $engine_type): array
	{
		$engine_type = ucfirst($engine_type);

		// Try to serve cached data first
		if (isset($this->engine_list[$engine_type]))
		{
			return $this->engine_list[$engine_type];
		}

		// Find absolute path to normal and plugins directories
		$temp      = $this->getEnginePartPaths('engine');
		$path_list = [];

		foreach ($temp as $path)
		{
			$path_list[] = $path . '/' . $engine_type;
		}

		// Initialize the array where we store our data
		$this->engine_list[$engine_type] = [];

		// Loop for the paths where engines can be found
		foreach ($path_list as $path)
		{
			if (!@is_dir($path))
			{
				continue;
			}

			if (!@is_readable($path))
			{
				continue;
			}

			$di = new DirectoryIterator($path);

			/** @var DirectoryIterator $file */
			foreach ($di as $file)
			{
				if (!$file->isFile())
				{
					continue;
				}

				if ($file->getExtension() !== 'json')
				{
					continue;
				}

				$bare_name = ucfirst($file->getBasename('.json'));

				// Some hosts copy .json and .php files, renaming them (ie foobar.1.php)
				// We need to exclude them, otherwise we'll get a fatal error for declaring the same class twice
				if (preg_match('/[^A-Za-z0-9]/', $bare_name))
				{
					continue;
				}

				$information = [];
				$parameters  = [];

				$this->parseEngineJSON($file->getRealPath(), $information, $parameters);

				$this->engine_list[$engine_type][lcfirst($bare_name)] = [
					'information' => $information,
					'parameters'  => $parameters,
				];
			}
		}

		return $this->engine_list[$engine_type];
	}

	/**
	 * Parses the GUI JSON files and returns an array of groups and their data
	 *
	 * @return  array
	 */
	public function getGUIGroups(): array
	{
		// Try to serve cached data first
		if (!empty($this->gui_list) && is_array($this->gui_list))
		{
			if (count($this->gui_list) > 0)
			{
				return $this->gui_list;
			}
		}

		// Find absolute path to normal and plugins directories
		$path_list = $this->getEnginePartPaths('gui');

		// Initialize the array where we store our data
		$this->gui_list = [];

		// Loop for the paths where engines can be found
		foreach ($path_list as $path)
		{
			if (!@is_dir($path))
			{
				continue;
			}

			if (!@is_readable($path))
			{
				continue;
			}

			$allJSONFiles = [];
			$di           = new DirectoryIterator($path);

			/** @var DirectoryIterator $file */
			foreach ($di as $file)
			{
				if (!$file->isFile())
				{
					continue;
				}

				// PHP 5.3.5 and earlier do not support getExtension
				if ($file->getExtension() !== 'json')
				{
					continue;
				}

				$allJSONFiles[] = $file->getRealPath();
			}

			if (empty($allJSONFiles))
			{
				continue;
			}

			// Sort GUI files alphabetically
			asort($allJSONFiles);

			// Include each GUI def file
			foreach ($allJSONFiles as $filename)
			{
				$information = [];
				$parameters  = [];

				$this->parseInterfaceJSON($filename, $information, $parameters);

				// This effectively skips non-GUI JSONs (e.g. the scripting JSON)
				if (!empty($information['description']))
				{
					if (!isset($information['merge']))
					{
						$information['merge'] = 0;
					}

					$group_name = substr(basename($filename), 0, -5);

					$def = [
						'information' => $information,
						'parameters'  => $parameters,
					];

					if (!$information['merge'] || !isset($this->gui_list[$group_name]))
					{
						$this->gui_list[$group_name] = $def;
					}
					else
					{
						$this->gui_list[$group_name]['information'] = array_merge($this->gui_list[$group_name]['information'], $def['information']);
						$this->gui_list[$group_name]['parameters']  = array_merge($this->gui_list[$group_name]['parameters'], $def['parameters']);
					}
				}
			}
		}

		ksort($this->gui_list);

		// Push stack filter settings to the 03.filters section
		$path_list = $this->getEnginePartPaths('filter');

		// Loop for the paths where optional filters can be found
		foreach ($path_list as $path)
		{
			if (!@is_dir($path))
			{
				continue;
			}

			if (!@is_readable($path))
			{
				continue;
			}

			// Store JSON names in temp array because we'll sort based on filename (GUI order IS IMPORTANT!!)
			$allJSONFiles = [];

			$di = new DirectoryIterator($path);

			/** @var DirectoryIterator $file */
			foreach ($di as $file)
			{
				if (!$file->isFile())
				{
					continue;
				}

				// PHP 5.3.5 and earlier do not support getExtension
				if ($file->getExtension() !== 'json')
				{
					continue;
				}

				$allJSONFiles[] = $file->getRealPath();
			}

			if (empty($allJSONFiles))
			{
				continue;
			}

			// Sort filter files alphabetically
			asort($allJSONFiles);

			// Include each filter def file
			foreach ($allJSONFiles as $filename)
			{
				$information = [];
				$parameters  = [];

				$this->parseInterfaceJSON($filename, $information, $parameters);

				if (!array_key_exists('03.filters', $this->gui_list))
				{
					$this->gui_list['03.filters'] = ['parameters' => []];
				}

				if (!array_key_exists('parameters', $this->gui_list['03.filters']))
				{
					$this->gui_list['03.filters']['parameters'] = [];
				}

				if (!is_array($parameters))
				{
					$parameters = [];
				}

				$this->gui_list['03.filters']['parameters'] = array_merge($this->gui_list['03.filters']['parameters'], $parameters);
			}
		}

		/**
		 * Parse showon attributes
		 *
		 * The GUI list array format is like this:
		 * ```
		 * [
		 *    '01.sectionName' => [
		 *       'information' => [...],
		 *       'parameters' => [
		 *           'some.parameter.name' => [
		 *               'title' => 'something',
		 *               ...
		 *               'showon' => 'some.other.parameter.name:1'
		 *           ],
		 *           ...
		 *       ]
		 *    ],
		 *    ...
		 * ]
		 * ```
		 *
		 * We need to convert the shown of the parameters to JSON data which can be used by the `showon` JavaScript
		 * code. The assumption only made here is that all parameter keys are turned into `INPUT` elements with an
		 * attribute `name="var[some.parameter.name]"`. This is how the GUI code in all backup applications our company
		 * makes works.
		 *
		 * For backup applications which do not have showon support (they lack the JavaScript code) this does not matter
		 * and neither does it break anything. All parameters are shown all the time like we have been doing for years.
		 */
		$this->gui_list = array_map(
			function (array $section): array {
				foreach ($section['parameters'] as $paramName => &$paramDef)
				{
					if (isset($paramDef['showon']))
					{
						// Parse showon
						$paramDef['showon'] = $this->parseShowOnConditions($paramDef['showon']);
					}
				}

				return $section;
			},
			$this->gui_list
		);

		return $this->gui_list;
	}

	/**
	 * Parses the installer JSON files and returns an array of installers and their data
	 *
	 * @param   boolean  $forDisplay  If true only returns the information relevant for displaying the GUI
	 *
	 * @return  array
	 */
	public function getInstallerList(bool $forDisplay = false): array
	{
		// Try to serve cached data first
		if (!empty($this->installer_list) && is_array($this->installer_list))
		{
			if (count($this->installer_list) > 0)
			{
				return $this->installer_list;
			}
		}

		// Find absolute path to normal and plugins directories
		$path_list = [
			Platform::getInstance()->get_installer_images_path(),
		];

		// Initialize the array where we store our data
		$this->installer_list = [];

		// Loop for the paths where engines can be found
		foreach ($path_list as $path)
		{
			if (!@is_dir($path))
			{
				continue;
			}

			if (!@is_readable($path))
			{
				continue;
			}

			$di = new DirectoryIterator($path);

			/** @var DirectoryIterator $file */
			foreach ($di as $file)
			{
				if (!$file->isFile())
				{
					continue;
				}

				// PHP 5.3.5 and earlier do not support getExtension
				if ($file->getExtension() !== 'json')
				{
					continue;
				}

				$rawData = file_get_contents($file->getRealPath());
				$data    = empty($rawData) ? [] : json_decode($rawData, true);

				if ($forDisplay)
				{
					$innerData = reset($data);

					if (array_key_exists('listinoptions', $innerData))
					{
						if ($innerData['listinoptions'] == 0)
						{
							continue;
						}
					}
				}

				foreach ($data as $key => $values)
				{
					$this->installer_list[$key] = [];

					foreach ($values as $key2 => $value)
					{
						$this->installer_list[$key][$key2] = $value;
					}
				}
			}
		}

		return $this->installer_list;
	}

	/**
	 * Returns the JSON representation of the GUI definition and the associated values
	 *
	 * @return   string
	 */
	public function getJsonGuiDefinition(): string
	{
		// Initialize the array which will be converted to JSON representation
		$json_array = [
			'engines'    => [],
			'installers' => [],
			'gui'        => [],
		];

		// Get a reference to the configuration
		$configuration = Factory::getConfiguration();

		// Get data for all engines
		$engine_types = [
			'archiver',
			'dump',
			'scan',
			'writer',
			'postproc',
		];

		foreach ($engine_types as $type)
		{
			$engines = $this->getEnginesList($type);

			$tempArray    = [];
			$engineTitles = [];

			foreach ($engines as $engine_name => $engine_data)
			{
				// Translate information
				foreach ($engine_data['information'] as $key => $value)
				{
					switch ($key)
					{
						case 'title':
						case 'content':
						case 'description':
							$value = Platform::getInstance()->translate($value);
							break;
					}

					$tempArray[$engine_name]['information'][$key] = $value;

					if ($key == 'title')
					{
						$engineTitles[$engine_name] = $value;
					}
				}

				// Process parameters
				$parameters = [];

				foreach ($engine_data['parameters'] as $param_key => $param)
				{
					$param['default'] = $configuration->get($param_key, $param['default'], false);

					foreach ($param as $option_key => $option_value)
					{
						// Translate title, description, enumkeys
						switch ($option_key)
						{
							case 'title':
							case 'description':
							case 'content':
							case 'labelempty':
							case 'labelnotempty':
								$param[$option_key] = Platform::getInstance()->translate($option_value);
								break;

							case 'enumkeys':
								$enumkeys = explode('|', $option_value);
								$new_keys = [];
								foreach ($enumkeys as $old_key)
								{
									$new_keys[] = Platform::getInstance()->translate($old_key);
								}
								$param[$option_key] = implode('|', $new_keys);
								break;

							case 'showon':
								$param[$option_key] = $this->parseShowOnConditions($param[$option_key]);

							default:
						}
					}

					$parameters[$param_key] = $param;
				}

				// Add processed parameters
				$tempArray[$engine_name]['parameters'] = $parameters;
			}

			asort($engineTitles);

			foreach ($engineTitles as $engineName => $title)
			{
				$json_array['engines'][$type][$engineName] = $tempArray[$engineName];
			}
		}

		// Get data for GUI elements
		$json_array['gui'] = [];
		$groupdefs         = $this->getGUIGroups();

		foreach ($groupdefs as $groupKey => $definition)
		{
			$group_name = '';

			if (isset($definition['information']) && isset($definition['information']['description']))
			{
				$group_name = Platform::getInstance()->translate($definition['information']['description']);
			}

			// Skip no-name groups
			if (empty($group_name))
			{
				continue;
			}

			$parameters = [];

			foreach ($definition['parameters'] as $param_key => $param)
			{
				$param['default'] = $configuration->get($param_key, $param['default'], false);

				foreach ($param as $option_key => $option_value)
				{
					// Translate title, description, enumkeys
					switch ($option_key)
					{
						case 'title':
						case 'description':
						case 'content':
							$param[$option_key] = Platform::getInstance()->translate($option_value);
							break;

						case 'enumkeys':
							$enumkeys = explode('|', $option_value);
							$new_keys = [];
							foreach ($enumkeys as $old_key)
							{
								$new_keys[] = Platform::getInstance()->translate($old_key);
							}
							$param[$option_key] = implode('|', $new_keys);
							break;

						default:
					}
				}
				$parameters[$param_key] = $param;
			}
			$json_array['gui'][$group_name] = $parameters;
		}

		// Get data for the installers
		$json_array['installers'] = $this->getInstallerList(true);

		uasort($json_array['installers'], function ($a, $b) {
			if ($a['name'] == $b['name'])
			{
				return 0;
			}

			return ($a['name'] < $b['name']) ? -1 : 1;
		});

		$json = json_encode($json_array);

		return $json;
	}

	/**
	 * Returns a volatile scripting parameter for the active backup type
	 *
	 * @param   string  $key      The relative key, e.g. core.createarchive
	 * @param   mixed   $default  Default value
	 *
	 * @return  mixed  The scripting parameter's value
	 */
	public function getScriptingParameter(string $key, $default = null)
	{
		$configuration = Factory::getConfiguration();

		if (is_null($this->activeType))
		{
			$this->activeType = $configuration->get('akeeba.basic.backup_type', 'full');
		}

		return $configuration->get('volatile.scripting.' . $this->activeType . '.' . $key, $default);
	}

	/**
	 * Imports the volatile scripting parameters to the registry
	 *
	 * @return  void
	 */
	public function importScriptingToRegistry(): void
	{
		$scripting     = $this->loadScripting();
		$configuration = Factory::getConfiguration();
		$configuration->mergeArray($scripting['data'], false);
	}

	/**
	 * Loads the scripting.json and returns an array with the domains, the scripts and the raw data
	 *
	 * @return  array  The parsed scripting.json. Array keys: domains, scripts, data
	 */
	public function loadScripting(?string $jsonPath = ''): ?array
	{
		if (!empty($this->scripting))
		{
			return $this->scripting;
		}

		$this->scripting = [];
		$jsonPath        = $jsonPath ?: Factory::getAkeebaRoot() . '/Core/scripting.json';

		if (!@file_exists($jsonPath))
		{
			return $this->scripting;
		}

		$rawData          = file_get_contents($jsonPath);
		$rawScriptingData = empty($rawData) ? [] : json_decode($rawData, true);
		$domain_keys      = explode('|', $rawScriptingData['volatile.akeebaengine.domains']);
		$domains          = [];

		foreach ($domain_keys as $key)
		{
			$record        = [
				'domain' => $rawScriptingData['volatile.domain.' . $key . '.domain'],
				'class'  => $rawScriptingData['volatile.domain.' . $key . '.class'],
				'text'   => $rawScriptingData['volatile.domain.' . $key . '.text'],
			];
			$domains[$key] = $record;
		}

		$script_keys = explode('|', $rawScriptingData['volatile.akeebaengine.scripts']);
		$scripts     = [];

		foreach ($script_keys as $key)
		{
			$record        = [
				'chain' => explode('|', $rawScriptingData['volatile.scripting.' . $key . '.chain']),
				'text'  => $rawScriptingData['volatile.scripting.' . $key . '.text'],
			];
			$scripts[$key] = $record;
		}

		$this->scripting = [
			'domains' => $domains,
			'scripts' => $scripts,
			'data'    => $rawScriptingData,
		];

		return $this->scripting;
	}

	/**
	 * Parses an engine JSON file returning two arrays, one with the general information
	 * of that engine and one with its configuration variables' definitions
	 *
	 * @param   string  $jsonPath     Absolute path to engine JSON file
	 * @param   array   $information  [out] The engine information hash array
	 * @param   array   $parameters   [out] The parameters hash array
	 *
	 * @return  bool  True if the file was loaded
	 */
	public function parseEngineJSON(string $jsonPath, array &$information, array &$parameters): bool
	{
		if (!file_exists($jsonPath))
		{
			return false;
		}

		$information = [
			'title'       => '',
			'description' => '',
		];

		$parameters = [];

		$rawData  = file_get_contents($jsonPath);
		$jsonData = empty($rawData) ? [] : json_decode($rawData, true);

		foreach ($jsonData ?? [] as $section => $data)
		{
			if (is_array($data))
			{
				if ($section == '_information')
				{
					// Parse information
					foreach ($data as $key => $value)
					{
						$information[$key] = $value;
					}
				}
				elseif (substr($section, 0, 1) != '_')
				{
					// Parse parameters
					$newparam = [
						'title'       => '',
						'description' => '',
						'type'        => 'string',
						'default'     => '',
					];

					foreach ($data as $key => $value)
					{
						$newparam[$key] = $value;
					}
					$parameters[$section] = $newparam;
				}
			}
		}

		return true;
	}

	/**
	 * Parses a graphical interface JSON file returning two arrays, one with the general
	 * information of that configuration section and one with its configuration variables'
	 * definitions.
	 *
	 * @param   string  $jsonPath     Absolute path to engine JSON file
	 * @param   array   $information  [out] The GUI information hash array
	 * @param   array   $parameters   [out] The parameters hash array
	 *
	 * @return bool True if the file was loaded
	 */
	public function parseInterfaceJSON(string $jsonPath, array &$information, array &$parameters): bool
	{
		if (!file_exists($jsonPath))
		{
			return false;
		}

		$information = [
			'description' => '',
		];

		$parameters = [];
		$rawData    = file_get_contents($jsonPath);
		$jsonData   = empty($rawData) ? [] : json_decode($rawData, true);

		foreach ($jsonData as $section => $data)
		{
			if (is_array($data))
			{
				if ($section == '_group')
				{
					// Parse information
					foreach ($data as $key => $value)
					{
						$information[$key] = $value;
					}

					continue;
				}

				if (substr($section, 0, 1) != '_')
				{
					// Parse parameters
					$newparam = [
						'title'       => '',
						'description' => '',
						'type'        => 'string',
						'default'     => '',
						'protected'   => 0,
					];

					foreach ($data as $key => $value)
					{
						$newparam[$key] = $value;
					}

					$parameters[$section] = $newparam;
				}
			}
		}

		return true;
	}

	/**
	 * Add a path to the beginning of the paths list for a specific section
	 *
	 * @param   string  $path     Absolute filesystem path to add
	 * @param   string  $section  The section to add it to (gui, engine, installer, filters)
	 *
	 * @return  void
	 */
	public function prependPath(string $path, string $section = 'gui'): void
	{
		$path = Factory::getFilesystemTools()->TranslateWinPath($path);

		// If the array is empty, populate with the defaults
		if (!array_key_exists($section, $this->enginePartPaths))
		{
			$this->getEnginePartPaths($section);
		}

		// If the path doesn't already exist, add it
		if (!in_array($path, $this->enginePartPaths[$section]))
		{
			array_unshift($this->enginePartPaths[$section], $path);
		}
	}

	/**
	 * Parse the `showon` conditions text into an instructions array for the ShowOn JavaScript
	 *
	 * @param   string|null  $showOn     The `showon` conditions text
	 * @param   string|null  $arrayName  The array all of our parameters are members of, default 'var'.
	 *
	 * @return  array  The ShowOn JavaScript instructions
	 *
	 * @since   9.3.1
	 */
	private function parseShowOnConditions(?string $showOn, ?string $arrayName = 'var'): array
	{
		if (empty($showOn))
		{
			return [];
		}

		$showOnData  = [];
		$showOnParts = preg_split('#(\[AND\]|\[OR\])#', $showOn, -1, PREG_SPLIT_DELIM_CAPTURE);
		$op          = '';

		foreach ($showOnParts as $showOnPart)
		{
			if (in_array($showOnPart, ['[AND]', '[OR]']))
			{
				$op = trim($showOnPart, '[]');

				continue;
			}

			$compareEqual     = strpos($showOnPart, '!:') === false;
			$showOnPartBlocks = explode(($compareEqual ? ':' : '!:'), $showOnPart, 2);

			$field = $arrayName
				? sprintf("%s[%s]", $arrayName, $showOnPartBlocks[0])
				: $showOnPartBlocks[0];

			$showOnData[] = [
				'field'  => $field,
				'values' => explode(',', $showOnPartBlocks[1]),
				'sign'   => $compareEqual === true ? '=' : '!=',
				'op'     => $op,
			];

			$op = '';
		}

		return $showOnData;
	}
}
PK�!]C�rr$BackupEngine/Util/FactoryStorage.phpnu&1i�<?php
/**
 * Akeeba Engine
 *
 * @package   akeebaengine
 * @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License version 3, or later
 *
 * 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, version 3.
 *
 * 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
 * <https://www.gnu.org/licenses/>.
 */

namespace Akeeba\Engine\Util;

defined('AKEEBAENGINE') || die();

use Akeeba\Engine\Factory;
use InvalidArgumentException;
use RuntimeException;

/**
 * Management class for temporary storage of the serialised engine state.
 */
class FactoryStorage
{
	protected static $tempFileStoragePath;

	/**
	 * Returns the fully qualified path to the storage file
	 *
	 * @param   string  $tag        Backup tag
	 * @param   string  $extension  File extension, default is php
	 *
	 * @return  string
	 */
	public function get_storage_filename($tag = null, $extension = 'php')
	{
		if (is_null(self::$tempFileStoragePath))
		{
			$registry                  = Factory::getConfiguration();
			self::$tempFileStoragePath = $registry->get('akeeba.basic.output_directory', '');

			if (empty(self::$tempFileStoragePath))
			{
				throw new InvalidArgumentException('You have not set a backup output directory.');
			}

			self::$tempFileStoragePath = rtrim(self::$tempFileStoragePath, '/\\');

			if (!is_writable(self::$tempFileStoragePath) || !is_readable(self::$tempFileStoragePath))
			{
				throw new InvalidArgumentException(sprintf('Backup output directory %s needs to be both readable and writeable to PHP for this backup software to function correctly.', self::$tempFileStoragePath));
			}
		}

		$tag      = empty($tag) ? '' : $tag;
		$filename = sprintf("akstorage%s%s.%s", empty($tag) ? '' : '_', $tag, $extension);

		return self::$tempFileStoragePath . DIRECTORY_SEPARATOR . $filename;
	}

	/**
	 * Resets the storage. This method removes all stored values.
	 *
	 * @param   null  $tag
	 *
	 * @return    bool    True on success
	 */
	public function reset($tag = null)
	{
		$filename = $this->get_storage_filename($tag);

		if (!is_file($filename) && !is_link($filename))
		{
			$filename = $this->get_storage_filename($tag, 'dat');
		}

		if (!is_file($filename) && !is_link($filename))
		{
			return false;
		}

		return @unlink($this->get_storage_filename($tag));
	}

	/**
	 * Stores a value to the storage
	 *
	 * @param   string       $value      Serialised value to store
	 * @param   string|null  $tag        Backup tag
	 * @param   string       $extension  File extension to use, default is php
	 *
	 * @return  bool  True on success
	 */
	public function set($value, $tag = null, $extension = 'php')
	{
		$storage_filename = $this->get_storage_filename($tag, $extension);

		if (file_exists($storage_filename))
		{
			@unlink($storage_filename);
		}

		$isPHPFile = strtolower($extension) == 'php';

		return @file_put_contents($storage_filename, $this->encode($value, $isPHPFile)) !== false;
	}

	/**
	 * Retrieves a value from storage
	 *
	 * @param   string|null  $tag  Backup tag. Used to determine the session state (memory) file name.
	 *
	 * @return  string|null
	 */
	public function &get($tag = null)
	{
		$ret              = null;
		$storage_filename = $this->get_storage_filename($tag);
		$isPHPFile        = true;
		$data             = @file_get_contents($storage_filename);

		/**
		 * Some hosts, like WPEngine, do not allow us to use .php files for storing the factory state. In these case we
		 * fall back to using the far less secure .dat extension. This if-block caters for that case.
		 */
		if ($data === false)
		{
			$storage_filename = $this->get_storage_filename($tag, 'dat');
			$isPHPFile        = false;
			$data             = @file_get_contents($storage_filename);
		}

		if ($data === false)
		{
			return $ret;
		}

		try
		{
			$ret = $this->decode($data, $isPHPFile);
		}
		catch (RuntimeException $e)
		{
			$ret = null;
		}

		unset($data);

		return $ret;
	}

	/**
	 * Encodes the (serialized) data in a format suitable for storing in a deliberately web-inaccessible PHP file.
	 *
	 * IMPORTANT: On some hosts we HAVE to fall back to a .dat file. This is nowhere near as secure. This is not a
	 * problem with Akeeba Backup but with the host, e.g. WPEngine. We WANT to do things securely but hosts' misguided
	 * attempts at "security" force us to have a very insecure fallback. Please do not report this as a security issue
	 * with us. report it to the host. We can't do something the host doesn't allow our code to do, obviously!
	 *
	 * @param   string  $data       The data to encode
	 * @param   bool    $isPHPFile  Is this file extension .php?
	 *
	 * @return  string  The encoded data
	 */
	public function encode(&$data, $isPHPFile = true)
	{
		$encodingMethod = $this->getEncodingMethod();
		
		switch ($encodingMethod)
		{
			case 'base64':
				$ret = base64_encode($data);
				break;

			case 'uuencode':
				$ret = convert_uuencode($data);
				break;

			case 'plain':
			default:
				$ret = $data;
				break;
		}

		if ($isPHPFile)
		{
			return '<' . '?' . 'php die(); ' . '>' . '?' . "\n" .
				$encodingMethod . "\n" . $ret;
		}

		return $encodingMethod . "\n" . $ret;
	}

	/**
	 * Decodes the data read from the deliberately web-inaccessible PHP file.
	 *
	 * @param   string  $data       The data read from the file
	 * @param   bool    $isPHPFile  Does the memory file have a .php extension?
	 *
	 * @return  false|string  The decoded data. False if the decoding failed.
	 */
	public function decode(&$data, $isPHPFile = true)
	{
		// Parts: 0 = PHP die line; 1 = encoding mode; 2 = data
		$parts = explode("\n", $data, 3);

		$expectedPartsCount = $isPHPFile ? 3 : 2;

		if (count($parts) != $expectedPartsCount)
		{
			throw new RuntimeException("Invalid backup temporary data (memory file)");
		}

		$encodingIndex = $isPHPFile ? 1 : 0;
		$dataIndex     = $isPHPFile ? 2 : 1;

		switch ($parts[$encodingIndex])
		{
			case 'base64';
				return base64_decode($parts[$dataIndex]);
				break;

			case 'uuencode':
				return convert_uudecode($parts[$dataIndex]);
				break;

			case 'plain':
				return $parts[$dataIndex];
				break;

			default:
				throw new RuntimeException(sprintf('Unsupported encoding method “%s”', $parts[$encodingIndex]));
				break;
		}
	}

	/**
	 * Get the recommended method for encoding the temporary data
	 *
	 * @return string
	 */
	protected function getEncodingMethod()
	{
		// Preferred encoding: base sixty four, handled by PHP
		if (function_exists('base64_encode') && function_exists('base64_decode'))
		{
			return 'base64';
		}

		// Fallback: UUencoding
		if (function_exists('convert_uuencode') && function_exists('convert_uudecode'))
		{
			return 'uuencode';
		}

		// Final fallback (should NOT be necessary): plain text encoding
		return 'plain';
	}
}
PK�!]�Ôii6BackupEngine/Util/Transfer/RemoteResourceInterface.phpnu&1i�<?php
/**
 * Akeeba Engine
 *
 * @package   akeebaengine
 * @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License version 3, or later
 *
 * 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, version 3.
 *
 * 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
 * <https://www.gnu.org/licenses/>.
 */

namespace Akeeba\Engine\Util\Transfer;

defined('AKEEBAENGINE') || die();

/**
 * An interface for Transfer adapters which support remote resources, allowing us to efficient read from / write to
 * remote locations as if they were local files.
 */
interface RemoteResourceInterface
{
	/**
	 * Return a string with the appropriate stream wrapper protocol for $path. You can use the result with all PHP
	 * functions / classes which accept file paths such as DirectoryIterator, file_get_contents, file_put_contents,
	 * fopen etc.
	 *
	 * @param   string  $path
	 *
	 * @return  string
	 */
	public function getWrapperStringFor($path);

	/**
	 * Return the raw server listing for the requested folder.
	 *
	 * @param   string  $folder  The path name to list
	 *
	 * @return  string
	 */
	public function getRawList($folder);
}
PK�!]R=G��C�C"BackupEngine/Util/Transfer/Ftp.phpnu&1i�<?php
/**
 * Akeeba Engine
 *
 * @package   akeebaengine
 * @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License version 3, or later
 *
 * 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, version 3.
 *
 * 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
 * <https://www.gnu.org/licenses/>.
 */

namespace Akeeba\Engine\Util\Transfer;

defined('AKEEBAENGINE') || die();

use Exception;
use RuntimeException;

/**
 * FTP transfer object, using PHP as the transport backend
 */
class Ftp implements TransferInterface, RemoteResourceInterface
{
	/**
	 * FTP server's hostname or IP address
	 *
	 * @var  string
	 */
	protected $host = 'localhost';

	/**
	 * FTP server's port, default: 21
	 *
	 * @var  integer
	 */
	protected $port = 21;

	/**
	 * Username used to authenticate to the FTP server
	 *
	 * @var  string
	 */
	protected $username = '';

	/**
	 * Password used to authenticate to the FTP server
	 *
	 * @var  string
	 */
	protected $password = '';

	/**
	 * FTP initial directory
	 *
	 * @var  string
	 */
	protected $directory = '/';

	/**
	 * Should I use SSL to connect to the server (FTP over explicit SSL, a.k.a. FTPS)?
	 *
	 * @var  boolean
	 */
	protected $ssl = false;

	/**
	 * Should I use FTP passive mode?
	 *
	 * @var bool
	 */
	protected $passive = true;

	/**
	 * Timeout for connecting to the FTP server, default: 10
	 *
	 * @var  integer
	 */
	protected $timeout = 10;

	/**
	 * The FTP connection handle
	 *
	 * @var  resource|null
	 */
	private $connection = null;

	/**
	 * Public constructor
	 *
	 * @param   array  $options  Configuration options
	 *
	 * @return  void
	 *
	 * @throws  RuntimeException
	 */
	public function __construct(array $options)
	{
		if (isset($options['host']))
		{
			$this->host = $options['host'];
		}

		if (isset($options['port']))
		{
			$this->port = (int) $options['port'];
		}

		if (isset($options['username']))
		{
			$this->username = $options['username'];
		}

		if (isset($options['password']))
		{
			$this->password = $options['password'];
		}

		if (isset($options['directory']))
		{
			$this->directory = '/' . ltrim(trim($options['directory']), '/');
		}

		if (isset($options['ssl']))
		{
			$this->ssl = $options['ssl'];
		}

		if (isset($options['passive']))
		{
			$this->passive = $options['passive'];
		}

		if (isset($options['timeout']))
		{
			$this->timeout = max(1, (int) $options['timeout']);
		}

		$this->connect();
	}

	/**
	 * Is this transfer method blocked by a server firewall?
	 *
	 * @param   array  $params  Any additional parameters you might need to pass
	 *
	 * @return  boolean  True if the firewall blocks connections to a known host
	 */
	public static function isFirewalled(array $params = [])
	{
		try
		{
			$connector = new static([
				'host'      => 'test.rebex.net',
				'port'      => 21,
				'username'  => 'demo',
				'password'  => 'password',
				'directory' => '',
				'ssl'       => $params['ssl'] ?? false,
				'passive'   => true,
				'timeout'   => 5,
			]);

			$data = $connector->read('readme.txt');

			if (empty($data))
			{
				return true;
			}
		}
		catch (Exception $e)
		{
			return true;
		}

		return false;
	}

	/**
	 * Save all parameters on serialization except the connection resource
	 *
	 * @return  array
	 */
	public function __sleep()
	{
		return ['host', 'port', 'username', 'password', 'directory', 'ssl', 'passive', 'timeout'];
	}

	/**
	 * Reconnect to the server on unserialize
	 *
	 * @return  void
	 */
	public function __wakeup()
	{
		$this->connect();
	}

	/**
	 * Connect to the FTP server
	 *
	 * @throws  RuntimeException
	 */
	public function connect()
	{
		// Try to connect to the server
		if ($this->ssl)
		{
			if (function_exists('ftp_ssl_connect'))
			{
				$this->connection = @ftp_ssl_connect($this->host, $this->port);
			}
			else
			{
				$this->connection = false;

				throw new RuntimeException('ftp_ssl_connect not available on this server', 500);
			}
		}
		else
		{
			$this->connection = @ftp_connect($this->host, $this->port, $this->timeout);
		}

		if ($this->connection === false)
		{
			throw new RuntimeException(sprintf('Cannot connect to FTP server [host:port] = %s:%s', $this->host, $this->port), 500);
		}

		// Attempt to authenticate
		if (!@ftp_login($this->connection, $this->username, $this->password))
		{
			@ftp_close($this->connection);
			$this->connection = null;

			throw new RuntimeException(sprintf('Cannot log in to FTP server [username:password] = %s:%s', $this->username, $this->password), 500);
		}

		// Attempt to change to the initial directory
		$defaultDir  = @ftp_pwd($this->connection) ?: '/';
		$directories = [
			$this->directory,
			rtrim($this->directory, '/'),
			trim($this->directory, '/'),
			$defaultDir,
		];

		foreach ($directories as $dir)
		{
			$changedDir = @ftp_chdir($this->connection, $dir);

			if ($changedDir)
			{
				$this->directory = $dir;

				break;
			}
		}

		if (!$changedDir)
		{
			@ftp_close($this->connection);
			$this->connection = null;

			throw new RuntimeException(sprintf('Cannot change to initial FTP directory "%s" – make sure the folder exists and that you have adequate permissions to it. Pro tip: the default directory of your FTP connection is reported to be %s', $this->directory, $defaultDir), 500);
		}

		// Apply the passive mode preference
		@ftp_pasv($this->connection, $this->passive);
	}

	/**
	 * Public destructor, closes any open FTP connections
	 */
	public function __destruct()
	{
		if (!is_null($this->connection))
		{
			@ftp_close($this->connection);
		}
	}

	/**
	 * Write the contents into the file
	 *
	 * @param   string  $fileName  The full path to the file
	 * @param   string  $contents  The contents to write to the file
	 *
	 * @return  boolean  True on success
	 */
	public function write($fileName, $contents)
	{
		// Make sure the buffer:// wrapper is loaded
		class_exists('\\Akeeba\\Engine\\Util\\Buffer', true);

		$handle = fopen('buffer://akeeba_engine_transfer_ftp', 'r+');
		fwrite($handle, $contents);
		rewind($handle);

		$cwd            = $this->cwd();
		$remoteFilename = '/' . ltrim($fileName, '/');
		$remotePath     = dirname($remoteFilename);
		$remoteName     = basename($remoteFilename);

		if (!$this->isDir($remotePath))
		{
			$this->mkdir($remotePath);
		}

		$changedDir = @ftp_chdir($this->connection, $remotePath);

		$ret = $changedDir && @ftp_fput($this->connection, $remoteName, $handle, FTP_BINARY);

		if ($changedDir)
		{
			@ftp_chdir($this->connection, $cwd);
		}

		fclose($handle);

		return $ret;
	}

	/**
	 * Uploads a local file to the remote storage
	 *
	 * @param   string  $localFilename   The full path to the local file
	 * @param   string  $remoteFilename  The full path to the remote file
	 * @param   bool    $useExceptions   Throw an exception instead of returning "false" on connection error.
	 *
	 * @return  boolean  True on success
	 */
	public function upload($localFilename, $remoteFilename, $useExceptions = true)
	{
		$handle = @fopen($localFilename, 'r');

		if ($handle === false)
		{
			if ($useExceptions)
			{
				throw new RuntimeException("Unreadable local file $localFilename");
			}

			return false;
		}

		$cwd            = $this->cwd();
		$remoteFilename = '/' . ltrim($remoteFilename, '/');
		$remotePath     = dirname($remoteFilename);
		$remoteName     = basename($remoteFilename);

		if (!$this->isDir($remotePath))
		{
			$this->mkdir($remotePath);
		}

		$changedDir = @ftp_chdir($this->connection, $remotePath);
		$ret        = $changedDir && @ftp_fput($this->connection, $remoteName, $handle, FTP_BINARY);

		if ($changedDir)
		{
			@ftp_chdir($this->connection, $cwd);
		}

		@fclose($handle);

		return $ret;
	}

	/**
	 * Read the contents of a remote file into a string
	 *
	 * @param   string  $fileName  The full path to the remote file
	 *
	 * @return  string  The contents of the remote file
	 */
	public function read($fileName)
	{
		// Make sure the buffer:// wrapper is loaded
		class_exists('\\Akeeba\\Engine\\Util\\Buffer', true);

		$handle = fopen('buffer://akeeba_engine_transfer_ftp', 'r+');

		$cwd            = $this->cwd();
		$remoteFilename = '/' . ltrim($fileName, '/');
		$remotePath     = dirname($remoteFilename);
		$remoteName     = basename($remoteFilename);
		$changedDir     = @ftp_chdir($this->connection, $remotePath);
		$result         = $changedDir && @ftp_fget($this->connection, $handle, $remoteName, FTP_BINARY);

		if ($changedDir)
		{
			@ftp_chdir($this->connection, $cwd);
		}

		if ($result === false)
		{
			fclose($handle);
			throw new RuntimeException("Can not download remote file $fileName");
		}

		rewind($handle);

		$ret = '';

		while (!feof($handle))
		{
			$ret .= fread($handle, 131072);
		}

		fclose($handle);

		return $ret;
	}

	/**
	 * Download a remote file into a local file
	 *
	 * @param   string  $remoteFilename  The remote file path to download from
	 * @param   string  $localFilename   The local file path to download to
	 * @param   bool    $useExceptions   Throw an exception instead of returning "false" on connection error.
	 *
	 * @return  boolean  True on success
	 */
	public function download($remoteFilename, $localFilename, $useExceptions = true)
	{
		$cwd            = $this->cwd();
		$remoteFilename = '/' . ltrim($remoteFilename, '/');
		$remotePath     = dirname($remoteFilename);
		$remoteName     = basename($remoteFilename);
		$changedDir     = @ftp_chdir($this->connection, $remotePath);

		$ret = $changedDir && @ftp_get($this->connection, $localFilename, $remoteName, FTP_BINARY);

		if ($changedDir)
		{
			@ftp_chdir($this->connection, $cwd);
		}

		if (!$ret && $useExceptions)
		{
			throw new RuntimeException("Cannot download remote file $remoteFilename through FTP.");
		}

		return $ret;
	}

	/**
	 * Delete a file (remove it from the disk)
	 *
	 * @param   string  $fileName  The full path to the file
	 *
	 * @return  boolean  True on success
	 */
	public function delete($fileName)
	{
		$cwd            = $this->cwd();
		$remoteFilename = '/' . ltrim($fileName, '/');
		$remotePath     = dirname($remoteFilename);
		$remoteName     = basename($remoteFilename);

		if (!$this->isDir($remotePath))
		{
			$this->mkdir($remotePath);
		}

		$changedDir = @ftp_chdir($this->connection, $remotePath);
		$ret        = $changedDir && @ftp_delete($this->connection, $remoteName);;

		if ($changedDir)
		{
			ftp_chdir($this->connection, $cwd);
		}

		return $ret;
	}

	/**
	 * Create a copy of the file. Actually, we have to read it in memory and upload it again.
	 *
	 * @param   string  $from  The full path of the file to copy from
	 * @param   string  $to    The full path of the file that will hold the copy
	 *
	 * @return  boolean  True on success
	 */
	public function copy($from, $to)
	{
		// Make sure the buffer:// wrapper is loaded
		class_exists('\\Akeeba\\Engine\\Util\\Buffer', true);

		$handle = fopen('buffer://akeeba_engine_transfer_ftp', 'r+');

		$cwd            = $this->cwd();
		$remoteFilename = '/' . ltrim($from, '/');
		$remotePath     = dirname($remoteFilename);
		$remoteName     = basename($remoteFilename);
		$changedDir     = @ftp_chdir($this->connection, $remotePath);

		$ret = $changedDir && @ftp_fget($this->connection, $handle, $remoteName, FTP_BINARY);

		if ($ret !== false)
		{
			rewind($handle);

			$remoteFilename = '/' . ltrim($to, '/');
			$remotePath     = dirname($remoteFilename);
			$remoteName     = basename($remoteFilename);

			if (!$this->isDir($remotePath))
			{
				$this->mkdir($remotePath);
			}

			$changedDir = @ftp_chdir($this->connection, $remotePath);
			$ret        = $changedDir && @ftp_fput($this->connection, $remoteName, $handle, FTP_BINARY);
		}

		if ($changedDir)
		{
			@ftp_chdir($this->connection, $cwd);
		}

		fclose($handle);

		return $ret;
	}

	/**
	 * Move or rename a file
	 *
	 * @param   string  $from  The full path of the file to move
	 * @param   string  $to    The full path of the target file
	 *
	 * @return  boolean  True on success
	 */
	public function move($from, $to)
	{
		return @ftp_rename($this->connection, $from, $to);
	}

	/**
	 * Change the permissions of a file
	 *
	 * @param   string   $fileName     The full path of the file whose permissions will change
	 * @param   integer  $permissions  The new permissions, e.g. 0644 (remember the leading zero in octal numbers!)
	 *
	 * @return  boolean  True on success
	 */
	public function chmod($fileName, $permissions)
	{
		if (@ftp_chmod($this->connection, $permissions, $fileName) !== false)
		{
			return true;
		}

		$permissionsOctal = decoct((int) $permissions);

		if (@ftp_site($this->connection, "CHMOD $permissionsOctal $fileName") !== false)
		{
			return true;
		}

		return false;
	}

	/**
	 * Create a directory if it doesn't exist. The operation is implicitly recursive, i.e. it will create all
	 * intermediate directories if they do not already exist.
	 *
	 * @param   string   $dirName      The full path of the directory to create
	 * @param   integer  $permissions  The permissions of the created directory
	 *
	 * @return  boolean  True on success
	 */
	public function mkdir($dirName, $permissions = 0755)
	{
		$targetDir = rtrim($dirName, '/');

		$directories = explode('/', $targetDir);

		$remoteDir = '';

		foreach ($directories as $dir)
		{
			if (!$dir)
			{
				continue;
			}

			$remoteDir .= '/' . $dir;

			// Continue if the folder already exists. Otherwise I'll get a an error even if everything is fine
			if ($this->isDir($remoteDir))
			{
				continue;
			}

			$ret = @ftp_mkdir($this->connection, $remoteDir);

			if ($ret === false)
			{
				return $ret;
			}
		}

		$this->chmod($dirName, $permissions);

		return true;
	}

	/**
	 * Checks if the given directory exists
	 *
	 * @param   string  $path  The full path of the remote directory to check
	 *
	 * @return  boolean  True if the directory exists
	 */
	public function isDir($path)
	{
		$cur_dir = ftp_pwd($this->connection);

		if (@ftp_chdir($this->connection, $path))
		{
			// If it is a directory, then change the directory back to the original directory
			ftp_chdir($this->connection, $cur_dir);

			return true;
		}
		else
		{
			return false;
		}
	}

	/**
	 * Get the current working directory
	 *
	 * @return  string
	 */
	public function cwd()
	{
		return ftp_pwd($this->connection);
	}

	/**
	 * Returns the absolute remote path from a path relative to the initial directory configured when creating the
	 * transfer object.
	 *
	 * @param   string  $fileName  The relative path of a file or directory
	 *
	 * @return  string  The absolute path for use by the transfer object
	 */
	public function getPath($fileName)
	{
		$fileName = str_replace('\\', '/', $fileName);

		if (strpos($fileName, $this->directory) === 0)
		{
			return $fileName;
		}

		$fileName = trim($fileName, '/');
		$fileName = rtrim($this->directory, '/') . '/' . $fileName;

		return $fileName;
	}

	/**
	 * Lists the subdirectories inside an FTP directory
	 *
	 * @param   null|string  $dir  The directory to scan. Skip to use the current directory.
	 *
	 * @return  array|bool  A list of folders, or false if we could not get a listing
	 *
	 * @throws  RuntimeException  When the server is incompatible with our FTP folder scanner
	 */
	public function listFolders($dir = null)
	{
		if (!@ftp_chdir($this->connection, $dir))
		{
			throw new RuntimeException(sprintf('Cannot change to FTP directory "%s" – make sure the folder exists and that you have adequate permissions to it', $dir), 500);
		}

		$list = @ftp_rawlist($this->connection, '.');

		if ($list === false)
		{
			throw new RuntimeException("Sorry, your FTP server doesn't support our FTP directory browser.");
		}

		$folders = [];

		foreach ($list as $v)
		{
			$vInfo = preg_split("/[\s]+/", $v, 9);

			if ($vInfo[0] !== "total")
			{
				$perms = $vInfo[0];

				if (substr($perms, 0, 1) == 'd')
				{
					$folders[] = $vInfo[8];
				}
			}
		}

		asort($folders);

		return $folders;
	}

	/**
	 * Return a string with the appropriate stream wrapper protocol for $path. You can use the result with all PHP
	 * functions / classes which accept file paths such as DirectoryIterator, file_get_contents, file_put_contents,
	 * fopen etc.
	 *
	 * @param   string  $path
	 *
	 * @return  string
	 */
	public function getWrapperStringFor($path)
	{
		$usernameEncoded = urlencode($this->username);
		$passwordEncoded = urlencode($this->password);
		$hostname        = $this->host . ($this->port ? ":{$this->port}" : '');
		$protocol        = $this->ssl ? "ftps" : "ftp";

		return "{$protocol}://{$usernameEncoded}:{$passwordEncoded}@{$hostname}{$path}";
	}

	/**
	 * Return the raw server listing for the requested folder.
	 *
	 * @param   string  $folder  The path name to list
	 *
	 * @return  string
	 */
	public function getRawList($folder)
	{
		return ftp_rawlist($this->connection, $folder);
	}
}
PK�!]Nj�dd0BackupEngine/Util/Transfer/TransferInterface.phpnu&1i�<?php
/**
 * Akeeba Engine
 *
 * @package   akeebaengine
 * @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License version 3, or later
 *
 * 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, version 3.
 *
 * 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
 * <https://www.gnu.org/licenses/>.
 */

namespace Akeeba\Engine\Util\Transfer;

defined('AKEEBAENGINE') || die();

use RuntimeException;

/**
 * An interface for Transfer adapters, used to transfer files to remote servers over FTP, FTPS, SFTP and possibly other
 * file transfer methods we might implement.
 *
 * @package   Akeeba\Engine\Util\Transfer
 */
interface TransferInterface
{
	/**
	 * Creates the uploader
	 *
	 * @param   array  $config
	 */
	public function __construct(array $config);

	/**
	 * Is this transfer method blocked by a server firewall?
	 *
	 * @param   array  $params  Any additional parameters you might need to pass
	 *
	 * @return  boolean  True if the firewall blocks connections to a known host
	 */
	public static function isFirewalled(array $params = []);

	/**
	 * Write the contents into the file
	 *
	 * @param   string  $fileName  The full path to the remote file
	 * @param   string  $contents  The contents to write to the file
	 *
	 * @return  boolean  True on success
	 */
	public function write($fileName, $contents);

	/**
	 * Uploads a local file to the remote storage
	 *
	 * @param   string  $localFilename   The full path to the local file
	 * @param   string  $remoteFilename  The full path to the remote file
	 * @param   bool    $useExceptions   Throw an exception instead of returning "false" on connection error.
	 *
	 * @return  boolean  True on success
	 */
	public function upload($localFilename, $remoteFilename, $useExceptions = true);

	/**
	 * Read the contents of a remote file into a string
	 *
	 * @param   string  $fileName  The full path to the remote file
	 *
	 * @return  string  The contents of the remote file
	 */
	public function read($fileName);

	/**
	 * Download a remote file into a local file
	 *
	 * @param   string  $remoteFilename  The remote file path to download from
	 * @param   string  $localFilename   The local file path to download to
	 * @param   bool    $useExceptions   Throw an exception instead of returning "false" on connection error.
	 *
	 * @return  boolean  True on success
	 */
	public function download($remoteFilename, $localFilename, $useExceptions = true);

	/**
	 * Delete a remote file
	 *
	 * @param   string  $fileName  The full path to the remote file
	 *
	 * @return  boolean  True on success
	 */
	public function delete($fileName);

	/**
	 * Create a copy of the remote file
	 *
	 * @param   string  $from  The full path of the remote file to copy from
	 * @param   string  $to    The full path of the remote file that will hold the copy
	 *
	 * @return  boolean  True on success
	 */
	public function copy($from, $to);

	/**
	 * Move or rename a file
	 *
	 * @param   string  $from  The full remote path of the file to move
	 * @param   string  $to    The full remote path of the target file
	 *
	 * @return  boolean  True on success
	 */
	public function move($from, $to);

	/**
	 * Change the permissions of a file
	 *
	 * @param   string   $fileName     The full path of the remote file whose permissions will change
	 * @param   integer  $permissions  The new permissions, e.g. 0644 (remember the leading zero in octal numbers!)
	 *
	 * @return  boolean  True on success
	 */
	public function chmod($fileName, $permissions);

	/**
	 * Create a directory if it doesn't exist. The operation is implicitly recursive, i.e. it will create all
	 * intermediate directories if they do not already exist.
	 *
	 * @param   string   $dirName      The full path of the remote directory to create
	 * @param   integer  $permissions  The permissions of the created directory
	 *
	 * @return  boolean  True on success
	 */
	public function mkdir($dirName, $permissions = 0755);

	/**
	 * Checks if the given directory exists
	 *
	 * @param   string  $path  The full path of the remote directory to check
	 *
	 * @return  boolean  True if the directory exists
	 */
	public function isDir($path);

	/**
	 * Get the current working directory
	 *
	 * @return  string
	 */
	public function cwd();

	/**
	 * Returns the absolute remote path from a path relative to the initial directory configured when creating the
	 * transfer object.
	 *
	 * @param   string  $fileName  The relative path of a file or directory
	 *
	 * @return  string  The absolute path for use by the transfer object
	 */
	public function getPath($fileName);

	/**
	 * Lists the subdirectories inside a directory
	 *
	 * @param   null|string  $dir  The directory to scan. Skip to use the current directory.
	 *
	 * @return  array|bool  A list of folders, or false if we could not get a listing
	 *
	 * @throws  RuntimeException  When the server is incompatible with our folder scanner
	 */
	public function listFolders($dir = null);
}
PK�!]�=�c�L�L&BackupEngine/Util/Transfer/FtpCurl.phpnu&1i�<?php
/**
 * Akeeba Engine
 *
 * @package   akeebaengine
 * @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License version 3, or later
 *
 * 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, version 3.
 *
 * 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
 * <https://www.gnu.org/licenses/>.
 */

namespace Akeeba\Engine\Util\Transfer;

defined('AKEEBAENGINE') || die();

use Akeeba\Engine\Postproc\ProxyAware;
use RuntimeException;

/**
 * FTP transfer object, using cURL as the transport backend
 */
class FtpCurl extends Ftp implements TransferInterface
{
	use ProxyAware;

	/**
	 * Timeout for transferring data to the FTP server, default: 10 minutes
	 *
	 * @var  integer
	 */
	protected $timeout = 600;

	/**
	 * Should I ignore the IP returned by the server during Passive mode transfers?
	 *
	 * @var   bool
	 *
	 * @see   http://www.elitehosts.com/blog/php-ftp-passive-ftp-server-behind-nat-nightmare/
	 */
	private $skipPassiveIP = true;

	/**
	 * Should we enable verbose output to STDOUT? Useful for debugging.
	 *
	 * @var   bool
	 */
	private $verbose = false;

	/**
	 * Public constructor
	 *
	 * @param   array  $options  Configuration options
	 *
	 * @throws  RuntimeException
	 */
	public function __construct(array $options)
	{
		parent::__construct($options);

		if (isset($options['passive_fix']))
		{
			$this->skipPassiveIP = $options['passive_fix'] ? true : false;
		}

		if (isset($options['verbose']))
		{
			$this->verbose = $options['verbose'] ? true : false;
		}
	}

	/**
	 * Save all parameters on serialization except the connection resource
	 *
	 * @return  array
	 */
	public function __sleep()
	{
		return [
			'host',
			'port',
			'username',
			'password',
			'directory',
			'ssl',
			'passive',
			'timeout',
			'skipPassiveIP',
			'verbose',
		];
	}

	/**
	 * Test the connection to the FTP server and whether the initial directory is correct. This is done by attempting to
	 * list the contents of the initial directory. The listing is not parsed (we don't really care!) and we do NOT check
	 * if we can upload files to that remote folder.
	 *
	 * @throws  RuntimeException
	 */
	public function connect()
	{
		$ch = $this->getCurlHandle($this->directory . '/');
		curl_setopt($ch, CURLOPT_HEADER, 1);
		curl_setopt($ch, CURLOPT_NOBODY, 1);
		curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);

		curl_exec($ch);

		$errNo = curl_errno($ch);
		$error = curl_error($ch);
		curl_close($ch);

		if ($errNo)
		{
			throw new RuntimeException("cURL Error $errNo connecting to remote FTP server: $error", 500);
		}
	}

	/**
	 * Write the contents into the file
	 *
	 * @param   string  $fileName  The full path to the file
	 * @param   string  $contents  The contents to write to the file
	 *
	 * @return  boolean  True on success
	 */
	public function write($fileName, $contents)
	{
		// Make sure the buffer:// wrapper is loaded
		class_exists('\\Akeeba\\Engine\\Util\\Buffer', true);

		$handle = fopen('buffer://akeeba_engine_transfer_ftp_curl', 'r+');
		fwrite($handle, $contents);

		// Note: don't manually close the file pointer, it's closed automatically by uploadFromHandle
		try
		{
			$this->uploadFromHandle($fileName, $handle);
		}
		catch (RuntimeException $e)
		{
			return false;
		}

		return true;
	}

	/**
	 * Uploads a local file to the remote storage
	 *
	 * @param   string  $localFilename   The full path to the local file
	 * @param   string  $remoteFilename  The full path to the remote file
	 * @param   bool    $useExceptions   Throw an exception instead of returning "false" on connection error.
	 *
	 * @return  boolean  True on success
	 */
	public function upload($localFilename, $remoteFilename, $useExceptions = true)
	{
		$fp = @fopen($localFilename, 'r');

		if ($fp === false)
		{
			throw new RuntimeException("Unreadable local file $localFilename");
		}

		// Note: don't manually close the file pointer, it's closed automatically by uploadFromHandle
		try
		{
			$this->uploadFromHandle($remoteFilename, $fp);
		}
		catch (RuntimeException $e)
		{
			if ($useExceptions)
			{
				throw $e;
			}

			return false;
		}

		return true;
	}

	/**
	 * Read the contents of a remote file into a string
	 *
	 * @param   string  $fileName  The full path to the remote file
	 *
	 * @return  string  The contents of the remote file
	 */
	public function read($fileName)
	{
		try
		{
			return $this->downloadToString($fileName);
		}
		catch (RuntimeException $e)
		{
			throw new RuntimeException("Can not download remote file $fileName", 500, $e);
		}
	}

	/**
	 * Download a remote file into a local file
	 *
	 * @param   string  $remoteFilename  The remote file path to download from
	 * @param   string  $localFilename   The local file path to download to
	 * @param   bool    $useExceptions   Throw an exception instead of returning "false" on connection error.
	 *
	 * @return  boolean  True on success
	 */
	public function download($remoteFilename, $localFilename, $useExceptions = true)
	{
		$fp = @fopen($localFilename, 'w');

		if ($fp === false)
		{
			if ($useExceptions)
			{
				throw new RuntimeException(sprintf('Download from FTP failed. Can not open local file %s for writing.', $localFilename));
			}

			return false;
		}

		// Note: don't manually close the file pointer, it's closed automatically by downloadToHandle
		try
		{
			$this->downloadToHandle($remoteFilename, $fp);
		}
		catch (RuntimeException $e)
		{
			if ($useExceptions)
			{
				throw $e;
			}

			return false;
		}

		return true;
	}

	/**
	 * Delete a file (remove it from the disk)
	 *
	 * @param   string  $fileName  The full path to the file
	 *
	 * @return  boolean  True on success
	 */
	public function delete($fileName)
	{
		$commands = [
			'DELE ' . $this->getPath($fileName),
		];

		try
		{
			$this->executeServerCommands($commands);
		}
		catch (RuntimeException $e)
		{
			return false;
		}

		return true;
	}

	/**
	 * Create a copy of the file. Actually, we have to read it in memory and upload it again.
	 *
	 * @param   string  $from  The full path of the file to copy from
	 * @param   string  $to    The full path of the file that will hold the copy
	 *
	 * @return  boolean  True on success
	 */
	public function copy($from, $to)
	{
		// Make sure the buffer:// wrapper is loaded
		class_exists('\\Akeeba\\Engine\\Util\\Buffer', true);

		$handle = fopen('buffer://akeeba_engine_transfer_ftp', 'r+');

		try
		{
			$this->downloadToHandle($from, $handle, false);
			$this->uploadFromHandle($to, $handle);
		}
		catch (RuntimeException $e)
		{
			return false;
		}

		return true;
	}

	/**
	 * Move or rename a file
	 *
	 * @param   string  $from  The full path of the file to move
	 * @param   string  $to    The full path of the target file
	 *
	 * @return  boolean  True on success
	 */
	public function move($from, $to)
	{
		$from = $this->getPath($from);
		$to   = $this->getPath($to);

		$commands = [
			'RNFR /' . $from,
			'RNTO /' . $to,
		];

		try
		{
			$this->executeServerCommands($commands);
		}
		catch (RuntimeException $e)
		{
			return false;
		}

		return true;
	}

	/**
	 * Change the permissions of a file
	 *
	 * @param   string   $fileName     The full path of the file whose permissions will change
	 * @param   integer  $permissions  The new permissions, e.g. 0644 (remember the leading zero in octal numbers!)
	 *
	 * @return  boolean  True on success
	 */
	public function chmod($fileName, $permissions)
	{
		// Make sure permissions are in an octal string representation
		if (!is_string($permissions))
		{
			$permissions = decoct($permissions);
		}

		$commands = [
			'SITE CHMOD ' . $permissions . ' /' . $this->getPath($fileName),
		];

		try
		{
			$this->executeServerCommands($commands);
		}
		catch (RuntimeException $e)
		{
			return false;
		}

		return true;
	}

	/**
	 * Create a directory if it doesn't exist. The operation is implicitly recursive, i.e. it will create all
	 * intermediate directories if they do not already exist.
	 *
	 * @param   string   $dirName      The full path of the directory to create
	 * @param   integer  $permissions  The permissions of the created directory
	 *
	 * @return  boolean  True on success
	 */
	public function mkdir($dirName, $permissions = 0755)
	{
		$targetDir = rtrim($dirName, '/');

		$directories = explode('/', $targetDir);

		$remoteDir = '';

		foreach ($directories as $dir)
		{
			if (!$dir)
			{
				continue;
			}

			$remoteDir .= '/' . $dir;

			// Continue if the folder already exists. Otherwise I'll get a an error even if everything is fine
			if ($this->isDir($remoteDir))
			{
				continue;
			}

			$commands = [
				'MKD ' . $remoteDir,
			];

			try
			{
				$this->executeServerCommands($commands);
			}
			catch (RuntimeException $e)
			{
				return false;
			}
		}

		$this->chmod($dirName, $permissions);

		return true;
	}

	/**
	 * Checks if the given directory exists
	 *
	 * @param   string  $path  The full path of the remote directory to check
	 *
	 * @return  boolean  True if the directory exists
	 */
	public function isDir($path)
	{
		$ch = $this->getCurlHandle($path . '/');
		curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);

		curl_exec($ch);

		$errNo = curl_errno($ch);
		curl_close($ch);

		if ($errNo)
		{
			return false;
		}

		return true;
	}

	/**
	 * Get the current working directory. NOT IMPLEMENTED.
	 *
	 * @return  string
	 */
	public function cwd()
	{
		$commands = [
			'PWD',
		];

		try
		{
			$result = $this->executeServerCommands($commands, '');
		}
		catch (RuntimeException $e)
		{
			return '/';
		}

		return $result;
	}

	/**
	 * Lists the subdirectories inside an FTP directory
	 *
	 * @param   null|string  $dir  The directory to scan. Skip to use the current directory.
	 *
	 * @return  array|bool   A list of folders, or false if we could not get a listing
	 *
	 * @throws  RuntimeException  When the server is incompatible with our FTP folder scanner
	 */
	public function listFolders($dir = null)
	{
		if (empty($dir))
		{
			$dir = $this->directory;
		}

		$dir = rtrim($dir, '/');

		$ch = $this->getCurlHandle($dir . '/');
		curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);

		$list = curl_exec($ch);

		$errNo = curl_errno($ch);
		$error = curl_error($ch);
		curl_close($ch);

		if ($errNo)
		{
			throw new RuntimeException(sprintf("cURL Error $errNo ($error) while listing contents of directory \"%s\" – make sure the folder exists and that you have adequate permissions to it", $dir), 500);
		}

		if (empty($list))
		{
			throw new RuntimeException("Sorry, your FTP server doesn't support our FTP directory browser.");
		}

		$folders = [];

		// Convert the directory listing into an array of lines without *NIX/Windows/Mac line ending characters
		$list = explode("\n", $list);
		$list = array_map('rtrim', $list);

		foreach ($list as $v)
		{
			$vInfo = preg_split("/[\s]+/", $v, 9);

			if ($vInfo[0] !== "total")
			{
				$perms = $vInfo[0];

				if (substr($perms, 0, 1) == 'd')
				{
					$folders[] = $vInfo[8];
				}
			}
		}

		asort($folders);

		return $folders;
	}

	/**
	 * Is the verbose debug option set?
	 *
	 * @return  boolean
	 */
	public function isVerbose()
	{
		return $this->verbose;
	}

	/**
	 * Set the verbose debug option
	 *
	 * @param   boolean  $verbose
	 *
	 * @return  void
	 */
	public function setVerbose($verbose)
	{
		$this->verbose = $verbose;
	}

	/**
	 * Returns the absolute remote path from a path relative to the initial directory configured when creating the
	 * transfer object.
	 *
	 * @param   string  $fileName  The relative path of a file or directory
	 *
	 * @return  string  The absolute path for use by the transfer object
	 */
	public function getPath($fileName)
	{
		$fileName = ltrim(str_replace('\\', '/', $fileName), '/');

		$isInitialDirectory = $fileName === $this->directory;
		$startsWithInitialDirectory = (strpos($fileName, rtrim($this->directory, '/') . '/') === 0)
			|| (strpos($fileName, trim($this->directory, '/') . '/') === 0);

		// Relative file? Add the initial directory
		if (!$isInitialDirectory && !$startsWithInitialDirectory)
		{
			$fileName = '/' . trim($this->directory, '/') .
				(empty($fileName) ? '' : '/') . $fileName;
		}

		return '/' . ltrim($fileName, '/');
	}

	/**
	 * Returns a cURL resource handler for the remote FTP server
	 *
	 * @param   string  $remoteFile  Optional. The remote file / folder on the FTP server you'll be manipulating with cURL.
	 *
	 * @return  resource
	 */
	protected function getCurlHandle($remoteFile = '')
	{
		/**
		 * Get the FTP URI
		 *
		 * VERY IMPORTANT! WE NEED THE DOUBLE SLASH AFTER THE HOST NAME since we are giving an absolute path.
		 * @see https://technicalsanctuary.wordpress.com/2012/11/01/curl-curl-9-server-denied-you-to-change-to-the-given-directory/
		 */
		$ftpUri = 'ftp://' . $this->host . '//';

		$isInitialDirectory = $remoteFile === $this->directory;
		$startsWithInitialDirectory = (strpos($remoteFile, rtrim($this->directory, '/') . '/') === 0)
			|| (strpos($remoteFile, trim($this->directory, '/') . '/') === 0);

		// Relative file? Add the initial directory
		if (!$isInitialDirectory && !$startsWithInitialDirectory)
		{
			$ftpUri .= '/' . trim($this->directory, '/');
		}

		if (!empty($remoteFile) && substr($ftpUri, -2) !== '//')
		{
			$ftpUri .= '/';
		}

		// Add a remote file if necessary. The filename must be URL encoded since we're creating a URI.
		if (!empty($remoteFile))
		{
			$suffix = '';

			if (substr($remoteFile, -7, 6) == ';type=')
			{
				$suffix     = substr($remoteFile, -7);
				$remoteFile = substr($remoteFile, 0, -7);
			}

			$dirname = dirname($remoteFile);

			// Windows messing up dirname('/'). KILL ME.
			if ($dirname == '\\')
			{
				$dirname = '';
			}

			$dirname  = trim($dirname, '/');
			$basename = basename($remoteFile);

			if ((substr($remoteFile, -1) == '/') && !empty($basename))
			{
				$suffix = '/' . $suffix;
			}

			$ftpUri .= '/' . $dirname . (empty($dirname) ? '' : '/') . urlencode($basename) . $suffix;
		}

		// Colons in usernames must be URL escaped
		$username = str_replace(':', '%3A', $this->username);

		$ch = curl_init();

		$this->applyProxySettingsToCurl($ch);

		curl_setopt($ch, CURLOPT_URL, $ftpUri);
		curl_setopt($ch, CURLOPT_USERPWD, $username . ":" . $this->password);
		curl_setopt($ch, CURLOPT_PORT, $this->port);
		curl_setopt($ch, CURLOPT_TIMEOUT, $this->timeout);

		// Should I enable Implict SSL?
		if ($this->ssl)
		{
			curl_setopt($ch, CURLOPT_FTP_SSL, CURLFTPSSL_ALL);
			curl_setopt($ch, CURLOPT_FTPSSLAUTH, CURLFTPAUTH_DEFAULT);

			// Most FTPS servers use self-signed certificates. That's the only way to connect to them :(
			curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
			curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
		}

		// Should I ignore the server-supplied passive mode IP address?
		if ($this->passive && $this->skipPassiveIP)
		{
			curl_setopt($ch, CURLOPT_FTP_SKIP_PASV_IP, 1);
		}

		// Should I enable active mode?
		if (!$this->passive)
		{
			/**
			 * cURL always uses passive mode for FTP transfers. Setting the CURLOPT_FTPPORT flag enables the FTP PORT
			 * command which makes the connection active. Setting it to '-'  lets the library use your system's default
			 * IP address.
			 *
			 * @see https://curl.haxx.se/libcurl/c/CURLOPT_FTPPORT.html
			 */
			curl_setopt($ch, CURLOPT_FTPPORT, '-');
		}

		// Should I enable verbose output? Useful for debugging.
		if ($this->verbose)
		{
			curl_setopt($ch, CURLOPT_VERBOSE, 1);
		}

		// Automatically create missing directories
		curl_setopt($ch, CURLOPT_FTP_CREATE_MISSING_DIRS, 1);

		return $ch;
	}

	/**
	 * Uploads a file using file contents provided through a file handle
	 *
	 * @param   string    $remoteFilename  Remote file to write contents to
	 * @param   resource  $fp              File or stream handler of the source data to upload
	 *
	 * @return  void
	 *
	 * @throws  RuntimeException
	 */
	protected function uploadFromHandle($remoteFilename, $fp)
	{
		// We need the file size. We can do that by getting the file position at EOF
		fseek($fp, 0, SEEK_END);
		$filesize = ftell($fp);
		rewind($fp);

		/**
		 * The ;type=i suffix forces Binary file transfer mode
		 *
		 * @see  https://curl.haxx.se/mail/archive-2008-05/0089.html
		 */
		$ch = $this->getCurlHandle($remoteFilename . ';type=i');
		curl_setopt($ch, CURLOPT_UPLOAD, 1);
		curl_setopt($ch, CURLOPT_INFILE, $fp);
		curl_setopt($ch, CURLOPT_INFILESIZE, $filesize);

		curl_exec($ch);

		$error_no = curl_errno($ch);
		$error    = curl_error($ch);

		curl_close($ch);
		fclose($fp);

		if ($error_no)
		{
			throw new RuntimeException($error, $error_no);
		}
	}

	/**
	 * Downloads a remote file to the provided file handle
	 *
	 * @param   string    $remoteFilename  Filename on the remote server
	 * @param   resource  $fp              File handle where the downloaded content will be written to
	 * @param   bool      $close           Optional. Should I close the file handle when I'm done? (Default: true)
	 *
	 * @return  void
	 *
	 * @throws  RuntimeException
	 */
	protected function downloadToHandle($remoteFilename, $fp, $close = true)
	{
		/**
		 * The ;type=i suffix forces Binary file transfer mode
		 *
		 * @see  https://curl.haxx.se/mail/archive-2008-05/0089.html
		 */
		$ch = $this->getCurlHandle($remoteFilename . ';type=i');

		curl_setopt($ch, CURLOPT_FILE, $fp);

		curl_exec($ch);

		$error_no = curl_errno($ch);
		$error    = curl_error($ch);

		curl_close($ch);

		if ($close)
		{
			fclose($fp);
		}

		if ($error_no)
		{
			throw new RuntimeException($error, $error_no);
		}
	}

	/**
	 * Downloads a remote file and returns it as a string
	 *
	 * @param   string  $remoteFilename  Filename on the remote server
	 *
	 * @return  string
	 *
	 * @throws  RuntimeException
	 */
	protected function downloadToString($remoteFilename)
	{
		/**
		 * The ;type=i suffix forces Binary file transfer mode
		 *
		 * @see  https://curl.haxx.se/mail/archive-2008-05/0089.html
		 */
		$ch = $this->getCurlHandle($remoteFilename . ';type=i');

		curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
		curl_setopt($ch, CURLOPT_HEADER, false);

		$ret = curl_exec($ch);

		$error_no = curl_errno($ch);
		$error    = curl_error($ch);

		curl_close($ch);

		if ($error_no)
		{
			throw new RuntimeException($error, $error_no);
		}

		return $ret;
	}

	/**
	 * Executes arbitrary FTP commands
	 *
	 * @param   string[]     $commands    An array with the FTP commands to be executed
	 * @param   string|null  $remoteFile  The remote file / folder to use in the cURL URI when executing the commands
	 *
	 * @return  string  The output of the executed commands
	 *
	 */
	protected function executeServerCommands(array $commands, ?string $remoteFile = null): string
	{
		$remoteFile = $remoteFile ?? ($this->directory . '/');

		$ch = $this->getCurlHandle($remoteFile);

		curl_setopt($ch, CURLOPT_QUOTE, $commands);
		curl_setopt($ch, CURLOPT_HEADER, 1);
		curl_setopt($ch, CURLOPT_NOBODY, 1);
		curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);

		$listing = curl_exec($ch);
		$errNo   = curl_errno($ch);
		$error   = curl_error($ch);
		curl_close($ch);

		if ($errNo)
		{
			throw new RuntimeException($error, $errNo);
		}

		return $listing;
	}
}
PK�!]�;f��:�:#BackupEngine/Util/Transfer/Sftp.phpnu&1i�<?php
/**
 * Akeeba Engine
 *
 * @package   akeebaengine
 * @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License version 3, or later
 *
 * 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, version 3.
 *
 * 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
 * <https://www.gnu.org/licenses/>.
 */

namespace Akeeba\Engine\Util\Transfer;

defined('AKEEBAENGINE') || die();

use DirectoryIterator;
use Exception;
use RuntimeException;

/**
 * SFTP transfer object
 */
class Sftp implements TransferInterface, RemoteResourceInterface
{
	/**
	 * SFTP server's hostname or IP address
	 *
	 * @var  string
	 */
	private $host = 'localhost';

	/**
	 * SFTP server's port, default: 21
	 *
	 * @var  integer
	 */
	private $port = 22;

	/**
	 * Username used to authenticate to the SFTP server
	 *
	 * @var  string
	 */
	private $username = '';

	/**
	 * Password used to authenticate to the SFTP server
	 *
	 * @var  string
	 */
	private $password = '';

	/**
	 * SFTP initial directory
	 *
	 * @var  string
	 */
	private $directory = '/';

	/**
	 * The absolute filesystem path to a private key file used for authentication instead of a password.
	 *
	 * @var  string
	 */
	private $privateKey = '';

	/**
	 * The absolute filesystem path to a public key file used for authentication instead of a password.
	 *
	 * @var  string
	 */
	private $publicKey = '';

	/**
	 * The SSH2 connection handle
	 *
	 * @var  resource|null
	 */
	private $connection = null;

	/**
	 * The SFTP connection handle
	 *
	 * @var  resource|null
	 */
	private $sftpHandle = null;

	/**
	 * Public constructor
	 *
	 * @param   array  $options  Configuration options for the filesystem abstraction object
	 *
	 * @return  Sftp
	 *
	 * @throws  RuntimeException
	 */
	public function __construct(array $options)
	{
		if (isset($options['host']))
		{
			$this->host = $options['host'];
		}

		if (isset($options['port']))
		{
			$this->port = (int) $options['port'];
		}

		if (isset($options['username']))
		{
			$this->username = $options['username'];
		}

		if (isset($options['password']))
		{
			$this->password = $options['password'];
		}

		if (isset($options['directory']))
		{
			$this->directory = '/' . ltrim(trim($options['directory']), '/');
		}

		if (isset($options['privateKey']))
		{
			$this->privateKey = $options['privateKey'];
		}

		if (isset($options['publicKey']))
		{
			$this->publicKey = $options['publicKey'];
		}

		$this->connect();
	}

	/**
	 * Is this transfer method blocked by a server firewall?
	 *
	 * @param   array  $params  Any additional parameters you might need to pass
	 *
	 * @return  boolean  True if the firewall blocks connections to a known host
	 */
	public static function isFirewalled(array $params = [])
	{
		try
		{
			$connector = new static([
				'host'      => 'test.rebex.net',
				'port'      => 22,
				'username'  => 'demo',
				'password'  => 'password',
				'directory' => '',
			]);

			$data = $connector->read('readme.txt');

			if (empty($data))
			{
				return true;
			}
		}
		catch (Exception $e)
		{
			return true;
		}

		return false;
	}

	/**
	 * Save all parameters on serialization except the connection resource
	 *
	 * @return  array
	 */
	public function __sleep()
	{
		return ['host', 'port', 'username', 'password', 'directory', 'privateKey', 'publicKey'];
	}

	/**
	 * Reconnect to the server on unserialize
	 *
	 * @return  void
	 */
	public function __wakeup()
	{
		$this->connect();
	}

	public function __destruct()
	{
		if (is_resource($this->connection))
		{
			@ssh2_exec($this->connection, 'exit;');
			$this->connection = null;
			$this->sftpHandle = null;
		}
	}

	/**
	 * Connect to the FTP server
	 *
	 * @throws  RuntimeException
	 */
	public function connect()
	{
		// Try to connect to the SSH server
		if (!function_exists('ssh2_connect'))
		{
			throw new RuntimeException('Your web server does not have the SSH2 PHP module, therefore can not connect to SFTP servers.', 500);
		}

		$this->connection = ssh2_connect($this->host, $this->port);

		if ($this->connection === false)
		{
			$this->connection = null;

			throw new RuntimeException(sprintf('Cannot connect to SFTP server [host:port] = %s:%s', $this->host, $this->port), 500);
		}

		// Attempt to authenticate
		if (!empty($this->publicKey) && !empty($this->privateKey))
		{
			if (!@ssh2_auth_pubkey_file($this->connection, $this->username, $this->publicKey, $this->privateKey, $this->password))
			{
				$this->connection = null;

				throw new RuntimeException(sprintf('Cannot log in to SFTP server using key files [username:private_key_file:public_key_file:password] = %s:%s:%s:%s', $this->username, $this->privateKey, $this->publicKey, $this->password), 500);
			}
		}
		else
		{
			if (!@ssh2_auth_password($this->connection, $this->username, $this->password))
			{
				$this->connection = null;

				throw new RuntimeException(sprintf('Cannot log in to SFTP server [username:password] = %s:%s', $this->username, $this->password), 500);
			}
		}

		// Get an SFTP handle
		$this->sftpHandle = ssh2_sftp($this->connection);

		if ($this->sftpHandle === false)
		{
			throw new RuntimeException('Cannot start an SFTP session with the server', 500);
		}
	}

	/**
	 * Write the contents into the file
	 *
	 * @param   string  $fileName  The full path to the file
	 * @param   string  $contents  The contents to write to the file
	 *
	 * @return  boolean  True on success
	 */
	public function write($fileName, $contents)
	{
		$fp = @fopen("ssh2.sftp://{$this->sftpHandle}/$fileName", 'w');

		if ($fp === false)
		{
			return false;
		}

		$ret = @fwrite($fp, $contents);

		@fclose($fp);

		return $ret;
	}

	/**
	 * Uploads a local file to the remote storage
	 *
	 * @param   string  $localFilename   The full path to the local file
	 * @param   string  $remoteFilename  The full path to the remote file
	 * @param   bool    $useExceptions   Throw an exception instead of returning "false" on connection error.
	 *
	 * @return  boolean  True on success
	 */
	public function upload($localFilename, $remoteFilename, $useExceptions = true)
	{
		$fp = @fopen("ssh2.sftp://{$this->sftpHandle}/$remoteFilename", 'w');

		if ($fp === false)
		{
			if ($useExceptions)
			{
				throw new RuntimeException("Could not open remote SFTP file $remoteFilename for writing");
			}

			return false;
		}

		$localFp = @fopen($localFilename, 'r');

		if ($localFp === false)
		{
			fclose($fp);

			if ($useExceptions)
			{
				throw new RuntimeException("Could not open local file $localFilename for reading");
			}

			return false;
		}

		while (!feof($localFp))
		{
			$data = fread($localFp, 131072);
			$ret  = @fwrite($fp, $data);

			if ($ret < strlen($data))
			{
				fclose($fp);
				fclose($localFp);

				if ($useExceptions)
				{
					throw new RuntimeException("An error occurred while copying file $localFilename to $remoteFilename");
				}

				return false;
			}
		}

		@fclose($fp);
		@fclose($localFp);

		return true;
	}

	/**
	 * Read the contents of a remote file into a string
	 *
	 * @param   string  $fileName  The full path to the remote file
	 *
	 * @return  string  The contents of the remote file
	 */
	public function read($fileName)
	{
		$fp = @fopen("ssh2.sftp://{$this->sftpHandle}/$fileName", 'r');

		if ($fp === false)
		{
			throw new RuntimeException("Can not download remote file $fileName");
		}

		$ret = '';

		while (!feof($fp))
		{
			$ret .= fread($fp, 131072);
		}

		@fclose($fp);

		return $ret;
	}

	/**
	 * Download a remote file into a local file
	 *
	 * @param   string  $remoteFilename  The remote file path to download from
	 * @param   string  $localFilename   The local file path to download to
	 * @param   bool    $useExceptions   Throw an exception instead of returning "false" on connection error.
	 *
	 * @return  boolean  True on success
	 */
	public function download($remoteFilename, $localFilename, $useExceptions = true)
	{
		$fp = @fopen("ssh2.sftp://{$this->sftpHandle}/$remoteFilename", 'r');

		if ($fp === false)
		{
			if ($useExceptions)
			{
				throw new RuntimeException("Could not open remote SFTP file $remoteFilename for reading");
			}

			return false;
		}

		$localFp = @fopen($localFilename, 'w');

		if ($localFp === false)
		{
			fclose($fp);

			if ($useExceptions)
			{
				throw new RuntimeException("Could not open local file $localFilename for writing");
			}

			return false;
		}

		while (!feof($fp))
		{
			$chunk = fread($fp, 131072);

			if ($chunk === false)
			{
				fclose($fp);
				fclose($localFp);

				if ($useExceptions)
				{
					throw new RuntimeException("An error occurred while copying file $remoteFilename to $localFilename");
				}

				return false;
			}

			fwrite($localFp, $chunk);
		}

		@fclose($fp);
		@fclose($localFp);

		return true;
	}

	/**
	 * Delete a file (remove it from the disk)
	 *
	 * @param   string  $fileName  The full path to the file
	 *
	 * @return  boolean  True on success
	 */
	public function delete($fileName)
	{
		try
		{
			$ret = @ssh2_sftp_unlink($this->sftpHandle, $fileName);
		}
		catch (Exception $e)
		{
			$ret = false;
		}

		return $ret;
	}

	/**
	 * Create a copy of the file. Actually, we have to read it in memory and upload it again.
	 *
	 * @param   string  $from  The full path of the file to copy from
	 * @param   string  $to    The full path of the file that will hold the copy
	 *
	 * @return  boolean  True on success
	 */
	public function copy($from, $to)
	{
		$contents = @file_get_contents($from);

		return $this->write($to, $contents);
	}

	/**
	 * Move or rename a file. Actually, we have to read it, upload it again and then delete the original.
	 *
	 * @param   string  $from  The full path of the file to move
	 * @param   string  $to    The full path of the target file
	 *
	 * @return  boolean  True on success
	 */
	public function move($from, $to)
	{
		$ret = $this->copy($from, $to);

		if ($ret)
		{
			$ret = $this->delete($from);
		}

		return $ret;
	}

	/**
	 * Change the permissions of a file
	 *
	 * @param   string   $fileName     The full path of the file whose permissions will change
	 * @param   integer  $permissions  The new permissions, e.g. 0644 (remember the leading zero in octal numbers!)
	 *
	 * @return  boolean  True on success
	 */
	public function chmod($fileName, $permissions)
	{
		// Prefer the SFTP way, if available
		if (function_exists('ssh2_sftp_chmod'))
		{
			return @ssh2_sftp_chmod($this->sftpHandle, $fileName, $permissions);
		}
		// Otherwise fall back to the (likely to fail) raw command mode
		else
		{
			$cmd = 'chmod ' . decoct($permissions) . ' ' . escapeshellarg($fileName);

			return @ssh2_exec($this->connection, $cmd);
		}
	}

	/**
	 * Create a directory if it doesn't exist. The operation is implicitly recursive, i.e. it will create all
	 * intermediate directories if they do not already exist.
	 *
	 * @param   string   $dirName      The full path of the directory to create
	 * @param   integer  $permissions  The permissions of the created directory
	 *
	 * @return  boolean  True on success
	 */
	public function mkdir($dirName, $permissions = 0755)
	{
		$targetDir = rtrim($dirName, '/');

		$ret = @ssh2_sftp_mkdir($this->sftpHandle, $targetDir, $permissions, true);

		return $ret;
	}

	/**
	 * Checks if the given directory exists
	 *
	 * @param   string  $path  The full path of the remote directory to check
	 *
	 * @return  boolean  True if the directory exists
	 */
	public function isDir($path)
	{
		return @ssh2_sftp_stat($this->sftpHandle, $path);
	}

	/**
	 * Get the current working directory
	 *
	 * @return  string
	 */
	public function cwd()
	{
		return ssh2_sftp_realpath($this->sftpHandle, ".");
	}

	/**
	 * Returns the absolute remote path from a path relative to the initial directory configured when creating the
	 * transfer object.
	 *
	 * @param   string  $fileName  The relative path of a file or directory
	 *
	 * @return  string  The absolute path for use by the transfer object
	 */
	public function getPath($fileName)
	{
		$fileName = str_replace('\\', '/', $fileName);
		$fileName = rtrim($this->directory, '/') . '/' . $fileName;

		return $fileName;
	}

	/**
	 * Lists the subdirectories inside an SFTP directory
	 *
	 * @param   null|string  $dir  The directory to scan. Skip to use the current directory.
	 *
	 * @return  array|bool  A list of folders, or false if we could not get a listing
	 *
	 * @throws  RuntimeException  When the server is incompatible with our SFTP folder scanner
	 */
	public function listFolders($dir = null)
	{
		if (empty($dir))
		{
			$dir = $this->directory;
		}

		// Get a raw directory listing (hoping it's a UNIX server!)
		$list = [];
		$dir  = ltrim($dir, '/');

		try
		{
			$di = new DirectoryIterator("ssh2.sftp://" . $this->sftpHandle . "/$dir");
		}
		catch (Exception $e)
		{
			throw new RuntimeException(sprintf('Cannot change to SFTP directory "%s" – make sure the folder exists and that you have adequate permissions to it', $dir), 500);
		}

		if (!$di->valid())
		{
			throw new RuntimeException(sprintf('Cannot change to SFTP directory "%s" – make sure the folder exists and that you have adequate permissions to it', $dir), 500);
		}

		/** @var DirectoryIterator $entry */
		foreach ($di as $entry)
		{
			if ($entry->isDot())
			{
				continue;
			}

			if (!$entry->isDir())
			{
				continue;
			}

			$list[] = $entry->getFilename();
		}

		unset($di);

		if (!empty($list))
		{
			asort($list);
		}

		return $list;
	}

	/**
	 * Return a string with the appropriate stream wrapper protocol for $path. You can use the result with all PHP
	 * functions / classes which accept file paths such as DirectoryIterator, file_get_contents, file_put_contents,
	 * fopen etc.
	 *
	 * @param   string  $path
	 *
	 * @return  string
	 */
	public function getWrapperStringFor($path)
	{
		return "ssh2.sftp://{$this->sftpHandle}{$path}";
	}

	/**
	 * Return the raw server listing for the requested folder.
	 *
	 * @param   string  $folder  The path name to list
	 *
	 * @return  string
	 */
	public function getRawList($folder)
	{
		// First try the command for Linxu servers
		$res = $this->ssh2cmd('ls -l ' . escapeshellarg($folder));

		// If an error occurred let's try the command for Windows servers
		if (empty($res))
		{
			$res = $this->ssh2cmd('CMD /C ' . escapeshellarg($folder));
		}

		return $res;
	}

	private function ssh2cmd($command)
	{
		$stream = ssh2_exec($this->connection, $command);
		stream_set_blocking($stream, true);
		$res = @stream_get_contents($stream);
		@fclose($stream);

		return $res;
	}
}
PK�!]�du�M�M'BackupEngine/Util/Transfer/SftpCurl.phpnu&1i�<?php
/**
 * Akeeba Engine
 *
 * @package   akeebaengine
 * @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License version 3, or later
 *
 * 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, version 3.
 *
 * 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
 * <https://www.gnu.org/licenses/>.
 */

namespace Akeeba\Engine\Util\Transfer;

defined('AKEEBAENGINE') || die();

use Akeeba\Engine\Postproc\ProxyAware;
use RuntimeException;

/**
 * SFTP transfer object, using cURL as the transport backend
 */
class SftpCurl extends Sftp implements TransferInterface
{
	use ProxyAware;

	/**
	 * SFTP server's hostname or IP address
	 *
	 * @var  string
	 */
	private $host = 'localhost';

	/**
	 * SFTP server's port, default: 21
	 *
	 * @var  integer
	 */
	private $port = 22;

	/**
	 * Username used to authenticate to the SFTP server
	 *
	 * @var  string
	 */
	private $username = '';

	/**
	 * Password used to authenticate to the SFTP server
	 *
	 * @var  string
	 */
	private $password = '';

	/**
	 * SFTP initial directory
	 *
	 * @var  string
	 */
	private $directory = '/';

	/**
	 * The absolute filesystem path to a private key file used for authentication instead of a password.
	 *
	 * @var  string
	 */
	private $privateKey = '';

	/**
	 * The absolute filesystem path to a public key file used for authentication instead of a password.
	 *
	 * @var  string
	 */
	private $publicKey = '';

	/**
	 * Timeout for connecting to the SFTP server, default: 10 minutes
	 *
	 * @var  integer
	 */
	private $timeout = 600;

	/**
	 * Should we enable verbose output to STDOUT? Useful for debugging.
	 *
	 * @var   bool
	 */
	private $verbose = false;

	/**
	 * Should I enabled the passive IP workaround for cURL?
	 *
	 * @var   bool
	 */
	private $skipPassiveIP = false;

	/**
	 * Public constructor
	 *
	 * @param   array  $options  Configuration options
	 *
	 * @return  self
	 *
	 * @throws  RuntimeException
	 */
	public function __construct(array $options)
	{
		if (isset($options['host']))
		{
			$this->host = $options['host'];
		}

		if (isset($options['port']))
		{
			$this->port = (int) $options['port'];
		}

		if (isset($options['username']))
		{
			$this->username = $options['username'];
		}

		if (isset($options['password']))
		{
			$this->password = $options['password'];
		}

		if (isset($options['directory']))
		{
			$this->directory = '/' . ltrim(trim($options['directory']), '/');
		}

		if (isset($options['privateKey']))
		{
			$this->privateKey = $options['privateKey'];
		}

		if (isset($options['publicKey']))
		{
			$this->publicKey = $options['publicKey'];
		}

		if (isset($options['timeout']))
		{
			$this->timeout = max(1, (int) $options['timeout']);
		}

		if (isset($options['passive_fix']))
		{
			$this->skipPassiveIP = $options['passive_fix'] ? true : false;
		}

		if (isset($options['verbose']))
		{
			$this->verbose = $options['verbose'] ? true : false;
		}
	}

	/**
	 * Save all parameters on serialization except the connection resource
	 *
	 * @return  array
	 */
	public function __sleep()
	{
		return [
			'host',
			'port',
			'username',
			'password',
			'directory',
			'privateKey',
			'publicKey',
			'timeout',
			'skipPassiveIP',
			'verbose',
		];
	}

	/**
	 * Test the connection to the SFTP server and whether the initial directory is correct. This is done by attempting to
	 * list the contents of the initial directory. The listing is not parsed (we don't really care!) and we do NOT check
	 * if we can upload files to that remote folder.
	 *
	 * @throws  RuntimeException
	 */
	public function connect()
	{
		$ch = $this->getCurlHandle($this->directory . '/');
		curl_setopt($ch, CURLOPT_HEADER, 1);
		curl_setopt($ch, CURLOPT_NOBODY, 1);
		curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);

		$listing = curl_exec($ch);
		$errNo   = curl_errno($ch);
		$error   = curl_error($ch);
		curl_close($ch);

		if ($errNo)
		{
			throw new RuntimeException("cURL Error $errNo connecting to remote SFTP server: $error", 500);
		}
	}

	/**
	 * Write the contents into the file
	 *
	 * @param   string  $fileName  The full path to the file
	 * @param   string  $contents  The contents to write to the file
	 *
	 * @return  boolean  True on success
	 */
	public function write($fileName, $contents)
	{
		// Make sure the buffer:// wrapper is loaded
		class_exists('\\Akeeba\\Engine\\Util\\Buffer', true);

		$handle = fopen('buffer://akeeba_engine_transfer_ftp_curl', 'r+');
		fwrite($handle, $contents);

		// Note: don't manually close the file pointer, it's closed automatically by uploadFromHandle
		try
		{
			$this->uploadFromHandle($fileName, $handle);
		}
		catch (RuntimeException $e)
		{
			return false;
		}

		return true;
	}

	/**
	 * Uploads a local file to the remote storage
	 *
	 * @param   string  $localFilename   The full path to the local file
	 * @param   string  $remoteFilename  The full path to the remote file
	 * @param   bool    $useExceptions   Throw an exception instead of returning "false" on connection error.
	 *
	 * @return  boolean  True on success
	 */
	public function upload($localFilename, $remoteFilename, $useExceptions = true)
	{
		$fp = @fopen($localFilename, 'r');

		if ($fp === false)
		{
			throw new RuntimeException("Unreadable local file $localFilename");
		}

		// Note: don't manually close the file pointer, it's closed automatically by uploadFromHandle
		try
		{
			$this->uploadFromHandle($remoteFilename, $fp);
		}
		catch (RuntimeException $e)
		{
			if ($useExceptions)
			{
				throw $e;
			}

			return false;
		}

		return true;
	}

	/**
	 * Read the contents of a remote file into a string
	 *
	 * @param   string  $fileName  The full path to the remote file
	 *
	 * @return  string  The contents of the remote file
	 */
	public function read($fileName)
	{
		try
		{
			return $this->downloadToString($fileName);
		}
		catch (RuntimeException $e)
		{
			throw new RuntimeException("Can not download remote file $fileName", 500, $e);
		}
	}

	/**
	 * Download a remote file into a local file
	 *
	 * @param   string  $remoteFilename  The remote file path to download from
	 * @param   string  $localFilename   The local file path to download to
	 * @param   bool    $useExceptions   Throw an exception instead of returning "false" on connection error.
	 *
	 * @return  boolean  True on success
	 */
	public function download($remoteFilename, $localFilename, $useExceptions = true)
	{
		$fp = @fopen($localFilename, 'w');

		if ($fp === false)
		{
			if ($useExceptions)
			{
				throw new RuntimeException(sprintf('Download from FTP failed. Can not open local file %s for writing.', $localFilename));
			}

			return false;
		}

		// Note: don't manually close the file pointer, it's closed automatically by downloadToHandle
		try
		{
			$this->downloadToHandle($remoteFilename, $fp);
		}
		catch (RuntimeException $e)
		{
			if ($useExceptions)
			{
				throw $e;
			}

			return false;
		}

		return true;
	}

	/**
	 * Delete a file (remove it from the disk)
	 *
	 * @param   string  $fileName  The full path to the file
	 *
	 * @return  boolean  True on success
	 */
	public function delete($fileName)
	{
		$commands = [
			'rm ' . $this->getPath($fileName),
		];

		try
		{
			$this->executeServerCommands($commands);
		}
		catch (RuntimeException $e)
		{
			return false;
		}

		return true;
	}

	/**
	 * Create a copy of the file. Actually, we have to read it in memory and upload it again.
	 *
	 * @param   string  $from  The full path of the file to copy from
	 * @param   string  $to    The full path of the file that will hold the copy
	 *
	 * @return  boolean  True on success
	 */
	public function copy($from, $to)
	{
		// Make sure the buffer:// wrapper is loaded
		class_exists('\\Akeeba\\Engine\\Util\\Buffer', true);

		$handle = fopen('buffer://akeeba_engine_transfer_ftp', 'r+');

		try
		{
			$this->downloadToHandle($from, $handle, false);
			$this->uploadFromHandle($to, $handle);
		}
		catch (RuntimeException $e)
		{
			return false;
		}

		return true;
	}

	/**
	 * Move or rename a file
	 *
	 * @param   string  $from  The full path of the file to move
	 * @param   string  $to    The full path of the target file
	 *
	 * @return  boolean  True on success
	 */
	public function move($from, $to)
	{
		$from = $this->getPath($from);
		$to   = $this->getPath($to);

		$commands = [
			'rename ' . $from . ' ' . $to,
		];

		try
		{
			$this->executeServerCommands($commands);
		}
		catch (RuntimeException $e)
		{
			return false;
		}

		return true;
	}

	/**
	 * Change the permissions of a file
	 *
	 * @param   string   $fileName     The full path of the file whose permissions will change
	 * @param   integer  $permissions  The new permissions, e.g. 0644 (remember the leading zero in octal numbers!)
	 *
	 * @return  boolean  True on success
	 */
	public function chmod($fileName, $permissions)
	{
		// Make sure permissions are in an octal string representation
		if (!is_string($permissions))
		{
			$permissions = decoct($permissions);
		}

		$commands = [
			'chmod ' . $permissions . ' ' . $this->getPath($fileName),
		];

		try
		{
			$this->executeServerCommands($commands);
		}
		catch (RuntimeException $e)
		{
			return false;
		}

		return true;
	}

	/**
	 * Create a directory if it doesn't exist. The operation is implicitly recursive, i.e. it will create all
	 * intermediate directories if they do not already exist.
	 *
	 * @param   string   $dirName      The full path of the directory to create
	 * @param   integer  $permissions  The permissions of the created directory
	 *
	 * @return  boolean  True on success
	 */
	public function mkdir($dirName, $permissions = 0755)
	{
		$targetDir = rtrim($dirName, '/');

		$directories = explode('/', $targetDir);

		$remoteDir = '';

		foreach ($directories as $dir)
		{
			if (!$dir)
			{
				continue;
			}

			$remoteDir .= '/' . $dir;

			// Continue if the folder already exists. Otherwise I'll get a an error even if everything is fine
			if ($this->isDir($remoteDir))
			{
				continue;
			}

			$commands = [
				'mkdir ' . $remoteDir,
			];

			try
			{
				$this->executeServerCommands($commands);
			}
			catch (RuntimeException $e)
			{
				return false;
			}
		}

		$this->chmod($dirName, $permissions);

		return true;
	}

	/**
	 * Checks if the given directory exists
	 *
	 * @param   string  $path  The full path of the remote directory to check
	 *
	 * @return  boolean  True if the directory exists
	 */
	public function isDir($path)
	{
		$ch = $this->getCurlHandle($path . '/');
		curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);

		$list = curl_exec($ch);

		$errNo = curl_errno($ch);
		curl_close($ch);

		if ($errNo)
		{
			return false;
		}

		return true;
	}

	/**
	 * Get the current working directory. NOT IMPLEMENTED.
	 *
	 * @return  string
	 */
	public function cwd()
	{
		return '';
	}

	/**
	 * Returns the absolute remote path from a path relative to the initial directory configured when creating the
	 * transfer object.
	 *
	 * @param   string  $fileName  The relative path of a file or directory
	 *
	 * @return  string  The absolute path for use by the transfer object
	 */
	public function getPath($fileName)
	{
		$fileName = str_replace('\\', '/', $fileName);

		if (strpos($fileName, $this->directory) === 0)
		{
			return $fileName;
		}

		$fileName = trim($fileName, '/');
		$fileName = rtrim($this->directory, '/') . '/' . $fileName;

		return $fileName;
	}

	/**
	 * Lists the subdirectories inside an SFTP directory
	 *
	 * @param   null|string  $dir  The directory to scan. Skip to use the current directory.
	 *
	 * @return  array|bool  A list of folders, or false if we could not get a listing
	 *
	 * @throws  RuntimeException  When the server is incompatible with our SFTP folder scanner
	 */
	public function listFolders($dir = null)
	{
		if (empty($dir))
		{
			$dir = $this->directory;
		}

		$dir = rtrim($dir, '/');

		$ch = $this->getCurlHandle($dir . '/');
		curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);

		$list = curl_exec($ch);

		$errNo = curl_errno($ch);
		$error = curl_error($ch);
		curl_close($ch);

		if ($errNo)
		{
			throw new RuntimeException(sprintf("cURL Error $errNo ($error) while listing contents of directory \"%s\" – make sure the folder exists and that you have adequate permissions to it", $dir), 500);
		}

		if (empty($list))
		{
			throw new RuntimeException("Sorry, your SFTP server doesn't support our SFTP directory browser.");
		}

		$folders = [];

		// Convert the directory listing into an array of lines without *NIX/Windows/Mac line ending characters
		$list = explode("\n", $list);
		$list = array_map('rtrim', $list);

		foreach ($list as $v)
		{
			$vInfo = preg_split("/[\s]+/", $v, 9);

			if ($vInfo[0] !== "total")
			{
				$perms = $vInfo[0];

				if (substr($perms, 0, 1) == 'd')
				{
					$folders[] = $vInfo[8];
				}
			}
		}

		asort($folders);

		return $folders;
	}

	/**
	 * Is the verbose debug option set?
	 *
	 * @return  boolean
	 */
	public function isVerbose()
	{
		return $this->verbose;
	}

	/**
	 * Set the verbose debug option
	 *
	 * @param   boolean  $verbose
	 *
	 * @return  void
	 */
	public function setVerbose($verbose)
	{
		$this->verbose = $verbose;
	}

	/**
	 * Returns a cURL resource handler for the remote SFTP server
	 *
	 * @param   string  $remoteFile  Optional. The remote file / folder on the SFTP server you'll be manipulating with cURL.
	 *
	 * @return  resource
	 */
	protected function getCurlHandle($remoteFile = '')
	{
		// Remember, the username has to be URL encoded as it's part of a URI!
		$authentication = urlencode($this->username);

		// We will only use username and password authentication if there are no certificates configured.
		if (empty($this->publicKey))
		{
			// Remember, both the username and password have to be URL encoded as they're part of a URI!
			$password       = urlencode($this->password);
			$authentication .= ':' . $password;
		}

		$ftpUri = 'sftp://' . $authentication . '@' . $this->host;

		if (!empty($this->port))
		{
			$ftpUri .= ':' . (int) $this->port;
		}

		// Relative path? Append the initial directory.
		if (substr($remoteFile, 0, 1) != '/')
		{
			$ftpUri .= $this->directory;
		}

		// Add a remote file if necessary. The filename must be URL encoded since we're creating a URI.
		if (!empty($remoteFile))
		{
			$suffix = '';

			$dirname = dirname($remoteFile);

			// Windows messing up dirname('/'). KILL ME.
			if ($dirname == '\\')
			{
				$dirname = '';
			}

			$dirname  = trim($dirname, '/');
			$basename = basename($remoteFile);

			if ((substr($remoteFile, -1) == '/') && !empty($basename))
			{
				$suffix = '/' . $suffix;
			}

			$ftpUri .= '/' . $dirname . (empty($dirname) ? '' : '/') . urlencode($basename) . $suffix;
		}

		$ch = curl_init();

		$this->applyProxySettingsToCurl($ch);

		curl_setopt($ch, CURLOPT_URL, $ftpUri);
		curl_setopt($ch, CURLOPT_TIMEOUT, $this->timeout);

		// Do I have to use certificate authentication?
		if (!empty($this->publicKey))
		{
			// We always need to provide a public key file
			curl_setopt($ch, CURLOPT_SSH_PUBLIC_KEYFILE, $this->publicKey);

			// Since SSH certificates are self-signed we cannot have cURL verify their signatures against a CA.
			curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
			curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
			curl_setopt($ch, CURLOPT_SSL_VERIFYSTATUS, 0);

			/**
			 * This is optional because newer versions of cURL can extract the private key file from a combined
			 * certificate file.
			 */
			if (!empty($this->privateKey))
			{
				curl_setopt($ch, CURLOPT_SSH_PRIVATE_KEYFILE, $this->privateKey);
			}

			/**
			 * In case of encrypted (a.k.a. password protected) private key files you need to also specify the
			 * certificate decryption key in the password field. However, if libcurl is compiled against the GnuTLS
			 * library (instead of OpenSSL) this will NOT work because of bugs / missing features in GnuTLS. It's the
			 * same problem you get when libssh is compiled against GnuTLS. The solution to that is having an
			 * unencrypted private key file.
			 */
			if (!empty($this->password))
			{
				curl_setopt($ch, CURLOPT_KEYPASSWD, $this->password);
			}
		}

		// Should I enable verbose output? Useful for debugging.
		if ($this->verbose)
		{
			curl_setopt($ch, CURLOPT_VERBOSE, 1);
		}

		// Automatically create missing directories
		curl_setopt($ch, CURLOPT_FTP_CREATE_MISSING_DIRS, 1);

		return $ch;
	}

	/**
	 * Uploads a file using file contents provided through a file handle
	 *
	 * @param   string    $remoteFilename
	 * @param   resource  $fp
	 *
	 * @return  void
	 *
	 * @throws  RuntimeException
	 */
	protected function uploadFromHandle($remoteFilename, $fp)
	{
		// We need the file size. We can do that by getting the file position at EOF
		fseek($fp, 0, SEEK_END);
		$filesize = ftell($fp);
		rewind($fp);

		$ch = $this->getCurlHandle($remoteFilename);
		curl_setopt($ch, CURLOPT_UPLOAD, 1);
		curl_setopt($ch, CURLOPT_INFILE, $fp);
		curl_setopt($ch, CURLOPT_INFILESIZE, $filesize);

		curl_exec($ch);

		$error_no = curl_errno($ch);
		$error    = curl_error($ch);

		curl_close($ch);
		fclose($fp);

		if ($error_no)
		{
			throw new RuntimeException($error, $error_no);
		}
	}

	/**
	 * Downloads a remote file to the provided file handle
	 *
	 * @param   string    $remoteFilename  Filename on the remote server
	 * @param   resource  $fp              File handle where the downloaded content will be written to
	 * @param   bool      $close           Optional. Should I close the file handle when I'm done? (Default: true)
	 *
	 * @return  void
	 *
	 * @throws  RuntimeException
	 */
	protected function downloadToHandle($remoteFilename, $fp, $close = true)
	{
		$ch = $this->getCurlHandle($remoteFilename);

		curl_setopt($ch, CURLOPT_FILE, $fp);

		curl_exec($ch);

		$error_no = curl_errno($ch);
		$error    = curl_error($ch);

		curl_close($ch);

		if ($close)
		{
			fclose($fp);
		}

		if ($error_no)
		{
			throw new RuntimeException($error, $error_no);
		}
	}

	/**
	 * Downloads a remote file and returns it as a string
	 *
	 * @param   string  $remoteFilename  Filename on the remote server
	 *
	 * @return  string
	 *
	 * @throws  RuntimeException
	 */
	protected function downloadToString($remoteFilename)
	{
		$ch = $this->getCurlHandle($remoteFilename);

		curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
		curl_setopt($ch, CURLOPT_HEADER, false);

		$ret = curl_exec($ch);

		$error_no = curl_errno($ch);
		$error    = curl_error($ch);

		curl_close($ch);

		if ($error_no)
		{
			throw new RuntimeException($error, $error_no);
		}

		return $ret;
	}

	/**
	 * Executes arbitrary SFTP commands
	 *
	 * @param   array  $commands  An array with the SFTP commands to be executed
	 *
	 * @return  string  The output of the executed commands
	 *
	 * @throws  RuntimeException
	 */
	protected function executeServerCommands($commands)
	{
		$ch = $this->getCurlHandle($this->directory . '/');

		curl_setopt($ch, CURLOPT_QUOTE, $commands);
		curl_setopt($ch, CURLOPT_HEADER, 1);
		curl_setopt($ch, CURLOPT_NOBODY, 1);
		curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);

		$listing = curl_exec($ch);
		$errNo   = curl_errno($ch);
		$error   = curl_error($ch);
		curl_close($ch);

		if ($errNo)
		{
			throw new RuntimeException($error, $errNo);
		}

		return $listing;
	}
}
PK�!]����BackupEngine/Util/Utf8.phpnu&1i�<?php
/**
 * Akeeba Engine
 *
 * @package   akeebaengine
 * @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License version 3, or later
 *
 * 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, version 3.
 *
 * 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
 * <https://www.gnu.org/licenses/>.
 */

namespace Akeeba\Engine\Util;

defined('AKEEBAENGINE') || die();

/**
 * Replacement for the utf8_encode and utf8_decode functions on PHP 8.2 and later.
 *
 * @see https://wiki.php.net/rfc/remove_utf8_decode_and_utf8_encode
 */
class Utf8
{
	public static function utf8_encode($s)
	{
		if (version_compare(PHP_VERSION, '8.1.999', 'le'))
		{
			return utf8_encode($s);
		}

		if (function_exists('mb_convert_encoding'))
		{
			return mb_convert_encoding($s, 'UTF-8', 'ISO-8859-1');
		}

		if (class_exists('UConverter'))
		{
			return UConverter::transcode($s, 'UTF8', 'ISO-8859-1');
		}

		if (function_exists('iconv'))
		{
			return iconv('ISO-8859-1', 'UTF-8', $s);
		}

		/**
		 * Fallback to the pure PHP implementation from Symfony Polyfill for PHP 7.2
		 *
		 * @see https://github.com/symfony/polyfill-php72/blob/v1.26.0/Php72.php
		 */
		$s .= $s;
		$len = \strlen($s);

		for ($i = $len >> 1, $j = 0; $i < $len; ++$i, ++$j) {
			switch (true) {
				case $s[$i] < "\x80": $s[$j] = $s[$i]; break;
				case $s[$i] < "\xC0": $s[$j] = "\xC2"; $s[++$j] = $s[$i]; break;
				default: $s[$j] = "\xC3"; $s[++$j] = \chr(\ord($s[$i]) - 64); break;
			}
		}

		return substr($s, 0, $j);
	}

	public static function utf8_decode($s)
	{
		if (version_compare(PHP_VERSION, '8.1.999', 'le'))
		{
			return utf8_decode($s);
		}

		if (function_exists('mb_convert_encoding'))
		{
			return mb_convert_encoding($s, 'ISO-8859-1', 'UTF-8');
		}

		if (class_exists('UConverter'))
		{
			return UConverter::transcode($s, 'ISO-8859-1', 'UTF8');
		}

		if (function_exists('iconv'))
		{
			return iconv('UTF-8', 'ISO-8859-1', $s);
		}

		/**
		 * Fallback to the pure PHP implementation from Symfony Polyfill for PHP 7.2
		 *
		 * @see https://github.com/symfony/polyfill-php72/blob/v1.26.0/Php72.php
		 */
		$s = (string) $s;
		$len = \strlen($s);

		for ($i = 0, $j = 0; $i < $len; ++$i, ++$j) {
			switch ($s[$i] & "\xF0") {
				case "\xC0":
				case "\xD0":
					$c = (\ord($s[$i] & "\x1F") << 6) | \ord($s[++$i] & "\x3F");
					$s[$j] = $c < 256 ? \chr($c) : '?';
					break;

				case "\xF0":
					++$i;
				// no break

				case "\xE0":
					$s[$j] = '?';
					$i += 2;
					break;

				default:
					$s[$j] = $s[$i];
			}
		}

		return substr($s, 0, $j);
	}
}PK�!]i/C�=�= BackupEngine/Util/Complexify.phpnu&1i�<?php
/**
 * Akeeba Engine
 *
 * @package   akeebaengine
 * @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License version 3, or later
 *
 * 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, version 3.
 *
 * 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
 * <https://www.gnu.org/licenses/>.
 */

namespace Akeeba\Engine\Util;

defined('AKEEBAENGINE') || die();

use Akeeba\Engine\Platform;
use RuntimeException;

/**
 * PHP port of http://github.com/danpalmer/jquery.complexify.js
 * Retrieved from https://github.com/mcrumley/php-complexify/blob/master/src/Complexify/Complexify.php
 * Error reporting is based on https://github.com/kislyuk/node-complexify
 */
class Complexify
{
	private static $MIN_COMPLEXITY = 66;

	private static $MAX_COMPLEXITY = 120; //  25 chars, all charsets

	private static $CHARSETS = [
		// Commonly Used
		////////////////////
		[0x0020, 0x0020], // Space
		[0x0030, 0x0039], // Numbers
		[0x0041, 0x005A], // Uppercase
		[0x0061, 0x007A], // Lowercase
		[0x0021, 0x002F], // Punctuation
		[0x003A, 0x0040], // Punctuation
		[0x005B, 0x0060], // Punctuation
		[0x007B, 0x007E], // Punctuation
		// Everything Else
		////////////////////
		[0x0080, 0x00FF], // Latin-1 Supplement
		[0x0100, 0x017F], // Latin Extended-A
		[0x0180, 0x024F], // Latin Extended-B
		[0x0250, 0x02AF], // IPA Extensions
		[0x02B0, 0x02FF], // Spacing Modifier Letters
		[0x0300, 0x036F], // Combining Diacritical Marks
		[0x0370, 0x03FF], // Greek
		[0x0400, 0x04FF], // Cyrillic
		[0x0530, 0x058F], // Armenian
		[0x0590, 0x05FF], // Hebrew
		[0x0600, 0x06FF], // Arabic
		[0x0700, 0x074F], // Syriac
		[0x0780, 0x07BF], // Thaana
		[0x0900, 0x097F], // Devanagari
		[0x0980, 0x09FF], // Bengali
		[0x0A00, 0x0A7F], // Gurmukhi
		[0x0A80, 0x0AFF], // Gujarati
		[0x0B00, 0x0B7F], // Oriya
		[0x0B80, 0x0BFF], // Tamil
		[0x0C00, 0x0C7F], // Telugu
		[0x0C80, 0x0CFF], // Kannada
		[0x0D00, 0x0D7F], // Malayalam
		[0x0D80, 0x0DFF], // Sinhala
		[0x0E00, 0x0E7F], // Thai
		[0x0E80, 0x0EFF], // Lao
		[0x0F00, 0x0FFF], // Tibetan
		[0x1000, 0x109F], // Myanmar
		[0x10A0, 0x10FF], // Georgian
		[0x1100, 0x11FF], // Hangul Jamo
		[0x1200, 0x137F], // Ethiopic
		[0x13A0, 0x13FF], // Cherokee
		[0x1400, 0x167F], // Unified Canadian Aboriginal Syllabics
		[0x1680, 0x169F], // Ogham
		[0x16A0, 0x16FF], // Runic
		[0x1780, 0x17FF], // Khmer
		[0x1800, 0x18AF], // Mongolian
		[0x1E00, 0x1EFF], // Latin Extended Additional
		[0x1F00, 0x1FFF], // Greek Extended
		[0x2000, 0x206F], // General Punctuation
		[0x2070, 0x209F], // Superscripts and Subscripts
		[0x20A0, 0x20CF], // Currency Symbols
		[0x20D0, 0x20FF], // Combining Marks for Symbols
		[0x2100, 0x214F], // Letterlike Symbols
		[0x2150, 0x218F], // Number Forms
		[0x2190, 0x21FF], // Arrows
		[0x2200, 0x22FF], // Mathematical Operators
		[0x2300, 0x23FF], // Miscellaneous Technical
		[0x2400, 0x243F], // Control Pictures
		[0x2440, 0x245F], // Optical Character Recognition
		[0x2460, 0x24FF], // Enclosed Alphanumerics
		[0x2500, 0x257F], // Box Drawing
		[0x2580, 0x259F], // Block Elements
		[0x25A0, 0x25FF], // Geometric Shapes
		[0x2600, 0x26FF], // Miscellaneous Symbols
		[0x2700, 0x27BF], // Dingbats
		[0x2800, 0x28FF], // Braille Patterns
		[0x2E80, 0x2EFF], // CJK Radicals Supplement
		[0x2F00, 0x2FDF], // Kangxi Radicals
		[0x2FF0, 0x2FFF], // Ideographic Description Characters
		[0x3000, 0x303F], // CJK Symbols and Punctuation
		[0x3040, 0x309F], // Hiragana
		[0x30A0, 0x30FF], // Katakana
		[0x3100, 0x312F], // Bopomofo
		[0x3130, 0x318F], // Hangul Compatibility Jamo
		[0x3190, 0x319F], // Kanbun
		[0x31A0, 0x31BF], // Bopomofo Extended
		[0x3200, 0x32FF], // Enclosed CJK Letters and Months
		[0x3300, 0x33FF], // CJK Compatibility
		[0x3400, 0x4DB5], // CJK Unified Ideographs Extension A
		[0x4E00, 0x9FFF], // CJK Unified Ideographs
		[0xA000, 0xA48F], // Yi Syllables
		[0xA490, 0xA4CF], // Yi Radicals
		[0xAC00, 0xD7A3], // Hangul Syllables
		[0xD800, 0xDB7F], // High Surrogates
		[0xDB80, 0xDBFF], // High Private Use Surrogates
		[0xDC00, 0xDFFF], // Low Surrogates
		[0xE000, 0xF8FF], // Private Use
		[0xF900, 0xFAFF], // CJK Compatibility Ideographs
		[0xFB00, 0xFB4F], // Alphabetic Presentation Forms
		[0xFB50, 0xFDFF], // Arabic Presentation Forms-A
		[0xFE20, 0xFE2F], // Combining Half Marks
		[0xFE30, 0xFE4F], // CJK Compatibility Forms
		[0xFE50, 0xFE6F], // Small Form Variants
		[0xFE70, 0xFEFE], // Arabic Presentation Forms-B
		[0xFEFF, 0xFEFF], // Specials
		[0xFF00, 0xFFEF], // Halfwidth and Fullwidth Forms
		[0xFFF0, 0xFFFD]  // Specials
	];

	// Generated from 500 worst passwords and 370 Banned Twitter lists found at
	// @source http://www.skullsecurity.org/wiki/index.php/Passwords
	private static $BANLIST = [
		'0', '1111', '1212', '1234', '1313', '2000', '2112', '2222',
		'3333', '4128', '4321', '4444', '5150', '5555', '6666', '6969', '7777', 'aaaa',
		'alex', 'asdf', 'baby', 'bear', 'beer', 'bill', 'blue', 'cock', 'cool', 'cunt',
		'dave', 'dick', 'eric', 'fire', 'fish', 'ford', 'fred', 'fuck', 'girl', 'golf',
		'jack', 'jake', 'john', 'king', 'love', 'mark', 'matt', 'mike', 'mine', 'pass',
		'paul', 'porn', 'rock', 'sexy', 'shit', 'slut', 'star', 'test', 'time', 'tits',
		'wolf', 'xxxx', '11111', '12345', 'angel', 'apple', 'beach', 'billy', 'bitch',
		'black', 'boobs', 'booty', 'brian', 'bubba', 'buddy', 'chevy', 'chris', 'cream',
		'david', 'dirty', 'eagle', 'enjoy', 'enter', 'frank', 'girls', 'great', 'green',
		'happy', 'hello', 'horny', 'house', 'james', 'japan', 'jason', 'juice', 'kelly',
		'kevin', 'kitty', 'lover', 'lucky', 'magic', 'money', 'movie', 'music', 'naked',
		'ou812', 'paris', 'penis', 'peter', 'porno', 'power', 'pussy', 'qwert', 'sammy',
		'scott', 'smith', 'stars', 'steve', 'super', 'teens', 'tiger', 'video', 'viper',
		'white', 'women', 'xxxxx', 'young', '111111', '112233', '121212', '123123',
		'123456', '131313', '232323', '654321', '666666', '696969', '777777', '987654',
		'aaaaaa', 'abc123', 'abcdef', 'access', 'action', 'albert', 'alexis', 'amanda',
		'andrea', 'andrew', 'angela', 'angels', 'animal', 'apollo', 'apples', 'arthur',
		'asdfgh', 'ashley', 'august', 'austin', 'badboy', 'bailey', 'banana', 'barney',
		'batman', 'beaver', 'beavis', 'bigdog', 'birdie', 'biteme', 'blazer', 'blonde',
		'blowme', 'bonnie', 'booboo', 'booger', 'boomer', 'boston', 'brandy', 'braves',
		'brazil', 'bronco', 'buster', 'butter', 'calvin', 'camaro', 'canada', 'carlos',
		'carter', 'casper', 'cheese', 'coffee', 'compaq', 'cookie', 'cooper', 'cowboy',
		'dakota', 'dallas', 'daniel', 'debbie', 'dennis', 'diablo', 'doctor', 'doggie',
		'donald', 'dragon', 'dreams', 'driver', 'eagle1', 'eagles', 'edward', 'erotic',
		'falcon', 'fender', 'flower', 'flyers', 'freddy', 'fucked', 'fucker', 'fuckme',
		'gators', 'gemini', 'george', 'giants', 'ginger', 'golden', 'golfer', 'gordon',
		'guitar', 'gunner', 'hammer', 'hannah', 'harley', 'helpme', 'hentai', 'hockey',
		'horney', 'hotdog', 'hunter', 'iceman', 'iwantu', 'jackie', 'jaguar', 'jasper',
		'jeremy', 'johnny', 'jordan', 'joseph', 'joshua', 'junior', 'justin', 'killer',
		'knight', 'ladies', 'lakers', 'lauren', 'legend', 'little', 'london', 'lovers',
		'maddog', 'maggie', 'magnum', 'marine', 'martin', 'marvin', 'master', 'matrix',
		'member', 'merlin', 'mickey', 'miller', 'monica', 'monkey', 'morgan', 'mother',
		'muffin', 'murphy', 'nascar', 'nathan', 'nicole', 'nipple', 'oliver', 'orange',
		'parker', 'peanut', 'pepper', 'player', 'please', 'pookie', 'prince', 'purple',
		'qazwsx', 'qwerty', 'rabbit', 'rachel', 'racing', 'ranger', 'redsox', 'robert',
		'rocket', 'runner', 'russia', 'samson', 'sandra', 'saturn', 'scooby', 'secret',
		'sexsex', 'shadow', 'shaved', 'sierra', 'silver', 'skippy', 'slayer', 'smokey',
		'snoopy', 'soccer', 'sophie', 'spanky', 'sparky', 'spider', 'squirt', 'steven',
		'sticky', 'stupid', 'suckit', 'summer', 'surfer', 'sydney', 'taylor', 'tennis',
		'teresa', 'tester', 'theman', 'thomas', 'tigers', 'tigger', 'tomcat', 'topgun',
		'toyota', 'travis', 'tucker', 'turtle', 'united', 'vagina', 'victor', 'viking',
		'voodoo', 'walter', 'willie', 'wilson', 'winner', 'winter', 'wizard', 'xavier',
		'xxxxxx', 'yamaha', 'yankee', 'yellow', 'zxcvbn', 'zzzzzz', '1234567', '7777777',
		'8675309', 'abgrtyu', 'amateur', 'anthony', 'arsenal', 'asshole', 'bigcock',
		'bigdick', 'bigtits', 'bitches', 'blondes', 'blowjob', 'bond007', 'brandon',
		'broncos', 'bulldog', 'cameron', 'captain', 'charles', 'charlie', 'chelsea',
		'chester', 'chicago', 'chicken', 'college', 'cowboys', 'crystal', 'cumming',
		'cumshot', 'diamond', 'dolphin', 'extreme', 'ferrari', 'fishing', 'florida',
		'forever', 'freedom', 'fucking', 'fuckyou', 'gandalf', 'gateway', 'gregory',
		'heather', 'hooters', 'hunting', 'jackson', 'jasmine', 'jessica', 'johnson',
		'leather', 'letmein', 'madison', 'matthew', 'maxwell', 'melissa', 'michael',
		'monster', 'mustang', 'naughty', 'ncc1701', 'newyork', 'nipples', 'packers',
		'panther', 'panties', 'patrick', 'peaches', 'phantom', 'phoenix', 'porsche',
		'private', 'pussies', 'raiders', 'rainbow', 'rangers', 'rebecca', 'richard',
		'rosebud', 'scooter', 'scorpio', 'shannon', 'success', 'testing', 'thunder',
		'thx1138', 'tiffany', 'trouble', 'twitter', 'voyager', 'warrior', 'welcome',
		'william', 'winston', 'yankees', 'zxcvbnm', '11111111', '12345678', 'access14',
		'baseball', 'bigdaddy', 'butthead', 'cocacola', 'computer', 'corvette',
		'danielle', 'dolphins', 'einstein', 'firebird', 'football', 'hardcore',
		'iloveyou', 'internet', 'jennifer', 'marlboro', 'maverick', 'mercedes',
		'michelle', 'midnight', 'mistress', 'mountain', 'nicholas', 'password',
		'princess', 'qwertyui', 'redskins', 'redwings', 'rush2112', 'samantha',
		'scorpion', 'srinivas', 'startrek', 'starwars', 'steelers', 'sunshine',
		'superman', 'swimming', 'trustno1', 'victoria', 'whatever', 'xxxxxxxx',
		'password1', 'password12', 'password123',
	];

	private $minimumChars = 8;

	private $strengthScaleFactor = 1;

	private $bannedPasswords = [];

	private $banMode = 'strict'; // (strict|loose)

	private $encoding = 'UTF-8';

	/**
	 * Constructor
	 *
	 * @param   array  $options  Override default options using an associative array of options
	 *
	 * Options:
	 *  - minimumChars: Minimum password length (default: 8)
	 *  - strengthScaleFactor: Required password strength multiplier (default: 1)
	 *  - bannedPasswords: Custom list of banned passwords (default: long list of common passwords)
	 *  - banMode: Use strict or loose comparisons for banned passwords. "strict" = don't allow a substring of a banned
	 *  password, "loose" = only ban exact matches (default: strict)
	 *  - encoding: Character set encoding of the password (default: UTF-8)
	 */
	public function __construct(array $options = [])
	{
		$this->bannedPasswords = self::$BANLIST;

		foreach ($options as $opt => $val)
		{
			if ($opt === 'banmode')
			{
				trigger_error('The lowercase banmode option is deprecated. Use banMode instead.', E_USER_DEPRECATED);
				$opt = 'banMode';
			}

			$this->{$opt} = $val;
		}
	}

	/**
	 * Checks if a password is strong enough for use on a live site. Used to check the front-end Secret Word.
	 *
	 * @param   string  $password         The password to check
	 * @param   bool    $throwExceptions  Throw an exception if the password is not strong enough?
	 *
	 * @return  bool
	 */
	public static function isStrongEnough($password, $throwExceptions = true)
	{
		$complexify = new self();

		$res = (object) [
			'valid'      => strlen($password) >= 32,
			'complexity' => 50,
			'errors'     => (strlen($password) >= 32) ? [] : ['tooshort'],
		];

		if (function_exists('mb_strlen') && function_exists('mb_convert_encoding') &&
			function_exists('mb_substr') && function_exists('mb_convert_case'))
		{
			$res = $complexify->evaluateSecurity($password);
		}


		if ($res->valid)
		{
			return true;
		}

		if (!$throwExceptions)
		{
			return false;
		}

		$error = count($res->errors) ? array_shift($res->errors) : 'toosimple';

		$errorMessage = Platform::getInstance()->translate('COM_AKEEBA_CPANEL_ERR_FESECRETWORD_' . $error);

		throw new RuntimeException($errorMessage, 403);
	}

	/**
	 * Check the complexity of a password
	 *
	 * @param   string  $password  The password to check
	 *
	 * @return  object  StdClass object with properties "valid", "complexity", and "error"
	 *  - valid: TRUE if the password is complex enough, FALSE if it is not
	 *  - complexity: The complexity of the password as a percent
	 *  - errors: Array containing descriptions of what made the password fail. Possible values are: banned, toosimple,
	 *  tooshort
	 */
	public function evaluateSecurity($password)
	{
		$complexity = 0;
		$error      = [];

		// Reset complexity to 0 when banned password is found
		if (!$this->inBanlist($password))
		{
			// Add character complexity
			foreach (self::$CHARSETS as $charset)
			{
				$complexity += $this->additionalComplexityForCharset($password, $charset);
			}
		}
		else
		{
			array_push($error, 'banned');
			$complexity = 1;
		}

		// Use natural log to produce linear scale
		$complexity = log($complexity ** mb_strlen($password, $this->encoding)) * (1 / $this->strengthScaleFactor);

		if ($complexity <= self::$MIN_COMPLEXITY)
		{
			array_push($error, 'toosimple');
		}

		if (mb_strlen($password, $this->encoding) < $this->minimumChars)
		{
			array_push($error, 'tooshort');
		}

		// Scale to percentage, so it can be used for a progress bar
		$complexity = ($complexity / self::$MAX_COMPLEXITY) * 100;
		$complexity = ($complexity > 100) ? 100 : $complexity;

		return (object) ['valid' => (is_array($error) || $error instanceof \Countable ? count($error) : 0) === 0, 'complexity' => $complexity, 'errors' => $error];
	}

	/**
	 * Determine the complexity added from a character set if it is used in a string
	 *
	 * @param   string  $str       String to check
	 * @param   int  [2]    $charset  Array of unicode code points representing the lower and upper bound of the
	 *                             character range
	 *
	 * @return  int  0 if there are no characters from the character set, size of the character set if there are any
	 *               characters used in the string
	 */
	private function additionalComplexityForCharset($str, $charset)
	{
		$len = mb_strlen($str, $this->encoding);
		for ($i = 0; $i < $len; $i++)
		{
			$c =
				unpack('Nord', mb_convert_encoding(mb_substr($str, $i, 1, $this->encoding), 'UCS-4BE', $this->encoding));
			if ($charset[0] <= $c['ord'] && $c['ord'] <= $charset[1])
			{
				return $charset[1] - $charset[0] + 1;
			}
		}

		return 0;
	}

	/**
	 * Check if a string is in the banned password list
	 *
	 * @param   string  $str  String to check
	 *
	 * @return  bool  TRUE if $str is a banned password, or if it is a substring of a banned password and
	 *                $this->banMode is 'strict'
	 */
	private function inBanlist($str)
	{
		if ($str == '')
		{
			return false;
		}

		$str = mb_convert_case($str, MB_CASE_LOWER, $this->encoding);

		if ($this->banMode === 'strict')
		{
			for ($i = 0; $i < count($this->bannedPasswords); $i++)
			{
				if (mb_strpos($this->bannedPasswords[$i], $str, 0, $this->encoding) !== false)
				{
					return true;
				}
			}

			return false;
		}

		return in_array($str, $this->bannedPasswords);
	}
}
PK�!]���KK$BackupEngine/Util/SecureSettings.phpnu&1i�<?php
/**
 * Akeeba Engine
 *
 * @package   akeebaengine
 * @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License version 3, or later
 *
 * 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, version 3.
 *
 * 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
 * <https://www.gnu.org/licenses/>.
 */

namespace Akeeba\Engine\Util;

defined('AKEEBAENGINE') || die();

use Akeeba\Engine\Factory;
use Akeeba\Engine\Platform;

/**
 * Implements encrypted settings handling features
 *
 * @author nicholas
 */
class SecureSettings
{
	/**
	 * The filename for the settings encryption key
	 *
	 * @var   string
	 */
	protected $keyFilename = 'serverkey.php';

	protected $key = null;

	/**
	 * Set the key filename e.g. 'serverkey.php';
	 *
	 * @param   string  $filename  The new filename to use
	 *
	 * @return  void
	 */
	public function setKeyFilename($filename)
	{
		$this->keyFilename = $filename;
	}

	/**
	 * Sets the server key, overriding an already loaded key.
	 *
	 * @param $key
	 */
	public function setKey($key)
	{
		$this->key = $key;
	}

	/**
	 * Gets the configured server key, automatically loading the server key storage file
	 * if required.
	 *
	 * @return string
	 */
	public function getKey()
	{
		if (is_null($this->key))
		{
			$this->key = '';

			if (!defined('AKEEBA_SERVERKEY'))
			{
				$filename = dirname(__FILE__) . '/../' . $this->keyFilename;
				$altFilename = $this->keyFilename;

				if (file_exists($filename))
				{
					include_once $filename;
				}
				elseif (file_exists($altFilename))
				{
					include_once $altFilename;
				}
			}

			if (defined('AKEEBA_SERVERKEY'))
			{
				$this->key = base64_decode(AKEEBA_SERVERKEY);
			}
		}

		return $this->key;
	}

	/**
	 * Do the server options allow us to use settings encryption?
	 *
	 * @return bool
	 */
	public function supportsEncryption()
	{
		// Do we have the encrypt.php plugin?
		if (!class_exists('\\Akeeba\\Engine\\Util\\Encrypt', true))
		{
			return false;
		}

		// Did the user intentionally disable settings encryption?
		$useEncryption = Platform::getInstance()->get_platform_configuration_option('useencryption', -1);

		if ($useEncryption == 0)
		{
			return false;
		}

		// Do we have base64_encode/_decode required for encryption?
		if (!function_exists('base64_encode') || !function_exists('base64_decode'))
		{
			return false;
		}

		// Pre-requisites met. We can encrypt and decrypt!
		return true;
	}

	/**
	 * Gets the preferred encryption mode. Currently, if mcrypt is installed and activated we will
	 * use AES128.
	 *
	 * @return string
	 */
	public function preferredEncryption()
	{
		$aes     = new Encrypt();
		$adapter = $aes->getAdapter();

		if (!$adapter->isSupported())
		{
			return 'CTR128';
		}

		return 'AES128';
	}

	/**
	 * Encrypts the settings using the automatically detected preferred algorithm
	 *
	 * @param   $rawSettings  string  The raw settings string
	 * @param   $key          string  The encryption key. Set to NULL to automatically find the key.
	 *
	 * @return  string  The encrypted data to store in the database
	 */
	public function encryptSettings($rawSettings, $key = null)
	{
		// Do we really support encryption?
		if (!$this->supportsEncryption())
		{
			return $rawSettings;
		}

		// Does any of the preferred encryption engines exist?
		$encryption = $this->preferredEncryption();

		if (empty($encryption))
		{
			return $rawSettings;
		}

		// Do we have a non-empty key to begin with?
		if (empty($key))
		{
			$key = $this->getKey();
		}

		if (empty($key))
		{
			return $rawSettings;
		}

		if ($encryption == 'AES128')
		{
			$encrypted = Factory::getEncryption()->AESEncryptCBC($rawSettings, $key);

			if (empty($encrypted))
			{
				$encryption = 'CTR128';
			}
			else
			{
				// Note: CBC returns the encrypted data as a binary string and requires Base 64 encoding
				$rawSettings = '###AES128###' . base64_encode($encrypted);
			}
		}

		if ($encryption == 'CTR128')
		{
			$encrypted = Factory::getEncryption()->AESEncryptCtr($rawSettings, $key, 128);

			if (!empty($encrypted))
			{
				// Note: CTR returns the encrypted data readily encoded in Base 64
				$rawSettings = '###CTR128###' . $encrypted;
			}
		}

		return $rawSettings;
	}

	/**
	 * Decrypts the encrypted settings and returns the plaintext INI string
	 *
	 * @param   string  $encrypted  The encrypted data
	 *
	 * @return  string  The decrypted data
	 */
	public function decryptSettings($encrypted, $key = null)
	{
		if (substr($encrypted, 0, 12) == '###AES128###')
		{
			$mode = 'AES128';
		}
		elseif (substr($encrypted, 0, 12) == '###CTR128###')
		{
			$mode = 'CTR128';
		}
		else
		{
			return $encrypted;
		}

		if (empty($key))
		{
			$key = $this->getKey();
		}

		if (empty($key))
		{
			return '';
		}

		$encrypted = substr($encrypted, 12);

		switch ($mode)
		{
			default:
			case 'AES128':
				$encrypted = base64_decode($encrypted);
				$decrypted = rtrim(Factory::getEncryption()->AESDecryptCBC($encrypted, $key), "\0");
				break;

			case 'CTR128':
				$decrypted = Factory::getEncryption()->AESDecryptCtr($encrypted, $key, 128);
				break;
		}

		if (empty($decrypted))
		{
			$decrypted = '';
		}

		return $decrypted;
	}
}
PK�!]&����+BackupEngine/Util/PushMessagesInterface.phpnu&1i�<?php
/**
 * Akeeba Engine
 *
 * @package   akeebaengine
 * @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License version 3, or later
 *
 * 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, version 3.
 *
 * 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
 * <https://www.gnu.org/licenses/>.
 */

/**
 * @package     Akeeba\Engine\Util
 * @subpackage
 *
 * @copyright   A copyright
 * @license     A "Slug" license name e.g. GPL2
 */

namespace Akeeba\Engine\Util;

defined('AKEEBAENGINE') || die();

interface PushMessagesInterface
{
	/**
	 * Sends a push message to all connected devices. The intent is to provide the user with an information message,
	 * e.g. notify them about the progress of the backup.
	 *
	 * @param   string  $subject  The subject of the message, shown in the lock screen. Keep it short.
	 * @param   string  $details  Long(er) description of what the message is about. Plain text (no HTML).
	 *
	 * @return  void
	 */
	public function message($subject, $details = null);

	/**
	 * Sends a push message, containing a URL/URI, to all connected devices. The URL will be rendered as something
	 * clickable on most devices.
	 *
	 * @param   string  $url      The URL/URI
	 * @param   string  $subject  The subject of the message, shown in the lock screen. Keep it short.
	 * @param   string  $details  Long(er) description of what the message is about. Plain text (no HTML).
	 *
	 * @return  void
	 */
	public function link($url, $subject, $details = null);
}PK�!]��Y�bb$BackupEngine/Util/TemporaryFiles.phpnu&1i�<?php
/**
 * Akeeba Engine
 *
 * @package   akeebaengine
 * @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License version 3, or later
 *
 * 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, version 3.
 *
 * 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
 * <https://www.gnu.org/licenses/>.
 */

namespace Akeeba\Engine\Util;

defined('AKEEBAENGINE') || die();

use Akeeba\Engine\Factory;

/**
 * Temporary files management class. Handles creation, tracking and cleanup.
 */
class TemporaryFiles
{

	/**
	 * Creates a randomly-named temporary file, registers it with the temporary
	 * files management and returns its absolute path
	 *
	 * @return  string  The temporary file name
	 */
	public function createRegisterTempFile()
	{
		// Create a randomly named file in the temp directory
		$registry = Factory::getConfiguration();
		$tempFile = tempnam($registry->get('akeeba.basic.output_directory'), 'ak');

		// Register it and return its absolute path
		$tempName = basename($tempFile);

		return Factory::getTempFiles()->registerTempFile($tempName);
	}

	/**
	 * Registers a temporary file with the Akeeba Engine, storing the list of temporary files
	 * in another temporary flat database file.
	 *
	 * @param   string  $fileName  The path of the file, relative to the temporary directory
	 *
	 * @return  string  The absolute path to the temporary file, for use in file operations
	 */
	public function registerTempFile($fileName)
	{
		$configuration = Factory::getConfiguration();
		$tempFiles     = $configuration->get('volatile.tempfiles', false);
		if ($tempFiles === false)
		{
			$tempFiles = [];
		}
		else
		{
			$tempFiles = @unserialize($tempFiles);

			if ($tempFiles === false)
			{
				$tempFiles = [];
			}
		}

		if (!in_array($fileName, $tempFiles))
		{
			$tempFiles[] = $fileName;
			$configuration->set('volatile.tempfiles', serialize($tempFiles));
		}

		return Factory::getFilesystemTools()->TranslateWinPath($configuration->get('akeeba.basic.output_directory') . '/' . $fileName);
	}

	/**
	 * Unregister and delete a temporary file
	 *
	 * @param   string  $fileName      The filename to unregister and delete
	 * @param   bool    $removePrefix  The prefix to remove
	 *
	 * @return  bool  True on success
	 */
	public function unregisterAndDeleteTempFile($fileName, $removePrefix = false)
	{
		$configuration = Factory::getConfiguration();

		if ($removePrefix)
		{
			$fileName = str_replace(Factory::getFilesystemTools()->TranslateWinPath($configuration->get('akeeba.basic.output_directory')), '', $fileName);

			if ((substr($fileName, 0, 1) == '/') || (substr($fileName, 0, 1) == '\\'))
			{
				$fileName = substr($fileName, 1);
			}

			if ((substr($fileName, -1) == '/') || (substr($fileName, -1) == '\\'))
			{
				$fileName = substr($fileName, 0, -1);
			}
		}

		// Make sure this file is registered
		$configuration = Factory::getConfiguration();

		$serialised = $configuration->get('volatile.tempfiles', false);
		$tempFiles  = [];

		if ($serialised !== false)
		{
			$tempFiles = @unserialize($serialised);
		}

		if (!is_array($tempFiles))
		{
			return false;
		}

		if (!in_array($fileName, $tempFiles))
		{
			return false;
		}

		$file = $configuration->get('akeeba.basic.output_directory') . '/' . $fileName;
		Factory::getLog()->debug("-- Removing temporary file $fileName");
		$platform = strtoupper(PHP_OS);

		// Chown normally doesn't work on Windows but many years ago I found it necessary to delete temp files. No idea.
		if ((substr($platform, 0, 6) == 'CYGWIN') || (substr($platform, 0, 3) == 'WIN'))
		{
			// On Windows we have to chown() the file first to make it owned by Nobody
			Factory::getLog()->debug("-- Windows hack: chowning $fileName");
			@chown($file, 600);
		}

		$result = @$this->nullifyAndDelete($file);

		// Make sure the file is removed before unregistering it
		if (!@file_exists($file))
		{
			$aPos = array_search($fileName, $tempFiles);

			if ($aPos !== false)
			{
				unset($tempFiles[$aPos]);

				$configuration->set('volatile.tempfiles', serialize($tempFiles));
			}
		}

		return $result;
	}


	/**
	 * Deletes all temporary files
	 *
	 * @return  void
	 */
	public function deleteTempFiles()
	{
		$configuration = Factory::getConfiguration();

		$serialised = $configuration->get('volatile.tempfiles', false);
		$tempFiles  = [];

		if ($serialised !== false)
		{
			$tempFiles = @unserialize($serialised);
		}

		if (!is_array($tempFiles))
		{
			$tempFiles = [];
		}

		$fileName = null;

		if (!empty($tempFiles))
		{
			foreach ($tempFiles as $fileName)
			{
				Factory::getLog()->debug("-- Removing temporary file $fileName");
				$file     = $configuration->get('akeeba.basic.output_directory') . '/' . $fileName;
				$platform = strtoupper(PHP_OS);

				// Chown normally doesn't work on Windows but many years ago I found it necessary to delete temp files. No idea.
				if ((substr($platform, 0, 6) == 'CYGWIN') || (substr($platform, 0, 3) == 'WIN'))
				{
					// On Windows we have to chwon() the file first to make it owned by Nobody
					@chown($file, 600);
				}

				$ret = @$this->nullifyAndDelete($file);
			}
		}

		$tempFiles = [];
		$configuration->set('volatile.tempfiles', serialize($tempFiles));
	}

	/**
	 * Nullify the contents of the file and try to delete it as well
	 *
	 * @param   string  $filename  The absolute path to the file to delete
	 *
	 * @return  bool  True of the deletion is successful
	 */
	public function nullifyAndDelete($filename)
	{
		// Try to nullify (method #1)
		$fp = @fopen($filename, 'w');

		if (is_resource($fp))
		{
			@fclose($fp);
		}
		else
		{
			// Try to nullify (method #2)
			@file_put_contents($filename, '');
		}

		// Unlink
		return @unlink($filename);
	}
}
PK�!]�K]ZQQ BackupEngine/Util/FileLister.phpnu&1i�<?php
/**
 * Akeeba Engine
 *
 * @package   akeebaengine
 * @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License version 3, or later
 *
 * 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, version 3.
 *
 * 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
 * <https://www.gnu.org/licenses/>.
 */

namespace Akeeba\Engine\Util;

defined('AKEEBAENGINE') || die();

use Akeeba\Engine\Factory;

/* Windows system detection */
if (!defined('_AKEEBA_IS_WINDOWS'))
{
	$isWindows = DIRECTORY_SEPARATOR == '\\';

	if (function_exists('php_uname'))
	{
		$isWindows = stristr(php_uname(), 'windows');
	}

	define('_AKEEBA_IS_WINDOWS', $isWindows);
}

/**
 * A filesystem scanner, for internal use
 */
class FileLister
{
	public function &getFiles($folder, $fullpath = false)
	{
		// Initialize variables
		$arr   = [];
		$false = false;

		if (!is_dir($folder) && !is_dir($folder . '/'))
		{
			return $false;
		}

		$handle = @opendir($folder);
		if ($handle === false)
		{
			$handle = @opendir($folder . '/');
		}
		// If directory is not accessible, just return FALSE
		if ($handle === false)
		{
			return $false;
		}

		$registry            = Factory::getConfiguration();
		$dereferencesymlinks = $registry->get('engine.archiver.common.dereference_symlinks');

		while ((($file = @readdir($handle)) !== false))
		{
			if (($file != '.') && ($file != '..'))
			{
				// # Fix 2.4.b1: Do not add DS if we are on the site's root and it's an empty string
				// # Fix 2.4.b2: Do not add DS is the last character _is_ DS
				$ds     = ($folder == '') || ($folder == '/') || (@substr($folder, -1) == '/') || (@substr($folder, -1) == DIRECTORY_SEPARATOR) ? '' : DIRECTORY_SEPARATOR;
				$dir    = "$folder/$file";
				$isDir  = @is_dir($dir);
				$isLink = @is_link($dir);

				//if (!$isDir || ($isDir && $isLink && !$dereferencesymlinks) ) {
				if (!$isDir)
				{
					if ($fullpath)
					{
						$data = _AKEEBA_IS_WINDOWS ? Factory::getFilesystemTools()->TranslateWinPath($dir) : $dir;
					}
					else
					{
						$data = _AKEEBA_IS_WINDOWS ? Factory::getFilesystemTools()->TranslateWinPath($file) : $file;
					}
					if ($data)
					{
						$arr[] = $data;
					}
				}
			}
		}
		@closedir($handle);

		return $arr;
	}

	public function &getFolders($folder, $fullpath = false)
	{
		// Initialize variables
		$arr   = [];
		$false = false;

		if (!is_dir($folder) && !is_dir($folder . '/'))
		{
			return $false;
		}

		$handle = @opendir($folder);
		if ($handle === false)

		{
			$handle = @opendir($folder . '/');
		}

		// If directory is not accessible, just return FALSE
		if ($handle === false)
		{
			return $false;
		}

		$registry            = Factory::getConfiguration();
		$dereferencesymlinks = $registry->get('engine.archiver.common.dereference_symlinks');

		while ((($file = @readdir($handle)) !== false))
		{
			if (($file != '.') && ($file != '..'))
			{
				$dir    = "$folder/$file";
				$isDir  = @is_dir($dir);
				$isLink = @is_link($dir);

				if ($isDir)
				{
					//if(!$dereferencesymlinks && $isLink) continue;
					if ($fullpath)
					{
						$data = _AKEEBA_IS_WINDOWS ? Factory::getFilesystemTools()->TranslateWinPath($dir) : $dir;
					}
					else
					{
						$data = _AKEEBA_IS_WINDOWS ? Factory::getFilesystemTools()->TranslateWinPath($file) : $file;
					}

					if ($data)
					{
						$arr[] = $data;
					}
				}
			}
		}
		@closedir($handle);

		return $arr;
	}
}
PK�!]��-7�l�lBackupEngine/Util/Encrypt.phpnu&1i�<?php
/**
 * Akeeba Engine
 *
 * @package   akeebaengine
 * @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License version 3, or later
 *
 * 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, version 3.
 *
 * 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
 * <https://www.gnu.org/licenses/>.
 */

namespace Akeeba\Engine\Util;

defined('AKEEBAENGINE') || die();

use Akeeba\Engine\Util\AesAdapter\AdapterInterface;
use Akeeba\Engine\Util\AesAdapter\Mcrypt;
use Akeeba\Engine\Util\AesAdapter\OpenSSL;

/**
 * AES implementation in PHP (c) Chris Veness 2005-2016.
 * Right to use and adapt is granted for under a simple creative commons attribution
 * licence. No warranty of any form is offered.
 *
 * Heavily modified for Akeeba Backup by Nicholas K. Dionysopoulos
 * Also added AES-128 CBC mode (with mcrypt and OpenSSL) on top of AES CTR
 */
class Encrypt
{
	use HashTrait;

	// Sbox is pre-computed multiplicative inverse in GF(2^8) used in SubBytes and KeyExpansion [�5.1.1]
	protected $Sbox =
		[
			0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76,
			0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0,
			0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15,
			0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75,
			0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84,
			0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf,
			0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8,
			0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2,
			0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73,
			0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb,
			0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79,
			0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08,
			0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a,
			0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e,
			0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf,
			0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16,
		];

	// Rcon is Round Constant used for the Key Expansion [1st col is 2^(r-1) in GF(2^8)] [�5.2]
	protected $Rcon = [
		[0x00, 0x00, 0x00, 0x00],
		[0x01, 0x00, 0x00, 0x00],
		[0x02, 0x00, 0x00, 0x00],
		[0x04, 0x00, 0x00, 0x00],
		[0x08, 0x00, 0x00, 0x00],
		[0x10, 0x00, 0x00, 0x00],
		[0x20, 0x00, 0x00, 0x00],
		[0x40, 0x00, 0x00, 0x00],
		[0x80, 0x00, 0x00, 0x00],
		[0x1b, 0x00, 0x00, 0x00],
		[0x36, 0x00, 0x00, 0x00],
	];

	protected $passwords = [];

	/**
	 * The algorithm to use for PBKDF2. Must be a supported hash_hmac algorithm. Default: sha1
	 *
	 * @var  string
	 */
	private $pbkdf2Algorithm = 'sha1';

	/**
	 * Number of iterations to use for PBKDF2
	 *
	 * @var  int
	 */
	private $pbkdf2Iterations = 1000;

	/**
	 * Should we use a static salt for PBKDF2?
	 *
	 * @var  int
	 */
	private $pbkdf2UseStaticSalt = 0;

	/**
	 * The static salt to use for PBKDF2
	 *
	 * @var  string
	 */
	private $pbkdf2StaticSalt = "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0";

	/**
	 * AES Cipher function: encrypt 'input' with Rijndael algorithm
	 *
	 * @param   string  $input  message as byte-array (16 bytes)
	 * @param   array   $w      key schedule as 2D byte-array (Nr+1 x Nb bytes) -
	 *                          generated from the cipher key by KeyExpansion()
	 *
	 * @return      array  ciphertext as byte-array (16 bytes)
	 */
	public function Cipher($input, $w)
	{
		// main Cipher function [�5.1]
		$Nb = 4; // block size (in words): no of columns in state (fixed at 4 for AES)
		$Nr = count($w) / $Nb - 1; // no of rounds: 10/12/14 for 128/192/256-bit keys

		$state = []; // initialise 4xNb byte-array 'state' with input [�3.4]

		for ($i = 0; $i < 4 * $Nb; $i++)
		{
			$state[$i % 4][(int) floor($i / 4)] = $input[$i];
		}

		$state = $this->AddRoundKey($state, $w, 0, $Nb);

		for ($round = 1; $round < $Nr; $round++)
		{
			// apply Nr rounds
			$state = $this->SubBytes($state, $Nb);
			$state = $this->ShiftRows($state, $Nb);
			$state = $this->MixColumns($state, $Nb);
			$state = $this->AddRoundKey($state, $w, $round, $Nb);
		}

		$state = $this->SubBytes($state, $Nb);
		$state = $this->ShiftRows($state, $Nb);
		$state = $this->AddRoundKey($state, $w, $Nr, $Nb);

		$output = [4 * $Nb]; // convert state to 1-d array before returning [�3.4]

		for ($i = 0; $i < 4 * $Nb; $i++)
		{
			$output[$i] = $state[$i % 4][(int) floor($i / 4)];
		}

		return $output;
	}

	/**
	 * Key expansion for Rijndael Cipher(): performs key expansion on cipher key
	 * to generate a key schedule
	 *
	 * @param   array  $key  cipher key byte-array (16 bytes)
	 *
	 * @return    array key schedule as 2D byte-array (Nr+1 x Nb bytes)
	 */
	public function KeyExpansion($key)
	{
		// generate Key Schedule from Cipher Key [�5.2]
		$Nb = 4; // block size (in words): no of columns in state (fixed at 4 for AES)
		$Nk = count($key) / 4; // key length (in words): 4/6/8 for 128/192/256-bit keys
		$Nr = $Nk + 6; // no of rounds: 10/12/14 for 128/192/256-bit keys

		$w    = [];
		$temp = [];

		for ($i = 0; $i < $Nk; $i++)
		{
			$r     = [$key[4 * $i], $key[4 * $i + 1], $key[4 * $i + 2], $key[4 * $i + 3]];
			$w[$i] = $r;
		}

		for ($i = $Nk; $i < ($Nb * ($Nr + 1)); $i++)
		{
			$w[(int) $i] = [];

			for ($t = 0; $t < 4; $t++)
			{
				$temp[$t] = $w[(int) $i - 1][$t];
			}

			if ($i % $Nk == 0)
			{
				$temp = $this->SubWord($this->RotWord($temp));

				for ($t = 0; $t < 4; $t++)
				{
					$temp[$t] ^= $this->Rcon[(int) ($i / $Nk)][$t];
				}
			}
			elseif ($Nk > 6 && $i % $Nk == 4)
			{
				$temp = $this->SubWord($temp);
			}

			for ($t = 0; $t < 4; $t++)
			{
				$w[(int) $i][$t] = $w[(int) $i - $Nk][$t] ^ $temp[$t];
			}
		}

		return $w;
	}

	/**
	 * Encrypt a text using AES encryption in Counter mode of operation
	 *  - see http://csrc.nist.gov/publications/nistpubs/800-38a/sp800-38a.pdf
	 *
	 * Unicode multi-byte character safe
	 *
	 * @param   string  $plaintext  source text to be encrypted
	 * @param   string  $password   the password to use to generate a key
	 * @param   int     $nBits      number of bits to be used in the key (128, 192, or 256)
	 *
	 * @return string encrypted text
	 */
	public function AESEncryptCtr($plaintext, $password, $nBits)
	{
		$blockSize = 16; // block size fixed at 16 bytes / 128 bits (Nb=4) for AES

		// standard allows 128/192/256 bit keys
		if (!($nBits == 128 || $nBits == 192 || $nBits == 256))
		{
			return '';
		}

		// note PHP (5) gives us plaintext and password in UTF8 encoding!

		// use AES itself to encrypt password to get cipher key (using plain password as source for
		// key expansion) - gives us well encrypted key
		$nBytes  = $nBits / 8; // no bytes in key
		$pwBytes = [];

		for ($i = 0; $i < $nBytes; $i++)
		{
			$pwBytes[$i] = ord(substr($password, $i, 1)) & 0xff;
		}

		$key = $this->Cipher($pwBytes, $this->KeyExpansion($pwBytes));
		$key = array_merge($key, array_slice($key, 0, $nBytes - 16)); // expand key to 16/24/32 bytes long

		// initialise counter block (NIST SP800-38A �B.2): millisecond time-stamp for nonce in
		// 1st 8 bytes, block counter in 2nd 8 bytes
		$counterBlock = [];
		$nonce        = floor(microtime(true) * 1000); // timestamp: milliseconds since 1-Jan-1970
		$nonceSec     = floor($nonce / 1000);
		$nonceMs      = $nonce % 1000;

		// encode nonce with seconds in 1st 4 bytes, and (repeated) ms part filling 2nd 4 bytes
		for ($i = 0; $i < 4; $i++)
		{
			$counterBlock[$i] = $this->urs($nonceSec, $i * 8) & 0xff;
		}

		for ($i = 0; $i < 4; $i++)
		{
			$counterBlock[$i + 4] = $nonceMs & 0xff;
		}

		// and convert it to a string to go on the front of the ciphertext
		$ctrTxt = '';

		for ($i = 0; $i < 8; $i++)
		{
			$ctrTxt .= chr($counterBlock[$i]);
		}

		// generate key schedule - an expansion of the key into distinct Key Rounds for each round
		$keySchedule = $this->KeyExpansion($key);

		$blockCount = ceil(strlen($plaintext) / $blockSize);
		$ciphertxt  = []; // ciphertext as array of strings

		for ($b = 0; $b < $blockCount; $b++)
		{
			// set counter (block #) in last 8 bytes of counter block (leaving nonce in 1st 8 bytes)
			// done in two stages for 32-bit ops: using two words allows us to go past 2^32 blocks (68GB)
			for ($c = 0; $c < 4; $c++)
			{
				$counterBlock[15 - $c] = $this->urs($b, $c * 8) & 0xff;
			}

			for ($c = 0; $c < 4; $c++)
			{
				$counterBlock[15 - $c - 4] = $this->urs($b / 0x100000000, $c * 8);
			}

			$cipherCntr = $this->Cipher($counterBlock, $keySchedule); // -- encrypt counter block --

			// block size is reduced on final block
			$blockLength = $b < $blockCount - 1 ? $blockSize : (strlen($plaintext) - 1) % $blockSize + 1;
			$cipherByte  = [];

			for ($i = 0; $i < $blockLength; $i++)
			{ // -- xor plaintext with ciphered counter byte-by-byte --
				$cipherByte[$i] = $cipherCntr[$i] ^ ord(substr($plaintext, $b * $blockSize + $i, 1));
				$cipherByte[$i] = chr($cipherByte[$i]);
			}

			$ciphertxt[$b] = implode('', $cipherByte); // escape troublesome characters in ciphertext
		}

		// implode is more efficient than repeated string concatenation
		$ciphertext = $ctrTxt . implode('', $ciphertxt);
		$ciphertext = base64_encode($ciphertext);

		return $ciphertext;
	}

	/**
	 * Decrypt a text encrypted by AES in counter mode of operation
	 *
	 * @param   string  $ciphertext  source text to be decrypted
	 * @param   string  $password    the password to use to generate a key
	 * @param   int     $nBits       number of bits to be used in the key (128, 192, or 256)
	 *
	 * @return string decrypted text
	 */
	public function AESDecryptCtr($ciphertext, $password, $nBits)
	{
		$blockSize = 16; // block size fixed at 16 bytes / 128 bits (Nb=4) for AES

		// standard allows 128/192/256 bit keys
		if (!($nBits == 128 || $nBits == 192 || $nBits == 256))
		{
			return '';
		}

		$ciphertext = base64_decode($ciphertext);

		// use AES to encrypt password (mirroring encrypt routine)
		$nBytes  = $nBits / 8; // no bytes in key
		$pwBytes = [];

		for ($i = 0; $i < $nBytes; $i++)
		{
			$pwBytes[$i] = ord(substr($password, $i, 1)) & 0xff;
		}

		$key = $this->Cipher($pwBytes, $this->KeyExpansion($pwBytes));
		$key = array_merge($key, array_slice($key, 0, $nBytes - 16)); // expand key to 16/24/32 bytes long

		// recover nonce from 1st element of ciphertext
		$counterBlock = [];
		$ctrTxt       = substr($ciphertext, 0, 8);

		for ($i = 0; $i < 8; $i++)
		{
			$counterBlock[$i] = ord(substr($ctrTxt, $i, 1));
		}

		// generate key schedule
		$keySchedule = $this->KeyExpansion($key);

		// separate ciphertext into blocks (skipping past initial 8 bytes)
		$nBlocks = ceil((strlen($ciphertext) - 8) / $blockSize);
		$ct      = [];

		for ($b = 0; $b < $nBlocks; $b++)
		{
			$ct[$b] = substr($ciphertext, 8 + $b * $blockSize, 16);
		}

		$ciphertext = $ct; // ciphertext is now array of block-length strings

		// plaintext will get generated block-by-block into array of block-length strings
		$plaintxt = [];

		for ($b = 0; $b < $nBlocks; $b++)
		{
			// set counter (block #) in last 8 bytes of counter block (leaving nonce in 1st 8 bytes)
			for ($c = 0; $c < 4; $c++)
			{
				$counterBlock[15 - $c] = $this->urs($b, $c * 8) & 0xff;
			}

			for ($c = 0; $c < 4; $c++)
			{
				$counterBlock[15 - $c - 4] = $this->urs(($b + 1) / 0x100000000 - 1, $c * 8) & 0xff;
			}

			$cipherCntr = $this->Cipher($counterBlock, $keySchedule); // encrypt counter block

			$plaintxtByte = [];

			for ($i = 0; $i < strlen($ciphertext[$b]); $i++)
			{
				// -- xor plaintext with ciphered counter byte-by-byte --
				$plaintxtByte[$i] = $cipherCntr[$i] ^ ord(substr($ciphertext[$b], $i, 1));
				$plaintxtByte[$i] = chr($plaintxtByte[$i]);
			}

			$plaintxt[$b] = implode('', $plaintxtByte);
		}

		// join array of blocks into single plaintext string
		$plaintext = implode('', $plaintxt);

		return $plaintext;
	}

	/**
	 * AES encryption in CBC mode. This is the standard mode (the CTR methods
	 * actually use Rijndael-128 in CTR mode, which - technically - isn't AES).
	 * The data length is tucked as a 32-bit unsigned integer (little endian)
	 * after the ciphertext. It supports AES-128 only.
	 *
	 * @param   string  $plaintext  The data to encrypt
	 * @param   string  $password   Encryption password
	 *
	 * @return  string  The ciphertext
	 * @author Nicholas K. Dionysopoulos
	 *
	 * @since  3.0.1
	 */
	public function AESEncryptCBC($plaintext, $password)
	{
		$adapter = $this->getAdapter();

		if (!$adapter->isSupported())
		{
			return false;
		}

		// Get encryption parameters
		$rand          = new RandomValue();
		$params        = $this->getKeyDerivationParameters();
		$useStaticSalt = $params['useStaticSalt'];
		$keySizeBytes  = $params['keySize'];
		$salt          = null;

		if ($useStaticSalt)
		{
			$key = $this->getStaticSaltExpandedKey($password);
		}
		else
		{
			// Create a salt and derive a key from the password using PBKDF2
			$algorithm  = $params['algorithm'];
			$iterations = $params['iterations'];
			$salt       = $rand->generate(64);
			$key        = $this->pbkdf2($password, $salt, $algorithm, $iterations, $keySizeBytes);
		}


		// Also create a new, random IV
		$iv = $rand->generate($keySizeBytes);

		// The ciphertext is the encrypted string...
		$ciphertext = $adapter->encrypt($plaintext, $key, $iv);

		// ...minus the IV which was placed in front
		$ciphertext = substr($ciphertext, $keySizeBytes);

		if (!$useStaticSalt)
		{
			// ...plus the PBKDF2 setup values at the end (68 bytes)...
			$ciphertext .= 'JPST' . $salt;
		}

		// ...plus the IV at the end (20 bytes)...
		$ciphertext .= 'JPIV' . $iv;

		// ...plus the plaintext length (4 bytes).
		$ciphertext .= pack('V', strlen($plaintext));

		return $ciphertext;
	}

	/**
	 * Get the parameters fed into PBKDF2 to expand the user password into an encryption key. These are the static
	 * parameters (key size, hashing algorithm and number of iterations). A new salt is used for each encryption block
	 * to minimize the risk of attacks against the password.
	 *
	 * @return  array
	 */
	public function getKeyDerivationParameters()
	{
		return [
			'keySize'       => 16,
			'algorithm'     => $this->pbkdf2Algorithm,
			'iterations'    => $this->pbkdf2Iterations,
			'useStaticSalt' => $this->pbkdf2UseStaticSalt,
			'staticSalt'    => $this->pbkdf2StaticSalt,
		];
	}

	/**
	 * AES decryption in CBC mode. This is the standard mode (the CTR methods
	 * actually use Rijndael-128 in CTR mode, which - technically - isn't AES).
	 *
	 * It supports AES-128 only. It assumes that the last 4 bytes
	 * contain a little-endian unsigned long integer representing the unpadded
	 * data length.
	 *
	 * @param   string  $ciphertext  The data to encrypt
	 * @param   string  $password    Encryption password
	 *
	 * @return  string  The plaintext
	 * @author Nicholas K. Dionysopoulos
	 *
	 * @since  3.0.1
	 */
	public function AESDecryptCBC($ciphertext, $password)
	{
		$adapter = $this->getAdapter();

		if (!$adapter->isSupported())
		{
			return false;
		}

		// Read the data size
		$data_size = unpack('V', substr($ciphertext, -4));

		// Do I have a PBKDF2 salt?
		$salt             = substr($ciphertext, -92, 68);
		$rightStringLimit = -4;

		$params        = $this->getKeyDerivationParameters();
		$keySizeBytes  = $params['keySize'];
		$algorithm     = $params['algorithm'];
		$iterations    = $params['iterations'];
		$useStaticSalt = $params['useStaticSalt'];

		if (substr($salt, 0, 4) == 'JPST')
		{
			// We have a stored salt. Retrieve it and tell decrypt to process the string minus the last 44 bytes
			// (4 bytes for JPST, 16 bytes for the salt, 4 bytes for JPIV, 16 bytes for the IV, 4 bytes for the
			// uncompressed string length - note that using PBKDF2 means we're also using a randomized IV per the
			// format specification).
			$salt             = substr($salt, 4);
			$rightStringLimit -= 68;

			$key = $this->pbkdf2($password, $salt, $algorithm, $iterations, $keySizeBytes);
		}
		elseif ($useStaticSalt)
		{
			// We have a static salt. Use it for PBKDF2.
			$key = $this->getStaticSaltExpandedKey($password);
		}
		else
		{
			// Get the expanded key from the password. THIS USES THE OLD, INSECURE METHOD.
			$key = $this->expandKey($password);
		}

		// Try to get the IV from the data
		$iv = substr($ciphertext, -24, 20);

		if (substr($iv, 0, 4) == 'JPIV')
		{
			// We have a stored IV. Retrieve it and tell mdecrypt to process the string minus the last 24 bytes
			// (4 bytes for JPIV, 16 bytes for the IV, 4 bytes for the uncompressed string length)
			$iv               = substr($iv, 4);
			$rightStringLimit -= 20;
		}
		else
		{
			// No stored IV. Do it the dumb way.
			$iv = $this->createTheWrongIV($password);
		}

		// Decrypt
		$plaintext = $adapter->decrypt($iv . substr($ciphertext, 0, $rightStringLimit), $key);

		// Trim padding, if necessary
		if (strlen($plaintext) > $data_size)
		{
			$plaintext = substr($plaintext, 0, $data_size);
		}

		return $plaintext;
	}

	/**
	 * That's the old way of creating an IV that's definitely not cryptographically sound.
	 *
	 * DO NOT USE, EVER, UNLESS YOU WANT TO DECRYPT LEGACY DATA
	 *
	 * @param   string  $password  The raw password from which we create an IV in a super bozo way
	 *
	 * @return  string  A 16-byte IV string
	 *
	 * @since   4.6.0
	 * @author  Nicholas K. Dionysopoulos
	 */
	function createTheWrongIV($password)
	{
		static $ivs = [];

		$key = self::md5($password);

		if (!isset($ivs[$key]))
		{
			// Create an Initialization Vector (IV) based on the password, using the same technique as for the key
			$nBytes  = 16; // AES uses a 128 -bit (16 byte) block size, hence the IV size is always 16 bytes
			$pwBytes = [];

			for ($i = 0; $i < $nBytes; $i++)
			{
				$pwBytes[$i] = ord(substr($password, $i, 1)) & 0xff;
			}

			$iv    = $this->Cipher($pwBytes, $this->KeyExpansion($pwBytes));
			$newIV = '';

			foreach ($iv as $int)
			{
				$newIV .= chr($int);
			}

			$ivs[$key] = $newIV;
		}

		return $ivs[$key];
	}

	/*
	 * Unsigned right shift function, since PHP has neither >>> operator nor unsigned ints
	 *
	 * @param a  number to be shifted (32-bit integer)
	 * @param b  number of bits to shift a to the right (0..31)
	 * @return   a right-shifted and zero-filled by b bits
	 */

	/**
	 * Expand the password to an appropriate 128-bit encryption key. THIS CODE IS OBSOLETE. DO NOT USE.
	 *
	 * @param   string  $password
	 *
	 * @return  string
	 *
	 * @since   5.2.0
	 * @author  Nicholas K. Dionysopoulos
	 */
	public function expandKey($password)
	{
		// Try to fetch cached key or create it if it doesn't exist
		$nBits     = 128;
		$lookupKey = self::md5($password . '-' . $nBits);

		if (array_key_exists($lookupKey, $this->passwords))
		{
			$key = $this->passwords[$lookupKey];

			return $key;
		}

		// use AES itself to encrypt password to get cipher key (using plain password as source for
		// key expansion) - gives us well encrypted key.
		$nBytes  = $nBits / 8; // Number of bytes in key
		$pwBytes = [];

		for ($i = 0; $i < $nBytes; $i++)
		{
			$pwBytes[$i] = ord(substr($password, $i, 1)) & 0xff;
		}

		$key    = $this->Cipher($pwBytes, $this->KeyExpansion($pwBytes));
		$key    = array_merge($key, array_slice($key, 0, $nBytes - 16)); // expand key to 16/24/32 bytes long
		$newKey = '';

		foreach ($key as $int)
		{
			$newKey .= chr($int);
		}

		$key = $newKey;

		$this->passwords[$lookupKey] = $key;

		return $key;
	}

	/**
	 * Returns the correct AES-128 CBC encryption adapter
	 *
	 * @return  AdapterInterface
	 *
	 * @since   5.2.0
	 * @author  Nicholas K. Dionysopoulos
	 */
	public function getAdapter()
	{
		static $adapter = null;

		if (is_object($adapter) && ($adapter instanceof AdapterInterface))
		{
			return $adapter;
		}

		$adapter = new OpenSSL();

		if (!$adapter->isSupported())
		{
			$adapter = new Mcrypt();
		}

		return $adapter;
	}

	/**
	 * Returns the length of a string in BYTES, not characters
	 *
	 * @param   string  $string  The string to get the length for
	 *
	 * @return int The size in BYTES
	 */
	public function stringLength($string)
	{
		return function_exists('mb_strlen') ? mb_strlen($string, '8bit') : strlen($string);
	}

	/**
	 * Attempt to use mbstring for getting parts of strings
	 *
	 * @param   string    $string
	 * @param   int       $start
	 * @param   int|null  $length
	 *
	 * @return  string
	 */
	public function subString($string, $start, $length = null)
	{
		return function_exists('mb_substr') ? mb_substr($string, $start, $length, '8bit') :
			substr($string, $start, $length);
	}

	/**
	 * PBKDF2 key derivation function as defined by RSA's PKCS #5: https://www.ietf.org/rfc/rfc2898.txt
	 *
	 * Test vectors can be found here: https://www.ietf.org/rfc/rfc6070.txt
	 *
	 * This implementation of PBKDF2 was originally created by https://defuse.ca
	 * With improvements by http://www.variations-of-shadow.com
	 * Modified for Akeeba Engine by Akeeba Ltd (removed unnecessary checks to make it faster)
	 *
	 * @param   string  $password    The password.
	 * @param   string  $salt        A salt that is unique to the password.
	 * @param   string  $algorithm   The hash algorithm to use. Default is sha1.
	 * @param   int     $count       Iteration count. Higher is better, but slower. Default: 1000.
	 * @param   int     $key_length  The length of the derived key in bytes.
	 *
	 * @return  string  A string of $key_length bytes
	 */
	public function pbkdf2($password, $salt, $algorithm = 'sha1', $count = 1000, $key_length = 16)
	{
		if (function_exists("hash_pbkdf2"))
		{
			return hash_pbkdf2($algorithm, $password, $salt, $count, $key_length, true);
		}

		$hash_length = $this->stringLength(hash($algorithm, "", true));
		$block_count = ceil($key_length / $hash_length);

		$output = "";

		for ($i = 1; $i <= $block_count; $i++)
		{
			// $i encoded as 4 bytes, big endian.
			$last = $salt . pack("N", $i);

			// First iteration
			$xorResult = hash_hmac($algorithm, $last, $password, true);
			$last      = $xorResult;

			// Perform the other $count - 1 iterations
			for ($j = 1; $j < $count; $j++)
			{
				$last      = hash_hmac($algorithm, $last, $password, true);
				$xorResult ^= $last;
			}

			$output .= $xorResult;
		}

		return $this->subString($output, 0, $key_length);
	}

	/**
	 * @return string
	 */
	public function getPbkdf2Algorithm()
	{
		return $this->pbkdf2Algorithm;
	}

	/**
	 * @param   string  $pbkdf2Algorithm
	 *
	 * @return Encrypt
	 */
	public function setPbkdf2Algorithm($pbkdf2Algorithm)
	{
		$this->pbkdf2Algorithm = $pbkdf2Algorithm;

		return $this;
	}

	/**
	 * @return int
	 */
	public function getPbkdf2Iterations()
	{
		return $this->pbkdf2Iterations;
	}

	/**
	 * @param   int  $pbkdf2Iterations
	 *
	 * @return Encrypt
	 */
	public function setPbkdf2Iterations($pbkdf2Iterations)
	{
		$this->pbkdf2Iterations = $pbkdf2Iterations;

		return $this;
	}

	/**
	 * @return int
	 */
	public function getPbkdf2UseStaticSalt()
	{
		return $this->pbkdf2UseStaticSalt;
	}

	/**
	 * @param   int  $pbkdf2UseStaticSalt
	 *
	 * @return Encrypt
	 */
	public function setPbkdf2UseStaticSalt($pbkdf2UseStaticSalt)
	{
		$this->pbkdf2UseStaticSalt = $pbkdf2UseStaticSalt;

		return $this;
	}

	/**
	 * @return string
	 */
	public function getPbkdf2StaticSalt()
	{
		return $this->pbkdf2StaticSalt;
	}

	/**
	 * @param   string  $pbkdf2StaticSalt
	 *
	 * @return Encrypt
	 */
	public function setPbkdf2StaticSalt($pbkdf2StaticSalt)
	{
		$this->pbkdf2StaticSalt = $pbkdf2StaticSalt;

		return $this;
	}

	/**
	 * Get the expanded key from the user supplied password using a static salt. The results are cached for performance
	 * reasons.
	 *
	 * @param   string  $password  The user-supplied password, UTF-8 encoded.
	 *
	 * @return  string  The expanded key
	 */
	public function getStaticSaltExpandedKey($password)
	{
		$params       = $this->getKeyDerivationParameters();
		$keySizeBytes = $params['keySize'];
		$algorithm    = $params['algorithm'];
		$iterations   = $params['iterations'];
		$staticSalt   = $params['staticSalt'];

		$lookupKey = "PBKDF2-$algorithm-$iterations-" . self::md5($password . $staticSalt);

		if (!array_key_exists($lookupKey, $this->passwords))
		{
			$this->passwords[$lookupKey] = $this->pbkdf2($password, $staticSalt, $algorithm, $iterations, $keySizeBytes);
		}

		return $this->passwords[$lookupKey];
	}

	protected function AddRoundKey($state, $w, $rnd, $Nb)
	{
		// xor Round Key into state S [�5.1.4]
		for ($r = 0; $r < 4; $r++)
		{
			for ($c = 0; $c < $Nb; $c++)
			{
				$state[$r][$c] ^= $w[$rnd * 4 + $c][$r];
			}
		}

		return $state;
	}

	protected function SubBytes($s, $Nb)
	{
		// apply SBox to state S [�5.1.1]
		for ($r = 0; $r < 4; $r++)
		{
			for ($c = 0; $c < $Nb; $c++)
			{
				$s[$r][$c] = $this->Sbox[$s[$r][$c]];
			}
		}

		return $s;
	}

	protected function ShiftRows($s, $Nb)
	{
		// shift row r of state S left by r bytes [�5.1.2]
		$t = [4];

		for ($r = 1; $r < 4; $r++)
		{
			// shift into temp copy
			for ($c = 0; $c < 4; $c++)
			{
				$t[$c] = $s[$r][($c + $r) % $Nb];
			}

			// and copy back
			for ($c = 0; $c < 4; $c++)
			{
				$s[$r][$c] = $t[$c];
			}

		}

		// note that this will work for Nb=4,5,6, but not 7,8 (always 4 for AES):

		return $s; // see fp.gladman.plus.com/cryptography_technology/rijndael/aes.spec.311.pdf
	}

	protected function MixColumns($s, $Nb)
	{
		// combine bytes of each col of state S [�5.1.3]
		for ($c = 0; $c < 4; $c++)
		{
			$a = [4]; // 'a' is a copy of the current column from 's'
			$b = [4]; // 'b' is a�{02} in GF(2^8)

			for ($i = 0; $i < 4; $i++)
			{
				$a[$i] = $s[$i][$c];
				$b[$i] = $s[$i][$c] & 0x80 ? $s[$i][$c] << 1 ^ 0x011b : $s[$i][$c] << 1;
			}

			// a[n] ^ b[n] is a�{03} in GF(2^8)
			$s[0][$c] = $b[0] ^ $a[1] ^ $b[1] ^ $a[2] ^ $a[3]; // 2*a0 + 3*a1 + a2 + a3
			$s[1][$c] = $a[0] ^ $b[1] ^ $a[2] ^ $b[2] ^ $a[3]; // a0 * 2*a1 + 3*a2 + a3
			$s[2][$c] = $a[0] ^ $a[1] ^ $b[2] ^ $a[3] ^ $b[3]; // a0 + a1 + 2*a2 + 3*a3
			$s[3][$c] = $a[0] ^ $b[0] ^ $a[1] ^ $a[2] ^ $b[3]; // 3*a0 + a1 + a2 + 2*a3
		}

		return $s;
	}

	protected function SubWord($w)
	{
		// apply SBox to 4-byte word w
		for ($i = 0; $i < 4; $i++)
		{
			$w[$i] = $this->Sbox[$w[$i]];
		}

		return $w;
	}

	protected function RotWord($w)
	{
		// rotate 4-byte word w left by one byte
		$tmp = $w[0];

		for ($i = 0; $i < 3; $i++)
		{
			$w[$i] = $w[$i + 1];
		}

		$w[3] = $tmp;

		return $w;
	}

	protected function urs($a, $b)
	{
		$a &= 0xffffffff;
		$b &= 0x1f; // (bounds check)

		if ($a & 0x80000000 && $b > 0)
		{
			// if left-most bit set
			$a = ($a >> 1) & 0x7fffffff; //   right-shift one bit & clear left-most bit
			$a = $a >> ($b - 1); //   remaining right-shifts
		}
		else
		{
			// otherwise
			$a = ($a >> $b); //   use normal right-shift
		}

		return $a;
	}
}
PK�!]��0PWW BackupEngine/Util/Statistics.phpnu&1i�<?php
/**
 * Akeeba Engine
 *
 * @package   akeebaengine
 * @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License version 3, or later
 *
 * 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, version 3.
 *
 * 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
 * <https://www.gnu.org/licenses/>.
 */

namespace Akeeba\Engine\Util;

defined('AKEEBAENGINE') || die();

use Akeeba\Engine\Factory;
use Akeeba\Engine\Platform;
use Exception;

class Statistics
{
	/** @var bool used to block multipart updating initializing the backup */
	private $multipart_lock = true;

	/** @var int The statistics record number of the current backup attempt */
	private $statistics_id = null;

	/** @var array Local cache of the stat record data */
	private $cached_data = [];

	/**
	 * Returns all the filenames of the backup archives for the specified stat record,
	 * or null if the backup type is wrong or the file doesn't exist. It takes into
	 * account the multipart nature of Split Backup Archives.
	 *
	 * @param   array  $stat             The backup statistics record
	 * @param   bool   $skipNonComplete  Skips over backups with no files produced
	 *
	 * @return array|null The filenames or null if it's not applicable
	 */
	public static function get_all_filenames($stat, $skipNonComplete = true)
	{
		// Shortcut for database entries marked as having no files
		if ($stat['filesexist'] == 0)
		{
			return [];
		}

		// Initialize
		$base_directory = @dirname($stat['absolute_path']);
		$base_filename  = $stat['archivename'];
		$filenames      = [$base_filename];

		if (empty($base_filename))
		{
			// This is a backup with a writer which doesn't store files on the server
			return null;
		}

		// Calculate all the filenames for this backup
		if ($stat['multipart'] > 1)
		{
			// Find the base filename and extension
			$dotpos    = strrpos($base_filename, '.');
			$extension = substr($base_filename, $dotpos);
			$basefile  = substr($base_filename, 0, $dotpos);

			// Calculate the multiple names
			$multipart = $stat['multipart'];

			for ($i = 1; $i < $multipart; $i++)
			{
				// Note: For $multipart = 10, it will produce i.e. .z01 through .z10
				// This is intentional. If the backup aborts and multipart=1, we
				// might be stuck with a .z01 file instead of a .zip. So do not
				// change the less than or equal with a straight less than.
				$filenames[] = $basefile . substr($extension, 0, 2) . sprintf('%02d', $i);
			}
		}

		// Check if the files exist, otherwise attempt to provide relocated filename
		$ret = [];

		$ds = DIRECTORY_SEPARATOR;
		// $test_file is the first file which must have been created
		$test_file = count($filenames) == 1 ? $filenames[0] : $filenames[1];

		if (
			(!@file_exists($base_directory . $ds . $test_file)) ||
			(!is_dir($base_directory))
		)
		{
			// The test file wasn't detected. Use the configured output directory.
			$registry       = Factory::getConfiguration();
			$base_directory = $registry->get('akeeba.basic.output_directory');
		}

		foreach ($filenames as $filename)
		{
			// Turn relative path to absolute
			$filename = $base_directory . $ds . $filename;

			// Return the new filename IF IT EXISTS!
			if (!@file_exists($filename))
			{
				$filename = '';
			}

			// Do not return filename for invalid backups
			if (!empty($filename))
			{
				$ret[] = $filename;
			}
		}

		// Edge case: still running backups, we have to brute force the scan
		// of existing files (multipart may be lying)
		if ($stat['status'] == 'run')
		{
			$base_filename = $stat['archivename'];
			$dotpos        = strrpos($base_filename, '.');
			$extension     = substr($base_filename, $dotpos);
			$basefile      = substr($base_filename, 0, $dotpos);

			$registry = Factory::getConfiguration();
			$dirs     = [
				@dirname($stat['absolute_path']),
				$registry->get('akeeba.basic.output_directory'),
			];

			// Look for base file
			foreach ($dirs as $dir)
			{
				if (@file_exists($dir . $ds . $base_filename))
				{
					$ret[] = $dir . $ds . $base_filename;

					break;
				}
			}

			// Look for added files
			$found = true;
			$i     = 0;

			while ($found)
			{
				$i++;
				$found          = false;
				$part_file_name = $basefile . substr($extension, 0, 2) . sprintf('%02d', $i);

				foreach ($dirs as $dir)
				{
					if (@file_exists($dir . $ds . $part_file_name))
					{
						$ret[] = $dir . $ds . $part_file_name;
						$found = true;

						break;
					}
				}
			}
		}

		if ((count($ret) == 0) && $skipNonComplete)
		{
			$ret = null;
		}

		if (!empty($ret) && is_array($ret))
		{
			$ret = array_unique($ret);
		}

		return $ret;
	}

	/**
	 * Releases the initial multipart lock
	 */
	public function release_multipart_lock()
	{
		$this->multipart_lock = false;
	}

	/**
	 * Updates the multipart status of the current backup attempt's statistics record
	 *
	 * @param   int  $multipart  The new multipart status
	 */
	public function updateMultipart($multipart)
	{
		if ($this->multipart_lock)
		{
			return;
		}

		Factory::getLog()->debug('Updating multipart status to ' . $multipart);

		// Cache this change and commit to db only after the backup is done, or failed
		$registry = Factory::getConfiguration();
		$registry->set('volatile.statistics.multipart', $multipart);
	}

	/**
	 * Sets or updates the statistics record of the current backup attempt
	 *
	 * @param   array  $data
	 *
	 * @return bool
	 * @throws Exception
	 */
	public function setStatistics($data)
	{
		$ret = Platform::getInstance()->set_or_update_statistics($this->statistics_id, $data);

		if ($ret === false)
		{
			return false;
		}

		if (!is_null($ret))
		{
			$this->statistics_id = $ret;
		}

		$this->cached_data = array_merge($this->cached_data, $data);

		return true;
	}

	/**
	 * Returns the statistics record ID (used in DB backup classes)
	 * @return int
	 */
	public function getId()
	{
		return $this->statistics_id;
	}

	/**
	 * Returns a copy of the cached data
	 * @return array
	 */
	public function getRecord()
	{
		return $this->cached_data;
	}

	/**
	 * Updates the "in step" flag of the current backup record.
	 *
	 * @param   false  $inStep  Am I currently executing a backup step? False if just finished.
	 *
	 * @return  bool
	 */
	public function updateInStep($inStep = false)
	{
		if (!$this->getId())
		{
			return false;
		}

		$data = $this->getRecord();

		/**
		 * We will only update the instep of running backups for two reasons:
		 *
		 * 1. The very last Kettenrad entry is after the backup process is complete. The record is marked 'complete'. I
		 *    must not touch it in this case.
		 *
		 * 2. When a record is marked 'fail' its instep is also set to 0. This happens in Factory::resetState().
		 */
		//
		if ($data['status'] == 'complete')
		{
			return true;
		}

		$data['instep']    = $inStep ? 1 : 0;
		$data['backupend'] = Platform::getInstance()->get_timestamp_database();

		try
		{
			return $this->setStatistics($data);
		}
		catch (Exception $e)
		{
			return false;
		}
	}
}
PK�!]��;;&BackupEngine/Util/ProfileMigration.phpnu&1i�<?php
/**
 * Akeeba Engine
 *
 * @package   akeebaengine
 * @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License version 3, or later
 *
 * 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, version 3.
 *
 * 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
 * <https://www.gnu.org/licenses/>.
 */

namespace Akeeba\Engine\Util;

defined('AKEEBAENGINE') || die();

use Akeeba\Engine\Factory;
use Akeeba\Engine\Platform;
use Exception;

/**
 * This helper class is used to migrate Akeeba Backup profiles to the new storage format implemented since version
 * 6.4.1
 *
 * @since       6.4.1
 */
abstract class ProfileMigration
{
	/**
	 * Tries to migrate a backup profile to the new JSON-based storage format used since version 6.4.1.
	 *
	 * @param   int  $profileID  The ID of the profile to migrate
	 *
	 * @return  bool  Whether we converted the profile
	 *
	 * @since   6.4.1
	 */
	public static function migrateProfile($profileID)
	{
		$platform = Platform::getInstance();
		$db       = Factory::getDatabase($platform->get_platform_database_options());

		// Is the database connected?
		if (!$db->connected())
		{
			return false;
		}

		// Load the raw data from the database
		try
		{
			$sql = (method_exists($db, 'createQuery') ? $db->createQuery() : $db->getQuery(true))
				->select('*')
				->from($db->qn($platform->tableNameProfiles))
				->where($db->qn('id') . ' = ' . $db->q($profileID));

			$rawData = $db->setQuery($sql)->loadAssoc();
		}
		catch (Exception $e)
		{
			return false;
		}

		// Decrypt the configuration data if required
		$rawData['configuration'] = self::decryptConfiguration($rawData['configuration']);
		$migrated                 = false;

		// Migrate the configuration from INI to JSON format
		if (self::looksLikeIni($rawData['configuration']))
		{
			$rawData['configuration'] = self::convertINItoJSON($rawData['configuration']);

			$migrated = true;
		}

		// Migrate the filters from INI to JSON format
		if (self::looksLikeSerialized($rawData['filters']))
		{
			$rawData['filters'] = self::convertSerializedToJSON($rawData['filters']);

			$migrated = true;
		}

		if (!$migrated)
		{
			return false;
		}

		$rawData['configuration'] = self::encryptConfiguration($rawData['configuration']);

		$sql = (method_exists($db, 'createQuery') ? $db->createQuery() : $db->getQuery(true))
			->update($db->qn($platform->tableNameProfiles))
			->set($db->qn('configuration') . ' = ' . $db->q($rawData['configuration']))
			->set($db->qn('filters') . ' = ' . $db->q($rawData['filters']))
			->where($db->qn('id') . ' = ' . $db->q($profileID));

		$db->setQuery($sql);

		try
		{
			$result = $db->query();
		}
		catch (Exception $exc)
		{
			return false;
		}

		return ($result == true);
	}

	/**
	 * Decrypt the configuration data if necessary. Returns the decrypted data.
	 *
	 * @param   string  $configData  The possibly encrypted data.
	 *
	 * @return  string  The decrypted data
	 *
	 * @since   6.4.1
	 */
	public static function decryptConfiguration($configData)
	{
		$noData    = empty($configData);
		$signature = ($noData || (strlen($configData) < 12)) ? '' : substr($configData, 0, 12);

		if (in_array($signature, ['###AES128###', '###CTR128###']))
		{
			return Factory::getSecureSettings()->decryptSettings($configData);
		}

		return $configData;
	}

	/**
	 * Encrypt the configuration data if necessary.
	 *
	 * @param   string  $configData  The raw configuration data
	 *
	 * @return  string  The possibly encrypted configuration data
	 *
	 * @since   6.4.1
	 */
	public static function encryptConfiguration($configData)
	{
		$secureSettings = Factory::getSecureSettings();

		return $secureSettings->encryptSettings($configData);
	}

	/**
	 * Does the provided configuration data look like it's INI encoded?
	 *
	 * @param   string  $configData  The unencrypted configuration data we read from the database.
	 *
	 * @return  bool
	 *
	 * @since   6.4.1
	 */
	public static function looksLikeIni($configData)
	{
		if (empty($configData))
		{
			return false;
		}

		if (strlen($configData) < 8)
		{
			return false;
		}

		if ((substr($configData, 0, 8) == '[global]') || substr($configData, 0, 8) == '[akeeba]')
		{
			return true;
		}

		return false;
	}

	/**
	 * Convert the INI-encoded data to JSON-encoded data
	 *
	 * @param   string  $configData  The INI-encoded data
	 *
	 * @return  string  The JSON-encoded data
	 *
	 * @since   6.4.1
	 */
	public static function convertINItoJSON($configData)
	{
		$dataArray = ParseIni::parse_ini_file($configData, true, true);

		return json_encode($dataArray, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_FORCE_OBJECT | JSON_PRETTY_PRINT);
	}

	/**
	 * Does the raw filters string provided seems to use serialized data? We actually check if it looks like JSON.
	 * If it's not, we assume it's serialized data.
	 *
	 * @param   string  $rawFilters  The raw filters string
	 *
	 * @return  bool  Does it look like a serialized string?
	 *
	 * @since   6.4.1
	 */
	public static function looksLikeSerialized($rawFilters)
	{
		if (empty($rawFilters))
		{
			return false;
		}

		if (substr($rawFilters, 0, 1) == '{')
		{
			return false;
		}

		return true;
	}

	/**
	 * Convert the serialized array in $rawFilters to JSON representation
	 *
	 * @param   string  $rawFilters  Raw serialized string
	 *
	 * @return  string  JSON-encoded string
	 *
	 * @since   6.4.1
	 */
	public static function convertSerializedToJSON($rawFilters)
	{
		$filters = unserialize($rawFilters);

		if (empty($filters))
		{
			$filters = [];
		}

		return json_encode($filters, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_FORCE_OBJECT | JSON_PRETTY_PRINT);
	}
}
PK�!]�G�+;;BackupEngine/Util/Logger.phpnu&1i�<?php
/**
 * Akeeba Engine
 *
 * @package   akeebaengine
 * @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License version 3, or later
 *
 * 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, version 3.
 *
 * 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
 * <https://www.gnu.org/licenses/>.
 */

namespace Akeeba\Engine\Util;

defined('AKEEBAENGINE') || die();

use Akeeba\Engine\Factory;
use Akeeba\Engine\Platform;
use Akeeba\Engine\Util\Log\LogInterface;
use Akeeba\Engine\Util\Log\WarningsLoggerAware;
use Akeeba\Engine\Util\Log\WarningsLoggerInterface;
use Akeeba\Engine\Psr\Log\InvalidArgumentException;
use Akeeba\Engine\Psr\Log\LoggerInterface;
use Akeeba\Engine\Psr\Log\LogLevel;

/**
 * Writes messages to the backup log file
 */
class Logger implements LoggerInterface, LogInterface, WarningsLoggerInterface
{
	use WarningsLoggerAware;

	/** @var  string  Full path to log file */
	protected $logName = null;

	/** @var  string  The current log tag */
	protected $currentTag = null;

	/** @var  resource  The file pointer to the current log file */
	protected $fp = null;

	/** @var  bool  Is the logging currently paused? */
	protected $paused = false;

	/** @var  int  The minimum log level */
	protected $configuredLoglevel;

	/** @var  string  The untranslated path to the site's root */
	protected $site_root_untranslated;

	/** @var  string  The translated path to the site's root */
	protected $site_root;

	/**
	 * Public constructor. Initialises the properties with the parameters from the backup profile and platform.
	 */
	public function __construct()
	{
		$this->initialiseWithProfileParameters();
	}

	/**
	 * When shutting down this class always close any open log files.
	 */
	public function __destruct()
	{
		$this->close();
	}

	/**
	 * Clears the logfile
	 *
	 * @param   string  $tag  Backup origin
	 */
	public function reset($tag = null)
	{
		// Pause logging
		$this->pause();

		// Get the file names for the default log and the tagged log
		$currentLogName = $this->logName;
		$this->logName  = $this->getLogFilename($tag);

		// Close the file if it's open
		if ($currentLogName == $this->logName)
		{
			$this->close();
		}

		// Remove the log file if it exists
		@unlink($this->logName);

		// Reset the log file
		$fp = @fopen($this->logName, 'w');
		$hasWritten = false;

		if ($fp !== false)
		{
			$hasWritten = fwrite($fp, '<' . '?' . 'php die(); ' . '?' . '>' . "\n") !== false;
			@fclose($fp);
		}

		// If I could not write to a .log.php file try using a .log file instead.
		if (!$hasWritten)
		{
			$this->logName  = $this->getLogFilename($tag, '');
			$fp = @fopen($this->logName, 'w');
			$hasWritten = false;

			if ($fp !== false)
			{
				$hasWritten = fwrite($fp, "\n") !== false;
				@fclose($fp);
			}
		}

		// Delete the default log file(s) if they exists
		$defaultLog     = $this->getLogFilename(null);

		if (!empty($tag) && @file_exists($defaultLog))
		{
			@unlink($defaultLog);
		}

		$defaultLog     = $this->getLogFilename(null, '');

		if (!empty($tag) && @file_exists($defaultLog))
		{
			@unlink($defaultLog);
		}

		// Set the current log tag
		$this->currentTag = $tag;

		// Unpause logging
		$this->unpause();
	}

	/**
	 * Writes a line to the log, if the log level is high enough
	 *
	 * @param   string  $level    The log level
	 * @param   string  $message  The message to write to the log
	 * @param   array   $context  The logging context. For PSR-3 compatibility but not used in text file logs.
	 *
	 * @return  void
	 */
	public function log($level, $message = '', array $context = [])
	{
		// Warnings are enqueued no matter what is the minimum log level to report in the log file
		if (in_array($level, [LogLevel::WARNING, LogLevel::NOTICE]))
		{
			$this->enqueueWarning($message);
		}

		// If we are told to not log anything we can't continue
		if ($this->configuredLoglevel == 0)
		{
			return;
		}

		// Open the log if it's closed
		if (is_null($this->fp))
		{
			$this->open($this->currentTag);
		}

		// If the log could not be opened we can't continue
		if (is_null($this->fp))
		{
			return;
		}

		// If the logging is paused we can't continue
		if ($this->paused)
		{
			return;
		}

		// Get the log level as an integer (compatibility with our minimum log level configuration parameter)
		switch ($level)
		{
			case LogLevel::EMERGENCY:
			case LogLevel::ALERT:
			case LogLevel::CRITICAL:
			case LogLevel::ERROR:
				$intLevel = 1;
				break;

			case LogLevel::WARNING:
			case LogLevel::NOTICE:
				$intLevel = 2;
				break;

			case LogLevel::INFO:
				$intLevel = 3;
				break;

			case LogLevel::DEBUG:
				$intLevel = 4;
				break;

			default:
				throw new InvalidArgumentException("Unknown log level $level", 500);
				break;
		}

		// If the minimum log level is lower than what we're trying to log we cannot continue
		if ($this->configuredLoglevel < $intLevel)
		{
			return;
		}

		$translateRoot = true;

		if (array_key_exists('root_translate', $context))
		{
			$translateRoot = ($context['root_translate'] === 1) || ($context['root_translate'] === '1') || ($context['root_translate'] === true);
		}

		// Replace the site's root with <root> in the log file
		if ($translateRoot && !defined('AKEEBADEBUG'))
		{
			$message = str_replace($this->site_root_untranslated, "<root>", $message);
			$message = str_replace($this->site_root, "<root>", $message);
		}

		// Replace new lines
		$message = str_replace("\r\n", "\n", $message);
		$message = str_replace("\r", "\n", $message);
		$message = str_replace("\n", ' \n ', $message);

		switch ($level)
		{
			case LogLevel::EMERGENCY:
			case LogLevel::ALERT:
			case LogLevel::CRITICAL:
			case LogLevel::ERROR:
				$string = "ERROR   |";
				break;

			case LogLevel::WARNING:
			case LogLevel::NOTICE:
				$string = "WARNING |";
				break;

			case LogLevel::INFO:
				$string = "INFO    |";
				break;

			default:
				$string = "DEBUG   |";
				break;
		}

		$string .= gmdate('Ymd H:i:s') . "|$message\r\n";

		@fwrite($this->fp, $string);
	}

	/**
	 * Calculates the absolute path to the log file
	 *
	 * @param   string  $tag  The backup run's tag
	 *
	 * @return    string    The absolute path to the log file
	 */
	public function getLogFilename($tag = null, $extension = '.php')
	{
		if (empty($tag))
		{
			$fileName = 'akeeba.log' . $extension;
		}
		else
		{
			$fileName = "akeeba.$tag.log" . $extension;
		}

		// Get output directory
		$registry        = Factory::getConfiguration();
		$outputDirectory = $registry->get('akeeba.basic.output_directory');

		// Get the log file name
		$absoluteLogFilename = Factory::getFilesystemTools()->TranslateWinPath($outputDirectory . DIRECTORY_SEPARATOR . $fileName);

		return $absoluteLogFilename;
	}

	/**
	 * Close the currently active log and set the current tag to null.
	 *
	 * @return  void
	 */
	public function close()
	{
		// The log file changed. Close the old log.
		if (is_resource($this->fp))
		{
			@fclose($this->fp);
		}

		$this->fp         = null;
		$this->currentTag = null;
	}

	/**
	 * Open a new log instance with the specified tag. If another log is already open it is closed before switching to
	 * the new log tag. If the tag is null use the default log defined in the logging system.
	 *
	 * @param   string|null  $tag  The log to open
	 *
	 * @return void
	 */
	public function open($tag = null, $extension = '.php')
	{
		// If the log is already open do nothing
		if (is_resource($this->fp) && ($tag == $this->currentTag))
		{
			return;
		}

		// If another log is open, close it
		if (is_resource($this->fp))
		{
			$this->close();
		}

		// Re-initialise site root and minimum log level since the active profile might have changed in the meantime
		$this->initialiseWithProfileParameters();

		// Set the current tag
		$this->currentTag = $tag;

		// Get the log filename
		$this->logName = $this->getLogFilename($tag, $extension);

		// Touch the file
		@touch($this->logName);

		// Open the log file. DO NOT USE APPEND ('ab') MODE. I NEED TO SEEK INTO THE FILE. SEE FURTHER BELOW!
		$this->fp = @fopen($this->logName, 'c');

		// If we couldn't open the file set the file pointer to null
		if ($this->fp === false)
		{
			$this->fp = null;

			return;
		}

		// Go to the end of the file, emulating append mode. DO NOT REPLACE THE fopen() FILE MODE!
		if (@fseek($this->fp, 0, SEEK_END) === -1)
		{
			@fclose($this->fp);
			@unlink($this->logName);

			$this->fp = null;

			return;
		}

		/**
		 * The following sounds pretty stupid but there is a reason for that convoluted code.
		 *
		 * Some hosts, like WP Engine, will now allow you to write to a log file with a .php extension. The code below
		 * tries to anticipate that when the log extension is .php. It will try to write to the *.log.php file and the
		 * text is actually resembling PHP code. Hosts like WP Engine will fail the fwrite() which will cause this
		 * method to terminate early and return a null pointer. Our code will catch this case and try to use a .log
		 * extension as a safe fallback.
		 */
		if ($extension !== '.php')
		{
			return;
		}

		// Try to write something into the file
		$written = @fwrite($this->fp, '<?php die("test"); ?>' . "\n");

		if ($written === false)
		{
			@fclose($this->fp);
			@unlink($this->logName);

			$this->fp = null;

			$this->open($tag, '');

			return;
		}

		// Store truncate offset, we will have to rewind the internal pointer to it
		$truncate_point = ftell($this->fp) - $written;

		if (ftruncate($this->fp, $truncate_point) === false)
		{
			@fclose($this->fp);
			@unlink($this->logName);

			$this->fp = null;

			$this->open($tag, '');

			return;
		}

		// Finally, move the file pointer at the truncation point. Otherwise PHP will append NULL bytes to the string
		// to "pad" the file length to the internal file pointer. No need to check if the operation was successful,
		// worst case scenario we will have some extra NULL bytes, there's no need to kill the log operation
		@fseek($this->fp, $truncate_point);
	}

	/**
	 * Temporarily pause log output. The log() method MUST respect this.
	 *
	 * @return  void
	 */
	public function pause()
	{
		$this->paused = true;
	}

	/**
	 * Resume the previously paused log output. The log() method MUST respect this.
	 *
	 * @return  void
	 */
	public function unpause()
	{
		$this->paused = false;
	}

	/**
	 * Returns the timestamp (in UNIX time long integer format) of the last log message written to the log with the
	 * specific tag. The timestamp MUST be read from the log itself, not from the logger object. It is used by the
	 * engine to find out the age of stalled backups which may have crashed.
	 *
	 * @param   string|null  $tag  The log tag for which the last timestamp is returned
	 *
	 * @return  int|null  The timestamp of the last log message, in UNIX time. NULL if we can't get the timestamp.
	 */
	public function getLastTimestamp($tag = null)
	{
		$fileName = $this->getLogFilename($tag);

		/**
		 * The log file akeeba.tag.log.php may not exist but the akeeba.tag.log does. This would be the case in some bad
		 * hosts, like WPEngine, which do not allow us to create .php files EVEN THOUGH that's the only way to ensure
		 * the privileged information in the log file is not readable over the web. You can't fix bad hosts, you can
		 * only work around them.
		 */
		if (!@file_exists($fileName) && @file_exists(substr($fileName, 0, -4)))
		{
			$fileName = substr($fileName, 0, -4);
		}

		$timestamp = @filemtime($fileName);

		if ($timestamp === false)
		{
			return null;
		}

		return $timestamp;
	}

	/**
	 * System is unusable.
	 *
	 * @param   string  $message
	 * @param   array   $context
	 *
	 * @return void
	 */
	public function emergency($message, array $context = [])
	{
		$this->log(LogLevel::EMERGENCY, $message, $context);
	}

	/**
	 * Action must be taken immediately.
	 *
	 * Example: Entire website down, database unavailable, etc. This should
	 * trigger the SMS alerts and wake you up.
	 *
	 * @param   string  $message
	 * @param   array   $context
	 *
	 * @return void
	 */
	public function alert($message, array $context = [])
	{
		$this->log(LogLevel::ALERT, $message, $context);
	}

	/**
	 * Critical conditions.
	 *
	 * Example: Application component unavailable, unexpected exception.
	 *
	 * @param   string  $message
	 * @param   array   $context
	 *
	 * @return void
	 */
	public function critical($message, array $context = [])
	{
		$this->log(LogLevel::CRITICAL, $message, $context);
	}

	/**
	 * Runtime errors that do not require immediate action but should typically
	 * be logged and monitored.
	 *
	 * @param   string  $message
	 * @param   array   $context
	 *
	 * @return void
	 */
	public function error($message, array $context = [])
	{
		$this->log(LogLevel::ERROR, $message, $context);
	}

	/**
	 * \Exceptional occurrences that are not errors.
	 *
	 * Example: Use of deprecated APIs, poor use of an API, undesirable things
	 * that are not necessarily wrong.
	 *
	 * @param   string  $message
	 * @param   array   $context
	 *
	 * @return void
	 */
	public function warning($message, array $context = [])
	{
		$this->log(LogLevel::WARNING, $message, $context);
	}

	/**
	 * Normal but significant events.
	 *
	 * @param   string  $message
	 * @param   array   $context
	 *
	 * @return void
	 */
	public function notice($message, array $context = [])
	{
		$this->log(LogLevel::NOTICE, $message, $context);
	}

	/**
	 * Interesting events.
	 *
	 * Example: User logs in, SQL logs.
	 *
	 * @param   string  $message
	 * @param   array   $context
	 *
	 * @return void
	 */
	public function info($message, array $context = [])
	{
		$this->log(LogLevel::INFO, $message, $context);
	}

	/**
	 * Detailed debug information.
	 *
	 * @param   string  $message
	 * @param   array   $context
	 *
	 * @return void
	 */
	public function debug($message, array $context = [])
	{
		$this->log(LogLevel::DEBUG, $message, $context);
	}

	/**
	 * Initialise the logger properties with parameters from the backup profile and the platform
	 *
	 * @return  void
	 */
	protected function initialiseWithProfileParameters()
	{
		// Get the site's translated and untranslated root
		$this->site_root_untranslated = Platform::getInstance()->get_site_root();
		$this->site_root              = Factory::getFilesystemTools()->TranslateWinPath($this->site_root_untranslated);

		// Load the registry and fetch log level
		$registry                 = Factory::getConfiguration();
		$this->configuredLoglevel = $registry->get('akeeba.basic.log_level');
		$this->configuredLoglevel = $this->configuredLoglevel * 1;
	}
}
PK�!]�Siz�-�-#BackupEngine/Util/ListingParser.phpnu&1i�<?php
/**
 * Akeeba Engine
 *
 * @package   akeebaengine
 * @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License version 3, or later
 *
 * 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, version 3.
 *
 * 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
 * <https://www.gnu.org/licenses/>.
 */

namespace Akeeba\Engine\Util;

defined('AKEEBAENGINE') || die();

/**
 * Parses directory listings of the standard UNIX or MS-DOS style, i.e. what is most commonly returned by FTP and SFTP
 * servers running on *NIX and Windows machines.
 *
 * This class is intended to be used with the result RemoteResourceInterface::getRawList, parsing the raw folder listing
 * returned by an (S)FTP server -meant to be read by a human- into something you can programmatically work with. Using
 * RemoteResourceInterface::getWrapperStringFor with DirectoryIterator is generally preferable, if only much slower due
 * to the synchronous nature of remote stat() requests on each iterated element.
 */
class ListingParser
{
	/**
	 * Parse a UNIX- or MS-DOS-style directory listing.
	 *
	 * You get a hash array with entries. Each entry has the following keys:
	 * name:    the file / folder name.
	 * type:    file, dir or link.
	 * target:  link target (when type == link).
	 * user:    owner user, numeric or text. IIS FTP fakes this with the literal string "owner".
	 * group:   owner group, numeric or text. IIS FTP fakes this with the literal string "group".
	 * size:    size in bytes; note that some Linux servers report non-zero sizes for directories.
	 * date:    file creation date, most likely blatantly wrong; see below
	 * perms:   permissions in decimal format. Cast with dec2oct to get the 4 digit permissions string, e.g. 1755
	 *
	 * Important Notes
	 *
	 * Some UNIX systems report a size for directories. Do not assume that something is a directory if it's size is 0,
	 * you will be surprised. Look at the 'type' element instead.
	 *
	 * Dates can be off. UNIX-style directory listings state either the year or the time, not both. If the file was
	 * modified during this year you will get a date with a resolution of 1 minute. If the file was modified on a
	 * different year you'll get a date with a resolution of 1 day. MS-DOS listings always contain the time. Again, the
	 * resolution is 1 minute.
	 *
	 * Most MS-DOS style listings don't list junctions and symlinks. As a result they will be reported as regular
	 * directories / files. This is the case for the IIS FTP server. Other servers may return the raw "dir" command
	 * results (as CMD.EXE would parse it) in which case links and link targets do get reported.
	 *
	 * @param   string  $list   The raw listing
	 * @param   bool    $quick  True to only include name, type, size and link target for each file.
	 *
	 * @return  array
	 */
	public function parseListing($list, $quick = false)
	{
		$res = $this->parseUnixListing($list, $quick);

		if (empty($res))
		{
			$res = $this->parseMSDOSListing($list, $quick);
		}

		return $res;
	}

	/**
	 * Parse a UNIX-style directory listing. This is the format produced by ls -la on *NIX systems.
	 *
	 * You get a hash array with entries. Each entry has the following keys:
	 * name: the file / folder name.
	 * type: file, dir or link.
	 * target: link target (when type == link).
	 * user: owner user, numeric or text. IIS FTP fakes this with the literal string "owner".
	 * group: owner group, numeric or text. IIS FTP fakes this with the literal string "group".
	 * size: size in bytes; note that some Linux servers report non-zero sizes for directories.
	 * date: file creation date, most likely blatantly wrong; see below
	 * perms: permissions in decimal format. Cast with dec2oct to get the 4 digit permissions string, e.g. 1755
	 *
	 * @param   string  $list   The raw listing
	 * @param   bool    $quick  True to only include name, type, size and link target for each file.
	 *
	 * @return  array
	 */
	protected function parseUnixListing($list, $quick = false)
	{
		$ret = [];

		$list = str_replace(["\r\n", "\r", "\n\n"], ["\n", "\n", "\n"], $list);
		$list = explode("\n", $list);
		$list = array_map('rtrim', $list);

		foreach ($list as $v)
		{
			$vInfo = preg_split("/[\s]+/", $v, 9);

			if ((is_array($vInfo) || $vInfo instanceof \Countable ? count($vInfo) : 0) != 9)
			{
				continue;
			}

			$entry = [
				'name'   => '',
				'type'   => 'file',
				'target' => '',
				'user'   => '0',
				'group'  => '0',
				'size'   => '0',
				'date'   => '0',
				'perms'  => '0',
			];

			if ($quick)
			{
				$entry = [
					'name'   => '',
					'type'   => 'file',
					'size'   => '0',
					'target' => '',
				];
			}

			// ===== Parse permissions =====
			$permString    = $vInfo[0];
			$permStringLen = strlen($permString);
			$typeBit       = '-';
			$userPerms     = 'r--';
			$groupPerms    = 'r--';
			$otherPerms    = 'r--';

			if ($permStringLen)
			{
				$typeBit = substr($permString, 0, 1);
			}

			switch ($typeBit)
			{
				case "d":
					$entry['type'] = 'dir';
					break;

				case "l":
					$entry['type'] = 'link';
					break;
			}

			// ===== Parse size =====
			$entry['size'] = $vInfo[4];

			if (!$quick)
			{
				if ($permStringLen >= 4)
				{
					$userPerms = substr($permString, 1, 3);
				}

				if ($permStringLen >= 7)
				{
					$groupPerms = substr($permString, 4, 3);
				}

				if ($permStringLen >= 10)
				{
					$otherPerms = substr($permString, 7, 3);
				}

				$bitPart   = 0;
				$permsPart = '';

				[$thisPerms, $thisBit] = $this->textPermsDecode($userPerms);
				$bitPart   += 4 * $thisBit; // SetUID
				$permsPart .= $thisPerms;

				[$thisPerms, $thisBit] = $this->textPermsDecode($groupPerms);
				$bitPart   += 2 * $thisBit; // SetGID
				$permsPart .= $thisPerms;

				[$thisPerms, $thisBit] = $this->textPermsDecode($otherPerms);
				$bitPart   += $thisBit; // Sticky (restricted deletion)
				$permsPart .= $thisPerms;

				$entry['perms'] = octdec($bitPart . $permsPart);

				// ===== Parse ownership =====
				$entry['user']  = $vInfo[2];
				$entry['group'] = $vInfo[3];

				// ===== Parse date =====
				$dateString    = $vInfo[6] . ' ' . $vInfo[5] . ' ' . $vInfo[7];
				$x             = date_create($dateString);
				$entry['date'] = ($x === false) ? 0 : $x->getTimestamp();
			}

			// ===== Parse name =====
			$name = $vInfo[8];

			// Ubuntu (possibly others?) tacks a start when either suid/sgid bits is set
			if (substr($name, -1) == '*')
			{
				$name = substr($name, 0, -1);
			}

			// Link target parsing
			if (strpos($name, '->') !== false)
			{
				[$name, $target] = explode('->', $name);

				$entry['target'] = trim($target);
			}

			$entry['name'] = trim($name);

			// ===== Return the entry =====
			$ret[] = $entry;
		}

		return $ret;
	}

	/**
	 * Parse am MS-DOS-style directory listing. This is the format produced by dir on MS-DOS and Windows systems.
	 *
	 * You get a hash array with entries. Each entry has the following keys:
	 * name: the file / folder name.
	 * type: file, dir or link.
	 * target: link target (when type == link).
	 * user: owner user, numeric or text. IIS FTP fakes this with the literal string "owner".
	 * group: owner group, numeric or text. IIS FTP fakes this with the literal string "group".
	 * size: size in bytes; note that some Linux servers report non-zero sizes for directories.
	 * date: file creation date, most likely blatantly wrong; see below
	 * perms: permissions in decimal format. Cast with dec2oct to get the 4 digit permissions string, e.g. 1755
	 *
	 * @param   string  $list   The raw listing
	 * @param   bool    $quick  True to only include name, type, size and link target for each file.
	 *
	 * @return  array
	 */
	protected function parseMSDOSListing($list, $quick = false)
	{
		$ret = [];

		$list = str_replace(["\r\n", "\r", "\n\n"], ["\n", "\n", "\n"], $list);
		$list = explode("\n", $list);
		$list = array_map('rtrim', $list);

		foreach ($list as $v)
		{
			$vInfo = preg_split("/[\s]+/", $v, 5);

			if ((is_array($vInfo) || $vInfo instanceof \Countable ? count($vInfo) : 0) < 4)
			{
				continue;
			}

			$entry = [
				'name'   => '',
				'type'   => 'file',
				'target' => '',
				'user'   => '0',
				'group'  => '0',
				'size'   => '0',
				'date'   => '0',
				'perms'  => '0',
			];

			if ($quick)
			{
				$entry = [
					'name'   => '',
					'type'   => 'file',
					'size'   => '0',
					'target' => '',
				];
			}

			// The first two fields are date and time
			$dateString = $vInfo[0] . ' ' . $vInfo[1];

			// If position 2 is AM/PM append it and remove it from the list
			if (in_array(strtoupper($vInfo[2]), ['AM', 'PM']))
			{
				$dateString .= ' ' . $vInfo[2];

				// This trick is required to remove the element and fix the indices for the rest of the parsing to work.
				unset ($vInfo[2]);
				$vInfo = array_merge($vInfo);
			}

			if (!$quick)
			{
				$x             = date_create($dateString);
				$entry['date'] = ($x === false) ? 0 : $x->getTimestamp();
			}

			// The third field is either a special type indicator or the file size
			switch (strtoupper($vInfo[2]))
			{
				// Regular directory
				case '<DIR>':
					$entry['type'] = 'dir';
					break;

				// Junction (like a directory symlink, pre-Win7)
				case '<JUNCTION>':
					// File symlink
				case '<SYMLINK>':
					// Directory symlink
				case '<SYMLINKD>':
					$entry['type'] = 'link';
					break;

				default:
					$entry['size'] = (int) $vInfo[2];
					break;
			}

			// And finally the file name. If it's a link it's in the format 'name [target]'
			preg_match('/(.*)[\s]+\[(.*)\]/', $vInfo[3], $matches);

			if (empty($matches))
			{
				$entry['name'] = $vInfo[3];
			}
			else
			{
				$entry['type']   = 'link';
				$entry['name']   = $matches[1];
				$entry['target'] = $matches[2];
			}

			// ===== Return the entry =====
			$ret[] = $entry;
		}

		return $ret;
	}

	/**
	 * Decode a textual permissions representation for a user, group or others to a pair of octal digits (permissions
	 * and flags). For example "r--" is converted to [4, 0], "r-x" to [5, 0], "r-t" to [5, 1]
	 *
	 * @param   string  $perms  The textual permissions representation for a user, group or others
	 *
	 * @return  array  Two octal digits for permissions and flags (suid/sgid/sticky bit)
	 */
	private function textPermsDecode($perms)
	{
		$permBit  = 0;
		$flagBits = 0;

		if (strpos($perms, 'r'))
		{
			$permBit += 4;
		}

		if (strpos($perms, 'w'))
		{
			$permBit += 2;
		}

		/**
		 * Both s and t denote flag set and imply the execute permissions is also granted. For user/groups it's
		 * SetUID/SetGID respectively, for others it's the "sticky" bit (restricted deletion). Since only one of x, s
		 * and t can be present at one time we use an if/elseif block. I don't use a switch because a. I am not 100%
		 * sure that all servers will report the text permissions in rwx order and b. I am not sure that switch and
		 * substr are faster than strpos (and too lazy to benchmark; sorry).
		 */
		if (strpos($perms, 'x'))
		{
			$permBit += 1;
		}
		elseif (strpos($perms, 't'))
		{
			$flagBits += 1;
		}
		elseif (strpos($perms, 's'))
		{
			$flagBits += 1;
		}

		return [$permBit, $flagBits];
	}
}
PK�!]���9�9(BackupEngine/Util/ConfigurationCheck.phpnu&1i�<?php
/**
 * Akeeba Engine
 *
 * @package   akeebaengine
 * @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License version 3, or later
 *
 * 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, version 3.
 *
 * 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
 * <https://www.gnu.org/licenses/>.
 */

namespace Akeeba\Engine\Util;

defined('AKEEBAENGINE') || die();

use Akeeba\Engine\Factory;
use Akeeba\Engine\Platform;

/**
 * Quirk detection helper class
 */
class ConfigurationCheck
{
	/**
	 * The configuration checks to perform
	 *
	 * @var  array
	 */
	protected $configurationChecks = [
		['code'        => '001', 'severity' => 'critical', 'callback' => [null, 'q001'],
		 'description' => 'COM_AKEEBA_CPANEL_WARNING_Q001',
		],
		['code'        => '003', 'severity' => 'critical', 'callback' => [null, 'q003'],
		 'description' => 'COM_AKEEBA_CPANEL_WARNING_Q003',
		],
		['code'        => '004', 'severity' => 'critical', 'callback' => [null, 'q004'],
		 'description' => 'COM_AKEEBA_CPANEL_WARNING_Q004',
		],

		['code'        => '101', 'severity' => 'high', 'callback' => [null, 'q101'],
		 'description' => 'COM_AKEEBA_CPANEL_WARNING_Q101',
		],
		['code'        => '103', 'severity' => 'high', 'callback' => [null, 'q103'],
		 'description' => 'COM_AKEEBA_CPANEL_WARNING_Q103',
		],
		['code'        => '104', 'severity' => 'high', 'callback' => [null, 'q104'],
		 'description' => 'COM_AKEEBA_CPANEL_WARNING_Q104',
		],
		['code'        => '106', 'severity' => 'high', 'callback' => [null, 'q106'],
		 'description' => 'COM_AKEEBA_CPANEL_WARNING_Q106',
		],

		['code'        => '201', 'severity' => 'medium', 'callback' => [null, 'q201'],
		 'description' => 'COM_AKEEBA_CPANEL_WARNING_Q201',
		],
		['code'        => '202', 'severity' => 'medium', 'callback' => [null, 'q202'],
		 'description' => 'COM_AKEEBA_CPANEL_WARNING_Q202',
		],
		['code'        => '204', 'severity' => 'medium', 'callback' => [null, 'q204'],
		 'description' => 'COM_AKEEBA_CPANEL_WARNING_Q204',
		],

		['code'        => '203', 'severity' => 'medium', 'callback' => [null, 'q203'],
		 'description' => 'COM_AKEEBA_CPANEL_WARNING_Q203',
		],
//		['code'        => '401', 'severity' => 'low', 'callback' => [null, 'q401'],
//		 'description' => 'COM_AKEEBA_CPANEL_WARNING_Q401',
//		],
	];

	/**
	 * The public constructor replaces the missing object reference in the configuration check callbacks
	 */
	function __construct()
	{
		$temp = [];

		foreach ($this->configurationChecks as $check)
		{
			$check['callback'] = [$this, $check['callback'][1]];
			$temp[]            = $check;
		}

		$this->configurationChecks = $temp;
	}

	/**
	 * Returns the output & temporary folder writable status
	 *
	 * @return  array  A hash array with the writable status
	 */
	public function getFolderStatus()
	{
		static $status = null;

		if (is_null($status))
		{
			$stock_dirs = Platform::getInstance()->get_stock_directories();

			// Get output writable status
			$registry = Factory::getConfiguration();
			$outdir   = $registry->get('akeeba.basic.output_directory');

			foreach ($stock_dirs as $macro => $replacement)
			{
				$outdir = str_replace($macro, $replacement, $outdir);
			}

			$status['output'] = @is_writable($outdir);
		}

		return $status;
	}

	/**
	 * Returns the overall status. It's true when both the temporary and output directories are writable and there are
	 * no critical configuration check failures.
	 *
	 * @return  boolean
	 */
	public function getShortStatus()
	{
		// Base the status on directory writeable status
		$status = $this->getFolderStatus();
		$ret    = $status['output'];

		// Scan for high severity configuration check errors
		$detailedStatus = $this->getDetailedStatus();

		if (!empty($detailedStatus))
		{
			foreach ($detailedStatus as $configCheck)
			{
				if ($configCheck['severity'] == 'critical')
				{
					$ret = false;
				}
			}
		}

		// Return status
		return $ret;
	}

	/**
	 * Add a configuration check definition
	 *
	 * @param   string  $code         The configuration check code (three digit number)
	 * @param   string  $severity     The severity (low, medium, high, critical)
	 * @param   string  $description  The description key for this configuration check
	 * @param   null    $callback     The callback used to determine the status of the configuration check
	 *
	 * @return  void
	 */
	public function addConfigurationCheckDefinition($code, $severity = 'low', $description = null, $callback = null)
	{
		if (!is_callable($callback))
		{
			$callback = [$this, 'q' . $code];
		}

		if (empty($description))
		{
			$description = 'COM_AKEEBA_CPANEL_WARNING_Q' . $code;
		}

		$newConfigurationCheck = [
			'code'        => $code,
			'severity'    => $severity,
			'description' => $description,
			'callback'    => $callback,
		];

		$this->configurationChecks[$code] = $newConfigurationCheck;
	}

	/**
	 * Remove a configuration check definition
	 *
	 * @param   string  $code  The code of the configuration check to remove
	 *
	 * @return  void
	 */
	public function removeConfigurationCheckDefinition($code)
	{
		if (isset($this->configurationChecks[$code]))
		{
			unset($this->configurationChecks[$code]);
		}
	}

	/**
	 * Clear the configuration check definitions
	 *
	 * @return  void
	 */
	public function clearConfigurationCheckDefinitions()
	{
		$this->configurationChecks = [];
	}

	/**
	 * Runs the configuration check scripts. These are potential problems related to server
	 * configuration, out of Akeeba's control. They are intended to give the user a
	 * chance to fix them before they cause the backup to fail.
	 *
	 * Numbering scheme:
	 * Q0xx    No-go errors
	 * Q1xx    Critical system configuration errors
	 * Q2xx    Medium and low system configuration warnings
	 * Q3xx    Critical software configuration errors
	 * Q4xx    Medium and low component configuration warnings
	 *
	 * @param   boolean  $low_priority       Should I include low priority quirks?
	 * @param   string   $help_url_template  The sprintf template from creating a help URL from a config check code
	 *
	 * @return  array
	 */
	public function getDetailedStatus($low_priority = false, $help_url_template = 'https://www.akeeba.com/documentation/warnings/q%s.html')
	{
		static $detailedStatus = null;

		if (is_null($detailedStatus) || $low_priority)
		{
			$detailedStatus = [];

			foreach ($this->configurationChecks as $quirkDef)
			{
				if (!$low_priority && ($quirkDef['severity'] == 'low'))
				{
					continue;
				}

				$this->checkConfiguration($detailedStatus, $quirkDef, $help_url_template);
			}
		}

		return $detailedStatus;
	}

	/**
	 * Checks if a path is restricted by open_basedirs
	 *
	 * @param   string  $check  The path to check
	 *
	 * @return  bool  True if the path is restricted (which is bad)
	 */
	public function checkOpenBasedirs($check)
	{
		static $paths;

		if (empty($paths))
		{
			$open_basedir = ini_get('open_basedir');

			if (empty($open_basedir))
			{
				return false;
			}

			$delimiter  = strpos($open_basedir, ';') !== false ? ';' : ':';
			$paths_temp = explode($delimiter, $open_basedir);

			// Some open_basedirs are using environemtn variables
			$paths = [];

			foreach ($paths_temp as $path)
			{
				if (array_key_exists($path, $_ENV))
				{
					$paths[] = $_ENV[$path];
				}
				else
				{
					$paths[] = $path;
				}
			}
		}

		if (empty($paths))
		{
			return false; // no restrictions
		}
		else
		{
			$newcheck = @realpath($check); // Resolve symlinks, like PHP does

			if (!($newcheck === false))
			{
				$check = $newcheck;
			}

			$included = false;

			foreach ($paths as $path)
			{
				$newpath = @realpath($path);

				if (!($newpath === false))
				{
					$path = $newpath;
				}

				if (strlen($check) >= strlen($path))
				{
					// Only check if the path to check is longer than the inclusion path.
					// Otherwise, I guarantee it's not included!!
					// If the path to check begins with an inclusion path, it's permitted. Easy, huh?
					if (substr($check, 0, strlen($path)) == $path)
					{
						$included = true;
					}
				}
			}

			return !$included;
		}
	}

	/**
	 * Make a configuration check and adds it to the list if it raises a warning / error
	 *
	 * @param   array   $detailedStatus     The configuration checks status array
	 * @param   array   $quirkDef           The configuration check definition
	 * @param   string  $help_url_template  The sprintf template from creating a help URL from a quirk code
	 *
	 * @return  void
	 */
	protected function checkConfiguration(&$detailedStatus, $quirkDef, $help_url_template)
	{
		if (call_user_func($quirkDef['callback']))
		{
			$description = Platform::getInstance()->translate($quirkDef['description']);

			$detailedStatus[(string) $quirkDef['code']] = [
				'code'        => $quirkDef['code'],
				'severity'    => $quirkDef['severity'],
				'description' => $description,
				'help_url'    => sprintf($help_url_template, $quirkDef['code']),
			];
		}
	}

	/**
	 * Q001 - HIGH - Output directory unwriteable
	 *
	 * @return  bool
	 */
	private function q001()
	{
		$status = $this->getFolderStatus();

		return !$status['output'];
	}

	/**
	 * Q003 - HIGH - Backup output or temporary set to site's root
	 *
	 * @return  bool
	 */
	private function q003()
	{
		$stock_dirs = Platform::getInstance()->get_stock_directories();

		$registry = Factory::getConfiguration();
		$outdir   = $registry->get('akeeba.basic.output_directory');

		foreach ($stock_dirs as $macro => $replacement)
		{
			$outdir = str_replace($macro, $replacement, $outdir);
		}

		$outdir_real = @realpath($outdir);

		if (!empty($outdir_real))
		{
			$outdir = $outdir_real;
		}

		$siteroot      = Platform::getInstance()->get_site_root();
		$siteroot_real = @realpath($siteroot);

		if (!empty($siteroot_real))
		{
			$siteroot = $siteroot_real;
		}

		return ($siteroot == $outdir);
	}

	/**
	 * Q004 - HIGH - Free memory too low
	 *
	 * @return bool
	 */
	private function q004()
	{
		// If we can't figure this out, don't report a problem. It doesn't
		// really matter, as the backup WILL crash eventually.
		if (!function_exists('ini_get'))
		{
			return false;
		}

		$memLimit = ini_get("memory_limit");
		$memLimit = $this->_return_bytes($memLimit);

		if ($memLimit <= 0)
		{
			return false;
		}

		// No limit?
		$availableRAM = $memLimit - memory_get_usage();

		// We need at least 12Mb of free memory
		return ($availableRAM <= (12 * 1024 * 1024));
	}

	/**
	 * Q101 - HIGH - open_basedir on output directory
	 *
	 * @return  bool
	 */
	private function q101()
	{
		$stock_dirs = Platform::getInstance()->get_stock_directories();

		// Get output writable status
		$registry = Factory::getConfiguration();
		$outdir   = $registry->get('akeeba.basic.output_directory');

		foreach ($stock_dirs as $macro => $replacement)
		{
			$outdir = str_replace($macro, $replacement, $outdir);
		}

		return $this->checkOpenBasedirs($outdir);
	}

	/**
	 * Q103 - HIGH - Less than 10" of max_execution_time with PHP Safe Mode enabled
	 *
	 * @return  bool
	 */
	private function q103()
	{
		$exectime = ini_get('max_execution_time');
		$safemode = ini_get('safe_mode');

		if (!$safemode)
		{
			return false;
		}

		if (!is_numeric($exectime))
		{
			return false;
		}

		if ($exectime <= 0)
		{
			return false;
		}

		return $exectime < 10;
	}

	/**
	 * Q104 - HIGH - Temp directory is the same as the site's root
	 *
	 * @return  bool
	 */
	private function q104()
	{

		$siteroot      = Platform::getInstance()->get_site_root();
		$siteroot_real = @realpath($siteroot);

		if (!empty($siteroot_real))
		{
			$siteroot = $siteroot_real;
		}

		$stockDirs      = Platform::getInstance()->get_stock_directories();
		$temp_directory = $stockDirs['[SITETMP]'];
		$temp_directory = @realpath($temp_directory);

		if (empty($temp_directory))
		{
			$temp_directory = $siteroot;
		}

		return ($siteroot == $temp_directory);
	}

	/**
	 * Q106 - HIGH  - Table name prefix contains uppercase characters
	 *
	 * @return  bool
	 */
	private function q106()
	{
		$filters   = Factory::getFilters();
		$databases = $filters->getInclusions('db');

		foreach ($databases as $db)
		{
			if (!isset($db['prefix']))
			{
				continue;
			}

			if (preg_match('/[A-Z]/', $db['prefix']))
			{
				return true;
			}
		}

		return false;
	}

	/**
	 * Q201 - MEDIUM - Outdated PHP version.
	 *
	 * We currently check for PHP lower than 8.0.
	 *
	 * @return  bool
	 */
	private function q201()
	{
		return version_compare(PHP_VERSION, '8.0.0', 'lt');
	}

	/**
	 * Q202 - MED  - CRC problems with hash extension not present
	 *
	 * @return  bool
	 */
	private function q202()
	{
		$registry = Factory::getConfiguration();
		$archiver = $registry->get('akeeba.advanced.archiver_engine');

		if ($archiver != 'zip')
		{
			return false;
		}

		return !function_exists('hash_file');
	}

	/**
	 * Q203 - MED  - Default output directory in use
	 *
	 * @return  bool
	 */
	private function q203()
	{
		$stock_dirs = Platform::getInstance()->get_stock_directories();

		$registry = Factory::getConfiguration();
		$outdir   = $registry->get('akeeba.basic.output_directory');

		foreach ($stock_dirs as $macro => $replacement)
		{
			$outdir = str_replace($macro, $replacement, $outdir);
		}

		$default = $stock_dirs['[DEFAULT_OUTPUT]'];

		$outdir  = Factory::getFilesystemTools()->TranslateWinPath($outdir);
		$default = Factory::getFilesystemTools()->TranslateWinPath($default);

		return $outdir == $default;
	}

	/**
	 * Q204 - MED  - Disabled functions may affect operation
	 *
	 * @return  bool
	 */
	private function q204()
	{
		$disabled = ini_get('disabled_functions');

		return (!empty($disabled));
	}

	/**
	 * Q401 - LOW  - ZIP format selected
	 *
	 * @return  bool
	 */
	private function q401()
	{
		$registry = Factory::getConfiguration();
		$archiver = $registry->get('akeeba.advanced.archiver_engine');

		return $archiver == 'zip';
	}

	private function _return_bytes($setting)
	{
		$val  = trim($setting);
		$last = strtolower(substr($val, -1));
		$val  = substr($val, 0, -1);

		if (is_numeric($last))
		{
			return $setting;
		}

		switch ($last)
		{
			case 't':
				$val *= 1024;
			case 'g':
				$val *= 1024;
			case 'm':
				$val *= 1024;
			case 'k':
				$val *= 1024;
		}

		return (int) $val;
	}
}
PK�!]�-�X��1BackupEngine/Util/AesAdapter/AdapterInterface.phpnu&1i�<?php
/**
 * Akeeba Engine
 *
 * @package   akeebaengine
 * @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License version 3, or later
 *
 * 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, version 3.
 *
 * 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
 * <https://www.gnu.org/licenses/>.
 */

namespace Akeeba\Engine\Util\AesAdapter;

defined('AKEEBAENGINE') || die();

/**
 * Interface for AES encryption adapters
 */
interface AdapterInterface
{
	/**
	 * Sets the AES encryption mode.
	 *
	 * WARNING: The strength is deprecated as it has a different effect in MCrypt and OpenSSL. MCrypt was abandoned in
	 * 2003 before the Rijndael-128 algorithm was officially the Advanced Encryption Standard (AES). MCrypt also offered
	 * Rijndael-192 and Rijndael-256 algorithms with different block sizes. These are NOT used in AES. OpenSSL, however,
	 * implements AES correctly. It always uses a 128-bit (16 byte) block. The 192 and 256 bit strengths refer to the
	 * key size, not the block size. Therefore using different strengths in MCrypt and OpenSSL will result in different
	 * and incompatible ciphertexts.
	 *
	 * TL;DR: Always use $strength = 128!
	 *
	 * @param   string  $mode      Choose between CBC (recommended) or ECB
	 * @param   int     $strength  Bit strength of the key (128, 192 or 256 bits). DEPRECATED. READ NOTES ABOVE.
	 *
	 * @return  mixed
	 */
	public function setEncryptionMode($mode = 'cbc', $strength = 128);

	/**
	 * Encrypts a string. Returns the raw binary ciphertext.
	 *
	 * WARNING: The plaintext is zero-padded to the algorithm's block size. You are advised to store the size of the
	 * plaintext and trim the string to that length upon decryption.
	 *
	 * @param   string       $plainText  The plaintext to encrypt
	 * @param   string       $key        The raw binary key (will be zero-padded or chopped if its size is different than the block size)
	 * @param   null|string  $iv         The initialization vector (for CBC mode algorithms)
	 *
	 * @return  string  The raw encrypted binary string.
	 */
	public function encrypt($plainText, $key, $iv = null);

	/**
	 * Decrypts a string. Returns the raw binary plaintext.
	 *
	 * $ciphertext MUST start with the IV followed by the ciphertext, even for EBC data (the first block of data is
	 * dropped in EBC mode since there is no concept of IV in EBC).
	 *
	 * WARNING: The returned plaintext is zero-padded to the algorithm's block size during encryption. You are advised
	 * to trim the string to the original plaintext's length upon decryption. While rtrim($decrypted, "\0") sounds
	 * appealing it's NOT the correct approach for binary data (zero bytes may actually be part of your plaintext, not
	 * just padding!).
	 *
	 * @param   string  $cipherText  The ciphertext to encrypt
	 * @param   string  $key         The raw binary key (will be zero-padded or chopped if its size is different than the block size)
	 *
	 * @return  string  The raw unencrypted binary string.
	 */
	public function decrypt($cipherText, $key);

	/**
	 * Returns the encryption block size in bytes
	 *
	 * @return  int
	 */
	public function getBlockSize();

	/**
	 * Is this adapter supported?
	 *
	 * @return  bool
	 */
	public function isSupported();
}
PK�!]n
_��
�
'BackupEngine/Util/AesAdapter/Mcrypt.phpnu&1i�<?php
/**
 * Akeeba Engine
 *
 * @package   akeebaengine
 * @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License version 3, or later
 *
 * 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, version 3.
 *
 * 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
 * <https://www.gnu.org/licenses/>.
 */

namespace Akeeba\Engine\Util\AesAdapter;

defined('AKEEBAENGINE') || die();

use Akeeba\Engine\Util\RandomValue;

class Mcrypt extends AbstractAdapter implements AdapterInterface
{
	protected $cipherType = MCRYPT_RIJNDAEL_128;

	protected $cipherMode = MCRYPT_MODE_CBC;

	public function setEncryptionMode($mode = 'cbc', $strength = 128)
	{
		switch ((int) $strength)
		{
			default:
			case '128':
				$this->cipherType = MCRYPT_RIJNDAEL_128;
				break;

			case '192':
				$this->cipherType = MCRYPT_RIJNDAEL_192;
				break;

			case '256':
				$this->cipherType = MCRYPT_RIJNDAEL_256;
				break;
		}

		switch (strtolower($mode))
		{
			case 'ecb':
				$this->cipherMode = MCRYPT_MODE_ECB;
				break;

			default:
			case 'cbc':
				$this->cipherMode = MCRYPT_MODE_CBC;
				break;
		}

	}

	public function encrypt($plainText, $key, $iv = null)
	{
		$iv_size = $this->getBlockSize();
		$key     = $this->resizeKey($key, $iv_size);
		$iv      = $this->resizeKey($iv, $iv_size);

		if (empty($iv))
		{
			$randVal = new RandomValue();
			$iv      = $randVal->generate($iv_size);
		}

		$cipherText = mcrypt_encrypt($this->cipherType, $key, $plainText, $this->cipherMode, $iv);
		$cipherText = $iv . $cipherText;

		return $cipherText;
	}

	public function decrypt($cipherText, $key)
	{
		$iv_size    = $this->getBlockSize();
		$key        = $this->resizeKey($key, $iv_size);
		$iv         = substr($cipherText, 0, $iv_size);
		$cipherText = substr($cipherText, $iv_size);
		$plainText  = mcrypt_decrypt($this->cipherType, $key, $cipherText, $this->cipherMode, $iv);

		return $plainText;
	}

	public function isSupported()
	{
		if (!function_exists('mcrypt_get_key_size'))
		{
			return false;
		}

		if (!function_exists('mcrypt_get_iv_size'))
		{
			return false;
		}

		if (!function_exists('mcrypt_create_iv'))
		{
			return false;
		}

		if (!function_exists('mcrypt_encrypt'))
		{
			return false;
		}

		if (!function_exists('mcrypt_decrypt'))
		{
			return false;
		}

		if (!function_exists('mcrypt_list_algorithms'))
		{
			return false;
		}

		if (!function_exists('hash'))
		{
			return false;
		}

		if (!function_exists('hash_algos'))
		{
			return false;
		}

		$algorightms = mcrypt_list_algorithms();

		if (!in_array('rijndael-128', $algorightms))
		{
			return false;
		}

		if (!in_array('rijndael-192', $algorightms))
		{
			return false;
		}

		if (!in_array('rijndael-256', $algorightms))
		{
			return false;
		}

		$algorightms = hash_algos();

		if (!in_array('sha256', $algorightms))
		{
			return false;
		}

		return true;
	}

	public function getBlockSize()
	{
		return mcrypt_get_iv_size($this->cipherType, $this->cipherMode);
	}
}
PK�!]ju,�	�	0BackupEngine/Util/AesAdapter/AbstractAdapter.phpnu&1i�<?php
/**
 * Akeeba Engine
 *
 * @package   akeebaengine
 * @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License version 3, or later
 *
 * 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, version 3.
 *
 * 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
 * <https://www.gnu.org/licenses/>.
 */

namespace Akeeba\Engine\Util\AesAdapter;

defined('AKEEBAENGINE') || die();

/**
 * Abstract AES encryption class
 */
abstract class AbstractAdapter
{
	/**
	 * Trims or zero-pads a key / IV
	 *
	 * @param   string  $key   The key or IV to treat
	 * @param   int     $size  The block size of the currently used algorithm
	 *
	 * @return  null|string  Null if $key is null, treated string of $size byte length otherwise
	 */
	public function resizeKey($key, $size)
	{
		if (empty($key))
		{
			return null;
		}

		$keyLength = strlen($key);

		if (function_exists('mb_strlen'))
		{
			$keyLength = mb_strlen($key, 'ASCII');
		}

		if ($keyLength == $size)
		{
			return $key;
		}

		if ($keyLength > $size)
		{
			if (function_exists('mb_substr'))
			{
				return mb_substr($key, 0, $size, 'ASCII');
			}

			return substr($key, 0, $size);
		}

		return $key . str_repeat("\0", ($size - $keyLength));
	}

	/**
	 * Returns null bytes to append to the string so that it's zero padded to the specified block size
	 *
	 * @param   string  $string     The binary string which will be zero padded
	 * @param   int     $blockSize  The block size
	 *
	 * @return  string  The zero bytes to append to the string to zero pad it to $blockSize
	 */
	protected function getZeroPadding($string, $blockSize)
	{
		$stringSize = strlen($string);

		if (function_exists('mb_strlen'))
		{
			$stringSize = mb_strlen($string, 'ASCII');
		}

		if ($stringSize == $blockSize)
		{
			return '';
		}

		if ($stringSize < $blockSize)
		{
			return str_repeat("\0", $blockSize - $stringSize);
		}

		$paddingBytes = $stringSize % $blockSize;

		return str_repeat("\0", $blockSize - $paddingBytes);
	}
}
PK�!]Qҙ�gg(BackupEngine/Util/AesAdapter/OpenSSL.phpnu&1i�<?php
/**
 * Akeeba Engine
 *
 * @package   akeebaengine
 * @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License version 3, or later
 *
 * 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, version 3.
 *
 * 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
 * <https://www.gnu.org/licenses/>.
 */

namespace Akeeba\Engine\Util\AesAdapter;

defined('AKEEBAENGINE') || die();

use Akeeba\Engine\Util\RandomValue;

class OpenSSL extends AbstractAdapter implements AdapterInterface
{
	/**
	 * The OpenSSL options for encryption / decryption
	 *
	 * @var  int
	 */
	protected $openSSLOptions = 0;

	/**
	 * The encryption method to use
	 *
	 * @var  string
	 */
	protected $method = 'aes-128-cbc';

	public function __construct()
	{
		$this->openSSLOptions = OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING;
	}

	public function setEncryptionMode($mode = 'cbc', $strength = 128)
	{
		static $availableAlgorithms = null;
		static $defaultAlgo = 'aes-128-cbc';

		if (!is_array($availableAlgorithms))
		{
			$availableAlgorithms = openssl_get_cipher_methods();

			foreach ([
				         'aes-256-cbc', 'aes-256-ecb', 'aes-192-cbc',
				         'aes-192-ecb', 'aes-128-cbc', 'aes-128-ecb',
			         ] as $algo)
			{
				if (in_array($algo, $availableAlgorithms))
				{
					$defaultAlgo = $algo;
					break;
				}
			}
		}

		$strength = (int) $strength;
		$mode     = strtolower($mode);

		if (!in_array($strength, [128, 192, 256]))
		{
			$strength = 256;
		}

		if (!in_array($mode, ['cbc', 'ebc']))
		{
			$mode = 'cbc';
		}

		$algo = 'aes-' . $strength . '-' . $mode;

		if (!in_array($algo, $availableAlgorithms))
		{
			$algo = $defaultAlgo;
		}

		$this->method = $algo;
	}

	public function encrypt($plainText, $key, $iv = null)
	{
		$iv_size = $this->getBlockSize();
		$key     = $this->resizeKey($key, $iv_size);
		$iv      = $this->resizeKey($iv, $iv_size);

		if (empty($iv))
		{
			$randVal = new RandomValue();
			$iv      = $randVal->generate($iv_size);
		}

		$plainText  .= $this->getZeroPadding($plainText, $iv_size);
		$cipherText = openssl_encrypt($plainText, $this->method, $key, $this->openSSLOptions, $iv);
		$cipherText = $iv . $cipherText;

		return $cipherText;
	}

	public function decrypt($cipherText, $key)
	{
		$iv_size    = $this->getBlockSize();
		$key        = $this->resizeKey($key, $iv_size);
		$iv         = substr($cipherText, 0, $iv_size);
		$cipherText = substr($cipherText, $iv_size);
		$plainText  = openssl_decrypt($cipherText, $this->method, $key, $this->openSSLOptions, $iv);

		return $plainText;
	}

	public function isSupported()
	{
		if (!function_exists('openssl_get_cipher_methods'))
		{
			return false;
		}

		if (!function_exists('openssl_random_pseudo_bytes'))
		{
			return false;
		}

		if (!function_exists('openssl_cipher_iv_length'))
		{
			return false;
		}

		if (!function_exists('openssl_encrypt'))
		{
			return false;
		}

		if (!function_exists('openssl_decrypt'))
		{
			return false;
		}

		if (!function_exists('hash'))
		{
			return false;
		}

		if (!function_exists('hash_algos'))
		{
			return false;
		}

		$algorightms = openssl_get_cipher_methods();

		if (!in_array('aes-128-cbc', $algorightms))
		{
			return false;
		}

		$algorightms = hash_algos();

		if (!in_array('sha256', $algorightms))
		{
			return false;
		}

		return true;
	}

	/**
	 * @return int
	 */
	public function getBlockSize()
	{
		return openssl_cipher_iv_length($this->method);
	}
}
PK�!]���LKKBackupEngine/Util/CRC32.phpnu&1i�<?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\Util;

defined('AKEEBAENGINE') || die();

use Akeeba\Engine\Factory;

/**
 * A handy class to abstract the calculation of CRC32 of files under various
 * server conditions and versions of PHP.
 */
class CRC32
{
	/**
	 * Returns the CRC32 of a file, selecting the more appropriate algorithm.
	 *
	 * @param   string   $filename                    Absolute path to the file being processed
	 * @param   integer  $AkeebaPackerZIP_CHUNK_SIZE  Obsoleted
	 *
	 * @return integer The CRC32 in numerical form
	 */
	public function crc32_file($filename, $AkeebaPackerZIP_CHUNK_SIZE)
	{
		static $configuration;

		if (!$configuration)
		{
			$configuration = Factory::getConfiguration();
		}

		$res = false;

		if (function_exists("hash_file"))
		{
			$res        = $this->crc32UsingHashExtension($filename);
			$calcMethod = 'HASH_FILE';
		}
		elseif (function_exists("file_get_contents") && (@filesize($filename) <= $AkeebaPackerZIP_CHUNK_SIZE))
		{
			$res        = $this->crc32Legacy($filename);
			$calcMethod = 'FILE_GET_CONTENTS';
		}
		else
		{
			$res        = 0;
			$calcMethod = 'FAKE - CANNOT CALCULATE';
		}

		if ($res === false)
		{
			$res = 0;

			Factory::getLog()->warning("File $filename - NOT READABLE: CRC32 IS WRONG!");
		}
		else
		{
			try
			{
				$humanReadable = dechex($res);
			}
			catch (\Throwable $e)
			{
			}

			if (($humanReadable ?? null) === null)
			{
				$temp = $res > 2147483647 ? -($res - 2147483648) : $res;
				$humanReadable = dechex($res);
			}

			Factory::getLog()->debug(sprintf("File %s - CRC32 = %s [%s]", $filename, $humanReadable ?? '(cannot display - you have a 32-bit version of PHP)', $calcMethod));
		}

		return $res;
	}

	/**
	 * Very efficient CRC32 calculation using the PHP 'hash' extension.
	 *
	 * @param   string  $filename  Absolute filepath
	 *
	 * @return integer The CRC32
	 */
	protected function crc32UsingHashExtension($filename)
	{
		// Detection of buggy PHP hosts
		static $mustInvert = null;

		if (is_null($mustInvert))
		{
			$test_crc   = @hash('crc32b', 'test', false);
			$mustInvert = (strtolower($test_crc) == '0c7e7fd8'); // Normally, it's D87F7E0C :)

			if ($mustInvert)
			{
				Factory::getLog()->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;
	}

	/**
	 * A compatible CRC32 calculation using file_get_contents, utilizing immense amounts of RAM
	 *
	 * @param   string  $filename
	 *
	 * @return integer
	 */
	protected function crc32Legacy($filename)
	{
		return crc32(@file_get_contents($filename));
	}
}
PK�!]ƈ���!BackupEngine/Util/RandomValue.phpnu&1i�<?php
/**
 * Akeeba Engine
 *
 * @package   akeebaengine
 * @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License version 3, or later
 *
 * 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, version 3.
 *
 * 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
 * <https://www.gnu.org/licenses/>.
 */

namespace Akeeba\Engine\Util;

defined('AKEEBAENGINE') || die();

/**
 * Crypto-safe random value generator. Based on the Randval class of the Aura for PHP's Session package.
 * The following is the license file accompanying the original file.
 *
 * ********************************************************************************
 * Copyright (c) 2011-2016, Aura for PHP
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * - Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 *
 * - Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 * ********************************************************************************
 *
 * Please note that this is a MODIFIED copy of the Randval class, mainly to allow it to be used on hosts
 * which lack both mbcrypt and OpenSSL PHP modules.
 */
class RandomValue
{
	/**
	 *
	 * Returns a cryptographically secure random value.
	 *
	 * @param   integer  $bytes  How many bytes to return
	 *
	 * @return  string
	 */
	public function generate($bytes = 32)
	{
		return random_bytes($bytes);
	}

	/**
	 * Generates a random string with the specified length. WARNING: You get to specify the number of
	 * random characters in the string, not the number of random bytes. The character pool is 64 characters
	 * (6 bits) long. The entropy of your string is 6 * $characters bits. This means that a random string
	 * of 32 characters has an entropy of 192 bits whereas a random sequence of 32 bytes returned by generate()
	 * has an entropy of 8 * 32 = 256 bits.
	 *
	 * @param   int    $characters    Number of characters
	 * @param   string $characterSet  Characters to pick from
	 *
	 * @return string
	 */
	public function generateString($characters = 32, $characterSet = 'abcdefghijklmnopqrstuvwxyz-ABCDEFGHIJKLMNOPQRSTUVWXYZ_0123456789')
	{
		$sourceString = str_split('abcdefghijklmnopqrstuvwxyz-ABCDEFGHIJKLMNOPQRSTUVWXYZ_0123456789', 1);
		$ret          = '';

		$bytes     = ceil($characters / 4) * 3;
		$randBytes = $this->generate($bytes);

		for ($i = 0; $i < $bytes; $i += 3)
		{
			$subBytes = substr($randBytes, $i, 3);
			$subBytes = str_split($subBytes, 1);
			$subBytes = ord($subBytes[0]) * 65536 + ord($subBytes[1]) * 256 + ord($subBytes[2]);
			$subBytes = $subBytes & bindec('00000000111111111111111111111111');

			$b    = [];
			$b[0] = $subBytes >> 18;
			$b[1] = ($subBytes >> 12) & bindec('111111');
			$b[2] = ($subBytes >> 6) & bindec('111111');
			$b[3] = $subBytes & bindec('111111');

			$ret .= $sourceString[$b[0]] . $sourceString[$b[1]] . $sourceString[$b[2]] . $sourceString[$b[3]];
		}

		return substr($ret, 0, $characters);
	}
}
PK�!]�"���BackupEngine/Util/Buffer.phpnu&1i�<?php
/**
 * Akeeba Engine
 *
 * @package   akeebaengine
 * @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License version 3, or later
 *
 * 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, version 3.
 *
 * 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
 * <https://www.gnu.org/licenses/>.
 */

namespace Akeeba\Engine\Util;

defined('AKEEBAENGINE') || die();

/**
 * Generic Buffer stream handler
 *
 * This class provides a generic buffer stream.  It can be used to store/retrieve/manipulate
 * string buffers with the standard PHP filesystem I/O methods.
 */
class Buffer
{

	/**
	 * Stream position
	 *
	 * @var    integer
	 */
	public $position = 0;

	/**
	 * Buffer name
	 *
	 * @var    string
	 */
	public $name = null;

	/**
	 * Buffer hash
	 *
	 * @var    array
	 */
	public $_buffers = [];

	/**
	 * Function to open file or url
	 *
	 * @param   string   $path          The URL that was passed
	 * @param   string   $mode          Mode used to open the file @see fopen
	 * @param   integer  $options       Flags used by the API, may be STREAM_USE_PATH and
	 *                                  STREAM_REPORT_ERRORS
	 * @param   string  &$opened_path   Full path of the resource. Used with STREAM_USE_PATH option
	 *
	 * @return  boolean
	 *
	 * @see     streamWrapper::stream_open
	 */
	public function stream_open($path, $mode, $options, &$opened_path)
	{
		$url                         = parse_url($path);
		$this->name                  = $url["host"];
		$this->_buffers[$this->name] = null;
		$this->position              = 0;

		return true;
	}

	/**
	 * Read stream
	 *
	 * @param   integer  $count  How many bytes of data from the current position should be returned.
	 *
	 * @return  mixed    The data from the stream up to the specified number of bytes (all data if
	 *                   the total number of bytes in the stream is less than $count. Null if
	 *                   the stream is empty.
	 *
	 * @see     streamWrapper::stream_read
	 */
	public function stream_read($count)
	{
		$ret            = substr($this->_buffers[$this->name], $this->position, $count);
		$this->position += strlen($ret);

		return $ret;
	}

	/**
	 * Write stream
	 *
	 * @param   string  $data  The data to write to the stream.
	 *
	 * @return  integer
	 *
	 * @see     streamWrapper::stream_write
	 */
	public function stream_write($data)
	{
		$left                        = substr($this->_buffers[$this->name], 0, $this->position);
		$right                       = substr($this->_buffers[$this->name], $this->position + strlen($data));
		$this->_buffers[$this->name] = $left . $data . $right;
		$this->position              += strlen($data);

		return strlen($data);
	}

	/**
	 * Function to get the current position of the stream
	 *
	 * @return  integer
	 *
	 * @see     streamWrapper::stream_tell
	 */
	public function stream_tell()
	{
		return $this->position;
	}

	/**
	 * Function to test for end of file pointer
	 *
	 * @return  boolean  True if the pointer is at the end of the stream
	 *
	 * @see     streamWrapper::stream_eof
	 */
	public function stream_eof()
	{
		return $this->position >= strlen($this->_buffers[$this->name]);
	}

	/**
	 * The read write position updates in response to $offset and $whence
	 *
	 * @param   integer  $offset   The offset in bytes
	 * @param   integer  $whence   Position the offset is added to
	 *                             Options are SEEK_SET, SEEK_CUR, and SEEK_END
	 *
	 * @return  boolean  True if updated
	 *
	 * @see     streamWrapper::stream_seek
	 */
	public function stream_seek($offset, $whence)
	{
		switch ($whence)
		{
			case SEEK_SET:
				if ($offset < strlen($this->_buffers[$this->name]) && $offset >= 0)
				{
					$this->position = $offset;

					return true;
				}
				else
				{
					return false;
				}
				break;

			case SEEK_CUR:
				if ($offset >= 0)
				{
					$this->position += $offset;

					return true;
				}
				else
				{
					return false;
				}
				break;

			case SEEK_END:
				if (strlen($this->_buffers[$this->name]) + $offset >= 0)
				{
					$this->position = strlen($this->_buffers[$this->name]) + $offset;

					return true;
				}
				else
				{
					return false;
				}
				break;

			default:
				return false;
		}
	}
}

// Register the stream
stream_wrapper_register("buffer", Buffer::class);
PK�!]ݢq  "BackupEngine/Util/PushMessages.phpnu&1i�<?php
/**
 * Akeeba Engine
 *
 * @package   akeebaengine
 * @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License version 3, or later
 *
 * 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, version 3.
 *
 * 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
 * <https://www.gnu.org/licenses/>.
 */

namespace Akeeba\Engine\Util;

defined('AKEEBAENGINE') || die();

use Akeeba\Engine\Factory;
use Akeeba\Engine\Platform;
use Akeeba\Engine\Util\Pushbullet\Connector;
use Exception;

class PushMessages implements PushMessagesInterface
{
	/**
	 * The PushBullet connector
	 *
	 * @var Connector[]
	 */
	private $connectors = [];

	/**
	 * Should we send push messages?
	 *
	 * @var bool
	 */
	private $enabled = true;

	/**
	 * Creates the push messaging object
	 */
	public function __construct()
	{
		$pushPreference = Platform::getInstance()->get_platform_configuration_option('push_preference', '0');
		$apiKey         = Platform::getInstance()->get_platform_configuration_option('push_apikey', '');

		// No API key? No push messages are enabled, so no point continuing really...
		if (empty($apiKey))
		{
			$pushPreference = 0;
		}

		// We use a switch in case we add support for more push APIs in the future. The push_preference platform
		// option will tell us which service to use. In that case we'll have to refactor this class, but the public
		// API will remain the same.
		switch ($pushPreference)
		{
			default:
			case 0:
				$this->enabled = false;
				break;

			case 1:
				$keys = explode(',', $apiKey);
				$keys = array_map('trim', $keys);

				foreach ($keys as $key)
				{
					try
					{
						$connector = new Connector($key);
						$connector->getDevices();
						$this->connectors[] = $connector;
					}
					catch (Exception $e)
					{
						Factory::getLog()->warning("Push messages cannot be sent with API key $key. Error received when trying to establish PushBullet connection: " . $e->getMessage());
					}
				}

				if (empty($this->connectors))
				{
					Factory::getLog()->warning('No push messages can be sent: none of the provided API keys is usable. Push messages have been deactivated.');

					$this->enabled = false;
				}

				break;
		}
	}

	/**
	 * Sends a push message to all connected devices. The intent is to provide the user with an information message,
	 * e.g. notify them about the progress of the backup.
	 *
	 * @param   string  $subject  The subject of the message, shown in the lock screen. Keep it short.
	 * @param   string  $details  Long(er) description of what the message is about. Plain text (no HTML).
	 *
	 * @return  void
	 */
	public function message($subject, $details = null)
	{
		if (!$this->enabled)
		{
			return;
		}

		foreach ($this->connectors as $connector)
		{
			try
			{
				$connector->pushNote('', $subject, $details);
			}
			catch (Exception $e)
			{
				Factory::getLog()->warning('Push messages suspended. Error received when trying to send push message:' . $e->getMessage());
				$this->enabled = false;
			}
		}
	}

	/**
	 * Sends a push message, containing a URL/URI, to all connected devices. The URL will be rendered as something
	 * clickable on most devices.
	 *
	 * @param   string  $url      The URL/URI
	 * @param   string  $subject  The subject of the message, shown in the lock screen. Keep it short.
	 * @param   string  $details  Long(er) description of what the message is about. Plain text (no HTML).
	 *
	 * @return  void
	 */
	public function link($url, $subject, $details = null)
	{
		if (!$this->enabled)
		{
			return;
		}

		foreach ($this->connectors as $connector)
		{
			try
			{
				$connector->pushLink('', $subject, $url, $details);
			}
			catch (Exception $e)
			{
				Factory::getLog()->warning('Push messages suspended. Error received when trying to send push message with a link:' . $e->getMessage());
				$this->enabled = false;
			}
		}
	}
}
PK�!]��ttBackupEngine/Util/ParseIni.phpnu&1i�<?php
/**
 * Akeeba Engine
 *
 * @package   akeebaengine
 * @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License version 3, or later
 *
 * 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, version 3.
 *
 * 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
 * <https://www.gnu.org/licenses/>.
 */

namespace Akeeba\Engine\Util;

defined('AKEEBAENGINE') || die();

/**
 * A utility class to parse INI files.
 *
 * This is marked deprecated since Akeeba Engine 6.4.1. The configuration of the engine is no longer stored as INI data.
 * Moreover, we will be migrating away from the current INI files used for defining engine and GUI configuration
 * parameters.
 *
 * @package     Akeeba\Engine\Util
 *
 * @deprecated  6.4.1
 */
abstract class ParseIni
{
	/**
	 * Parse an INI file and return an associative array. This monstrosity is required because some so-called hosts
	 * have disabled PHP's parse_ini_file() function for "security reasons". Apparently their blatant ignorance doesn't
	 * allow them to discern between the innocuous parse_ini_file and the potentially dangerous ini_set, leading them to
	 * disable the former and let the latter enabled.
	 *
	 * @param   string  $file              The file name or raw INI data to process
	 * @param   bool    $process_sections  True to also process INI sections
	 * @param   bool    $rawdata           Is this raw INI data? False when $file is a filepath.
	 * @param   bool    $forcePHP          Should I force the use of the pure-PHP INI file parser?
	 *
	 * @return   array    An associative array of sections, keys and values
	 */
	public static function parse_ini_file($file, $process_sections = false, $rawdata = false, $forcePHP = false)
	{
		/**
		 * WARNING: DO NOT USE INI_SCANNER_RAW IN THE parse_ini_string / parse_ini_file FUNCTION CALLS WITHOUT POST-
		 *          PROCESSING!
		 *
		 * Sometimes we need to save data which is either multiline or has double quotes in the Engine's
		 * configuration. For this reason we have to manually escape \r, \n, \t and \" in
		 * Akeeba\Engine\Configuration::dumpObject(). If we don't we end up with multiline INI values which
		 * won't work. However, if we are using INI_SCANNER_RAW these characters are not escaped back to their
		 * original form. As a result we end up with broken data which cause various problems, the most visible
		 * of which is that Google Storage integration is broken since the JSON data included in the config is
		 * now unparseable.
		 *
		 * However, not using raw mode introduces other problems. For example, the sequence \$ is converted to $ because
		 * it's assumed to be an escaped dollar sign. Things like $foo are addressed as variable interpolation, i.e.
		 * "This is ${foo} wrong" results in "This is  wrong" because $foo is considered as an interpolated variable.
		 *
		 * The solution to that is to use raw mode to parse the INI files and THEN unescape the variables. However, we
		 * cannot simply use stripslashes/stripcslashes because we could end up replacing more than we should (unlike
		 * addcslashes we cannot specify a list of escaped characters to consider). We have to do a slower string
		 * replace instead.
		 *
		 * The next problem to consider is that when $process_sections is true some of the values generated are arrays
		 * or even nested arrays. If you try to string replace on them hilarity ensues. Therefore we have the recursive
		 * unescape method which takes care of that. To make things faster and maintain the array keys we use array_map
		 * to apply recursiveUnescape to the array.
		 */

		if ($rawdata)
		{
			if (!function_exists('parse_ini_string'))
			{
				return self::parse_ini_file_php($file, $process_sections, $rawdata);
			}

			// !!! VERY IMPORTANT !!! Read the warning above before touching this line
			return array_map([
				__CLASS__, 'recursiveUnescape',
			], parse_ini_string($file, $process_sections, INI_SCANNER_RAW));
		}

		if (!function_exists('parse_ini_file'))
		{
			return self::parse_ini_file_php($file, $process_sections);
		}

		// !!! VERY IMPORTANT !!! Read the warning above before touching this line
		return array_map([__CLASS__, 'recursiveUnescape'], parse_ini_file($file, $process_sections, INI_SCANNER_RAW));
	}

	/**
	 * Recursively unescape values which have been escaped by Akeeba\Engine\Configuration::dumpObject().
	 *
	 * @param   string|array  $value
	 *
	 * @return  string|array  Unescaped result
	 */
	static function recursiveUnescape($value)
	{
		if (is_array($value))
		{
			return array_map([__CLASS__, 'recursiveUnescape'], $value);
		}

		return str_replace(['\r', '\n', '\t', '\"'], ["\r", "\n", "\t", '"'], $value);
	}

	/**
	 * A PHP based INI file parser.
	 *
	 * Thanks to asohn ~at~ aircanopy ~dot~ net for posting this handy function on
	 * the parse_ini_file page on http://gr.php.net/parse_ini_file
	 *
	 * @param   string  $file              Filename to process
	 * @param   bool    $process_sections  True to also process INI sections
	 * @param   bool    $rawdata           If true, the $file contains raw INI data, not a filename
	 *
	 * @return    array    An associative array of sections, keys and values
	 */
	static function parse_ini_file_php($file, $process_sections = false, $rawdata = false)
	{
		$process_sections = ($process_sections !== true) ? false : true;

		if (!$rawdata)
		{
			$ini = file($file);
		}
		else
		{
			$file = str_replace("\r", "", $file);
			$ini  = explode("\n", $file);
		}

		if (!is_array($ini))
		{
			return [];
		}

		if (count($ini) == 0)
		{
			return [];
		}

		$sections = [];
		$values   = [];
		$result   = [];
		$globals  = [];
		$i        = 0;
		foreach ($ini as $line)
		{
			$line = trim($line);
			$line = str_replace("\t", " ", $line);

			// Comments
			if (!preg_match('/^[a-zA-Z0-9[]/', $line))
			{
				continue;
			}

			// Sections
			if ($line[0] == '[')
			{
				$tmp        = explode(']', $line);
				$sections[] = trim(substr($tmp[0], 1));
				$i++;
				continue;
			}

			// Key-value pair
			$lineParts = explode('=', $line, 2);
			if (count($lineParts) != 2)
			{
				continue;
			}
			$key   = trim($lineParts[0]);
			$value = trim($lineParts[1]);
			unset($lineParts);

			if (strstr($value, ";"))
			{
				$tmp = explode(';', $value);
				if (count($tmp) == 2)
				{
					if ((($value[0] != '"') && ($value[0] != "'")) ||
						preg_match('/^".*"\s*;/', $value) || preg_match('/^".*;[^"]*$/', $value) ||
						preg_match("/^'.*'\s*;/", $value) || preg_match("/^'.*;[^']*$/", $value)
					)
					{
						$value = $tmp[0];
					}
				}
				else
				{
					if ($value[0] == '"')
					{
						$value = preg_replace('/^"(.*)".*/', '$1', $value);
					}
					elseif ($value[0] == "'")
					{
						$value = preg_replace("/^'(.*)'.*/", '$1', $value);
					}
					else
					{
						$value = $tmp[0];
					}
				}
			}
			$value = trim($value);
			$value = trim($value, "'\"");

			if ($i == 0)
			{
				if (substr($line, -1, 2) == '[]')
				{
					$globals[$key][] = $value;
				}
				else
				{
					$globals[$key] = $value;
				}
			}
			else
			{
				if (substr($line, -1, 2) == '[]')
				{
					$values[$i - 1][$key][] = $value;
				}
				else
				{
					$values[$i - 1][$key] = $value;
				}
			}
		}

		for ($j = 0; $j < $i; $j++)
		{
			if ($process_sections === true)
			{
				if (isset($sections[$j]) && isset($values[$j]))
				{
					$result[$sections[$j]] = $values[$j];
				}
			}
			else
			{
				if (isset($values[$j]))
				{
					$result[] = $values[$j];
				}
			}
		}

		return $result + $globals;
	}
}
PK�!]ju@E��-BackupEngine/Util/Pushbullet/ApiException.phpnu&1i�<?php
/**
 * Akeeba Engine
 *
 * @package   akeebaengine
 * @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License version 3, or later
 *
 * 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, version 3.
 *
 * 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
 * <https://www.gnu.org/licenses/>.
 */

namespace Akeeba\Engine\Util\Pushbullet;

defined('AKEEBAENGINE') || die();

use Exception;

class ApiException extends Exception
{
	// Exception thrown by Pushbullet
}
PK�!]�π1�1*BackupEngine/Util/Pushbullet/Connector.phpnu&1i�<?php
/**
 * Akeeba Engine
 *
 * @package   akeebaengine
 * @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License version 3, or later
 *
 * 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, version 3.
 *
 * 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
 * <https://www.gnu.org/licenses/>.
 */

namespace Akeeba\Engine\Util\Pushbullet;

defined('AKEEBAENGINE') || die();

use Akeeba\Engine\Postproc\ProxyAware;
use CURLFile;

/**
 * Based on Pushbullet-for-PHP 2.10.1 – https://github.com/ivkos/Pushbullet-for-PHP/tree/v2
 *
 * The license for the original class is as follows:
 * ----------
 * The MIT License (MIT)
 *
 * Copyright (c) 2014 Ivaylo Stoyanov
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 * ----------
 *
 * The following class is a derivative work, NOT the original work.
 */
class Connector
{
	use ProxyAware;

	public const URL_PUSHES = 'https://api.pushbullet.com/v2/pushes';
	public const URL_DEVICES = 'https://api.pushbullet.com/v2/devices';
	public const URL_CONTACTS = 'https://api.pushbullet.com/v2/contacts';
	public const URL_UPLOAD_REQUEST = 'https://api.pushbullet.com/v2/upload-request';
	public const URL_USERS = 'https://api.pushbullet.com/v2/users';
	public const URL_SUBSCRIPTIONS = 'https://api.pushbullet.com/v2/subscriptions';
	public const URL_CHANNEL_INFO = 'https://api.pushbullet.com/v2/channel-info';
	public const URL_EPHEMERALS = 'https://api.pushbullet.com/v2/ephemerals';
	public const URL_PHONEBOOK = 'https://api.pushbullet.com/v2/permanents/phonebook';
	private $_apiKey;
	private $_curlCallback;

	/**
	 * Pushbullet constructor.
	 *
	 * @param   string  $apiKey  API key.
	 *
	 * @throws ApiException
	 */
	public function __construct($apiKey)
	{
		$this->_apiKey = $apiKey;

		if (!function_exists('curl_init'))
		{
			throw new ApiException('cURL library is not loaded.');
		}
	}

	/**
	 * Parse recipient.
	 *
	 * @param   string  $recipient  Recipient string.
	 * @param   array   $data       Data array to populate with the correct recipient parameter.
	 */
	private static function _parseRecipient($recipient, array &$data)
	{
		if (!empty($recipient))
		{
			if (filter_var($recipient, FILTER_VALIDATE_EMAIL) !== false)
			{
				$data['email'] = $recipient;
			}
			else
			{
				if (substr($recipient, 0, 1) == "#")
				{
					$data['channel_tag'] = substr($recipient, 1);
				}
				else
				{
					$data['device_iden'] = $recipient;
				}
			}
		}
	}

	/**
	 * Push a note.
	 *
	 * @param   string  $recipient  Recipient. Can be device_iden, email or channel #tagname.
	 * @param   string  $title      The note's title.
	 * @param   string  $body       The note's message.
	 *
	 * @return object Response.
	 * @throws ApiException
	 */
	public function pushNote($recipient, $title, $body = null)
	{
		$data = [];

		Connector::_parseRecipient($recipient, $data);
		$data['type']  = 'note';
		$data['title'] = $title;
		$data['body']  = $body;

		return $this->_curlRequest(self::URL_PUSHES, 'POST', $data);
	}

	/**
	 * Push a link.
	 *
	 * @param   string  $recipient  Recipient. Can be device_iden, email or channel #tagname.
	 * @param   string  $title      The link's title.
	 * @param   string  $url        The URL to open.
	 * @param   string  $body       A message associated with the link.
	 *
	 * @return object Response.
	 * @throws ApiException
	 */
	public function pushLink($recipient, $title, $url, $body = null)
	{
		$data = [];

		Connector::_parseRecipient($recipient, $data);
		$data['type']  = 'link';
		$data['title'] = $title;
		$data['url']   = $url;
		$data['body']  = $body;

		return $this->_curlRequest(self::URL_PUSHES, 'POST', $data);
	}

	/**
	 * Push a checklist.
	 *
	 * @param   string    $recipient  Recipient. Can be device_iden, email or channel #tagname.
	 * @param   string    $title      The list's title.
	 * @param   string[]  $items      The list items.
	 *
	 * @return object Response.
	 * @throws ApiException
	 */
	public function pushList($recipient, $title, array $items)
	{
		$data = [];

		Connector::_parseRecipient($recipient, $data);
		$data['type']  = 'list';
		$data['title'] = $title;
		$data['items'] = $items;

		return $this->_curlRequest(self::URL_PUSHES, 'POST', $data);
	}

	/**
	 * Push a file.
	 *
	 * @param   string  $recipient    Recipient. Can be device_iden, email or channel #tagname.
	 * @param   string  $filePath     The path of the file to push.
	 * @param   string  $mimeType     The MIME type of the file. If null, we'll try to guess it.
	 * @param   string  $title        The title of the push notification.
	 * @param   string  $body         The body of the push notification.
	 * @param   string  $altFileName  Alternative file name to use instead of the original one.
	 *                                For example, you might want to push 'someFile.tmp' as 'image.jpg'.
	 *
	 * @return object Response.
	 * @throws ApiException
	 */
	public function pushFile($recipient, $filePath, $mimeType = null, $title = null, $body = null, $altFileName = null)
	{
		$data = [];

		$fullFilePath = realpath($filePath);

		if (!is_readable($fullFilePath))
		{
			throw new ApiException('File: File does not exist or is unreadable.');
		}

		if (filesize($fullFilePath) > 25 * 1024 * 1024)
		{
			throw new ApiException('File: File size exceeds 25 MB.');
		}

		$data['file_name'] = $altFileName ?? basename($fullFilePath);

		// Try to guess the MIME type if the argument is NULL
		$data['file_type'] = $mimeType ?? mime_content_type($fullFilePath);

		// Request authorization to upload the file
		$response         = $this->_curlRequest(self::URL_UPLOAD_REQUEST, 'GET', $data);
		$data['file_url'] = $response->file_url;

		$response->data->file = new CURLFile($fullFilePath);

		// Upload the file
		$this->_curlRequest($response->upload_url, 'POST', $response->data, false, false);

		Connector::_parseRecipient($recipient, $data);
		$data['type']  = 'file';
		$data['title'] = $title;
		$data['body']  = $body;

		return $this->_curlRequest(self::URL_PUSHES, 'POST', $data);
	}

	/**
	 * Get push history.
	 *
	 * @param   int     $modifiedAfter  Request pushes modified after this UNIX timestamp.
	 * @param   string  $cursor         Request the next page via its cursor from a previous response. See the API
	 *                                  documentation (https://docs.pushbullet.com/http/) for a detailed description.
	 * @param   int     $limit          Maximum number of objects on each page.
	 *
	 * @return object Response.
	 * @throws ApiException
	 */
	public function getPushHistory($modifiedAfter = 0, $cursor = null, $limit = null)
	{
		$data                   = [];
		$data['modified_after'] = $modifiedAfter;

		if ($cursor !== null)
		{
			$data['cursor'] = $cursor;
		}

		if ($limit !== null)
		{
			$data['limit'] = $limit;
		}

		return $this->_curlRequest(self::URL_PUSHES, 'GET', $data);
	}

	/**
	 * Dismiss a push.
	 *
	 * @param   string  $pushIden  push_iden of the push notification.
	 *
	 * @return object Response.
	 * @throws ApiException
	 */
	public function dismissPush($pushIden)
	{
		return $this->_curlRequest(self::URL_PUSHES . '/' . $pushIden, 'POST', ['dismissed' => true]);
	}

	/**
	 * Delete a push.
	 *
	 * @param   string  $pushIden  push_iden of the push notification.
	 *
	 * @return object Response.
	 * @throws ApiException
	 */
	public function deletePush($pushIden)
	{
		return $this->_curlRequest(self::URL_PUSHES . '/' . $pushIden, 'DELETE');
	}

	/**
	 * Get a list of available devices.
	 *
	 * @param   int     $modifiedAfter  Request devices modified after this UNIX timestamp.
	 * @param   string  $cursor         Request the next page via its cursor from a previous response. See the API
	 *                                  documentation (https://docs.pushbullet.com/http/) for a detailed description.
	 * @param   int     $limit          Maximum number of objects on each page.
	 *
	 * @return object Response.
	 * @throws ApiException
	 */
	public function getDevices($modifiedAfter = 0, $cursor = null, $limit = null)
	{
		$data                   = [];
		$data['modified_after'] = $modifiedAfter;

		if ($cursor !== null)
		{
			$data['cursor'] = $cursor;
		}

		if ($limit !== null)
		{
			$data['limit'] = $limit;
		}

		return $this->_curlRequest(self::URL_DEVICES, 'GET', $data);
	}

	/**
	 * Get information about the current user.
	 *
	 * @return object Response.
	 * @throws ApiException
	 */
	public function getUserInformation()
	{
		return $this->_curlRequest(self::URL_USERS . '/me', 'GET');
	}

	/**
	 * Update preferences for the current user.
	 *
	 * @param   array  $preferences  Preferences.
	 *
	 * @return object Response.
	 * @throws ApiException
	 */
	public function updateUserPreferences($preferences)
	{
		return $this->_curlRequest(self::URL_USERS . '/me', 'POST', ['preferences' => $preferences]);
	}

	/**
	 * Add a callback function that will be invoked right before executing each cURL request.
	 *
	 * @param   callable  $callback  The callback function.
	 */
	public function addCurlCallback(callable $callback)
	{
		$this->_curlCallback = $callback;
	}

	/**
	 * Send a request to a remote server using cURL.
	 *
	 * @param   string  $url         URL to send the request to.
	 * @param   string  $method      HTTP method.
	 * @param   array   $data        Query data.
	 * @param   bool    $sendAsJSON  Send the request as JSON.
	 * @param   bool    $auth        Use the API key to authenticate
	 *
	 * @return object Response.
	 * @throws ApiException
	 */
	private function _curlRequest($url, $method, $data = null, $sendAsJSON = true, $auth = true)
	{
		$curl = curl_init();

		$this->applyProxySettingsToCurl($curl);

		if ($method == 'GET' && $data !== null)
		{
			$url .= '?' . http_build_query($data);
		}

		curl_setopt($curl, CURLOPT_URL, $url);

		if ($auth)
		{
			curl_setopt($curl, CURLOPT_USERPWD, $this->_apiKey);
		}

		curl_setopt($curl, CURLOPT_CUSTOMREQUEST, $method);

		if ($method == 'POST' && $data !== null)
		{
			if ($sendAsJSON)
			{
				$data = json_encode($data);
				curl_setopt($curl, CURLOPT_HTTPHEADER, [
					'Content-Type: application/json',
					'Content-Length: ' . strlen($data),
				]);
			}

			curl_setopt($curl, CURLOPT_POSTFIELDS, $data);
		}

		curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
		curl_setopt($curl, CURLOPT_HEADER, false);

		@curl_setopt($curl, CURLOPT_CAINFO, AKEEBA_CACERT_PEM);
		curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 2);
		curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, true);

		if ($this->_curlCallback !== null)
		{
			$curlCallback = $this->_curlCallback;
			$curlCallback($curl);
		}

		$response = curl_exec($curl);

		if ($response === false)
		{
			$curlError = curl_error($curl);
			curl_close($curl);
			throw new ApiException('cURL Error: ' . $curlError);
		}

		$httpCode = curl_getinfo($curl, CURLINFO_HTTP_CODE);

		if ($httpCode >= 400)
		{
			curl_close($curl);
			$responseParsed = json_decode($response);
			throw new ApiException('HTTP Error ' . $httpCode .
				' (' . $responseParsed->error->type . '): ' . $responseParsed->error->message);
		}

		curl_close($curl);

		return json_decode($response);
	}
}
PK�!]�<��^^$BackupEngine/Util/FileCloseAware.phpnu&1i�<?php
/**
 * Akeeba Engine
 *
 * @package   akeebaengine
 * @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License version 3, or later
 *
 * 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, version 3.
 *
 * 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
 * <https://www.gnu.org/licenses/>.
 */

namespace Akeeba\Engine\Util;

defined('AKEEBAENGINE') || die();

use Throwable;

trait FileCloseAware
{
	protected function conditionalFileClose($fp): bool
	{
		if (!is_resource($fp))
		{
			return false;
		}

		try
		{
			return @fclose($fp);
		}
		catch (Throwable $e)
		{
			return false;
		}
	}

}PK�!]����?8?8 BackupEngine/Util/FileSystem.phpnu&1i�<?php
/**
 * Akeeba Engine
 *
 * @package   akeebaengine
 * @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License version 3, or later
 *
 * 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, version 3.
 *
 * 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
 * <https://www.gnu.org/licenses/>.
 */

namespace Akeeba\Engine\Util;

defined('AKEEBAENGINE') || die();

use Akeeba\Engine\Factory;
use Akeeba\Engine\Platform;

/**
 * Utility functions related to filesystem objects, e.g. path translation
 */
class FileSystem
{
	/**
	 * Are we running under Windows?
	 *
	 * @var   bool
	 */
	private $isWindows = false;

	/**
	 * Local cache of the platform stock directories
	 *
	 * @var   array|null
	 * @since 7.0.3
	 */
	protected static $stockDirs = null;

	/**
	 * Initialise the object
	 */
	public function __construct()
	{
		$this->isWindows = (DIRECTORY_SEPARATOR == '\\');
	}

	/**
	 * Makes a Windows path more UNIX-like, by turning backslashes to forward slashes.
	 * It takes into account UNC paths, e.g. \\myserver\some\folder becomes
	 * \\myserver/some/folder.
	 *
	 * This function will also fix paths with multiple slashes, e.g. convert /var//www////html to /var/www/html
	 *
	 * @param   string  $p_path  The path to transform
	 *
	 * @return  string
	 */
	public function TranslateWinPath($p_path)
	{
		$is_unc = false;

		if ($this->isWindows)
		{
			// Is this a UNC path?
			$is_unc = (substr($p_path, 0, 2) == '\\\\') || (substr($p_path, 0, 2) == '//');

			// Change potential windows directory separator
			if ((strpos($p_path, '\\') > 0) || (substr($p_path, 0, 1) == '\\'))
			{
				$p_path = strtr($p_path, '\\', '/');
			}
		}

		// Remove multiple slashes
		$p_path = str_replace('///', '/', $p_path);
		$p_path = str_replace('//', '/', $p_path);

		// Fix UNC paths
		if ($is_unc)
		{
			$p_path = '//' . ltrim($p_path, '/');
		}

		return $p_path;
	}

	/**
	 * Removes trailing slash or backslash from a pathname
	 *
	 * @param   string  $path  The path to treat
	 *
	 * @return  string  The path without the trailing slash/backslash
	 */
	public function TrimTrailingSlash($path)
	{
		$newpath = $path;

		if (substr($path, strlen($path) - 1, 1) == '\\')
		{
			$newpath = substr($path, 0, strlen($path) - 1);
		}

		if (substr($path, strlen($path) - 1, 1) == '/')
		{
			$newpath = substr($path, 0, strlen($path) - 1);
		}

		return $newpath;
	}

	/**
	 * Returns an array with the archive name variables and their values. This is used to replace variables in archive
	 * and directory names, etc.
	 *
	 * If there is a non-empty configuration value called volatile.core.archivenamevars with a serialised array it will
	 * be unserialised and used. Otherwise the name variables will be calculated on-the-fly.
	 *
	 * IMPORTANT: These variables do NOT include paths such as [SITEROOT]
	 *
	 * @return  array
	 */
	public function get_archive_name_variables()
	{
		$variables = [];

		$registry   = Factory::getConfiguration();
		$serialized = $registry->get('volatile.core.archivenamevars', null);

		if (!empty($serialized))
		{
			$variables = @unserialize($serialized);
		}

		if (empty($variables) || !is_array($variables))
		{
			$host         = Platform::getInstance()->get_host();
			$version      = defined('AKEEBA_VERSION') ? AKEEBA_VERSION : 'svn';
			$version      = defined('AKEEBABACKUP_VERSION') ? AKEEBABACKUP_VERSION : $version;
			$platformVars = Platform::getInstance()->getPlatformVersion();

			$siteName = $this->stringUrlUnicodeSlug(Platform::getInstance()->get_site_name());

			if (strlen($siteName) > 50)
			{
				$siteName = substr($siteName, 0, 50);
			}

			/**
			 * Time components. Expressed in whatever timezone the Platform decides to use.
			 */
			// Raw timezone, e.g. "EEST"
			$rawTz = Platform::getInstance()->get_local_timestamp("T");
			// Filename-safe timezone, e.g. "eest". Note the lowercase letters.
			$fsSafeTZ = strtolower(str_replace([' ', '/', ':'], ['_', '_', '_'], $rawTz));

			$randVal = new RandomValue();

			$variables = [
				'[DATE]'             => Platform::getInstance()->get_local_timestamp("Ymd"),
				'[YEAR]'             => Platform::getInstance()->get_local_timestamp("Y"),
				'[MONTH]'            => Platform::getInstance()->get_local_timestamp("m"),
				'[DAY]'              => Platform::getInstance()->get_local_timestamp("d"),
				'[TIME]'             => Platform::getInstance()->get_local_timestamp("His"),
				'[TIME_TZ]'          => Platform::getInstance()->get_local_timestamp("His") . $fsSafeTZ,
				'[WEEK]'             => Platform::getInstance()->get_local_timestamp("W"),
				'[WEEKDAY]'          => Platform::getInstance()->get_local_timestamp("l"),
				'[TZ]'               => $fsSafeTZ,
				'[TZ_RAW]'           => $rawTz,
				'[GMT_OFFSET]'       => Platform::getInstance()->get_local_timestamp("O"),
				'[HOST]'             => empty($host) ? 'unknown_host' : $host,
				'[VERSION]'          => $version,
				'[PLATFORM_NAME]'    => $platformVars['name'],
				'[PLATFORM_VERSION]' => $platformVars['version'],
				'[SITENAME]'         => $siteName,
				'[RANDOM]'           => $randVal->generateString(16, 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_0123456789'),
			];
		}

		return $variables;
	}

	/**
	 * Expands the archive name variables in $source. For example "[DATE]-foobar" would be expanded to something
	 * like "141101-foobar". IMPORTANT: These variables do NOT include paths.
	 *
	 * @param   string  $source  The input string, possibly containing variables in the form of [VARIABLE]
	 *
	 * @return  string  The expanded string
	 */
	public function replace_archive_name_variables($source)
	{
		$tagReplacements = $this->get_archive_name_variables();

		return str_replace(array_keys($tagReplacements), array_values($tagReplacements), $source);
	}

	/**
	 * Expand the platform-specific stock directories variables in the input string. For example "[SITEROOT]/foobar"
	 * would be expanded to something like "/var/www/html/mysite/foobar"
	 *
	 * @param   string  $folder               The input string to expand
	 * @param   bool    $translate_win_dirs   Should I translate Windows path separators to UNIX path separators? (default: false)
	 * @param   bool    $trim_trailing_slash  Should I remove the trailing slash (default: false)
	 *
	 * @return  string  The expanded string
	 */
	public function translateStockDirs($folder, $translate_win_dirs = false, $trim_trailing_slash = false)
	{
		if (is_null(self::$stockDirs))
		{
			self::$stockDirs = Platform::getInstance()->get_stock_directories();
		}

		$temp = $folder;

		foreach (self::$stockDirs as $find => $replace)
		{
			$temp = str_replace($find, $replace, $temp);
		}

		if ($translate_win_dirs)
		{
			$temp = $this->TranslateWinPath($temp);
		}

		if ($trim_trailing_slash)
		{
			$temp = $this->TrimTrailingSlash($temp);
		}

		return $temp;
	}

	/**
	 * Rebase a path to the platform filesystem variables (most to least specific).
	 *
	 * This is the inverse procedure of translateStockDirs().
	 *
	 * @param   string  $path
	 *
	 * @return  string
	 * @since   7.3.0
	 */
	public function rebaseFolderToStockDirs(string $path): string
	{
		// Normalize the path
		$path = $this->TrimTrailingSlash($path);
		$path = $this->TranslateWinPath($path);

		// Get the stock directories, normalize them and sort them by longest to shortest
		$stock_directories = Platform::getInstance()->get_stock_directories();

		$stock_directories = array_map(function ($path) {
			$path = $this->TrimTrailingSlash($path);

			return $this->TranslateWinPath($path);
		}, $stock_directories);

		uasort($stock_directories, function ($a, $b) {
			return -($a <=> $b);
		});

		// Start replacing paths with variables
		foreach ($stock_directories as $var => $stockPath)
		{
			if (empty($stockPath))
			{
				continue;
			}

			if (strpos($path, $stockPath) !== 0)
			{
				continue;
			}

			$path = $var . substr($path, strlen($stockPath));
		}

		return $path;
	}

	/**
	 * Generates a set of files which prevent direct web access or at least web listing of the folder contents.
	 *
	 * This method generates a .htaccess for Apache, Lighttpd and Litespeed; a web.config file for IIS 7 or later; an
	 * index.php, index.html and index.htm file for all other browsers.
	 *
	 * Despite this security precaution it is STRONGLY advised to keep your backup archives in a directory outside the
	 * site's web root as explained in the Security Information chapter of the documentation. This method is designed
	 * to only provide a defence of last resort.
	 *
	 * @param   string  $dir    The output directory to secure against web access
	 * @param   bool    $force  Forcibly overwrite existing files
	 *
	 * @return  void
	 * @since   7.0.3
	 */
	public function ensureNoAccess($dir, $force = false)
	{
		// Create a .htaccess file to prevent all web access (Apache 1.3+, Lightspeed, Lighttpd, ...)
		if (!is_file($dir . '/.htaccess') || $force)
		{
			$htaccess = <<< APACHE
## This file was generated automatically by the Akeeba Backup Engine
##
## DO NOT REMOVE THIS FILE
##
## This file makes sure that your backup output directory is not directly accessible from the web if you are using
## the Apache, Lighttpd and Litespeed web server. This prevents unauthorized access to your backup archive files and
## backup log files. Removing this file could have security implications for your site.
##
## You are strongly advised to never delete or modify any of the files automatically created in this folder by the
## Akeeba Backup Engine, namely:
##
## * .htaccess
## * web.config
## * index.html
## * index.htm
## * index.php
##
<IfModule !mod_authz_core.c>
Order deny,allow
Deny from all
</IfModule>
<IfModule mod_authz_core.c>
  <RequireAll>
    Require all denied
  </RequireAll>
</IfModule>
APACHE;

			@file_put_contents($dir . '/.htaccess', $htaccess);
		}

		// Create a web.config to prevent all web access (IIS 7+)
		if (!is_file($dir . '/web.config') || $force)
		{
			$webConfig = <<< XML
<?xml version="1.0"?>
<!--
This file was generated automatically by the Akeeba Backup Engine

DO NOT REMOVE THIS FILE

This file makes sure that your backup output directory is not directly accessible from the web if you are using the
Microsoft Internet Information Services (IIS) web server, version 7 or later. This prevents unauthorized access to your
backup archive files and backup log files. Removing this file could have security implications for your site.

As noted above, this only works on IIS 7 or later.
See https://www.iis.net/configreference/system.webserver/security/requestfiltering/fileextensions

You are strongly advised to never delete or modify any of the files automatically created in this folder by the
Akeeba Backup Engine, namely:

* .htaccess
* web.config
* index.html
* index.htm
* index.php

-->
<configuration>
    <system.webServer>
        <security>
            <requestFiltering>
                <fileExtensions allowUnlisted="false" >
                    <clear />
                    <add fileExtension=".html" allowed="true"/>
                </fileExtensions>
            </requestFiltering>
        </security>
    </system.webServer>
</configuration>
XML;
			@file_put_contents($dir . '/web.config', $webConfig);
		}

		// Create a blank index.html or index.htm to prevent directory listings (all servers)
		$blankHtml = <<< HTML
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
  <head>
    <title>Access Denied</title>
  </head>
  <body>
	  <h1>Access Denied</h1>
  </body>
</html>
HTML;

		if (!is_file($dir . '/index.html') || $force)
		{
			@file_put_contents($dir . '/index.html', $blankHtml);
		}

		if (!is_file($dir . '/index.htm') || $force)
		{
			@file_put_contents($dir . '/index.htm', $blankHtml);
		}

		// Create a default index.php to prevent directory listings with an error (all servers)
		if (!is_file($dir . '/index.php') || $force)
		{
			$deadPHP = '<' . '?' . 'php header(\'HTTP/1.1 403 Forbidden\'); return;' . '?' . ">\n";
			$deadPHP .= <<< TEXT
This file was generated automatically by the Akeeba Backup Engine

DO NOT REMOVE THIS FILE

This file tells your web server to not list the contents of this directory, instead returning an HTTP 403 Forbidden
error. This makes it implausible for a malicious third party to successfully guess the filenames of your backup
archives. Therefore, even if this folder is directly web accessible – despite the .htaccess and web.config file already
put in place by the Akeeba Backup Engine – it will still be reasonably protected against malicious users trying to
download your backup archives.

Please do not remove this file as it could have security implications for your site.

You are strongly advised to never delete or modify any of the files automatically created in this folder by the
Akeeba Backup Engine, namely:

* .htaccess
* web.config
* index.html
* index.htm
* index.php

TEXT;

			@file_put_contents($dir . '/index.php', $deadPHP);
		}
	}

	/**
	 * Convert a string to a (Unicode) slug
	 *
	 * @param   string  $string  String to process
	 *
	 * @return  string  Processed string
	 *
	 * @since   7.5.0
	 */
	public function stringUrlUnicodeSlug(string $string): string
	{
		// Replace double byte whitespaces by single byte (East Asian languages)
		$str = preg_replace('/\xE3\x80\x80/', ' ', $string);

		// Remove any '-' from the string as they will be used as concatenator.
		$str = str_replace('-', ' ', $str);

		// Replace forbidden characters by whitespaces
		$str = preg_replace('#[:\?\#\*"@+=;!><&\.%()\]\/\'\\\\|\[]#', "\x20", $str);

		// Delete all '?'
		$str = str_replace('?', '', $str);

		// Trim white spaces at beginning and end of alias and make lowercase
		$str = trim(strtolower($str));

		// Remove any duplicate whitespace and replace whitespaces by hyphens
		$str = preg_replace('#\x20+#', '-', $str);

		return $str;
	}

}
PK�!]e�?�""BackupEngine/Util/HashTrait.phpnu&1i�<?php
/**
 * Akeeba Engine
 *
 * @package   akeebaengine
 * @copyright Copyright (c)2024 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License version 3, or later
 *
 * 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, version 3.
 *
 * 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
 * <https://www.gnu.org/licenses/>.
 */

namespace Akeeba\Engine\Util;

/**
 * PHP 8.4+ workaround for standalone MD5 and SHA-1 functions.
 *
 * PHP 8.4 deprecates the standalone md5(), md5_file(), sha1(), and sha1_file() functions. This trait creates shims
 * which use the hash() and hash_file() functions instead where available.
 *
 * IMPORTANT! PHP 7.4 made the ext/hash extension mandatory. These shims are here only as a backwards compatibility aid.
 * Eventually, we need to remove them, replacing their use by the direct use of hash() and hash_file().
 *
 * @deprecated 10.0
 */
trait HashTrait
{
	/**
	 * @deprecated 10.0 Use hash() instead
	 */
	private static function md5($string, $binary = false)
	{
		static $shouldUseHash = null;

		if ($shouldUseHash === null)
		{
			$shouldUseHash = function_exists('hash')
			                 && function_exists('hash_algos')
			                 && in_array('md5', hash_algos());
		}

		return $shouldUseHash ? hash('md5', $string, $binary) : md5($string, $binary);
	}

	/**
	 * @deprecated 10.0 Use hash() instead
	 */
	private static function sha1($string, $binary = false)
	{
		static $shouldUseHash = null;

		if ($shouldUseHash === null)
		{
			$shouldUseHash = function_exists('hash')
			                 && function_exists('hash_algos')
			                 && in_array('sha1', hash_algos());
		}

		return $shouldUseHash ? hash('sha1', $string, $binary) : sha1($string, $binary);
	}

	/**
	 * @deprecated 10.0 Use hash_file() instead
	 */
	private static function md5_file($filename, $binary = false)
	{
		static $shouldUseHash = null;

		if ($shouldUseHash === null)
		{
			$shouldUseHash = function_exists('hash')
			                 && function_exists('hash_algos')
			                 && in_array('md5', hash_algos());
		}

		return $shouldUseHash ? hash_file('md5', $filename, $binary) : md5_file($filename, $binary);
	}

	/**
	 * @deprecated 10.0 Use hash_file() instead
	 */
			private static function sha1_file($filename, $binary = false)
	{
		static $shouldUseHash = null;

		if ($shouldUseHash === null)
		{
			$shouldUseHash = function_exists('hash')
			                 && function_exists('hash_algos')
			                 && in_array('sha1', hash_algos());
		}

		return $shouldUseHash ? hash_file('sha1', $filename, $binary) : sha1_file($filename, $binary);
	}
}PK�!]4�ٹBtBtBackupEngine/Factory.phpnu&1i�<?php
/**
 * Akeeba Engine
 *
 * @package   akeebaengine
 * @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License version 3, or later
 *
 * 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, version 3.
 *
 * 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
 * <https://www.gnu.org/licenses/>.
 */

namespace Akeeba\Engine;

defined('AKEEBAENGINE') || die();

use Akeeba\Engine\Base\Part;
use Akeeba\Engine\Core\Database;
use Akeeba\Engine\Core\Filters;
use Akeeba\Engine\Core\Kettenrad;
use Akeeba\Engine\Core\Timer;
use Akeeba\Engine\Driver\Base;
use Akeeba\Engine\Dump\Native;
use Akeeba\Engine\Postproc\PostProcInterface;
use Akeeba\Engine\Util\ConfigurationCheck;
use Akeeba\Engine\Util\Encrypt;
use Akeeba\Engine\Util\EngineParameters;
use Akeeba\Engine\Util\FactoryStorage;
use Akeeba\Engine\Util\FileLister;
use Akeeba\Engine\Util\FileSystem;
use Akeeba\Engine\Util\Logger;
use Akeeba\Engine\Util\PushMessagesInterface;
use Akeeba\Engine\Util\RandomValue;
use Akeeba\Engine\Util\SecureSettings;
use Akeeba\Engine\Util\Statistics;
use Akeeba\Engine\Util\TemporaryFiles;
use DateTime;
use DateTimeZone;
use Exception;
use RuntimeException;

// Try to kill errors display
if (function_exists('ini_set') && !defined('AKEEBADEBUG'))
{
	ini_set('display_errors', false);
}

// Make sure the class autoloader is loaded
require_once __DIR__ . '/Autoloader.php';

/**
 * The Akeeba Engine Factory class
 *
 * This class is responsible for instantiating all Akeeba Engine classes
 */
abstract class Factory
{
	/**
	 * The absolute path to Akeeba Engine's installation
	 *
	 * @var  string
	 */
	private static $root;

	/**
	 * Partial class names of the loaded engines e.g. 'archiver' => 'Archiver\\Jpa'. Survives serialization.
	 *
	 * @var  array
	 */
	private static $engineClassnames = [];

	/**
	 * A list of instantiated objects which will persist after serialisation / unserialisation
	 *
	 * @var   array
	 */
	private static $objectList = [];

	/**
	 * A list of instantiated objects which will NOT persist after serialisation / unserialisation
	 *
	 * @var   array
	 */
	private static $temporaryObjectList = [];

	/**
	 * The class to use for push messages
	 *
	 * @since 9.3.1
	 * @var   string
	 */
	private static $pushClassName = 'Util\\PushMessages';

	/**
	 * Gets a serialized snapshot of the Factory for safekeeping (hibernate)
	 *
	 * @return  string  The serialized snapshot of the Factory
	 */
	public static function serialize(): string
	{
		// Call _onSerialize in all objects known to the factory
		foreach (static::$objectList as $object)
		{
			if (method_exists($object, '_onSerialize'))
			{
				call_user_func([$object, '_onSerialize']);
			}
		}

		// Serialise an array with all the engine information
		$engineInfo = [
			'root'             => static::$root,
			'objectList'       => static::$objectList,
			'engineClassnames' => static::$engineClassnames,
			'pushClassname'    => static::$pushClassName,
		];

		// Serialize the factory
		return base64_encode(serialize($engineInfo));
	}

	/**
	 * Regenerates the full Factory state from a serialized snapshot (resume)
	 *
	 * @param   string  $serializedData  The serialized snapshot to resume from
	 *
	 * @return  void
	 */
	public static function unserialize(string $serializedData): void
	{
		static::nuke();

		$engineInfo = unserialize(base64_decode($serializedData));

		static::$root                = $engineInfo['root'] ?? '';
		static::$objectList          = $engineInfo['objectList'] ?? [];
		static::$engineClassnames    = $engineInfo['engineClassnames'] ?? [];
		static::$pushClassName       = $engineInfo['pushClassname'] ?? 'Utils\\PushMessages';
		static::$temporaryObjectList = [];
	}

	/**
	 * Reset the internal factory state, freeing all previously created objects
	 *
	 * @return  void
	 */
	public static function nuke()
	{
		foreach (static::$objectList as &$object)
		{
			$object = null;
		}

		foreach (static::$temporaryObjectList as &$object)
		{
			$object = null;
		}

		static::$objectList          = [];
		static::$temporaryObjectList = [];
	}

	/**
	 * Saves the engine state to temporary storage
	 *
	 * @param   string|null  $tag       The backup origin to save. Leave empty to get from already loaded Kettenrad
	 *                                  instance.
	 * @param   string|null  $backupId  The backup ID to save. Leave empty to get from already loaded Kettenrad
	 *                                  instance.
	 *
	 * @return  void
	 *
	 * @throws  RuntimeException  When the state save fails for any reason
	 * @noinspection PhpUnused
	 */
	public static function saveState(?string $tag = null, ?string $backupId = null): void
	{
		$kettenrad = static::getKettenrad();
		$tag       = $tag ?: $kettenrad->getTag();
		$backupId  = $backupId ?: $kettenrad->getBackupId();

		$saveTag = rtrim($tag . '.' . ($backupId ?: ''), '.');
		$ret     = $kettenrad->getStatusArray();

		if ($ret['HasRun'] == 1)
		{
			Factory::getLog()->debug("Will not save a finished Kettenrad instance");

			return;
		}

		Factory::getLog()->debug("Saving Kettenrad instance $tag");

		// Save a Factory snapshot
		$factoryStorage = static::getFactoryStorage();

		$logger = static::getLog();
		$logger->resetWarnings();

		$serializedFactoryData = static::serialize();
		$memoryFileExtension   = 'php';
		$result                = $factoryStorage->set($serializedFactoryData, $saveTag, $memoryFileExtension);

		/**
		 * Some hosts, such as WPEngine, do not allow us to save the memory files in .php files. In this case we use the
		 * far more insecure .dat extension.
		 */
		if ($result === false)
		{
			$memoryFileExtension = 'dat';
			$result              = $factoryStorage->set($serializedFactoryData, $saveTag, $memoryFileExtension);
		}

		if ($result === false)
		{
			$saveKey      = $factoryStorage->get_storage_filename($saveTag, $memoryFileExtension);
			$errorMessage = "Cannot save factory state in storage, storage filename $saveKey";

			$logger->error($errorMessage);

			throw new RuntimeException($errorMessage);
		}
	}

	/**
	 * Loads the engine state from the storage (if it exists).
	 *
	 * When failIfMissing is true (default) an exception will be thrown if the memory file / database record is no
	 * longer there. This is a clear indication of an issue with the storage engine, e.g. the host deleting the memory
	 * files in the middle of the backup step. Therefore, we'll switch the storage engine type before throwing the
	 * exception.
	 *
	 * When failIfMissing is false we do NOT throw an exception. Instead, we do a hard reset of the backup factory. This
	 * is required by the resetState method when we ask it to reset multiple origins at once.
	 *
	 * @param   string|null  $tag            The backup origin to load
	 * @param   string|null  $backupId       The backup ID to load
	 * @param   bool         $failIfMissing  Throw an exception if the memory data is no longer there
	 *
	 * @return  void
	 */
	public static function loadState(?string $tag = null, ?string $backupId = null, bool $failIfMissing = true): void
	{
		/** @noinspection PhpUndefinedConstantInspection */
		$tag     = $tag ?: (defined('AKEEBA_BACKUP_ORIGIN') ? AKEEBA_BACKUP_ORIGIN : 'backend');
		$loadTag = rtrim($tag . '.' . ($backupId ?: ''), '.');

		// In order to load anything, we need to have the correct profile loaded. Let's assume
		// that the latest backup record in this tag has the correct profile number set.
		$config = static::getConfiguration();

		if (empty($config->activeProfile))
		{
			$profile = Platform::getInstance()->get_active_profile();

			if (empty($profile) || ($profile <= 1))
			{
				// Only bother loading a configuration if none has been already loaded
				$filters = [
					['field' => 'tag', 'value' => $tag],
				];

				if (!empty($backupId))
				{
					$filters[] = ['field' => 'backupid', 'value' => $backupId];
				}

				$statList = Platform::getInstance()->get_statistics_list([
						'filters' => $filters, 'order' => [
							'by' => 'id', 'order' => 'DESC',
						],
					]
				);

				if (is_array($statList))
				{
					$stat    = array_pop($statList) ?? [];
					$profile = $stat['profile_id'] ?? 1;
				}
			}

			Platform::getInstance()->load_configuration($profile);
		}

		$profile = $config->activeProfile;

		Factory::getLog()->open($loadTag);
		Factory::getLog()->debug("Kettenrad :: Attempting to load from database ($tag) [$loadTag]");

		$serializedFactory = static::getFactoryStorage()->get($loadTag);

		if ($serializedFactory === null)
		{
			if ($failIfMissing)
			{
				throw new RuntimeException("Akeeba Engine detected a problem while saving temporary data. Please restart your backup.", 500);
			}

			// There is no serialized factory. Nuke the in-memory factory.
			Factory::getLog()->debug(" -- Stored Akeeba Factory ($tag) [$loadTag] not found - hard reset");
			static::nuke();
			Platform::getInstance()->load_configuration($profile);
		}
		else
		{
			Factory::getLog()->debug(" -- Loaded stored Akeeba Factory ($tag) [$loadTag]");
			static::unserialize($serializedFactory);
		}


		unset($serializedFactory);
	}

	// ========================================================================
	// Public factory interface
	// ========================================================================

	/**
	 * Resets the engine state, wiping out any pending backups and/or stale temporary data.
	 *
	 * The configuration parameters are:
	 *
	 * * global  `bool`  True to reset all backups, regardless of the origin or profile ID
	 * * log     `bool`  True to log our actions (default: false)
	 * * maxrun  `int`   Only backup records older than this number of seconds will be reset (default: 180)
	 *
	 * Special considerations:
	 *
	 * * If global = true all backups from all origins are taken into account to determine which ones are stuck (over
	 *   the maxrun threshold since their last database entry).
	 *
	 * * If global = false only backups from the current backup origin are taken into account.
	 *
	 * * If global = false AND the current origin is 'backend' all pending and idle backups with the 'backup' origin are
	 *   considered stuck regardless of their age. In other words, maxrun is effectively set to 0. The idea is that only
	 *   a single person, from a single browser, should be taking backend backups at a time. Resetting single origin
	 *   backups is only ever meant to be called by the consumer when starting a backup.
	 *
	 * * Corollary to the above: starting a frontend, CLI or JSON API backup with the same backup profile DOES NOT reset
	 *   a previously failed backup if the new backup starts less than 'maxrun' seconds since the last step of the
	 *   failed backup started.
	 *
	 * * The time information for the backup age is taken from the database, namely the backupend field. If no time
	 *   is recorded for the last step we use the backupstart field instead.
	 *
	 * @param   array  $config  Configuration parameters for the reset operation
	 *
	 * @return  void
	 * @throws  Exception
	 * @noinspection PhpUnused
	 */
	public static function resetState(array $config = []): void
	{
		$defaultConfig = [
			'global' => true,
			'log'    => false,
			'maxrun' => 180,
		];

		$config = (object) array_merge($defaultConfig, $config);

		// Pause logging if so desired
		if (!$config->log)
		{
			Factory::getLog()->pause();
		}

		// Get the origin to clear, depending on the 'global' setting
		$originTag = $config->global ? null : Platform::getInstance()->get_backup_origin();

		// Cache the factory before proceeding
		$factory = static::serialize();

		// Get all running backups for the selected origin (or all origins, if global was false).
		$runningList = Platform::getInstance()->get_running_backups($originTag);

		// Sanity check
		if (!is_array($runningList))
		{
			$runningList = [];
		}

		// If the current origin is 'backend' we assume maxrun = 0 per the method docblock notes.
		$maxRun = ($originTag == 'backend') ? 0 : $config->maxrun;

		// Filter out entries by backup age
		$now         = time();
		$cutOff      = $now - $maxRun;
		$runningList = array_filter($runningList, function (array $running) use ($cutOff, $maxRun) {
			// No cutoff time: include all currently running backup records
			if ($maxRun == 0)
			{
				return true;
			}

			// Try to get the last backup tick timestamp
			try
			{
				$backupTickTime = !empty($running['backupend']) ? $running['backupend'] : $running['backupstart'];
				$tz             = new DateTimeZone('UTC');
				$tstamp         = (new DateTime($backupTickTime, $tz))->getTimestamp();
			}
			catch (Exception $e)
			{
				$tstamp = Factory::getLog()->getLastTimestamp($running['origin']);
			}

			if (is_null($tstamp))
			{
				return false;
			}

			// Only include still running backups whose last tick was BEFORE the cutoff time
			return $tstamp <= $cutOff;
		});

		// Mark running backups as failed
		foreach ($runningList as $running)
		{
			// Delete the failed backup's leftover archive parts
			$filenames = Factory::getStatistics()->get_all_filenames($running, false);
			$filenames = is_null($filenames) ? [] : $filenames;
			$totalSize = 0;

			foreach ($filenames as $failedArchive)
			{
				if (!@file_exists($failedArchive))
				{
					continue;
				}

				$totalSize += (int) @filesize($failedArchive);
				Platform::getInstance()->unlink($failedArchive);
			}

			// Mark the backup failed
			$running['status']     = 'fail';
			$running['instep']     = 0;
			$running['total_size'] = empty($running['total_size']) ? $totalSize : $running['total_size'];
			$running['multipart']  = 0;

			Platform::getInstance()->set_or_update_statistics($running['id'], $running);

			// Remove the temporary data
			$backupId = isset($running['backupid']) ? ('.' . $running['backupid']) : '';

			self::removeTemporaryData($running['origin'] . $backupId);
		}

		// Reload the factory
		static::unserialize($factory);
		unset($factory);

		// Unpause logging if it was previously paused
		if (!$config->log)
		{
			Factory::getLog()->unpause();
		}
	}

	/**
	 * Returns an Akeeba Configuration object
	 *
	 * @return  Configuration  The Akeeba Configuration object
	 */
	public static function getConfiguration(): Configuration
	{
		/** @noinspection PhpIncompatibleReturnTypeInspection */
		return static::getObjectInstance(Configuration::class);
	}

	/**
	 * Returns a statistics object, used to track current backup's progress
	 *
	 * @return  Statistics
	 */
	public static function getStatistics(): Statistics
	{
		/** @noinspection PhpIncompatibleReturnTypeInspection */
		return static::getObjectInstance(Statistics::class);
	}

	/**
	 * Returns the currently configured archiver engine
	 *
	 * @param   bool  $reset  Should I try to forcibly create a new instance?
	 *
	 * @return  Archiver\Base|null
	 */
	public static function getArchiverEngine(bool $reset = false): ?Archiver\Base
	{
		/** @noinspection PhpIncompatibleReturnTypeInspection */
		return static::getEngineInstance(
			'archiver', 'akeeba.advanced.archiver_engine',
			'Archiver\\', 'Archiver\\Jpa',
			$reset
		);
	}

	/**
	 * Returns the currently configured dump engine
	 *
	 * @param   boolean  $reset  Should I try to forcibly create a new instance?
	 *
	 * @return  Dump\Base|Native|null
	 */
	public static function getDumpEngine(bool $reset = false): ?object
	{
		/** @noinspection PhpIncompatibleReturnTypeInspection */
		return static::getEngineInstance(
			'dump', 'akeeba.advanced.dump_engine',
			'Dump\\', 'Dump\\Native',
			$reset
		);
	}

	/**
	 * Returns the filesystem scanner engine instance
	 *
	 * @param   bool  $reset  Should I try to forcibly create a new instance?
	 *
	 * @return  Scan\Base|null  The scanner engine
	 */
	public static function getScanEngine(bool $reset = false): ?Scan\Base
	{
		/** @noinspection PhpIncompatibleReturnTypeInspection */
		return static::getEngineInstance(
			'scan', 'akeeba.advanced.scan_engine',
			'Scan\\', 'Scan\\Large',
			$reset
		);
	}

	/**
	 * Returns the current post-processing engine. If no class is specified we
	 * return the post-processing engine configured in akeeba.advanced.postproc_engine
	 *
	 * @param   string|null  $engine  The name of the post-processing class to forcibly return
	 *
	 * @return  PostProcInterface|null
	 */
	public static function getPostprocEngine(?string $engine = null): ?PostProcInterface
	{
		if (!is_null($engine))
		{
			static::$engineClassnames['postproc'] = 'Postproc\\' . ucfirst($engine);

			/** @noinspection PhpIncompatibleReturnTypeInspection */
			return static::getObjectInstance(static::$engineClassnames['postproc']);
		}

		/** @noinspection PhpIncompatibleReturnTypeInspection */
		return static::getEngineInstance(
			'postproc', 'akeeba.advanced.postproc_engine',
			'Postproc\\', 'Postproc\\None',
			true
		);
	}

	// ========================================================================
	// Core objects which are part of the engine state
	// ========================================================================

	/**
	 * Returns an instance of the Filters feature class
	 *
	 * @return  Filters  The Filters feature class' object instance
	 */
	public static function getFilters(): Filters
	{
		/** @noinspection PhpIncompatibleReturnTypeInspection */
		return static::getObjectInstance(Filters::class);
	}

	/**
	 * Returns an instance of the specified filter group class. Do note that it does not
	 * work with platform filter classes. They are handled internally by AECoreFilters.
	 *
	 * @param   string  $filter_name  The filter class to load, without AEFilter prefix
	 *
	 * @return  Filter\Base|null  The filter class' object instance
	 */
	public static function getFilterObject(string $filter_name): ?Filter\Base
	{
		/** @noinspection PhpIncompatibleReturnTypeInspection */
		return static::getObjectInstance('Filter\\' . ucfirst($filter_name));
	}

	/**
	 * Loads an engine domain class and returns its associated object
	 *
	 * @param   string  $domainName  The name of the domain, e.g. installer for AECoreDomainInstaller
	 *
	 * @return  Part|null
	 */
	public static function getDomainObject(string $domainName): ?Part
	{
		/** @noinspection PhpIncompatibleReturnTypeInspection */
		return static::getObjectInstance('Core\\Domain\\' . ucfirst($domainName));
	}

	/**
	 * Returns a database connection object. It's an alias of AECoreDatabase::getDatabase()
	 *
	 * !!! IMPORTANT !!!
	 * DO NOT STATIC TYPE THIS METHOD.
	 *
	 * Akeeba Backup for Joomla is using a decorator to the Joomla DB object which makes use of the magic __call method
	 * to proxy driver calls. As a result it cannot adhere to an object declaration or interface. Until this is
	 * refactored we have to keep this method untyped.
	 *
	 * @param   array|null  $options  Options to use when instantiating the database connection
	 *
	 * @return  Base
	 */
	public static function getDatabase(?array $options = null)
	{
		if (is_null($options))
		{
			$options = Platform::getInstance()->get_platform_database_options();
		}

		if (isset($options['username']) && !isset($options['user']))
		{
			$options['user'] = $options['username'];
		}

		return Database::getDatabase($options);
	}

	/**
	 * Returns a database connection object. It's an alias of AECoreDatabase::getDatabase()
	 *
	 * @param   array|null  $options  Options to use when instantiating the database connection
	 *
	 * @return  void
	 */
	public static function unsetDatabase(?array $options = null): void
	{
		if (is_null($options))
		{
			$options = Platform::getInstance()->get_platform_database_options();
		}

		$db = Database::getDatabase($options);
		$db->close();

		Database::unsetDatabase($options);
	}

	/**
	 * Get a reference to the Akeeba Engine's timer
	 *
	 * @return  Timer
	 */
	public static function getTimer(): Timer
	{
		/** @noinspection PhpIncompatibleReturnTypeInspection */
		return static::getObjectInstance(Timer::class);
	}

	/**
	 * Get a reference to Akeeba Engine's main controller called Kettenrad
	 *
	 * @return  Kettenrad
	 */
	public static function getKettenrad(): Kettenrad
	{
		/** @noinspection PhpIncompatibleReturnTypeInspection */
		return static::getObjectInstance(Kettenrad::class);
	}

	/**
	 * Returns an instance of the factory temporary storage class
	 *
	 * @return  FactoryStorage
	 */
	public static function getFactoryStorage(): FactoryStorage
	{
		/** @noinspection PhpIncompatibleReturnTypeInspection */
		return static::getTempObjectInstance(FactoryStorage::class);
	}

	/**
	 * Returns an instance of the encryption class
	 *
	 * @return  Encrypt
	 */
	public static function getEncryption(): Encrypt
	{
		/** @noinspection PhpIncompatibleReturnTypeInspection */
		return static::getTempObjectInstance(Encrypt::class);
	}

	/**
	 * Returns an instance of the crypto-safe random value generator class
	 *
	 * @return  RandomValue
	 */
	public static function getRandval(): RandomValue
	{
		/** @noinspection PhpIncompatibleReturnTypeInspection */
		return static::getTempObjectInstance(RandomValue::class);
	}

	/**
	 * Returns an instance of the filesystem tools class
	 *
	 * @return  FileSystem
	 */
	public static function getFilesystemTools(): FileSystem
	{
		/** @noinspection PhpIncompatibleReturnTypeInspection */
		return static::getTempObjectInstance(FileSystem::class);
	}

	/**
	 * Returns an instance of the filesystem tools class
	 *
	 * @return  FileLister
	 * @noinspection PhpUnused
	 */
	public static function getFileLister(): FileLister
	{
		/** @noinspection PhpIncompatibleReturnTypeInspection */
		return static::getTempObjectInstance(FileLister::class);
	}

	// ========================================================================
	// Temporary objects which are not part of the engine state
	// ========================================================================

	/**
	 * Returns an instance of the engine parameters provider which provides information on scripting, GUI configuration
	 * elements and engine parts
	 *
	 * @return  EngineParameters
	 */
	public static function getEngineParamsProvider(): EngineParameters
	{
		/** @noinspection PhpIncompatibleReturnTypeInspection */
		return static::getTempObjectInstance(EngineParameters::class);
	}

	/**
	 * Returns an instance of the log object
	 *
	 * @return  Logger
	 */
	public static function getLog(): Logger
	{
		/** @noinspection PhpIncompatibleReturnTypeInspection */
		return static::getTempObjectInstance(Logger::class);
	}

	/**
	 * Returns an instance of the configuration checks object
	 *
	 * @return  ConfigurationCheck
	 */
	public static function getConfigurationChecks(): ConfigurationCheck
	{
		/** @noinspection PhpIncompatibleReturnTypeInspection */
		return static::getTempObjectInstance(ConfigurationCheck::class);
	}

	/**
	 * Returns an instance of the secure settings handling object
	 *
	 * @return  SecureSettings
	 */
	public static function getSecureSettings(): SecureSettings
	{
		/** @noinspection PhpIncompatibleReturnTypeInspection */
		return static::getTempObjectInstance(SecureSettings::class);
	}

	/**
	 * Returns an instance of the secure settings handling object
	 *
	 * @return  TemporaryFiles
	 */
	public static function getTempFiles(): TemporaryFiles
	{
		/** @noinspection PhpIncompatibleReturnTypeInspection */
		return static::getTempObjectInstance(TemporaryFiles::class);
	}

	/**
	 * Get the connector object for push messages
	 *
	 * !!! WARNING !!! DO NOT STATIC TYPE
	 *
	 * The object type may change using setPushClass.
	 *
	 * @return  PushMessagesInterface
	 */
	public static function getPush()
	{
		/** @noinspection PhpIncompatibleReturnTypeInspection */
		return static::getObjectInstance(self::$pushClassName);
	}

	/**
	 * Set the push notifications helper class to use with this factory
	 *
	 * @param   string  $className  The classname to use
	 *
	 * @since   9.3.1
	 * @noinspection PhpUnused
	 */
	public static function setPushClass(string $className): void
	{
		self::$pushClassName = $className;
	}

	/**
	 * Returns the absolute path to Akeeba Engine's installation
	 *
	 * @return  string
	 */
	public static function getAkeebaRoot(): string
	{
		if (empty(static::$root))
		{
			static::$root = __DIR__;
		}

		return static::$root;
	}

	/**
	 * @param   string  $engineType  Engine type, e.g. 'archiver', 'postproc', ...
	 * @param   string  $configKey   Profile config key with configured engine e.g. 'akeeba.advanced.archiver_engine'
	 * @param   string  $prefix      Prefix for engine classes, e.g. 'Archiver\\'
	 * @param   string  $fallback    Fallback class if the configured one doesn't exist e.g. 'Archiver\\Jpa'. Empty for
	 *                               no fallback.
	 * @param   bool    $reset       Should I force-reload the engine? Default: false.
	 *
	 * @return  object|null  The Singleton engine object instance
	 */
	protected static function getEngineInstance(string $engineType, string $configKey, string $prefix, string $fallback, bool $reset = false): ?object
	{
		if (!$reset && !empty(static::$engineClassnames[$engineType]))
		{
			return static::getObjectInstance(static::$engineClassnames[$engineType]);
		}

		// Unset the existing engine object
		if (!empty(static::$engineClassnames[$engineType]))
		{
			static::unsetObjectInstance(static::$engineClassnames[$engineType]);
		}

		// Get the engine name from the backup profile, construct a class name and check if it exists
		$registry                              = static::getConfiguration();
		$engine                                = $registry->get($configKey);
		static::$engineClassnames[$engineType] = $prefix . ucfirst($engine);
		$object                                = static::getObjectInstance(static::$engineClassnames[$engineType]);

		// If the engine object does not exist, fall back to the default
		if (!empty($fallback) && !is_object($object))
		{
			static::unsetObjectInstance(static::$engineClassnames[$engineType]);

			static::$engineClassnames[$engineType] = $fallback;
		}

		return static::getObjectInstance(static::$engineClassnames[$engineType]);
	}

	/**
	 * Internal function which instantiates an object of a class named $class_name.
	 *
	 * @param   string  $className
	 *
	 * @return  object|null
	 */
	protected static function getObjectInstance(string $className): ?object
	{
		$className = trim($className, '\\');

		if (substr($className, 0, 14) === 'Akeeba\\Engine\\')
		{
			$searchClass = $className;
			$className = substr($className, 14);
		}
		else
		{
			$searchClass = '\\Akeeba\\Engine\\' . $className;
		}

		if (isset(static::$objectList[$className]))
		{
			return static::$objectList[$className];
		}

		static::$objectList[$className] = null;

		if (class_exists($searchClass))
		{
			static::$objectList[$className] = new $searchClass;
		}
		elseif (class_exists($className))
		{
			static::$objectList[$className] = new $className;
		}

		return static::$objectList[$className];
	}

	// ========================================================================
	// Handy functions
	// ========================================================================

	/**
	 * Internal function which removes the object of the class named $class_name
	 *
	 * @param   string  $className
	 *
	 * @return  void
	 */
	protected static function unsetObjectInstance(string $className): void
	{
		if (substr($className, 0, 14) === 'Akeeba\\Engine\\')
		{
			$className = substr($className, 14);
		}

		if (isset(static::$objectList[$className]))
		{
			static::$objectList[$className] = null;
			unset(static::$objectList[$className]);
		}
	}

	/**
	 * Internal function which instantiates an object of a class named $class_name. This is a temporary instance which
	 * will not survive serialisation and subsequent unserialisation.
	 *
	 * @param   string  $className
	 *
	 * @return  object|null
	 */
	protected static function getTempObjectInstance(string $className): ?object
	{
		$className = trim($className, '\\');

		if (substr($className, 0, 14) === 'Akeeba\\Engine\\')
		{
			$searchClass = $className;
			$className = substr($className, 14);
		}
		else
		{
			$searchClass = '\\Akeeba\\Engine\\' . $className;
		}

		if (!isset(static::$temporaryObjectList[$className]))
		{
			static::$temporaryObjectList[$className] = null;

			if (class_exists($searchClass))
			{
				static::$temporaryObjectList[$className] = new $searchClass;
			}
		}

		return static::$temporaryObjectList[$className];
	}

	/**
	 * Remote the temporary data for a specific backup tag.
	 *
	 * @param   string  $originTag  The backup tag to reset e.g. 'backend.id123' or 'frontend'.
	 *
	 * @return  void
	 */
	protected static function removeTemporaryData(string $originTag): void
	{
		static::loadState($originTag, null, false);
		// Remove temporary files
		Factory::getTempFiles()->deleteTempFiles();
		// Delete any stale temporary data
		static::getFactoryStorage()->reset($originTag);
	}
}

/**
 * Timeout handler. It is registered as a global PHP shutdown function.
 *
 * If a PHP reports a timeout we will log this before letting PHP kill us.
 */
function AkeebaTimeoutTrap(): void
{
	if (connection_status() >= 2)
	{
		Factory::getLog()->error('Akeeba Engine has timed out');
	}
}

register_shutdown_function("\\Akeeba\\Engine\\AkeebaTimeoutTrap");
PK�!]��ѡ��BackupEngine/.htaccessnu&1i�<IfModule !mod_authz_core.c>
Order deny,allow
Deny from all
</IfModule>
<IfModule mod_authz_core.c>
  <RequireAll>
    Require all denied
  </RequireAll>
</IfModule>
PK�!]N���zz!BackupEngine/FixMySQLHostname.phpnu&1i�<?php
/**
 * Akeeba Engine
 *
 * @package   akeebaengine
 * @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License version 3, or later
 *
 * 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, version 3.
 *
 * 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
 * <https://www.gnu.org/licenses/>.
 */

namespace Akeeba\Engine;

defined('AKEEBAENGINE') || die();

trait FixMySQLHostname
{
	/**
	 * Tries to parse all the weird hostname definitions and normalize them into something that the MySQLi connector
	 * will understand. Please note that there are some differences to the old MySQL driver:
	 *
	 * * Port and socket MUST be provided separately from the hostname. Hostnames in the form of 127.0.0.1:8336 are no
	 *   longer acceptable.
	 *
	 * * The hostname "localhost" has special meaning. It means "use named pipes / sockets". Anything else uses TCP/IP.
	 *   This is the ONLY way to specify a. TCP/IP or b. named pipes / sockets connection.
	 *
	 * * You SHOULD NOT use a numeric TCP/IP port with hostname localhost. For some strange reason it's still allowed
	 *   but the manual is self-contradicting over what this really does...
	 *
	 * * Likewise you CANNOT use a socket / named pipe path with hostname other than localhost. Named pipes and sockets
	 *   can only be used with the local machine, therefore the hostname MUST be localhost.
	 *
	 * * You cannot give a TCP/IP port number in the socket parameter or a named pipe / socket path to the port
	 *   parameter. This leads to an error.
	 *
	 * * You cannot use an empty string, 0 or any other non-null value when you want to omit either of the port or
	 *   socket parameters.
	 *
	 * * Persistent connections must be prefixed with the string literal 'p:'. Therefore you cannot have a hostname
	 *   called 'p' (not to mention that'd be daft). You can also not specify something like 'p:1234' to make a
	 *   persistent connection to a port. This wasn't even supported by the old MySQL driver. As a result we don't even
	 *   try to catch that degenerate case.
	 *
	 * This method will try to apply all of the aforementioned rules with one additional disambiguation rule:
	 *
	 * A port / socket set in the hostname overrides a port specified separately. A port specified separately overrides
	 * a socket specified separately.
	 *
	 * @param   string  $host    The hostname. Can contain legacy hostname:port or hostname:sc=ocket definitions.
	 * @param   int     $port    The port. Alternatively it can contain the path to the socket.
	 * @param   string  $socket  The path to the socket. You could abuse it to enter the port number. DON'T!
	 *
	 * @return  void  All parameters are passed by reference.
	 *
	 * @since   9.2.3
	 */
	protected function fixHostnamePortSocket(&$host, &$port, &$socket)
	{
		// Is this a persistent connection? Persistent connections are indicated by the literal "p:" in front of the hostname
		$isPersistent = (substr($host, 0, 2) == 'p:');
		$host         = $isPersistent ? substr($host, 2) : $host;

		// If the hostname looks like a *NIX filename we need to treat it as a socket.
		if (preg_match('#^/([^/]*/)?[^/]#', $host))
		{
			$socket = $host;
			$host = null;
		}

		// Special case: Windows named pipe (\\.\something\or\another), with or without parentheses.
		$isNamedPipe = false;

		if (preg_match("#^\(?\\\\\\\\\.\\\\#", $host))
		{
			$isNamedPipe = true;
			$socket = $host;
			$host = '.';
		}

		/*
		 * Unlike mysql_connect(), mysqli_connect() takes the port and socket as separate arguments. Therefore, we
		 * have to extract them from the host string.
		 */
		$port = !empty($port) ? $port : 3306;

		if ($host === 'localhost')
		{
			$port = null;
		}
		// UNIX socket URI, e.g. 'unix:/path/to/unix/socket.sock'
		elseif (preg_match('/^unix:(?P<socket>[^:]+)$/', $host, $matches))
		{
			$host   = null;
			$socket = $matches['socket'];
			$port   = null;
		}
		// It's an IPv4 address with or without port
		elseif (preg_match('/^(?P<host>((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?))(:(?P<port>.+))?$/', $host, $matches))
		{
			$host = $matches['host'];

			if (!empty($matches['port']))
			{
				$port = $matches['port'];
			}
		}
		// Square-bracketed IPv6 address with or without port, e.g. [fe80:102::2%eth1]:3306
		elseif (preg_match('/^(?P<host>\[.*\])(:(?P<port>.+))?$/', $host, $matches))
		{
			$host = $matches['host'];

			if (!empty($matches['port']))
			{
				$port = $matches['port'];
			}
		}
		// Named host (e.g example.com or localhost) with or without port
		elseif (preg_match('/^(?P<host>(\w+:\/{2,3})?[a-z0-9\.\-]+)(:(?P<port>[^:]+))?$/i', $host, $matches))
		{
			$host = $matches['host'];

			if (!empty($matches['port']))
			{
				$port = $matches['port'];
			}
		}
		// Empty host, just port, e.g. ':3306'
		elseif (preg_match('/^:(?P<port>[^:]+)$/', $host, $matches))
		{
			$host = '127.0.0.1';
			$port = $matches['port'];
		}
		// ... else we assume normal (naked) IPv6 address, so host and port stay as they are or default

		// If there is both a valid port and a valid socket we will choose the socket instead
		if (is_numeric($port) && !empty($socket))
		{
			$port = null;
		}

		// Get the port number or socket name
		if (is_numeric($port))
		{
			$port   = (int) $port;
			$socket = '';
		}
		elseif (is_string($port) && empty($socket))
		{
			$socket = $port;
			$port = null;
		}

		// If there is a socket the hostname must be null
		if (!empty($socket))
		{
			$host = null;
		}

		// If there is a socket the port must be null
		if (!empty($socket))
		{
			$port = null;
		}

		// If there is a numeric port and the hostname is 'localhost' convert to 127.0.0.1
		if (is_numeric($port) && ($host === 'localhost'))
		{
			$host = '127.0.0.1';
		}

		/**
		 * Special case: MySQL sockets on Windows need to be enclosed with parentheses and have \\.\ in front.
		 *
		 * @see https://dev.mysql.com/doc/mysql-shell/8.0/en/mysql-shell-connection-socket.html
		 * @see https://www.php.net/manual/en/mysqli.quickstart.connections.php
		 */
		if (!empty($socket) && $isNamedPipe)
		{
			$host = '.';

			/**
			 * Remove any existing parentheses, otherwise URL-decode the socket (in case it was given in the correct
			 * percent encoded format).
			 */
			if (substr($socket, 0, 1) === '(' && substr($socket, -1) === ')')
			{
				$socket = substr($socket, 1, -1);

			}
			else
			{
				$socket = rawurldecode($socket);
			}

			// If the socket doesn't already start with \\.\ add it
			if (substr($socket, 0, 4) !== '\\\\.\\')
			{
				$socket = '\\\\.\\' . $socket;
			}

			$socket = '(' . $socket . ')';
		}

		// Finally, if it's a persistent connection we have to prefix the hostname with 'p:'
		$host = ($isPersistent && $host !== null) ? "p:$host" : $host;
	}

}PK�!]��#��BackupEngine/Autoloader.phpnu&1i�<?php
/**
 * Akeeba Engine
 *
 * @package   akeebaengine
 * @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License version 3, or later
 *
 * 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, version 3.
 *
 * 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
 * <https://www.gnu.org/licenses/>.
 */

namespace Akeeba\Engine;

defined('AKEEBAENGINE') || die();

/**
 * The main class autoloader for AkeebaEngine
 */
class Autoloader
{
	/**
	 * An instance of this autoloader
	 *
	 * @var   Autoloader
	 */
	public static $autoloader = null;

	/**
	 * The path to the Akeeba Engine root directory
	 *
	 * @var   string
	 */
	public static $enginePath = null;

	/**
	 * The directories where Akeeba Engine platforms are stored
	 *
	 * @var      array
	 */
	public static $platformDirs = null;

	/**
	 * Public constructor. Registers the autoloader with PHP.
	 */
	public function __construct()
	{
		self::$enginePath = __DIR__;

		spl_autoload_register([$this, 'autoload_akeeba_engine']);
	}

	/**
	 * Initialise this autoloader
	 *
	 * @return  Autoloader
	 */
	public static function init()
	{
		if (self::$autoloader == null)
		{
			self::$autoloader = new self;
		}

		return self::$autoloader;
	}

	/**
	 * The actual autoloader
	 *
	 * @param   string  $className  The name of the class to load
	 *
	 * @return  void
	 */
	public function autoload_akeeba_engine($className)
	{
		// Trim the trailing backslash
		$className = ltrim($className, '\\');

		// Make sure the class has an Akeeba\Engine prefix
		if (substr($className, 0, 13) != 'Akeeba\\Engine')
		{
			return;
		}

		// Remove the prefix and explode on backslashes
		$className = substr($className, 14);
		$class     = explode('\\', $className);

		// Do we have a list of platform directories?
		if (is_null(self::$platformDirs) && class_exists('\\Akeeba\\Engine\\Platform', false))
		{
			self::$platformDirs = Platform::getPlatformDirectories();

			if (!is_array(self::$platformDirs))
			{
				self::$platformDirs = [];
			}
		}

		$rootPaths = [self::$enginePath];

		if (is_array(self::$platformDirs))
		{
			$rootPaths = array_merge(
				self::$platformDirs, [self::$enginePath]
			);
		}

		foreach ($rootPaths as $rootPath)
		{
			// First try finding in structured directory format (preferred)
			$path = $rootPath . '/' . implode('/', $class) . '.php';

			if (@file_exists($path))
			{
				include_once $path;
			}

			// Then try the duplicate last name structured directory format (not recommended)
			if (!class_exists($className, false))
			{
				reset($class);
				$lastPart = end($class);
				$path     = $rootPath . '/' . implode('/', $class) . '/' . $lastPart . '.php';

				if (@file_exists($path))
				{
					include_once $path;
				}
			}
		}
	}
}

// Register the Akeeba Engine autoloader
Autoloader::init();
PK�!]d��cvvBackupEngine/serverkey.phpnu&1i�<?php defined('AKEEBAENGINE') or die(); define('AKEEBA_SERVERKEY', 'NjdiZmViODZmZjUxY2ZmNDU0YzY4NjkzMDMyOThiMWI='); ?>PK�!]��dYdY&BackupEngine/Archiver/BaseArchiver.phpnu&1i�<?php
/**
 * Akeeba Engine
 *
 * @package   akeebaengine
 * @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License version 3, or later
 *
 * 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, version 3.
 *
 * 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
 * <https://www.gnu.org/licenses/>.
 */

namespace Akeeba\Engine\Archiver;

defined('AKEEBAENGINE') || die();

use Akeeba\Engine\Base\Exceptions\ErrorException;
use Akeeba\Engine\Base\Exceptions\WarningException;
use Akeeba\Engine\Factory;

if (!defined('AKEEBA_CHUNK'))
{
	$configuration = Factory::getConfiguration();
	$chunksize     = $configuration->get('engine.archiver.common.chunk_size', 1048576);
	define('AKEEBA_CHUNK', $chunksize);
}

if (!function_exists('aksubstr'))
{
	/**
	 * Attempt to use mbstring for getting parts of strings
	 *
	 * @param   string    $string
	 * @param   int       $start
	 * @param   int|null  $length
	 *
	 * @return  string
	 */
	function aksubstr($string, $start, $length = null)
	{
		return function_exists('mb_substr') ? mb_substr($string, $start, $length, '8bit') :
			substr($string, $start, $length);
	}
}

/**
 * Abstract class for custom archiver implementations
 */
abstract class BaseArchiver extends BaseFileManagement
{
	/** @var   array  The last part which has been finalized and waits to be post-processed */
	public $finishedPart = [];

	/** @var resource File pointer to the archive being currently written to */
	protected $fp = null;

	/** @var resource File pointer to the archive's central directory file (for ZIP) */
	protected $cdfp = null;

	/** @var string The name of the file holding the archive's data, which becomes the final archive */
	protected $_dataFileName;

	/** @var string Archive full path without extension */
	protected $dataFileNameWithoutExtension = '';

	/** @var bool Should I store symlinks as such (no dereferencing?) */
	protected $storeSymlinkTarget = false;

	/** @var int Part size for split archives, in bytes */
	protected $partSize = 0;

	/** @var bool Should I use Split ZIP? */
	protected $useSplitArchive = false;

	/** @var int Permissions for the backup archive part files */
	protected $permissions = null;

	/**
	 * Release file pointers when the object is being serialized
	 *
	 * @codeCoverageIgnore
	 *
	 * @return  void
	 */
	public function _onSerialize()
	{
		$this->_closeAllFiles();

		$this->fp   = null;
		$this->cdfp = null;
	}

	/**
	 * Release file pointers when the object is being destroyed
	 *
	 * @codeCoverageIgnore
	 *
	 * @return  void
	 */
	public function __destruct()
	{
		$this->_closeAllFiles();

		$this->fp   = null;
		$this->cdfp = null;
	}

	/**
	 * Create a new archive part file (but does NOT open it for writing)
	 *
	 * @param   bool  $finalPart  True if this is the final part
	 *
	 * @return  bool  False if creating a new part fails
	 */
	abstract protected function createNewPartFile($finalPart = false);

	/**
	 * Create a new part file and open it for writing
	 *
	 * @param   bool  $finalPart  Is this the final part?
	 *
	 * @return  void
	 */
	protected function createAndOpenNewPart($finalPart = false)
	{
		@$this->fclose($this->fp);
		$this->fp = null;

		// Not enough space on current part, create new part
		if (!$this->createNewPartFile($finalPart))
		{
			$extension = $this->getExtension();
			$extension = ltrim(strtoupper($extension), '.');

			throw new ErrorException("Could not create new $extension part file " . basename($this->_dataFileName));
		}

		$this->openArchiveForOutput(true);
	}

	/**
	 * Create a new backup archive
	 *
	 * @return  void
	 *
	 * @throws  ErrorException
	 */
	protected function createNewBackupArchive()
	{
		Factory::getLog()->debug(__CLASS__ . " :: Killing old archive");

		$this->fp = $this->fopen($this->_dataFileName, "w");

		if ($this->fp === false)
		{
			if (file_exists($this->_dataFileName))
			{
				@unlink($this->_dataFileName);
			}

			@touch($this->_dataFileName);
			@chmod($this->_dataFileName, 0666);

			$this->fp = $this->fopen($this->_dataFileName, "w");

			if ($this->fp !== false)
			{
				throw new ErrorException("Could not open archive file '{$this->_dataFileName}' for append!");
			}
		}

		@ftruncate($this->fp, 0);
	}

	/**
	 * Opens the backup archive file for output. Returns false if the archive file cannot be opened in binary append
	 * mode.
	 *
	 * @param   bool  $force  Should I forcibly reopen the file? If false, I'll only open the file if the current
	 *                        file pointer is null.
	 *
	 * @return  void
	 */
	protected function openArchiveForOutput($force = false)
	{
		if (is_null($this->fp) || $force)
		{
			$this->fp = $this->fopen($this->_dataFileName, "a");
		}

		if ($this->fp === false)
		{
			$this->fp = null;

			throw new ErrorException("Could not open archive file '{$this->_dataFileName}' for append!");
		}
	}

	/**
	 * Converts a human formatted size to integer representation of bytes,
	 * e.g. 1M to 1024768
	 *
	 * @param   string  $setting  The value in human readable format, e.g. "1M"
	 *
	 * @return  integer  The value in bytes
	 */
	protected function humanToIntegerBytes($setting)
	{
		$val  = trim($setting);
		$last = strtolower($val[strlen($val) - 1]);

		if (is_numeric($last))
		{
			return $setting;
		}

		switch ($last)
		{
			case 't':
				$val *= 1024;
			case 'g':
				$val *= 1024;
			case 'm':
				$val *= 1024;
			case 'k':
				$val *= 1024;
		}

		return (int) $val;
	}

	/**
	 * Get the PHP memory limit in bytes
	 *
	 * @return int|null  Memory limit in bytes or null if we can't figure it out.
	 */
	protected function getMemoryLimit()
	{
		if (!function_exists('ini_get'))
		{
			return null;
		}

		$memLimit = ini_get("memory_limit");

		if ((is_numeric($memLimit) && ($memLimit < 0)) || !is_numeric($memLimit))
		{
			$memLimit = 0; // 1.2a3 -- Rare case with memory_limit < 0, e.g. -1Mb!
		}

		$memLimit = $this->humanToIntegerBytes($memLimit);

		return $memLimit;
	}

	/**
	 * Enable storing of symlink target if we are not on Windows
	 *
	 * @return  void
	 */
	protected function enableSymlinkTargetStorage()
	{
		$configuration       = Factory::getConfiguration();
		$dereferenceSymlinks = $configuration->get('engine.archiver.common.dereference_symlinks', true);

		if ($dereferenceSymlinks)
		{
			return;
		}

		// We are told not to dereference symlinks. Are we on Windows?
		$isWindows = (DIRECTORY_SEPARATOR == '\\');

		if (function_exists('php_uname'))
		{
			$isWindows = stristr(php_uname(), 'windows');
		}

		// If we are not on Windows, enable symlink target storage
		$this->storeSymlinkTarget = !$isWindows;
	}

	/**
	 * Gets the file size and last modification time (also works on virtual files and symlinks)
	 *
	 * @param   string  $sourceNameOrData  File path to the source file or source data (if $isVirtual is true)
	 * @param   bool    $isVirtual         Is this a virtual file?
	 * @param   bool    $isSymlink         Is this a symlink?
	 * @param   bool    $isDir             Is this a directory?
	 *
	 * @return  array
	 */
	protected function getFileSizeAndModificationTime(&$sourceNameOrData, $isVirtual, $isSymlink, $isDir)
	{
		// Get real size before compression
		if ($isVirtual)
		{
			$fileSize    = akstrlen($sourceNameOrData);
			$fileModTime = time();

			return [$fileSize, $fileModTime];
		}


		if ($isSymlink)
		{
			$fileSize    = akstrlen(@readlink($sourceNameOrData));
			$fileModTime = 0;

			return [$fileSize, $fileModTime];
		}

		// Is the file readable?
		if (!is_readable($sourceNameOrData) && !$isDir)
		{
			// Really, REALLY check if it is readable (PHP sometimes lies, dammit!)
			$myFP = @$this->fopen($sourceNameOrData, 'r');

			if ($myFP === false)
			{
				// Unreadable file, skip it.
				throw new WarningException('Unreadable file ' . $sourceNameOrData . '. Check permissions');
			}

			@$this->fclose($myFP);
		}

		// Get the file size
		$fileSize    = $isDir ? 0 : @filesize($sourceNameOrData);
		$fileModTime = $isDir ? 0 : @filemtime($sourceNameOrData);

		return [$fileSize, $fileModTime];
	}

	/**
	 * Get the preferred compression method for a file
	 *
	 * @param   int   $fileSize   File size in bytes
	 * @param   int   $memLimit   Memory limit in bytes
	 * @param   bool  $isDir      Is it a directory?
	 * @param   bool  $isSymlink  Is it a symlink?
	 *
	 * @return  int  Compression method to use: 0 (uncompressed) or 1 (gzip deflate)
	 */
	protected function getCompressionMethod($fileSize, $memLimit, $isDir, $isSymlink)
	{
		// If we don't have gzip installed we can't compress anything
		if (!function_exists("gzcompress"))
		{
			return 0;
		}

		// Don't compress directories or symlinks
		if ($isDir || $isSymlink)
		{
			return 0;
		}

		// Do not compress files over the compression threshold
		if ($fileSize >= _AKEEBA_COMPRESSION_THRESHOLD)
		{
			return 0;
		}

		// No memory limit, file smaller than the compression threshold: always compress.
		if (is_numeric($memLimit) && ($memLimit == 0))
		{
			return 1;
		}

		// Non-zero memory limit, PHP can report memory usage, see if there's enough memory.
		if (is_numeric($memLimit) && function_exists("memory_get_usage"))
		{
			$availableRAM = $memLimit - memory_get_usage();
			// Conservative approach: if the file size is over 40% of the available memory we won't compress.
			$compressionMethod = (($availableRAM / 2.5) >= $fileSize) ? 1 : 0;

			return $compressionMethod;
		}

		// Non-zero memory limit, PHP can't report memory usage, compress only files up to 512Kb (very conservative)
		return ($fileSize <= 524288) ? 1 : 0;
	}

	/**
	 * Checks if the file exists and is readable
	 *
	 * @param   string  $sourceNameOrData  The path to the file being compressed, or the raw file data for virtual files
	 * @param   bool    $isVirtual         Is this a virtual file?
	 * @param   bool    $isSymlink         Is this a symlink?
	 * @param   bool    $isDir             Is this a directory?
	 *
	 * @return  void
	 *
	 * @throws  WarningException
	 */
	protected function testIfFileExists(&$sourceNameOrData, &$isVirtual, &$isDir, &$isSymlink)
	{
		if ($isVirtual || $isDir)
		{
			return;
		}

		if (!@file_exists($sourceNameOrData))
		{
			if ($isSymlink)
			{
				throw new WarningException('The symlink ' . $sourceNameOrData . ' points to a file or folder that no longer exists and will NOT be backed up.');
			}

			throw new WarningException('The file ' . $sourceNameOrData . ' no longer exists and will NOT be backed up. Are you backing up temporary or cache data?');
		}

		if (!@is_readable($sourceNameOrData))
		{
			throw new WarningException('Unreadable file ' . $sourceNameOrData . '. Check permissions.');
		}
	}

	/**
	 * Try to get the compressed data for a file
	 *
	 * @param   string  $sourceNameOrData
	 * @param   bool    $isVirtual
	 * @param   int     $compressionMethod
	 * @param   string  $zdata
	 * @param   int     $unc_len
	 * @param   int     $c_len
	 *
	 * @return  void
	 */
	protected function getZData(&$sourceNameOrData, &$isVirtual, &$compressionMethod, &$zdata, &$unc_len, &$c_len)
	{
		// Get uncompressed data
		$udata =& $sourceNameOrData;

		if (!$isVirtual)
		{
			$udata = @file_get_contents($sourceNameOrData);
		}

		// If the compression fails, we will let it behave like no compression was available
		$c_len             = $unc_len;
		$compressionMethod = 0;

		// Proceed with compression
		$zdata = @gzcompress($udata);

		if ($zdata !== false)
		{
			// The compression succeeded
			unset($udata);
			$compressionMethod = 1;
			$zdata             = aksubstr($zdata, 2, -4);
			$c_len             = akstrlen($zdata);
		}
	}

	/**
	 * Returns the bytes available for writing data to the current part file (i.e. part size minus current offset)
	 *
	 * @return  int
	 */
	protected function getPartFreeSize()
	{
		clearstatcache();
		$current_part_size = @filesize($this->_dataFileName);

		return (int) $this->partSize - ($current_part_size === false ? 0 : $current_part_size);
	}

	/**
	 * Enable split archive creation where possible
	 *
	 * @return  void
	 */
	protected function enableSplitArchives()
	{
		$configuration = Factory::getConfiguration();
		$partSize      = $configuration->get('engine.archiver.common.part_size', 0);

		// If the part size is less than 64Kb we won't enable split archives
		if ($partSize < 65536)
		{
			return;
		}

		$extension            = $this->getExtension();
		$altExtension         = substr($extension, 0, 2) . '01';
		$archiveTypeUppercase = strtoupper(substr($extension, 1));

		Factory::getLog()->info(__CLASS__ . " :: Split $archiveTypeUppercase creation enabled");

		$this->useSplitArchive              = true;
		$this->partSize                     = $partSize;
		$this->dataFileNameWithoutExtension =
			dirname($this->_dataFileName) . '/' . basename($this->_dataFileName, $extension);
		$this->_dataFileName                = $this->dataFileNameWithoutExtension . $altExtension;

		// Indicate that we have at least 1 part
		$statistics = Factory::getStatistics();
		$statistics->updateMultipart(1);
	}

	/**
	 * Write a file's GZip compressed data to the archive, taking into account archive splitting
	 *
	 * @param   string  $zdata  The compressed data to write to the archive
	 *
	 * @return  void
	 */
	protected function putRawDataIntoArchive(&$zdata)
	{
		// Single part archive. Just dump the compressed data.
		if (!$this->useSplitArchive)
		{
			$this->fwrite($this->fp, $zdata);

			return;
		}

		// Split JPA. Check if we need to split the part in the middle of the data.
		$freeSpaceInPart = $this->getPartFreeSize();

		// Nope. We have enough space to write all of the data in this part.
		if ($freeSpaceInPart >= akstrlen($zdata))
		{
			$this->fwrite($this->fp, $zdata);

			return;
		}

		$bytesLeftInData = akstrlen($zdata);

		while ($bytesLeftInData > 0)
		{
			// Try to write to the archive. We can only write as much bytes as the free space in the backup archive OR
			// the total data bytes left, whichever is lower.
			$bytesWritten = $this->fwrite($this->fp, $zdata, min($bytesLeftInData, $freeSpaceInPart));

			// Since we may have written fewer bytes than anticipated we use the real bytes written for calculations
			$freeSpaceInPart -= $bytesWritten;
			$bytesLeftInData -= $bytesWritten;

			// If we still have data to write, remove the part already written and keep the rest
			if ($bytesLeftInData > 0)
			{
				$zdata = aksubstr($zdata, -$bytesLeftInData);
			}

			// If the part file is full create a new one
			if ($freeSpaceInPart <= 0)
			{
				// Create new part
				$this->createAndOpenNewPart();

				// Get its free space
				$freeSpaceInPart = $this->getPartFreeSize();
			}
		}

		// Tell PHP to free up some memory
		$zdata = null;
	}

	/**
	 * Begin or resume adding an uncompressed file into the archive.
	 *
	 * IMPORTANT! Only this case can be spanned across steps: uncompressed, non-virtual data
	 *
	 * @param   string  $sourceNameOrData  The path to the file we are reading from.
	 * @param   int     $fileLength        The file size we are supposed to read, in bytes.
	 * @param   int     $resumeOffset      Offset in the file to resume reading from
	 *
	 * @return  bool  True to indicate more processing is required in the next step
	 */
	protected function putUncompressedFileIntoArchive(&$sourceNameOrData, $fileLength = 0, $resumeOffset = null)
	{
		// Copy the file contents, ignore directories
		$sourceFilePointer = @fopen($sourceNameOrData, "r");

		if ($sourceFilePointer === false)
		{
			// If we have already written the file header and can't read the data your archive is busted.
			throw new ErrorException('Unreadable file ' . $sourceNameOrData . '. Check permissions. Your archive is corrupt!');
		}

		// Seek to the resume point if required
		if (!is_null($resumeOffset))
		{
			// Seek to new offset
			$seek_result = @fseek($sourceFilePointer, $resumeOffset);

			if ($seek_result === -1)
			{
				// What?! We can't resume!
				$this->conditionalFileClose($sourceFilePointer);

				throw new ErrorException(sprintf('Could not resume packing of file %s. Your archive is damaged!', $sourceNameOrData));
			}

			// Change the uncompressed size to reflect the remaining data
			$fileLength -= $resumeOffset;
		}

		$mustBreak = $this->putDataFromFileIntoArchive($sourceFilePointer, $fileLength);

		$this->conditionalFileClose($sourceFilePointer);

		return $mustBreak;
	}

	/**
	 * Return the requested permissions for the backup archive file.
	 *
	 * @return  int
	 * @since   8.0.0
	 */
	protected function getPermissions(): int
	{
		if (!is_null($this->permissions))
		{
			return $this->permissions;
		}

		$configuration     = Factory::getConfiguration();
		$permissions       = $configuration->get('engine.archiver.common.permissions', '0666') ?: '0666';
		$this->permissions = octdec($permissions);

		return $this->permissions;
	}

	/**
	 * Put up to $fileLength bytes of the file pointer $sourceFilePointer into the backup archive. Returns true if we
	 * ran out of time and need to perform a step break. Returns false when the whole quantity of data has been copied.
	 * Throws an ErrorException if something terrible happens.
	 *
	 * @param   resource  $sourceFilePointer  The pointer to the input file
	 * @param   int       $fileLength         How many bytes to copy
	 *
	 * @return  bool  True to indicate we need to resume packing the file in the next step
	 */
	private function putDataFromFileIntoArchive(&$sourceFilePointer, &$fileLength)
	{
		// Get references to engine objects we're going to be using
		$configuration = Factory::getConfiguration();
		$timer         = Factory::getTimer();
		$isEOF         = false;

		// Quick copy data into the archive, AKEEBA_CHUNK bytes at a time
		while (!$isEOF && ($timer->getTimeLeft() > 0) && ($fileLength > 0))
		{
			// Normally I read up to AKEEBA_CHUNK bytes at a time, unless the remaining $fileLength is smaller.
			$chunkSize = min(AKEEBA_CHUNK, $fileLength);

			// Do I have a split ZIP?
			if ($this->useSplitArchive)
			{
				// I must only read up to the free space in the part file if it's less than AKEEBA_CHUNK.
				$free_space = $this->getPartFreeSize();
				$chunkSize  = min($free_space, AKEEBA_CHUNK);

				// If I ran out of free space I have to create a new part file.
				if ($free_space <= 0)
				{
					$this->createAndOpenNewPart();

					// We have created the part. If the user asked for immediate post-proc, break step now.
					if ($configuration->get('engine.postproc.common.after_part', 0))
					{
						$resumeOffset = @ftell($sourceFilePointer);
						$this->conditionalFileClose($sourceFilePointer);

						$configuration->set('volatile.engine.archiver.resume', $resumeOffset);
						$configuration->set('volatile.engine.archiver.processingfile', true);
						$configuration->set('volatile.breakflag', true);

						// Always close the open part when immediate post-processing is requested
						@$this->fclose($this->fp);
						$this->fp = null;

						return true;
					}

					// No immediate post-proc. Recalculate the optimal chunk size.
					$free_space = $this->getPartFreeSize();
					$chunkSize  = min($free_space, AKEEBA_CHUNK);
				}
			}

			// Read some data and write it to the backup archive part file
			$data         = fread($sourceFilePointer, $chunkSize);
			$bytesWritten = $this->fwrite($this->fp, $data, akstrlen($data));

			// Subtract the written bytes from the bytes left to write
			$fileLength -= $bytesWritten;

			/**
			 * Have we reached the End of File?
			 *
			 * When we have read _exactly_ as many bytes as the size of the file we have not, in fact, reached EOF. We
			 * reach the EOF if we try to read _beyond_ the actual end of file.
			 *
			 * So, if we have read exactly as many bytes as the file claimed to be at the start of backup (i.e. the
			 * $fileLength is now 0) we try to read one more byte. If this returns no data (or false, e.g. the file went
			 * away in the meantime) OR PHP reports we reached EOF then we consider we've reached EOF.
			 *
			 * If the file grew in size in the meantime this condition will fail and $isEOF will be false. We will still
			 * exit the while loop because $fileLength === 0 but the next if-block after the while-block will catch this
			 * discrepancy and issue a warning that the file grew in size.
			 */
			$isEOF = feof($sourceFilePointer);

			if (!$isEOF && $fileLength === 0)
			{
				$junk = fread($sourceFilePointer, 1);
				$isEOF = (($junk === false) || (is_string($junk) && strlen($junk) === 0)) || feof($sourceFilePointer);
				fseek($sourceFilePointer, -1, SEEK_CUR);
			}
		}

		/**
		 * We have finished reading the entire file as per its size before we started backing it up. However, we still
		 * have not reached EOF (there's more to the file). This means that the file grew in size in the meantime. Warn
		 * the user.
		 */
		if (!$isEOF && ($timer->getTimeLeft() > 0) && ($fileLength === 0))
		{
			Factory::getLog()->warning(
				'The file grew in size while putting it in the backup archive. If this is a temporary or cache file we advise you to exclude it, or exclude the contents of the temporary / cache folder it is contained in.'
			);
		}

		/**
		 * According to the file size we read when we were writing the file header we have more data to write. However,
		 * we reached the end of the file. This means the file went away or shrunk. We can't reliably go back and
		 * change the file header since it may be in a previous part file that's already been post-processed. All we can
		 * do is try to warn the user.
		 */
		if ($isEOF && ($timer->getTimeLeft() > 0) && ($fileLength > 0))
		{
			throw new ErrorException(
				'The file shrunk or went away while putting it in the backup archive. Your archive is damaged! If this is a temporary or cache file we advise you to exclude it, or exclude the contents of the temporary / cache folder it is contained in.'
			);
		}

		// WARNING!!! The extra $unc_len != 0 check is necessary as PHP won't reach EOF for 0-byte files.
		if (!feof($sourceFilePointer) && ($fileLength != 0))
		{
			// We have to break, or we'll time out!
			$resumeOffset = @ftell($sourceFilePointer);
			$this->conditionalFileClose($sourceFilePointer);

			$configuration->set('volatile.engine.archiver.resume', $resumeOffset);
			$configuration->set('volatile.engine.archiver.processingfile', true);

			return true;
		}

		return false;
	}
}
PK�!]�3�pPPBackupEngine/Archiver/jpa.jsonnu&1i�{
    "_information": {
        "title": "COM_AKEEBA_CONFIG_ENGINE_ARCHIVER_JPA_TITLE",
        "description": "COM_AKEEBA_CONFIG_ENGINE_ARCHIVER_JPA_DESCRIPTION"
    },
    "engine.archiver.common.dereference_symlinks": {
        "default": "0",
        "type": "bool",
        "title": "COM_AKEEBA_CONFIG_DEREFERENCESYMLINKS_TITLE",
        "description": "COM_AKEEBA_CONFIG_DEREFERENCESYMLINKS_DESCRIPTION"
    },
    "engine.archiver.common.part_size": {
        "default": "0",
        "type": "integer",
        "min": "0",
        "max": "2147483648",
        "shortcuts": "0|131072|262144|524288|1048576|2097152|5242880|10485760|20971520|52428800|104857600|268435456|536870912|1073741824|1610612736|2097152000",
        "scale": "1048576",
        "uom": "MB",
        "title": "COM_AKEEBA_CONFIG_PARTSIZE_TITLE",
        "description": "COM_AKEEBA_CONFIG_PARTSIZE_DESCRIPTION"
    },
    "engine.archiver.common.permissions": {
        "default": "0666",
        "type": "enum",
        "enumkeys": "COM_AKEEBA_CONFIG_PERMISSIONS_0600|COM_AKEEBA_CONFIG_PERMISSIONS_0644|COM_AKEEBA_CONFIG_PERMISSIONS_0666",
        "enumvalues": "0600|0644|0666",
        "title": "COM_AKEEBA_CONFIG_PERMISSIONS_TITLE",
        "description": "COM_AKEEBA_CONFIG_PERMISSIONS_DESCRIPTION"
    },
    "engine.archiver.common.chunk_size": {
        "default": "1048576",
        "type": "integer",
        "min": "65536",
        "max": "10485760",
        "shortcuts": "65536|131072|262144|524288|1048576|2097152|5242880|10485760",
        "scale": "1048576",
        "uom": "MB",
        "title": "COM_AKEEBA_CONFIG_CHUNKSIZE_TITLE",
        "description": "COM_AKEEBA_CONFIG_CHUNKSIZE_DESCRIPTION"
    },
    "engine.archiver.common.big_file_threshold": {
        "default": "1048576",
        "type": "integer",
        "min": "65536",
        "max": "10485760",
        "shortcuts": "65536|131072|262144|524288|1048576|2097152|5242880|10485760",
        "scale": "1048576",
        "uom": "MB",
        "title": "COM_AKEEBA_CONFIG_BIGFILETHRESHOLD_TITLE",
        "description": "COM_AKEEBA_CONFIG_BIGFILETHRESHOLD_DESCRIPTION"
    }
}PK�!]��y�
[
[BackupEngine/Archiver/Base.phpnu&1i�<?php
/**
 * Akeeba Engine
 *
 * @package   akeebaengine
 * @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License version 3, or later
 *
 * 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, version 3.
 *
 * 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
 * <https://www.gnu.org/licenses/>.
 */

namespace Akeeba\Engine\Archiver;

defined('AKEEBAENGINE') || die();

use Akeeba\Engine\Base\Exceptions\ErrorException;
use Akeeba\Engine\Base\Exceptions\WarningException;
use Akeeba\Engine\Factory;
use Akeeba\Engine\Platform;
use Akeeba\Engine\Util\FileCloseAware;
use Akeeba\Engine\Util\FileSystem;
use Exception;
use RuntimeException;

/**
 * Abstract parent class of all archiver engines
 */
abstract class Base
{
	use FileCloseAware;

	/** @var   string  The archive's comment. It's currently used ONLY in the ZIP file format */
	protected $_comment;

	/** @var Filesystem Filesystem utilities object */
	protected $fsUtils = null;

	/** @var   resource  JPA transformation source handle */
	private $_xform_fp;

	/** @var   int  The total size of the source JPA file */
	private $totalSourceJPASize = 0;

	/**
	 * Public constructor
	 *
	 * @codeCoverageIgnore
	 *
	 * @return  void
	 */
	public function __construct()
	{
		$this->__bootstrap_code();
	}

	/**
	 * Wakeup (unserialization) function
	 *
	 * @codeCoverageIgnore
	 *
	 * @return  void
	 */
	public function __wakeup()
	{
		$this->__bootstrap_code();
	}

	/**
	 * Adds a single file in the archive
	 *
	 * @param   string  $file        The absolute path to the file to add
	 * @param   string  $removePath  Path to remove from $file
	 * @param   string  $addPath     Path to prepend to $file
	 *
	 * @return  void
	 *
	 * @throws  Exception
	 */
	public final function addFile($file, $removePath = '', $addPath = '')
	{
		$storedName = $this->addRemovePaths($file, $removePath, $addPath);

		$this->addFileRenamed($file, $storedName);
	}

	/**
	 * Adds a list of files into the archive, removing $removePath from the
	 * file names and adding $addPath to them.
	 *
	 * @param   array   $fileList    A simple string array of filepaths to include
	 * @param   string  $removePath  Paths to remove from the filepaths
	 * @param   string  $addPath     Paths to add in front of the filepaths
	 *
	 * @return  void
	 *
	 * @throws Exception
	 */
	public final function addFileList(&$fileList, $removePath = '', $addPath = '')
	{
		if (!is_array($fileList))
		{
			Factory::getLog()->warning('addFileList called without a file list array');

			return;
		}

		foreach ($fileList as $file)
		{
			$this->addFile($file, $removePath, $addPath);
		}
	}

	/**
	 * Adds a file to the archive, with a name that's different from the source
	 * filename
	 *
	 * @param   string  $sourceFile  Absolute path to the source file
	 * @param   string  $targetFile  Relative filename to store in archive
	 *
	 * @return  void
	 *
	 * @throws  Exception
	 */
	public function addFileRenamed($sourceFile, $targetFile)
	{
		$mb_encoding = '8bit';

		if (function_exists('mb_internal_encoding'))
		{
			$mb_encoding = mb_internal_encoding();
			mb_internal_encoding('ISO-8859-1');
		}

		try
		{
			$this->_addFile(false, $sourceFile, $targetFile);
		}
		catch (WarningException $e)
		{
			Factory::getLog()->warning($e->getMessage());
		}
		finally
		{
			if (function_exists('mb_internal_encoding'))
			{
				mb_internal_encoding($mb_encoding);
			}
		}
	}

	/**
	 * Adds a file to the archive, given the stored name and its contents
	 *
	 * @param   string  $fileName        The base file name
	 * @param   string  $addPath         The relative path to prepend to file name
	 * @param   string  $virtualContent  The contents of the file to be archived
	 *
	 * @return  void
	 */
	public final function addFileVirtual($fileName, $addPath, &$virtualContent)
	{
		$storedName  = $this->addRemovePaths($fileName, '', $addPath);
		$mb_encoding = '8bit';

		if (function_exists('mb_internal_encoding'))
		{
			$mb_encoding = mb_internal_encoding();
			mb_internal_encoding('ISO-8859-1');
		}

		try
		{
			$this->_addFile(true, $virtualContent, $storedName);
		}
		catch (WarningException $e)
		{
			Factory::getLog()->warning($e->getMessage());
		}
		finally
		{
			if (function_exists('mb_internal_encoding'))
			{
				mb_internal_encoding($mb_encoding);
			}
		}
	}

	/**
	 * Adds a file to the archive, given the stored name and its contents
	 *
	 * @param   string  $fileName        The base file name
	 * @param   string  $addPath         The relative path to prepend to file name
	 * @param   string  $virtualContent  The contents of the file to be archived
	 *
	 * @return  void
	 *
	 * @deprecated 7.0.0
	 */
	public final function addVirtualFile($fileName, $addPath, &$virtualContent)
	{
		Factory::getLog()->debug('DEPRECATED: addVirtualFile() has been renamed to addFileVirtual().');

		$this->addFileVirtual($fileName, $addPath, $virtualContent);
	}

	/**
	 * Makes whatever finalization is needed for the archive to be considered
	 * complete and useful (or, generally, clean up)
	 *
	 * @return  void
	 */
	abstract public function finalize();

	/**
	 * Returns a string with the extension (including the dot) of the files produced
	 * by this class.
	 *
	 * @return  string
	 */
	abstract public function getExtension();

	/**
	 * Initialises the archiver class, creating the archive from an existent
	 * installer's JPA archive. MUST BE OVERRIDEN BY CHILDREN CLASSES.
	 *
	 * @param   string  $targetArchivePath  Absolute path to the generated archive
	 * @param   array   $options            A named key array of options (optional)
	 *
	 * @return  void
	 */
	abstract public function initialize($targetArchivePath, $options = []);

	/**
	 * Notifies the engine on the backup comment and converts it to plain text for
	 * inclusion in the archive file, if applicable.
	 *
	 * @param   string  $comment  The archive's comment
	 *
	 * @return  void
	 */
	public function setComment($comment)
	{
		// First, sanitize the comment in a text-only format
		$comment        = str_replace("\n", " ", $comment); // Replace newlines with spaces
		$comment        = str_replace("<br>", "\n", $comment); // Replace HTML4 <br> with single newlines
		$comment        = str_replace("<br/>", "\n", $comment); // Replace HTML4 <br> with single newlines
		$comment        = str_replace("<br />", "\n", $comment); // Replace HTML <br /> with single newlines
		$comment        = str_replace("</p>", "\n\n", $comment); // Replace paragraph endings with double newlines
		$comment        = str_replace("<b>", "*", $comment); // Replace bold with star notation
		$comment        = str_replace("</b>", "*", $comment); // Replace bold with star notation
		$comment        = str_replace("<i>", "_", $comment); // Replace italics with underline notation
		$comment        = str_replace("</i>", "_", $comment); // Replace italics with underline notation
		$this->_comment = strip_tags($comment, '');
	}

	/**
	 * Transforms a JPA archive (containing an installer) to the native archive format
	 * of the class. It actually extracts the source JPA in memory and instructs the
	 * class to include each extracted file.
	 *
	 * @codeCoverageIgnore
	 *
	 * @param   integer  $index   The index in the source JPA archive's list currently in use
	 * @param   integer  $offset  The source JPA archive's offset to use
	 *
	 * @return  array|bool  False if an error occurred, return array otherwise
	 */
	public function transformJPA($index, $offset)
	{
		$xform_source = null;

		// Do we have to open the file?
		if (!$this->_xform_fp)
		{
			// Get the source path
			$registry           = Factory::getConfiguration();
			$embedded_installer = $registry->get('akeeba.advanced.embedded_installer');

			// Fetch the name of the installer image
			$installerDescriptors = Factory::getEngineParamsProvider()->getInstallerList();
			$xform_source         = Platform::getInstance()->get_installer_images_path() .
				'/foobar.jpa'; // We need this as a "safe fallback"

			// Try to find a sane default if we are not given a valid embedded installer
			if (!array_key_exists($embedded_installer, $installerDescriptors))
			{
				$embedded_installer = 'angie';

				if (!array_key_exists($embedded_installer, $installerDescriptors))
				{
					$allInstallers = array_keys($installerDescriptors);

					foreach ($allInstallers as $anInstaller)
					{
						if ($anInstaller == 'none')
						{
							continue;
						}

						$embedded_installer = $anInstaller;
						break;
					}
				}
			}

			if (array_key_exists($embedded_installer, $installerDescriptors))
			{
				$packages  = $installerDescriptors[$embedded_installer]['package'] ?? '';
				$langPacks = $installerDescriptors[$embedded_installer]['language'] ?? '';

				if (empty($packages))
				{
					// No installer package specified. Pretend we are done!
					$retArray = [
						"filename" => '', // File name extracted
						"data"     => '', // File data
						"index"    => 0, // How many source JPA files I have
						"offset"   => 0, // Offset in JPA file
						"skip"     => false, // Skip this?
						"done"     => true, // Are we done yet?
						"filesize" => 0,
					];

					return $retArray;
				}

				$packages                 = explode(',', $packages);
				$langPacks                = explode(',', $langPacks);
				$this->totalSourceJPASize = 0;
				$pathPrefix               = Platform::getInstance()->get_installer_images_path() . '/';

				foreach ($packages as $package)
				{
					$filePath                 = $pathPrefix . $package;
					$this->totalSourceJPASize += (int) @filesize($filePath);
				}

				foreach ($langPacks as $langPack)
				{
					$filePath = $pathPrefix . $langPack;

					if (!is_file($filePath))
					{
						continue;
					}

					$packages[]               = $langPack;
					$this->totalSourceJPASize += (int) @filesize($filePath);
				}

				if (count($packages) < $index)
				{
					throw new RuntimeException(__CLASS__ . ":: Installer package index $index not found for embedded installer $embedded_installer");
				}

				$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 (!is_null($xform_source) && !file_exists($xform_source) && (basename($xform_source) != 'angie.jpa'))
			{
				throw new RuntimeException(__CLASS__ . ":: Installer package $xform_source of embedded installer $embedded_installer not found. Please go to the configuration page, select an Embedded Installer, save the configuration and try backing up again.");
			}

			// Try opening the file
			if (!is_null($xform_source) && file_exists($xform_source))
			{
				$this->_xform_fp = @fopen($xform_source, 'r');

				if ($this->_xform_fp === false)
				{
					throw new RuntimeException(__CLASS__ . ":: Can't seed archive with installer package " . $xform_source);
				}
			}
			else
			{
				throw new RuntimeException(__CLASS__ . ":: Installer package " . $xform_source . " does not exist!");
			}
		}

		$headerDataLength = 0;

		if (!$offset)
		{
			// First run detected!
			Factory::getLog()->debug('Initializing with JPA package ' . $xform_source);

			// Skip over the header and check no problem exists
			$offset = $this->_xformReadHeader();

			if ($offset === false)
			{
				throw new RuntimeException('JPA package file was not read');
			}

			$headerDataLength = $offset;
		}

		$ret = $this->_xformExtract($offset);

		$ret['index'] = $index;

		if (is_array($ret))
		{
			$ret['chunkProcessed'] = $headerDataLength + $ret['offset'] - $offset;
			$offset                = $ret['offset'];

			if (!$ret['skip'] && !$ret['done'])
			{
				Factory::getLog()->debug('  Adding ' . $ret['filename'] . '; Next offset:' . $offset);

				$this->addFileVirtual($ret['filename'], '', $ret['data']);
			}
			elseif ($ret['done'])
			{
				$registry             = Factory::getConfiguration();
				$embedded_installer   = $registry->get('akeeba.advanced.embedded_installer');
				$installerDescriptors = Factory::getEngineParamsProvider()->getInstallerList();
				$packages             = $installerDescriptors[$embedded_installer]['package'];
				$packages             = explode(',', $packages);
				$pathPrefix           = Platform::getInstance()->get_installer_images_path() . '/';
				$langPacks            = $installerDescriptors[$embedded_installer]['language'];
				$langPacks            = explode(',', $langPacks);

				foreach ($langPacks as $langPack)
				{
					$filePath = $pathPrefix . $langPack;

					if (!is_file($filePath))
					{
						continue;
					}

					$packages[] = $langPack;
				}

				Factory::getLog()->debug('  Done with package ' . $packages[$index]);

				if (count($packages) > ($index + 1))
				{
					$ret['done']     = false;
					$ret['index']    = $index + 1;
					$ret['offset']   = 0;
					$this->_xform_fp = null;
				}
				else
				{
					Factory::getLog()->debug('  Done with installer seeding.');
				}
			}
			else
			{
				$reason = '  Skipping ' . $ret['filename'];
				Factory::getLog()->debug($reason);
			}
		}
		else
		{
			throw new RuntimeException('JPA extraction returned FALSE. The installer image is corrupt.');
		}

		if ($ret['done'])
		{
			// We are finished! Close the file
			$this->conditionalFileClose($this->_xform_fp);
			Factory::getLog()->debug('Initializing with JPA package has finished');
		}

		$ret['filesize'] = $this->totalSourceJPASize;

		return $ret;
	}

	/**
	 * Common code which gets called on instance creation or wake-up (unserialization)
	 *
	 * @codeCoverageIgnore
	 *
	 * @return  void
	 */
	protected function __bootstrap_code()
	{
		$this->fsUtils = Factory::getFilesystemTools();
	}

	/**
	 * The most basic file transaction: add a single entry (file or directory) to
	 * the archive.
	 *
	 * @param   boolean  $isVirtual         If true, the next parameter contains file data instead of a file name
	 * @param   string   $sourceNameOrData  Absolute file name to read data from or the file data itself is $isVirtual
	 *                                      is true
	 * @param   string   $targetName        The (relative) file name under which to store the file in the archive
	 *
	 * @return  boolean  True on success, false otherwise. DEPRECATED: Use exceptions instead.
	 *
	 * @throws  WarningException  When there's a warning (the backup integrity is NOT compromised)
	 * @throws  ErrorException    When there's an error (the backup integrity is compromised – backup dead)
	 */
	abstract protected function _addFile($isVirtual, &$sourceNameOrData, $targetName);

	/**
	 * This function indicates if the path $p_path is under the $p_dir tree. Or,
	 * said in an other way, if the file or sub-dir $p_path is inside the dir
	 * $p_dir.
	 * The function indicates also if the path is exactly the same as the dir.
	 * This function supports path with duplicated '/' like '//', but does not
	 * support '.' or '..' statements.
	 *
	 * Copied verbatim from pclZip library
	 *
	 * @codeCoverageIgnore
	 *
	 * @param   string  $p_dir   Source tree
	 * @param   string  $p_path  Check if this is part of $p_dir
	 *
	 * @return  integer   0 if $p_path is not inside directory $p_dir,
	 *                    1 if $p_path is inside directory $p_dir
	 *                    2 if $p_path is exactly the same as $p_dir
	 */
	private function _PathInclusion($p_dir, $p_path)
	{
		$v_result = 1;

		// ----- Explode dir and path by directory separator
		$v_list_dir       = explode("/", $p_dir);
		$v_list_dir_size  = count($v_list_dir);
		$v_list_path      = explode("/", $p_path);
		$v_list_path_size = count($v_list_path);

		// ----- Study directories paths
		$i = 0;
		$j = 0;

		while (($i < $v_list_dir_size) && ($j < $v_list_path_size) && ($v_result))
		{
			// ----- Look for empty dir (path reduction)
			if ($v_list_dir[$i] == '')
			{
				$i++;

				continue;
			}

			if ($v_list_path[$j] == '')
			{
				$j++;

				continue;
			}

			// ----- Compare the items
			if (($v_list_dir[$i] != $v_list_path[$j]) && ($v_list_dir[$i] != '') && ($v_list_path[$j] != ''))
			{
				$v_result = 0;
			}

			// ----- Next items
			$i++;
			$j++;
		}

		// ----- Look if everything seems to be the same
		if ($v_result)
		{
			// ----- Skip all the empty items
			while (($j < $v_list_path_size) && ($v_list_path[$j] == ''))
			{
				$j++;
			}

			while (($i < $v_list_dir_size) && ($v_list_dir[$i] == ''))
			{
				$i++;
			}

			if (($i >= $v_list_dir_size) && ($j >= $v_list_path_size))
			{
				// ----- There are exactly the same
				$v_result = 2;
			}
			else if ($i < $v_list_dir_size)
			{
				// ----- The path is shorter than the dir
				$v_result = 0;
			}
		}

		// ----- Return
		return $v_result;
	}

	/**
	 * Extracts a file from the JPA archive and returns an in-memory array containing it
	 * and its file data. The data returned is an array, consisting of the following keys:
	 * "filename" => relative file path stored in the archive
	 * "data"     => file data
	 * "offset"   => next offset to use
	 * "skip"     => if this is not a file, just skip it...
	 * "done"     => No more files left in archive
	 *
	 * @codeCoverageIgnore
	 *
	 * @param   integer  $offset  The absolute data offset from archive's header
	 *
	 * @return  array|bool  See description for more information
	 */
	private function &_xformExtract($offset)
	{
		$false = false; // Used to return false values in case an error occurs

		// Generate a return array
		$retArray = [
			"filename" => '', // File name extracted
			"data"     => '', // File data
			"offset"   => 0, // Offset in ZIP file
			"skip"     => false, // Skip this?
			"done"     => false // Are we done yet?
		];

		// If we can't open the file, return an error condition
		if ($this->_xform_fp === false)
		{
			return $false;
		}

		// Go to the offset specified
		if (!fseek($this->_xform_fp, $offset) == 0)
		{
			return $false;
		}

		// Get and decode Entity Description Block
		$signature = fread($this->_xform_fp, 3);

		// Check signature
		if ($signature == 'JPF')
		{
			// This a JPA Entity Block. Process the header.

			// Read length of EDB and of the Entity Path Data
			$length_array = unpack('vblocksize/vpathsize', fread($this->_xform_fp, 4));
			// Read the path data
			$file = fread($this->_xform_fp, $length_array['pathsize']);
			// Read and parse the known data portion
			$bin_data    = fread($this->_xform_fp, 14);
			$header_data = unpack('Ctype/Ccompression/Vcompsize/Vuncompsize/Vperms', $bin_data);
			// Read any unknwon data
			$restBytes = $length_array['blocksize'] - (21 + $length_array['pathsize']);

			if ($restBytes > 0)
			{
				$junk = fread($this->_xform_fp, $restBytes);
			}

			$compressionType = $header_data['compression'];

			// Populate the return array
			$retArray['filename'] = $file;
			$retArray['skip']     = ($header_data['compsize'] == 0); // Skip over directories

			switch ($header_data['type'])
			{
				case 0:
					// directory
					break;

				case 1:
					// file
					switch ($compressionType)
					{
						case 0: // No compression
							if ($header_data['compsize'] > 0) // 0 byte files do not have data to be read
							{
								$retArray['data'] = fread($this->_xform_fp, $header_data['compsize']);
							}
							break;

						case 1: // GZip compression
							$zipData          = fread($this->_xform_fp, $header_data['compsize']);
							$retArray['data'] = gzinflate($zipData);
							break;

						case 2: // BZip2 compression
							$zipData          = fread($this->_xform_fp, $header_data['compsize']);
							$retArray['data'] = bzdecompress($zipData);
							break;
					}
					break;
			}
		}
		else
		{
			// This is not a file header. This means we are done.
			$retArray['done'] = true;
		}

		$retArray['offset'] = ftell($this->_xform_fp);

		return $retArray;
	}

	/**
	 * Skips over the JPA header entry and returns the offset file data starts from
	 *
	 * @codeCoverageIgnore
	 *
	 * @return  boolean|integer  False on failure, offset otherwise
	 */
	private function _xformReadHeader()
	{
		// Fail for unreadable files
		if ($this->_xform_fp === false)
		{
			return false;
		}

		// Go to the beggining of the file
		rewind($this->_xform_fp);

		// Read the signature
		$sig = fread($this->_xform_fp, 3);

		// Not a JPA Archive?
		if ($sig != 'JPA')
		{
			return false;
		}

		// Read and parse header length
		$header_length_array = unpack('v', fread($this->_xform_fp, 2));
		$header_length       = $header_length_array[1];

		// Read and parse the known portion of header data (14 bytes)
		$bin_data    = fread($this->_xform_fp, 14);
		$header_data = unpack('Cmajor/Cminor/Vcount/Vuncsize/Vcsize', $bin_data);

		// Load any remaining header data (forward compatibility)
		$rest_length = $header_length - 19;

		if ($rest_length > 0)
		{
			$junk = fread($this->_xform_fp, $rest_length);
		}

		return ftell($this->_xform_fp);
	}

	/**
	 * Removes the $p_remove_dir from $p_filename, while prepending it with $p_add_dir.
	 * Largely based on code from the pclZip library.
	 *
	 * @param   string  $p_filename    The absolute file name to treat
	 * @param   string  $p_remove_dir  The path to remove
	 * @param   string  $p_add_dir     The path to prefix the treated file name with
	 *
	 * @return  string  The treated file name
	 */
	private function addRemovePaths($p_filename, $p_remove_dir, $p_add_dir)
	{
		$p_filename   = $this->fsUtils->TranslateWinPath($p_filename);
		$p_remove_dir = ($p_remove_dir == '') ? '' :
			$this->fsUtils->TranslateWinPath($p_remove_dir); //should fix corrupt backups, fix by nicholas

		$v_stored_filename = $p_filename;

		if (!($p_remove_dir == ""))
		{
			if (substr($p_remove_dir, -1) != '/')
			{
				$p_remove_dir .= "/";
			}

			if ((substr($p_filename, 0, 2) == "./") || (substr($p_remove_dir, 0, 2) == "./"))
			{
				if ((substr($p_filename, 0, 2) == "./") && (substr($p_remove_dir, 0, 2) != "./"))
				{
					$p_remove_dir = "./" . $p_remove_dir;
				}

				if ((substr($p_filename, 0, 2) != "./") && (substr($p_remove_dir, 0, 2) == "./"))
				{
					$p_remove_dir = substr($p_remove_dir, 2);
				}
			}

			$v_compare = $this->_PathInclusion($p_remove_dir, $p_filename);

			if ($v_compare > 0)
			{
				if ($v_compare == 2)
				{
					$v_stored_filename = "";
				}
				else
				{
					$v_stored_filename =
						substr($p_filename, (function_exists('mb_strlen') ? mb_strlen($p_remove_dir, '8bit') :
							strlen($p_remove_dir)));
				}
			}
		}
		else
		{
			$v_stored_filename = $p_filename;
		}

		if (!($p_add_dir == ""))
		{
			if (substr($p_add_dir, -1) == "/")
			{
				$v_stored_filename = $p_add_dir . $v_stored_filename;
			}
			else
			{
				$v_stored_filename = $p_add_dir . "/" . $v_stored_filename;
			}
		}

		return $v_stored_filename;
	}
}
PK�!]_�

BackupEngine/Archiver/zip.jsonnu&1i�{
    "_information": {
        "title": "COM_AKEEBA_CONFIG_ENGINE_ARCHIVER_ZIP_TITLE",
        "description": "COM_AKEEBA_CONFIG_ENGINE_ARCHIVER_ZIP_DESCRIPTION"
    },
    "engine.archiver.common.dereference_symlinks": {
        "default": "0",
        "type": "bool",
        "title": "COM_AKEEBA_CONFIG_DEREFERENCESYMLINKS_TITLE",
        "description": "COM_AKEEBA_CONFIG_DEREFERENCESYMLINKS_DESCRIPTION"
    },
    "engine.archiver.common.part_size": {
        "default": "0",
        "type": "integer",
        "min": "0",
        "max": "2147483648",
        "shortcuts": "0|131072|262144|524288|1048576|2097152|5242880|10485760|20971520|52428800|104857600|268435456|536870912|1073741824|1610612736|2097152000",
        "scale": "1048576",
        "uom": "MB",
        "title": "COM_AKEEBA_CONFIG_PARTSIZE_TITLE",
        "description": "COM_AKEEBA_CONFIG_PARTSIZE_DESCRIPTION"
    },
    "engine.archiver.common.chunk_size": {
        "default": "1048576",
        "type": "integer",
        "min": "65536",
        "max": "10485760",
        "shortcuts": "65536|131072|262144|524288|1048576|2097152|5242880|10485760",
        "scale": "1048576",
        "uom": "MB",
        "title": "COM_AKEEBA_CONFIG_CHUNKSIZE_TITLE",
        "description": "COM_AKEEBA_CONFIG_CHUNKSIZE_DESCRIPTION"
    },
    "engine.archiver.common.permissions": {
        "default": "0666",
        "type": "enum",
        "enumkeys": "COM_AKEEBA_CONFIG_PERMISSIONS_0600|COM_AKEEBA_CONFIG_PERMISSIONS_0644|COM_AKEEBA_CONFIG_PERMISSIONS_0666",
        "enumvalues": "0600|0644|0666",
        "title": "COM_AKEEBA_CONFIG_PERMISSIONS_TITLE",
        "description": "COM_AKEEBA_CONFIG_PERMISSIONS_DESCRIPTION"
    },
    "engine.archiver.common.big_file_threshold": {
        "default": "1048576",
        "type": "integer",
        "min": "65536",
        "max": "10485760",
        "shortcuts": "65536|131072|262144|524288|1048576|2097152|5242880|10485760",
        "scale": "1048576",
        "uom": "MB",
        "title": "COM_AKEEBA_CONFIG_BIGFILETHRESHOLD_TITLE",
        "description": "COM_AKEEBA_CONFIG_BIGFILETHRESHOLD_DESCRIPTION"
    },
    "engine.archiver.zip.cd_glue_chunk_size": {
        "default": "1048576",
        "type": "integer",
        "min": "65536",
        "max": "10485760",
        "shortcuts": "65536|131072|262144|524288|1048576|2097152|5242880|10485760",
        "scale": "1048576",
        "uom": "MB",
        "title": "COM_AKEEBA_CONFIG_ZIPCDGLUECHUNKSIZE_TITLE",
        "description": "COM_AKEEBA_CONFIG_ZIPCDGLUECHUNKSIZE_DESCRIPTION"
    }
}PK�!]��4��,BackupEngine/Archiver/BaseFileManagement.phpnu&1i�<?php
/**
 * Akeeba Engine
 *
 * @package   akeebaengine
 * @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License version 3, or later
 *
 * 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, version 3.
 *
 * 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
 * <https://www.gnu.org/licenses/>.
 */

namespace Akeeba\Engine\Archiver;

defined('AKEEBAENGINE') || die();

use Akeeba\Engine\Base\Exceptions\ErrorException;
use Akeeba\Engine\Factory;

if (!function_exists('akstrlen'))
{
	/**
	 * Attempt to use mbstring for calculating the binary string length.
	 *
	 * @param $string
	 *
	 * @return int
	 */
	function akstrlen($string)
	{
		return function_exists('mb_strlen') ? mb_strlen($string, '8bit') : strlen($string);
	}
}

/**
 * Abstract class for an archiver using managed file pointers
 */
abstract class BaseFileManagement extends Base
{
	/** @var resource File pointer to the archive's central directory file (for ZIP) */
	protected $cdfp = null;

	/** @var resource File pointer to the archive being currently written to */
	protected $fp = null;

	/** @var   array  An array of the last open files for writing and their last written to offsets */
	private $fileOffsets = [];

	/** @var   array  An array of open file pointers */
	private $filePointers = [];

	/** @var   null|string  The last filename fwrite() wrote to */
	private $lastFileName = null;

	/** @var   null|resource  The last file pointer fwrite() wrote to */
	private $lastFilePointer = null;

	/**
	 * Release file pointers when the object is being destroyed
	 *
	 * @codeCoverageIgnore
	 *
	 * @return  void
	 */
	public function __destruct()
	{
		$this->_closeAllFiles();

		$this->fp   = null;
		$this->cdfp = null;
	}

	/**
	 * Release file pointers when the object is being serialized
	 *
	 * @codeCoverageIgnore
	 *
	 * @return  void
	 */
	public function _onSerialize()
	{
		$this->_closeAllFiles();

		$this->fp   = null;
		$this->cdfp = null;
	}

	/**
	 * Closes all open files known to this archiver object
	 *
	 * @return  void
	 */
	protected function _closeAllFiles()
	{
		if (!empty($this->filePointers))
		{
			foreach ($this->filePointers as $file => $fp)
			{
				$this->conditionalFileClose($fp);

				unset($this->filePointers[$file]);
			}
		}
	}

	/**
	 * Closes an already open file
	 *
	 * @param   resource  $fp  The file pointer to close
	 *
	 * @return  boolean
	 */
	protected function fclose(&$fp)
	{
		$result = true;

		$offset = array_search($fp, $this->filePointers, true);

		if (!is_null($fp) && is_resource($fp))
		{
			$result = $this->conditionalFileClose($fp);
		}

		if ($offset !== false)
		{
			unset($this->filePointers[$offset]);
		}

		$fp = null;

		return $result;
	}

	protected function fcloseByName($file)
	{
		if (!array_key_exists($file, $this->filePointers))
		{
			return true;
		}

		$ret = $this->fclose($this->filePointers[$file]);

		if (array_key_exists($file, $this->filePointers))
		{
			unset($this->filePointers[$file]);
		}

		return $ret;
	}

	/**
	 * Opens a file, if it's not already open, or returns its cached file pointer if it's already open
	 *
	 * @param   string  $file  The filename to open
	 * @param   string  $mode  File open mode, defaults to binary write
	 *
	 * @return  resource
	 */
	protected function fopen($file, $mode = 'w')
	{
		if (!array_key_exists($file, $this->filePointers))
		{
			//Factory::getLog()->debug("Opening backup archive $file with mode $mode");
			$this->filePointers[$file] = @fopen($file, $mode);

			// If we open a file for append we have to seek to the correct offset
			if (substr($mode, 0, 1) == 'a')
			{
				if (isset($this->fileOffsets[$file]))
				{
					Factory::getLog()->debug("Truncating backup archive file $file to " . $this->fileOffsets[$file] . " bytes");
					@ftruncate($this->filePointers[$file], $this->fileOffsets[$file]);
				}

				fseek($this->filePointers[$file], 0, SEEK_END);
			}
		}

		return $this->filePointers[$file];
	}

	/**
	 * Write to file, defeating magic_quotes_runtime settings (pure binary write)
	 *
	 * @param   resource  $fp     Handle to a file
	 * @param   string    $data   The data to write to the file
	 * @param   integer   $p_len  Maximum length of data to write
	 *
	 * @return  int  The number of bytes written
	 *
	 * @throws  ErrorException  When writing to the file is not possible
	 */
	protected function fwrite($fp, $data, $p_len = null)
	{
		if ($fp !== $this->lastFilePointer)
		{
			$this->lastFilePointer = $fp;
			$this->lastFileName    = array_search($fp, $this->filePointers, true);
		}

		$len = is_null($p_len) ? (akstrlen($data)) : $p_len;
		$ret = fwrite($fp, $data, $len);

		if (($ret === false) || (abs(($ret - $len)) >= 1))
		{
			// Log debug information about the archive file's existence and current size. This helps us figure out if
			// there is a server-imposed maximum file size limit.
			clearstatcache();
			$fileExists  = @file_exists($this->lastFileName) ? 'exists' : 'does NOT exist';
			$currentSize = @filesize($this->lastFileName);

			Factory::getLog()->debug(sprintf("%s::_fwrite() ERROR!! Cannot write to archive file %s. The file %s. File size %s bytes after writing %s of %d bytes. Please check the output directory permissions and make sure you have enough disk space available. If this does not help, please set up a Part Size for Split Archives LOWER than this size and retry backing up.", __CLASS__, $this->lastFileName, $fileExists, $currentSize, $ret, $len));

			throw new ErrorException(sprintf("Couldn\'t write to the archive file; check the output directory permissions and make sure you have enough disk space available. [len=%s / %s]", $ret, $len));
		}

		if ($this->lastFileName !== false)
		{
			$this->fileOffsets[$this->lastFileName] = @ftell($fp);
		}

		return $ret;
	}

	/**
	 * Removes a file path from the list of resumable offsets
	 *
	 * @param $filename
	 */
	protected function removeFromOffsetsList($filename)
	{
		if (isset($this->fileOffsets[$filename]))
		{
			unset($this->fileOffsets[$filename]);
		}
	}

}
PK�!]g�.���BackupEngine/Archiver/Zip.phpnu&1i�<?php
/**
 * Akeeba Engine
 *
 * @package   akeebaengine
 * @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License version 3, or later
 *
 * 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, version 3.
 *
 * 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
 * <https://www.gnu.org/licenses/>.
 */

namespace Akeeba\Engine\Archiver;

defined('AKEEBAENGINE') || die();

use Akeeba\Engine\Base\Exceptions\ErrorException;
use Akeeba\Engine\Base\Exceptions\WarningException;
use Akeeba\Engine\Factory;
use Akeeba\Engine\Util\CRC32;
use RuntimeException;

class Zip extends BaseArchiver
{
	/** @var string Beginning of central directory record. */
	private $centralDirectoryRecordStartSignature = "\x50\x4b\x01\x02";

	/** @var string End of central directory record. */
	private $centralDirectoryRecordEndSignature = "\x50\x4b\x05\x06";

	/** @var string Beginning of file contents. */
	private $fileHeaderSignature = "\x50\x4b\x03\x04";

	/** @var string The name of the temporary file holding the ZIP's Central Directory */
	private $centralDirectoryFilename;

	/** @var integer The total number of files and directories stored in the ZIP archive */
	private $totalFilesCount;

	/** @var integer The total size of data in the archive. Note: On 32-bit versions of PHP, this will overflow for archives over 2Gb! */
	private $totalCompressedSize = 0;

	/** @var integer The chunk size for CRC32 calculations */
	private $AkeebaPackerZIP_CHUNK_SIZE;

	/** @var int Current part file number */
	private $currentPartNumber = 1;

	/** @var int Total number of part files */
	private $totalParts = 1;

	/**
	 * Class constructor - initializes internal operating parameters
	 *
	 * @return  void
	 */
	public function __construct()
	{
		Factory::getLog()->debug(__CLASS__ . " :: New instance");

		// Find the optimal chunk size for ZIP archive processing
		$this->findOptimalChunkSize();

		Factory::getLog()->debug("Chunk size for CRC is now " . $this->AkeebaPackerZIP_CHUNK_SIZE . " bytes");

		// Should I use Symlink Target Storage?
		$this->enableSymlinkTargetStorage();

		parent::__construct();

		if (!function_exists('hash_file') || !function_exists('hash'))
		{
			$action = version_compare(PHP_VERSION, '7.4.0', 'lt')
				? 'Please ask your host to enable the PHP hash extension and to make sure the hash_file() and hash() functions are not disabled.'
				: 'Please ask your host to change their PHP configuration so that the hash_file() and hash() functions are not disabled.';

			Factory::getLog()->warning(
				sprintf(
					'Your server lacks support for the hash_file() and/or hash() functions. CRC32 checksum cannot be calculated. Third party ZIP extraction tools may report the backup archive as broken. %s',
					$action
				)
			);
		}
	}

	/**
	 * Initialises the archiver class, creating the archive from an existent
	 * installer's JPA archive.
	 *
	 * @param   string  $sourceJPAPath      Absolute path to an installer's JPA archive
	 * @param   string  $targetArchivePath  Absolute path to the generated archive
	 * @param   array   $options            A named key array of options (optional). This is currently not supported
	 *
	 * @return void
	 */
	public function initialize($targetArchivePath, $options = [])
	{
		Factory::getLog()->debug(__CLASS__ . " :: initialize - archive $targetArchivePath");

		// Get names of temporary files
		$this->_dataFileName = $targetArchivePath;

		// Should we enable split archive feature?
		$this->enableSplitArchives();

		// Create the Central Directory temporary file
		$this->createCentralDirectoryTempFile();

		// Try to kill the archive if it exists
		$this->createNewBackupArchive();

		// On split archives, include the "Split ZIP" header, for PKZIP 2.50+ compatibility
		if ($this->useSplitArchive)
		{
			$this->openArchiveForOutput();
			$this->fwrite($this->fp, "\x50\x4b\x07\x08");
		}
	}

	public function finalize()
	{
		$this->finalizeZIPFile();
	}

	/**
	 * Glues the Central Directory of the ZIP file to the archive and takes care about the differences between single
	 * and multipart archives.
	 *
	 * Official ZIP file format: http://www.pkware.com/appnote.txt
	 *
	 * @return  void
	 */
	public function finalizeZIPFile()
	{
		// 1. Get size of central directory
		clearstatcache();
		$cdOffset                  = @filesize($this->_dataFileName);
		$this->totalCompressedSize += $cdOffset;
		$cdSize                    = @filesize($this->centralDirectoryFilename);

		// 2. Append Central Directory to data file and remove the CD temp file afterwards
		if (!is_null($this->fp))
		{
			$this->fclose($this->fp);
		}

		if (!is_null($this->cdfp))
		{
			$this->fclose($this->cdfp);
		}

		$this->openArchiveForOutput(true);

		/**
		 * Do not remove the fcloseByName line! This is required for post-processing multipart archives when for any
		 * reason $this->filePointers[$this->centralDirectoryFilename] contains null instead of boolean false. In this
		 * case the while loop would be stuck forever and the backup would fail. This HAS happened and I have been able
		 * to reproduce it but I did not have enough time to identify the real root cause. This workaround, however,
		 * works.
		 */
		$this->fcloseByName($this->centralDirectoryFilename);
		$this->cdfp = $this->fopen($this->centralDirectoryFilename, "r");

		if ($this->cdfp === false)
		{
			// Already glued, return
			$this->fclose($this->fp);
			$this->fp   = null;
			$this->cdfp = null;

			return;
		}

		// Comment length (I need it before I start gluing the archive)
		$comment_length = akstrlen($this->_comment);

		// Special consideration for split ZIP files
		if ($this->useSplitArchive)
		{
			// Calculate size of Central Directory + EOCD records
			$total_cd_eocd_size = $cdSize + 22 + $comment_length;

			// Free space on the part
			$free_space = $this->getPartFreeSize();

			if (($free_space < $total_cd_eocd_size) && ($total_cd_eocd_size > 65536))
			{
				// Not enough space on archive for CD + EOCD, will go on separate part
				$this->createAndOpenNewPart(true);
			}
		}

		/**
		 * Write the CD record
		 *
		 * Note about is_resource: in some circumstances where multipart ZIP files are generated, the $this->cdfp will
		 * contain a null value. This seems to happen when $this->fopen returns null, i.e. $this->filePointers has a
		 * null value instead of a file pointer (resource). Why this happens is unclear but the workaround is to remove
		 * the null value from $this->filePointers and retry $this->fopen. Normally this should not be required since we
		 * already to the fcloseByName/fopen dance above. This if-block is our last hope to catch a potential issue
		 * which would either make the while loop go infinite (not anymore, I've patched it) or the Central Directory
		 * not get written to the archive, which results in a broken archive.
		 */
		if (!is_resource($this->cdfp))
		{
			$this->fcloseByName($this->centralDirectoryFilename);
			$this->cdfp = $this->fopen($this->centralDirectoryFilename, "r");

			// We tried reopening the central directory file and failed again. Time to report a fatal error.
			if (!$this->cdfp)
			{
				throw new RuntimeException("Cannot open central directory temporary file {$this->centralDirectoryFilename} for reading.");
			}
		}

		while (!feof($this->cdfp) && is_resource($this->cdfp))
		{
			/**
			 * Why not split the Central Directory between parts?
			 *
			 * APPNOTE.TXT §8.5.2 "The central directory may span segment boundaries, but no single record in the
			 * central directory should be split across segments."
			 *
			 * This would require parsing the CD temp file to prevent any CD record from spanning across two parts.
			 * But how many bytes is each CD record? It's about 100 bytes per file which gives us about 10,400 files
			 * per MB. Even a 2MB part size holds more than 20,000 file records. A typical 10Mb part size holds more
			 * files than the largest backup I've ever seen. Therefore there is no need to waste computational power
			 * to see if we need to span the Central Directory between parts.
			 */
			$chunk = fread($this->cdfp, _AKEEBA_DIRECTORY_READ_CHUNK);
			$this->fwrite($this->fp, $chunk);
		}

		unset($chunk);

		// Delete the temporary CD file
		$this->fclose($this->cdfp);
		$this->cdfp = null;
		Factory::getTempFiles()->unregisterAndDeleteTempFile($this->centralDirectoryFilename);

		// 3. Write the rest of headers to the end of the ZIP file
		$this->fwrite($this->fp, $this->centralDirectoryRecordEndSignature);

		if ($this->useSplitArchive)
		{
			// Split ZIP files, enter relevant disk number information
			$this->fwrite($this->fp, pack('v', $this->totalParts - 1)); /* Number of this disk. */
			$this->fwrite($this->fp, pack('v', $this->totalParts - 1)); /* Disk with central directory start. */
		}
		else
		{
			// Non-split ZIP files, the disk number MUST be 0
			$this->fwrite($this->fp, pack('V', 0));
		}

		$this->fwrite($this->fp, pack('v', $this->totalFilesCount)); /* Total # of entries "on this disk". */
		$this->fwrite($this->fp, pack('v', $this->totalFilesCount)); /* Total # of entries overall. */
		$this->fwrite($this->fp, pack('V', $cdSize)); /* Size of central directory. */
		$this->fwrite($this->fp, pack('V', $cdOffset)); /* Offset to start of central dir. */

		// 2.0.b2 -- Write a ZIP file comment
		$this->fwrite($this->fp, pack('v', $comment_length)); /* ZIP file comment length. */
		$this->fwrite($this->fp, $this->_comment);
		$this->fclose($this->fp);

		// If Split ZIP and there is no .zip file, rename the last fragment to .ZIP
		if ($this->useSplitArchive)
		{
			$extension = substr($this->_dataFileName, -3);

			if ($extension != '.zip')
			{
				Factory::getLog()->debug('Renaming last ZIP part to .ZIP extension');

				$newName = $this->dataFileNameWithoutExtension . '.zip';

				if (!@rename($this->_dataFileName, $newName))
				{
					throw new RuntimeException('Could not rename last ZIP part to .ZIP extension.');
				}

				$this->_dataFileName = $newName;
			}

			// If Split ZIP and only one fragment, change the signature
			if ($this->totalParts == 1)
			{
				$this->fp = $this->fopen($this->_dataFileName, 'r+');
				$this->fwrite($this->fp, "\x50\x4b\x30\x30");
			}
		}

		@chmod($this->_dataFileName, $this->getPermissions());
	}

	/**
	 * Returns a string with the extension (including the dot) of the files produced
	 * by this class.
	 *
	 * @return string
	 */
	public function getExtension()
	{
		return '.zip';
	}

	/**
	 * Extend the bootstrap code to add some define's used by the ZIP format engine
	 *
	 * @return  void
	 */
	protected function __bootstrap_code()
	{
		if (!defined('_AKEEBA_COMPRESSION_THRESHOLD'))
		{
			$config = Factory::getConfiguration();
			define("_AKEEBA_COMPRESSION_THRESHOLD", $config->get('engine.archiver.common.big_file_threshold')); // Don't compress files over this size
			define("_AKEEBA_DIRECTORY_READ_CHUNK", $config->get('engine.archiver.zip.cd_glue_chunk_size')); // How much data to read at once when finalizing ZIP archives
		}

		parent::__bootstrap_code();
	}

	/**
	 * 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 bool True on success, false otherwise
	 */
	protected function _addFile($isVirtual, &$sourceNameOrData, $targetName)
	{
		$configuration = Factory::getConfiguration();

		// Note down the starting disk number for Split ZIP archives
		$starting_disk_number_for_this_file = 0;

		if ($this->useSplitArchive)
		{
			$starting_disk_number_for_this_file = $this->currentPartNumber - 1;
		}

		// Open data file for output
		$this->openArchiveForOutput();

		// Should I continue backing up a file from the previous step?
		$continueProcessingFile = $configuration->get('volatile.engine.archiver.processingfile', false);

		// Initialize with the default values. Why are *these* values default? If we are continuing file packing, by
		// definition we have an uncompressed, non-virtual file. Hence the default values.
		$isDir             = false;
		$isSymlink         = false;
		$compressionMethod = 1;
		$zdata             = null;
		// If we are continuing file packing we have an uncompressed, non-virtual file.
		$isVirtual = $continueProcessingFile ? false : $isVirtual;
		$resume    = $continueProcessingFile ? 0 : null;

		if (!$continueProcessingFile)
		{
			// Log the file being added
			$messageSource = $isVirtual ? '(virtual data)' : "(source: $sourceNameOrData)";
			Factory::getLog()->debug("-- Adding $targetName to archive $messageSource");

			$this->writeFileHeader($sourceNameOrData, $targetName, $isVirtual, $isSymlink, $isDir,
				$compressionMethod, $zdata, $unc_len,
				$storedName, $crc, $c_len, $hexdtime, $old_offset);
		}
		else
		{
			// Since we are continuing archiving, it's an uncompressed regular file. Set up the variables.
			$sourceNameOrData = $configuration->get('volatile.engine.archiver.sourceNameOrData', '');
			$resume           = $configuration->get('volatile.engine.archiver.resume', 0);
			$unc_len          = $configuration->get('volatile.engine.archiver.unc_len');
			$storedName       = $configuration->get('volatile.engine.archiver.storedName');
			$crc              = $configuration->get('volatile.engine.archiver.crc');
			$c_len            = $configuration->get('volatile.engine.archiver.c_len');
			$hexdtime         = $configuration->get('volatile.engine.archiver.hexdtime');
			$old_offset       = $configuration->get('volatile.engine.archiver.old_offset');

			// Log the file we continue packing
			Factory::getLog()->debug("-- Resuming adding file $sourceNameOrData to archive from position $resume (total size $unc_len)");
		}

		/* "File data" segment. */
		if ($compressionMethod == 8)
		{
			$this->putRawDataIntoArchive($zdata);
		}
		elseif ($isVirtual)
		{
			// Virtual data. Put into the archive.
			$this->putRawDataIntoArchive($sourceNameOrData);
		}
		elseif ($isSymlink)
		{
			$this->fwrite($this->fp, @readlink($sourceNameOrData));
		}
		elseif ((!$isDir) && (!$isSymlink))
		{
			// Uncompressed file.
			if ($this->putUncompressedFileIntoArchive($sourceNameOrData, $unc_len, $resume) === true)
			{
				// If it returns true we are doing a step break to resume packing in the next step. So we need to return
				// true here to avoid running the final bit of code which writes the central directory record and
				// uncaches the file resume data.
				return true;
			}
		}

		// Open the central directory file for append
		if (is_null($this->cdfp))
		{
			$this->cdfp = @$this->fopen($this->centralDirectoryFilename, "a");
		}

		if ($this->cdfp === false)
		{
			throw new ErrorException("Could not open Central Directory temporary file for append!");
		}

		$this->fwrite($this->cdfp, $this->centralDirectoryRecordStartSignature);

		if (!$isSymlink)
		{
			$this->fwrite($this->cdfp, "\x14\x00"); /* Version made by (always set to 2.0). */
			$this->fwrite($this->cdfp, "\x14\x00"); /* Version needed to extract */
			$this->fwrite($this->cdfp, pack('v', 2048)); /* General purpose bit flag */
			$this->fwrite($this->cdfp, ($compressionMethod == 8) ? "\x08\x00" : "\x00\x00"); /* Compression method. */
		}
		else
		{
			// Symlinks get special treatment
			$this->fwrite($this->cdfp, "\x14\x03"); /* Version made by (version 2.0 with UNIX extensions). */
			$this->fwrite($this->cdfp, "\x0a\x03"); /* Version needed to extract */
			$this->fwrite($this->cdfp, pack('v', 2048)); /* General purpose bit flag */
			$this->fwrite($this->cdfp, "\x00\x00"); /* Compression method. */
		}

		$this->fwrite($this->cdfp, $hexdtime); /* Last mod time/date. */
		$this->fwrite($this->cdfp, $crc); /* CRC 32 information. */
		$this->fwrite($this->cdfp, pack('V', $c_len)); /* Compressed filesize. */

		if ($compressionMethod == 0)
		{
			// When we are not compressing, $unc_len is being reduced to 0 while backing up.
			// With this trick, we always store the correct length, as in this case the compressed
			// and uncompressed length is always the same.
			$this->fwrite($this->cdfp, pack('V', $c_len)); /* Uncompressed filesize. */
		}
		else
		{
			// When compressing, the uncompressed length differs from compressed length
			// and this line writes the correct value.
			$this->fwrite($this->cdfp, pack('V', $unc_len)); /* Uncompressed filesize. */
		}

		$fn_length = akstrlen($storedName);
		$this->fwrite($this->cdfp, pack('v', $fn_length)); /* Length of filename. */
		$this->fwrite($this->cdfp, pack('v', 0)); /* Extra field length. */
		$this->fwrite($this->cdfp, pack('v', 0)); /* File comment length. */
		$this->fwrite($this->cdfp, pack('v', $starting_disk_number_for_this_file)); /* Disk number start. */
		$this->fwrite($this->cdfp, pack('v', 0)); /* Internal file attributes. */

		/* External file attributes */
		if (!$isSymlink)
		{
			// Archive bit set
			$this->fwrite($this->cdfp, pack('V', $isDir ? 0x41FF0010 : 0xFE49FFE0));
		}
		else
		{
			// For SymLinks we store UNIX file attributes
			$this->fwrite($this->cdfp, "\x20\x80\xFF\xA1");
		}

		$this->fwrite($this->cdfp, pack('V', $old_offset)); /* Relative offset of local header. */
		$this->fwrite($this->cdfp, $storedName); /* File name. */

		/* Optional extra field, file comment goes here. */

		// Finally, increase the file counter by one
		$this->totalFilesCount++;

		// Uncache data
		$configuration->set('volatile.engine.archiver.sourceNameOrData', null);
		$configuration->set('volatile.engine.archiver.unc_len', null);
		$configuration->set('volatile.engine.archiver.resume', null);
		$configuration->set('volatile.engine.archiver.hexdtime', null);
		$configuration->set('volatile.engine.archiver.crc', null);
		$configuration->set('volatile.engine.archiver.c_len', null);
		$configuration->set('volatile.engine.archiver.fn_length', null);
		$configuration->set('volatile.engine.archiver.old_offset', null);
		$configuration->set('volatile.engine.archiver.storedName', null);
		$configuration->set('volatile.engine.archiver.sourceNameOrData', null);

		$configuration->set('volatile.engine.archiver.processingfile', false);

		// ... and return TRUE = success
		return true;
	}

	/**
	 * Write the file header before putting the file data into the archive
	 *
	 * @param   string  $sourceNameOrData   The path to the file being compressed, or the raw file data for virtual files
	 * @param   string  $targetName         The target path to be stored inside the archive
	 * @param   bool    $isVirtual          Is this a virtual file?
	 * @param   bool    $isSymlink          Is this a symlink?
	 * @param   bool    $isDir              Is this a directory?
	 * @param   int     $compressionMethod  The compression method chosen for this file
	 * @param   string  $zdata              If we have compression method other than 0 this holds the compressed data.
	 *                                      We return that from this method to avoid having to compress the same data
	 *                                      twice (once to write the compressed data length in the header and once to
	 *                                      write the compressed data to the archive).
	 * @param   int     $unc_len            The uncompressed size of the file / source data
	 *
	 * @param   string  $storedName         The file path stored in the archive
	 * @param   string  $crc                CRC-32 for the file
	 * @param   int     $c_len              Compressed data length
	 * @param   string  $hexdtime           ZIP's hexadecimal notation if the file's modification date
	 * @param   int     $old_offset         Offset of the file header in the part file
	 */
	protected function writeFileHeader(&$sourceNameOrData, $targetName, &$isVirtual, &$isSymlink, &$isDir,
	                                   &$compressionMethod, &$zdata, &$unc_len, &$storedName, &$crc, &$c_len,
	                                   &$hexdtime, &$old_offset)
	{
		static $memLimit = null;

		if (is_null($memLimit))
		{
			$memLimit = $this->getMemoryLimit();
		}

		$configuration = Factory::getConfiguration();

		// See if it's a directory
		$isDir = $isVirtual ? false : is_dir($sourceNameOrData);

		// See if it's a symlink (w/out dereference)
		$isSymlink = false;

		if ($this->storeSymlinkTarget && !$isVirtual)
		{
			$isSymlink = is_link($sourceNameOrData);
		}

		// Get real size before compression
		[$unc_len, $fileModTime] =
			$this->getFileSizeAndModificationTime($sourceNameOrData, $isVirtual, $isSymlink, $isDir);

		// Decide if we will compress
		$compressionMethod = $this->getCompressionMethod($unc_len, $memLimit, $isDir, $isSymlink);

		if ($isVirtual)
		{
			Factory::getLog()->debug('  Virtual add:' . $targetName . ' (' . $unc_len . ') - ' . $compressionMethod);
		}

		/* "Local file header" segment. */

		$crc = $this->getCRCForEntity($sourceNameOrData, $isVirtual, $isDir, $isSymlink);

		$storedName = $targetName;

		if (!$isSymlink && $isDir)
		{
			$storedName .= "/";
			$unc_len    = 0;
		}

		// Test for non-existing or unreadable files
		$this->testIfFileExists($sourceNameOrData, $isVirtual, $isDir, $isSymlink);

		// Default compressed (archived) length = uncompressed length – valid unless we can actually compress the data.
		$c_len = $unc_len;

		// If we have to compress, read the data in memory and compress it
		if ($compressionMethod == 8)
		{
			$this->getZData($sourceNameOrData, $isVirtual, $compressionMethod, $zdata, $unc_len, $c_len);

			// The method modifies $compressionMethod to 0 (uncompressed) or 1 (Deflate) but the ZIP format needs it
			// to be 0 (uncompressed) or 8 (Deflate). So I just multiply by 8.
			$compressionMethod *= 8;
		}

		// Get the hex time.
		$hexdtime = pack('V', $this->unix2DOSTime($fileModTime));

		// If it's a split ZIP file, we've got to make sure that the header can fit in the part
		if ($this->useSplitArchive)
		{
			// Get header size, taking into account any extra header necessary
			$header_size = 30 + akstrlen($storedName);

			// Compare to free part space
			$free_space = $this->getPartFreeSize();

			if ($free_space <= $header_size)
			{
				// Not enough space on current part, create new part
				$this->createAndOpenNewPart();
			}
		}

		$old_offset = @ftell($this->fp);

		if ($this->useSplitArchive && ($old_offset == 0))
		{
			// Because in split ZIPs we have the split ZIP marker in the first four bytes.
			@fseek($this->fp, 4);
			$old_offset = @ftell($this->fp);
		}

		// Get the file name length in bytes
		$fn_length = akstrlen($storedName);

		$this->fwrite($this->fp, $this->fileHeaderSignature); /* Begin creating the ZIP data. */

		/* Version needed to extract. */
		if (!$isSymlink)
		{
			$this->fwrite($this->fp, "\x14\x00");
		}
		else
		{
			$this->fwrite($this->fp, "\x0a\x03");
		}

		$this->fwrite($this->fp, pack('v', 2048)); /* General purpose bit flag. Bit 11 set = use UTF-8 encoding for filenames & comments */
		$this->fwrite($this->fp, ($compressionMethod == 8) ? "\x08\x00" : "\x00\x00"); /* Compression method. */
		$this->fwrite($this->fp, $hexdtime); /* Last modification time/date. */
		$this->fwrite($this->fp, $crc); /* CRC 32 information. */
		$this->fwrite($this->fp, pack('V', $c_len)); /* Compressed filesize. */
		$this->fwrite($this->fp, pack('V', $unc_len)); /* Uncompressed filesize. */
		$this->fwrite($this->fp, pack('v', $fn_length)); /* Length of filename. */
		$this->fwrite($this->fp, pack('v', 0)); /* Extra field length. */
		$this->fwrite($this->fp, $storedName); /* File name. */

		// Cache useful information about the file
		if (!$isDir && !$isSymlink && !$isVirtual)
		{
			$configuration->set('volatile.engine.archiver.unc_len', $unc_len);
			$configuration->set('volatile.engine.archiver.hexdtime', $hexdtime);
			$configuration->set('volatile.engine.archiver.crc', $crc);
			$configuration->set('volatile.engine.archiver.c_len', $c_len);
			$configuration->set('volatile.engine.archiver.fn_length', $fn_length);
			$configuration->set('volatile.engine.archiver.old_offset', $old_offset);
			$configuration->set('volatile.engine.archiver.storedName', $storedName);
			$configuration->set('volatile.engine.archiver.sourceNameOrData', $sourceNameOrData);
		}
	}

	/**
	 * Get the preferred compression method for a file
	 *
	 * @param   int   $fileSize   File size in bytes
	 * @param   int   $memLimit   Memory limit in bytes
	 * @param   bool  $isDir      Is it a directory?
	 * @param   bool  $isSymlink  Is it a symlink?
	 *
	 * @return  int  Compression method to use
	 */
	protected function getCompressionMethod($fileSize, $memLimit, $isDir, $isSymlink)
	{
		// ZIP uses 0 for uncompressed and 8 for GZip Deflate whereas the parent method returns 0 and 1 respectively
		return 8 * parent::getCompressionMethod($fileSize, $memLimit, $isDir, $isSymlink);
	}

	/**
	 * Calculate the CRC-32 checksum
	 *
	 * @param   string  $sourceNameOrData  The path to the file being compressed, or the raw file data for virtual files
	 * @param   bool    $isVirtual         Is this a virtual file?
	 * @param   bool    $isSymlink         Is this a symlink?
	 * @param   bool    $isDir             Is this a directory?
	 *
	 * @return  int  The CRC-32
	 */
	protected function getCRCForEntity(&$sourceNameOrData, &$isVirtual, &$isDir, &$isSymlink)
	{
		// No hash? No CRC32!
		if (!function_exists('hash'))
		{
			return pack('V', 0);
		}

		// Do I need to reverse the endianness of CRC32b data?
		static $reverseEndianness = null;

		if ($reverseEndianness === null)
		{
			$reverseEndianness = hash('crc32b', 'The quick brown fox jumped over the lazy dog.', true) === pack('N', 2191738434);
		}

		$entityType = 'file';
		$loggedName = null;

		// Directories: dummy CRC-32
		if (!$isSymlink && $isDir)
		{
			$entityType = 'folder';
			$crc        = pack('V', 0);
		}
		// Symlinks: CRC32 of the link source
		elseif ($isSymlink)
		{
			$entityType = 'symlink';
			$crc        = hash('crc32b', @readlink($sourceNameOrData) ?: '', true);
		}
		// Virtual files: CRC32 of the contents
		elseif ($isVirtual)
		{
			$entityType = 'virtual file';
			$loggedName = sprintf('of size %u', strlen($sourceNameOrData));
			$crc        = hash('crc32b', $sourceNameOrData ?: '', true);
		}
		// Files: CRC32 of the file contents
		else
		{
			// Get the CRC32 for the file
			$crc = function_exists("hash_file") ? @hash_file('crc32b', $sourceNameOrData, true) : null;

			// If the file was unreadable skip it
			if ($crc === false)
			{
				throw new WarningException('Could not calculate CRC32 for ' . $sourceNameOrData . '. Looks like it is an unreadable file.');
			}

			// If hash_file is not available use a fake CRC32
			$crc = $crc ?: pack('V', 0);
		}

		/**
		 * If CRC32 returns Big Endian data I'll have to convert it to Little Endian, as required by ZIP.
		 *
		 * I cannot unpack as Big Endian and repack as Little Endian. The intermediate conversion to integer might
		 * overflow 32-bit versions of PHP as its internal integer type is platform-dependent.
		 *
		 * I cannot reverse the string using string functions because the internal character encoding may be something
		 * other than ASCII / 8-bit and mb_string might not be available.
		 *
		 * Instead, I am unpacking the binary string as an array of unsigned bytes and then repacking the bytes, in
		 * reverse order, into a binary string. pack() and unpack() are not affected by the character encoding. Using
		 * unsigned bytes guarantees they will fit into internal integer variables regardless of the platform used.
		 */
		if ($crc !== "\000\000\000\000" && $reverseEndianness)
		{
			$temp = array_values(unpack('C4', $crc));
			$crc  = pack('C*', $temp[3], $temp[2], $temp[1], $temp[0]);
		}

		// Log the calculated CRC32 if the site is in debug mode
		if (defined('AKEEBADEBUG'))
		{
			$asHexChars = array_map(
				function ($c) {
					return dechex($c);
				}, unpack('C*', $crc)
			);

			Factory::getLog()->debug(
				sprintf(
					'%s %s - CRC32 = %s',
					$entityType,
					$loggedName ?? $sourceNameOrData,
					implode('', $reverseEndianness ? array_reverse($asHexChars) : $asHexChars)
				)
			);
		}

		return $crc;
	}

	/**
	 * Converts a UNIX timestamp to a 4-byte DOS date and time format
	 * (date in high 2-bytes, time in low 2-bytes allowing magnitude
	 * comparison).
	 *
	 * @param   integer  $unixtime  The current UNIX timestamp.
	 *
	 * @return integer  The current date in a 4-byte DOS format.
	 */
	protected function unix2DOSTime($unixtime = null)
	{
		$timearray = (is_null($unixtime)) ? getdate() : getdate($unixtime);

		if ($timearray['year'] < 1980)
		{
			$timearray['year']    = 1980;
			$timearray['mon']     = 1;
			$timearray['mday']    = 1;
			$timearray['hours']   = 0;
			$timearray['minutes'] = 0;
			$timearray['seconds'] = 0;
		}

		return (($timearray['year'] - 1980) << 25) |
			($timearray['mon'] << 21) |
			($timearray['mday'] << 16) |
			($timearray['hours'] << 11) |
			($timearray['minutes'] << 5) |
			($timearray['seconds'] >> 1);
	}

	/**
	 * Creates a new part for the spanned archive
	 *
	 * @param   bool  $finalPart  Is this the final archive part?
	 *
	 * @return  bool  True on success
	 */
	protected function createNewPartFile($finalPart = false)
	{
		// Close any open file pointers
		if (is_resource($this->fp))
		{
			$this->fclose($this->fp);
		}

		if (is_resource($this->cdfp))
		{
			$this->fclose($this->cdfp);
		}

		// Remove the just finished part from the list of resumable offsets
		$this->removeFromOffsetsList($this->_dataFileName);

		// Set the file pointers to null
		$this->fp   = null;
		$this->cdfp = null;

		// Push the previous part if we have to post-process it immediately
		$configuration = Factory::getConfiguration();

		if ($configuration->get('engine.postproc.common.after_part', 0))
		{
			$this->finishedPart[] = $this->_dataFileName;
		}

		// Add the part's size to our rolling sum
		clearstatcache();
		$this->totalCompressedSize += filesize($this->_dataFileName);
		$this->totalParts++;
		$this->currentPartNumber = $this->totalParts;

		if ($finalPart)
		{
			$this->_dataFileName = $this->dataFileNameWithoutExtension . '.zip';
		}
		else
		{
			$this->_dataFileName = $this->dataFileNameWithoutExtension . '.z' . sprintf('%02d', $this->currentPartNumber);
		}

		Factory::getLog()->info('Creating new ZIP part #' . $this->currentPartNumber . ', file ' . $this->_dataFileName);

		// Inform the backup engine that we have changed the multipart number
		$statistics = Factory::getStatistics();
		$statistics->updateMultipart($this->totalParts);

		// Try to remove any existing file
		@unlink($this->_dataFileName);

		// Touch the new file
		$result = @touch($this->_dataFileName);

		@chmod($this->_dataFileName, $this->getPermissions());

		return $result;
	}

	/**
	 * Find the optimal chunk size for CRC32 calculations and file processing
	 *
	 * @return  void
	 */
	private function findOptimalChunkSize()
	{
		$configuration = Factory::getConfiguration();

		// The user has entered their own preference
		if ($configuration->get('engine.archiver.common.chunk_size', 0) > 0)
		{
			$this->AkeebaPackerZIP_CHUNK_SIZE = AKEEBA_CHUNK;

			return;
		}

		// Get the PHP memory limit
		$memLimit = $this->getMemoryLimit();

		// Can't get a PHP memory limit? Use 2Mb chunks (fairly large, right?)
		if (is_null($memLimit))
		{
			$this->AkeebaPackerZIP_CHUNK_SIZE = 2097152;

			return;
		}

		if (!function_exists("memory_get_usage"))
		{
			// PHP can't report memory usage, use a conservative 512Kb
			$this->AkeebaPackerZIP_CHUNK_SIZE = 524288;

			return;
		}

		// PHP *can* report memory usage, see if there's enough available memory
		$availableRAM = $memLimit - memory_get_usage();

		if ($availableRAM > 0)
		{
			$this->AkeebaPackerZIP_CHUNK_SIZE = $availableRAM * 0.5;

			return;
		}

		// NEGATIVE AVAILABLE MEMORY?!! Some borked PHP implementations also return the size of the httpd footprint.
		if (($memLimit - 6291456) > 0)
		{
			$this->AkeebaPackerZIP_CHUNK_SIZE = $memLimit - 6291456;

			return;
		}

		// If all else fails, use 2Mb and cross your fingers
		$this->AkeebaPackerZIP_CHUNK_SIZE = 2097152;
	}

	/**
	 * Create a Central Directory temporary file
	 *
	 * @return  void
	 *
	 * @throws  ErrorException
	 */
	private function createCentralDirectoryTempFile()
	{
		$configuration                  = Factory::getConfiguration();
		$this->centralDirectoryFilename = tempnam($configuration->get('akeeba.basic.output_directory'), 'akzcd');
		$this->centralDirectoryFilename = basename($this->centralDirectoryFilename);
		$pos                            = strrpos($this->centralDirectoryFilename, '/');

		if ($pos !== false)
		{
			$this->centralDirectoryFilename = substr($this->centralDirectoryFilename, $pos + 1);
		}

		$pos = strrpos($this->centralDirectoryFilename, '\\');

		if ($pos !== false)
		{
			$this->centralDirectoryFilename = substr($this->centralDirectoryFilename, $pos + 1);
		}

		$this->centralDirectoryFilename = Factory::getTempFiles()->registerTempFile($this->centralDirectoryFilename);

		Factory::getLog()->debug(__CLASS__ . " :: CntDir Tempfile = " . $this->centralDirectoryFilename);

		// Create temporary file
		if (!@touch($this->centralDirectoryFilename))
		{
			throw new ErrorException("Could not open temporary file for ZIP archiver. Please check your temporary directory's permissions!");
		}

		@chmod($this->centralDirectoryFilename, $this->getPermissions());
	}
}
PK�!]N:q��K�KBackupEngine/Archiver/Jpa.phpnu&1i�<?php
/**
 * Akeeba Engine
 *
 * @package   akeebaengine
 * @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License version 3, or later
 *
 * 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, version 3.
 *
 * 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
 * <https://www.gnu.org/licenses/>.
 */

namespace Akeeba\Engine\Archiver;

defined('AKEEBAENGINE') || die();

use Akeeba\Engine\Base\Exceptions\ErrorException;
use Akeeba\Engine\Factory;
use RuntimeException;

/**
 * JPA creation class
 *
 * JPA Format 1.2 implemented, minus BZip2 compression support
 */
class Jpa extends BaseArchiver
{
	/** @var string Standard Header signature */
	private const ARCHIVE_SIGNATURE = "\x4A\x50\x41";

	/** @var string Entity Block signature */
	private const FILE_HEADER_SIGNATURE = "\x4A\x50\x46";

	/** @var string Marks the split archive's extra header */
	private const SPLIT_ARCHIVE_EXTRA_HEADER = "\x4A\x50\x01\x01";

	/** @var string Marks the archive's 64-bit integer representations of file sizes (JPA v1.3) */
	private const ARCHIVE_LONGLONG_SIZES_EXTRA_HEADER = "\x4A\x50\x01\x02";

	/** @var integer How many files are contained in the archive */
	private $totalFilesCount = 0;

	/** @var integer The total size of files contained in the archive as they are stored */
	private $totalCompressedSize = 0;

	/** @var integer The total size of files contained in the archive when they are extracted to disk. */
	private $totalUncompressedSize = 0;

	/** @var int Current part file number */
	private $currentPartNumber = 1;

	/** @var int Total number of part files */
	private $totalParts = 1;

	/**
	 * Initialises the archiver class, creating the archive from an existent
	 * installer's JPA archive.
	 *
	 * @param   string  $targetArchivePath  Absolute path to the generated archive
	 * @param   array   $options            A named key array of options (optional)
	 *
	 * @return  void
	 */
	public function initialize($targetArchivePath, $options = [])
	{
		Factory::getLog()->debug(__CLASS__ . " :: new instance - archive $targetArchivePath");
		$this->_dataFileName = $targetArchivePath;

		// Should we enable Split ZIP feature?
		$this->enableSplitArchives();

		// Should I use Symlink Target Storage?
		$this->enableSymlinkTargetStorage();

		// Try to kill the archive if it exists
		$this->createNewBackupArchive();

		// Write the initial instance of the archive header
		$this->_writeArchiveHeader();
	}

	/**
	 * Updates the Standard Header with current information
	 *
	 * @return  void
	 */
	public function finalize()
	{
		if (is_resource($this->fp))
		{
			$this->fclose($this->fp);
		}

		if (is_resource($this->cdfp))
		{
			$this->fclose($this->cdfp);
		}

		$this->_closeAllFiles();

		// If Spanned JPA and there is no .jpa file, rename the last fragment to .jpa
		if ($this->useSplitArchive)
		{
			$extension = substr($this->_dataFileName, -4);

			if ($extension != '.jpa')
			{
				Factory::getLog()->debug('Renaming last JPA part to .JPA extension');

				$newName = $this->dataFileNameWithoutExtension . '.jpa';

				if (!@rename($this->_dataFileName, $newName))
				{
					throw new RuntimeException('Could not rename last JPA part to .JPA extension.');
				}

				$this->_dataFileName = $newName;
			}

			// Finally, point to the first part so that we can re-write the correct header information
			if ($this->totalParts > 1)
			{
				$this->_dataFileName = $this->dataFileNameWithoutExtension . '.j01';
			}
		}

		// Re-write the archive header
		$this->_writeArchiveHeader();
	}

	/**
	 * Returns a string with the extension (including the dot) of the files produced
	 * by this class.
	 *
	 * @return string
	 */
	public function getExtension()
	{
		return '.jpa';
	}

	/**
	 * Outputs a Standard Header at the top of the file
	 *
	 * @return  void
	 */
	protected function _writeArchiveHeader()
	{
		if (!is_null($this->fp))
		{
			$this->fclose($this->fp);
			$this->fp = null;
		}

		$this->fp = $this->fopen($this->_dataFileName, 'c');

		if ($this->fp === false)
		{
			throw new ErrorException('Could not open ' . $this->_dataFileName . ' for writing. Check permissions and open_basedir restrictions.');
		}

		// Calculate total header size
		$headerSize = 19; // Standard Header

		if ($this->useSplitArchive)
		{
			// Spanned JPA header
			$headerSize += 8;
		}

		$is64Bit = $this->is64Bit();

		if ($is64Bit)
		{
			// Version 1.3 long long sizes
			$headerSize += 22;

		}

		// Write the archive header
		$this->fwrite($this->fp, self::ARCHIVE_SIGNATURE); // ID string (JPA)
		$this->fwrite($this->fp, pack('v', $headerSize)); // Header length; fixed to 19 bytes
		$this->fwrite($this->fp, pack('C', _JPA_MAJOR)); // Major version
		$this->fwrite($this->fp, pack('C', _JPA_MINOR)); // Minor version
		$this->fwrite($this->fp, pack('V', $this->totalFilesCount)); // File count
		$this->fwrite($this->fp, pack('V', $this->totalUncompressedSize)); // Size of files when extracted
		$this->fwrite($this->fp, pack('V', $this->totalCompressedSize)); // Size of files when stored

		// Do I need to add a split archive's header too?
		if ($this->useSplitArchive)
		{
			$this->fwrite($this->fp, self::SPLIT_ARCHIVE_EXTRA_HEADER); // Signature
			$this->fwrite($this->fp, pack('v', 4)); // Extra field length
			$this->fwrite($this->fp, pack('v', $this->totalParts)); // Number of parts
		}

		if ($is64Bit)
		{
			// Version 1.3
			$this->fwrite($this->fp, self::ARCHIVE_LONGLONG_SIZES_EXTRA_HEADER); // Signature
			$this->fwrite($this->fp, pack('v', 18)); // Extra field length
			$this->fwrite($this->fp, pack('P', $this->totalUncompressedSize)); // Size of files when extracted
			$this->fwrite($this->fp, pack('P', $this->totalCompressedSize)); // Size of files when stored
		}

		$this->fclose($this->fp);

		@chmod($this->_dataFileName, $this->getPermissions());
	}

	/**
	 * Extend the bootstrap code to add some define's used by the JPA format engine
	 *
	 * @codeCoverageIgnore
	 *
	 * @return  void
	 */
	protected function __bootstrap_code()
	{
		if (!defined('_AKEEBA_COMPRESSION_THRESHOLD'))
		{
			$config = Factory::getConfiguration();
			define("_AKEEBA_COMPRESSION_THRESHOLD", $config->get('engine.archiver.common.big_file_threshold')); // Don't compress files over this size

			/**
			 * Akeeba Backup and JPA Format version change chart:
			 * Akeeba Backup 3.0: JPA Format 1.1
			 * Akeeba Backup 3.1: JPA Format 1.2 with file modification timestamp is used
			 * Akeeba Backup for Joomla 8.3/9.6, Solo/Akeeba Backup for WordPress 7.9: JPA Format 1.3
			 */
			define('_JPA_MAJOR', 1); // JPA Format major version number

			if ($this->is64Bit())
			{
				define('_JPA_MINOR', 3); // JPA Format minor version number
			}
			else
			{
				define('_JPA_MINOR', 2); // JPA Format minor version number
			}
		}
		parent::__bootstrap_code();
	}

	/**
	 * 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 boolean True on success, false otherwise
	 *
	 * @since  1.2.1
	 */
	protected function _addFile($isVirtual, &$sourceNameOrData, $targetName)
	{
		// Get references to engine objects we're going to be using
		$configuration = Factory::getConfiguration();

		// Is this a virtual file?
		$isVirtual = (bool) $isVirtual;

		// Open data file for output
		$this->openArchiveForOutput();

		// Should I continue backing up a file from the previous step?
		$continueProcessingFile = $configuration->get('volatile.engine.archiver.processingfile', false);

		// Initialize with the default values. Why are *these* values default? If we are continuing file packing, by
		// definition we have an uncompressed, non-virtual file. Hence the default values.
		$isDir             = false;
		$isSymlink         = false;
		$compressionMethod = 0;
		$zdata             = null;
		// If we are continuing file packing we have an uncompressed, non-virtual file.
		$isVirtual = $continueProcessingFile ? false : $isVirtual;
		$resume    = $continueProcessingFile ? 0 : null;

		if (!$continueProcessingFile)
		{
			// Log the file being added
			$messageSource = $isVirtual ? '(virtual data)' : "(source: $sourceNameOrData)";
			Factory::getLog()->debug("-- Adding $targetName to archive $messageSource");

			// Write a file header
			$this->writeFileHeader($sourceNameOrData, $targetName, $isVirtual, $isSymlink, $isDir, $compressionMethod, $zdata, $unc_len);
		}
		else
		{
			$sourceNameOrData = $configuration->get('volatile.engine.archiver.sourceNameOrData', '');
			$unc_len          = $configuration->get('volatile.engine.archiver.unc_len', 0);
			$resume           = $configuration->get('volatile.engine.archiver.resume', 0);

			// Log the file we continue packing
			Factory::getLog()->debug("-- Resuming adding file $sourceNameOrData to archive from position $resume (total size $unc_len)");
		}

		/* "File data" segment. */
		if ($compressionMethod == 1)
		{
			// Compressed data. Put into the archive.
			$this->putRawDataIntoArchive($zdata);
		}
		elseif ($isVirtual)
		{
			// Virtual data. Put into the archive.
			$this->putRawDataIntoArchive($sourceNameOrData);
		}
		elseif ($isSymlink)
		{
			// Symlink. Just put the link target into the archive.
			$this->fwrite($this->fp, @readlink($sourceNameOrData));
		}
		elseif ((!$isDir) && (!$isSymlink))
		{
			// Uncompressed file.
			if ($this->putUncompressedFileIntoArchive($sourceNameOrData, $unc_len, $resume) === true)
			{
				// If it returns true we are doing a step break to resume packing in the next step. So we need to return
				// true here to avoid running the final bit of code which uncaches the file resume data.
				return true;
			}
		}

		// Factory::getLog()->debug("DEBUG -- Added $targetName to archive");

		// Uncache data
		$configuration->set('volatile.engine.archiver.sourceNameOrData', null);
		$configuration->set('volatile.engine.archiver.unc_len', null);
		$configuration->set('volatile.engine.archiver.resume', null);
		$configuration->set('volatile.engine.archiver.processingfile', false);

		// ... and return TRUE = success
		return true;
	}

	/**
	 * Write the file header to the backup archive.
	 *
	 * Only the first three parameters are input. All other are ignored for input and are overwritten.
	 *
	 * @param   string  $sourceNameOrData   The path to the file being compressed, or the raw file data for virtual files
	 * @param   string  $targetName         The target path to be stored inside the archive
	 * @param   bool    $isVirtual          Is this a virtual file?
	 * @param   bool    $isSymlink          Is this a symlink?
	 * @param   bool    $isDir              Is this a directory?
	 * @param   int     $compressionMethod  The compression method chosen for this file
	 * @param   string  $zdata              If we have compression method other than 0 this holds the compressed data.
	 *                                      We return that from this method to avoid having to compress the same data
	 *                                      twice (once to write the compressed data length in the header and once to
	 *                                      write the compressed data to the archive).
	 * @param   int     $unc_len            The uncompressed size of the file / source data
	 *
	 * @return  void
	 */
	protected function writeFileHeader(&$sourceNameOrData, &$targetName, &$isVirtual, &$isSymlink, &$isDir, &$compressionMethod, &$zdata, &$unc_len)
	{
		static $memLimit = null;

		if (is_null($memLimit))
		{
			$memLimit = $this->getMemoryLimit();
		}

		$configuration = Factory::getConfiguration();

		// Uncache data -- WHY DO THAT?!
		/**
		 * $configuration->set('volatile.engine.archiver.sourceNameOrData', null);
		 * $configuration->set('volatile.engine.archiver.unc_len', null);
		 * $configuration->set('volatile.engine.archiver.resume', null);
		 * $configuration->set('volatile.engine.archiver.processingfile',false);
		 * /**/

		// See if it's a directory
		$isDir = $isVirtual ? false : is_dir($sourceNameOrData);

		// See if it's a symlink (w/out dereference)
		$isSymlink = false;

		if ($this->storeSymlinkTarget && !$isVirtual)
		{
			$isSymlink = is_link($sourceNameOrData);
		}

		// Get real size before compression
		[$fileSize, $fileModTime] =
			$this->getFileSizeAndModificationTime($sourceNameOrData, $isVirtual, $isSymlink, $isDir);

		// Decide if we will compress
		$compressionMethod = $this->getCompressionMethod($fileSize, $memLimit, $isDir, $isSymlink);

		$storedName = $targetName;

		/* "Entity Description Block" segment. */
		$unc_len    = $fileSize; // File size
		$storedName .= ($isDir) ? "/" : "";

		/**
		 * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
		 * !!!! WARNING!!! DO NOT MOVE THIS BLOCK OF CODE AFTER THE testIfFileExists OR getZData!!!!       !!!!
		 * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
		 *
		 * PHP 5.6.3 IS BROKEN. Possibly the same applies for all old versions of PHP. If you try to get the file
		 * permissions after reading its contents PHP segfaults.
		 */
		// Get file permissions
		$perms = 0644;

		if (!$isVirtual)
		{
			$perms = @fileperms($sourceNameOrData);
		}

		// Test for non-existing or unreadable files
		$this->testIfFileExists($sourceNameOrData, $isVirtual, $isDir, $isSymlink);

		// Default compressed (archived) length = uncompressed length – valid unless we can actually compress the data.
		$c_len = $unc_len;

		if ($compressionMethod == 1)
		{
			$this->getZData($sourceNameOrData, $isVirtual, $compressionMethod, $zdata, $unc_len, $c_len);
		}

		$this->totalCompressedSize   += $c_len; // Update global data
		$this->totalUncompressedSize += $fileSize; // Update global data
		$this->totalFilesCount++;

		// Calculate Entity Description Block length
		$blockLength = 21 + akstrlen($storedName);

		// If we need to store the file mod date
		if ($fileModTime > 0)
		{
			$blockLength += 8;
		}

		$is64Bit = $this->is64Bit();

		if ($is64Bit)
		{
			// We need to account for the Long Long File Sizes Extra Field in JPA v1.3
			$blockLength += 20;
		}

		// Get file type
		$fileType = 1;

		if ($isSymlink)
		{
			$fileType = 2;
		}
		elseif ($isDir)
		{
			$fileType = 0;
		}

		// If it's a split JPA file, we've got to make sure that the header can fit in the part
		if ($this->useSplitArchive)
		{
			// Compare to free part space
			$free_space = $this->getPartFreeSize();

			if ($free_space <= $blockLength)
			{
				// Not enough space on current part, create new part
				$this->createAndOpenNewPart();
			}
		}

		$this->fwrite($this->fp, self::FILE_HEADER_SIGNATURE); // Entity Description Block header
		$this->fwrite($this->fp, pack('v', $blockLength)); // Entity Description Block header length
		$this->fwrite($this->fp, pack('v', akstrlen($storedName))); // Length of entity path
		$this->fwrite($this->fp, $storedName); // Entity path
		$this->fwrite($this->fp, pack('C', $fileType)); // Entity type
		$this->fwrite($this->fp, pack('C', $compressionMethod)); // Compression method
		$this->fwrite($this->fp, pack('V', $c_len)); // Compressed size
		$this->fwrite($this->fp, pack('V', $unc_len)); // Uncompressed size
		$this->fwrite($this->fp, pack('V', $perms)); // Entity permissions

		// Timestamp Extra Field, only for files
		if ($fileModTime > 0)
		{
			$this->fwrite($this->fp, "\x00\x01"); // Extra Field Identifier
			$this->fwrite($this->fp, pack('v', 8)); // Extra Field Length
			$this->fwrite($this->fp, pack('V', $fileModTime)); // Timestamp
		}

		if ($is64Bit)
		{
			// The Long Long File Sizes Extra Field
			$this->fwrite($this->fp, "\x00\x02"); // Extra Field Identifier
			$this->fwrite($this->fp, pack('v', 20)); // Extra Field Length
			$this->fwrite($this->fp, pack('P', $c_len)); // Compressed size
			$this->fwrite($this->fp, pack('P', $unc_len)); // Uncompressed size
		}

		// Cache useful information about the file
		if (!$isDir && !$isSymlink && !$isVirtual)
		{
			$configuration->set('volatile.engine.archiver.unc_len', $unc_len);
			$configuration->set('volatile.engine.archiver.sourceNameOrData', $sourceNameOrData);
		}
	}

	/**
	 * Creates a new part for the spanned archive
	 *
	 * @param   bool  $finalPart  Is this the final archive part?
	 *
	 * @return  bool  True on success
	 */
	protected function createNewPartFile($finalPart = false)
	{
		// Close any open file pointers
		if (!is_resource($this->fp))
		{
			$this->fclose($this->fp);
		}

		if (is_resource($this->cdfp))
		{
			$this->fclose($this->cdfp);
		}

		// Remove the just finished part from the list of resumable offsets
		$this->removeFromOffsetsList($this->_dataFileName);

		// Set the file pointers to null
		$this->fp   = null;
		$this->cdfp = null;

		// Push the previous part if we have to post-process it immediately
		$configuration = Factory::getConfiguration();

		if ($configuration->get('engine.postproc.common.after_part', 0))
		{
			// The first part needs its header overwritten during archive
			// finalization. Skip it from immediate processing.
			if ($this->currentPartNumber != 1)
			{
				$this->finishedPart[] = $this->_dataFileName;
			}
		}

		$this->totalParts++;
		$this->currentPartNumber = $this->totalParts;

		if ($finalPart)
		{
			$this->_dataFileName = $this->dataFileNameWithoutExtension . '.jpa';
		}
		else
		{
			$this->_dataFileName = $this->dataFileNameWithoutExtension . '.j' . sprintf('%02d', $this->currentPartNumber);
		}

		Factory::getLog()->info('Creating new JPA part #' . $this->currentPartNumber . ', file ' . $this->_dataFileName);
		$statistics = Factory::getStatistics();
		$statistics->updateMultipart($this->totalParts);

		// Try to remove any existing file
		@unlink($this->_dataFileName);

		// Touch the new file
		$result = @touch($this->_dataFileName);

		chmod($this->_dataFileName, $this->getPermissions());

		// 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);
			@chmod($this->_dataFileName, $this->getPermissions());
		}

		return $result;
	}

	/**
	 * Is this a 64-bit version of PHP?
	 *
	 * @return  bool
	 *
	 * @since   9.6.1
	 * @see     https://www.php.net/manual/en/reserved.constants.php
	 */
	private function is64Bit(): bool
	{
		// 64-bit versions use 8 bytes for the Integer intrinsic type. We use >= for forward compatibility.
		return PHP_INT_SIZE >= 8;
	}
}
PK�!]Qr�3��%BackupEngine/Filter/Regexskipdirs.phpnu&1i�<?php
/**
 * Akeeba Engine
 *
 * @package   akeebaengine
 * @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License version 3, or later
 *
 * 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, version 3.
 *
 * 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
 * <https://www.gnu.org/licenses/>.
 */

namespace Akeeba\Engine\Filter;

defined('AKEEBAENGINE') || die();

/**
 * Subdirectories exclusion filter based on regular expressions
 */
class Regexskipdirs extends Base
{
	public function __construct()
	{
		$this->object  = 'dir';
		$this->subtype = 'children';
		$this->method  = 'regex';

		if (empty($this->filter_name))
		{
			$this->filter_name = strtolower(basename(__FILE__, '.php'));
		}

		parent::__construct();
	}
}
PK�!]�-Ҷ�%BackupEngine/Filter/Stack/README.htmlnu&1i�<?xml version="1.0" encoding="UTF-8" ?>
<!--~
  ~ Akeeba Engine
  ~
  ~ @package   akeebaengine
  ~ @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd
  ~ @license   https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License version 3, or later
  ~
  ~ 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, version 3.
  ~
  ~ 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
  ~ <https://www.gnu.org/licenses/>.
  -->

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
        "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
    <title>AkeebaBackup :: Filter Stack</title>
</head>
<body>
<h1>What is this directory?</h1>

<p>In this directory, Akeeba Backup and Akeeba Solo store the optional filters (Optional Filters in the Configuration
    page). Unlike regular filters which are always loaded, optional filters are only loaded when the user chooses to
    enable them. Each filter consists of two files: <var>filtername</var>.ini and <var>filtername</var>.php. The former
    contains the filter-specific configuration options and the later contains the actual filter code.</p>

<p>
    If you want to create new optional filters, put them in here. Do note that the INI file must always contain a
    boolean key named core.filters.<var>filtername</var>.enabled which controls the loading of this particular filter.
</p>

<p>
    Optional filters are always named Akeeba\Engine\Filter\Stack\Stack<var>Filtername</var> so that the autoloader can
    find them. For the same reason their filename must be Stack<var>Filtername</var>.php   Please watch out for the
    letter case in the names, it's important.
</p>
</body>
</html>
PK�!]
��yYY2BackupEngine/Filter/Stack/StackDateconditional.phpnu&1i�<?php
/**
 * Akeeba Engine
 *
 * @package   akeebaengine
 * @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License version 3, or later
 *
 * 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, version 3.
 *
 * 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
 * <https://www.gnu.org/licenses/>.
 */

namespace Akeeba\Engine\Filter\Stack;

defined('AKEEBAENGINE') || die();

use Akeeba\Engine\Factory;
use Akeeba\Engine\Filter\Base;

/**
 * Date conditional filter
 *
 * It will only backup files modified after a specific date and time
 */
class StackDateconditional extends Base
{
	public function __construct()
	{
		$this->object  = 'file';
		$this->subtype = 'all';
		$this->method  = 'api';

	}

	protected function is_excluded_by_api($test, $root)
	{
		static $from_datetime;

		$config = Factory::getConfiguration();

		if (is_null($from_datetime))
		{
			$user_setting  = $config->get('core.filters.dateconditional.start');
			$from_datetime = strtotime($user_setting);
		}

		// Get the filesystem path for $root
		$fsroot   = $config->get('volatile.filesystem.current_root', '');
		$ds       = ($fsroot == '') || ($fsroot == '/') ? '' : DIRECTORY_SEPARATOR;
		$filename = $fsroot . $ds . $test;

		// Get the timestamp of the file
		$timestamp = @filemtime($filename);

		// If we could not get this information, include the file in the archive
		if ($timestamp === false)
		{
			return false;
		}

		// Compare it with the user-defined minimum timestamp and exclude if it's older than that
		if ($timestamp <= $from_datetime)
		{
			return true;
		}

		// No match? Just include the file!
		return false;
	}

}
PK�!]�j�,BackupEngine/Filter/Stack/StackErrorlogs.phpnu&1i�<?php
/**
 * Akeeba Engine
 *
 * @package   akeebaengine
 * @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License version 3, or later
 *
 * 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, version 3.
 *
 * 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
 * <https://www.gnu.org/licenses/>.
 */

namespace Akeeba\Engine\Filter\Stack;

defined('AKEEBAENGINE') || die();

use Akeeba\Engine\Filter\Base;

/**
 * Files exclusion filter based on regular expressions
 */
class StackErrorlogs extends Base
{
	function __construct()
	{
		$this->object  = 'file';
		$this->subtype = 'all';
		$this->method  = 'api';

		if (empty($this->filter_name))
		{
			$this->filter_name = strtolower(basename(__FILE__, '.php'));
		}

		parent::__construct();
	}

	protected function is_excluded_by_api($test, $root)
	{
		// Is it an error log? Exclude the file.
		if (in_array(basename($test), [
			'php_error',
			'php_errorlog',
			'error_log',
			'error.log',
		]))
		{
			return true;
		}

		// No match? Just include the file!
		return false;
	}

}
PK�!]	��w��,BackupEngine/Filter/Stack/StackHoststats.phpnu&1i�<?php
/**
 * Akeeba Engine
 *
 * @package   akeebaengine
 * @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License version 3, or later
 *
 * 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, version 3.
 *
 * 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
 * <https://www.gnu.org/licenses/>.
 */

namespace Akeeba\Engine\Filter\Stack;

defined('AKEEBAENGINE') || die();

use Akeeba\Engine\Filter\Base;

/**
 * Exclude folders and files belonging to the host web stat (ie Webalizer)
 */
class StackHoststats extends Base
{
	public function __construct()
	{
		$this->object  = 'dir';
		$this->subtype = 'all';
		$this->method  = 'api';

		if (empty($this->filter_name))
		{
			$this->filter_name = strtolower(basename(__FILE__, '.php'));
		}

		parent::__construct();
	}

	protected function is_excluded_by_api($test, $root)
	{
		if ($test == 'stats')
		{
			return true;
		}

		// No match? Just include the file!
		return false;
	}

}
PK�!]�����.BackupEngine/Filter/Stack/dateconditional.jsonnu&1i�{
    "core.filters.dateconditional.enabled": {
        "default": "0",
        "type": "bool",
        "title": "COM_AKEEBA_CONFIG_OPTIONALFILTERS_DATECONDITIONAL_ENABLED_TITLE",
        "description": "COM_AKEEBA_CONFIG_OPTIONALFILTERS_DATECONDITIONAL_ENABLED_DESCRIPTION",
        "bold": "1"
    },
    "core.filters.dateconditional.start": {
        "default": "1970-01-01 00:00 GMT",
        "type": "string",
        "title": "COM_AKEEBA_CONFIG_OPTIONALFILTERS_DATECONDITIONAL_START_TITLE",
        "description": "COM_AKEEBA_CONFIG_OPTIONALFILTERS_DATECONDITIONAL_START_DESCRIPTION",
        "showon": "core.filters.dateconditional.enabled:1"
    }
}PK�!]�D(BackupEngine/Filter/Stack/hoststats.jsonnu&1i�{
    "core.filters.hoststats.enabled": {
        "default": "1",
        "type": "bool",
        "title": "COM_AKEEBA_CONFIG_OPTIONALFILTERS_HOSTSTATS_ENABLED_TITLE",
        "description": "COM_AKEEBA_CONFIG_OPTIONALFILTERS_HOSTSTATS_ENABLED_DESCRIPTION",
        "bold": "1"
    }
}PK�!]&Y�(BackupEngine/Filter/Stack/errorlogs.jsonnu&1i�{
    "core.filters.errorlogs.enabled": {
        "default": "1",
        "type": "bool",
        "title": "COM_AKEEBA_CONFIG_OPTIONALFILTERS_ERRORLOGS_ENABLED_TITLE",
        "description": "COM_AKEEBA_CONFIG_OPTIONALFILTERS_ERRORLOGS_ENABLED_DESCRIPTION",
        "bold": "1"
    }
}PK�!]s�j[�� BackupEngine/Filter/Skipdirs.phpnu&1i�<?php
/**
 * Akeeba Engine
 *
 * @package   akeebaengine
 * @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License version 3, or later
 *
 * 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, version 3.
 *
 * 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
 * <https://www.gnu.org/licenses/>.
 */

namespace Akeeba\Engine\Filter;

defined('AKEEBAENGINE') || die();

/**
 * Subdirectories exclusion filter
 */
class Skipdirs extends Base
{
	public function __construct()
	{
		$this->object  = 'dir';
		$this->subtype = 'children';
		$this->method  = 'direct';

		if (empty($this->filter_name))
		{
			$this->filter_name = strtolower(basename(__FILE__, '.php'));
		}

		parent::__construct();
	}
}
PK�!]�M�|��(BackupEngine/Filter/Regexdirectories.phpnu&1i�<?php
/**
 * Akeeba Engine
 *
 * @package   akeebaengine
 * @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License version 3, or later
 *
 * 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, version 3.
 *
 * 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
 * <https://www.gnu.org/licenses/>.
 */

namespace Akeeba\Engine\Filter;

defined('AKEEBAENGINE') || die();

/**
 * Directory exclusion filter based on regular expressions
 */
class Regexdirectories extends Base
{
	public function __construct()
	{
		$this->object  = 'dir';
		$this->subtype = 'all';
		$this->method  = 'regex';

		if (empty($this->filter_name))
		{
			$this->filter_name = strtolower(basename(__FILE__, '.php'));
		}

		parent::__construct();
	}
}
PK�!]��	��BackupEngine/Filter/Tables.phpnu&1i�<?php
/**
 * Akeeba Engine
 *
 * @package   akeebaengine
 * @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License version 3, or later
 *
 * 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, version 3.
 *
 * 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
 * <https://www.gnu.org/licenses/>.
 */

namespace Akeeba\Engine\Filter;

defined('AKEEBAENGINE') || die();

/**
 * Database table exclusion filter
 */
class Tables extends Base
{
	public function __construct()
	{
		$this->object  = 'dbobject';
		$this->subtype = 'all';
		$this->method  = 'direct';

		if (empty($this->filter_name))
		{
			$this->filter_name = strtolower(basename(__FILE__, '.php'));
		}

		parent::__construct();
	}
}
PK�!].�A��&BackupEngine/Filter/Regexskipfiles.phpnu&1i�<?php
/**
 * Akeeba Engine
 *
 * @package   akeebaengine
 * @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License version 3, or later
 *
 * 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, version 3.
 *
 * 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
 * <https://www.gnu.org/licenses/>.
 */

namespace Akeeba\Engine\Filter;

defined('AKEEBAENGINE') || die();

/**
 * Directory contents (files) exclusion filter based on regular expressions
 */
class Regexskipfiles extends Base
{
	public function __construct()
	{
		$this->object  = 'dir';
		$this->subtype = 'content';
		$this->method  = 'regex';

		if (empty($this->filter_name))
		{
			$this->filter_name = strtolower(basename(__FILE__, '.php'));
		}

		parent::__construct();
	}
}
PK�!]��7��#BackupEngine/Filter/Regextables.phpnu&1i�<?php
/**
 * Akeeba Engine
 *
 * @package   akeebaengine
 * @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License version 3, or later
 *
 * 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, version 3.
 *
 * 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
 * <https://www.gnu.org/licenses/>.
 */

namespace Akeeba\Engine\Filter;

defined('AKEEBAENGINE') || die();

/**
 * Database table exclusion filter
 */
class Regextables extends Base
{
	public function __construct()
	{
		$this->object  = 'dbobject';
		$this->subtype = 'all';
		$this->method  = 'regex';

		if (empty($this->filter_name))
		{
			$this->filter_name = strtolower(basename(__FILE__, '.php'));
		}

		parent::__construct();
	}
}
PK�!]�̍�PP&BackupEngine/Filter/Regextabledata.phpnu&1i�<?php
/**
 * Akeeba Engine
 *
 * @package   akeebaengine
 * @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License version 3, or later
 *
 * 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, version 3.
 *
 * 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
 * <https://www.gnu.org/licenses/>.
 */

namespace Akeeba\Engine\Filter;

defined('AKEEBAENGINE') || die();

/**
 * Database table records exclusion filter
 *
 * This is simple stuff. If a table's on the list, it will backup just its structure, not
 * its contents. Fair and square...
 */
class Regextabledata extends Base
{
	public function __construct()
	{
		$this->object  = 'dbobject';
		$this->subtype = 'content';
		$this->method  = 'regex';

		if (empty($this->filter_name))
		{
			$this->filter_name = strtolower(basename(__FILE__, '.php'));
		}

		parent::__construct();
	}
}
PK�!]�@�A??BackupEngine/Filter/Base.phpnu&1i�<?php
/**
 * Akeeba Engine
 *
 * @package   akeebaengine
 * @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License version 3, or later
 *
 * 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, version 3.
 *
 * 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
 * <https://www.gnu.org/licenses/>.
 */

namespace Akeeba\Engine\Filter;

defined('AKEEBAENGINE') || die();

use Akeeba\Engine\Factory;
use Akeeba\Engine\Util\FileSystem;

abstract class Base
{
	/** @var string Filter's internal name; defaults to filename without .php extension */
	public $filter_name = '';

	/** @var string The filtering object: dir|file|dbobject|db */
	public $object = 'dir';

	/** @var string The filtering subtype (all|content|children|inclusion) */
	public $subtype = null;

	/** @var string The filtering method (direct|regex|api) */
	public $method = 'direct';

	/** @var bool Is the filter active? */
	public $enabled = true;

	/** @var array An array holding filter or regex strings per root, i.e. $filter_data[$root] = array() */
	protected $filter_data = null;

	/** @var FileSystem  Used by treatDirectory */
	protected $fsTools = null;

	/**
	 * Public constructor
	 */
	public function __construct()
	{
		// Set the filter name if it's missing (filename in lowercase, minus the .php extension)
		if (empty($this->filter_name))
		{
			$this->filter_name = strtolower(basename(__FILE__, '.php'));
		}
	}

	/**
	 * Extra SQL statements to append to the SQL dump file. Useful for extension
	 * filters which have to filter out specific database records. This method
	 * must be overridden in children classes.
	 *
	 * @param   string  $root  The database for which to get the extra SQL statements
	 *
	 * @return  array  Extra SQL statements
	 */
	public function getExtraSQL(string $root): array
	{
		return [];
	}

	public $canFilterDatabaseRowContent = false;

	public function filterDatabaseRowContent(string $root, string $tableAbstract, array &$row): void
	{
		// No operation
	}

	/**
	 * Returns filtering (exclusion) status of the $test object
	 *
	 * @param   string  $test     The string to check for filter status (e.g. filename, dir name, table name, etc)
	 * @param   string  $root     The exclusion root test belongs to
	 * @param   string  $object   What type of object is it? dir|file|dbobject
	 * @param   string  $subtype  Filter subtype (all|content|children)
	 *
	 * @return    bool    True if it excluded, false otherwise
	 */
	public function isFiltered($test, $root, $object, $subtype)
	{
		if (!$this->enabled)
		{
			return false;
		}

		//Factory::getLog()->log(LogLevel::DEBUG,"Filtering [$object:$subtype] $root // $test");

		// Inclusion filters do not qualify for exclusion
		if ($this->subtype == 'inclusion')
		{
			return false;
		}

		// The object and subtype must match
		if (($this->object != $object) || ($this->subtype != $subtype))
		{
			return false;
		}

		if (in_array($this->method, ['direct', 'regex']))
		{
			// -- Direct or regex based filters --

			// Get a local reference of the filter data, if necessary
			if (is_null($this->filter_data))
			{
				$filters           = Factory::getFilters();
				$this->filter_data = $filters->getFilterData($this->filter_name);
			}

			// Check if the root exists and if there's a filter for the $test
			if (!array_key_exists($root, $this->filter_data))
			{
				// Root not found
				return false;
			}
			else
			{
				// Root found, search in the array
				if ($this->method == 'direct')
				{
					// Direct filtering
					return in_array($test, $this->filter_data[$root]);
				}
				else
				{
					// Regex matching
					foreach ($this->filter_data[$root] as $regex)
					{
						if (substr($regex, 0, 1) == '!')
						{
							// Custom Akeeba Backup extension to PCRE notation. If you put a ! before the PCRE, it negates the result of the PCRE.
							if (!preg_match(substr($regex, 1), $test))
							{
								return true;
							}
						}
						else
						{
							// Normal PCRE
							if (preg_match($regex, $test))
							{
								return true;
							}
						}
					}

					// if we're here, no match exists
					return false;
				}
			}
		}
		else
		{
			// -- API-based filters --
			return $this->is_excluded_by_api($test, $root);
		}
	}

	/**
	 * Returns the inclusion filters defined by this class for the requested $object
	 *
	 * @param   string  $object  The object to get inclusions for (dir|db)
	 *
	 * @return    array    The inclusion filters
	 */
	public function &getInclusions($object)
	{
		$dummy = [];

		if (!$this->enabled)
		{
			return $dummy;
		}

		if (($this->subtype != 'inclusion') || ($this->object != $object))
		{
			return $dummy;
		}

		switch ($this->method)
		{
			case 'api':
				return $this->get_inclusions_by_api();
				break;

			case 'direct':
				// Get a local reference of the filter data, if necessary
				if (is_null($this->filter_data))
				{
					$filters           = Factory::getFilters();
					$this->filter_data = $filters->getFilterData($this->filter_name);
				}

				return $this->filter_data;
				break;

			default:
				// regex inclusion is not supported at the moment
				$dummy = [];

				return $dummy;
				break;
		}
	}

	/**
	 * Adds an exclusion filter, or add/replace an inclusion filter
	 *
	 * @param   string  $root  Filter's root
	 * @param   mixed   $test  Exclusion: the filter string. Inclusion: the root definition data
	 *
	 * @return bool True on success
	 */
	public function set($root, $test)
	{
		if (in_array($this->subtype, ['all', 'content', 'children']))
		{
			return $this->setExclusion($root, $test);
		}
		else
		{
			return $this->setInclusion($root, $test);
		}
	}

	/**
	 * Unsets a given filter
	 *
	 * @param   string  $root  Filter's root
	 * @param   string  $test  The filter to remove
	 *
	 * @return bool
	 */
	public function remove($root, $test = null)
	{
		if ($this->subtype == 'inclusion')
		{
			return $this->removeInclusion($root);
		}
		else
		{
			return $this->removeExclusion($root, $test);
		}
	}

	/**
	 * Completely removes all filters off a specific root
	 *
	 * @param   string  $root
	 *
	 * @return bool
	 */
	public function reset($root)
	{
		switch ($this->method)
		{
			default:
			case 'api':
				return false;
				break;

			case 'direct':
			case 'regex':
				// Get a local reference of the filter data, if necessary
				if (is_null($this->filter_data))
				{
					$filters           = Factory::getFilters();
					$this->filter_data = $filters->getFilterData($this->filter_name);
				}
				// Direct filters
				if (array_key_exists($root, $this->filter_data))
				{
					unset($this->filter_data[$root]);
				}
				else
				{
					// Root not found
					return false;
				}
				break;
		}

		$filters = Factory::getFilters();
		$filters->setFilterData($this->filter_name, $this->filter_data);

		return true;
	}

	/**
	 * Toggles a filter
	 *
	 * @param   string  $root        The filter root object
	 * @param   string  $test        The filter string to toggle
	 * @param   bool    $new_status  The new filter status after the operation (true: enabled, false: disabled)
	 *
	 * @return bool True on successful change, false if we failed to change it
	 */
	public function toggle($root, $test, &$new_status)
	{
		// Can't toggle inclusion filters!
		if ($this->subtype == 'inclusion')
		{
			return false;
		}

		$is_set     = $this->isFiltered($test, $root, $this->object, $this->subtype);
		$new_status = !$is_set;
		if ($is_set)
		{
			$status = $this->remove($root, $test);
		}
		else
		{
			$status = $this->set($root, $test);
		}
		if (!$status)
		{
			$new_status = $is_set;
		}

		return $status;
	}

	/**
	 * Does this class has any filters? If it doesn't, its methods are never called by
	 * Akeeba's engine to speed things up.
	 * @return bool
	 */
	public function hasFilters()
	{
		if (!$this->enabled)
		{
			return false;
		}

		switch ($this->method)
		{
			default:
			case 'api':
				// API filters always have data!
				return true;
				break;

			case 'direct':
			case 'regex':
				// Get a local reference of the filter data, if necessary
				if (is_null($this->filter_data))
				{
					$filters           = Factory::getFilters();
					$this->filter_data = $filters->getFilterData($this->filter_name);
				}

				return !empty($this->filter_data);
				break;
		}
	}

	/**
	 * Returns a list of filter strings for the given root. Used by MySQLDump engine.
	 *
	 * @param   string  $root
	 *
	 * @return array
	 */
	public function getFilters($root)
	{
		$dummy = [];

		if (!$this->enabled)
		{
			return $dummy;
		}

		switch ($this->method)
		{
			default:
			case 'api':
				// API filters never have a list
				return $dummy;
				break;

			case 'direct':
			case 'regex':
				// Get a local reference of the filter data, if necessary
				if (is_null($this->filter_data))
				{
					$filters           = Factory::getFilters();
					$this->filter_data = $filters->getFilterData($this->filter_name);
				}

				if (is_null($root))
				{
					// When NULL is passed as the root, we return all roots
					return $this->filter_data;
				}
				elseif (array_key_exists($root, $this->filter_data))
				{
					// The root exists, return its data
					return $this->filter_data[$root];
				}
				else
				{
					// The root doesn't exist, return an empty array
					return $dummy;
				}
				break;
		}
	}

	/**
	 * This method must be overriden by API-type exclusion filters.
	 *
	 * @param   string  $test  The object to test for exclusion
	 * @param   string  $root  The object's root
	 *
	 * @return    bool    Return true if it matches your filters
	 *
	 * @codeCoverageIgnore
	 */
	protected function is_excluded_by_api($test, $root)
	{
		return false;
	}

	/**
	 * This method must be overriden by API-type inclusion filters.
	 *
	 * @return    array    The inclusion filters
	 *
	 * @codeCoverageIgnore
	 */
	protected function &get_inclusions_by_api()
	{
		$dummy = [];

		return $dummy;
	}

	/**
	 * Remove the root prefix from an absolute path
	 *
	 * @param   string  $directory  The absolute path
	 *
	 * @return  string  The translated path, relative to the root directory of the backup job
	 */
	protected function treatDirectory($directory)
	{
		if (!is_object($this->fsTools))
		{
			$this->fsTools = Factory::getFilesystemTools();
		}

		// Get the site's root
		$configuration = Factory::getConfiguration();

		if ($configuration->get('akeeba.platform.override_root', 0))
		{
			$root = $configuration->get('akeeba.platform.newroot', '[SITEROOT]');
		}
		else
		{
			$root = '[SITEROOT]';
		}

		if (stristr($root, '['))
		{
			$root = $this->fsTools->translateStockDirs($root);
		}

		$site_root = $this->fsTools->TrimTrailingSlash($this->fsTools->TranslateWinPath($root));

		$directory = $this->fsTools->TrimTrailingSlash($this->fsTools->TranslateWinPath($directory));

		// Trim site root from beginning of directory
		if (substr($directory, 0, strlen($site_root)) == $site_root)
		{
			$directory = substr($directory, strlen($site_root));

			if (substr($directory, 0, 1) == '/')
			{
				$directory = substr($directory, 1);
			}
		}

		return $directory;
	}

	/**
	 * Sets a filter, for direct and regex exclusion filter types
	 *
	 * @param   string  $root  The filter root object
	 * @param   string  $test  The filter string to set
	 *
	 * @return    bool    True on success
	 *
	 * @codeCoverageIgnore
	 */
	private function setExclusion($root, $test)
	{
		switch ($this->method)
		{
			default:
			case 'api':
				// we can't set new filter elements for API-type filters
				return false;
				break;

			case 'direct':
			case 'regex':
				// Get a local reference of the filter data, if necessary
				if (is_null($this->filter_data))
				{
					$filters           = Factory::getFilters();
					$this->filter_data = $filters->getFilterData($this->filter_name);
				}

				// Direct filters
				if (array_key_exists($root, $this->filter_data))
				{
					if (!in_array($test, $this->filter_data[$root]))
					{
						$this->filter_data[$root][] = $test;
					}
					else
					{
						return false;
					}
				}
				else
				{
					$this->filter_data[$root] = [$test];
				}
				break;
		}

		$filters = Factory::getFilters();
		$filters->setFilterData($this->filter_name, $this->filter_data);

		return true;
	}

	/**
	 * Sets a filter, for direct inclusion filter types
	 *
	 * @param   string  $root  The inclusion filter key (root)
	 * @param   string  $test  The inclusion filter raw data
	 *
	 * @return    bool    True on success
	 *
	 * @codeCoverageIgnore
	 */
	private function setInclusion($root, $test)
	{
		switch ($this->method)
		{
			default:
			case 'api':
			case 'regex':
				// we can't set new filter elements for API or regex type filters
				return false;
				break;

			case 'direct':
				// Get a local reference of the filter data, if necessary
				if (is_null($this->filter_data))
				{
					$filters           = Factory::getFilters();
					$this->filter_data = $filters->getFilterData($this->filter_name);
				}

				$this->filter_data[$root] = $test;
				break;
		}

		$filters = Factory::getFilters();
		$filters->setFilterData($this->filter_name, $this->filter_data);

		return true;
	}

	/**
	 * Remove a key from direct and regex filters
	 *
	 * @param   string  $root  The filter root object
	 * @param   string  $test  The filter string to set
	 *
	 * @return    bool    True on success
	 *
	 * @codeCoverageIgnore
	 */
	private function removeExclusion($root, $test)
	{
		switch ($this->method)
		{
			default:
			case 'api':
				// we can't remove filter elements from API-type filters
				return false;
				break;

			case 'direct':
			case 'regex':
				// Get a local reference of the filter data, if necessary
				if (is_null($this->filter_data))
				{
					$filters           = Factory::getFilters();
					$this->filter_data = $filters->getFilterData($this->filter_name);
				}

				// Direct filters
				if (array_key_exists($root, $this->filter_data))
				{
					if (in_array($test, $this->filter_data[$root]))
					{
						if (count($this->filter_data[$root]) == 1)
						{
							// If it's the only element, remove the entire root key
							unset($this->filter_data[$root]);
						}
						else
						{
							// If there are more elements, remove just the $test value
							$key = array_search($test, $this->filter_data[$root]);
							unset($this->filter_data[$root][$key]);
						}
					}
					else
					{
						// Filter object not found
						return false;
					}
				}
				else
				{
					// Root not found
					return false;
				}
				break;
		}

		$filters = Factory::getFilters();
		$filters->setFilterData($this->filter_name, $this->filter_data);

		return true;
	}

	/**
	 * Remove an inclusion filter
	 *
	 * @param   string  $root  The root of the filter to remove
	 *
	 * @return bool
	 *
	 * @codeCoverageIgnore
	 */
	private function removeInclusion($root)
	{
		switch ($this->method)
		{
			default:
			case 'api':
			case 'regex':
				// we can't remove filter elements from API or regex type filters
				return false;
				break;

			case 'direct':
				// Get a local reference of the filter data, if necessary
				if (is_null($this->filter_data))
				{
					$filters           = Factory::getFilters();
					$this->filter_data = $filters->getFilterData($this->filter_name);
				}

				if (array_key_exists($root, $this->filter_data))
				{
					unset($this->filter_data[$root]);
				}
				else
				{
					// Root not found
					return false;
				}
				break;
		}

		$filters = Factory::getFilters();
		$filters->setFilterData($this->filter_name, $this->filter_data);

		return true;
	}
}
PK�!]�*��BackupEngine/Filter/Multidb.phpnu&1i�<?php
/**
 * Akeeba Engine
 *
 * @package   akeebaengine
 * @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License version 3, or later
 *
 * 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, version 3.
 *
 * 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
 * <https://www.gnu.org/licenses/>.
 */

namespace Akeeba\Engine\Filter;

defined('AKEEBAENGINE') || die();

/**
 * Multiple Database inclusion filter
 */
class Multidb extends Base
{
	public function __construct()
	{
		$this->object  = 'db';
		$this->subtype = 'inclusion';
		$this->method  = 'direct';

		if (empty($this->filter_name))
		{
			$this->filter_name = strtolower(basename(__FILE__, '.php'));
		}

		parent::__construct();
	}
}
PK�!]j����!BackupEngine/Filter/Skipfiles.phpnu&1i�<?php
/**
 * Akeeba Engine
 *
 * @package   akeebaengine
 * @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License version 3, or later
 *
 * 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, version 3.
 *
 * 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
 * <https://www.gnu.org/licenses/>.
 */

namespace Akeeba\Engine\Filter;

defined('AKEEBAENGINE') || die();

/**
 * Directory contents (files) exclusion filter
 */
class Skipfiles extends Base
{
	public function __construct()
	{
		$this->object  = 'dir';
		$this->subtype = 'content';
		$this->method  = 'direct';

		if (empty($this->filter_name))
		{
			$this->filter_name = strtolower(basename(__FILE__, '.php'));
		}

		parent::__construct();
	}
}
PK�!]r���LL!BackupEngine/Filter/Tabledata.phpnu&1i�<?php
/**
 * Akeeba Engine
 *
 * @package   akeebaengine
 * @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License version 3, or later
 *
 * 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, version 3.
 *
 * 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
 * <https://www.gnu.org/licenses/>.
 */

namespace Akeeba\Engine\Filter;

defined('AKEEBAENGINE') || die();

/**
 * Database table records exclusion filter
 *
 * This is simple stuff. If a table's on the list, it will backup just its structure, not
 * its contents. Fair and square...
 */
class Tabledata extends Base
{
	public function __construct()
	{
		$this->object  = 'dbobject';
		$this->subtype = 'content';
		$this->method  = 'direct';

		if (empty($this->filter_name))
		{
			$this->filter_name = strtolower(basename(__FILE__, '.php'));
		}

		parent::__construct();
	}
}
PK�!]P���#BackupEngine/Filter/Directories.phpnu&1i�<?php
/**
 * Akeeba Engine
 *
 * @package   akeebaengine
 * @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License version 3, or later
 *
 * 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, version 3.
 *
 * 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
 * <https://www.gnu.org/licenses/>.
 */

namespace Akeeba\Engine\Filter;

defined('AKEEBAENGINE') || die();

/**
 * Directory exclusion filter
 */
class Directories extends Base
{
	public function __construct()
	{
		$this->object  = 'dir';
		$this->subtype = 'all';
		$this->method  = 'direct';

		if (empty($this->filter_name))
		{
			$this->filter_name = strtolower(basename(__FILE__, '.php'));
		}

		parent::__construct();
	}
}
PK�!]�R���"BackupEngine/Filter/Regexfiles.phpnu&1i�<?php
/**
 * Akeeba Engine
 *
 * @package   akeebaengine
 * @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License version 3, or later
 *
 * 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, version 3.
 *
 * 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
 * <https://www.gnu.org/licenses/>.
 */

namespace Akeeba\Engine\Filter;

defined('AKEEBAENGINE') || die();

/**
 * Files exclusion filter based on regular expressions
 */
class Regexfiles extends Base
{
	public function __construct()
	{
		$this->object  = 'file';
		$this->subtype = 'all';
		$this->method  = 'regex';

		if (empty($this->filter_name))
		{
			$this->filter_name = strtolower(basename(__FILE__, '.php'));
		}

		parent::__construct();
	}
}
PK�!]��V\��!BackupEngine/Filter/Extradirs.phpnu&1i�<?php
/**
 * Akeeba Engine
 *
 * @package   akeebaengine
 * @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License version 3, or later
 *
 * 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, version 3.
 *
 * 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
 * <https://www.gnu.org/licenses/>.
 */

namespace Akeeba\Engine\Filter;

defined('AKEEBAENGINE') || die();

/**
 * Extra Directories inclusion filter
 */
class Extradirs extends Base
{
	public function __construct()
	{
		$this->object  = 'dir';
		$this->subtype = 'inclusion';
		$this->method  = 'direct';

		if (empty($this->filter_name))
		{
			$this->filter_name = strtolower(basename(__FILE__, '.php'));
		}

		parent::__construct();
	}
}
PK�!]6�o3gg#BackupEngine/Filter/Incremental.phpnu&1i�<?php
/**
 * Akeeba Engine
 *
 * @package   akeebaengine
 * @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License version 3, or later
 *
 * 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, version 3.
 *
 * 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
 * <https://www.gnu.org/licenses/>.
 */

namespace Akeeba\Engine\Filter;

defined('AKEEBAENGINE') || die();

use Akeeba\Engine\Factory;
use Akeeba\Engine\Platform;
use DateTime;
use DateTimeZone;

/**
 * Incremental file filter
 *
 * It will only backup files which are newer than the last backup taken with this profile
 */
class Incremental extends Base
{

	public function __construct()
	{
		$this->object  = 'file';
		$this->subtype = 'all';
		$this->method  = 'api';
	}

	protected function is_excluded_by_api($test, $root)
	{
		static $filter_switch = null;
		static $last_backup = null;

		if (is_null($filter_switch))
		{
			$config        = Factory::getConfiguration();
			$filter_switch = Factory::getEngineParamsProvider()->getScriptingParameter('filter.incremental', 0);
			$filter_switch = ($filter_switch == 1);

			$last_backup = $config->get('volatile.filter.last_backup', null);

			if (is_null($last_backup) && $filter_switch)
			{
				// Get a list of backups on this profile
				$backups = Platform::getInstance()->get_statistics_list([
					'filters' => [
						[
							'field' => 'profile_id',
							'value' => Platform::getInstance()->get_active_profile(),
						],
					],
				]);

				// Find this backup's ID
				$model = Factory::getStatistics();
				$id    = $model->getId();

				if (is_null($id))
				{
					$id = -1;
				}

				// Initialise
				$last_backup = time();
				$now         = $last_backup;

				// Find the last time a successful backup with this profile was made
				if (count($backups))
				{
					foreach ($backups as $backup)
					{
						// Skip the current backup
						if ($backup['id'] == $id)
						{
							continue;
						}

						// Skip non-complete backups
						if ($backup['status'] != 'complete')
						{
							continue;
						}

						$tzUTC      = new DateTimeZone('UTC');
						$dateTime   = new DateTime($backup['backupstart'], $tzUTC);
						$backuptime = $dateTime->getTimestamp();

						$last_backup = $backuptime;
						break;
					}
				}

				if ($last_backup == $now)
				{
					// No suitable backup found; disable this filter
					$config->set('volatile.scripting.incfile.filter.incremental', 0);
					$filter_switch = false;
				}
				else
				{
					// Cache the last backup timestamp
					$config->set('volatile.filter.last_backup', $last_backup);
				}
			}
		}

		if (!$filter_switch)
		{
			return false;
		}

		// Get the filesystem path for $root
		$config   = Factory::getConfiguration();
		$fsroot   = $config->get('volatile.filesystem.current_root', '');
		$ds       = ($fsroot == '') || ($fsroot == '/') ? '' : DIRECTORY_SEPARATOR;
		$filename = $fsroot . $ds . $test;

		// Get the timestamp of the file
		$timestamp = @filemtime($filename);

		// If we could not get this information, include the file in the archive
		if ($timestamp === false)
		{
			return false;
		}

		// Compare it with the last backup timestamp and exclude if it's older than the last backup
		if ($timestamp <= $last_backup)
		{
			//Factory::getLog()->debug("Excluding $filename due to incremental backup restrictions");
			return true;
		}

		// No match? Just include the file!
		return false;
	}

}
PK�!]���+BackupEngine/Filter/Tablesalwaysskipped.phpnu&1i�<?php
/**
 * Akeeba Engine
 *
 * @package   akeebaengine
 * @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License version 3, or later
 *
 * 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, version 3.
 *
 * 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
 * <https://www.gnu.org/licenses/>.
 */

/**
 * @package     Akeeba\Engine\Filter
 * @subpackage
 *
 * @copyright   A copyright
 * @license     A "Slug" license name e.g. GPL2
 */

namespace Akeeba\Engine\Filter;

defined('AKEEBAENGINE') || die();

class Tablesalwaysskipped extends Base
{
	public function __construct()
	{
		$this->object  = 'dbobject';
		$this->subtype = 'content';
		$this->method  = 'api';

		parent::__construct();
	}

	/**
	 * This method must be overridden by API-type exclusion filters.
	 *
	 * @param   string  $test  The object to test for exclusion
	 * @param   string  $root  The object's root
	 *
	 * @return  bool  Return true if it matches your filters
	 */
	protected function is_excluded_by_api($test, $root)
	{
		static $alwaysExcludeTables = [
			// Tables from the service connector that shall not be named
			'bf_core_hashes',
			'bf_files',
			'bf_files_last',
			'bf_folders',
			'bf_folders_to_scan',
		];

		// Is it one of the always excluded tables?
		return in_array($test, $alwaysExcludeTables);
	}

}PK�!]�튮�BackupEngine/Filter/Files.phpnu&1i�<?php
/**
 * Akeeba Engine
 *
 * @package   akeebaengine
 * @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License version 3, or later
 *
 * 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, version 3.
 *
 * 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
 * <https://www.gnu.org/licenses/>.
 */

namespace Akeeba\Engine\Filter;

defined('AKEEBAENGINE') || die();

/**
 * Files exclusion filter
 */
class Files extends Base
{
	public function __construct()
	{
		$this->object  = 'file';
		$this->subtype = 'all';
		$this->method  = 'direct';

		if (empty($this->filter_name))
		{
			$this->filter_name = strtolower(basename(__FILE__, '.php'));
		}

		parent::__construct();
	}
}
PK�!]5Xc�
�
BackupEngine/Dump/native.jsonnu&1i�{
    "_information": {
        "title": "COM_AKEEBA_CONFIG_ENGINE_DUMP_NATIVE_TITLE",
        "description": "COM_AKEEBA_CONFIG_ENGINE_DUMP_NATIVE_DESCRIPTION"
    },
    "engine.dump.divider.common": {
        "default": "0",
        "type": "separator",
        "title": "COM_AKEEBA_CONFIG_DUMP_DIVIDER_COMMON",
        "bold": "1"
    },
    "engine.dump.common.blankoutpass": {
        "default": "0",
        "type": "bool",
        "title": "COM_AKEEBA_CONFIG_BLANKOUTPASS_TITLE",
        "description": "COM_AKEEBA_CONFIG_BLANKOUTPASS_DESCRIPTION"
    },
    "engine.dump.common.extended_inserts": {
        "default": "1",
        "type": "bool",
        "title": "COM_AKEEBA_CONFIG_EXTENDEDINSERTS_TITLE",
        "description": "COM_AKEEBA_CONFIG_EXTENDEDINSERTS_DESCRIPTION"
    },
    "engine.dump.common.packet_size": {
        "default": "131072",
        "type": "integer",
        "min": "1",
        "max": "1048576",
        "shortcuts": "16384|32768|65536|131072|262144|524288|1048576",
        "scale": "1024",
        "uom": "KB",
        "title": "COM_AKEEBA_CONFIG_MAXPACKET_TITLE",
        "description": "COM_AKEEBA_CONFIG_MAXPACKET_DESCRIPTION"
    },
    "engine.dump.common.splitsize": {
        "default": "524288",
        "type": "integer",
        "min": "0",
        "max": "10485760",
        "shortcuts": "524288|1048576|2097152|5242880|10485760",
        "scale": "1048576",
        "uom": "MB",
        "title": "COM_AKEEBA_CONFIG_SPLITDBDUMP_TITLE",
        "description": "COM_AKEEBA_CONFIG_SPLITDBDUMP_DESCRIPTION"
    },
    "engine.dump.common.batchsize": {
        "default": "1000",
        "type": "integer",
        "min": "0",
        "max": "100000",
        "shortcuts": "10|20|50|100|200|500|1000",
        "scale": "1",
        "uom": "queries",
        "title": "COM_AKEEBA_CONFIG_BACTHSIZE_TITLE",
        "description": "COM_AKEEBA_CONFIG_BACTHSIZE_DESCRIPTION"
    },
    "engine.dump.divider.mysql": {
        "default": "0",
        "type": "separator",
        "title": "COM_AKEEBA_CONFIG_DUMP_DIVIDER_MYSQL",
        "bold": "1"
    },
    "engine.dump.native.advanced_entitites": {
        "default": "0",
        "type": "bool",
        "title": "COM_AKEEBA_CONFIG_MYSQL5FEATURES_ENABLE_TITLE",
        "description": "COM_AKEEBA_CONFIG_MYSQL5FEATURES_ENABLE_DESCRIPTION"
    },
    "engine.dump.native.nodependencies": {
        "default": "0",
        "type": "bool",
        "title": "COM_AKEEBA_CONFIG_NODEPENDENCIES_TITLE",
        "description": "COM_AKEEBA_CONFIG_NODEPENDENCIES_DESCRIPTION"
    },
    "engine.dump.native.nobtree": {
        "default": "1",
        "type": "bool",
        "title": "COM_AKEEBA_CONFIG_MYSQLNOBTREE_TITLE",
        "description": "COM_AKEEBA_CONFIG_MYSQLNOBTREE_TIP"
    }
}PK�!]�~v�__BackupEngine/Dump/Native.phpnu&1i�<?php
/**
 * Akeeba Engine
 *
 * @package   akeebaengine
 * @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License version 3, or later
 *
 * 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, version 3.
 *
 * 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
 * <https://www.gnu.org/licenses/>.
 */

namespace Akeeba\Engine\Dump;

defined('AKEEBAENGINE') || die();

use Akeeba\Engine\Base\Exceptions\ErrorException;
use Akeeba\Engine\Base\Part;
use Akeeba\Engine\Dump\Base as DumpBase;
use Akeeba\Engine\Factory;
use RuntimeException;

#[\AllowDynamicProperties]
class Native extends Part
{
	/** @var DumpBase */
	private $_engine = null;

	private $tablePrefix;

	/**
	 * Implements the constructor of the class
	 *
	 * @return  void
	 */
	public function __construct()
	{
		parent::__construct();

		Factory::getLog()->debug(__CLASS__ . " :: New instance");
	}

	/**
	 * Runs the preparation for this part. Should set _isPrepared
	 * to true
	 *
	 * @return  void
	 */
	protected function _prepare()
	{
		Factory::getLog()->debug(__CLASS__ . " :: Processing parameters");

		$options = null;

		// Get the DB connection parameters
		if (is_array($this->_parametersArray))
		{
			$driver   = $this->_parametersArray['driver'] ?? 'mysql';
			$prefix   = $this->_parametersArray['prefix'] ?? '';

			if (($driver == 'mysql') && !function_exists('mysql_connect'))
			{
				$driver = 'mysqli';
			}

			$options = [
				'driver'   => $driver,
				'host'     => $this->_parametersArray['host'] ?? '',
				'port'     => $this->_parametersArray['port'] ?? '',
				'user'     => $this->_parametersArray['user'] ?? ($this->_parametersArray['username'] ?? ''),
				'password' => $this->_parametersArray['password'] ?? '',
				'database' => $this->_parametersArray['database'] ?? '',
				'prefix'   => is_null($prefix) ? '' : $prefix,
			];

			$options['ssl'] = $this->_parametersArray['ssl'] ?? [];
			$options['ssl'] = is_array($options['ssl']) ? $options['ssl'] : [];

			$options['ssl']['enable']             = (bool) ($options['ssl']['enable'] ?? $this->_parametersArray['dbencryption'] ?? false);
			$options['ssl']['cipher']             = ($options['ssl']['cipher'] ?? $this->_parametersArray['dbsslcipher'] ?? null) ?: null;
			$options['ssl']['ca']                 = ($options['ssl']['ca'] ?? $this->_parametersArray['dbsslca'] ?? null) ?: null;
			$options['ssl']['capath']             = ($options['ssl']['capath'] ?? $this->_parametersArray['dbsslcapath'] ?? null) ?: null;
			$options['ssl']['key']                = ($options['ssl']['key'] ?? $this->_parametersArray['dbsslkey'] ?? null) ?: null;
			$options['ssl']['cert']               = ($options['ssl']['cert'] ?? $this->_parametersArray['dbsslcert'] ?? null) ?: null;
			$options['ssl']['verify_server_cert'] = (bool) (($options['ssl']['verify_server_cert'] ?? $this->_parametersArray['dbsslverifyservercert'] ?? false) ?: false);

		}

		$db         = Factory::getDatabase($options);

		if ($db->getErrorNum() > 0)
		{
			$error = $db->getErrorMsg();

			throw new RuntimeException(__CLASS__ . ' :: Database Error: ' . $error);
		}

		$driverType = $db->getDriverType();
		$className  = '\\Akeeba\\Engine\\Dump\\Native\\' . ucfirst($driverType);

		// Check if we have a native dump driver
		if (!class_exists($className, true))
		{
			$this->setState(self::STATE_ERROR);

			throw new ErrorException('Akeeba Engine does not have a native dump engine for ' . $driverType . ' databases');
		}

		Factory::getLog()->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->tablePrefix = $this->_engine->getPrefix();
	}

	/**
	 * Runs the finalisation process for this part. Should set
	 * _isFinished to true.
	 *
	 * @return  void
	 */
	protected function _finalize()
	{
		$this->_engine->callStage('_finalize');
		$this->setState($this->_engine->getState());
	}

	/**
	 * Runs the main functionality loop for this part. Upon calling,
	 * should set the _isRunning to true. When it finished, should set
	 * the _hasRan to true. If an error is encountered, setError should
	 * be used.
	 *
	 * @return  void
	 */
	protected function _run()
	{
		$this->_engine->callStage('_run');
		$this->setState($this->_engine->getState());
		$this->setStep($this->_engine->getStep());
		$this->setSubstep($this->_engine->getSubstep());
		$this->partNumber = $this->_engine->partNumber;
	}

	/**
	 * Get the database table prefix
	 *
	 * @return string The database prefix
	 */
	public function getPrefix()
	{
		return $this->tablePrefix;
	}

}
PK�!]56T�����BackupEngine/Dump/Base.phpnu&1i�<?php
/**
 * Akeeba Engine
 *
 * @package   akeebaengine
 * @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License version 3, or later
 *
 * 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, version 3.
 *
 * 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
 * <https://www.gnu.org/licenses/>.
 */

namespace Akeeba\Engine\Dump;

defined('AKEEBAENGINE') || die();

use Akeeba\Engine\Base\Part;
use Akeeba\Engine\Core\Domain\Pack;
use Akeeba\Engine\Driver\Base as DriverBase;
use Akeeba\Engine\Factory;
use Akeeba\Engine\Platform;
use Akeeba\Engine\Util\FileCloseAware;
use Akeeba\Engine\Util\HashTrait;
use Exception;
use RuntimeException;

#[\AllowDynamicProperties]
abstract class Base extends Part
{
	use HashTrait;
	use FileCloseAware;

	// **********************************************************************
	// Configuration parameters
	// **********************************************************************

	/** @var int Current dump file part number */
	public $partNumber = 0;

	/** @var string Prefix to this database */
	protected $prefix = '';

	/** @var string MySQL database server host name or IP address */
	protected $host = '';

	/** @var string MySQL database server port (optional) */
	protected $port = '';

	/** @var string MySQL socket or named pipe (optional) */
	protected $socket = '';

	/** @var string MySQL user name, for authentication */
	protected $username = '';

	/** @var string MySQL password, for authentication */
	protected $password = '';

	/** @var string MySQL database */
	protected $database = '';

	/** @var string The database driver to use */
	protected $driver = '';

	/** @var array|null The MySQL SSL options */
	protected $ssl;

	// **********************************************************************
	// File handling fields
	// **********************************************************************
	/** @var boolean Should I post process quoted values */
	protected $postProcessValues = false;

	/** @var string Absolute path to dump file; must be writable (optional; if left blank it is automatically calculated) */
	protected $dumpFile = '';

	/** @var string Data cache, used to cache data before being written to disk */
	protected $data_cache = '';

	/** @var int */
	protected $largest_query = 0;

	/** @var int Size of the data cache, default 128Kb */
	protected $cache_size = 131072;

	/** @var bool Should I process empty prefixes when creating abstracted names? */
	protected $processEmptyPrefix = true;

	/** @var string Absolute path to the temp file */
	protected $tempFile = '';

	/** @var string Relative path of how the file should be saved in the archive */
	protected $saveAsName = '';

	/** @var array Contains the sorted (by dependencies) list of tables/views to backup */
	protected $tables = [];

	// **********************************************************************
	// Protected fields (data handling)
	// **********************************************************************
	/** @var array Contains the configuration data of the tables */
	protected $tables_data = [];

	/** @var array Maps database table names to their abstracted format */
	protected $table_name_map = [];

	/** @var array Contains the dependencies of tables and views (temporary) */
	protected $dependencies = [];

	/** @var string The next table to backup */
	protected $nextTable;

	/** @var integer The next row of the table to start backing up from */
	protected $nextRange;

	/** @var integer Current table's row count */
	protected $maxRange;

	/** @var bool Use extended INSERTs */
	protected $extendedInserts = false;

	/** @var integer Maximum packet size for extended INSERTs, in bytes */
	protected $packetSize = 0;

	/** @var string Extended INSERT query, while it's being constructed */
	protected $query = '';

	/** @var int Dump part's maximum size */
	protected $partSize = 0;

	/** @var resource Filepointer to the current dump part */
	private $fp = null;

	/**
	 * Should I be using an abstract prefix (#__) for table names?
	 *
	 * @var   bool
	 * @since 9.1.0
	 */
	private $useAbstractPrefix;

	/**
	 * Public constructor.
	 *
	 * @return  void
	 * @since   9.1.0
	 */
	public function __construct()
	{
		$this->useAbstractPrefix =
			Factory::getEngineParamsProvider()->getScriptingParameter('db.saveasname', 'normal') !== 'output';

		parent::__construct();
	}


	/**
	 * This method is called when the factory is being serialized and is used to perform necessary cleanup steps.
	 *
	 * @return  void
	 */
	public function _onSerialize()
	{
		$this->closeFile();
	}

	/**
	 * This method is called when the object is destroyed and is used to perform necessary cleanup steps.
	 */
	public function __destruct()
	{
		$this->closeFile();
	}

	/**
	 * Close any open SQL dump (output) file.
	 */
	public function closeFile()
	{
		if (!is_resource($this->fp))
		{
			return;
		}

		Factory::getLog()->debug("Closing SQL dump file.");

		$this->conditionalFileClose($this->fp);
		$this->fp = null;
	}

	/**
	 * Call a specific stage of the dump engine
	 *
	 * @param   string  $stage
	 *
	 * @throws Exception
	 */
	public function callStage($stage)
	{
		switch ($stage)
		{
			case '_prepare':
				$this->_prepare();
				break;

			case '_run':
				$this->_run();
				break;

			case '_finalize':
				$this->_finalize();
				break;
		}
	}

	public function getPrefix(): string
	{
		return $this->prefix ?: '';
	}

	/**
	 * Find where to store the backup files
	 *
	 * @param $partNumber int The SQL part number, default is 0 (.sql)
	 */
	protected function getBackupFilePaths($partNumber = 0)
	{
		Factory::getLog()->debug(__CLASS__ . " :: Getting temporary file");
		$basename       = substr(self::md5(microtime() . random_bytes(8)), random_int(0, 16), 16);
		$this->tempFile = Factory::getTempFiles()->registerTempFile($basename . '.sql');
		Factory::getLog()->debug(__CLASS__ . " :: Temporary file is {$this->tempFile}");

		// Get the base name of the dump file
		$partNumber = intval($partNumber);
		$baseName   = $this->dumpFile;

		if ($partNumber > 0)
		{
			// The file names are in the format dbname.sql, dbname.s01, dbname.s02, etc
			if (strtolower(substr($baseName, -4)) == '.sql')
			{
				$baseName = substr($baseName, 0, -4) . '.s' . sprintf('%02u', $partNumber);
			}
			else
			{
				$baseName = $baseName . '.s' . sprintf('%02u', $partNumber);
			}
		}

		if (empty($this->installerSettings))
		{
			// Fetch the installer settings
			$this->installerSettings = (object) [
				'installerroot' => 'installation',
				'sqlroot'       => 'installation/sql',
				'databasesini'  => 1,
				'readme'        => 1,
				'extrainfo'     => 1,
			];
			$config                  = Factory::getConfiguration();
			$installerKey            = $config->get('akeeba.advanced.embedded_installer');
			$installerDescriptors    = Factory::getEngineParamsProvider()->getInstallerList();

			if (array_key_exists($installerKey, $installerDescriptors))
			{
				// The selected installer exists, use it
				$this->installerSettings = (object) $installerDescriptors[$installerKey];
			}
			elseif (array_key_exists('angie', $installerDescriptors))
			{
				// The selected installer doesn't exist, but ANGIE exists; use that instead
				$this->installerSettings = (object) $installerDescriptors['angie'];
			}
		}

		switch (Factory::getEngineParamsProvider()->getScriptingParameter('db.saveasname', 'normal'))
		{
			case 'output':
				// The SQL file will be stored uncompressed in the output directory
				$statistics       = Factory::getStatistics();
				$statRecord       = $statistics->getRecord();
				$this->saveAsName = $statRecord['absolute_path'];
				break;

			case 'normal':
				// The SQL file will be stored in the SQL root of the archive, as
				// specified by the particular embedded installer's settings
				$this->saveAsName = $this->installerSettings->sqlroot . '/' . $baseName;
				break;

			case 'short':
				// The SQL file will be stored on archive's root
				$this->saveAsName = $baseName;
				break;
		}

		if ($partNumber > 0)
		{
			Factory::getLog()->debug("AkeebaDomainDBBackup :: Creating new SQL dump part #$partNumber");
		}

		Factory::getLog()->debug("AkeebaDomainDBBackup :: SQL temp file is " . $this->tempFile);
		Factory::getLog()->debug("AkeebaDomainDBBackup :: SQL file location in archive is " . $this->saveAsName);
	}

	/**
	 * Deletes any leftover files from previous backup attempts
	 *
	 */
	protected function removeOldFiles()
	{
		Factory::getLog()->debug("AkeebaDomainDBBackup :: Deleting leftover files, if any");

		if (file_exists($this->tempFile))
		{
			@unlink($this->tempFile);
		}
	}

	/**
	 * Populates the table arrays with the information for the db entities to back up
	 *
	 * @return  void
	 */
	abstract protected function getTablesToBackup(): void;

	/**
	 * Runs a step of the database dump
	 *
	 * @return  void
	 */
	abstract protected function stepDatabaseDump(): void;

	/**
	 * Return the current database name by querying the database connection object (e.g. SELECT DATABASE() in MySQL)
	 *
	 * @return  string
	 */
	abstract protected function getDatabaseNameFromConnection(): string;

	abstract protected function getAllTables(): array;

	/**
	 * Implements the _prepare abstract method
	 *
	 * @throws Exception
	 */
	protected function _prepare()
	{
		$this->setStep('Initialization');
		$this->setSubstep('');

		// Process parameters, passed to us using the setup() public method
		Factory::getLog()->debug(__CLASS__ . " :: Processing parameters");

		if (is_array($this->_parametersArray))
		{
			$this->parametersArrayToProperties();
		}

		// Try to detect and rectify the wrong database table naming prefix
		$this->workaroundWrongPrefix();

		// Make sure we have self-assigned the first part
		$this->partNumber = 0;

		// Get DB backup only mode
		$configuration = Factory::getConfiguration();

		// Find tables to be included and put them in the $_tables variable
		$this->getTablesToBackup();

		// Find where to store the database backup files
		$this->getBackupFilePaths($this->partNumber);

		// Remove any leftovers
		$this->removeOldFiles();

		// Initialize the extended INSERTs feature
		$this->extendedInserts = ($configuration->get('engine.dump.common.extended_inserts', 0) != 0);
		$this->packetSize      = (int) $configuration->get('engine.dump.common.packet_size', 0);

		if ($this->packetSize == 0)
		{
			$this->extendedInserts = false;
		}

		// Initialize the split dump feature
		$this->partSize = $configuration->get('engine.dump.common.splitsize', 1048576);
		if (Factory::getEngineParamsProvider()->getScriptingParameter('db.saveasname', 'normal') == 'output')
		{
			$this->partSize = 0;
		}
		if (($this->partSize != 0) && ($this->packetSize != 0) && ($this->packetSize > $this->partSize))
		{
			$this->packetSize = floor($this->partSize / 2);
		}

		// Initialize the algorithm
		Factory::getLog()->debug(__CLASS__ . " :: Initializing algorithm for first run");
		$this->nextTable = array_shift($this->tables);

		// If there is no table to back up we are done with the database backup
		if (empty($this->nextTable) && $this->nextTable !== '0')
		{
			$this->setState(self::STATE_POSTRUN);

			return;
		}

		$this->nextRange = 0;
		$this->query     = '';

		// FIX 2.2: First table of extra databases was not being written to disk.
		// This deserved a place in the Bug Fix Hall Of Fame. In subsequent calls to _init, the $fp in
		// _writeline() was not nullified. Therefore, the first dump chunk (that is, the first table's
		// definition and first chunk of its data) were not written to disk. This call causes $fp to be
		// nullified, causing it to be recreated, pointing to the correct file.
		$null = null;
		$this->writeline($null);

		// Finally, mark ourselves "prepared".
		$this->setState(self::STATE_PREPARED);
	}

	/**
	 * Implements the _run() abstract method
	 *
	 * @throws Exception
	 */
	protected function _run()
	{
		// Check if we are already done
		if ($this->getState() == self::STATE_POSTRUN)
		{
			Factory::getLog()->debug(__CLASS__ . " :: Already finished");
			$this->setStep("");
			$this->setSubstep("");

			return;
		}

		// Mark ourselves as still running (we will test if we actually do towards the end ;) )
		$this->setState(self::STATE_RUNNING);

		/**
		 * Resume packing / post-processing part files if necessary.
		 *
		 * @see \Akeeba\Engine\Archiver\BaseArchiver::putDataFromFileIntoArchive
		 *
		 * Sometimes the SQL part file may be bigger than the big file threshold (engine.archiver.common.
		 * big_file_threshold). In this case when we try to add it to the backup archive the archiver engine figures
		 * out it has to be added uncompressed, one chunk (engine.archiver.common.chunk_size) bytes at a time. This
		 * happens in a loop. We read a chunk, push it to the archive, rinse and repeat.
		 *
		 * There are two cases when we might break the loop:
		 *
		 * 1. Not enough free space in the backup archive part and engine.postproc.common.after_part (immediate post-
		 *    processing) is enabled. We break the step to let the backup part be post-processed.
		 *
		 * 2. We ran out of time copying data.
		 *
		 * The following if-blocks deal with these two cases.
		 */
		if (Factory::getEngineParamsProvider()->getScriptingParameter('db.saveasname', 'normal') != 'output')
		{
			$archiver      = Factory::getArchiverEngine();
			$configuration = Factory::getConfiguration();

			// Check whether we need to immediately post-processing a done part
			if (Pack::postProcessDonePartFile($archiver, $configuration))
			{
				return;
			}

			// We had already started putting the DB dump file into the archive but it needs more time
			if ($configuration->get('volatile.engine.archiver.processingfile', false))
			{
				/**
				 * We MUST NOT try to continue adding the file to the backup archive manually. Instead, we have to go
				 * through getNextDumpPart. This method will continue adding the part to the backup archive and when
				 * this is done it will remove the file and create a new one.
				 *
				 * If that method returns false it means that we either hit an error or the archiver didn't have enough
				 * time to add the part to the backup archive. In either case we have to return and let the Engine step.
				 */
				if ($this->getNextDumpPart() === false)
				{
					return;
				}
			}
		}

		$this->stepDatabaseDump();

		$null = null;
		$this->writeline($null);
	}

	/**
	 * Implements the _finalize() abstract method
	 *
	 * @throws Exception
	 */
	protected function _finalize()
	{
		Factory::getLog()->debug("Adding any extra SQL statements imposed by the filters");

		foreach (Factory::getFilters()->getExtraSQL($this->databaseRoot) as $sqlStatement)
		{
			$sqlStatement = trim($sqlStatement) . "\n";

			$this->writeDump($sqlStatement, true);
		}

		// We need this to write out the cached extra SQL statements before closing the file.
		$this->writeDump(null, true);

		// Close the file pointer (otherwise the SQL file is left behind)
		$this->closeFile();

		// If we are not just doing a main db only backup, add the SQL file to the archive
		$finished = true;

		if (Factory::getEngineParamsProvider()->getScriptingParameter('db.saveasname', 'normal') != 'output')
		{
			$archiver      = Factory::getArchiverEngine();
			$configuration = Factory::getConfiguration();

			if ($configuration->get('volatile.engine.archiver.processingfile', false))
			{
				// We had already started archiving the db file, but it needs more time
				Factory::getLog()->debug("Continuing adding the SQL dump to the archive");
				$archiver->addFile(null, null, null);

				$finished = !$configuration->get('volatile.engine.archiver.processingfile', false);
			}
			else
			{
				// We have to add the dump file to the archive
				Factory::getLog()->debug("Adding the final SQL dump to the archive");
				$archiver->addFileRenamed($this->tempFile, $this->saveAsName);

				$finished = !$configuration->get('volatile.engine.archiver.processingfile', false);
			}
		}
		else
		{
			// We just have to move the dump file to its final destination
			Factory::getLog()->debug("Moving the SQL dump to its final location");
			$result = Platform::getInstance()->move($this->tempFile, $this->saveAsName);

			if (!$result)
			{
				Factory::getLog()->debug("Removing temporary file of final SQL dump");
				Factory::getTempFiles()->unregisterAndDeleteTempFile($this->tempFile, true);

				throw new RuntimeException('Could not move the SQL dump to its final location');
			}
		}

		// Make sure that if the archiver needs more time to process the file we can supply it
		if ($finished)
		{
			Factory::getLog()->debug("Removing temporary file of final SQL dump");
			Factory::getTempFiles()->unregisterAndDeleteTempFile($this->tempFile, true);

			$this->setState(self::STATE_FINISHED);
		}
	}

	/**
	 * Creates a new dump part
	 *
	 * @return bool
	 * @throws Exception
	 */
	protected function getNextDumpPart()
	{
		// On database dump only mode we mustn't create part files!
		if (Factory::getEngineParamsProvider()->getScriptingParameter('db.saveasname', 'normal') == 'output')
		{
			return false;
		}

		// Is the archiver still processing?
		$configuration   = Factory::getConfiguration();
		$archiver        = Factory::getArchiverEngine();
		$stillProcessing = $configuration->get('volatile.engine.archiver.processingfile', false);

		if ($stillProcessing)
		{
			/**
			 * The archiver is still adding the previous dump part. This means that we are called from the top few lines
			 * of the _run method. We must continue adding the previous dump part.
			 */
			Factory::getLog()->debug("Continuing adding the SQL dump part to the archive");
			$archiver->addFile('', '', '');
		}
		else
		{
			/**
			 * There is no other dump part being processed. Therefore the current SQL dump part is still open. We must
			 * close it and ask the archiver to add it to the backup archive.
			 */
			$this->closeFile();
			Factory::getLog()->debug("Adding the SQL dump part to the archive");
			$archiver->addFileRenamed($this->tempFile, $this->saveAsName);
		}

		// Return false if the file didn't finish getting added to the archive
		if ($configuration->get('volatile.engine.archiver.processingfile', false))
		{
			Factory::getLog()->debug("The SQL dump file has not been processed thoroughly by the archiver. Resuming in the next step.");

			return false;
		}

		/**
		 * If you are here the SQL dump part file is completely added to the backup archive. All we have to do now is
		 * remove it and create a new dump part file.
		 */
		// Remove the old file
		Factory::getLog()->debug("Removing dump part's temporary file");
		Factory::getTempFiles()->unregisterAndDeleteTempFile($this->tempFile, true);

		// Create the new dump part
		$this->partNumber++;
		$this->getBackupFilePaths($this->partNumber);
		$null = null;
		$this->writeline($null);

		return true;
	}

	/**
	 * Creates a new dump part, but only if required to do so
	 *
	 * @return bool
	 * @throws Exception
	 */
	protected function createNewPartIfRequired()
	{
		if ($this->partSize == 0)
		{
			return true;
		}

		$filesize = 0;

		if (@file_exists($this->tempFile))
		{
			$filesize = @filesize($this->tempFile);
		}

		$projectedSize = $filesize + strlen($this->query);

		if ($this->extendedInserts)
		{
			$projectedSize = $filesize + $this->packetSize;
		}

		if ($projectedSize > $this->partSize)
		{
			return $this->getNextDumpPart();
		}

		return true;
	}

	/**
	 * Returns a table's abstract name (replacing the prefix with the magic #__ string)
	 *
	 * @param   string  $tableName  The canonical name, e.g. 'jos_content'
	 *
	 * @return string The abstract name, e.g. '#__content'
	 */
	protected function getAbstract($tableName)
	{
		// Don't return abstract names for non-CMS tables
		if (is_null($this->prefix))
		{
			return $tableName;
		}

		switch ($this->prefix)
		{
			case '':
				if ($this->processEmptyPrefix)
				{
					// This is more of a hack; it assumes all tables are core CMS tables if the prefix is empty.
					return '#__' . $tableName;
				}

				// If $this->processEmptyPrefix (the process_empty_prefix config flag) is false, we don't
				// assume anything.
				return $tableName;

				break;

			default:
				// Normal behaviour for 99% of sites. Start by assuming the table has no prefix, therefore is non-core.
				$tableAbstract = $tableName;

				// If there's a prefix use the abstract name
				if (!empty($this->prefix) && (substr($tableName, 0, strlen($this->prefix)) == $this->prefix))
				{
					$tableAbstract = '#__' . substr($tableName, strlen($this->prefix));
				}

				return $tableAbstract;

				break;
		}
	}

	/**
	 * Writes the SQL dump into the output files. If it fails, it sets the error
	 *
	 * @param   string  $data       Data to write to the dump file. Pass NULL to force flushing to file.
	 * @param   bool    $addMarker  Should I prefix the data with a marker?
	 *
	 * @return  boolean  TRUE on successful write, FALSE otherwise
	 * @throws  Exception
	 */
	protected function writeDump($data, $addMarker = false)
	{
		if (!empty($data))
		{
			if ($addMarker && $this->useAbstractPrefix)
			{
				$this->data_cache .= '/**ABDB**/';
			}
			elseif (!$this->useAbstractPrefix)
			{
				// Replace #__ with the prefix when writing plain .sql files
				$db   = $this->getDB();
				$data = $db->replacePrefix($data) . "\n";
			}

			$this->data_cache .= $data;

			if (strlen($data) > $this->largest_query)
			{
				$this->largest_query = strlen($data);
				Factory::getConfiguration()->set('volatile.database.largest_query', $this->largest_query);
			}
		}

		if ((strlen($this->data_cache) >= $this->cache_size) || (is_null($data) && (!empty($this->data_cache))))
		{
			$this->data_cache = rtrim($this->data_cache, "\n");

			if ($this->useAbstractPrefix && $addMarker && substr($this->data_cache, -10) !== '/**ABDB**/')
			{
				$this->data_cache .= '/**ABDB**/';
			}

			$this->data_cache .= "\n";

			Factory::getLog()->debug("Writing " . strlen($this->data_cache) . " bytes to the dump file");
			$result = $this->writeline($this->data_cache);

			if (!$result)
			{
				$errorMessage = sprintf('Couldn\'t write to the SQL dump file %s; check the temporary directory permissions and make sure you have enough disk space available.', $this->tempFile);
				throw new RuntimeException($errorMessage);
			}

			$this->data_cache = '';
		}

		return true;
	}

	/**
	 * Saves the string in $fileData to the file $backupfile. Returns TRUE. If saving
	 * failed, return value is FALSE.
	 *
	 * @param   string  $fileData  Data to write. Set to null to close the file handle.
	 *
	 * @return boolean TRUE is saving to the file succeeded
	 * @throws Exception
	 */
	protected function writeline(&$fileData)
	{
		if (!is_resource($this->fp))
		{
			$this->fp = @fopen($this->tempFile, 'a');

			if ($this->fp === false)
			{
				throw new RuntimeException('Could not open ' . $this->tempFile . ' for append, in DB dump.');
			}
		}

		if (is_null($fileData))
		{
			$this->conditionalFileClose($this->fp);

			$this->fp = null;

			return true;
		}
		else
		{
			if ($this->fp)
			{
				$ret = fwrite($this->fp, $fileData);
				@clearstatcache();

				// Make sure that all data was written to disk
				return ($ret == strlen($fileData));
			}

			return false;
		}
	}

	/**
	 * Return an instance of DriverBase
	 *
	 * @return DriverBase|bool
	 *
	 * @throws Exception
	 */
	protected function &getDB()
	{
		$ssl     = $this->ssl ?? [];
		$ssl     = is_array($ssl) ? $ssl : [];
		$options = [
			'driver'   => $this->driver,
			'host'     => $this->host,
			'port'     => $this->port,
			'socket'   => $this->socket,
			'user'     => $this->username,
			'password' => $this->password,
			'database' => $this->database,
			'prefix'   => is_null($this->prefix) ? '' : $this->prefix,
			'ssl'      => $ssl,
		];

		$db = Factory::getDatabase($options);

		if ($db->getErrorNum() > 0)
		{
			$error = $db->getErrorMsg();

			throw new RuntimeException(__CLASS__ . ' :: Database Error: ' . $error);
		}

		return $db;
	}

	/**
	 * Returns the database name. If the name was not declared when the object was created we will go through the
	 * getDatabaseNameFromConnection method to populate it.
	 *
	 * @return  string
	 */
	protected function getDatabaseName()
	{
		if (empty($this->database) && $this->database !== '0')
		{
			$this->database = $this->getDatabaseNameFromConnection();
		}

		return $this->database;
	}

	/**
	 * Post process a quoted value before it's written to the database dump.
	 * So far it's only required for SQL Server which has a problem escaping
	 * newline characters...
	 *
	 * @param   string  $value  The quoted value to post-process
	 *
	 * @return  string
	 */
	protected function postProcessQuotedValue($value)
	{
		return $value;
	}

	/**
	 * Returns a preamble for the data dump portion of the SQL backup. This is
	 * used to output commands before the first INSERT INTO statement for a
	 * table when outputting a plain SQL file.
	 *
	 * Practical use: the SET IDENTITY_INSERT sometable ON required for SQL Server
	 *
	 * @param   string   $tableAbstract  Abstract name of the table, e.g. #__foobar
	 * @param   string   $tableName      Real name of the table, e.g. abc_foobar
	 * @param   integer  $maxRange       Row count on this table
	 *
	 * @return  string   The SQL commands you want to be written in the dump file
	 */
	protected function getDataDumpPreamble($tableAbstract, $tableName, $maxRange)
	{
		return '';
	}

	/**
	 * Returns an epilogue for the data dump portion of the SQL backup. This is
	 * used to output commands after the last INSERT INTO statement for a
	 * table when outputting a plain SQL file.
	 *
	 * Practical use: the SET IDENTITY_INSERT sometable OFF required for SQL Server
	 *
	 * @param   string   $tableAbstract  Abstract name of the table, e.g. #__foobar
	 * @param   string   $tableName      Real name of the table, e.g. abc_foobar
	 * @param   integer  $maxRange       Row count on this table
	 *
	 * @return  string   The SQL commands you want to be written in the dump file
	 */
	protected function getDataDumpEpilogue($tableAbstract, $tableName, $maxRange)
	{
		return '';
	}

	/**
	 * Return a list of field names for the INSERT INTO statements. This is only
	 * required for Microsoft SQL Server because without it the SET IDENTITY_INSERT
	 * has no effect.
	 *
	 * @param   array|string  $fieldNames  A list of field names in array format or '*' if it's all fields
	 *
	 * @return  string
	 * @throws Exception
	 */
	protected function getFieldListSQL($fieldNames)
	{
		// If we get a literal '*' we dumped all columns so we don't need to add column names in the INSERT.
		if ($fieldNames === '*')
		{
			return '';
		}

		return '(' . implode(', ', array_map([$this->getDB(), 'qn'], $fieldNames)) . ')';
	}

	/**
	 * Return a list of columns to use in the SELECT query for dumping table data.
	 *
	 * This is used to filter out all generated rows.
	 *
	 * @param   string  $tableAbstract
	 *
	 * @return  string|array  An array of table columns or the string literal '*' to quickly select all columns.
	 *
	 * @see  https://dev.mysql.com/doc/refman/5.7/en/create-table-generated-columns.html
	 */
	protected function getSelectColumns($tableAbstract)
	{
		return '*';
	}

	/**
	 * Converts a human formatted size to integer representation of bytes,
	 * e.g. 1M to 1024768
	 *
	 * @param   string  $setting  The value in human readable format, e.g. "1M"
	 *
	 * @return  integer  The value in bytes
	 */
	protected function humanToIntegerBytes($setting)
	{
		$val  = trim($setting);
		$last = strtolower($val[strlen($val) - 1]);

		if (is_numeric($last))
		{
			return $setting;
		}

		switch ($last)
		{
			case 't':
				$val *= 1024;
			case 'g':
				$val *= 1024;
			case 'm':
				$val *= 1024;
			case 'k':
				$val *= 1024;
		}

		return (int) $val;
	}

	/**
	 * Get the PHP memory limit in bytes
	 *
	 * @return int|null  Memory limit in bytes or null if we can't figure it out.
	 */
	protected function getMemoryLimit()
	{
		if (!function_exists('ini_get'))
		{
			return null;
		}

		$memLimit = ini_get("memory_limit");

		if ((is_numeric($memLimit) && ($memLimit < 0)) || !is_numeric($memLimit))
		{
			$memLimit = 0; // 1.2a3 -- Rare case with memory_limit < 0, e.g. -1Mb!
		}

		$memLimit = $this->humanToIntegerBytes($memLimit);

		return $memLimit;
	}

	/**
	 * @return void
	 */
	private function parametersArrayToProperties(): void
	{
		$this->driver             = $this->_parametersArray['driver'] ?? $this->driver;
		$this->host               = $this->_parametersArray['host'] ?? $this->host;
		$this->port               = $this->_parametersArray['port'] ?? $this->port;
		$this->socket             = $this->_parametersArray['socket'] ?? $this->socket;
		$this->username           = $this->_parametersArray['username'] ?? $this->username;
		$this->username           = $this->_parametersArray['user'] ?? $this->username;
		$this->password           = $this->_parametersArray['password'] ?? $this->password;
		$this->database           = $this->_parametersArray['database'] ?? $this->database;
		$this->prefix             = $this->_parametersArray['prefix'] ?? $this->prefix;
		$this->dumpFile           = $this->_parametersArray['dumpFile'] ?? $this->dumpFile;
		$this->processEmptyPrefix = $this->_parametersArray['process_empty_prefix'] ?? $this->processEmptyPrefix;
		$this->ssl                = $this->_parametersArray['ssl'] ?? $this->ssl;
		$this->ssl                = is_array($this->ssl) ? $this->ssl : [];

		$this->ssl['enable']             = (bool) (($this->ssl['enable'] ?? $this->_parametersArray['dbencryption'] ?? false) ?: false);
		$this->ssl['cipher']             = ($this->ssl['cipher'] ?? $this->_parametersArray['dbsslcipher'] ?? null) ?: null;
		$this->ssl['ca']                 = ($this->ssl['ca'] ?? $this->_parametersArray['dbsslca'] ?? null) ?: null;
		$this->ssl['capath']             = ($this->ssl['capath'] ?? $this->_parametersArray['dbsslcapath'] ?? null) ?: null;
		$this->ssl['key']                = ($this->ssl['key'] ?? $this->_parametersArray['dbsslkey'] ?? null) ?: null;
		$this->ssl['cert']               = ($this->ssl['cert'] ?? $this->_parametersArray['dbsslcert'] ?? null) ?: null;
		$this->ssl['verify_server_cert'] = (bool) (($this->ssl['verify_server_cert'] ?? $this->_parametersArray['dbsslverifyservercert'] ?? false) ?: false);
	}

	private function workaroundWrongPrefix(): void
	{
		// Let's see what kind of prefix I have
		$allLowerCasePrefix = strtolower($this->prefix);
		$allUpperCasePrefix = strtoupper($this->prefix);
		$isUpperCasePrefix  = $this->prefix === $allUpperCasePrefix;
		$isLowerCasePrefix  = $this->prefix === $allLowerCasePrefix;
		$isMixedCasePrefix  = !$isUpperCasePrefix && !$isLowerCasePrefix;

		// Log a message
		if ($isUpperCasePrefix)
		{
			Factory::getLog()->info(
				sprintf(
					'You have an all uppercase database prefix (%s). This might cause backup and restoration issues. We are applying automatic mitigations.',
					$this->prefix
				)
			);
		}
		elseif (!$isLowerCasePrefix)
		{
			Factory::getLog()->info(
				sprintf(
					'You have a mixed-case database prefix (%s). This might cause backup and restoration issues. We are applying automatic mitigations.',
					$this->prefix
				)
			);
		}

		// Check if I have any tables with any form of the prefix
		$allTables = $this->getAllTables();

		$hasOriginalTables = array_reduce(
			$allTables,
			function (bool $carry, string $table): bool {
				return $carry || substr($table, 0, strlen($this->prefix)) === $this->prefix;
			},
			false
		);

		$hasLowercaseTables = array_reduce(
			$allTables,
			function (bool $carry, string $table) use ($allLowerCasePrefix): bool {
				return $carry || substr($table, 0, strlen($allLowerCasePrefix)) === $allLowerCasePrefix;
			},
			false
		);

		$hasUppercaseTables = array_reduce(
			$allTables,
			function (bool $carry, string $table) use ($allUpperCasePrefix): bool {
				return $carry || substr($table, 0, strlen($allUpperCasePrefix)) === $allUpperCasePrefix;
			},
			false
		);

		$hasWrongMixedCaseTables = array_reduce(
			$allTables,
			function (bool $carry, string $table) use ($allUpperCasePrefix, $allLowerCasePrefix): bool {
				if ($carry)
				{
					return $carry;
				}

				$prefix = substr($table, 0, strlen($allUpperCasePrefix));

				if ($prefix === $allUpperCasePrefix || $prefix === $allLowerCasePrefix || $prefix === $this->prefix)
				{
					return false;
				}

				return strtolower($prefix) === strtolower($this->prefix);
			},
			false
		);

		// Set up the warning message
		$warningMessage = sprintf(
			'You have database tables whose name starts with a form of the database prefix (%s) which has a different letter case. This WILL cause problems if you restore your site on Windows or macOS. We strongly recommend excluding all tables which do not start with the configured prefix, exactly as shown above (case-sensitive).',
			$this->prefix
		);

		/**
		 * We have tables returned with the original prefix (e.g. fOo_).
		 *
		 * We won't change the prefix, but we have to warn the user if there is a mix of prefixes in the installed
		 * tables.
		 *
		 * We warn when:
		 * - Any kind of prefix, there are tables with a mixed case prefix which doesn't match the configured one.
		 * - Lowercase prefix, there are uppercase tables
		 * - Uppercase prefix, there are lowercase tables
		 * - Mixed case prefix, there are upper- or lowercase tables
		 */
		if ($hasOriginalTables)
		{
			if (
				$hasWrongMixedCaseTables
				|| ($isLowerCasePrefix && $hasUppercaseTables)
				|| ($isUpperCasePrefix && $hasLowercaseTables)
				|| ($isMixedCasePrefix && ($hasLowercaseTables || $hasUppercaseTables))
			)
			{
				Factory::getLog()->warning($warningMessage);
			}

			return;
		}

		/**
		 * No tables with this prefix. Nothing for me to do.
		 *
		 * At this point we have checked if there are any tables with the mixed case prefix, the all lowercase prefix,
		 * and the uppercase prefix. None was found in any of these cases.
		 *
		 * I will not change the prefix to back up. However, if there are tables with the wrong mixed case format of the
		 * prefix I will have to issue a warning.
		 */
		if (!$hasLowercaseTables && !$hasUppercaseTables)
		{
			if ($hasWrongMixedCaseTables)
			{
				Factory::getLog()->warning($warningMessage);
			}

			return;
		}

		if (
			($isLowerCasePrefix && !$hasLowercaseTables && $hasUppercaseTables)
			|| ($isMixedCasePrefix && $hasUppercaseTables)
		)
		{
			Factory::getLog()->info(
				sprintf(
					'Auto-fixing the database prefix: you have configured the database table name prefix %s but we could not find any tables with this prefix. Instead, we found tables with the %s prefix; using that instead.',
					$this->prefix, $allUpperCasePrefix
				)
			);

			$this->_parametersArray['prefix'] = $allUpperCasePrefix;
			$this->prefix                     = $allUpperCasePrefix;
		}
		elseif (
			($isUpperCasePrefix && !$hasUppercaseTables && $hasLowercaseTables)
			|| ($isMixedCasePrefix && $hasLowercaseTables)
		)
		{
			Factory::getLog()->info(
				sprintf(
					'Auto-fixing the database prefix: you have configured the database table name prefix %s but we could not find any tables with this prefix. Instead, we found tables with the %s prefix; using that instead.',
					$this->prefix, $allLowerCasePrefix
				)
			);

			$this->_parametersArray['prefix'] = $allLowerCasePrefix;
			$this->prefix                     = $allLowerCasePrefix;
		}
		else
		{
			Factory::getLog()->warning(
				sprintf(
					'WRONG DATABASE PREFIX. You have configured the database table name prefix %s but we could not find any tables with this prefix, its lowercase (%s) or uppercase (%s) form. THIS WILL CAUSE RESTORATION PROBLEMS. Please rename your tables starting with different forms of the %1$s prefix so that they all start with its lowercase form (%2$s), then retake the backup.',
					$this->prefix, $allLowerCasePrefix, $allUpperCasePrefix
				)
			);

			return;
		}

		if ($hasLowercaseTables && $hasUppercaseTables)
		{
			Factory::getLog()->warning($warningMessage);
		}
	}
}
PK�!]�ԫ%QQ#BackupEngine/Dump/Native/Sqlite.phpnu&1i�<?php
/**
 * Akeeba Engine
 *
 * @package   akeebaengine
 * @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License version 3, or later
 *
 * 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, version 3.
 *
 * 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
 * <https://www.gnu.org/licenses/>.
 */

namespace Akeeba\Engine\Dump\Native;

defined('AKEEBAENGINE') || die();

use RuntimeException;

/**
 * Dump class for the "None" database driver (ie no database used by the application)
 */
#[\AllowDynamicProperties]
class Sqlite extends None
{
	public function __construct()
	{
		parent::__construct();

		throw new RuntimeException("Please do not add SQLite databases, they are files. If they are under your site's root they are backed up automatically. Otherwise use the Off-site Directories Inclusion to include them in the backup.");
	}

}
PK�!]����!BackupEngine/Dump/Native/None.phpnu&1i�<?php
/**
 * Akeeba Engine
 *
 * @package   akeebaengine
 * @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License version 3, or later
 *
 * 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, version 3.
 *
 * 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
 * <https://www.gnu.org/licenses/>.
 */

namespace Akeeba\Engine\Dump\Native;

defined('AKEEBAENGINE') || die();

use Akeeba\Engine\Dump\Base;
use Akeeba\Engine\Factory;

/**
 * Dump class for the "None" database driver (ie no database used by the application)
 */
#[\AllowDynamicProperties]
class None extends Base
{
	public function __construct()
	{
		parent::__construct();
	}

	/** @inheritDoc */
	protected function getTablesToBackup(): void
	{
	}

	/** @inheritDoc */
	protected function stepDatabaseDump(): void
	{
		Factory::getLog()->info("Reminder: database definitions using the 'None' driver result in no data being backed up.");

		$this->setState(self::STATE_FINISHED);
	}

	/** @inheritDoc */
	protected function getDatabaseNameFromConnection(): string
	{
		return '';
	}

	/** @inheritDoc */
	protected function getAllTables(): array
	{
		return [];
	}

	protected function _run()
	{
		Factory::getLog()->info("Reminder: database definitions using the 'None' driver result in no data being backed up.");

		$this->setState(self::STATE_POSTRUN);
	}

	protected function _finalize()
	{
		Factory::getLog()->info("Reminder: database definitions using the 'None' driver result in no data being backed up.");

		$this->setState(self::STATE_FINISHED);
	}
}
PK�!]@s�->>"BackupEngine/Dump/Native/Mysql.phpnu&1i�<?php
/**
 * Akeeba Engine
 *
 * @package   akeebaengine
 * @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License version 3, or later
 *
 * 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, version 3.
 *
 * 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
 * <https://www.gnu.org/licenses/>.
 */

namespace Akeeba\Engine\Dump\Native;

defined('AKEEBAENGINE') || die();

use Akeeba\Engine\Driver\QueryException;
use Akeeba\Engine\Dump\Base;
use Akeeba\Engine\Factory;
use Akeeba\Engine\Platform;
use Exception;
use RuntimeException;

/**
 * A generic MySQL database dump class.
 * Now supports views; merge, in-memory, federated, blackhole, etc tables
 * Configuration parameters:
 * host            <string>    MySQL database server host name or IP address
 * port            <string>    MySQL database server port (optional)
 * username        <string>    MySQL user name, for authentication
 * password        <string>    MySQL password, for authentication
 * database        <string>    MySQL database
 * dumpFile        <string>    Absolute path to dump file; must be writable (optional; if left blank it is
 * automatically calculated)
 */
#[\AllowDynamicProperties]
class Mysql extends Base
{
	/**
	 * The primary key structure of the currently backed up table. The keys contained are:
	 * - table        The name of the table being backed up
	 * - field        The name of the primary key field
	 * - value        The last value of the PK field
	 *
	 * @var array
	 */
	protected $table_autoincrement = [
		'table' => null,
		'field' => null,
		'value' => null,
	];

	private $columnListColumnType = [];

	private $columnListSelectColumn = '*';

	private $lastTableColumnType = null;

	private $lastTableSelectColumn = null;

	/**
	 * Implements the constructor of the class
	 *
	 * @return  void
	 */
	public function __construct()
	{
		parent::__construct();

		Factory::getLog()->debug(__CLASS__ . " :: New instance");
	}

	/**
	 * Replaces the table names in the CREATE query with their abstract form. Optionally updates dependencies.
	 *
	 * @param   string  $tableName        The table name the CREATE query is for
	 * @param   string  $tableSql         The CREATE query itself
	 * @param   bool    $withDependecies  Should I update dependencies?
	 *
	 * @return  array [$dependencies, $modifiedSQLQuery] - Dependency information for the table (if $withDependencies)
	 *                and the new CREATE query with all table names replaced with abstract versions.
	 *
	 * @throws  Exception  When we cannot get the DB object
	 */
	public function replaceTableNamesWithAbstracts($tableName, $tableSql, $withDependecies = false)
	{
		// Initialization
		$dependencies = [];
		$tableNameMap = $this->table_name_map;
		$db           = $this->getDB();

		if (!array_key_exists($tableName, $tableNameMap))
		{
			$tableNameMap[$tableName] = $this->getAbstract($tableName);
		}

		foreach ($tableNameMap as $fullName => $abstractName)
		{
			$quotedFullName     = $db->quoteName($fullName);
			$quotedAbstractName = $db->quoteName($abstractName);
			$pos                = strpos($tableSql, $quotedFullName);
			$numReplacements    = 0;

			if ($pos !== false)
			{
				$numReplacements = 1;

				// Do the replacement
				$tableSql = str_replace($quotedFullName, $quotedAbstractName, $tableSql);
			}
			elseif (!is_numeric($fullName))
			{
				$offset                   = 0;
				$fullNameLength           = strlen($fullName);
				$quotedAbstractNameLength = strlen($quotedAbstractName);

				/**
				 * We need to detect the edges of table names. If they are enclosed in backticks it's pretty clear. If they are
				 * not, e.g. in the definitions of TRIGGERs, we need to base our detection on the valid characters for
				 * unquoted MySQL table names per https://dev.mysql.com/doc/refman/5.7/en/identifiers.html
				 */
				[$bareCharRegex, $regexFlags] = $this->getMySQLIdentifierCharacterRegEx();
				$fullCharRegex = "/$bareCharRegex/$regexFlags";

				while (true)
				{
					$pos = strpos($tableSql, $fullName, $offset);

					if ($pos === false)
					{
						break;
					}

					// Skip over table-name-like strings in strings enclosed by single quotes
					$quotePos = strpos($tableSql, "'", $offset);

					if ($quotePos !== false && $quotePos < $pos)
					{
						// The table-like token is inside a string. Find its end.
						while (true)
						{
							$nextPos = strpos($tableSql, "'", $quotePos + 1);

							if ($nextPos === false)
							{
								break;
							}

							$prevChar = $nextPos > 0 ? $tableSql[$nextPos - 1] : null;
							$nextChar = strlen($tableSql) > $nextPos + 1 ? $tableSql[$nextPos + 1] : null;

							// Catch quote escaped as \'
							if ($prevChar === '\\')
							{
								$quotePos = $nextPos;

								continue;
							}

							// Catch quote escaped as ''
							if ($nextChar === "'")
							{
								$quotePos = $nextPos + 1;

								continue;
							}

							$offset = $nextPos + 1;

							continue 2;
						}
					}

					// Skip over table-name-like strings in strings enclosed by double quotes
					$quotePos = strpos($tableSql, '"', $offset);

					if ($quotePos !== false && $quotePos < $pos)
					{
						// The table-like token is inside a string. Find its end.
						while (true)
						{
							$nextPos = strpos($tableSql, '"', $quotePos + 1);

							if ($nextPos === false)
							{
								break;
							}

							$prevChar = $nextPos > 0 ? $tableSql[$nextPos - 1] : null;
							$nextChar = strlen($tableSql) > $nextPos + 1 ? $tableSql[$nextPos + 1] : null;

							// Catch quote escaped as \"
							if ($prevChar === '\\')
							{
								$quotePos = $nextPos;

								continue;
							}

							// Catch quote escaped as ""
							if ($nextChar === '"')
							{
								$quotePos = $nextPos + 1;

								continue;
							}

							$offset = $nextPos + 1;

							continue 2;
						}
					}

					// Catch table-name-like substring inside another table's name
					$previousChar    = ($pos > 0) ? substr($tableSql, $pos - 1, 1) : '';
					$nextChar        = ($pos < (strlen($tableSql) - $fullNameLength)) ? substr($tableSql, $pos + $fullNameLength, 1) : '';
					$prevIsTableChar = $previousChar === '' ? false : preg_match($fullCharRegex, $previousChar);
					$nextIsTableChar = $nextChar === '' ? false : preg_match($fullCharRegex, $nextChar);

					if ($prevIsTableChar || $nextIsTableChar)
					{
						$offset = $pos + 1;

						continue;
					}

					$before = ($pos > 0) ? substr($tableSql, 0, $pos) : '';
					$after  = ($pos < (strlen($tableSql) - $fullNameLength)) ? substr($tableSql, $pos + $fullNameLength) : '';

					$numReplacements++;
					$tableSql = $before . $quotedAbstractName . $after;

					$offset = $pos + $quotedAbstractNameLength;
				}
			}

			if ($withDependecies && $numReplacements && ($fullName != $tableName))
			{
				// Add a reference hit
				$this->dependencies[$fullName][] = $tableName;
				// Add the dependency to this table's metadata
				$dependencies[] = $fullName;
			}
		}

		return [$dependencies, $tableSql];
	}

	/**
	 * Creates a drop query from a CREATE query
	 *
	 * @param   string  $query  The CREATE query to process
	 *
	 * @return  string  The DROP statement
	 */
	protected function createDrop($query)
	{
		$db = $this->getDB();

		// Initialize
		$dropQuery = '';

		// Parse CREATE TABLE commands
		if (substr($query, 0, 12) == 'CREATE TABLE')
		{
			// Try to get the table name
			$restOfQuery = trim(substr($query, 12, strlen($query) - 12)); // Rest of query, after CREATE TABLE

			// Is there a backtick?
			if (substr($restOfQuery, 0, 1) == '`')
			{
				// There is... Good, we'll just find the matching backtick
				$pos       = strpos($restOfQuery, '`', 1);
				$tableName = substr($restOfQuery, 1, $pos - 1);
			}
			else
			{
				// Nope, let's assume the table name ends in the next blank character
				$pos       = strpos($restOfQuery, ' ', 1);
				$tableName = substr($restOfQuery, 0, $pos);
			}

			unset($restOfQuery);

			// Try to drop the table anyway
			$dropQuery = 'DROP TABLE IF EXISTS ' . $db->nameQuote($tableName) . ';';
		}
		// Parse CREATE VIEW commands
		elseif ((substr($query, 0, 7) == 'CREATE ') && (strpos($query, ' VIEW ') !== false))
		{
			// Try to get the view name
			$view_pos    = strpos($query, ' VIEW ');
			$restOfQuery = trim(substr($query, $view_pos + 6)); // Rest of query, after VIEW string

			// Is there a backtick?
			if (substr($restOfQuery, 0, 1) == '`')
			{
				// There is... Good, we'll just find the matching backtick
				$pos       = strpos($restOfQuery, '`', 1);
				$tableName = substr($restOfQuery, 1, $pos - 1);
			}
			else
			{
				// Nope, let's assume the table name ends in the next blank character
				$pos       = strpos($restOfQuery, ' ', 1);
				$tableName = substr($restOfQuery, 0, $pos);
			}

			unset($restOfQuery);

			$dropQuery = 'DROP VIEW IF EXISTS ' . $db->nameQuote($tableName) . ';';
		}
		// CREATE PROCEDURE pre-processing
		elseif ((substr($query, 0, 7) == 'CREATE ') && (strpos($query, 'PROCEDURE ') !== false))
		{
			// Try to get the procedure name
			$entity_keyword = ' PROCEDURE ';
			$entity_pos     = strpos($query, $entity_keyword);
			$restOfQuery    = trim(substr($query, $entity_pos + strlen($entity_keyword))); // Rest of query, after entity key string

			// Is there a backtick?
			if (substr($restOfQuery, 0, 1) == '`')
			{
				// There is... Good, we'll just find the matching backtick
				$pos         = strpos($restOfQuery, '`', 1);
				$entity_name = substr($restOfQuery, 1, $pos - 1);
			}
			else
			{
				// Nope, let's assume the entity name ends in the next blank character
				$pos         = strpos($restOfQuery, ' ', 1);
				$entity_name = substr($restOfQuery, 0, $pos);
			}

			unset($restOfQuery);

			$dropQuery = 'DROP' . $entity_keyword . 'IF EXISTS `' . $entity_name . '`;';
		}
		// CREATE FUNCTION pre-processing
		elseif ((substr($query, 0, 7) == 'CREATE ') && (strpos($query, 'FUNCTION ') !== false))
		{
			// Try to get the procedure name
			$entity_keyword = ' FUNCTION ';
			$entity_pos     = strpos($query, $entity_keyword);
			$restOfQuery    = trim(substr($query, $entity_pos + strlen($entity_keyword))); // Rest of query, after entity key string

			// Is there a backtick?
			if (substr($restOfQuery, 0, 1) == '`')
			{
				// There is... Good, we'll just find the matching backtick
				$pos         = strpos($restOfQuery, '`', 1);
				$entity_name = substr($restOfQuery, 1, $pos - 1);
			}
			else
			{
				// Nope, let's assume the entity name ends in the next blank character
				$pos         = strpos($restOfQuery, ' ', 1);
				$entity_name = substr($restOfQuery, 0, $pos);
			}

			unset($restOfQuery);

			// Try to drop the entity anyway
			$dropQuery = 'DROP' . $entity_keyword . 'IF EXISTS `' . $entity_name . '`;';
		}
		// CREATE TRIGGER pre-processing
		elseif ((substr($query, 0, 7) == 'CREATE ') && (strpos($query, 'TRIGGER ') !== false))
		{
			// Try to get the procedure name
			$entity_keyword = ' TRIGGER ';
			$entity_pos     = strpos($query, $entity_keyword);
			$restOfQuery    = trim(substr($query, $entity_pos + strlen($entity_keyword))); // Rest of query, after entity key string

			// Is there a backtick?
			if (substr($restOfQuery, 0, 1) == '`')
			{
				// There is... Good, we'll just find the matching backtick
				$pos         = strpos($restOfQuery, '`', 1);
				$entity_name = substr($restOfQuery, 1, $pos - 1);
			}
			else
			{
				// Nope, let's assume the entity name ends in the next blank character
				$pos         = strpos($restOfQuery, ' ', 1);
				$entity_name = substr($restOfQuery, 0, $pos);
			}

			unset($restOfQuery);

			// Try to drop the entity anyway
			$dropQuery = 'DROP' . $entity_keyword . 'IF EXISTS `' . $entity_name . '`;';
		}

		return $dropQuery;
	}

	/**
	 * Applies the SQL compatibility setting
	 *
	 * @return  void
	 */
	protected function enforceSQLCompatibility()
	{
		$db = $this->getDB();

		// Try to enforce SQL_BIG_SELECTS option
		try
		{
			$db->setQuery('SET sql_big_selects=1');
			$db->query();
		}
		catch (Exception $e)
		{
			// Do nothing; some versions of MySQL don't allow you to use the BIG_SELECTS option.
		}

		$db->resetErrors();
	}

	/**
	 * Return a list of columns and their data types.
	 *
	 * @param   string  $tableAbstract
	 *
	 * @return  array  An array of table columns and their data types.
	 */
	protected function getColumnTypes($tableAbstract)
	{
		if ($this->lastTableColumnType == $tableAbstract)
		{
			return $this->columnListColumnType;
		}

		$this->lastTableColumnType = $tableAbstract;

		try
		{
			$db = $this->getDB();

			$db->setQuery('SHOW COLUMNS FROM ' . $db->qn($tableAbstract));

			$tableCols = $db->loadAssocList();
		}
		catch (Exception $e)
		{
			return $this->columnListColumnType;
		}

		foreach ($tableCols as $col)
		{
			$typeParts                                 = explode('(', $col['Type'], 2);
			$this->columnListColumnType[$col['Field']] = strtoupper($typeParts[0]);
		}

		return $this->columnListColumnType;
	}

// =============================================================================
// Dependency processing - the Twilight Zone starts here
// =============================================================================

	/**
	 * Return the current database name by querying the database connection object (e.g. SELECT DATABASE() in MySQL)
	 *
	 * @return  string
	 */
	protected function getDatabaseNameFromConnection(): string
	{
		$db = $this->getDB();

		try
		{
			$ret = $db->setQuery('SELECT DATABASE()')->loadResult();
		}
		catch (Exception $e)
		{
			return '';
		}

		return empty($ret) ? '' : $ret;
	}

	/**
	 * Get the default database dump batch size from the configuration
	 *
	 * @return  int
	 */
	protected function getDefaultBatchSize()
	{
		static $batchSize = null;

		if (is_null($batchSize))
		{
			$configuration = Factory::getConfiguration();
			$batchSize     = intval($configuration->get('engine.dump.common.batchsize', 1000));

			if ($batchSize <= 0)
			{
				$batchSize = 1000;
			}
		}

		return $batchSize;
	}

	/**
	 * Get a regular expression and its options for valid characters of an unquoted MySQL identifier.
	 *
	 * This is used wherever we need to detect an arbitrary, unquoted MySQL identifier per
	 * https://dev.mysql.com/doc/refman/5.7/en/identifiers.html
	 *
	 * Also what if Unicode support is not compiled in PCRE? In this case we will fall back to a much simpler regex
	 * which only supports the ASCII subset of the allowed characters. In this case your database dump will be wrong
	 * if you use table names with non-ASCII characters.
	 *
	 * Since the detection is horribly slow we cache its results in an internal static variable.
	 *
	 * @return  array  In the format [$regex, $flags]
	 * @since   7.0.0
	 */
	protected function getMySQLIdentifierCharacterRegEx()
	{
		static $validCharRegEx = null;
		static $unicodeFlag = null;

		if (is_null($validCharRegEx) || is_null($unicodeFlag))
		{
			$noUnicode      = @preg_match('/\p{L}/u', 'σ') !== 1;
			$unicodeFlag    = $noUnicode ? '' : 'u';
			$validCharRegEx = $noUnicode ? '[0-9a-zA-Z$_]' : '[0-9a-zA-Z$_]|[\x{0080}-\x{FFFF}]';
		}

		return [$validCharRegEx, $unicodeFlag];
	}

	/**
	 * Get the optimal row batch size for a given table based on the available memory
	 *
	 * @param   string  $tableAbstract     The abstract table name, e.g. #__foobar
	 * @param   int     $defaultBatchSize  The default row batch size in the application configuration
	 *
	 * @return  int
	 */
	protected function getOptimalBatchSize($tableAbstract, $defaultBatchSize)
	{
		$db = $this->getDB();

		try
		{
			$info = $db->setQuery('SHOW TABLE STATUS LIKE ' . $db->q($tableAbstract))->loadAssoc();
		}
		catch (Exception $e)
		{
			return $defaultBatchSize;
		}

		if (!isset($info['Avg_row_length']) || empty($info['Avg_row_length']))
		{
			return $defaultBatchSize;
		}

		// That's the average row size as reported by MySQL.
		$avgRow = str_replace([',', '.'], ['', ''], $info['Avg_row_length']);
		// The memory available for manipulating data is less than the free memory
		$memoryLimit = $this->getMemoryLimit();
		$memoryLimit = empty($memoryLimit) ? 33554432 : $memoryLimit;
		$usedMemory  = memory_get_usage();
		$memoryLeft  = 0.75 * ($memoryLimit - $usedMemory);
		// The 3.25 factor is empirical and leans on the safe side.
		$maxRows = (int) ($memoryLeft / (3.25 * $avgRow));

		return max(1, min($maxRows, $defaultBatchSize));
	}

	/**
	 * Gets the row count for table $tableAbstract. Also updates the $this->maxRange variable.
	 *
	 * @param   string  $tableAbstract  The abstract name of the table (works with canonical names too, though)
	 *
	 * @return  void
	 *
	 * @throws  QueryException
	 */
	protected function getRowCount($tableAbstract)
	{
		$db = $this->getDB();

		$sql = (method_exists($db, 'createQuery') ? $db->createQuery() : $db->getQuery(true))
			->select('COUNT(*)')
			->from($db->nameQuote($tableAbstract));

		$errno = 0;
		$error = '';

		try
		{
			$db->setQuery($sql);
			$this->maxRange = $db->loadResult();

			if (is_null($this->maxRange))
			{
				$errno = $db->getErrorNum();
				$error = $db->getErrorMsg(false);
			}
		}
		catch (Exception $e)
		{
			$this->maxRange = null;
			$errno          = $e->getCode();
			$error          = $e->getMessage();
		}

		if (is_null($this->maxRange))
		{
			Factory::getLog()->warning("Cannot get number of rows of $tableAbstract. MySQL error $errno: $error");

			return;
		}

		Factory::getLog()->debug("Rows on " . $tableAbstract . " : " . $this->maxRange);
	}

	/**
	 * Return a list of columns to use in the SELECT query for dumping table data.
	 *
	 * This is used to filter out all generated rows.
	 *
	 * @param   string  $tableAbstract
	 *
	 * @return  string|array  An array of table columns or the string literal '*' to quickly select all columns.
	 *
	 * @see  https://dev.mysql.com/doc/refman/5.7/en/create-table-generated-columns.html
	 */
	protected function getSelectColumns($tableAbstract)
	{
		if ($this->lastTableSelectColumn == $tableAbstract)
		{
			return $this->columnListSelectColumn;
		}

		$this->lastTableSelectColumn = $tableAbstract;

		try
		{
			$db = $this->getDB();

			$db->setQuery('SHOW COLUMNS FROM ' . $db->qn($tableAbstract));

			$tableCols = $db->loadAssocList();
		}
		catch (Exception $e)
		{
			return $this->columnListSelectColumn;
		}

		$totalColumns                 = is_array($tableCols) || $tableCols instanceof \Countable ? count($tableCols) : 0;
		$this->columnListSelectColumn = [];

		$hasInvisibleColumns = false;

		foreach ($tableCols as $col)
		{
			// Skip over generated columns
			$attribs = array_map('strtoupper', empty($col['Extra']) ? [] : explode(' ', $col['Extra']));

			if (in_array('GENERATED', $attribs))
			{
				continue;
			}

			if (in_array('INVISIBLE', $attribs))
			{
				$hasInvisibleColumns = true;
			}

			$this->columnListSelectColumn[] = $col['Field'];
		}

		if (!$hasInvisibleColumns && ($totalColumns == count($this->columnListSelectColumn)))
		{
			$this->columnListSelectColumn = '*';
		}

		return $this->columnListSelectColumn;
	}

	/**
	 * Scans the database for tables to be backed up and sorts them according to
	 * their dependencies on one another. Updates $this->dependencies.
	 *
	 * @return  void
	 */
	protected function getTablesToBackup(): void
	{
		// Makes the MySQL connection compatible with our class
		$this->enforceSQLCompatibility();

		$configuration = Factory::getConfiguration();
		$notracking    = $configuration->get('engine.dump.native.nodependencies', 0);

		// First, get a map of table names <--> abstract names
		$this->get_tables_mapping();

		if ($notracking)
		{
			// Do not process table & view dependencies
			$this->get_tables_data_without_dependencies();
		}
		// Process table & view dependencies (default)
		else
		{
			// Find the type and CREATE command of each table/view in the database
			$this->get_tables_data();

			// Process dependencies and rearrange tables respecting them
			$this->process_dependencies();

			// Remove dependencies array
			$this->dependencies = [];
		}
	}

	/**
	 * Gets the CREATE TABLE command for a given table/view/procedure/function/trigger
	 *
	 * @param   string  $table_abstract  The abstracted name of the entity
	 * @param   string  $table_name      The name of the table
	 * @param   string  $type            The type of the entity to scan. If it's found to differ, the correct type is
	 *                                   returned.
	 * @param   array   $dependencies    The dependencies of this table
	 *
	 * @return  string|null  The CREATE command
	 */
	protected function get_create($table_abstract, $table_name, &$type, &$dependencies)
	{
		$configuration = Factory::getConfiguration();
		$notracking    = $configuration->get('engine.dump.native.nodependencies', 0);

		$db = $this->getDB();

		switch ($type)
		{
			case 'table':
			case 'merge':
			case 'view':
			default:
				$sql = "SHOW CREATE TABLE `$table_abstract`";
				break;

			case 'procedure':
				$sql = "SHOW CREATE PROCEDURE `$table_abstract`";
				break;

			case 'function':
				$sql = "SHOW CREATE FUNCTION `$table_abstract`";
				break;

			case 'trigger':
				$sql = "SHOW CREATE TRIGGER `$table_abstract`";
				break;
		}

		$db->setQuery($sql);

		try
		{
			$temp = $db->loadRowList();
		}
		catch (Exception $e)
		{
			// If the query failed we don't have the necessary SHOW privilege. Log the error and fake an empty reply.
			$entityType = ($type == 'merge') ? 'table' : $type;
			$msg        = $e->getMessage();
			Factory::getLog()->warning("Cannot get the structure of $entityType $table_abstract. Database returned error $msg running $sql  Please check your database privileges. Your database backup may be incomplete.");

			$db->resetErrors();

			$temp = [
				['', '', ''],
			];
		}

		if (in_array($type, ['procedure', 'function', 'trigger']))
		{
			$table_sql = $temp[0][2];

			if (empty($table_sql))
			{
				Factory::getLog()->warning("Cannot get the structure of $type $table_abstract. The database refused to return the CREATE command for this $type. Please check your database privileges. Your database backup may be incomplete.");

				return null;
			}

			// MySQL adds the database name into everything. We have to remove it.
			$dbName    = $db->qn($this->database) . '.`';
			$table_sql = str_replace($dbName, '`', $table_sql);

			// These can contain comment lines, starting with a double dash. Remove them.
			$table_sql = trim($table_sql);

			/**
			 * Remove the definer from the CREATE PROCEDURE/TRIGGER/FUNCTION. For example, MySQL returns this:
			 * CREATE DEFINER=`myuser`@`localhost` PROCEDURE `abc_myProcedure`() ...
			 * If you're restoring on a different machine the definer will probably be invalid, therefore we need to
			 * remove it from the (portable) output.
			 *
			 * Remember, $table_sql may be multiline. Therefore we need to process only the first line and append any
			 * further lines to the CREATE statement.
			 */
			$table_sql = trim($table_sql);
			$lines     = explode("\n", $table_sql);
			$firstLine = array_shift($lines);
			$pattern   = '/^CREATE(.*?) ' . strtoupper($type) . ' (.*)/i';
			$result    = preg_match($pattern, $firstLine, $matches);
			$table_sql = 'CREATE ' . strtoupper($type) . ' ' . $matches[2] . "\n" . implode("\n", $lines);
			$table_sql = trim($table_sql);
		}
		else
		{
			$table_sql = $temp[0][1];
		}
		unset($temp);

		// Smart table type detection
		if (in_array($type, ['table', 'merge', 'view']))
		{
			// Check for CREATE VIEW
			$pattern = '/^CREATE(.*?) VIEW (.*)/i';
			$result  = preg_match($pattern, $table_sql, $matches);

			if ($result === 1)
			{
				// This is a view.
				$type = 'view';

				/**
				 * Newer MySQL versions add the definer and other information in the CREATE VIEW output, e.g.
				 * CREATE ALGORITHM=UNDEFINED DEFINER=`muyser`@`localhost` SQL SECURITY DEFINER VIEW `abc_myview` AS ...
				 * We need to remove that to prevent restoration troubles.
				 */
				$table_sql = 'CREATE VIEW ' . $matches[2];
			}
			else
			{
				// This is a table.
				$type = 'table';

				// # Fix 3.2.1: USING BTREE / USING HASH in indices causes issues migrating from MySQL 5.1+ hosts to
				// MySQL 5.0 hosts
				if ($configuration->get('engine.dump.native.nobtree', 1))
				{
					$table_sql = str_replace(' USING BTREE', ' ', $table_sql);
					$table_sql = str_replace(' USING HASH', ' ', $table_sql);
				}

				// Translate TYPE= to ENGINE=
				$table_sql = str_replace('TYPE=', 'ENGINE=', $table_sql);

				/**
				 * Remove the TABLESPACE option.
				 *
				 * The format of the TABLESPACE table option is:
				 * TABLESPACE tablespace_name [STORAGE {DISK|MEMORY}]
				 * where tablespace_name can be a quoted or unquoted identifier.
				 */
				[$validCharRegEx, $unicodeFlag] = $this->getMySQLIdentifierCharacterRegEx();
				$tablespaceName = "((($validCharRegEx){1,})|(`.*`))";
				$suffix         = 'STORAGE\s{1,}(DISK|MEMORY)';
				$regex          = "#TABLESPACE\s{1,}$tablespaceName\s{0,}($suffix){0,1}#i" . $unicodeFlag;
				$table_sql      = preg_replace($regex, '', $table_sql);

				// Remove table options {DATA|INDEX} DIRECTORY
				$regex     = "#(DATA|INDEX)\s{1,}DIRECTORY\s*=?\s*'.*'#i";
				$table_sql = preg_replace($regex, '', $table_sql);

				// Remove table options ROW_FORMAT=whatever
				$regex     = "#ROW_FORMAT\s*=\s*[A-Z]{1,}#i";
				$table_sql = preg_replace($regex, '', $table_sql);

				// Remove MariaDB MyISAM option PAGE_CHECKSUM
				$regex     = "#PAGE_CHECKSUM\s*=\s*[\d]{1,}#i";
				$table_sql = preg_replace($regex, '', $table_sql);

				// Abstract the names of table constraints and indices
				$regex     = "#(CONSTRAINT|KEY|INDEX)\s{1,}`{$this->prefix}#i";
				$table_sql = preg_replace($regex, '$1 `#__', $table_sql);
			}

			// Is it a VIEW but we don't have SHOW VIEW privileges?
			if (empty($table_sql))
			{
				$type = 'view';
			}
		}

		/**
		 * Replace table name and names of referenced tables with their abstracted forms and populate dependency tables
		 * at the same time.
		 */
		// On DB only backup we don't want any replacing to take place, do we?
		if (!Factory::getEngineParamsProvider()->getScriptingParameter('db.abstractnames', 1))
		{
			$old_table_sql = $table_sql;
		}

		/**
		 * Replace the table names in the CREATE command with the abstract versions.
		 *
		 * Moreover, it updates the dependency tracking information.
		 *
		 * We have to quote the table name. If we don't we'll get wrong results. Imagine that you have a column whose
		 * name starts with the string literal of the table name itself.
		 *
		 * Example: table `poll`, column `poll_id` would become #__poll, #__poll_id
		 *
		 * By quoting before we make sure this won't happen.
		 */
		[$dependencies, $table_sql] = $this->replaceTableNamesWithAbstracts($table_name, $table_sql, !$notracking);

		// On DB only backup we don't want any replacing to take place, do we?
		if (!Factory::getEngineParamsProvider()->getScriptingParameter('db.abstractnames', 1))
		{
			$table_sql = $old_table_sql;
		}

		// Add final semicolon and newline character
		$table_sql .= ";\n";

		/**
		 * Views, procedures, functions and triggers may contain the database name followed by the table name, always
		 * quoted e.g. `db`.`table_name`  We need to replace all these instances with just the table name. The only
		 * reliable way to do that is to look for "`db`.`" and replace it with "`"
		 */
		if (in_array($type, ['view', 'procedure', 'function', 'trigger']))
		{
			$dbName      = $db->qn($this->getDatabaseName());
			$dummyQuote  = $db->qn('foo');
			$findWhat    = $dbName . '.' . substr($dummyQuote, 0, 1);
			$replaceWith = substr($dummyQuote, 0, 1);
			$table_sql   = str_replace($findWhat, $replaceWith, $table_sql);
		}

		// Post-process CREATE VIEW
		if ($type == 'view')
		{
			$pos_view = strpos($table_sql, ' VIEW ');

			if ($pos_view > 7)
			{
				// Only post process if there are view properties between the CREATE and VIEW keywords
				$propstring = substr($table_sql, 7, $pos_view - 7); // Properties string
				// Fetch the ALGORITHM={UNDEFINED | MERGE | TEMPTABLE} keyword
				$algostring = '';
				$algo_start = strpos($propstring, 'ALGORITHM=');

				if ($algo_start !== false)
				{
					$algo_end   = strpos($propstring, ' ', $algo_start);
					$algostring = substr($propstring, $algo_start, $algo_end - $algo_start + 1);
				}

				// Create our modified create statement
				$table_sql = 'CREATE OR REPLACE ' . $algostring . substr($table_sql, $pos_view);
			}
		}
		elseif ($type == 'procedure')
		{
			$pos_entity = stripos($table_sql, ' PROCEDURE ');

			if ($pos_entity !== false)
			{
				$table_sql = 'CREATE' . substr($table_sql, $pos_entity);
			}
		}
		elseif ($type == 'function')
		{
			$pos_entity = stripos($table_sql, ' FUNCTION ');

			if ($pos_entity !== false)
			{
				$table_sql = 'CREATE' . substr($table_sql, $pos_entity);
			}
		}
		elseif ($type == 'trigger')
		{
			$pos_entity = stripos($table_sql, ' TRIGGER ');

			if ($pos_entity !== false)
			{
				$table_sql = 'CREATE' . substr($table_sql, $pos_entity);
			}
		}

		return $table_sql;
	}

	/**
	 * Populates the _tables array with the metadata of each table and generates
	 * dependency information for views and merge tables. Updates $this->tables_data.
	 *
	 * @return  void
	 */
	protected function get_tables_data()
	{
		Factory::getLog()->debug(__CLASS__ . " :: Starting CREATE TABLE and dependency scanning");

		// Get a database connection
		$db = $this->getDB();

		Factory::getLog()->debug(__CLASS__ . " :: Got database connection");

		// Reset internal tables
		$this->tables_data  = [];
		$this->dependencies = [];

		// Get a list of tables where their engine type is shown
		$sql = 'SHOW TABLES';
		$db->setQuery($sql);
		$metadata_list = $db->loadRowList();

		Factory::getLog()->debug(__CLASS__ . " :: Got SHOW TABLES");

		// Get filters and filter root
		$registry = Factory::getConfiguration();
		$root     = $registry->get('volatile.database.root', '[SITEDB]');
		$filters  = Factory::getFilters();

		foreach ($metadata_list as $table_metadata)
		{
			// Skip over tables not included in the backup set
			if (!array_key_exists($table_metadata[0], $this->table_name_map))
			{
				continue;
			}

			// Basic information
			$table_name     = $table_metadata[0];
			$table_abstract = $this->table_name_map[$table_metadata[0]];
			$new_entry      = [
				'type'         => 'table',
				'dump_records' => true,
			];

			// Get the CREATE command
			$dependencies              = [];
			$new_entry['create']       = $this->get_create($table_abstract, $table_name, $new_entry['type'], $dependencies);

			if ($new_entry['create'] === null)
			{
				continue;
			}

			$new_entry['dependencies'] = $dependencies;

			if ($new_entry['type'] == 'view')
			{
				$new_entry['dump_records'] = false;
			}
			else
			{
				$new_entry['dump_records'] = true;
			}

			// Scan for the table engine.
			$engine = null; // So that we detect VIEWs correctly

			if ($new_entry['type'] == 'table')
			{
				$engine      = 'MyISAM'; // So that even with MySQL 4 hosts we don't screw this up
				$engine_keys = ['ENGINE=', 'TYPE='];

				foreach ($engine_keys as $engine_key)
				{
					$start_pos = strrpos($new_entry['create'], $engine_key);

					if ($start_pos !== false)
					{
						// Advance the start position just after the position of the ENGINE keyword
						$start_pos += strlen($engine_key);
						// Try to locate the space after the engine type
						$end_pos = stripos($new_entry['create'], ' ', $start_pos);

						if ($end_pos === false)
						{
							// Uh... maybe it ends with ENGINE=EngineType;
							$end_pos = stripos($new_entry['create'], ';', $start_pos);
						}

						if ($end_pos !== false)
						{
							// Grab the string
							$engine = substr($new_entry['create'], $start_pos, $end_pos - $start_pos);

							if (empty($engine))
							{
								Factory::getLog()->debug("*** DEBUG *** $table_name - engine $engine");
								Factory::getLog()->debug($new_entry['create']);
								Factory::getLog()->debug("start $start_pos - end $end_pos");
							}
						}
					}
				}

				$engine = strtoupper($engine);
			}

			switch ($engine)
			{
				/*
				// Views -- They are detected based on their CREATE statement
				case null:
					$new_entry['type'] = 'view';
					$new_entry['dump_records'] = false;
					break;
				*/

				// Merge tables
				case 'MRG_MYISAM':
					$new_entry['type']         = 'merge';
					$new_entry['dump_records'] = false;

					break;

				// Tables whose data we do not back up (memory, federated and can-have-no-data tables)
				case 'MEMORY':
				case 'EXAMPLE':
				case 'BLACKHOLE':
				case 'FEDERATED':
					$new_entry['dump_records'] = false;

					break;

				// Normal tables and VIEWs
				default:
					break;
			}

			// Table Data Filter - skip dumping table contents of filtered out tables
			if ($filters->isFiltered($table_abstract, $root, 'dbobject', 'content'))
			{
				$new_entry['dump_records'] = false;
			}

			$this->tables_data[$table_name] = $new_entry;
		}

		Factory::getLog()->debug(__CLASS__ . " :: Got table list");

		// If we have MySQL > 5.0 add stored procedures, stored functions and triggers
		$enable_entities = $registry->get('engine.dump.native.advanced_entitites', true);

		if ($enable_entities)
		{
			Factory::getLog()->debug(__CLASS__ . " :: Listing MySQL entities");
			// Get a list of procedures
			$sql = 'SHOW PROCEDURE STATUS WHERE `Db`=' . $db->quote($this->database);
			$db->setQuery($sql);

			try
			{
				$metadata_list = $db->loadRowList();
			}
			catch (Exception $e)
			{
				$metadata_list = null;
			}

			if (is_array($metadata_list))
			{
				if (count($metadata_list))
				{
					foreach ($metadata_list as $entity_metadata)
					{
						// Skip over entities not included in the backup set
						if (!array_key_exists($entity_metadata[1], $this->table_name_map))
						{
							continue;
						}

						// Basic information
						$entity_name     = $entity_metadata[1];
						$entity_abstract = $this->table_name_map[$entity_metadata[1]];
						$new_entry       = [
							'type'         => 'procedure',
							'dump_records' => false,
						];

						// There's no point trying to add a non-procedure entity
						if ($entity_metadata[2] != 'PROCEDURE')
						{
							continue;
						}

						$dependencies                    = [];
						$new_entry['create']             = $this->get_create($entity_abstract, $entity_name, $new_entry['type'], $dependencies);

						if ($new_entry['create'] === null)
						{
							continue;
						}

						$new_entry['dependencies']       = $dependencies;
						$this->tables_data[$entity_name] = $new_entry;
					}
				}
			} // foreach

			// Get a list of functions
			$sql = 'SHOW FUNCTION STATUS WHERE `Db`=' . $db->quote($this->database);
			$db->setQuery($sql);

			try
			{
				$metadata_list = $db->loadRowList();
			}
			catch (Exception $e)
			{
				$metadata_list = null;
			}

			if (is_array($metadata_list))
			{
				if (count($metadata_list))
				{
					foreach ($metadata_list as $entity_metadata)
					{
						// Skip over entities not included in the backup set
						if (!array_key_exists($entity_metadata[1], $this->table_name_map))
						{
							continue;
						}

						// Basic information
						$entity_name     = $entity_metadata[1];
						$entity_abstract = $this->table_name_map[$entity_metadata[1]];
						$new_entry       = [
							'type'         => 'function',
							'dump_records' => false,
						];

						// There's no point trying to add a non-function entity
						if ($entity_metadata[2] != 'FUNCTION')
						{
							continue;
						}

						$dependencies                    = [];
						$new_entry['create']             = $this->get_create($entity_abstract, $entity_name, $new_entry['type'], $dependencies);

						if ($new_entry['create'] === null)
						{
							continue;
						}

						$new_entry['dependencies']       = $dependencies;
						$this->tables_data[$entity_name] = $new_entry;
					}
				}
			} // foreach

			// Get a list of triggers
			$sql = 'SHOW TRIGGERS';
			$db->setQuery($sql);

			try
			{
				$metadata_list = $db->loadRowList();
			}
			catch (Exception $e)
			{
				$metadata_list = null;
			}

			if (is_array($metadata_list))
			{
				if (count($metadata_list))
				{
					foreach ($metadata_list as $entity_metadata)
					{
						// Skip over entities not included in the backup set
						if (!array_key_exists($entity_metadata[0], $this->table_name_map))
						{
							continue;
						}

						// Basic information
						$entity_name     = $entity_metadata[0];
						$entity_abstract = $this->table_name_map[$entity_metadata[0]];
						$new_entry       = [
							'type'         => 'trigger',
							'dump_records' => false,
						];

						$dependencies                    = [];
						$new_entry['create']             = $this->get_create($entity_abstract, $entity_name, $new_entry['type'], $dependencies);

						if ($new_entry['create'] === null)
						{
							continue;
						}

						$new_entry['dependencies']       = $dependencies;
						$this->tables_data[$entity_name] = $new_entry;
					}
				}
			} // foreach

			Factory::getLog()->debug(__CLASS__ . " :: Got MySQL entities list");
		}

		/**
		 * // Only store unique values
		 * if(count($dependencies) > 0)
		 * $dependencies = array_unique($dependencies);
		 * /**/
	}

	/**
	 * Populates the _tables array with the metadata of each table.
	 * Updates $this->tables_data and $this->tables.
	 *
	 * @return  void
	 */
	protected function get_tables_data_without_dependencies()
	{
		Factory::getLog()->debug(__CLASS__ . " :: Pushing table data (without dependency tracking)");

		// Reset internal tables
		$this->tables_data  = [];
		$this->dependencies = [];

		// Get filters and filter root
		$registry = Factory::getConfiguration();
		$root     = $registry->get('volatile.database.root', '[SITEDB]');
		$filters  = Factory::getFilters();

		foreach ($this->table_name_map as $table_name => $table_abstract)
		{
			$new_entry = [
				'type'         => 'table',
				'dump_records' => true,
			];

			// Table Data Filter - skip dumping table contents of filtered out tables
			if ($filters->isFiltered($table_abstract, $root, 'dbobject', 'content'))
			{
				$new_entry['dump_records'] = false;
			}

			$this->tables_data[$table_name] = $new_entry;
			$this->tables[]                 = $table_name;
		} // foreach

		Factory::getLog()->debug(__CLASS__ . " :: Got table list");
	}

	/**
	 * Generates a mapping between table names as they're stored in the database
	 * and their abstract representation. Updates $this->table_name_map
	 *
	 * @return  void
	 */
	protected function get_tables_mapping()
	{
		// Get a database connection
		Factory::getLog()->debug(__CLASS__ . " :: Finding tables to include in the backup set");
		$db = $this->getDB();

		// Reset internal tables
		$this->table_name_map = [];

		// Get the list of all database tables
		$sql = "SHOW TABLES";
		$db->setQuery($sql);
		$all_tables = $db->loadResultArray();

		$registry = Factory::getConfiguration();
		$root     = $registry->get('volatile.database.root', '[SITEDB]');

		// If we have filters, make sure the tables pass the filtering
		$filters = Factory::getFilters();

		foreach ($all_tables as $table_name)
		{
			if (substr($table_name, 0, 3) == '#__')
			{
				Factory::getLog()->warning(__CLASS__ . " :: Table $table_name has a prefix of #__. This would cause restoration errors; table skipped.");

				continue;
			}

			if ((strpos($table_name, "\r") !== false) || (strpos($table_name, "\n") !== false))
			{
				$table_name = str_replace(["\r", "\n"], ['\\r', '\\n'], $table_name);
				Factory::getLog()->warning(__CLASS__ . " :: [SECURITY] Table $table_name includes newline characters. Skipping table to protect you against possible MySQL vulnerability CVE-2017-3600 (“Bad Dump”).");

				continue;
			}

			$table_abstract = $this->getAbstract($table_name);

			if (substr($table_abstract, 0, 4) != 'bak_') // Skip backup tables
			{
				// Apply exclusion filters
				if (!$filters->isFiltered($table_abstract, $root, 'dbobject', 'all'))
				{
					Factory::getLog()->info(__CLASS__ . " :: Adding $table_name (internal name $table_abstract)");
					$this->table_name_map[$table_name] = $table_abstract;
				}
				else
				{
					Factory::getLog()->info(__CLASS__ . " :: Skipping $table_name (internal name $table_abstract)");
				}
			}
			else
			{
				Factory::getLog()->info(__CLASS__ . " :: Backup table $table_name automatically skipped.");
			}
		}

		// If we have MySQL > 5.0 add the list of stored procedures, stored functions
		// and triggers, but only if user has allows that and the target compatibility is
		// not MySQL 4! Also, if dependency tracking is disabled, we won't dump triggers,
		// functions and procedures.
		$enable_entities = $registry->get('engine.dump.native.advanced_entitites', true);
		$notracking      = $registry->get('engine.dump.native.nodependencies', 0);

		if (!$enable_entities)
		{
			Factory::getLog()->debug(__CLASS__ . " :: NOT listing stored PROCEDUREs, FUNCTIONs and TRIGGERs (you told me not to)");
		}
		elseif ($notracking != 0)
		{
			Factory::getLog()->debug(__CLASS__ . " :: NOT listing stored PROCEDUREs, FUNCTIONs and TRIGGERs (you have disabled dependency tracking, therefore I can't handle advanced entities)");
		}

		if ($enable_entities && ($notracking == 0))
		{
			// Cache the database name if this is the main site's database

			// 1. Stored procedures
			Factory::getLog()->debug(__CLASS__ . " :: Listing stored PROCEDUREs");
			$sql = "SHOW PROCEDURE STATUS WHERE `Db`=" . $db->quote($this->database);
			$db->setQuery($sql);

			try
			{
				$all_entries = $db->loadResultArray(1);
			}
			catch (Exception $e)
			{
				$all_entries = [];
			}

			// If we have filters, make sure the tables pass the filtering
			if (is_array($all_entries))
			{
				if (count($all_entries))
				{
					foreach ($all_entries as $entity_name)
					{
						if ((strpos($entity_name, "\r") !== false) || (strpos($entity_name, "\n") !== false))
						{
							$entity_name = str_replace(["\r", "\n"], ['\\r', '\\n'], $entity_name);
							Factory::getLog()->warning(__CLASS__ . " :: [SECURITY] Procedure $entity_name includes newline characters. Skipping table to protect you against possible MySQL vulnerability CVE-2017-3600 (“Bad Dump”).");

							continue;
						}

						$entity_abstract = $this->getAbstract($entity_name);

						if (!(substr($entity_abstract, 0, 4) == 'bak_')) // Skip backup entities
						{
							if (!$filters->isFiltered($entity_abstract, $root, 'dbobject', 'all'))
							{
								$this->table_name_map[$entity_name] = $entity_abstract;
							}
						}
					}
				}
			}

			// 2. Stored functions
			Factory::getLog()->debug(__CLASS__ . " :: Listing stored FUNCTIONs");
			$sql = "SHOW FUNCTION STATUS WHERE `Db`=" . $db->quote($this->database);
			$db->setQuery($sql);

			try
			{
				$all_entries = $db->loadResultArray(1);
			}
			catch (Exception $e)
			{
				$all_entries = [];
			}

			// If we have filters, make sure the tables pass the filtering
			if (is_array($all_entries))
			{
				if (count($all_entries))
				{
					foreach ($all_entries as $entity_name)
					{
						if ((strpos($entity_name, "\r") !== false) || (strpos($entity_name, "\n") !== false))
						{
							$entity_name = str_replace(["\r", "\n"], ['\\r', '\\n'], $entity_name);
							Factory::getLog()->warning(__CLASS__ . " :: [SECURITY] Function $entity_name includes newline characters. Skipping table to protect you against possible MySQL vulnerability CVE-2017-3600 (“Bad Dump”).");

							continue;
						}

						$entity_abstract = $this->getAbstract($entity_name);

						if (!(substr($entity_abstract, 0, 4) == 'bak_')) // Skip backup entities
						{
							// Apply exclusion filters if set
							if (!$filters->isFiltered($entity_abstract, $root, 'dbobject', 'all'))
							{
								$this->table_name_map[$entity_name] = $entity_abstract;
							}
						}
					}
				}
			}

			// 3. Triggers
			Factory::getLog()->debug(__CLASS__ . " :: Listing stored TRIGGERs");
			$sql = "SHOW TRIGGERS";
			$db->setQuery($sql);

			try
			{
				$all_entries = $db->loadResultArray();
			}
			catch (Exception $e)
			{
				$all_entries = [];
			}

			// If we have filters, make sure the tables pass the filtering
			if (is_array($all_entries))
			{
				if (count($all_entries))
				{
					foreach ($all_entries as $entity_name)
					{
						if ((strpos($entity_name, "\r") !== false) || (strpos($entity_name, "\n") !== false))
						{
							$entity_name = str_replace(["\r", "\n"], ['\\r', '\\n'], $entity_name);
							Factory::getLog()->warning(__CLASS__ . " :: [SECURITY] Trigger $entity_name includes newline characters. Skipping table to protect you against possible MySQL vulnerability CVE-2017-3600 (“Bad Dump”).");

							continue;
						}

						$entity_abstract = $this->getAbstract($entity_name);

						if (!(substr($entity_abstract, 0, 4) == 'bak_')) // Skip backup entities
						{
							// Apply exclusion filters if set
							if (!$filters->isFiltered($entity_abstract, $root, 'dbobject', 'all'))
							{
								$this->table_name_map[$entity_name] = $entity_abstract;
							}
						}
					}
				}
			}
		} // if MySQL 5

		/**
		 * Store all abstract entity names (tables, views, triggers etc etc ) into a volatile variable, so we can fetch
		 * it later when creating the databases.json file
		 */
		ksort($this->table_name_map);
		$registry->set('volatile.database.table_names', array_values($this->table_name_map));

		/**
		 * IMPORTANT -- DO NOT REMOVE
		 *
		 * We now need to reverse sort the table_name_map. This is of paramount importance in how the
		 * replaceTableNamesWithAbstracts method works. Consider the following case:
		 * foo_test_2 => #__test_2
		 * foo_test_20 => #__test_20
		 * If foo_test_2 comes before foo_test_2 (alpha sort) the CREATE command of foo_test_20 will end up as
		 * CREATE TABLE ``#__test_2`0` (...)
		 * instead of the correct
		 * CREATE TABLE `#__test_20` (...)
		 * That's because the first table replacement done there will be foo_test_2 => `#__test_2`. Ouch.
		 *
		 * By doing a reverse alpha sort on the keys we ENSURE that the longer table names which may be a superset of
		 * another table's name will always end up first on the list.
		 *
		 * In our example the first replacement made is foo_test_20 => `#__test_20`. When we reach the next possible
		 * replacement (foo_test_2) we no longer have the concrete table name foo_test_2 therefore we won't accidentally
		 * break the CREATE command.
		 *
		 * Of course the same replacement problem exists within VIEWs, TRIGGERs, PROCEDUREs and FUNCTIONs. Again, the
		 * reverse alpha sort by concrete table name solves this issue elegantly.
		 */
		krsort($this->table_name_map);
	}

	/**
	 * Process all table dependencies
	 *
	 * @return  void
	 */
	protected function process_dependencies()
	{
		if ((is_array($this->table_name_map) || $this->table_name_map instanceof \Countable ? count($this->table_name_map) : 0) > 0)
		{
			foreach ($this->table_name_map as $table_name => $table_abstract)
			{
				$this->push_table($table_name);
			}
		}

		Factory::getLog()->debug(__CLASS__ . " :: Processed dependencies");
	}

	/**
	 * Pushes a table in the _tables stack, making sure it will appear after
	 * its dependencies and other tables/views depending on it will eventually
	 * appear after it. It's a complicated chicken-and-egg problem. Just make
	 * sure you don't have any bloody circular references!!
	 *
	 * @param   string  $table_name  Canonical name of the table to push
	 * @param   array   $stack       When called recursive, other views/tables previously processed in order to detect
	 *                               *ahem* dependency loops...
	 *
	 * @return  void
	 */
	protected function push_table($table_name, $stack = [], $currentRecursionDepth = 0)
	{
		if (!isset($this->tables_data[$table_name]))
		{
			return;
		}

		// Load information
		$table_data = $this->tables_data[$table_name];

		if (array_key_exists('dependencies', $table_data))
		{
			$referenced = $table_data['dependencies'];
		}
		else
		{
			$referenced = [];
		}

		unset($table_data);

		// Try to find the minimum insert position, so as to appear after the last referenced table
		$insertpos = false;

		if (is_array($referenced) || $referenced instanceof \Countable ? count($referenced) : 0)
		{
			foreach ($referenced as $referenced_table)
			{
				if (is_array($this->tables) || $this->tables instanceof \Countable ? count($this->tables) : 0)
				{
					$newpos = array_search($referenced_table, $this->tables);

					if ($newpos !== false)
					{
						if ($insertpos === false)
						{
							$insertpos = $newpos;
						}
						else
						{
							$insertpos = max($insertpos, $newpos);
						}
					}
				}
			}
		}

		// Add to the _tables array
		if ((is_array($this->tables) || $this->tables instanceof \Countable ? count($this->tables) : 0) && ($insertpos !== false))
		{
			array_splice($this->tables, $insertpos + 1, 0, $table_name);
		}
		else
		{
			$this->tables[] = $table_name;
		}

		// Here's what... Some other table/view might depend on us, so we must appear
		// before it (actually, it must appear after us). So, we scan for such
		// tables/views and relocate them
		if (is_array($this->dependencies) || $this->dependencies instanceof \Countable ? count($this->dependencies) : 0)
		{
			if (array_key_exists($table_name, $this->dependencies))
			{
				foreach ($this->dependencies[$table_name] as $depended_table)
				{
					// First, make sure that either there is no stack, or the
					// depended table doesn't belong it. In any other case, we
					// were fooled to follow an endless dependency loop and we
					// will simply bail out and let the user sort things out.
					if (count($stack) > 0)
					{
						if (in_array($depended_table, $stack))
						{
							continue;
						}
					}

					$my_position     = array_search($table_name, $this->tables);
					$remove_position = array_search($depended_table, $this->tables);

					if (($remove_position !== false) && ($remove_position < $my_position))
					{
						$stack[] = $table_name;
						array_splice($this->tables, $remove_position, 1);

						// Where should I put the other table/view now? Don't tell me.
						// I have to recurse...
						if ($currentRecursionDepth < 19)
						{
							$this->push_table($depended_table, $stack, ++$currentRecursionDepth);
						}
						else
						{
							// We're hitting a circular dependency. We'll add the removed $depended_table
							// in the penultimate position of the table and cross our virtual fingers...
							array_splice($this->tables, (is_array($this->tables) || $this->tables instanceof \Countable ? count($this->tables) : 0) - 1, 0, $depended_table);
						}
					}
				}
			}
		}
	}

	/**
	 * Try to find an auto_increment field for the table being currently backed up and populate the
	 * $this->table_autoincrement table. Updates $this->table_autoincrement.
	 *
	 * @return  void
	 */
	protected function setAutoIncrementInfo()
	{
		$this->table_autoincrement = [
			'table' => $this->nextTable,
			'field' => null,
			'value' => null,
		];

		$db = $this->getDB();

		$query   = 'SHOW COLUMNS FROM ' . $db->qn($this->nextTable) . ' WHERE ' . $db->qn('Extra') . ' = ' .
			$db->q('auto_increment') . ' AND ' . $db->qn('Null') . ' = ' . $db->q('NO');
		$keyInfo = $db->setQuery($query)->loadAssocList();

		if (!empty($keyInfo))
		{
			$row                                = array_shift($keyInfo);
			$this->table_autoincrement['field'] = $row['Field'];
		}
	}

	/**
	 * Performs one more step of dumping database data
	 *
	 * @return  void
	 *
	 * @throws QueryException
	 * @throws Exception
	 */
	protected function stepDatabaseDump(): void
	{
		// Initialize local variables
		$db = $this->getDB();

		if (!is_object($db) || ($db === false))
		{
			throw new RuntimeException(__CLASS__ . '::_run() Could not connect to database?!');
		}

		$outData = ''; // Used for outputting INSERT INTO commands

		$this->enforceSQLCompatibility(); // Apply MySQL compatibility option

		// Touch SQL dump file
		$nada = "";
		$this->writeline($nada);

		// Get this table's information
		$tableName = $this->nextTable;
		$this->setStep($tableName);
		$this->setSubstep('');
		$tableAbstract = trim($this->table_name_map[$tableName]);
		$dump_records  = $this->tables_data[$tableName]['dump_records'];

		// Restore any previously information about the largest query we had to run
		$this->largest_query = Factory::getConfiguration()->get('volatile.database.largest_query', 0);

		// If it is the first run, find number of rows and get the CREATE TABLE command
		if ($this->nextRange == 0)
		{
			$outCreate = '';

			if (is_array($this->tables_data[$tableName]))
			{
				if (array_key_exists('create', $this->tables_data[$tableName]))
				{
					$outCreate = $this->tables_data[$tableName]['create'];
				}
			}

			if (empty($outCreate) && !empty($tableName))
			{
				// The CREATE command wasn't cached. Time to create it. The $type and $dependencies
				// variables will be thrown away.
				$type         = $this->tables_data[$tableName]['type'] ?? 'table';
				$dependencies = [];
				$outCreate    = $this->get_create($tableAbstract, $tableName, $type, $dependencies);
			}

			// Create drop statements if required (the key is defined by the scripting engine)
			if (!empty($outCreate) && Factory::getEngineParamsProvider()->getScriptingParameter('db.dropstatements', 0))
			{
				if (array_key_exists('create', $this->tables_data[$tableName]))
				{
					$dropStatement = $this->createDrop($this->tables_data[$tableName]['create']);
				}
				else
				{
					$type            = 'table';
					$createStatement = $this->get_create($tableAbstract, $tableName, $type, $dependencies);
					$dropStatement   = $this->createDrop($createStatement);
				}

				if (!empty($dropStatement))
				{
					$dropStatement .= "\n";

					if (!$this->writeDump($dropStatement, true))
					{
						return;
					}
				}
			}

			/**
			 * If we have a PROCEDURE, FUNCTION or TRIGGER and we are doing a SQL export meant to be run directly by
			 * MySQL (the scripting db.delimiterstatements flag is set to 1) we need to surround the CREATE statement
			 * with DELIMITER $$ commands.
			 */
			if (
				!empty($outCreate) &&
				(Factory::getEngineParamsProvider()->getScriptingParameter('db.delimiterstatements', 0) == 1)
				&& in_array($this->tables_data[$tableName]['type'], ['trigger', 'function', 'procedure'])
			)
			{
				$outCreate = rtrim($outCreate, ";\n");
				$outCreate = "DELIMITER $$\n$outCreate$$\nDELIMITER ;\n";
			}

			// Write the CREATE command after any DROP command which might be necessary.
			if (!empty($outCreate) && !$this->writeDump($outCreate, true))
			{
				return;
			}

			if (!empty($outCreate) && $dump_records)
			{
				// We are dumping data from a table, get the row count
				$this->getRowCount($tableAbstract);

				// If we can't get the row count we cannot back up this table's data
				if (is_null($this->maxRange))
				{
					$dump_records = false;
				}
			}
			elseif (!$dump_records)
			{
				/**
				 * Do NOT move this line to the if-block below. We need to only log this message on tables which are
				 * filtered, not on tables we simply cannot get the row count information for!
				 */
				Factory::getLog()->info("Skipping dumping data of " . $tableAbstract);
			}

			// The table is either filtered or we cannot get the row count. Either way we should not dump any data.
			if (!$dump_records || empty($outCreate))
			{
				$this->maxRange  = 0;
				$this->nextRange = 1;
				$outData         = '';
				$numRows         = 0;
				$dump_records    = false;
			}

			// Output any data preamble commands, e.g. SET IDENTITY_INSERT for SQL Server
			if ($dump_records && Factory::getEngineParamsProvider()->getScriptingParameter('db.dropstatements', 0))
			{
				Factory::getLog()->debug("Writing data dump preamble for " . $tableAbstract);
				$preamble = $this->getDataDumpPreamble($tableAbstract, $tableName, $this->maxRange);

				if (!empty($preamble))
				{
					if (!$this->writeDump($preamble, true))
					{
						return;
					}
				}
			}

			// Get the table's auto increment information
			if ($dump_records)
			{
				$this->setAutoIncrementInfo();
			}
		}

		// Load the active database root
		$configuration = Factory::getConfiguration();
		$dbRoot        = $configuration->get('volatile.database.root', '[SITEDB]');

		// Get the default and the current (optimal) batch size
		$defaultBatchSize = $this->getDefaultBatchSize();
		$batchSize        = $configuration->get('volatile.database.batchsize', $defaultBatchSize);

		// Check if we have more work to do on this table
		if (($this->nextRange < $this->maxRange))
		{
			$timer = Factory::getTimer();

			// Get the number of rows left to dump from the current table
			$columns         = $this->getSelectColumns($tableAbstract);
			$columnTypes     = $this->getColumnTypes($tableAbstract);
			$columnsForQuery = is_array($columns) ? array_map([$db, 'qn'], $columns) : $columns;
			$sql             = (method_exists($db, 'createQuery') ? $db->createQuery() : $db->getQuery(true))
				->select($columnsForQuery)
				->from($db->nameQuote($tableAbstract));

			if (!is_null($this->table_autoincrement['field']))
			{
				$sql->order($db->qn($this->table_autoincrement['field']) . ' ASC');
			}

			if ($this->nextRange == 0)
			{
				// Get the optimal batch size for this table and save it to the volatile data
				$batchSize = $this->getOptimalBatchSize($tableAbstract, $defaultBatchSize);
				$configuration->set('volatile.database.batchsize', $batchSize);

				// First run, get a cursor to all records
				$db->setQuery($sql, 0, $batchSize);
				Factory::getLog()->info("Beginning dump of " . $tableAbstract);
				Factory::getLog()->debug("Up to $batchSize records will be read at once.");
			}
			else
			{
				// Subsequent runs, get a cursor to the rest of the records
				$this->setSubstep($this->nextRange . ' / ' . $this->maxRange);

				// If we have an auto_increment value and the table has over $batchsize records use the indexed select instead of a plain limit
				if (!is_null($this->table_autoincrement['field']) && !is_null($this->table_autoincrement['value']))
				{
					Factory::getLog()
						->info("Continuing dump of " . $tableAbstract . " from record #{$this->nextRange} using auto_increment column {$this->table_autoincrement['field']} and value {$this->table_autoincrement['value']}");
					$sql->where($db->qn($this->table_autoincrement['field']) . ' > ' . $db->q($this->table_autoincrement['value']));
					$db->setQuery($sql, 0, $batchSize);
				}
				else
				{
					Factory::getLog()
						->info("Continuing dump of " . $tableAbstract . " from record #{$this->nextRange}");
					$db->setQuery($sql, $this->nextRange, $batchSize);
				}
			}

			$this->query  = '';
			$numRows      = 0;
			$use_abstract = Factory::getEngineParamsProvider()->getScriptingParameter('db.abstractnames', 1);

			$filters            = Factory::getFilters();
			$mustFilterRows     = $filters->hasFilterType('dbobject', 'children');
			$mustFilterContents = $filters->canFilterDatabaseRowContent();

			try
			{
				$cursor = $db->query();
			}
			catch (Exception $exc)
			{
				// Issue a warning about the failure to dump data
				$errno = $exc->getCode();
				$error = $exc->getMessage();
				Factory::getLog()->warning("Failed dumping $tableAbstract from record #{$this->nextRange}. MySQL error $errno: $error");

				// Reset the database driver's state (we will try to dump other tables anyway)
				$db->resetErrors();
				$cursor = null;

				// Mark this table as done since we are unable to dump it.
				$this->nextRange = $this->maxRange;
			}

			$statsTableAbstract = Platform::getInstance()->tableNameStats;

			while (is_array($myRow = $db->fetchAssoc()) && ($numRows < ($this->maxRange - $this->nextRange)))
			{
				if ($this->createNewPartIfRequired() == false)
				{
					/**
					 * When createNewPartIfRequired returns false it means that we have began adding a SQL part to the
					 * backup archive but it hasn't finished. If we don't return here, the code below will keep adding
					 * data to that dump file. Yes, despite being closed. When you call writeDump the file is reopened.
					 * As a result of writing data of length Y, the file that had a size X now has a size of X + Y. This
					 * means that the loop in BaseArchiver which tries to add it to the archive will never see its End
					 * Of File since we are trying to resume the backup from *beyond* the file position that was
					 * recorded as the file size. The archive can detect a file shrinking but not a file growing!
					 * Therefore we hit an infinite loop a.k.a. runaway backup.
					 */
					return;
				}

				$numRows++;
				$numOfFields = is_array($myRow) || $myRow instanceof \Countable ? count($myRow) : 0;

				// On MS SQL Server there's always a RowNumber pseudocolumn added at the end, screwing up the backup (GRRRR!)
				if ($db->getDriverType() == 'mssql')
				{
					$numOfFields--;
				}

				// If row-level filtering is enabled, please run the filtering
				if ($mustFilterRows)
				{
					$isFiltered = $filters->isFiltered(
						[
							'table' => $tableAbstract,
							'row'   => $myRow,
						],
						$dbRoot,
						'dbobject',
						'children'
					);

					if ($isFiltered)
					{
						// Update the auto_increment value to avoid edge cases when the batch size is one
						if (!is_null($this->table_autoincrement['field']) && isset($myRow[$this->table_autoincrement['field']]))
						{
							$this->table_autoincrement['value'] = $myRow[$this->table_autoincrement['field']];
						}

						continue;
					}
				}

				if ($mustFilterContents)
				{
					$filters->filterDatabaseRowContent($dbRoot, $tableAbstract, $myRow);
				}

				if (
					(!$this->extendedInserts) || // Add header on simple INSERTs, or...
					($this->extendedInserts && empty($this->query)) //...on extended INSERTs if there are no other data, yet
				)
				{
					$newQuery  = true;
					$fieldList = $this->getFieldListSQL($columns);

					if ($numOfFields > 0)
					{
						$this->query = "INSERT INTO " . $db->nameQuote((!$use_abstract ? $tableName : $tableAbstract)) . " {$fieldList} VALUES \n";
					}
				}
				else
				{
					// On other cases, just mark that we should add a comma and start a new VALUES entry
					$newQuery = false;
				}

				$outData = '(';

				// Step through each of the row's values
				$fieldID = 0;

				// Used in running backup fix
				$isCurrentBackupEntry = false;

				// Fix 1.2a - NULL values were being skipped
				if ($numOfFields > 0)
				{
					foreach ($myRow as $fieldName => $value)
					{
						// The ID of the field, used to determine placement of commas
						$fieldID++;

						if ($fieldID > $numOfFields)
						{
							// This is required for SQL Server backups, do NOT remove!
							continue;
						}

						// Fix 2.0: Mark currently running backup as successful in the DB snapshot
						if ($tableAbstract == $statsTableAbstract)
						{
							if ($fieldID == 1)
							{
								// Compare the ID to the currently running
								$statistics           = Factory::getStatistics();
								$isCurrentBackupEntry = ($value == $statistics->getId());
							}
							elseif ($fieldID == 6)
							{
								// Treat the status field
								$value = $isCurrentBackupEntry ? 'complete' : $value;
							}
						}

						// Post-process the value
						if (is_null($value))
						{
							$outData .= "NULL"; // Cope with null values
						}
						else
						{
							// Accommodate for runtime magic quotes
							if (function_exists('get_magic_quotes_runtime'))
							{
								$value = @get_magic_quotes_runtime() ? stripslashes($value) : $value;
							}

							switch ($columnTypes[$fieldName] ?? '')
							{
								// Hex encode spatial data
								case 'GEOMETRY':
								case 'POINT':
								case 'LINESTRING':
								case 'POLYGON':
								case 'MULTIPOINT':
								case 'MULTILINESTRING':
								case 'MULTIPOLYGON':
								case 'GEOMETRYCOLLECTION':
									$hexEncoded = bin2hex($value);
									$value      = "x'$hexEncoded'";
									break;

								default:
									$value = $db->quote($value);
									break;
							}

							if ($this->postProcessValues)
							{
								$value = $this->postProcessQuotedValue($value);
							}

							$outData .= $value;
						}

						if ($fieldID < $numOfFields)
						{
							$outData .= ', ';
						}
					}
				}

				$outData .= ')';

				if ($numOfFields)
				{
					// If it's an existing query and we have extended inserts
					if ($this->extendedInserts && !$newQuery)
					{
						// Check the existing query size
						$query_length = strlen($this->query);
						$data_length  = strlen($outData);

						if (($query_length + $data_length) > $this->packetSize)
						{
							// We are about to exceed the packet size. Write the data so far.
							$this->query .= ";\n";

							if (!$this->writeDump($this->query, true))
							{
								return;
							}

							// Then, start a new query
							$fieldList = $this->getFieldListSQL($columns);

							$this->query = '';
							$this->query = "INSERT INTO " . $db->nameQuote((!$use_abstract ? $tableName : $tableAbstract)) . " {$fieldList} VALUES \n";
							$this->query .= $outData;
						}
						else
						{
							// We have room for more data. Append $outData to the query.
							$this->query .= ",\n";
							$this->query .= $outData;
						}
					}
					// If it's a brand new insert statement in an extended INSERTs set
					elseif ($this->extendedInserts && $newQuery)
					{
						// Append the data to the INSERT statement
						$this->query .= $outData;
						// Let's see the size of the dumped data...
						$query_length = strlen($this->query);

						if ($query_length >= $this->packetSize)
						{
							// This was a BIG query. Write the data to disk.
							$this->query .= ";\n";

							if (!$this->writeDump($this->query, true))
							{
								return;
							}

							// Then, start a new query
							$this->query = '';
						}
					}
					// It's a normal (not extended) INSERT statement
					else
					{
						// Append the data to the INSERT statement
						$this->query .= $outData;
						// Write the data to disk.
						$this->query .= ";\n";

						if (!$this->writeDump($this->query, true))
						{
							return;
						}

						// Then, start a new query
						$this->query = '';
					}
				}

				$outData = '';

				// Update the auto_increment value to avoid edge cases when the batch size is one
				if (!is_null($this->table_autoincrement['field']))
				{
					$this->table_autoincrement['value'] = $myRow[$this->table_autoincrement['field']];
				}

				unset($myRow);

				// Check for imminent timeout
				if ($timer->getTimeLeft() <= 0)
				{
					Factory::getLog()
						->debug("Breaking dump of $tableAbstract after $numRows rows; will continue on next step");

					break;
				}
			}

			$db->freeResult($cursor);

			// Advance the _nextRange pointer
			$this->nextRange += ($numRows != 0) ? $numRows : 1;

			$this->setStep($tableName);
			$this->setSubstep($this->nextRange . ' / ' . $this->maxRange);
		}

		// Finalize any pending query
		// WARNING! If we do not do that now, the query will be emptied in the next operation and all
		// accumulated data will go away...
		if (!empty($this->query))
		{
			$this->query .= ";\n";

			if (!$this->writeDump($this->query, true))
			{
				return;
			}

			$this->query = '';
		}

		// Check for end of table dump (so that it happens inside the same operation)
		if ($this->nextRange >= $this->maxRange)
		{
			// Tell the user we are done with the table
			Factory::getLog()->debug("Done dumping " . $tableAbstract);

			// Output any data preamble commands, e.g. SET IDENTITY_INSERT for SQL Server
			if ($dump_records && Factory::getEngineParamsProvider()->getScriptingParameter('db.dropstatements', 0))
			{
				Factory::getLog()->debug("Writing data dump epilogue for " . $tableAbstract);
				$epilogue = $this->getDataDumpEpilogue($tableAbstract, $tableName, $this->maxRange);

				if (!empty($epilogue))
				{
					if (!$this->writeDump($epilogue, true))
					{
						return;
					}
				}
			}

			if ((is_array($this->tables) || $this->tables instanceof \Countable ? count($this->tables) : 0) == 0)
			{
				// We have finished dumping the database!
				Factory::getLog()->info("End of database detected; flushing the dump buffers...");
				$this->writeDump(null);
				Factory::getLog()->info("Database has been successfully dumped to SQL file(s)");
				$this->setState(self::STATE_POSTRUN);
				$this->setStep('');
				$this->setSubstep('');
				$this->nextTable = '';
				$this->nextRange = 0;

				/**
				 * At the end of the database dump, if any query was longer than 1Mb, let's put a warning file in the
				 * installation folder, but ONLY if the backup is not a SQL-only backup (which has no backup archive).
				 */
				$isSQLOnly = $configuration->get('akeeba.basic.backup_type') == 'dbonly';

				if (!$isSQLOnly && ($this->largest_query >= 1024 * 1024))
				{
					$archive = Factory::getArchiverEngine();
					$archive->addFileVirtual('large_tables_detected', $this->installerSettings->installerroot, $this->largest_query);
				}
			}
			elseif ((is_array($this->tables) || $this->tables instanceof \Countable ? count($this->tables) : 0) != 0)
			{
				// Switch tables
				$this->nextTable = array_shift($this->tables);
				$this->nextRange = 0;
				$this->setStep($this->nextTable);
				$this->setSubstep('');
			}
		}
	}

	/** @inheritDoc */
	protected function getAllTables(): array
	{
		// Get a database connection
		$db = $this->getDB();

		$this->enforceSQLCompatibility();

		$sql = 'SHOW TABLES';
		$db->setQuery($sql);

		return $db->loadColumn() ?: [];
	}
}
PK�!]Z2R��8�8BackupEngine/Configuration.phpnu&1i�<?php
/**
 * Akeeba Engine
 *
 * @package   akeebaengine
 * @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License version 3, or later
 *
 * 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, version 3.
 *
 * 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
 * <https://www.gnu.org/licenses/>.
 */

namespace Akeeba\Engine;

defined('AKEEBAENGINE') || die();

use DirectoryIterator;
use stdClass;

/**
 * The Akeeba Engine configuration registry class
 */
class Configuration
{
	/**
	 * The currently loaded profile
	 *
	 * @var   integer
	 */
	public $activeProfile = null;

	/**
	 * Default namespace
	 *
	 * @var   string
	 */
	private $defaultNameSpace = 'global';

	/**
	 * Array keys which may contain stock directory definitions
	 *
	 * @var   array
	 */
	private $directory_containing_keys = [
		'akeeba.basic.output_directory',
	];

	/**
	 * Keys whose default values should never be overridden
	 *
	 * @var   array
	 */
	private $protected_nodes = [];

	/** The registry data
	 *
	 * @var   array
	 */
	private $registry = [];

	/**
	 * Constructor
	 *
	 * @return  void
	 */
	public function __construct()
	{
		// Create the default namespace
		$this->makeNameSpace($this->defaultNameSpace);

		// Create a default configuration
		$this->reset();
	}

	/**
	 * Create a namespace
	 *
	 * @param   string  $namespace  Name of the namespace to create
	 *
	 * @return  void
	 */
	public function makeNameSpace($namespace)
	{
		$this->registry[$namespace] = ['data' => new stdClass()];
	}

	/**
	 * Get the list of namespaces
	 *
	 * @return  array  List of namespaces
	 */
	public function getNameSpaces()
	{
		return array_keys($this->registry);
	}

	/**
	 * Get a registry value
	 *
	 * @param   string   $regpath               Registry path (e.g. global.directory.temporary)
	 * @param   mixed    $default               Optional default value
	 * @param   boolean  $process_special_vars  Optional. If true (default), it processes special variables, e.g.
	 *                                          [SITEROOT] in folder names
	 *
	 * @return  mixed  Value of entry or null
	 */
	public function get($regpath, $default = null, $process_special_vars = true)
	{
		// Cache the platform-specific stock directories
		static $stock_directories = [];

		if (empty($stock_directories))
		{
			$stock_directories = Platform::getInstance()->get_stock_directories();
		}

		$result = $default;

		// Explode the registry path into an array
		if ($nodes = explode('.', $regpath))
		{
			// Get the namespace
			$count = count($nodes);

			if ($count < 2)
			{
				$namespace = $this->defaultNameSpace;
				$nodes[1]  = $nodes[0];
			}
			else
			{
				$namespace = $nodes[0];
			}

			if (isset($this->registry[$namespace]))
			{
				$ns        = $this->registry[$namespace]['data'];
				$pathNodes = $count - 1;

				for ($i = 1; $i < $pathNodes; $i++)
				{
					if ((isset($ns->{$nodes[$i]})))
					{
						$ns = $ns->{$nodes[$i]};
					}
				}

				if (isset($ns->{$nodes[$i]}))
				{
					$result = $ns->{$nodes[$i]};
				}
			}
		}

		// Post-process certain directory-containing variables
		if ($process_special_vars && in_array($regpath, $this->directory_containing_keys))
		{
			if (!empty($stock_directories))
			{
				foreach ($stock_directories as $tag => $content)
				{
					$result = str_replace($tag, $content, $result);
				}
			}
		}

		return $result;
	}

	/**
	 * Set a registry value
	 *
	 * @param   string  $regpath               Registry Path (e.g. global.directory.temporary)
	 * @param   mixed   $value                 Value of entry
	 * @param   bool    $process_special_vars  Optional. If true (default), it processes special variables, e.g.
	 *                                         [SITEROOT] in folder names
	 *
	 * @return  mixed  Value of old value or boolean false if operation failed
	 */
	public function set($regpath, $value, $process_special_vars = true)
	{
		// Cache the platform-specific stock directories
		static $stock_directories = [];

		if (empty($stock_directories))
		{
			$stock_directories = Platform::getInstance()->get_stock_directories();
		}

		if (in_array($regpath, $this->protected_nodes))
		{
			return $this->get($regpath);
		}

		// Explode the registry path into an array
		$nodes = explode('.', $regpath);

		// Get the namespace
		$count = count($nodes);

		if ($count < 2)
		{
			$namespace = $this->defaultNameSpace;
		}
		else
		{
			$namespace = array_shift($nodes);
			$count--;
		}

		if (!isset($this->registry[$namespace]))
		{
			$this->makeNameSpace($namespace);
		}

		$ns = $this->registry[$namespace]['data'];

		$pathNodes = $count - 1;

		if ($pathNodes < 0)
		{
			$pathNodes = 0;
		}

		for ($i = 0; $i < $pathNodes; $i++)
		{
			// If any node along the registry path does not exist, create it
			if (!isset($ns->{$nodes[$i]}))
			{
				$ns->{$nodes[$i]} = new stdClass();
			}
			$ns = $ns->{$nodes[$i]};
		}

		// Set the new values
		if (is_string($value))
		{
			if (substr($value, 0, 10) == '###json###')
			{
				$value = json_decode(substr($value, 10));
			}
		}

		// Post-process certain directory-containing variables
		if ($process_special_vars && in_array($regpath, $this->directory_containing_keys))
		{
			if (!empty($stock_directories))
			{
				$data = $value;

				foreach ($stock_directories as $tag => $content)
				{
					$data = str_replace($tag, $content, $data);
				}

				$ns->{$nodes[$i]} = $data;

				return $ns->{$nodes[$i]};
			}
		}

		// This is executed if any of the previous two if's is false
		if (empty($nodes[$i]))
		{
			return false;
		}

		$ns->{$nodes[$i]} = $value;

		return $ns->{$nodes[$i]};
	}

	/**
	 * Unset (remove) a registry value
	 *
	 * @param   string  $regpath  Registry Path (e.g. global.directory.temporary)
	 *
	 * @return  boolean  True if the node was removed
	 */
	public function remove($regpath)
	{
		// Explode the registry path into an array
		$nodes = explode('.', $regpath);

		// Get the namespace
		$count = count($nodes);

		if ($count < 2)
		{
			$namespace = $this->defaultNameSpace;
		}
		else
		{
			$namespace = array_shift($nodes);
			$count--;
		}

		if (!isset($this->registry[$namespace]))
		{
			$this->makeNameSpace($namespace);
		}

		$ns = $this->registry[$namespace]['data'];

		$pathNodes = $count - 1;

		if ($pathNodes < 0)
		{
			$pathNodes = 0;
		}

		for ($i = 0; $i < $pathNodes; $i++)
		{
			// If any node along the registry path does not exist, return false
			if (!isset($ns->{$nodes[$i]}))
			{
				return false;
			}

			$ns = $ns->{$nodes[$i]};
		}

		unset($ns->{$nodes[$i]});

		return true;
	}

	/**
	 * Resets the registry to the default values
	 */
	public function reset()
	{
		// Load the Akeeba Engine INI files
		$root_path = __DIR__;

		$paths = [
			$root_path . '/Core',
			$root_path . '/Archiver',
			$root_path . '/Dump',
			$root_path . '/Scan',
			$root_path . '/Writer',
			$root_path . '/Proc',
			$root_path . '/Platform/Filter/Stack',
			$root_path . '/Filter/Stack',
		];

		$platform_paths = Platform::getInstance()->getPlatformDirectories();

		foreach ($platform_paths as $p)
		{
			$paths[] = $p . '/Filter/Stack';
			$paths[] = $p . '/Config';
		}

		foreach ($paths as $root)
		{
			if (!(is_dir($root) || is_link($root)))
			{
				continue;
			}

			if (!is_readable($root))
			{
				continue;
			}

			$di = new DirectoryIterator($root);

			/** @var DirectoryIterator $file */
			foreach ($di as $file)
			{
				if (!$file->isFile())
				{
					continue;
				}

				if ($file->getExtension() == 'json')
				{
					$this->mergeEngineJSON($file->getRealPath());
				}
			}
		}
	}

	/**
	 * Merges an associative array of key/value pairs into the registry.
	 * If noOverride is set, only non set or null values will be applied.
	 *
	 * @param   array  $array                 An associative array. Its keys are registry paths.
	 * @param   bool   $noOverride            [optional] Do not override pre-set values.
	 * @param   bool   $process_special_vars  Optional. If true (default), it processes special variables, e.g.
	 *                                        [SITEROOT] in folder names
	 */
	public function mergeArray($array, $noOverride = false, $process_special_vars = true)
	{
		if (!$noOverride)
		{
			foreach ($array as $key => $value)
			{
				$this->set($key, $value, $process_special_vars);
			}
		}
		else
		{
			foreach ($array as $key => $value)
			{
				if (is_null($this->get($key, null)))
				{
					$this->set($key, $value, $process_special_vars);
				}
			}
		}
	}

	/**
	 * Merges a JSON file into the registry. Its top level keys are registry paths,
	 * child keys are appended to the section-defined paths and then set equal to the
	 * values. If noOverride is set, only non set or null values will be applied.
	 * Top level keys beginning with an underscore will be ignored.
	 *
	 * @param   string   $jsonPath    The full path to the INI file to load
	 * @param   boolean  $noOverride  [optional] Do not override pre-set values.
	 *
	 * @return  boolean  True on success
	 */
	public function mergeJSON($jsonPath, $noOverride = false)
	{
		if (!file_exists($jsonPath))
		{
			return false;
		}

		$rawData  = file_get_contents($jsonPath);
		$jsonData = empty($rawData) ? [] : json_decode($rawData, true);

		foreach ($jsonData as $rootkey => $rootvalue)
		{
			if (!is_array($rootvalue))
			{
				if (!$noOverride)
				{
					$this->set($rootkey, $rootvalue);
				}
				elseif (is_null($this->get($rootkey, null)))
				{
					$this->set($rootkey, $rootvalue);
				}
			}
			elseif (substr($rootkey, 0, 1) != '_')
			{
				foreach ($rootvalue as $key => $value)
				{
					if (!$noOverride)
					{
						$this->set($rootkey . '.' . $key, $rootvalue);
					}
					elseif (is_null($this->get($rootkey . '.' . $key, null)))
					{
						$this->set($rootkey . '.' . $key, $rootvalue);
					}
				}
			}
		}

		return true;
	}

	/**
	 * Merges an engine JSON file to the configuration. Each top level key defines a full
	 * registry path (section.subsection.key). It searches each top level key for the
	 * child key named "default" and merges its value to the configuration. The other keys
	 * are simply ignored.
	 *
	 * @param   string  $jsonPath    The absolute path to an JSON file
	 * @param   bool    $noOverride  [optional] If true, values from the JSON file will not override the configuration
	 *
	 * @return  boolean  True on success
	 */
	public function mergeEngineJSON($jsonPath, $noOverride = false)
	{
		if (!file_exists($jsonPath))
		{
			return false;
		}

		$rawData  = file_get_contents($jsonPath);
		$jsonData = empty($rawData) ? [] : json_decode($rawData, true);

		foreach ($jsonData ?? [] as $section => $nodes)
		{
			if (is_array($nodes))
			{
				if (substr($section, 0, 1) != '_')
				{
					// Is this a protected node?
					$protected = false;

					if (array_key_exists('protected', $nodes))
					{
						$protected = $nodes['protected'];
					}

					// If overrides are allowed, unprotect until we can set the value
					if (!$noOverride)
					{
						if (in_array($section, $this->protected_nodes))
						{
							$pnk = array_search($section, $this->protected_nodes);
							unset($this->protected_nodes[$pnk]);
						}
					}

					if (array_key_exists('remove', $nodes))
					{
						// Remove a node if it has "remove" set
						$this->remove($section);
					}
					elseif (isset($nodes['default']))
					{
						if (!$noOverride)
						{
							// Update the default value if No Override is set
							$this->set($section, $nodes['default']);
						}
						elseif (is_null($this->get($section, null)))
						{
							// Set the default value if it does not exist
							$this->set($section, $nodes['default']);
						}
					}

					// Finally, if it's a protected node, enable the protection
					if ($protected)
					{
						$this->protected_nodes[] = $section;
					}
					else
					{
						$idx = array_search($section, $this->protected_nodes);

						if ($idx !== false)
						{
							unset($this->protected_nodes[$idx]);
						}
					}
				}
			}
		}

		return true;
	}

	/**
	 * Exports the current registry snapshot as a JSON-encoded object. Each namespace is a property of the top-level
	 * JSON object.
	 *
	 * @return  string  The JSON-encoded representation of the registry
	 *
	 * @since   6.4.1
	 */
	public function exportAsJSON()
	{
		$forJSON    = [];
		$namespaces = $this->getNameSpaces();

		foreach ($namespaces as $namespace)
		{
			if ($namespace == 'volatile')
			{
				continue;
			}

			$forJSON[$namespace] = $this->registry[$namespace]['data'];
		}

		return json_encode($forJSON, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_FORCE_OBJECT | JSON_PRETTY_PRINT);

	}

	/**
	 * Sets the protection status for a specific configuration key
	 *
	 * @param   string|array  $node     The node to protect/unprotect
	 * @param   boolean       $protect  True to protect, false to unprotect
	 *
	 * @return  void
	 */
	public function setKeyProtection($node, $protect = false)
	{
		if (is_array($node))
		{
			foreach ($node as $k)
			{
				$this->setKeyProtection($k, $protect);
			}
		}
		elseif (is_string($node))
		{
			if (is_array($this->protected_nodes))
			{
				$protected = in_array($node, $this->protected_nodes);
			}
			else
			{
				$this->protected_nodes = [];
				$protected             = false;
			}

			if ($protect)
			{
				if (!$protected)
				{
					$this->protected_nodes[] = $node;
				}
			}
			else
			{
				if ($protected)
				{
					$pnk = array_search($node, $this->protected_nodes);
					unset($this->protected_nodes[$pnk]);
				}
			}
		}
	}

	/**
	 * Returns a list of protected keys
	 *
	 * @return  array
	 */
	public function getProtectedKeys()
	{
		return $this->protected_nodes;
	}

	/**
	 * Resets the protected keys
	 *
	 * @return  void
	 */
	public function resetProtectedKeys()
	{
		$this->protected_nodes = [];
	}

	/**
	 * Sets the protected keys
	 *
	 * @param   array  $keys  A list of keys to protect
	 *
	 * @return  void
	 */
	public function setProtectedKeys($keys)
	{
		$this->protected_nodes = $keys;
	}
}
PK�!]/×W		BackupEngine/Core/04.quota.jsonnu&1i�{
    "_group": {
        "description": "COM_AKEEBA_CONFIG_HEADER_QUOTA"
    },
    "akeeba.quota.maxage.enable": {
        "default": "0",
        "type": "none",
        "protected": "1"
    },
    "akeeba.quota.obsolete_quota": {
        "default": "50",
        "type": "integer",
        "min": "0",
        "max": "500",
        "shortcuts": "1|10|20|30|40|50",
        "scale": "1",
        "uom": "items",
        "title": "COM_AKEEBA_CONFIG_OBSOLETEQUOTA_ENABLE_TITLE",
        "description": "COM_AKEEBA_CONFIG_OBSOLETEQUOTA_ENABLE_DESCRIPTION"
    },
    "akeeba.quota.enable_size_quota": {
        "default": "0",
        "type": "bool",
        "title": "COM_AKEEBA_CONFIG_SIZEQUOTA_ENABLE_TITLE",
        "description": "COM_AKEEBA_CONFIG_SIZEQUOTA_ENABLE_DESCRIPTION"
    },
    "akeeba.quota.size_quota": {
        "default": "15728640",
        "type": "integer",
        "min": "1",
        "max": "1125899906842624",
        "shortcuts": "15728640|52428800|104857600|268435456|536870912|1073741824|2147483648|5368709120|10737418240|21474836480|1099511627776",
        "scale": "1048576",
        "uom": "MB",
        "title": "COM_AKEEBA_CONFIG_SIZEQUOTA_VALUE_TITLE",
        "description": "COM_AKEEBA_CONFIG_SIZEQUOTA_VALUE_DESCRIPTION",
        "showon": "akeeba.quota.size_quota:1"
    },
    "akeeba.quota.enable_count_quota": {
        "default": "1",
        "type": "bool",
        "title": "COM_AKEEBA_CONFIG_COUNTQUOTA_ENABLE_TITLE",
        "description": "COM_AKEEBA_CONFIG_COUNTQUOTA_ENABLE_DESCRIPTION"
    },
    "akeeba.quota.count_quota": {
        "default": "3",
        "type": "integer",
        "min": "1",
        "max": "200",
        "shortcuts": "1|5|10|50|100|200",
        "scale": "1",
        "uom": "",
        "title": "COM_AKEEBA_CONFIG_COUNTQUOTA_VALUE_TITLE",
        "description": "COM_AKEEBA_CONFIG_COUNTQUOTA_VALUE_DESCRIPTION",
        "showon": "akeeba.quota.enable_count_quota:1"
    },
    "akeeba.quota.remotely.maxage.enable": {
        "default": "0",
        "type": "none",
        "protected": "1"
    },
    "akeeba.quota.remotely.enable_count_quota": {
        "default": "0",
        "type": "none",
        "protected": "1"
    },
    "akeeba.quota.remotely.enable_size_quota": {
        "default": "0",
        "type": "none",
        "protected": "1"
    }
}PK�!]�y��

 BackupEngine/Core/05.tuning.jsonnu&1i�{
    "_group": {
        "description": "COM_AKEEBA_CONFIG_HEADER_TUNING"
    },
    "akeeba.tuning.min_exec_time": {
        "default": "2000",
        "type": "integer",
        "min": "0",
        "max": "20000",
        "shortcuts": "0|250|500|1000|2000|3000|4000|5000|7500|10000|15000|20000",
        "scale": "1000",
        "uom": "s",
        "title": "COM_AKEEBA_CONFIG_MINEXECTIME_TITLE",
        "description": "COM_AKEEBA_CONFIG_MINEXECTIME_DESCRIPTION"
    },
    "akeeba.tuning.max_exec_time": {
        "default": "14",
        "type": "integer",
        "min": "0",
        "max": "180",
        "shortcuts": "1|2|3|5|7|10|14|15|20|23|25|30|45|60|90|120|180",
        "scale": "1",
        "uom": "s",
        "title": "COM_AKEEBA_CONFIG_MAXEXECTIME_TITLE",
        "description": "COM_AKEEBA_CONFIG_MAXEXECTIME_DESCRIPTION"
    },
    "akeeba.tuning.run_time_bias": {
        "default": "75",
        "type": "integer",
        "min": "10",
        "max": "100",
        "shortcuts": "10|20|25|30|40|50|60|75|80|90|100",
        "scale": "1",
        "uom": "%",
        "title": "COM_AKEEBA_CONFIG_RUNTIMEBIAS_TITLE",
        "description": "COM_AKEEBA_CONFIG_RUNTIMEBIAS_DESCRIPTION"
    },
    "akeeba.advanced.autoresume": {
        "default": "1",
        "type": "bool",
        "title": "COM_AKEEBA_CONFIG_AUTORESUME_TITLE",
        "description": "COM_AKEEBA_CONFIG_AUTORESUME_DESCRIPTION"
    },
    "akeeba.advanced.autoresume_timeout": {
        "default": "10",
        "type": "integer",
        "min": "1",
        "max": "36000",
        "scale": "1",
        "uom": "s",
        "shortcuts": "3|5|10|15|20|30|45|60|90|120|300|600|900|1800|3600",
        "title": "COM_AKEEBA_CONFIG_AUTORESUME_TIMEOUT_TITLE",
        "description": "COM_AKEEBA_CONFIG_AUTORESUME_TIMEOUT_DESCRIPTION",
        "showon": "akeeba.advanced.autoresume:1"
    },
    "akeeba.advanced.autoresume_maxretries": {
        "default": "3",
        "type": "integer",
        "min": "1",
        "max": "1000",
        "scale": "1",
        "shortcuts": "1|3|5|7|10|15|20|30|50|100",
        "title": "COM_AKEEBA_CONFIG_AUTORESUME_MAXRETRIES_TITLE",
        "description": "COM_AKEEBA_CONFIG_AUTORESUME_MAXRETRIES_DESCRIPTION",
        "showon": "akeeba.advanced.autoresume:1"
    },
    "akeeba.tuning.nobreak.beforelargefile": {
        "default": "0",
        "type": "none",
        "protected": "1"
    },
    "akeeba.tuning.nobreak.afterlargefile": {
        "default": "0",
        "type": "none",
        "protected": "1"
    },
    "akeeba.tuning.nobreak.proactive": {
        "default": "0",
        "type": "none",
        "protected": "1"
    },
    "akeeba.tuning.nobreak.domains": {
        "default": "0",
        "type": "none",
        "protected": "1"
    },
    "akeeba.tuning.nobreak.finalization": {
        "default": "0",
        "type": "none",
        "protected": "1"
    },
    "akeeba.tuning.settimelimit": {
        "default": "1",
        "type": "bool",
        "title": "COM_AKEEBA_CONFIG_ADVANCED_SETTIMELIMIT_LABEL",
        "description": "COM_AKEEBA_CONFIG_ADVANCED_SETTIMELIMIT_DESC"
    },
    "akeeba.tuning.setmemlimit": {
        "default": "1",
        "type": "bool",
        "title": "COM_AKEEBA_CONFIG_ADVANCED_SETMEMLIMIT_LABEL",
        "description": "COM_AKEEBA_CONFIG_ADVANCED_SETMEMLIMIT_DESC"
    }
}PK�!]|M0�JJBackupEngine/Core/Database.phpnu&1i�<?php
/**
 * Akeeba Engine
 *
 * @package   akeebaengine
 * @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License version 3, or later
 *
 * 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, version 3.
 *
 * 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
 * <https://www.gnu.org/licenses/>.
 */

namespace Akeeba\Engine\Core;

defined('AKEEBAENGINE') || die();

use Akeeba\Engine\Driver\Base as DriverBase;
use Akeeba\Engine\Driver\Mysqli;
use Akeeba\Engine\Platform;
use Akeeba\Engine\Util\HashTrait;

/**
 * A utility class to return a database connection object
 */
class Database
{
	use HashTrait;

	private static $instances = [];

	/**
	 * Returns a database connection object. It caches the created objects for future use.
	 *
	 * @param   array  $options  The database driver connection options
	 *
	 * @return  DriverBase|object  A DriverBase object or something with magic methods that's compatible with it
	 */
	public static function &getDatabase(array $options)
	{
		// Get the options signature.
		$signature = self::md5(serialize($options));

		// If there's a cached object return it.
		if (!empty(self::$instances[$signature]))
		{
			return self::$instances[$signature];
		}

		// Get the driver name / class
		$driver = preg_replace('/[^A-Z0-9_\\\.-]/i', '', $options['driver'] ?? '');

		// If there is no driver specified ask the Platform to guess it.
		if (empty($driver))
		{
			$default_signature = self::md5(serialize(Platform::getInstance()->get_platform_database_options()));
			$driver            = Platform::getInstance()->get_default_database_driver($signature === $default_signature);
		}

		// Ensure we have the FQN of the driver class
		if ((substr($driver, 0, 7) != '\\Akeeba') && substr($driver, 0, 7) != 'Akeeba\\')
		{
			$driver = '\\Akeeba\\Engine\\Driver\\' . ucfirst($driver);
		}

		// Map the legacy MySQL driver to the newer MySQLi
		if (($driver == '\\Akeeba\\Engine\\Driver\\Mysql') && !function_exists('mysql_connect'))
		{
			$driver = Mysqli::class;
		}

		// Translate MySQL SSL options
		if (!isset($options['ssl']) || !is_array($options['ssl']))
		{
			$options['ssl'] = [
				'enable'             => (bool) ($options['dbencryption'] ?? false),
				'cipher'             => ($options['dbsslcipher'] ?? '') ?: '',
				'ca'                 => ($options['dbsslca'] ?? '') ?: '',
				'capath'             => ($options['dbsslcapath'] ?? '') ?: '',
				'key'                => ($options['dbsslkey'] ?? '') ?: '',
				'cert'               => ($options['dbsslcert'] ?? '') ?: '',
				'verify_server_cert' => ($options['dbsslverifyservercert'] ?? false) ?: false,
			];
		}

		// Instantiate the database driver object and return it
		self::$instances[$signature] = new $driver($options);

		return self::$instances[$signature];
	}

	/**
	 * Un-cache a database driver object
	 *
	 * @param   array  $options  The database driver connection options
	 *
	 * @return  void
	 */
	public static function unsetDatabase(array $options): void
	{
		$signature = self::md5(serialize($options));

		if (!isset(self::$instances[$signature]))
		{
			return;
		}

		unset(self::$instances[$signature]);
	}
}
PK�!]N����� BackupEngine/Core/scripting.jsonnu&1i�{
    "volatile.akeebaengine.domains": "init|installer|packdb|packing|finale",
    "volatile.akeebaengine.scripts": "full|dbonly|fileonly|alldb|incfile|incfull",

    "volatile.domain.init.domain": "init",
    "volatile.domain.init.class": "Init",
    "volatile.domain.init.text": "COM_AKEEBA_BACKUP_LABEL_DOMAIN_INIT",

    "volatile.domain.installer.domain": "installer",
    "volatile.domain.installer.class": "Installer",
    "volatile.domain.installer.text": "COM_AKEEBA_BACKUP_LABEL_DOMAIN_INSTALLER",

    "volatile.domain.packdb.domain": "PackDB",
    "volatile.domain.packdb.class": "Db",
    "volatile.domain.packdb.text": "COM_AKEEBA_BACKUP_LABEL_DOMAIN_PACKDB",

    "volatile.domain.packing.domain": "Packing",
    "volatile.domain.packing.class": "Pack",
    "volatile.domain.packing.text": "COM_AKEEBA_BACKUP_LABEL_DOMAIN_PACKING",

    "volatile.domain.finale.domain": "finale",
    "volatile.domain.finale.class": "Finalization",
    "volatile.domain.finale.text": "COM_AKEEBA_BACKUP_LABEL_DOMAIN_FINISHED",

    "volatile.scripting.full.chain": "init|installer|packdb|packing|finale",
    "volatile.scripting.full.text": "COM_AKEEBA_CONFIG_BACKUPTYPE_FULL",
    "volatile.scripting.full.db.saveasname": "normal",
    "volatile.scripting.full.db.databasesini": "1",
    "volatile.scripting.full.db.skipextradb": "0",
    "volatile.scripting.full.db.abstractnames": "1",
    "volatile.scripting.full.db.dropstatements": "0",
    "volatile.scripting.full.db.delimiterstatements": "0",
    "volatile.scripting.full.core.createarchive": "1",

    "volatile.scripting.dbonly.chain": "init|packdb|finale",
    "volatile.scripting.dbonly.text": "COM_AKEEBA_CONFIG_BACKUPTYPE_DBONLY",
    "volatile.scripting.dbonly.db.saveasname": "output",
    "volatile.scripting.dbonly.db.databasesini": "0",
    "volatile.scripting.dbonly.db.skipextradb": "1",
    "volatile.scripting.dbonly.db.abstractnames": "0",
    "volatile.scripting.dbonly.db.dropstatements": "1",
    "volatile.scripting.dbonly.db.delimiterstatements": "1",
    "volatile.scripting.dbonly.core.forceextension": ".sql",
    "volatile.scripting.dbonly.core.createarchive": "0",

    "volatile.scripting.fileonly.chain": "init|packing|finale",
    "volatile.scripting.fileonly.text": "COM_AKEEBA_CONFIG_BACKUPTYPE_FILEONLY",
    "volatile.scripting.fileonly.core.createarchive": "1",

    "volatile.scripting.alldb.chain": "init|installer|packdb|finale",
    "volatile.scripting.alldb.text": "COM_AKEEBA_CONFIG_BACKUPTYPE_ALLDB",
    "volatile.scripting.alldb.db.tempfile": "temporary",
    "volatile.scripting.alldb.db.saveasname": "normal",
    "volatile.scripting.alldb.db.databasesini": "1",
    "volatile.scripting.alldb.db.skipextradb": "0",
    "volatile.scripting.alldb.db.abstractnames": "1",
    "volatile.scripting.alldb.db.dropstatements": "0",
    "volatile.scripting.alldb.db.delimiterstatements": "0",
    "volatile.scripting.alldb.db.finalizearchive": "1",
    "volatile.scripting.alldb.core.createarchive": "1",

    "volatile.scripting.incfile.chain": "init|packing|finale",
    "volatile.scripting.incfile.text": "COM_AKEEBA_CONFIG_BACKUPTYPE_INCFILE",
    "volatile.scripting.incfile.filter.incremental": "1",

    "volatile.scripting.incfull.chain": "init|installer|packdb|packing|finale",
    "volatile.scripting.incfull.text": "COM_AKEEBA_CONFIG_BACKUPTYPE_INCFULL",
    "volatile.scripting.incfull.db.saveasname": "normal",
    "volatile.scripting.incfull.db.databasesini": "1",
    "volatile.scripting.incfull.db.skipextradb": "0",
    "volatile.scripting.incfull.db.abstractnames": "1",
    "volatile.scripting.incfull.db.dropstatements": "0",
    "volatile.scripting.incfull.db.delimiterstatements": "0",
    "volatile.scripting.incfull.core.createarchive": "1",
    "volatile.scripting.incfull.filter.incremental": "1"
}PK�!]^�E\��BackupEngine/Core/Timer.phpnu&1i�<?php
/**
 * Akeeba Engine
 *
 * @package   akeebaengine
 * @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License version 3, or later
 *
 * 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, version 3.
 *
 * 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
 * <https://www.gnu.org/licenses/>.
 */

namespace Akeeba\Engine\Core;

defined('AKEEBAENGINE') || die();

use Akeeba\Engine\Factory;

/**
 * Timer class
 */
class Timer
{

	/** @var float Maximum execution time allowance per step */
	private $max_exec_time = null;

	/** @var int Timestamp of execution start */
	private $start_time = null;

	/**
	 * Public constructor, creates the timer object and calculates the execution time limits
	 *
	 * @param   int|null  $maxExecTime  Maximum execution time, in seconds (minimum 1 second)
	 * @param   int|null  $bias         Execution time bias, in percentage points (10-100)
	 */
	public function __construct(?int $maxExecTime = null, ?int $bias = null)
	{
		// Initialize start time
		$this->start_time = $this->microtime_float();

		// Make sure we have max execution time and execution time bias or use the ones configured in the backup profile
		$configuration = Factory::getConfiguration();
		$maxExecTime   = $maxExecTime ?? (int) $configuration->get('akeeba.tuning.max_exec_time', 14);
		$bias          = $bias ?? (int) $configuration->get('akeeba.tuning.run_time_bias', 75);

		// Make sure both max exec time and bias are positive integers within the allowed range of values
		$maxExecTime = max(1, $maxExecTime);
		$bias        = min(100, max(10, $bias));

		$this->max_exec_time = $maxExecTime * $bias / 100;
	}

	/**
	 * Wake-up function to reset internal timer when we get unserialized
	 */
	public function __wakeup()
	{
		// Re-initialize start time on wake-up
		$this->start_time = $this->microtime_float();
	}

	/**
	 * Gets the number of seconds left, before we hit the "must break" threshold
	 *
	 * @return  float
	 */
	public function getTimeLeft()
	{
		return $this->max_exec_time - $this->getRunningTime();
	}

	/**
	 * Gets the time elapsed since object creation/unserialization, effectively how
	 * long Akeeba Engine has been processing data
	 *
	 * @return float
	 */
	public function getRunningTime()
	{
		return $this->microtime_float() - $this->start_time;
	}

	/**
	 * Enforce the minimum execution time
	 *
	 * @param   bool  $log              Should I log what I'm doing? Default is true.
	 * @param   bool  $serverSideSleep  Should I sleep on the server side? If false we return the amount of time to
	 *                                  wait in msec
	 *
	 * @return  int Wait time to reach min_execution_time in msec
	 */
	public function enforce_min_exec_time($log = true, $serverSideSleep = true)
	{
		// Try to get a sane value for PHP's maximum_execution_time INI parameter
		if (@function_exists('ini_get'))
		{
			$php_max_exec = @ini_get("maximum_execution_time");
		}
		else
		{
			$php_max_exec = 10;
		}
		if (($php_max_exec == "") || ($php_max_exec == 0))
		{
			$php_max_exec = 10;
		}
		// Decrease $php_max_exec time by 500 msec we need (approx.) to tear down
		// the application, as well as another 500msec added for rounding
		// error purposes. Also make sure this is never gonna be less than 0.
		$php_max_exec = max($php_max_exec * 1000 - 1000, 0);

		// Get the "minimum execution time per step" Akeeba Backup configuration variable
		$configuration = Factory::getConfiguration();
		$minexectime   = $configuration->get('akeeba.tuning.min_exec_time', 0);
		if (!is_numeric($minexectime))
		{
			$minexectime = 0;
		}

		// Make sure we are not over PHP's time limit!
		if ($minexectime > $php_max_exec)
		{
			$minexectime = $php_max_exec;
		}

		// Get current running time
		$elapsed_time = $this->getRunningTime() * 1000;

		$clientSideSleep = 0;

		// Only run a sleep delay if we haven't reached the minexectime execution time
		if (($minexectime > $elapsed_time) && ($elapsed_time > 0))
		{
			$sleep_msec = (int)($minexectime - $elapsed_time);

			if (!$serverSideSleep)
			{
				Factory::getLog()->debug("Asking client to sleep for $sleep_msec msec");
				$clientSideSleep = $sleep_msec;
			}
			elseif (function_exists('usleep'))
			{
				if ($log)
				{
					Factory::getLog()->debug("Sleeping for $sleep_msec msec, using usleep()");
				}
				usleep(1000 * $sleep_msec);
			}
			elseif (function_exists('time_nanosleep'))
			{
				if ($log)
				{
					Factory::getLog()->debug("Sleeping for $sleep_msec msec, using time_nanosleep()");
				}
				$sleep_sec  = floor($sleep_msec / 1000);
				$sleep_nsec = 1000000 * ($sleep_msec - ($sleep_sec * 1000));
				time_nanosleep($sleep_sec, $sleep_nsec);
			}
			elseif (function_exists('time_sleep_until'))
			{
				if ($log)
				{
					Factory::getLog()->debug("Sleeping for $sleep_msec msec, using time_sleep_until()");
				}
				$until_timestamp = time() + $sleep_msec / 1000;
				time_sleep_until($until_timestamp);
			}
			elseif (function_exists('sleep'))
			{
				$sleep_sec = ceil($sleep_msec / 1000);
				if ($log)
				{
					Factory::getLog()->debug("Sleeping for $sleep_sec seconds, using sleep()");
				}
				sleep($sleep_sec);
			}
		}
		elseif ($elapsed_time > 0)
		{
			// No sleep required, even if user configured us to be able to do so.
			if ($log)
			{
				Factory::getLog()->debug("No need to sleep; execution time: $elapsed_time msec; min. exec. time: $minexectime msec");
			}
		}

		return $clientSideSleep;
	}

	/**
	 * Reset the timer. It should only be used in CLI mode!
	 */
	public function resetTime()
	{
		$this->start_time = $this->microtime_float();
	}

	/**
	 * Returns the current timestamp in decimal seconds
	 */
	protected function microtime_float()
	{
		[$usec, $sec] = explode(" ", microtime());

		return ((float) $usec + (float) $sec);
	}
}
PK�!]qE�*�*BackupEngine/Core/Domain/Db.phpnu&1i�<?php
/**
 * Akeeba Engine
 *
 * @package   akeebaengine
 * @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License version 3, or later
 *
 * 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, version 3.
 *
 * 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
 * <https://www.gnu.org/licenses/>.
 */

namespace Akeeba\Engine\Core\Domain;

defined('AKEEBAENGINE') || die();

use Akeeba\Engine\Base\Part;
use Akeeba\Engine\Dump\Base as DumpBase;
use Akeeba\Engine\Factory;
use RuntimeException;

/**
 * Multiple database backup engine.
 */
final class Db extends Part
{
	/** @var array A list of the databases to be packed */
	private $database_list = [];

	/** @var array The current database configuration data */
	private $database_config = null;

	/** @var DumpBase The current dumper engine used to backup tables */
	private $dump_engine = null;

	/** @var string The contents of the databases.json file */
	private $databases_json = '';

	/** @var array An array containing the database definitions of all dumped databases so far */
	private $dumpedDatabases = [];

	/** @var int Total number of databases left to be processed */
	private $total_databases = 0;

	/**
	 * Implements the constructor of the class
	 *
	 * @return  void
	 */
	public function __construct()
	{
		parent::__construct();

		Factory::getLog()->debug(__CLASS__ . " :: New instance");
	}

	/**
	 * Implements the getProgress() percentage calculation based on how many
	 * databases we have fully dumped and how much of the current database we
	 * have dumped.
	 *
	 * @return  float
	 */
	public function getProgress()
	{
		if (!$this->total_databases)
		{
			return 0;
		}

		// Get the overall percentage (based on databases fully dumped so far)
		$remaining_steps = count($this->database_list);
		$remaining_steps++;
		$overall = 1 - ($remaining_steps / $this->total_databases);

		// How much is this step worth?
		$this_max = 1 / $this->total_databases;

		// Get the percentage done of the current database
		$local = is_object($this->dump_engine) ? $this->dump_engine->getProgress() : 0;

		$percentage = $overall + $local * $this_max;

		if ($percentage < 0)
		{
			$percentage = 0;
		}
		elseif ($percentage > 1)
		{
			$percentage = 1;
		}

		return $percentage;
	}

	/**
	 * Implements the _prepare abstract method
	 *
	 * @return  void
	 */
	protected function _prepare()
	{
		Factory::getLog()->debug(__CLASS__ . " :: Preparing instance");

		// Populating the list of databases
		$this->populate_database_list();

		$this->total_databases = count($this->database_list);

		$this->setState(self::STATE_PREPARED);
	}

	/**
	 * Implements the _run() abstract method
	 *
	 * @return  void
	 */
	protected function _run()
	{
		if ($this->getState() == self::STATE_POSTRUN)
		{
			Factory::getLog()->debug(__CLASS__ . " :: Already finished");
			$this->setStep('');
			$this->setSubstep('');
		}
		else
		{
			$this->setState(self::STATE_RUNNING);
		}

		// Make sure we have a dumper instance loaded!
		if (is_null($this->dump_engine) && !empty($this->database_list))
		{
			Factory::getLog()->debug(__CLASS__ . " :: Iterating next database");

			// Reset the volatile key holding the table names for this database
			Factory::getConfiguration()->set('volatile.database.table_names', []);

			// Create a new instance
			$this->dump_engine = Factory::getDumpEngine(true);

			// Configure the dumper instance and pass on the volatile database root registry key
			$registry = Factory::getConfiguration();
			$rootkeys = array_keys($this->database_list);
			$root     = array_shift($rootkeys);
			$registry->set('volatile.database.root', $root);

			$this->database_config                         = array_shift($this->database_list);
			$this->database_config['root']                 = $root;
			$this->database_config['process_empty_prefix'] = ($root == '[SITEDB]') ? true : false;

			Factory::getLog()->debug(sprintf("%s :: Now backing up %s (%s)", __CLASS__, $root, $this->database_config['database']));

			$this->dump_engine->setup($this->database_config);
		}
		elseif (is_null($this->dump_engine) && empty($this->database_list))
		{
			throw new RuntimeException('Current dump engine died while resuming the step');
		}

		// Try to step the instance
		$retArray = $this->dump_engine->tick();

		// Error propagation
		$this->lastException = $retArray['ErrorException'];

		if (!is_null($this->lastException))
		{
			throw $this->lastException;
		}

		$this->setStep($retArray['Step']);
		$this->setSubstep($retArray['Substep']);

		// Check if the instance has finished
		if (!$retArray['HasRun'])
		{
			// Set the number of parts
			$this->database_config['parts'] = $this->dump_engine->partNumber + 1;

			// Push the list of tables in the database into the definition of the last database backed up
			$this->database_config['tables'] = Factory::getConfiguration()->get('volatile.database.table_names', []);
			Factory::getConfiguration()->set('volatile.database.table_names', []);

			// Get the (possibly updated) database table name prefix
			$this->database_config['prefix'] = $this->dump_engine->getPrefix();

			// Push the definition of the last database backed up into dumpedDatabases
			array_push($this->dumpedDatabases, $this->database_config);

			// Go to the next entry in the list and dispose the old AkeebaDumperDefault instance
			$this->dump_engine = null;

			// Are we past the end of the list?
			if (empty($this->database_list))
			{
				Factory::getLog()->debug(__CLASS__ . " :: No more databases left to iterate");
				$this->setState(self::STATE_POSTRUN);
			}
		}
	}

	/**
	 * Implements the _finalize() abstract method
	 *
	 * @return  void
	 */
	protected function _finalize()
	{
		$this->setState(self::STATE_FINISHED);

		// If we are in db backup mode, don't create a databases.json
		$configuration = Factory::getConfiguration();

		if (!Factory::getEngineParamsProvider()->getScriptingParameter('db.databasesini', 1))
		{
			Factory::getLog()->debug(__CLASS__ . " :: Skipping databases.json");
		}
		// Create the databases.json contents
		// P.A. This still has the old name with the "ini" string. That's for legacy support. Must update it in the future
		elseif ($this->installerSettings->databasesini)
		{
			$this->createDatabasesJSON();

			Factory::getLog()->debug(__CLASS__ . " :: Creating databases.json");

			// Create a new string
			$databasesJSON = json_encode($this->databases_json, JSON_PRETTY_PRINT);

			Factory::getLog()->debug(__CLASS__ . " :: Writing databases.json contents");

			$archiver        = Factory::getArchiverEngine();
			$virtualLocation = (Factory::getEngineParamsProvider()->getScriptingParameter('db.saveasname', 'normal') == 'short') ? '' : $this->installerSettings->sqlroot;
			$archiver->addFileVirtual('databases.json', $virtualLocation, $databasesJSON);
		}

		// On alldb mode, we have to finalize the archive as well
		if (Factory::getEngineParamsProvider()->getScriptingParameter('db.finalizearchive', 0))
		{
			Factory::getLog()->info("Finalizing database dump archive");

			$archiver = Factory::getArchiverEngine();
			$archiver->finalize();
		}

		// In CLI mode we'll also close the database connection
		if (defined('AKEEBACLI'))
		{
			Factory::getLog()->info("Closing the database connection to the main database");
			Factory::unsetDatabase();
		}
	}

	/**
	 * Populates database_list with the list of databases in the settings
	 *
	 * @return void
	 */
	protected function populate_database_list()
	{
		// Get database inclusion filters
		$filters             = Factory::getFilters();
		$this->database_list = $filters->getInclusions('db');

		if (Factory::getEngineParamsProvider()->getScriptingParameter('db.skipextradb', 0))
		{
			// On database only backups we prune extra databases
			Factory::getLog()->debug(__CLASS__ . " :: Adding only main database");

			if (count($this->database_list) > 1)
			{
				$this->database_list = array_shift($this->database_list);
			}
		}
	}

	protected function createDatabasesJSON()
	{
		// caching databases.json contents
		Factory::getLog()->debug(__CLASS__ . " :: Creating databases.json data");

		// Create a new array
		$this->databases_json = [];

		$registry = Factory::getConfiguration();

		$blankOutPass = $registry->get('engine.dump.common.blankoutpass', 0);
		$siteRoot     = $registry->get('akeeba.platform.newroot', '');

		// Loop through databases list
		foreach ($this->dumpedDatabases as $definition)
		{
			$section = basename($definition['dumpFile']);

			$dboInstance = Factory::getDatabase($definition);
			$type        = $dboInstance->name;
			$tech        = $dboInstance->getDriverType();

			// If the database is a sqlite one, we have to process the database name which contains the path
			// At the moment we only handle the case where the db file is UNDER site root
			if ($tech == 'sqlite')
			{
				$definition['database'] = str_replace($siteRoot, '#SITEROOT#', $definition['database']);
			}

			$this->databases_json[$section] = [
				'dbtype'                => $type,
				'dbtech'                => $tech,
				'dbname'                => $definition['database'],
				'sqlfile'               => $definition['dumpFile'],
				'marker'                => "\n/**ABDB**/",
				'dbhost'                => $definition['host'] ?? '',
				'dbport'                => $definition['port'] ?? '',
				'dbsocket'              => $definition['socket'] ?? '',
				'dbuser'                => $definition['username'] ?? '',
				'dbpass'                => $definition['password'] ?? '',
				'prefix'                => $definition['prefix'] ?? '',
				'dbencryption'          => $definition['dbencryption'] ?? 0,
				'dbsslcipher'           => $definition['dbsslcipher'] ?? '',
				'dbsslca'               => $definition['dbsslca'] ?? '',
				'dbsslkey'              => $definition['dbsslkey'] ?? '',
				'dbsslcert'             => $definition['dbsslcert'] ?? '',
				'dbsslverifyservercert' => $definition['dbsslverifyservercert'] ?? 0,
				'parts'                 => $definition['parts'],
				'tables'                => $definition['tables'],
			];

			if ($blankOutPass)
			{
				$this->databases_json[$section]['dbuser']    = '';
				$this->databases_json[$section]['dbpass']    = '';
				$this->databases_json[$section]['dbsslca']   = '';
				$this->databases_json[$section]['dbsslkey']  = '';
				$this->databases_json[$section]['dbsslcert'] = '';
			}
		}
	}
}
PK�!]�|B�h�h�!BackupEngine/Core/Domain/Pack.phpnu&1i�<?php
/**
 * Akeeba Engine
 *
 * @package   akeebaengine
 * @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License version 3, or later
 *
 * 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, version 3.
 *
 * 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
 * <https://www.gnu.org/licenses/>.
 */

namespace Akeeba\Engine\Core\Domain;

defined('AKEEBAENGINE') || die();

use Akeeba\Engine\Archiver\Base as BaseArchiverClass;
use Akeeba\Engine\Base\Exceptions\ErrorException;
use Akeeba\Engine\Base\Exceptions\WarningException;
use Akeeba\Engine\Base\Part;
use Akeeba\Engine\Configuration;
use Akeeba\Engine\Factory;
use Akeeba\Engine\Platform;
use Exception;
use Akeeba\Engine\Psr\Log\LogLevel;
use RuntimeException;

/* Windows system detection */
if (!defined('_AKEEBA_IS_WINDOWS'))
{
	$isWindows = DIRECTORY_SEPARATOR == '\\';

	if (function_exists('php_uname'))
	{
		$isWindows = stristr(php_uname(), 'windows');
	}

	define('_AKEEBA_IS_WINDOWS', $isWindows);
}

/**
 * Packing engine. Takes care of putting gathered files (the file list) into
 * an archive.
 */
final class Pack extends Part
{
	/** @var array Directories left to be scanned */
	private $directory_list;

	/** @var array Files left to be put into the archive */
	private $file_list;

	/**
	 * Have we finished scanning all subdirectories of the current directory?
	 *
	 * @var   boolean
	 */
	private $done_subdir_scanning = false;

	/**
	 * Have we finished scanning all files of the current directory?
	 *
	 * @var   boolean
	 */
	private $done_file_scanning = true;

	/**
	 * Is the current directory completely excluded?
	 *
	 * @var boolean
	 */
	private $excluded_folder = false;

	/**
	 * Are the current directory's subdirectories excluded?
	 *
	 * @var boolean
	 */
	private $excluded_subdirectories = false;

	/**
	 * Are the current directory's files excluded?
	 *
	 * @var boolean
	 */
	private $excluded_files = false;

	/** @var   string  Path to add to scanned files */
	private $path_prefix;

	/** @var   string  Path to remove from scanned files */
	private $remove_path_prefix;

	/** @var   array   An array of root directories to scan */
	private $root_definitions = [];

	/** @var   integer  How many files have been processed in the current step */
	private $processed_files_counter;

	/** @var   string  Current directory being scanned */
	private $current_directory;

	/** @var   integer|null  The position in the file list scanning */
	private $getFiles_position = null;

	/** @var   integer|null  The position in the folder list scanning */
	private $getFolders_position = null;

	/** @var   string  Current root directory being processed */
	private $root = '[SITEROOT]';

	/** @var   integer  Total root directories to scan, used in percentage calculation */
	private $total_roots = 0;

	/** @var   integer  Total files to process */
	private $total_files = 0;

	/** @var   integer  Total files already processed */
	private $done_files = 0;

	/** @var   integer  Total folders to process */
	private $total_folders = 0;

	/** @var   integer  Total folders already processed */
	private $done_folders = 0;

	/**
	 * Public constructor of the class
	 *
	 * @return  void
	 */
	public function __construct()
	{
		parent::__construct();

		Factory::getLog()->debug(__CLASS__ . " :: new instance");
	}

	/**
	 * Immediate post-processing of a part file that's just been completed.
	 *
	 * This method returns false when no post-processing was possible, or if it failed.
	 *
	 * The method returns true when it's post-processed a part file, even if such post-processing has only been partial.
	 *
	 * @param   BaseArchiverClass  $archiver       The archiver engine
	 * @param   Configuration      $configuration  Reference to the Factory configuration object
	 *
	 * @return  bool  True to indicate our caller should return immediately.
	 */
	public static function postProcessDonePartFile(BaseArchiverClass $archiver, Configuration $configuration)
	{
		// Get configuration parameters
		$postprocEngine               = Factory::getPostprocEngine();
		$allowImmediatePostProcessing = $configuration->get('engine.postproc.common.after_part', 0);
		$filename                     = $configuration->get('volatile.postproc.filename', null);
		$shouldDeleteProcessedPart    = $configuration->get('engine.postproc.common.delete_after', false);
		$shouldAbortOnFailed          = $configuration->get('engine.postproc.common.abort_on_fail', false);
		$engineCanDeleteFiles         = $postprocEngine->isFileDeletionAfterProcessingAdvisable();

		// Is the immediate post-processing disabled? Return false.
		if (!$allowImmediatePostProcessing)
		{
			return false;
		}

		// Are we continuing the post-processing of a previous file?
		if (!empty($filename))
		{
			Factory::getLog()->info(sprintf("Continuing immediate post-processing of part file %s", basename($filename)));
		}

		// Do we have a NEW file to post-process?
		if (empty($filename) && !empty($archiver->finishedPart))
		{
			$filename = array_shift($archiver->finishedPart);

			if (!empty($filename))
			{
				Factory::getLog()->info(sprintf("Starting immediate post-processing of part file %s", basename($filename)));
			}
		}

		// Not continuing post-processing and no new file to process? Return false and let the caller carry on.
		if (empty($filename))
		{
			return false;
		}

		// Store the file to post-process in volatile storage
		$configuration->set('volatile.postproc.filename', $filename);

		// Try to post-process the file
		$timer     = Factory::getTimer();
		$startTime = $timer->getRunningTime();

		try
		{
			$postProcessingResult = $postprocEngine->processPart($filename);
			$endTime              = $timer->getRunningTime();
			$stepTime             = $endTime - $startTime;
			$notEnoughTimeLeft    = $timer->getTimeLeft() < $stepTime;

			/**
			 * FINISHED POST-PROCESSING THE FILE
			 */
			if ($postProcessingResult === true)
			{
				Factory::getLog()->info('Successfully processed file ' . basename($filename));

				// Indicate the file has finished post-processing
				$configuration->set('volatile.postproc.filename', null);

				// Add this part's size to the volatile storage variable holding the total size of the backup set
				$volatileTotalSize = $configuration->get('volatile.engine.archiver.totalsize', 0);
				$volatileTotalSize += (int) @filesize($filename);

				$configuration->set('volatile.engine.archiver.totalsize', $volatileTotalSize);

				// If the engine recommends breaking the step after post-processing let's set the break flag
				if ($postprocEngine->recommendsBreakAfter())
				{
					$configuration->set('volatile.breakflag', true);
				}

				// If I don't need to delete the post-processed part just return
				if (!$shouldDeleteProcessedPart)
				{
					return true;
				}

				if (!$engineCanDeleteFiles)
				{
					return true;
				}

				Factory::getLog()->debug(sprintf("Deleting post-processed file %s", basename($filename)));
				Platform::getInstance()->unlink($filename);

				return true;
			}

			/**
			 * MORE WORK REQUIRED
			 */
			Factory::getLog()->info(sprintf("More post-processing steps required for file %s", basename($filename)));

			/**
			 * If the time left is not at least as much as the previous post-processing step took us we break the step.
			 * This prevents a time-out trying to post-process another chunk of the file.
			 */
			if ($notEnoughTimeLeft)
			{
				$configuration->set('volatile.breakflag', true);
			}
		}
		/**
		 * POST-PROCESSING FAILED
		 */
		catch (Exception $e)
		{
			// Indicate no further processing is possible on this file.
			$configuration->set('volatile.postproc.filename', null);

			Factory::getLog()->warning('Failed to process file ' . basename($filename));
			Factory::getLog()->warning('Error received from the post-processing engine:');

			self::logErrorsFromException($e, $shouldAbortOnFailed ? LogLevel::ERROR : LogLevel::WARNING);

			// Fail the backup if we're configured to do so
			if ($shouldAbortOnFailed)
			{
				throw new ErrorException(sprintf('Failed to process backup archive file %s', basename($filename)));
			}

			return false;
		}

		// No further processing necessary
		return true;
	}

	/**
	 * Implements the getProgress() percentage calculation based on how many
	 * roots we have fully backed up and how much of the current root we
	 * have backed up.
	 */
	public function getProgress()
	{
		if (empty($this->total_roots))
		{
			return 0;
		}

		// Get the overall percentage (based on databases fully dumped so far)
		$remaining_steps = count($this->root_definitions);
		$remaining_steps++;
		$overall = 1 - ($remaining_steps / $this->total_roots);

		// How much is this step worth?
		$this_max = 1 / $this->total_roots;

		// Get the percentage done of the current root. Hey, the calculation *is* dodgy, I know it!
		$local = 0;
		if ($this->total_files > 0)
		{
			$local += 0.05 * $this->done_files / $this->total_files;
		}
		if ($this->total_folders > 0)
		{
			$local += 0.95 * $this->done_folders / $this->total_folders;
		}

		$percentage = $overall + $local * $this_max;
		if ($percentage < 0)
		{
			$percentage = 0;
		}
		if ($percentage > 1)
		{
			$percentage = 1;
		}

		return $percentage;
	}

	/**
	 * Implements the _prepare() abstract method
	 *
	 * @return  void
	 */
	protected function _prepare()
	{
		Factory::getLog()->debug(__CLASS__ . " :: Starting _prepare()");

		$registry = Factory::getConfiguration();

		// Get a list of directories to include
		Factory::getLog()->debug(__CLASS__ . " :: Getting directory inclusion filters");
		$filters                = Factory::getFilters();
		$this->root_definitions = $filters->getInclusions('dir');

		$this->total_roots = count($this->root_definitions);

		// Add the mapping text file if there are external directories defined!
		if (count($this->root_definitions) > 1)
		{
			// The site's root is the last directory to be backed up. Um, no,
			// this is not what we need
			$temp = array_pop($this->root_definitions);
			array_unshift($this->root_definitions, $temp);

			// We add a README.txt file in our virtual directory...
			Factory::getLog()->debug("Creating README.txt in the EFF virtual folder");
			$virtualContents = <<<ENDVCONTENT
This directory contains directories above the web site's root you chose to
include in the backup set.  This file helps you figure out which directory
in the backup  set corresponds to  which directory in the  original site's
structure. You'll have to restore these files manually!


ENDVCONTENT;

			$counter = 0;
			$vdir    = trim($registry->get('akeeba.advanced.virtual_folder'), '/') . '/';
			$effini  = ['eff' => []];

			$rootsToPack = array_filter(
				$this->root_definitions,
				function ($data) {
					return $data[0] != '[SITEROOT]';
				}
			);

			foreach ($rootsToPack as $dir)
			{
				$counter++;

				$test = trim($dir[1]);

				if ($test == '/')
				{
					$counter--;
					continue;
				}

				$virtualContents .= $dir[1] . "\tis the backup of\t" . $dir[0] . "\n";

				$effini['eff'][$dir[0]] = $vdir . $dir[1];
			}

			$effini = json_encode($effini, JSON_PRETTY_PRINT);

			// Add the file to our archive
			$archiver = Factory::getArchiverEngine();

			if ($counter >= 1)
			{
				$archiver->addFileVirtual('README.txt', $registry->get('akeeba.advanced.virtual_folder'), $virtualContents);
				$archiver->addFileVirtual('eff.json', $this->installerSettings->installerroot, $effini);
			}
			else
			{
				Factory::getLog()->debug("README.txt was not created; all EFF directories are being backed up to the archive's root");
			}
		}

		// Find the site's root element and shift it into the directory list
		$dir_definition = array_shift($this->root_definitions);
		$count          = 0;
		$max_dir_count  = count($this->root_definitions);
		while (!is_null($dir_definition[1]) && ($count < $max_dir_count))
		{
			$count++;
			array_push($this->root_definitions, $dir_definition);
			$dir_definition = array_shift($this->root_definitions);
		}

		// Settling with whatever we have, let's put it to use, shall we?
		$this->remove_path_prefix = $dir_definition[0]; // Remove absolute path to directory when storing the file
		if (is_null($dir_definition[1]))
		{
			$this->path_prefix = ''; // No added path for main site
			if (empty($dir_definition[0]))
			{
				$this->root = '[SITEROOT]';
			}
			else
			{
				$this->root = $dir_definition[0];
			}
		}
		else
		{
			$dir_definition[1] = trim($dir_definition[1]);
			if (empty($dir_definition[1]) || $dir_definition[1] == '/')
			{
				$this->path_prefix = '';
			}
			else
			{
				$this->path_prefix = $registry->get('akeeba.advanced.virtual_folder') . '/' . $dir_definition[1];
			}
			$this->root = $dir_definition[0];
		}
		// Translate the root into an absolute path
		$stock_dirs   = Platform::getInstance()->get_stock_directories();
		$absolute_dir = substr($this->root, 0);
		if (!empty($stock_dirs))
		{
			foreach ($stock_dirs as $key => $replacement)
			{
				$absolute_dir = str_replace($key, $replacement, $absolute_dir);
			}
		}
		$this->directory_list[]   = $absolute_dir;
		$this->remove_path_prefix = $absolute_dir;
		$registry                 = Factory::getConfiguration();
		$registry->set('volatile.filesystem.current_root', $absolute_dir);

		$this->done_subdir_scanning = true;
		$this->done_file_scanning   = true;
		$this->total_files          = 0;
		$this->done_files           = 0;
		$this->total_folders        = 0;
		$this->done_folders         = 0;

		$this->setState(self::STATE_PREPARED);

		Factory::getLog()->debug(__CLASS__ . " :: prepared");
	}

	// ============================================================================================
	// PRIVATE METHODS
	// ============================================================================================

	/**
	 * @return void
	 * @throws Exception
	 */
	protected function _run()
	{
		if ($this->getState() == self::STATE_POSTRUN)
		{
			Factory::getLog()->debug(__CLASS__ . " :: Already finished");
			$this->setStep("-");
			$this->setSubstep("");

			return;
		}

		// If I'm done scanning files and subdirectories and there are no more files to pack get the next
		// directory. This block is triggered in the first step in a new root.
		if (empty($this->file_list) && $this->done_subdir_scanning && $this->done_file_scanning)
		{
			$this->progressMarkFolderDone();

			if (!$this->getNextDirectory())
			{
				if ($this->getNextRoot())
				{
					if (!$this->getNextDirectory())
					{
						return;
					}
				}
				else
				{
					return;
				}
			}
		}

		/**
		 * Automated tests override
		 *
		 * If the file .akeeba_engine_automated_tests_error file is present in the site's root I will throw an error.
		 */
		[$root, $translated_root, $dir] = $this->getCleanDirectoryComponents();

		if (@file_exists($translated_root . '/.akeeba_engine_automated_tests_error'))
		{
			throw new RuntimeException("Akeeba Engine automated tests: I am throwing an error because the file .akeeba_engine_automated_tests_error is present in the site's root folder.");
		}

		// If I'm not done scanning for files and the file list is empty then scan for more files
		if (!$this->done_file_scanning && empty($this->file_list))
		{
			$this->scanFiles();
		}
		// If I have files left, pack them
		elseif (!empty($this->file_list))
		{
			$this->pack_files();
		}
		// If I'm not done scanning subdirectories, go ahead and scan some more of them
		elseif (!$this->done_subdir_scanning)
		{
			$this->scanSubdirs();
		}
		/**
		 * If we have excluded contained files or subdirectories BUT NOT the entire folder itself AND there are
		 * no files in this directory THEN add an empty directory to the archive.
		 **/
		elseif (
			($this->excluded_files || $this->excluded_subdirectories)
			&&
			!$this->excluded_folder
			&&
			empty($this->file_list)
		)
		{
			Factory::getLog()->info("Empty directory " . $this->current_directory . ' (files and directories are filtered)');

			$archiver = Factory::getArchiverEngine();

			if ($this->current_directory != $this->remove_path_prefix)
			{
				$archiver->addFile($this->current_directory, $this->remove_path_prefix, $this->path_prefix);
			}
		}
	}

	/**
	 * Implements the _finalize() abstract method
	 *
	 */
	protected function _finalize()
	{
		Factory::getLog()->info("Finalizing archive");
		$archive = Factory::getArchiverEngine();
		$archive->finalize();

		Factory::getLog()->debug("Archive is finalized");

		$this->setState(self::STATE_FINISHED);
	}

	/**
	 * Gets the next directory to scan from the stack. It also applies folder
	 * filters (directory exclusion, subdirectory exclusion, file exclusion),
	 * updating the operation toggle properties of the class.
	 *
	 * @return   boolean  True if we found a directory, false if the directory
	 *                    stack is empty. It also returns true if the folder is
	 *                    filtered (we are told to skip it)
	 */
	protected function getNextDirectory()
	{
		// Reset the file / folder scanning positions
		$this->getFiles_position       = null;
		$this->getFolders_position     = null;
		$this->done_file_scanning      = false;
		$this->done_subdir_scanning    = false;
		$this->excluded_folder         = false;
		$this->excluded_subdirectories = false;
		$this->excluded_files          = false;

		if ((is_array($this->directory_list) || $this->directory_list instanceof \Countable ? count($this->directory_list) : 0) == 0)
		{
			// No directories left to scan
			return false;
		}
		else
		{
			// Get and remove the last entry from the $directory_list array
			$this->current_directory = array_pop($this->directory_list);
			$this->setStep($this->current_directory);
			$this->processed_files_counter = 0;
		}

		[$root, $translated_root, $dir] = $this->getCleanDirectoryComponents();

		// Get a filters instance
		$filters = Factory::getFilters();

		// Apply DEF (directory exclusion filters)
		// Note: the !empty($dir) prevents the site's root from being filtered out
		if ($filters->isFiltered($dir, $root, 'dir', 'all') && !empty($dir))
		{
			Factory::getLog()->info("Skipping directory " . $this->current_directory);
			$this->done_subdir_scanning = true;
			$this->done_file_scanning   = true;
			$this->excluded_folder      = true;

			return true;
		}

		// Apply Skip Contained Directories Filters
		if ($filters->isFiltered($dir, $root, 'dir', 'children'))
		{
			$this->excluded_subdirectories = true;

			Factory::getLog()->info("Skipping subdirectories of directory " . $this->current_directory);

			$this->done_subdir_scanning = true;
		}

		// Apply Skipfiles
		if ($filters->isFiltered($dir, $root, 'dir', 'content'))
		{
			$this->excluded_files = true;

			Factory::getLog()->info("Skipping files of directory " . $this->current_directory);

			$this->done_file_scanning = true;

			// When the files of a folder are skipped we will have to add some
			// files anyway if they are present. These are files used to
			// prevent direct access to the folder.

			// Try to find and include .htaccess and index.htm(l) files
			// # Fix 2.4: Do not add DIRECTORY_SEPARATOR if we are on the site's root and it's an empty string
			$ds                            = ($this->current_directory == '') || ($this->current_directory == '/') ? '' : DIRECTORY_SEPARATOR;
			$checkForTheseFiles            = [
				$this->current_directory . $ds . '.htaccess',
				$this->current_directory . $ds . 'web.config',
				$this->current_directory . $ds . 'index.html',
				$this->current_directory . $ds . 'index.htm',
				$this->current_directory . $ds . 'robots.txt',
			];
			$this->processed_files_counter = 0;

			foreach ($checkForTheseFiles as $fileName)
			{
				if (@file_exists($fileName))
				{
					// Fix 3.3 - We have to also put them through other filters, ahem!
					if (!$filters->isFiltered($fileName, $root, 'file', 'all'))
					{
						$this->file_list[] = $fileName;
						$this->processed_files_counter++;
					}
				}
			}
		}

		return true;
	}

	/**
	 * Try to add some files from the $file_list into the archive
	 *
	 * @return   boolean   True if there were files packed, false otherwise
	 *                     (empty filelist or fatal error)
	 */
	protected function pack_files()
	{
		// Get a reference to the archiver and the timer classes
		$archiver      = Factory::getArchiverEngine();
		$timer         = Factory::getTimer();
		$configuration = Factory::getConfiguration();

		// Check whether we need to immediately post-processing a done part
		if (self::postProcessDonePartFile($archiver, $configuration))
		{
			return true;
		}

		// If the archiver has work to do, make sure it finished up before continuing
		if ($configuration->get('volatile.engine.archiver.processingfile', false))
		{
			Factory::getLog()->debug("Continuing file packing from previous step");
			$archiver->addFile('', '', '');

			// If that was the last step for packing this file, 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 ((is_array($this->file_list) || $this->file_list instanceof \Countable ? count($this->file_list) : 0) == 0)
		{
			// No files left to pack. Return true and let the engine loop
			$this->progressMarkFolderDone();

			return true;
		}
		else
		{
			Factory::getLog()->debug("Packing files");
			$packedSize    = 0;
			$numberOfFiles = 0;

			[$usec, $sec] = explode(" ", microtime());
			$opStartTime = ((float) $usec + (float) $sec);

			$largeFileThreshold = Factory::getConfiguration()->get('engine.scan.common.largefile', 10485760);

			while (((is_array($this->file_list) || $this->file_list instanceof \Countable ? count($this->file_list) : 0) > 0))
			{
				$file = @array_shift($this->file_list);
				$size = 0;
				if (file_exists($file))
				{
					$size = @filesize($file);
				}
				// Anticipatory file size algorithm
				if (($numberOfFiles > 0) && ($size > $largeFileThreshold))
				{
					if (!Factory::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();
						Factory::getLog()->info("Breaking step _before_ large file: " . $file . " - size: " . $size);
						// Push the file back to the list.
						array_unshift($this->file_list, $file);

						// Return true and let the engine loop
						return true;
					}
				}

				// Proactive potential timeout detection
				// Rough estimation of packing speed in bytes per second
				[$usec, $sec] = explode(" ", microtime());

				$opEndTime = ((float) $usec + (float) $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 (!Factory::getConfiguration()->get('akeeba.tuning.nobreak.proactive', 0))
					{
						array_unshift($this->file_list, $file);
						Factory::getLog()->info("Proactive step break - file: " . $file . " - size: " . $size . " - req. time " . sprintf('%2.2f', $_reqTime));
						$this->setBreakFlag();

						return true;
					}
				}

				$packedSize += $size;
				$numberOfFiles++;
				$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();
				}

				// If this was the first file packed and we've already gone past
				// the large file size threshold break the step. Continuing with
				// more operations after packing such a big file is increasing
				// the risk to hit a timeout.
				if (($packedSize > $largeFileThreshold) && ($numberOfFiles == 1))
				{
					if (!Factory::getConfiguration()->get('akeeba.tuning.nobreak.afterlargefile', 0))
					{
						Factory::getLog()->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;
				}
			}

			// True if we have more files, false if we're done packing
			return ((is_array($this->file_list) || $this->file_list instanceof \Countable ? count($this->file_list) : 0) > 0);
		}
	}

	protected function progressAddFile()
	{
		$this->total_files++;
	}

	protected function progressMarkFileDone()
	{
		$this->done_files++;
	}

	protected function progressAddFolder()
	{
		$this->total_folders++;
	}

	protected function progressMarkFolderDone()
	{
		$this->done_folders++;
	}

	/**
	 * Returns the site root, the translated site root and the translated current directory
	 *
	 * @return array
	 */
	protected function getCleanDirectoryComponents()
	{
		$fsUtils = Factory::getFilesystemTools();

		// Break directory components
		if (Factory::getConfiguration()->get('akeeba.platform.override_root', 0))
		{
			$siteroot = Factory::getConfiguration()->get('akeeba.platform.newroot', '[SITEROOT]');
		}
		else
		{
			$siteroot = '[SITEROOT]';
		}

		$root = $this->root;

		if ($this->root == $siteroot)
		{
			$translated_root = $fsUtils->translateStockDirs($siteroot, true);
		}
		else
		{
			$translated_root = $this->remove_path_prefix;
		}

		$dir = $fsUtils->TrimTrailingSlash($this->current_directory);

		if (strtoupper(substr(PHP_OS, 0, 3)) == 'WIN')
		{
			$translated_root = $fsUtils->TranslateWinPath($translated_root);
			$dir             = $fsUtils->TranslateWinPath($dir);
		}

		if (substr($dir, 0, strlen($translated_root)) == $translated_root)
		{
			$dir = substr($dir, strlen($translated_root));
		}
		elseif (in_array(substr($translated_root, -1), ['/', '\\']))
		{
			$new_translated_root = rtrim($translated_root, '/\\');
			if (substr($dir, 0, strlen($new_translated_root)) == $new_translated_root)
			{
				$dir = substr($dir, strlen($new_translated_root));
			}
		}

		if (substr($dir, 0, 1) == '/')
		{
			$dir = substr($dir, 1);
		}

		return [$root, $translated_root, $dir];
	}

	/**
	 * Steps the subdirectory scanning of the current directory
	 *
	 * @return  boolean  True on success, false on fatal error
	 */
	protected function scanSubdirs()
	{
		$engine         = Factory::getScanEngine();
		$subdirectories = false;

		[$root, $translated_root, $dir] = $this->getCleanDirectoryComponents();

		// Get a filters instance
		$filters = Factory::getFilters();

		if (is_null($this->getFolders_position))
		{
			Factory::getLog()->info("Scanning directories of " . $this->current_directory);
		}
		else
		{
			Factory::getLog()->info("Resuming scanning directories of " . $this->current_directory);
		}

		// Get subdirectories
		$exception = null;

		try
		{
			$subdirectories = $engine->getFolders($this->current_directory, $this->getFolders_position);
		}
		catch (WarningException $e)
		{
			Factory::getLog()->warning($e->getMessage());

			$subdirectories = false;
		}
		catch (Exception $e)
		{
			$exception = $e;
		}

		// If the list contains "too many" items, please break this step!
		if (Factory::getConfiguration()->get('volatile.breakflag', false))
		{
			// Log the step break decision, for debugging reasons
			Factory::getLog()->info("Large directory " . $this->current_directory . " while scanning for subdirectories; I will resume scanning in next step.");

			// Return immediately, marking that we are not done yet!
			return true;
		}

		// Error control
		if (!is_null($exception))
		{
			throw $exception;
		}

		// Start adding the subdirectories
		if (!empty($subdirectories) && is_array($subdirectories))
		{
			$dereferenceSymlinks = Factory::getConfiguration()->get('engine.archiver.common.dereference_symlinks');

			// If we have to treat symlinks as real directories just add everything
			if ($dereferenceSymlinks)
			{
				// Treat symlinks to directories as actual directories
				foreach ($subdirectories as $subdirectory)
				{
					$this->directory_list[] = $subdirectory;
					$this->progressAddFolder();
				}
			}
			// If we are told not to dereference symlinks we'll need to check each subdirectory thoroughly
			else
			{
				// Treat symlinks to directories as simple symlink files (ONLY WORKS WITH CERTAIN ARCHIVERS!)
				foreach ($subdirectories as $subdirectory)
				{
					if (is_link($subdirectory))
					{
						// Symlink detected; apply directory filters to it
						if (empty($dir))
						{
							$dirSlash = $dir;
						}
						else
						{
							$dirSlash = $dir . '/';
						}

						$check = $dirSlash . basename($subdirectory);
						Factory::getLog()->debug("Directory symlink detected: $check");

						if (_AKEEBA_IS_WINDOWS)
						{
							$check = Factory::getFilesystemTools()->TranslateWinPath($check);
						}

						// Do I need this? $dir contains a path relative to the root anyway...
						$check = ltrim(str_replace($translated_root, '', $check), '/');

						// Check for excluded symlinks (note that they are excluded as DIRECTORIES in the GUI)
						if ($filters->isFiltered($check, $root, 'dir', 'all'))
						{
							Factory::getLog()->info("Skipping directory symlink " . $check);
						}
						else
						{
							Factory::getLog()->debug('Adding folder symlink: ' . $check);
							$this->file_list[] = $subdirectory;
							$this->progressAddFile();
						}
					}
					else
					{
						$this->directory_list[] = $subdirectory;
						$this->progressAddFolder();
					}
				}
			}
		}

		// If the scanner nullified the next position to scan, we're done
		// scanning for subdirectories
		if (is_null($this->getFolders_position))
		{
			$this->done_subdir_scanning = true;
		}

		return true;
	}

	/**
	 * Steps the files scanning of the current directory
	 *
	 * @return  void  True on success, false on fatal error
	 * @throws Exception
	 */
	protected function scanFiles()
	{
		$engine   = Factory::getScanEngine();
		$fileList = false;

		[$root, $translated_root, $dir] = $this->getCleanDirectoryComponents();

		// Get a filters instance
		$filters = Factory::getFilters();

		if (is_null($this->getFiles_position))
		{
			Factory::getLog()->info("Scanning files of " . $this->current_directory);
			$this->processed_files_counter = 0;
		}
		else
		{
			Factory::getLog()->info("Resuming scanning files of " . $this->current_directory);
		}

		// Get file listing
		$exception = null;

		try
		{
			$fileList = $engine->getFiles($this->current_directory, $this->getFiles_position);
		}
		catch (WarningException $e)
		{
			Factory::getLog()->warning($e->getMessage());

			$fileList = false;
		}
		catch (Exception $e)
		{
			$exception = $e;
		}

		// If the list contains "too many" items, please break this step!
		if (Factory::getConfiguration()->get('volatile.breakflag', false))
		{
			// Log the step break decision, for debugging reasons
			Factory::getLog()->info("Large directory " . $this->current_directory . " while scanning for files; I will resume scanning in next step.");

			// Return immediately, marking that we are not done yet!
			return;
		}

		// Error control
		if (!is_null($exception))
		{
			throw $exception;
		}

		// Do I have an unreadable directory?
		if ($fileList === false)
		{
			Factory::getLog()->warning('Unreadable directory ' . $this->current_directory);

			$this->done_file_scanning = true;
		}
		// Directory was readable, process the file list
		elseif (is_array($fileList) && !empty($fileList))
		{
			// Add required trailing slash to $dir
			if (!empty($dir))
			{
				$dir .= '/';
			}

			// Scan all directory entries
			foreach ($fileList as $fileName)
			{
				$check = $dir . basename($fileName);

				if (_AKEEBA_IS_WINDOWS)
				{
					$check = Factory::getFilesystemTools()->TranslateWinPath($check);
				}

				// Do I need this? $dir contains a path relative to the root anyway...
				$check        = ltrim(str_replace($translated_root, '', $check), '/');
				$byFilter     = '';
				$skipThisFile = $filters->isFilteredExtended($check, $root, 'file', 'all', $byFilter);

				if ($skipThisFile)
				{
					Factory::getLog()->info("Skipping file $fileName (filter: $byFilter)");
				}
				else
				{
					$this->file_list[] = $fileName;
					$this->processed_files_counter++;
					$this->progressAddFile();
				}
			}
		}

		// If the scanner engine nullified the next position we are done
		// scanning for files
		if (is_null($this->getFiles_position))
		{
			$this->done_file_scanning = true;
		}

		// If the directory was genuinely empty we will have to add an empty
		// directory entry in the archive, otherwise this directory will never
		// be restored.
		if ($this->done_file_scanning && ($this->processed_files_counter == 0))
		{
			Factory::getLog()->info("Empty directory " . $this->current_directory);

			$archiver = Factory::getArchiverEngine();

			if ($this->current_directory != $this->remove_path_prefix)
			{
				$archiver->addFile($this->current_directory, $this->remove_path_prefix, $this->path_prefix);
			}

			unset($archiver);
		}

		return;
	}

	/**
	 * Try to determine the next root folder to scan
	 *
	 * @return  boolean  True if there was a new root to scan
	 */
	protected function getNextRoot()
	{
		// We have finished with our directory list. Hmm... Do we have extra directories?
		if (count($this->root_definitions) > 0)
		{
			Factory::getLog()->debug("More off-site directories detected");
			$registry       = Factory::getConfiguration();
			$dir_definition = array_shift($this->root_definitions);

			$this->remove_path_prefix = $dir_definition[0]; // Remove absolute path to directory when storing the file

			if (is_null($dir_definition[1]))
			{
				$this->path_prefix = ''; // No added path for main site
			}
			else
			{
				$dir_definition[1] = trim($dir_definition[1]);

				if (empty($dir_definition[1]) || $dir_definition[1] == '/')
				{
					$this->path_prefix = '';
				}
				else
				{
					$this->path_prefix = $registry->get('akeeba.advanced.virtual_folder') . '/' . $dir_definition[1];
				}
			}

			$this->done_scanning = false; // Make sure we process this file list!
			$this->root          = $dir_definition[0];

			// Translate the root into an absolute path
			$stock_dirs   = Platform::getInstance()->get_stock_directories();
			$absolute_dir = substr($this->root, 0);

			if (!empty($stock_dirs))
			{
				foreach ($stock_dirs as $key => $replacement)
				{
					$absolute_dir = str_replace($key, $replacement, $absolute_dir);
				}
			}

			$this->directory_list[]   = $absolute_dir;
			$this->remove_path_prefix = $absolute_dir;

			$registry->set('volatile.filesystem.current_root', $absolute_dir);

			$this->total_files   = 0;
			$this->done_files    = 0;
			$this->total_folders = 0;
			$this->done_folders  = 0;

			Factory::getLog()->info("Including new off-site directory to " . $dir_definition[1]);

			return true;
		}
		else
			// Nope, we are completely done!
		{
			$this->setState(self::STATE_POSTRUN);

			return false;
		}
	}
}
PK�!]��8:8:!BackupEngine/Core/Domain/Init.phpnu&1i�<?php
/**
 * Akeeba Engine
 *
 * @package   akeebaengine
 * @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License version 3, or later
 *
 * 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, version 3.
 *
 * 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
 * <https://www.gnu.org/licenses/>.
 */

namespace Akeeba\Engine\Core\Domain;

defined('AKEEBAENGINE') || die();

use Akeeba\Engine\Base\Part;
use Akeeba\Engine\Factory;
use Akeeba\Engine\Platform;
use RuntimeException;

/**
 * Backup initialization domain
 */
final class Init extends Part
{
	/** @var   string  The backup description */
	private $description = '';

	/** @var   string  The backup comment */
	private $comment = '';

	/**
	 * Implements the constructor of the class
	 *
	 * @return  void
	 */
	public function __construct()
	{
		parent::__construct();

		Factory::getLog()->debug(__CLASS__ . " :: New instance");
	}

	/**
	 * Converts a PHP error to a string
	 *
	 * @return  string
	 */
	public static function error2string()
	{
		if (!function_exists('error_reporting'))
		{
			return "Not applicable; host too restrictive";
		}

		$value       = error_reporting();
		$level_names = [
			E_ERROR         => 'E_ERROR', E_WARNING => 'E_WARNING',
			E_PARSE         => 'E_PARSE', E_NOTICE => 'E_NOTICE',
			E_CORE_ERROR    => 'E_CORE_ERROR', E_CORE_WARNING => 'E_CORE_WARNING',
			E_COMPILE_ERROR => 'E_COMPILE_ERROR', E_COMPILE_WARNING => 'E_COMPILE_WARNING',
			E_USER_ERROR    => 'E_USER_ERROR', E_USER_WARNING => 'E_USER_WARNING',
			E_USER_NOTICE   => 'E_USER_NOTICE',
		];

		if (defined('E_STRICT'))
		{
			$level_names[E_STRICT] = 'E_STRICT';
		}

		$levels = [];

		if (($value & E_ALL) == E_ALL)
		{
			$levels[] = 'E_ALL';
			$value    &= ~E_ALL;
		}

		foreach ($level_names as $level => $name)
		{
			if (($value & $level) == $level)
			{
				$levels[] = $name;
			}
		}

		return implode(' | ', $levels);
	}

	/**
	 * Reports whether the error display (output to HTML) is enabled or not
	 *
	 * @return string
	 */
	public static function errordisplay()
	{
		if (!function_exists('ini_get'))
		{
			return "Not applicable; host too restrictive";
		}

		return ini_get('display_errors') ? 'on' : 'off';
	}

	/**
	 * Implements the _prepare abstract method
	 *
	 * @return  void
	 */
	protected function _prepare()
	{
		// Load parameters (description and comment)
		$jpskey   = '';
		$angiekey = '';

		if (!empty($this->_parametersArray))
		{
			$params = $this->_parametersArray;

			if (isset($params['description']))
			{
				$this->description = $params['description'];
			}

			if (isset($params['comment']))
			{
				$this->comment = $params['comment'];
			}

			if (isset($params['jpskey']))
			{
				$jpskey = $params['jpskey'];
			}

			if (isset($params['angiekey']))
			{
				$angiekey = $params['angiekey'];
			}
		}

		// Load configuration -- No. This is already done by the model. Doing it again removes all overrides.
		// Platform::getInstance()->load_configuration();

		// Initialize counters
		$registry = Factory::getConfiguration();

		if (!empty($jpskey))
		{
			$registry->set('engine.archiver.jps.key', $jpskey);
		}

		if (!empty($angiekey))
		{
			$registry->set('engine.installer.angie.key', $angiekey);
		}

		// Initialize temporary storage
		Factory::getFactoryStorage()->reset();

		// Force load the tag -- do not delete!
		$kettenrad = Factory::getKettenrad();
		$tag       = $kettenrad->getTag(); // Yes, this is an unused variable by we MUST run this method. DO NOT DELETE.

		// Push the comment and description in temp vars for use in the installer phase
		$registry->set('volatile.core.description', $this->description);
		$registry->set('volatile.core.comment', $this->comment);

		$this->setState(self::STATE_PREPARED);
	}

	/**
	 * Implements the _run() abstract method
	 *
	 * @return  void
	 */
	protected function _run()
	{
		if ($this->getState() == self::STATE_POSTRUN)
		{
			Factory::getLog()->debug(__CLASS__ . " :: Already finished");
			$this->setStep('');
			$this->setSubstep('');

			return;
		}
		else
		{
			$this->setState(self::STATE_RUNNING);
		}

		// Initialise the extra notes variable, used by platform classes to return warnings and errors
		$extraNotes = null;

		// Load the version defines
		Platform::getInstance()->load_version_defines();

		$registry = Factory::getConfiguration();

		// Write log file's header
		$version = defined('AKEEBABACKUP_VERSION') ? AKEEBABACKUP_VERSION : AKEEBA_VERSION;
		$date    = defined('AKEEBABACKUP_DATE') ? AKEEBABACKUP_DATE : AKEEBA_DATE;

		Factory::getLog()->info("--------------------------------------------------------------------------------");
		Factory::getLog()->info("Akeeba Backup " . $version . ' (' . $date . ')');
		Factory::getLog()->info("--------------------------------------------------------------------------------");

		// PHP configuration variables are tried to be logged only for debug and info log levels
		if ($registry->get('akeeba.basic.log_level') >= 2)
		{
			Factory::getLog()->info("--- System Information ---");
			Factory::getLog()->info("PHP Version        :" . PHP_VERSION);
			Factory::getLog()->info("PHP OS             :" . PHP_OS);
			Factory::getLog()->info("PHP SAPI           :" . PHP_SAPI);

			if (function_exists('php_uname'))
			{
				Factory::getLog()->info("OS Version         :" . php_uname('s'));
			}

			$db = Factory::getDatabase();
			Factory::getLog()->info("DB Version         :" . $db->getVersion());

			if (isset($_SERVER['SERVER_SOFTWARE']))
			{
				$server = $_SERVER['SERVER_SOFTWARE'];
			}
			elseif (($sf = getenv('SERVER_SOFTWARE')))
			{
				$server = $sf;
			}
			else
			{
				$server = 'n/a';
			}

			Factory::getLog()->info("Web Server         :" . $server);

			$platform     = 'Unknown platform';
			$version      = '(unknown version)';
			$platformData = Platform::getInstance()->getPlatformVersion();
			Factory::getLog()->info($platformData['name'] . " version    :" . $platformData['version']);

			if (isset($_SERVER['HTTP_USER_AGENT']))
			{
				Factory::getLog()->info("User agent         :" . $_SERVER['HTTP_USER_AGENT']);
			}

			Factory::getLog()->info("Safe mode          :" . ini_get("safe_mode"));
			Factory::getLog()->info("Display errors     :" . ini_get("display_errors"));
			Factory::getLog()->info("Error reporting    :" . self::error2string());
			Factory::getLog()->info("Error display      :" . self::errordisplay());
			Factory::getLog()->info("Disabled functions :" . ini_get("disable_functions"));
			Factory::getLog()->info("open_basedir restr.:" . ini_get('open_basedir'));
			Factory::getLog()->info("Max. exec. time    :" . ini_get("max_execution_time"));
			Factory::getLog()->info("Memory limit       :" . ini_get("memory_limit"));

			if (function_exists("memory_get_usage"))
			{
				Factory::getLog()->info("Current mem. usage :" . memory_get_usage());
			}

			if (function_exists("gzcompress"))
			{
				Factory::getLog()->info("GZIP Compression   : available (good)");
			}
			else
			{
				Factory::getLog()->info("GZIP Compression   : n/a (no compression)");
			}

			$extraNotes = Platform::getInstance()->log_platform_special_directories();

			if (!empty($extraNotes) && is_array($extraNotes))
			{
				if (isset($extraNotes['warnings']) && is_array($extraNotes['warnings']))
				{
					foreach ($extraNotes['warnings'] as $warning)
					{
						Factory::getLog()->warning($warning);
					}
				}

				if (isset($extraNotes['errors']) && is_array($extraNotes['errors']))
				{
					foreach ($extraNotes['errors'] as $error)
					{
						Factory::getLog()->error($error);
					}

					if (!empty($extraNotes['errors']))
					{
						throw new RuntimeException($extraNotes['errors'][0]);
					}
				}
			}

			$min_time = $registry->get('akeeba.tuning.min_exec_time');
			$max_time = $registry->get('akeeba.tuning.max_exec_time');
			$bias     = $registry->get('akeeba.tuning.run_time_bias');

			Factory::getLog()->info("Min/Max/Bias       :" . $min_time . '/' . $max_time . '/' . $bias);
			Factory::getLog()->info("Output directory   :" . $registry->get('akeeba.basic.output_directory'), ['root_translate' => false]);
			Factory::getLog()->info("Part size (bytes)  :" . $registry->get('engine.archiver.common.part_size', 0));
			Factory::getLog()->info("--------------------------------------------------------------------------------");
		}

		// Quirks reporting
		$quirks = Factory::getConfigurationChecks()->getDetailedStatus(true);

		if (!empty($quirks))
		{
			Factory::getLog()->info("Akeeba Backup has detected the following potential problems:");

			foreach ($quirks as $q)
			{
				Factory::getLog()->info('- ' . $q['code'] . ' ' . $q['description'] . ' (' . $q['severity'] . ')');
			}

			Factory::getLog()->info("You probably do not have to worry about them, but you should be aware of them.");
			Factory::getLog()->info("--------------------------------------------------------------------------------");
		}

		$phpVersion = PHP_VERSION;

		if (version_compare($phpVersion, '7.3.0', 'lt'))
		{
			Factory::getLog()->warning("You are using PHP $phpVersion which is officially End of Life. We recommend using PHP 7.4 or later for best results. Your version of PHP, $phpVersion, will stop being supported by this backup software in the future.");
		}

		// Report profile ID
		$profile_id = Platform::getInstance()->get_active_profile();
		Factory::getLog()->info("Loaded profile #$profile_id");

		// Get archive name
		[$relativeArchiveName, $absoluteArchiveName] = $this->getArchiveName();

		// ==== Stats initialisation ===
		$origin     = Platform::getInstance()->get_backup_origin(); // Get backup origin
		$profile_id = Platform::getInstance()->get_active_profile(); // Get active profile

		$registry   = Factory::getConfiguration();
		$backupType = $registry->get('akeeba.basic.backup_type');
		Factory::getLog()->debug("Backup type is now set to '" . $backupType . "'");

		// Substitute "variables" in the archive name
		$fsUtils     = Factory::getFilesystemTools();
		$description = $fsUtils->replace_archive_name_variables($this->description);
		$comment     = $fsUtils->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 = Factory::getKettenrad();

		$temp = [
			'description'   => $description,
			'comment'       => $comment,
			'backupstart'   => Platform::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(),
			'backupid'      => $kettenrad->getBackupId(),
		];

		// Save the entry
		$statistics = Factory::getStatistics();
		$statistics->setStatistics($temp);
		$statistics->release_multipart_lock();

		// Initialize the archive.
		if (Factory::getEngineParamsProvider()->getScriptingParameter('core.createarchive', true))
		{
			Factory::getLog()->debug("Expanded archive file name: " . $absoluteArchiveName);

			Factory::getLog()->debug("Initializing archiver engine");
			$archiver = Factory::getArchiverEngine();
			$archiver->initialize($absoluteArchiveName);
			$archiver->setComment($comment); // Add the comment to the archive itself.
		}

		$this->setState(self::STATE_POSTRUN);
	}

	/**
	 * Implements the abstract _finalize method
	 *
	 * @return  void
	 */
	protected function _finalize()
	{
		$this->setState(self::STATE_FINISHED);
	}

	/**
	 * Returns the relative and absolute path to the archive
	 */
	protected function getArchiveName()
	{
		$registry = Factory::getConfiguration();

		// Import volatile scripting keys to the registry
		Factory::getEngineParamsProvider()->importScriptingToRegistry();

		// Determine the extension
		$force_extension = Factory::getEngineParamsProvider()->getScriptingParameter('core.forceextension', null);

		if (is_null($force_extension))
		{
			$archiver  = Factory::getArchiverEngine();
			$extension = $archiver->getExtension();
		}
		else
		{
			$extension = $force_extension;
		}

		// Get the template name
		$templateName = $registry->get('akeeba.basic.archive_name');
		Factory::getLog()->debug("Archive template name: $templateName");

		/**
		 * Security: Protect archives in the default backup output directory
		 *
		 * If the configured backup output directory is the same as the default backup output directory the following
		 * actions are taken:
		 *
		 * 1. The backup archive name must include [RANDOM]. If it doesn't, '-[RANDOM]' will be appended to it.
		 * 2. We make sure that the direct web access blocking files .htaccess, web.config, index.html, index.htm and
		 *    index.php exist in that directory. If they do not they will be forcibly added.
		 */
		$configuredOutputPath = $registry->get('akeeba.basic.output_directory');
		$stockDirs            = Platform::getInstance()->get_stock_directories();
		$defaultOutputPath    = $stockDirs['[DEFAULT_OUTPUT]'];
		$fsUtils              = Factory::getFilesystemTools();

		if (@realpath($configuredOutputPath) === @realpath($defaultOutputPath))
		{
			$this->ensureHasRandom($templateName);

			$fsUtils->ensureNoAccess($defaultOutputPath);
		}

		// Parse all tags
		$fsUtils      = Factory::getFilesystemTools();
		$templateName = $fsUtils->replace_archive_name_variables($templateName);

		Factory::getLog()->debug("Expanded template name: $templateName");

		$relative_path = $templateName . $extension;
		$absolute_path = $fsUtils->TranslateWinPath($configuredOutputPath . DIRECTORY_SEPARATOR . $relative_path);

		return [$relative_path, $absolute_path];
	}

	/**
	 * Make sure that the archive template name contains the [RANDOM] variable.
	 *
	 * @param   string  $templateName
	 *
	 * @return void
	 */
	protected function ensureHasRandom(&$templateName)
	{
		if (strpos($templateName, '[RANDOM]') !== false)
		{
			return;
		}

		$templateName .= '-[RANDOM]';
	}
}
PK�!]��q!!&BackupEngine/Core/Domain/Installer.phpnu&1i�<?php
/**
 * Akeeba Engine
 *
 * @package   akeebaengine
 * @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License version 3, or later
 *
 * 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, version 3.
 *
 * 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
 * <https://www.gnu.org/licenses/>.
 */

namespace Akeeba\Engine\Core\Domain;

defined('AKEEBAENGINE') || die();

use Akeeba\Engine\Base\Part;
use Akeeba\Engine\Factory;
use Akeeba\Engine\Platform;
use Akeeba\Engine\Util\HashTrait;

/**
 * Installer deployment
 */
final class Installer extends Part
{
	use HashTrait;

	/** @var int Installer image file offset last read */
	private $offset;

	/** @var int How much installer data I have processed yet */
	private $runningSize = 0;

	/** @var int Installer image file index last read */
	private $xformIndex = 0;

	/** @var int Percentage of process done */
	private $progress = 0;

	/**
	 * Public constructor
	 *
	 * @return  void
	 */
	public function __construct()
	{
		parent::__construct();

		Factory::getLog()->debug(__CLASS__ . " :: New instance");
	}

	/**
	 * Implements the _prepare abstract method
	 *
	 */
	function _prepare()
	{
		$archive = Factory::getArchiverEngine();

		// Add the backup description and comment in a README.html file in the
		// installation directory. This makes it the first file in the archive.
		if (!empty($this->installerSettings->readme ?? ''))
		{
			$data = $this->createReadme();
			$archive->addFileVirtual('README.html', $this->installerSettings->installerroot, $data);
		}

		if (!empty($this->installerSettings->extrainfo ?? ''))
		{
			$data = $this->createExtrainfo();
			$archive->addFileVirtual('extrainfo.json', $this->installerSettings->installerroot, $data);
		}

		if (!empty($this->installerSettings->password ?? ''))
		{
			$data = $this->createPasswordFile();

			if (!empty($data))
			{
				$archive->addFileVirtual('password.php', $this->installerSettings->installerroot, $data);
			}
		}

		$this->progress = 0;

		// Set our state to prepared
		$this->setState(self::STATE_PREPARED);
	}

	/**
	 * Implements the _run() abstract method
	 */
	function _run()
	{
		if ($this->getState() == self::STATE_POSTRUN)
		{
			Factory::getLog()->debug(__CLASS__ . " :: Already finished");
			$this->setStep('');
			$this->setSubstep('');
		}
		else
		{
			$this->setState(self::STATE_RUNNING);
		}

		// Try to step the archiver
		$archive = Factory::getArchiverEngine();
		$ret     = $archive->transformJPA($this->xformIndex, $this->offset);

		if ($ret !== false)
		{
			$this->offset     = $ret['offset'];
			$this->xformIndex = $ret['index'];
			$this->setStep($ret['filename']);
		}

		// Check for completion
		if ($ret['done'])
		{
			Factory::getLog()->debug(__CLASS__ . ":: archive is initialized");
			$this->setState(self::STATE_FINISHED);
		}

		// Calculate percentage
		$this->runningSize += $ret['chunkProcessed'] ?? 0;

		if ($ret['filesize'] > 0)
		{
			$this->progress = $this->runningSize / $ret['filesize'];
		}
	}

	/**
	 * Implements the _finalize() abstract method
	 *
	 */
	function _finalize()
	{
		$this->setState(self::STATE_FINISHED);
		$this->progress = 1;
	}

	/**
	 * Implements the progress calculation based on how much of the installer image
	 * archive we have processed so far.
	 */
	public function getProgress()
	{
		return $this->progress;
	}

	/**
	 * Creates the contents of an HTML file with the description and comment of
	 * the backup. This file will be saved as README.html in the installer's root
	 * directory, as specified by the embedded installer's settings.
	 *
	 * @return string The contents of the HTML file.
	 */
	protected function createReadme()
	{
		$config = Factory::getConfiguration();

		$version = defined('AKEEBABACKUP_VERSION') ? AKEEBABACKUP_VERSION : AKEEBA_VERSION;
		$date    = defined('AKEEBABACKUP_DATE') ? AKEEBABACKUP_DATE : AKEEBA_DATE;
		$pro     = defined('AKEEBABACKUP_PRO') ? AKEEBABACKUP_PRO : AKEEBA_PRO;

		$lbl_version   = $version . ' (' . $date . ')';
		$lbl_coreorpro = ($pro == 1) ? 'Professional' : 'Core';

		$description = $config->get('volatile.core.description', '');
		$comment     = $config->get('volatile.core.comment', '');

		$config->set('volatile.core.description', null);
		$config->set('volatile.core.comment', null);

		return <<<ENDHTML
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
	<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
	<title>Akeeba Backup Archive Identity</title>
</head>
<body>
	<h1>Backup Description</h1>
	<p id="description"><![CDATA[$description]]></p>
	<h1>Backup Comment</h1>
	<div id="comment">
	$comment
	</div>
	<hr/>
	<p>
		Akeeba Backup $lbl_coreorpro $lbl_version
	</p>
</body>
</html>
ENDHTML;
	}

	protected function createExtrainfo()
	{
		$abversion  = defined('AKEEBABACKUP_VERSION') ? AKEEBABACKUP_VERSION : AKEEBA_VERSION;
		$host       = Platform::getInstance()->get_host();
		$backupdate = gmdate('Y-m-d H:i:s');
		$phpversion = PHP_VERSION;
		$rootPath   = Platform::getInstance()->get_site_root();

		$data = [
			'host'           => $host,
			'backup_date'    => $backupdate,
			'akeeba_version' => $abversion,
			'php_version'    => $phpversion,
			'root'           => $rootPath,
		];

		$platform = Platform::getInstance();

		try
		{
			$extraData = $platform->get_extra_info();
		}
		catch (\Exception $e)
		{
			// Not all platform classes implement get_extra_info(), therefore Platform will throw an Exception
			$extraData = [];
		}

		$data = array_merge($data, $extraData);

		$ret = json_encode($data, JSON_PRETTY_PRINT);

		return $ret;
	}

	protected function createPasswordFile()
	{
		$config = Factory::getConfiguration();
		$ret    = '';

		$password = $config->get('engine.installer.angie.key', '');

		if (empty($password))
		{
			return $ret;
		}

		$randVal = Factory::getRandval();

		$salt     = $randVal->generateString(32);
		$passhash = self::md5($password . $salt) . ':' . $salt;
		$ret      = "<?php\n";
		$ret      .= "define('AKEEBA_PASSHASH', '" . $passhash . "');\n";

		return $ret;
	}
}
PK�!]�-���;BackupEngine/Core/Domain/Finalizer/RemoveTemporaryFiles.phpnu&1i�<?php
/**
 * Akeeba Engine
 *
 * @package   akeebaengine
 * @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License version 3, or later
 *
 * 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, version 3.
 *
 * 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
 * <https://www.gnu.org/licenses/>.
 */

/**
 * @package     Akeeba\Engine\Core\Domain\Finalizer
 * @subpackage
 *
 * @copyright   A copyright
 * @license     A "Slug" license name e.g. GPL2
 */

namespace Akeeba\Engine\Core\Domain\Finalizer;

use Akeeba\Engine\Factory;

/**
 * Removes temporary files.
 *
 * @since       9.3.1
 * @package     Akeeba\Engine\Core\Domain\Finalizer
 *
 */
final class RemoveTemporaryFiles extends AbstractFinalizer
{
	/**
	 * @inheritDoc
	 */
	public function __invoke()
	{
		$this->setStep('Removing temporary files');
		$this->setSubstep('');
		Factory::getLog()->debug("Removing temporary files");
		Factory::getTempFiles()->deleteTempFiles();

		return true;
	}
}PK�!]���yy9BackupEngine/Core/Domain/Finalizer/FinalizerInterface.phpnu&1i�<?php
/**
 * Akeeba Engine
 *
 * @package   akeebaengine
 * @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License version 3, or later
 *
 * 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, version 3.
 *
 * 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
 * <https://www.gnu.org/licenses/>.
 */

/**
 * @package     Akeeba\Engine\Core\Domain\Finalizer
 * @subpackage
 *
 * @copyright   A copyright
 * @license     A "Slug" license name e.g. GPL2
 */

namespace Akeeba\Engine\Core\Domain\Finalizer;

use Akeeba\Engine\Core\Domain\Finalization;
use Exception;

/**
 * Interface to a finalizer invokable class.
 *
 * @since 9.3.1
 */
interface FinalizerInterface
{
	/**
	 * Public constructor
	 *
	 * @param   Finalization  $finalizationPart  The part we belong to.
	 *
	 * @since   9.3.1
	 */
	public function __construct(Finalization $finalizationPart);

	/**
	 * Executes the finalizer job. Returns true when done, false if it needs to run further.
	 *
	 * @return  bool  True if we are fully done. False if we must be called again.
	 * @throws  Exception  When an error occurs.
	 *
	 * @since   9.3.1
	 */
	public function __invoke();
}PK�!]�^�m�7�7>BackupEngine/Core/Domain/Finalizer/AbstractQuotaManagement.phpnu&1i�<?php
/**
 * Akeeba Engine
 *
 * @package   akeebaengine
 * @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License version 3, or later
 *
 * 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, version 3.
 *
 * 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
 * <https://www.gnu.org/licenses/>.
 */

/**
 * @package     Akeeba\Engine\Core\Domain\Finalizer
 * @subpackage
 *
 * @copyright   A copyright
 * @license     A "Slug" license name e.g. GPL2
 */

namespace Akeeba\Engine\Core\Domain\Finalizer;

use Akeeba\Engine\Factory;
use DateTime;
use Exception;

abstract class AbstractQuotaManagement extends AbstractFinalizer
{
	/**
	 * Abstraction for the engine configuration keys used in the concrete quota management class.
	 *
	 * @since 9.3.1
	 * @var   string[]
	 */
	protected $configKeys = [
		'maxAgeEnable' => 'akeeba.quota.maxage.enable',
		'maxAgeDays'   => 'akeeba.quota.maxage.maxdays',
		'maxAgeKeep'   => 'akeeba.quota.maxage.keepday',
		'countEnable'  => 'akeeba.quota.enable_count_quota',
		'countValue'   => 'akeeba.quota.count_quota',
		'sizeEnable'   => 'akeeba.quota.enable_size_quota',
		'sizeValue'    => 'akeeba.quota.size_quota',
	];

	/**
	 * The ID of the latest backup (the one we are running in right now)
	 *
	 * @since 9.3.1
	 * @var   int
	 */
	protected $latestBackupId;

	/**
	 * Human-readable quote type e.g. 'local', 'remote', etc. for the concrete quota management class.
	 *
	 * @since 9.3.1
	 * @var   string
	 */
	protected $quotaType = 'local';

	/**
	 * @inheritDoc
	 */
	public function __invoke()
	{
		$this->setStep(
			sprintf(
				'Applying %s quotas',
				$this->quotaType
			)
		);
		$this->setSubstep('');

		// If no quota settings are enabled, quit
		$configuration  = Factory::getConfiguration();
		$timer          = Factory::getTimer();
		$useDayQuotas   = $configuration->get($this->configKeys['maxAgeEnable']);
		$useCountQuotas = $configuration->get($this->configKeys['countEnable']);
		$useSizeQuotas  = $configuration->get($this->configKeys['sizeEnable']);

		if (!($useDayQuotas || $useCountQuotas || $useSizeQuotas))
		{
			Factory::getLog()->debug(
				sprintf(
					'No %s quotas were defined; old backup files will be kept intact',
					$this->quotaType
				)
			);

			return true;
		}

		// Get the latest backup ID
		$statistics           = Factory::getStatistics();
		$this->latestBackupId = $statistics->getId();

		// Try to load the calculated quotas from the volatile keys
		$keyIsCalculated    = sprintf('volatile.quotas.%s.calculated', $this->quotaType);
		$keyRemoveBackupIDs = sprintf('volatile.quotas.%s.removeBackupIDs', $this->quotaType);
		$keyRemoveLogPaths  = sprintf('volatile.quotas.%s.removeLogPaths', $this->quotaType);
		$keyFilesToRemove   = sprintf('volatile.quotas.%s.filesToRemove', $this->quotaType);

		$isCalculated    = $configuration->get($keyIsCalculated, false);
		$removeBackupIDs = $configuration->get($keyRemoveBackupIDs, []);
		$removeLogPaths  = $configuration->get($keyRemoveLogPaths, []);
		$filesToRemove   = $configuration->get($keyFilesToRemove, []);

		// Calculate the quotas if nothing was calculated just yet.
		if (!$isCalculated)
		{
			// Calculate the quotas. If nothing is found, return immediately.
			if ($this->calculateQuotas($allRecords, $removeBackupIDs, $removeLogPaths, $filesToRemove) === false)
			{
				return true;
			}

			$this->saveCalculatedQuotas($removeBackupIDs, $removeLogPaths, $filesToRemove);

			// Do I have enough time to process removals?
			if ($timer->getTimeLeft() <= 0)
			{
				return false;
			}
		}

		// Process a chunk of removals
		if (!$this->processRemovals($removeBackupIDs, $filesToRemove, $removeLogPaths))
		{
			$this->saveCalculatedQuotas($removeBackupIDs, $removeLogPaths, $filesToRemove);

			return false;
		}

		$removeBackupIDs = null;
		$removeLogPaths  = null;
		$filesToRemove   = null;

		$this->saveCalculatedQuotas($removeBackupIDs, $removeLogPaths, $filesToRemove);

		return true;
	}

	/**
	 * Get all the backup records to apply quotas on.
	 *
	 * @return  array
	 *
	 * @since   9.3.1
	 */
	abstract protected function getAllRecords(): array;

	/**
	 * Processes a list of records for removal. The removal DOES NOT take place here.
	 *
	 * @param   array  $allRecords       The records to process
	 * @param   array  $removeBackupIDs  Running tally of backup IDs to remove files from
	 * @param   array  $removeLogPaths   Running tally of log entries to remove
	 * @param   array  $ret              Running tally of arrays of files to remove
	 * @param   array  $leftover         Leftover records to be processed by the next quota rule
	 *
	 * @since   9.3.1
	 */
	protected function markAllRecordsForRemoval(array &$allRecords, array &$removeBackupIDs, array &$removeLogPaths, array &$ret, array &$leftover)
	{
		foreach ($allRecords as $def)
		{
			if ($def['id'] != $this->latestBackupId)
			{
				continue;
			}

			$temp       = array_pop($leftover);
			$leftover[] = $def;
			array_unshift($allRecords, $temp);

			break;
		}

		foreach ($allRecords as $def)
		{
			$ret[]             = $def['filenames'];
			$removeBackupIDs[] = $def['id'];

			if (empty($def['logname']))
			{
				continue;
			}

			$filePath = $def['absolute_path'];

			if (empty($filePath))
			{
				continue;
			}

			$logPath = dirname($filePath) . '/' . $def['logname'];

			if (@file_exists($logPath))
			{
				$removeLogPaths[] = $logPath;

				continue;
			}

			$altLogPath = substr($logPath, 0, -4);

			if (@file_exists($altLogPath))
			{
				/**
				 * Bad host: the log file akeeba.tag.log.php may not exist but the akeeba.tag.log file
				 * does. This code addresses this problem.
				 */
				$removeLogPaths[] = $altLogPath;
			}
		}
	}

	/**
	 * Performs the actual removal.
	 *
	 * @param   array  $removeBackupIDs  The backup IDs which will have their files removed
	 * @param   array  $filesToRemove    The flat list of files to remove
	 * @param   array  $removeLogPaths   The flat list of log paths to remove
	 *
	 * @return  bool  True if we are done, false to come back in the next step of the engine
	 * @throws  Exception
	 * @since   9.3.1
	 */
	abstract protected function processRemovals(array &$removeBackupIDs, array &$filesToRemove, array &$removeLogPaths): bool;

	/**
	 * Applies the Count Quotas.
	 *
	 * @param   array  $allRecords       All records left to process
	 * @param   array  $removeBackupIDs  Running tally of backup IDs to remove files from
	 * @param   array  $removeLogPaths   Running tally of log entries to remove
	 * @param   array  $ret              Running tally of arrays of files to remove
	 *
	 * @return  void
	 * @since   9.3.1
	 */
	private function applyCountQuotas(array &$allRecords, array &$removeBackupIDs, array &$removeLogPaths, array &$ret)
	{
		$configuration  = Factory::getConfiguration();
		$useCountQuotas = $configuration->get($this->configKeys['countEnable']);
		$countQuota     = $configuration->get($this->configKeys['countValue']);

		// Do we need to apply count quotas?
		if (!$useCountQuotas || !is_numeric($countQuota) || $countQuota <= 0)
		{
			return;
		}

		// We should only run a count quota if there are more files than the set limit
		if (count($allRecords) <= $countQuota)
		{
			return;
		}

		Factory::getLog()->debug(
			sprintf(
				'Processing %s count quotas',
				$this->quotaType
			)
		);

		/**
		 * Backups are sorted by reverse ID order, e.g. 200, 199, 198, 197, 196, 195, 194, 193, 192, 191, 190, 189, 188.
		 * I need to keep the first $countQuota records in $leftover and process the remaining records.
		 */
		$leftover   = array_slice($allRecords, 0, $countQuota);
		$allRecords = array_slice($allRecords, $countQuota);

		$this->markAllRecordsForRemoval($allRecords, $removeBackupIDs, $removeLogPaths, $ret, $leftover);

		$allRecords = $leftover;
	}

	/**
	 * Applies the Day-Based Quotas.
	 *
	 * @param   array  $allRecords       All records left to process
	 * @param   array  $removeBackupIDs  Running tally of backup IDs to remove files from
	 * @param   array  $removeLogPaths   Running tally of log entries to remove
	 * @param   array  $ret              Running tally of arrays of files to remove
	 *
	 * @return  void
	 * @since   9.3.1
	 */
	private function applyDayQuotas(array &$allRecords, array &$removeBackupIDs, array &$removeLogPaths, array &$ret): void
	{
		$configuration = Factory::getConfiguration();
		$daysQuota     = $configuration->get($this->configKeys['maxAgeDays']);
		$preserveDay   = $configuration->get($this->configKeys['maxAgeKeep']);
		$leftover      = [];

		$killDatetime = new DateTime();
		$killDatetime->modify('-' . $daysQuota . ($daysQuota == 1 ? ' day' : ' days'));
		$killTS = $killDatetime->format('U');

		/**
		 * Move the following kind of records FROM allRecords TO leftover:
		 * - Current backup record
		 * - Backups on a preserve day
		 * - Backups newer than the earliest removal date
		 */
		$allRecords = array_filter(
			$allRecords,
			function (array $def) use ($killDatetime, $killTS, $preserveDay, &$leftover): bool {
				if ($def['id'] == $this->latestBackupId)
				{
					$leftover[] = $def;

					return false;
				}

				// Is this on a preserve day?
				if ($preserveDay > 0 && $def['day'] == $preserveDay)
				{
					$leftover[] = $def;

					return false;
				}

				// Otherwise, check the timestamp
				if ($def['backupstart'] >= $killTS)
				{
					$leftover[] = $def;

					return false;
				}

				return true;
			}
		);

		$this->markAllRecordsForRemoval($allRecords, $removeBackupIDs, $removeLogPaths, $ret, $leftover);

		$allRecords = $leftover;
	}

	/**
	 * Applies the Maximum Size Quotas.
	 *
	 * @param   array  $allRecords       All records left to process
	 * @param   array  $removeBackupIDs  Running tally of backup IDs to remove files from
	 * @param   array  $removeLogPaths   Running tally of log entries to remove
	 * @param   array  $ret              Running tally of arrays of files to remove
	 *
	 * @return  void
	 * @since   9.3.1
	 */
	private function applySizeQuotas(array &$allRecords, array &$removeBackupIDs, array &$removeLogPaths, array &$ret)
	{
		$configuration = Factory::getConfiguration();
		$useSizeQuotas = $configuration->get($this->configKeys['sizeEnable']);
		$sizeQuota     = $configuration->get($this->configKeys['sizeValue']);

		// Do we need to apply size quotas?
		if (!$useSizeQuotas || !is_numeric($sizeQuota) || $sizeQuota <= 0 || count($allRecords) <= 0)
		{
			return;
		}

		Factory::getLog()->debug(
			sprintf(
				'Processing %s size quotas',
				$this->quotaType
			)
		);

		// First I will find how many elements of the array I need to get to the $sizeQuota size.
		$runningSize     = 0;
		$numberOfRecords = 0;

		foreach ($allRecords as $def)
		{
			$numberOfRecords++;

			if ($def['id'] == $this->latestBackupId)
			{
				continue;
			}

			$runningSize += $def['size'];

			if ($runningSize >= $sizeQuota)
			{
				break;
			}
		}

		$leftover   = array_slice($allRecords, 0, $numberOfRecords);
		$allRecords = array_slice($allRecords, $numberOfRecords);

		$this->markAllRecordsForRemoval($allRecords, $removeBackupIDs, $removeLogPaths, $ret, $leftover);

		$allRecords = $leftover;
	}

	private function calculateQuotas(&$allRecords, &$removeBackupIDs, &$removeLogPaths, &$filesToRemove): bool
	{
		$configuration = Factory::getConfiguration();
		$useDayQuotas  = $configuration->get($this->configKeys['maxAgeEnable']);

		$allRecords = $this->getAllRecords();

		// If there are no files, exit early
		if (count($allRecords) == 0)
		{
			Factory::getLog()->debug(
				sprintf(
					'There were no old backup records to apply %s quotas on',
					$this->quotaType
				)
			);

			return false;
		}

		// Init arrays
		$removeBackupIDs = [];
		$removeLogPaths  = [];
		$ret             = [];

		// Do we need to apply maximum backup age quotas?
		if ($useDayQuotas)
		{
			$this->applyDayQuotas($allRecords, $removeBackupIDs, $removeLogPaths, $ret);
		}
		else
		{
			$this->applyCountQuotas($allRecords, $removeBackupIDs, $removeLogPaths, $ret);
			$this->applySizeQuotas($allRecords, $removeBackupIDs, $removeLogPaths, $ret);
		}

		// Convert the $ret 2-dimensional array to single dimensional
		$filesToRemove = [];

		foreach ($ret as $temp)
		{
			$filesToRemove = array_merge($filesToRemove ?? [], $temp);
		}

		return true;
	}

	/**
	 * Save the calculated quotas into the volatile storage
	 *
	 * @param   array|null  $removeBackupIDs  Running tally of backup IDs to remove files from
	 * @param   array|null  $removeLogPaths   Running tally of log entries to remove
	 * @param   array|null  $filesToRemove    The flat list of files to remove
	 *
	 * @return  void
	 * @since   9.3.1
	 */
	private function saveCalculatedQuotas(?array &$removeBackupIDs, ?array &$removeLogPaths, ?array &$filesToRemove): void
	{
		$configuration      = Factory::getConfiguration();
		$keyIsCalculated    = sprintf('volatile.quotas.%s.calculated', $this->quotaType);
		$keyRemoveBackupIDs = sprintf('volatile.quotas.%s.removeBackupIDs', $this->quotaType);
		$keyRemoveLogPaths  = sprintf('volatile.quotas.%s.removeLogPaths', $this->quotaType);
		$keyFilesToRemove   = sprintf('volatile.quotas.%s.filesToRemove', $this->quotaType);

		$isCalculated = $removeBackupIDs !== null || $removeLogPaths !== null || $filesToRemove !== null;

		if ($isCalculated)
		{
			$configuration->set($keyIsCalculated, true);
			$configuration->set($keyRemoveBackupIDs, $removeBackupIDs);
			$configuration->set($keyRemoveLogPaths, $removeLogPaths);
			$configuration->set($keyFilesToRemove, $filesToRemove);

			return;
		}

		$configuration->remove($keyIsCalculated);
		$configuration->remove($keyRemoveBackupIDs);
		$configuration->remove($keyRemoveLogPaths);
		$configuration->remove($keyFilesToRemove);
	}
}PK�!]\#Co^^3BackupEngine/Core/Domain/Finalizer/RemoteQuotas.phpnu&1i�<?php
/**
 * Akeeba Engine
 *
 * @package   akeebaengine
 * @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License version 3, or later
 *
 * 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, version 3.
 *
 * 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
 * <https://www.gnu.org/licenses/>.
 */

/**
 * @package     Akeeba\Engine\Core\Domain\Finalizer
 * @subpackage
 *
 * @copyright   A copyright
 * @license     A "Slug" license name e.g. GPL2
 */

namespace Akeeba\Engine\Core\Domain\Finalizer;

use Akeeba\Engine\Core\Domain\Finalization;
use Akeeba\Engine\Factory;
use Akeeba\Engine\Platform;
use DateTime;
use Exception;

class RemoteQuotas extends AbstractQuotaManagement
{
	/** @inheritDoc */
	public function __construct(Finalization $finalizationPart)
	{
		$this->quotaType = 'remote';

		$this->configKeys = [
			'maxAgeEnable' => 'akeeba.quota.remotely.maxage.enable',
			'maxAgeDays'   => 'akeeba.quota.remotely.maxage.maxdays',
			'maxAgeKeep'   => 'akeeba.quota.remotely.maxage.keepday',
			'countEnable'  => 'akeeba.quota.remotely.enable_count_quota',
			'countValue'   => 'akeeba.quota.remotely.count_quota',
			'sizeEnable'   => 'akeeba.quota.remotely.enable_size_quota',
			'sizeValue'    => 'akeeba.quota.remotely.size_quota',
		];

		parent::__construct($finalizationPart);
	}

	/** @inheritDoc */
	protected function getAllRecords(): array
	{
		$configuration = Factory::getConfiguration();
		$useLatest = $configuration->get('akeeba.quota.remote_latest', '1') == 1;

		// Get all records with a remote filename and filter out the current record and frozen records
		$allRecords = array_filter(
			Platform::getInstance()->get_valid_remote_records() ?: [],
			function (array $stat) use ($useLatest): bool {
				// Exclude frozen records from quota management
				if (isset($stat['frozen']) && $stat['frozen'])
				{
					Factory::getLog()->debug(
						sprintf(
							'Excluding frozen backup id %d from %s quota management',
							$stat['id'],
							$this->quotaType
						)
					);

					return false;
				}

				// Exclude the current record from the remote quota management
				return $useLatest ? true : ($stat['id'] != $this->latestBackupId);
			}
		);

		// Convert stat records to entries used in quota management
		return array_map(
			function (array $stat): array {
				$remoteFilenames = $this->getRemoteFiles($stat['remote_filename'], $stat['multipart']);

				try
				{
					$backupStart = new DateTime($stat['backupstart']);
					$backupTS    = $backupStart->format('U');
					$backupDay   = $backupStart->format('d');
				}
				catch (Exception $e)
				{
					$backupTS  = 0;
					$backupDay = 0;
				}

				// Get the log file name
				$tag      = $stat['tag'] ?? 'backend';
				$backupId = $stat['backupid'] ?? '';
				$logName  = '';

				if (!empty($backupId))
				{
					$logName = 'akeeba.' . $tag . '.' . $backupId . '.log.php';
				}

				return [
					'id'            => $stat['id'],
					'filenames'     => $remoteFilenames,
					'size'          => $stat['total_size'],
					'backupstart'   => $backupTS,
					'day'           => $backupDay,
					'logname'       => $logName,
					'absolute_path' => $stat['absolute_path'],
				];
			},
			$allRecords
		);
	}

	/**
	 * Performs the actual removal.
	 *
	 * @param   array  $removeBackupIDs  The backup IDs which will have their files removed
	 * @param   array  $filesToRemove    The flat list of files to remove
	 * @param   array  $removeLogPaths   The flat list of log paths to remove
	 *
	 * @return  bool  True if we are done, false to come back in the next step of the engine
	 * @throws  Exception
	 * @since   9.3.1
	 */
	protected function processRemovals(array &$removeBackupIDs, array &$filesToRemove, array &$removeLogPaths): bool
	{
		$timer = Factory::getTimer();

		// Update the statistics record with the removed remote files
		if (!empty($removeBackupIDs))
		{
			Factory::getLog()->debug(
				sprintf(
					'Applying %s quotas: updating backup records',
					$this->quotaType
				)
			);
		}

		while (!empty($removeBackupIDs) && $timer->getTimeLeft() > 0)
		{
			$id   = array_shift($removeBackupIDs);
			$data = ['remote_filename' => ''];

			Platform::getInstance()->set_or_update_statistics($id, $data);
		}

		// Check if I have enough time
		if ($timer->getTimeLeft() <= 0)
		{
			return false;
		}

		// Apply quotas upon backup records
		if (!empty($filesToRemove) > 0)
		{
			Factory::getLog()->debug(
				sprintf(
					'Applying %s quotas: removing backup archives',
					$this->quotaType
				)
			);
		}

		while (!empty($filesToRemove) && $timer->getTimeLeft() > 0)
		{
			$filename = array_shift($filesToRemove);
			[$engineName, $path] = explode('://', $filename);
			$engine = Factory::getPostprocEngine($engineName);

			if (!$engine->supportsDelete())
			{
				continue;
			}

			Factory::getLog()->debug(
				sprintf(
					'Removing remotely stored file %s',
					$filename
				)
			);

			try
			{
				$engine->delete($path);
			}
			catch (Exception $e)
			{
				Factory::getLog()->debug(
					sprintf(
						'Could not remove remotely stored file. Error: %s',
						$e->getMessage()
					)
				);
			}
		}

		// Check if I have enough time
		if ($timer->getTimeLeft() <= 0)
		{
			return false;
		}

		// Apply quotas to log files
		if (!empty($removeLogPaths))
		{
			Factory::getLog()->debug(
				sprintf(
					'Applying %s quotas: removing obsolete log files',
					$this->quotaType
				)
			);
			Factory::getLog()->debug('Removing obsolete log files');
		}

		while (!empty($removeLogPaths) && $timer->getTimeLeft() > 0)
		{
			$logPath = array_shift($removeLogPaths);

			if (@Platform::getInstance()->unlink($logPath))
			{
				continue;
			}

			Factory::getLog()->debug(
				sprintf(
					'Failed to remove old log file %s',
					$logPath
				)
			);
		}

		// Check if I have enough time
		if ($timer->getTimeLeft() <= 0)
		{
			return false;
		}

		return true;
	}

	/**
	 * Get the full paths to all remote backup parts
	 *
	 * @param   string  $filename   The full filename of the last part stored in the database
	 * @param   int     $multipart  How many parts does this archive consist of?
	 *
	 * @return  array  A list of the full paths of all remotely stored backup archive parts
	 * @since   9.3.1
	 */
	private function getRemoteFiles(string $filename, int $multipart): array
	{
		$result = [];

		$extension       = substr($filename, -3);
		$base            = substr($filename, 0, -4);
		$extensionPrefix = substr($extension, 0, 1);
		$result[]        = $filename;

		if ($multipart <= 1)
		{
			return $result;
		}

		for ($i = 1; $i < $multipart; $i++)
		{
			$newExt   = $extensionPrefix . sprintf('%02u', $i);
			$result[] = $base . '.' . $newExt;
		}

		return $result;
	}

}PK�!]8�.M��2BackupEngine/Core/Domain/Finalizer/LocalQuotas.phpnu&1i�<?php
/**
 * Akeeba Engine
 *
 * @package   akeebaengine
 * @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License version 3, or later
 *
 * 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, version 3.
 *
 * 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
 * <https://www.gnu.org/licenses/>.
 */

/**
 * @package     Akeeba\Engine\Core\Domain\Finalizer
 * @subpackage
 *
 * @copyright   A copyright
 * @license     A "Slug" license name e.g. GPL2
 */

namespace Akeeba\Engine\Core\Domain\Finalizer;

use Akeeba\Engine\Factory;
use Akeeba\Engine\Platform;
use DateTime;
use Exception;

final class LocalQuotas extends AbstractQuotaManagement
{

	/**
	 * Get all the backup records to apply quotas on.
	 *
	 * @return  array
	 *
	 * @since   9.3.1
	 */
	protected function getAllRecords(): array
	{
		// Get valid-looking backup ID's
		$validIDs = Platform::getInstance()->get_valid_backup_records(true) ?: [];

		// Create a list of valid files
		$allFiles   = [];
		$statistics = Factory::getStatistics();

		foreach ($validIDs as $id)
		{
			$stat = Platform::getInstance()->get_statistics($id);

			// Exclude frozen record from quota management
			if (isset($stat['frozen']) && $stat['frozen'])
			{
				Factory::getLog()->debug(
					sprintf(
						'Excluding frozen backup id %d from %s quota management',
						$id,
						$this->quotaType
					)
				);
				continue;
			}

			try
			{
				$backupstart = new DateTime($stat['backupstart']);
				$backupTS    = $backupstart->format('U');
				$backupDay   = $backupstart->format('d');
			}
			catch (Exception $e)
			{
				$backupTS  = 0;
				$backupDay = 0;
			}

			// Get the log file name
			$tag      = $stat['tag'];
			$backupId = $stat['backupid'] ?? '';
			$logName  = '';

			if (!empty($backupId))
			{
				$logName = 'akeeba.' . $tag . '.' . $backupId . '.log.php';
			}

			// Multipart processing
			$filenames = $statistics->get_all_filenames($stat, true);

			// Only process existing files
			if (is_null($filenames))
			{
				continue;
			}

			$filesize = 0;

			foreach ($filenames as $filename)
			{
				$filesize += @filesize($filename);
			}

			$allFiles[] = [
				'id'            => $id,
				'filenames'     => $filenames,
				'size'          => $filesize,
				'backupstart'   => $backupTS,
				'day'           => $backupDay,
				'logname'       => $logName,
				'absolute_path' => $stat['absolute_path'],
			];
		}

		return $allFiles;
	}

	/**
	 * Performs the actual removal.
	 *
	 * @param   array  $removeBackupIDs  The backup IDs which will have their files removed
	 * @param   array  $filesToRemove    The flat list of files to remove
	 * @param   array  $removeLogPaths   The flat list of log paths to remove
	 *
	 * @return  bool  True if we are done, false to come back in the next step of the engine
	 * @throws  Exception
	 * @since   9.3.1
	 */
	protected function processRemovals(array &$removeBackupIDs, array &$filesToRemove, array &$removeLogPaths): bool
	{
		$timer = Factory::getTimer();

		// Update the statistics record with the removed remote files
		if (!empty($removeBackupIDs))
		{
			Factory::getLog()->debug(
				sprintf(
					'Applying %s quotas: updating backup records',
					$this->quotaType
				)
			);
		}

		while (!empty($removeBackupIDs) && $timer->getTimeLeft() > 0)
		{
			$id   = array_shift($removeBackupIDs);
			$data = ['filesexist' => '0'];

			Platform::getInstance()->set_or_update_statistics($id, $data);
		}

		// Check if I have enough time
		if ($timer->getTimeLeft() <= 0)
		{
			return false;
		}

		// Apply quotas upon backup records
		if (!empty($filesToRemove) > 0)
		{
			Factory::getLog()->debug(
				sprintf(
					'Applying %s quotas: removing backup archives',
					$this->quotaType
				)
			);
		}

		while (!empty($filesToRemove) && $timer->getTimeLeft() > 0)
		{
			$file = array_shift($filesToRemove);

			if (@Platform::getInstance()->unlink($file))
			{
				continue;
			}

			Factory::getLog()->warning(
				sprintf(
					'Failed to remove old backup file %s',
					$file
				)
			);
		}

		// Check if I have enough time
		if ($timer->getTimeLeft() <= 0)
		{
			return false;
		}

		// Apply quotas to log files
		if (!empty($removeLogPaths))
		{
			Factory::getLog()->debug(
				sprintf(
					'Applying %s quotas: removing obsolete log files',
					$this->quotaType
				)
			);
			Factory::getLog()->debug('Removing obsolete log files');
		}

		while (!empty($removeLogPaths) && $timer->getTimeLeft() > 0)
		{
			$logPath = array_shift($removeLogPaths);

			if (@Platform::getInstance()->unlink($logPath))
			{
				continue;
			}

			Factory::getLog()->debug(
				sprintf(
					'Failed to remove old log file %s',
					$logPath
				)
			);
		}

		// Check if I have enough time
		if ($timer->getTimeLeft() <= 0)
		{
			return false;
		}

		return true;
	}
}PK�!]0���<BackupEngine/Core/Domain/Finalizer/ObsoleteRecordsQuotas.phpnu&1i�<?php
/**
 * Akeeba Engine
 *
 * @package   akeebaengine
 * @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License version 3, or later
 *
 * 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, version 3.
 *
 * 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
 * <https://www.gnu.org/licenses/>.
 */

/**
 * @package     Akeeba\Engine\Core\Domain\Finalizer
 * @subpackage
 *
 * @copyright   A copyright
 * @license     A "Slug" license name e.g. GPL2
 */

namespace Akeeba\Engine\Core\Domain\Finalizer;

use Akeeba\Engine\Factory;
use Akeeba\Engine\Platform;

/**
 * Keeps a maximum number of "obsolete" records
 *
 * @since       9.3.1
 * @package     Akeeba\Engine\Core\Domain\Finalizer
 */
final class ObsoleteRecordsQuotas extends AbstractFinalizer
{

	/**
	 * @inheritDoc
	 */
	public function __invoke()
	{
		$this->setStep('Applying quota limit on obsolete backup records');
		$this->setSubstep('');
		$registry = Factory::getConfiguration();
		$limit    = $registry->get('akeeba.quota.obsolete_quota', 0);
		$limit    = (int) $limit;

		if ($limit <= 0)
		{
			return true;
		}

		$platform   = Platform::getInstance();
		$statsTable = $platform->tableNameStats;
		$db         = Factory::getDatabase($platform->get_platform_database_options());
		$query      =
			(method_exists($db, 'createQuery') ? $db->createQuery() : $db->getQuery(true))
			   ->select([
				   $db->qn('id'),
				   $db->qn('tag'),
				   $db->qn('backupid'),
				   $db->qn('absolute_path'),
			   ])
			   ->from($db->qn($statsTable))
			   ->where($db->qn('profile_id') . ' = ' . $db->q($platform->get_active_profile()))
			   ->where($db->qn('status') . ' = ' . $db->q('complete'))
			   ->where($db->qn('filesexist') . '=' . $db->q('0'))
			   ->where(
				   '(' .
				   $db->qn('remote_filename') . '=' . $db->q('') . ' OR ' .
				   $db->qn('remote_filename') . ' IS NULL'
				   . ')'
			   )
			   ->order($db->qn('id') . ' DESC');

		$db->setQuery($query, $limit, 100000);
		$records = $db->loadAssocList();

		if (empty($records))
		{
			return true;
		}

		$array = [];

		// Delete backup-specific log files if they exist and add the IDs of the records to delete in the $array
		foreach ($records as $stat)
		{
			$array[] = $stat['id'];

			// We can't delete logs if there is no backup ID in the record
			if (!isset($stat['backupid']) || empty($stat['backupid']))
			{
				continue;
			}

			$logFileName = 'akeeba.' . $stat['tag'] . '.' . $stat['backupid'] . '.log.php';
			$logPath     = dirname($stat['absolute_path']) . '/' . $logFileName;

			if (@file_exists($logPath))
			{
				@unlink($logPath);
			}

			/**
			 * Transitional period: the log file akeeba.tag.log.php may not exist but the akeeba.tag.log does. This
			 * addresses this transition.
			 */
			$logPath = dirname($stat['absolute_path']) . '/' . substr($logFileName, 0, -4);

			if (@file_exists($logPath))
			{
				@unlink($logPath);
			}
		}

		$ids = [];

		foreach ($array as $id)
		{
			$ids[] = $db->q($id);
		}

		$ids = implode(',', $ids);

		$query = (method_exists($db, 'createQuery') ? $db->createQuery() : $db->getQuery(true))
		            ->delete($db->qn($statsTable))
		            ->where($db->qn('id') . " IN ($ids)");
		$db->setQuery($query);
		$db->query();

		return true;
	}
}PK�!]�O�n
n
6BackupEngine/Core/Domain/Finalizer/UpdateFileSizes.phpnu&1i�<?php
/**
 * Akeeba Engine
 *
 * @package   akeebaengine
 * @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License version 3, or later
 *
 * 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, version 3.
 *
 * 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
 * <https://www.gnu.org/licenses/>.
 */

/**
 * @package     Akeeba\Engine\Core\Domain\Finalizer
 * @subpackage
 *
 * @copyright   A copyright
 * @license     A "Slug" license name e.g. GPL2
 */

namespace Akeeba\Engine\Core\Domain\Finalizer;

use Akeeba\Engine\Factory;

/**
 * Updates the file sizes in the statistics records
 *
 * @since       9.3.1
 * @package     Akeeba\Engine\Core\Domain\Finalizer
 *
 */
final class UpdateFileSizes extends AbstractFinalizer
{
	/**
	 * @inheritDoc
	 */
	public function __invoke()
	{
		$this->setStep('Updating file sizes');
		$this->setSubstep('');
		Factory::getLog()->debug("Updating statistics with file sizes");

		// Fetch the stats record
		$statistics    = Factory::getStatistics();
		$configuration = Factory::getConfiguration();
		$record        = $statistics->getRecord();
		$filenames     = $statistics->get_all_filenames($record) ?: [];
		$filesize      = 0.0;

		// Calculate file sizes of files remaining on the server
		foreach ($filenames as $file)
		{
			$filesize += ((@filesize($file)) ?: 0) * 1.0;
		}

		// Get the part size in volatile storage, set from the immediate part uploading effected by the
		// "Process each part immediately" option, and add it to the total file size
		$config              = $configuration;
		$postProcImmediately = $config->get('engine.postproc.common.after_part', 0, false);
		$deleteAfter         = $config->get('engine.postproc.common.delete_after', 0, false);
		$postProcEngine      = $config->get('akeeba.advanced.postproc_engine', 'none');

		if ($postProcImmediately && $deleteAfter && ($postProcEngine != 'none'))
		{
			$filesize += $configuration->get('volatile.engine.archiver.totalsize', 0) ?: 0;
		}

		$data = [
			'total_size' => $filesize,
		];

		Factory::getLog()->debug("Total size of backup archive (in bytes): $filesize");

		$statistics->setStatistics($data);

		return true;
	}
}PK�!]�f�1�	�	8BackupEngine/Core/Domain/Finalizer/AbstractFinalizer.phpnu&1i�<?php
/**
 * Akeeba Engine
 *
 * @package   akeebaengine
 * @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License version 3, or later
 *
 * 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, version 3.
 *
 * 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
 * <https://www.gnu.org/licenses/>.
 */

/**
 * @package     Akeeba\Engine\Core\Domain\Finalizer
 * @subpackage
 *
 * @copyright   A copyright
 * @license     A "Slug" license name e.g. GPL2
 */

namespace Akeeba\Engine\Core\Domain\Finalizer;

use Akeeba\Engine\Core\Domain\Finalization;
use Akeeba\Engine\Psr\Log\LogLevel;

/**
 * Abstract implementation of a finalizer class
 *
 * @since       9.3.1
 * @package     Akeeba\Engine\Core\Domain\Finalizer
 */
abstract class AbstractFinalizer implements FinalizerInterface
{
	/**
	 * The part we belong to
	 *
	 * @since 9.3.1
	 * @var   Finalization
	 */
	private $finalizationPart;

	/**
	 * Public constructor
	 *
	 * @param   Finalization  $finalizationPart  The part we belong to.
	 *
	 * @since   9.3.1
	 */
	public function __construct(Finalization $finalizationPart)
	{
		$this->finalizationPart = $finalizationPart;
	}

	/**
	 * Relays an exception so it can be logged
	 *
	 * @param   \Throwable  $e
	 * @param   string      $logLevel
	 *
	 * @return  void
	 * @since   9.3.1
	 */
	protected function logErrorsFromException(\Throwable $e, string $logLevel = LogLevel::ERROR): void
	{
		$this->finalizationPart->relayException($e, $logLevel);
	}

	/**
	 * Relays the current step back to the parent finalization engine part
	 *
	 * @param   string  $step  The step name to set
	 *
	 * @return  void
	 * @since   9.3.1
	 */
	protected function setStep(string $step): void
	{
		$this->finalizationPart->relayStep($step);
	}

	/**
	 * Relays the current sub-step back to the parent finalization engine part
	 *
	 * @param   string  $substep  The sub-step name to set
	 *
	 * @return  void
	 * @since   9.3.1
	 */
	protected function setSubstep(string $substep): void
	{
		$this->finalizationPart->relaySubstep($substep);
	}
}PK�!]
���#�#9BackupEngine/Core/Domain/Finalizer/MailAdministrators.phpnu&1i�<?php
/**
 * Akeeba Engine
 *
 * @package   akeebaengine
 * @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License version 3, or later
 *
 * 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, version 3.
 *
 * 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
 * <https://www.gnu.org/licenses/>.
 */

/**
 * @package     Akeeba\Engine\Core\Domain\Finalizer
 * @subpackage
 *
 * @copyright   A copyright
 * @license     A "Slug" license name e.g. GPL2
 */

namespace Akeeba\Engine\Core\Domain\Finalizer;

use Akeeba\Engine\Factory;
use Akeeba\Engine\Platform;

/**
 * Sends an email to the site administrators on backup completion
 *
 * @since       9.3.1
 * @package     Akeeba\Engine\Core\Domain\Finalizer
 */
final class MailAdministrators extends AbstractFinalizer
{
	/**
	 * @inheritDoc
	 */
	public function __invoke()
	{
		$this->setStep('Processing emails to administrators');
		$this->setSubstep('');

		$platform = Platform::getInstance();

		// Skip email for back-end backups
		if ($platform->get_backup_origin() == 'backend')
		{
			return true;
		}

		// Is the feature enabled?
		if ($platform->get_platform_configuration_option('frontend_email_on_finish', 0) == 0)
		{
			return true;
		}

		Factory::getLog()->debug("Preparing to send e-mail to administrators");

        $emails = $this->getEmailAddresses();

		if (empty($emails))
		{
			Factory::getLog()->debug("No email recipients found! Skipping email.");

			return true;
		}

		Factory::getLog()->debug("Creating email subject and body");

		// Get the statistics
		$statistics    = Factory::getStatistics();
		$profileNumber = $platform->get_active_profile();

		$db    = Factory::getDatabase();
		$query = (method_exists($db, 'createQuery') ? $db->createQuery() : $db->getQuery(true))->select('1');
		$dummy = $db->setQuery($query)->loadResult();

		$profileName   = $platform->get_profile_name($profileNumber);
		$statsRecord   = $statistics->getRecord();
		$partsCount    = max(1, $statsRecord['multipart']);
		$allFilenames  = $this->getPartFilenames($statsRecord);
		$filesList     = implode(
			"\n", array_map(function ($file) {
				return "\t" . $file;
			}, $allFilenames)
		);
		$totalSize     = (int) ($statsRecord['total_size'] ?? 0);

		// Get the approximate part sizes and create a list of files and sizes
		$configuration     = Factory::getConfiguration();
		$partSize          = $configuration->get('engine.archiver.common.part_size', 0);
		$lastPartSize      = $totalSize - (($partsCount - 1) * $partSize);
		$partSizes         = array_fill(0, $partsCount - 1, $partSize);
		$partSizes[]       = $lastPartSize;
		$filesAndSizesList = implode(
			"\n",
			array_map(
				function ($file, $size) {
					return sprintf(
						"\t%s (approx. %s)",
						$file,
						$this->formatByteSize($size)
					);
				},
				$allFilenames,
				$partSizes
			)
		);

		// Determine the upload to remote storage status
		$remoteStatus   = '';
		$failedUpdate   = false;
		$postProcEngine = Factory::getConfiguration()->get('akeeba.advanced.postproc_engine');

		if (!empty($postProcEngine) && ($postProcEngine != 'none'))
		{
			$remoteStatus = $platform->translate('COM_AKEEBA_EMAIL_POSTPROCESSING_SUCCESS');

			if (empty($statsRecord['remote_filename']))
			{
				$failedUpdate = true;
				$remoteStatus = $platform->translate('COM_AKEEBA_EMAIL_POSTPROCESSING_FAILED');
			}
		}

		// Did the user ask to be emailed only on failed uploads but the upload has succeeded?
		if (
			!$failedUpdate
			&& ($platform->get_platform_configuration_option('frontend_email_when', 'always') == 'failedupload')
		)
		{
			return true;
		}

		// Fetch user's preferences
		$subject = trim($platform->get_platform_configuration_option('frontend_email_subject', ''));
		$body    = trim($platform->get_platform_configuration_option('frontend_email_body', ''));

		// Get a default subject or post-process a manually defined subject
		$subject = empty($subject)
			? $platform->translate('COM_AKEEBA_COMMON_EMAIL_SUBJECT_OK')
			: Factory::getFilesystemTools()->replace_archive_name_variables($subject);

		// Do we need a default body?
		if (empty($body))
		{
			$body = $platform->translate('COM_AKEEBA_COMMON_EMAIL_BODY_OK');
			$body .= "\n\n";
			$body .= sprintf(
				$platform->translate('COM_AKEEBA_COMMON_EMAIL_BODY_INFO'),
				$profileNumber,
				$partsCount
			);
			$body .= "\n\n";
			$body .= $filesAndSizesList;
		}
		else
		{
			// Post-process the body
			$body = Factory::getFilesystemTools()->replace_archive_name_variables($body);
			$body = str_replace('[PROFILENUMBER]', $profileNumber, $body);
			$body = str_replace('[PROFILENAME]', $profileName, $body);
			$body = str_replace('[PARTCOUNT]', $partsCount, $body);
			$body = str_replace('[FILELIST]', $filesList, $body);
			$body = str_replace('[FILESIZESLIST]', $filesAndSizesList, $body);
			$body = str_replace('[REMOTESTATUS]', $remoteStatus, $body);
			$body = str_replace('[TOTALSIZE]', $this->formatByteSize($totalSize), $body);
		}

		// Post-process the subject (support the [REMOTESTATUS] variable)
		$subject = str_replace('[REMOTESTATUS]', $remoteStatus, $subject);

		// Sometimes $body contains literal \n instead of newlines
		$body = str_replace('\\n', "\n", $body);

		foreach ($emails as $email)
		{
			Factory::getLog()->debug("Sending email to $email");
			try
			{
				$platform->send_email($email, $subject, $body);
			}
			catch (\Exception $e)
			{
				// Don't cry if we cannot send an email; just log it as a warning
				Factory::getLog()->warning(
					sprintf(
						'Cannot send email to ‘%s’. Error message: “%s”',
						$email,
						$e->getMessage()
					)
				);
			}
		}

		return true;
	}

	/**
	 * Returns a list of files' base names for the given backup statistics record
	 *
	 * @param   array  $statsRecord  The statistics record
	 *
	 * @return  array  List of file names
	 *
	 * @since   9.3.1
	 */
	private function getPartFilenames(array $statsRecord): array
	{
		$baseFile = basename(
			$statsRecord['absolute_path'] ?? $statsRecord['archivename'] ?? $statsRecord['remote_filename'] ?? ''
		);

		if (empty($baseFile))
		{
			return [];
		}

		$partsCount = max($statsRecord['multipart'] ?? 1, 1);

		if ($partsCount === 1)
		{
			return [$baseFile];
		}

		$ret       = [];
		$extension = substr($baseFile, strrpos($baseFile, '.'));
		$bareName  = basename($baseFile, $extension);

		for ($i = 1; $i < $partsCount; $i++)
		{
			$ret[] = $bareName . substr($extension, 0, 2) . sprintf('%02d', $i);
		}

		$ret[] = $baseFile;

		return $ret;
	}

	/**
	 * Formats a number of bytes in human-readable format
	 *
	 * @param   int|float  $size  The size in bytes to format, e.g. 8254862
	 *
	 * @return  string  The human-readable representation of the byte size, e.g. "7.87 Mb"
	 * @since   9.3.1
	 */
	private function formatByteSize($size): string
	{
		$unit = ['b', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB'];

		return @round($size / 1024 ** ($i = floor(log($size, 1024))), 2) . ' ' . $unit[$i];
	}

	/**
	 * Get the addresses to send emails to.
	 *
	 * @return  array
	 * @since   9.4.9
	 */
    private function getEmailAddresses(): array
    {
        $platform      = Platform::getInstance();
        $configuration = Factory::getConfiguration();

        // Check if we have a list of emails saved inside the profile
        $email = trim($configuration->get('akeeba.basic.email_recipients', '') ?? '');

        if (!empty($email))
        {
            Factory::getLog()->debug("Using list of emails stored inside profile configuration");

	        $list = array_map(
				function ($x) {
					return trim ($x ?? '');
				},
		        explode(',', $email)
	        );

	        $list = array_filter(
		        $list,
		        function ($x)
		        {
					return !empty($x);
		        }
	        );

			if (!empty($list))
			{
				return $list;
			}
        }

        $email = trim($platform->get_platform_configuration_option('frontend_email_address', '') ?? '');

        if (!empty($email))
        {
            Factory::getLog()->debug("Using pre-defined list of emails");

	        $list = array_map(
		        function ($x) {
			        return trim ($x ?? '');
		        },
		        explode(',', $email)
	        );

	        $list = array_filter(
		        $list,
		        function ($x)
		        {
			        return !empty($x);
		        }
	        );

	        if (!empty($list))
	        {
		        return $list;
	        }
        }

        Factory::getLog()->debug("Fetching list of site administrator emails");

        return $platform->get_administrator_emails();
    }
}PK�!]����	�	7BackupEngine/Core/Domain/Finalizer/UpdateStatistics.phpnu&1i�<?php
/**
 * Akeeba Engine
 *
 * @package   akeebaengine
 * @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License version 3, or later
 *
 * 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, version 3.
 *
 * 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
 * <https://www.gnu.org/licenses/>.
 */

/**
 * @package     Akeeba\Engine\Core\Domain\Finalizer
 * @subpackage
 *
 * @copyright   A copyright
 * @license     A "Slug" license name e.g. GPL2
 */

namespace Akeeba\Engine\Core\Domain\Finalizer;

use Akeeba\Engine\Factory;
use Akeeba\Engine\Platform;
use Exception;

/**
 * Updates the backup statistics record
 *
 * @since       9.3.1
 * @package     Akeeba\Engine\Core\Domain\Finalizer
 */
final class UpdateStatistics extends AbstractFinalizer
{
	/**
	 * @inheritDoc
	 */
	public function __invoke()
	{
		$this->setStep('Updating backup record information');
		$this->setSubstep('');

		Factory::getLog()->debug('Updating statistics');

		// We finished normally. Fetch the stats record
		$statistics = Factory::getStatistics();
		$registry   = Factory::getConfiguration();
		$data       = [
			'backupend' => Platform::getInstance()->get_timestamp_database(),
			'status'    => 'complete',
			'multipart' => $registry->get('volatile.statistics.multipart', 0),
		];

		try
		{
			$result = $statistics->setStatistics($data);
		}
		catch (Exception $e)
		{
			$result = false;
		}

		if ($result === false)
		{
			// Most likely a "MySQL has gone away" issue...
			$configuration = Factory::getConfiguration();
			$configuration->set('volatile.breakflag', true);

			return false;
		}

		/**
		 * We could have handled it in $data above. However, if the schema has not been updated this function will
		 * continue failing infinitely, causing the backup to never end.
		 */
		$statistics->updateInStep(false);

		$stat = (object) $statistics->getRecord();
		Platform::getInstance()->remove_duplicate_backup_records($stat->archivename);

		return true;
	}
}PK�!]��V-�&�&5BackupEngine/Core/Domain/Finalizer/PostProcessing.phpnu&1i�<?php
/**
 * Akeeba Engine
 *
 * @package   akeebaengine
 * @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License version 3, or later
 *
 * 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, version 3.
 *
 * 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
 * <https://www.gnu.org/licenses/>.
 */

/**
 * @package     Akeeba\Engine\Core\Domain\Finalizer
 * @subpackage
 *
 * @copyright   A copyright
 * @license     A "Slug" license name e.g. GPL2
 */

namespace Akeeba\Engine\Core\Domain\Finalizer;

use Akeeba\Engine\Base\Exceptions\ErrorException;
use Akeeba\Engine\Factory;
use Akeeba\Engine\Platform;
use Akeeba\Engine\Postproc\Base;
use Akeeba\Engine\Postproc\PostProcInterface;
use Exception;
use Akeeba\Engine\Psr\Log\LogLevel;

/**
 * Performs any necessary post-processing (remote file uploading) still pending.
 *
 * @since       9.3.1
 * @package     Akeeba\Engine\Core\Domain\Finalizer
 *
 */
final class PostProcessing extends AbstractFinalizer
{
	/** @var array A list of all backup parts to process */
	private $backupParts = [];

	/** @var int The backup part we are currently processing */
	private $backupPartsIndex = -1;

	/** @var int How many finalisation substeps I have already done */
	private $subStepsDone = 0;

	/** @var int How many finalisation substeps I have in total */
	private $subStepsTotal = 0;

	/**
	 * @inheritDoc
	 */
	public function __invoke()
	{
		$this->setStep('Post-processing');
		$this->setSubstep('');

		// Do not run if the archive engine doesn't produce archives
		$configuration       = Factory::getConfiguration();
		$engineName          = $configuration->get('akeeba.advanced.postproc_engine');
		$shouldAbortOnFailed = $configuration->get('engine.postproc.common.abort_on_fail', false);

		Factory::getLog()->debug("Loading post-processing engine object ($engineName)");

		$postProcEngine = Factory::getPostprocEngine($engineName);

		if (!is_object($postProcEngine) || !($postProcEngine instanceof Base))
		{
			Factory::getLog()->debug(
				sprintf(
					'Post-processing engine “%s” not found.',
					$engineName
				)
			);
			Factory::getLog()->debug("The post-processing engine has either been removed or you are trying to use a profile created with the Professional version of the backup software in the Core version which doesn't have this post-processing engine.");

			return true;
		}

		// Initialize the archive part list if required
		if (empty($this->backupParts))
		{
			$ret = $this->initialiseBackupParts($postProcEngine);

			if ($ret !== null)
			{
				return $ret;
			}
		}

		// Make sure we don't accidentally break the step when not required to do so
		$configuration->set('volatile.breakflag', false);

		// Do we have a filename from the previous run of the post-proc engine?
		$filename = $configuration->get('volatile.postproc.filename', null);

		if (empty($filename))
		{
			$filename = $this->backupParts[$this->backupPartsIndex];
			Factory::getLog()->info('Beginning post processing file ' . $filename);
		}
		else
		{
			Factory::getLog()->info('Continuing post processing file ' . $filename);
		}

		$this->setStep('Post-processing');
		$this->setSubstep(basename($filename));
		$timer               = Factory::getTimer();
		$startTime           = $timer->getRunningTime();
		$processingException = null;

		try
		{
			$finishedProcessing = $postProcEngine->processPart($filename);
		}
		catch (Exception $e)
		{
			$finishedProcessing  = false;
			$processingException = $e;
		}

		if (!is_null($processingException))
		{
			Factory::getLog()->warning('Failed to process file ' . $filename);
			Factory::getLog()->warning('Error received from the post-processing engine:');

			$this->logErrorsFromException($processingException, $shouldAbortOnFailed ? LogLevel::ERROR : LogLevel::WARNING);

			if ($shouldAbortOnFailed)
			{
				throw new ErrorException(sprintf('Failed to process backup archive file %s', basename($filename)));
			}
		}
		elseif ($finishedProcessing === true)
		{
			// The post-processing of this file ended successfully
			Factory::getLog()->info('Finished post-processing file ' . $filename);
			$configuration->set('volatile.postproc.filename', null);
		}
		else
		{
			// More work required
			Factory::getLog()->info('More post-processing steps required for file ' . $filename);
			$configuration->set('volatile.postproc.filename', $filename);

			// Do we need to break the step?
			$endTime  = $timer->getRunningTime();
			$stepTime = $endTime - $startTime;
			$timeLeft = $timer->getTimeLeft();

			// By default, we assume that we have enough time to run yet another step
			$configuration->set('volatile.breakflag', false);

			/**
			 * However, if the last step took longer than the time we already have left on the timer we can predict
			 * that we are running out of time, therefore we need to break the step.
			 */
			if ($timeLeft < $stepTime)
			{
				$configuration->set('volatile.breakflag', true);
			}
		}

		// Should we delete the file afterwards?
		$canAndShouldDeleteFileAfterwards =
			$configuration->get('engine.postproc.common.delete_after', false)
			&& $postProcEngine->isFileDeletionAfterProcessingAdvisable();

		if ($canAndShouldDeleteFileAfterwards && $finishedProcessing)
		{
			Factory::getLog()->debug('Deleting already processed file ' . $filename);
			Platform::getInstance()->unlink($filename);
		}
		elseif ($canAndShouldDeleteFileAfterwards && !$finishedProcessing)
		{
			Factory::getLog()->debug('Not removing the non-processed file ' . $filename);
		}
		else
		{
			Factory::getLog()->debug('Not removing processed file ' . $filename);
		}

		if ($finishedProcessing === true)
		{
			// Move the index forward if the part finished processing
			$this->backupPartsIndex++;

			// Mark substep done
			$this->subStepsDone++;

			// Break step after processing?
			if (
				$postProcEngine->recommendsBreakAfter()
				&& !Factory::getConfiguration()->get('akeeba.tuning.nobreak.finalization', 0)
			)
			{
				$configuration->set('volatile.breakflag', true);
			}

			// If we just finished processing the first archive part, save its remote path in the statistics.
			if (($this->subStepsDone == 1) || ($this->subStepsTotal == 0))
			{
				$this->updateStatistics($postProcEngine, $engineName);
			}

			// Are we past the end of the array (i.e. we're finished)?
			if ($this->backupPartsIndex >= count($this->backupParts))
			{
				Factory::getLog()->info('Post-processing has finished for all files');

				return true;
			}
		}

		if (!is_null($processingException))
		{
			// If the post-processing failed, make sure we don't process anything else
			$this->backupPartsIndex = count($this->backupParts);
			Factory::getLog()->warning('Post-processing interrupted -- no more files will be transferred');

			return true;
		}

		// Indicate we're not done yet
		return false;
	}

	/**
	 * Update the backup record upon post-processing the first part.
	 *
	 * @param   PostProcInterface  $postProcEngine  The post-processing engine we're using
	 * @param   string             $engineName      The name of the post-processing engine we're using
	 *
	 * @throws  Exception
	 * @since   9.3.1
	 */
	public function updateStatistics(PostProcInterface $postProcEngine, string $engineName): void
	{
		if (empty($postProcEngine->getRemotePath()))
		{
			return;
		}

		$configuration   = Factory::getConfiguration();
		$statistics      = Factory::getStatistics();
		$remote_filename = $engineName . '://';
		$remote_filename .= $postProcEngine->getRemotePath();
		$data            = [
			'remote_filename' => $remote_filename,
		];
		$remove_after    = $configuration->get('engine.postproc.common.delete_after', false);

		if ($remove_after)
		{
			$data['filesexist'] = 0;
		}

		$statistics->setStatistics($data);
	}

	/**
	 * Initialise the backup parts information
	 *
	 * @param   PostProcInterface  $postProcEngine  The post-processing engine we're using
	 *
	 * @return  bool|null
	 *
	 * @since   9.3.1
	 */
	private function initialiseBackupParts(PostProcInterface $postProcEngine): ?bool
	{
		$configuration = Factory::getConfiguration();

		Factory::getLog()->info('Initializing post-processing engine');

		// Initialize the flag for multistep post-processing of parts
		$configuration->set('volatile.postproc.filename', null);
		$configuration->set('volatile.postproc.directory', null);

		// Populate array w/ absolute names of backup parts
		$statistics        = Factory::getStatistics();
		$stat              = $statistics->getRecord();
		$this->backupParts = Factory::getStatistics()->get_all_filenames($stat, false);

		if (is_null($this->backupParts))
		{
			// No archive produced, or they are all already post-processed
			Factory::getLog()->info('No archive files found to post-process');

			return true;
		}

		Factory::getLog()->debug(count($this->backupParts) . ' files to process found');

		$this->subStepsTotal = count($this->backupParts);
		$this->subStepsDone  = 0;

		$this->backupPartsIndex = 0;

		// If we have an empty array, do not run
		if (empty($this->backupParts))
		{
			return true;
		}

		// Break step before processing?
		if (
			$postProcEngine->recommendsBreakBefore()
			&& !$configuration->get(
				'akeeba.tuning.nobreak.finalization', 0
			)
		)
		{
			Factory::getLog()->debug('Breaking step before post-processing run');
			$configuration->set('volatile.breakflag', true);

			return false;
		}

		return null;
	}
}PK�!]#��*6BackupEngine/Core/Domain/Finalizer/UploadKickstart.phpnu&1i�<?php
/**
 * Akeeba Engine
 *
 * @package   akeebaengine
 * @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License version 3, or later
 *
 * 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, version 3.
 *
 * 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
 * <https://www.gnu.org/licenses/>.
 */

/**
 * @package     Akeeba\Engine\Core\Domain\Finalizer
 * @subpackage
 *
 * @copyright   A copyright
 * @license     A "Slug" license name e.g. GPL2
 */

namespace Akeeba\Engine\Core\Domain\Finalizer;

use Akeeba\Engine\Factory;
use Akeeba\Engine\Platform;
use Exception;
use Akeeba\Engine\Psr\Log\LogLevel;

/**
 * Uploads Kickstart using the post-processing engine
 *
 * @since       9.3.1
 * @package     Akeeba\Engine\Core\Domain\Finalizer
 */
final class UploadKickstart extends AbstractFinalizer
{

	/**
	 * @inheritDoc
	 */
	public function __invoke()
	{
		$this->setStep('Post-processing Kickstart');
		$this->setSubstep('');

		$configuration = Factory::getConfiguration();

		// Do not run if we are not told to upload Kickstart
		$uploadKickstart = $configuration->get('akeeba.advanced.uploadkickstart', 0);

		if (!$uploadKickstart)
		{
			return true;
		}

		$engineName = $configuration->get('akeeba.advanced.postproc_engine');
		Factory::getLog()->debug("Loading post-processing engine object ($engineName)");
		$postProcEngine = Factory::getPostprocEngine($engineName);

		// Set $filename to kickstart's source file
		$filename = Platform::getInstance()->get_installer_images_path() . '/kickstart.txt';

		// Post-process the file
		$this->setSubstep('kickstart.php');

		if (!@file_exists($filename) || !is_file($filename))
		{
			Factory::getLog()->warning(
				sprintf(
					'Failed to upload kickstart.php. Missing file %s',
					$filename
				)
			);

			// Indicate we're done.
			return true;
		}

		$exception          = null;
		$finishedProcessing = false;

		try
		{
			$finishedProcessing = $postProcEngine->processPart($filename, 'kickstart.php');
		}
		catch (Exception $e)
		{
			$exception = $e;
		}

		if (!is_null($exception))
		{
			Factory::getLog()->warning('Failed to upload kickstart.php');
			Factory::getLog()->warning('Error received from the post-processing engine:');
			$this->logErrorsFromException($exception, LogLevel::WARNING);
		}
		elseif ($finishedProcessing === true)
		{
			// The post-processing of this file ended successfully
			Factory::getLog()->info('Finished uploading kickstart.php');
			$configuration->set('volatile.postproc.filename', null);
		}

		// Indicate we're done
		return true;
	}
}PK�!]��O�yy)BackupEngine/Core/Domain/Finalization.phpnu&1i�<?php
/**
 * Akeeba Engine
 *
 * @package   akeebaengine
 * @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License version 3, or later
 *
 * 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, version 3.
 *
 * 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
 * <https://www.gnu.org/licenses/>.
 */

namespace Akeeba\Engine\Core\Domain;

defined('AKEEBAENGINE') || die();

use Akeeba\Engine\Base\Part;
use Akeeba\Engine\Core\Domain\Finalizer\LocalQuotas;
use Akeeba\Engine\Core\Domain\Finalizer\MailAdministrators;
use Akeeba\Engine\Core\Domain\Finalizer\ObsoleteRecordsQuotas;
use Akeeba\Engine\Core\Domain\Finalizer\PostProcessing;
use Akeeba\Engine\Core\Domain\Finalizer\RemoteQuotas;
use Akeeba\Engine\Core\Domain\Finalizer\RemoveTemporaryFiles;
use Akeeba\Engine\Core\Domain\Finalizer\UpdateFileSizes;
use Akeeba\Engine\Core\Domain\Finalizer\UpdateStatistics;
use Akeeba\Engine\Core\Domain\Finalizer\UploadKickstart;
use Akeeba\Engine\Factory;
use Akeeba\Engine\Platform;
use DateTime;
use Exception;
use Akeeba\Engine\Psr\Log\LogLevel;

/**
 * Backup finalization domain
 */
final class Finalization extends Part
{
	/** @var array The finalisation actions we have to execute (FIFO queue) */
	private $actionQueue = [];

	/** @var string The current method, shifted from the action queye */
	private $currentActionClass = '';

	private $currentActionObject = null;

	/** @var int How many finalisation steps I have already done */
	private $stepsDone = 0;

	/** @var int How many finalisation steps I have in total */
	private $stepsTotal = 0;

	/** @var int How many finalisation substeps I have already done */
	private $subStepsDone = 0;

	/** @var int How many finalisation substeps I have in total */
	private $subStepsTotal = 0;

	/**
	 * Get the percentage of finalization steps done
	 *
	 * @return  float
	 */
	public function getProgress()
	{
		if ($this->stepsTotal <= 0)
		{
			return 0;
		}

		$overall = $this->stepsDone / $this->stepsTotal;
		$local   = 0;

		if ($this->subStepsTotal > 0)
		{
			$local = $this->subStepsDone / $this->subStepsTotal;
		}

		return $overall + ($local / $this->stepsTotal);
	}

	/**
	 * Relays and logs an exception
	 *
	 * @param   \Throwable  $e         The exception or throwable to log
	 * @param   string      $logLevel  The log level to log it with
	 *
	 * @return  void
	 * @since   9.3.1
	 */
	public function relayException(\Throwable $e, string $logLevel = LogLevel::ERROR): void
	{
		self::logErrorsFromException($e, $logLevel);
	}

	/**
	 * Used by additional handler classes to relay their step to us
	 *
	 * @param   string  $step  The current step
	 */
	public function relayStep($step)
	{
		$this->setStep($step);
	}

	/**
	 * Used by additional handler classes to relay their substep to us
	 *
	 * @param   string  $substep  The current sub-step
	 */
	public function relaySubstep($substep)
	{
		$this->setSubstep($substep);
	}

	/**
	 * Implements the abstract method
	 *
	 * @return void
	 */
	protected function _finalize()
	{
		$this->setState(self::STATE_FINISHED);
	}

	/**
	 * Initialise the finalisation engine
	 */
	protected function _prepare()
	{
		// Make sure the break flag is not set
		$configuration = Factory::getConfiguration();
		$configuration->get('volatile.breakflag', false);

		// Get the quota actions
		$quotaActions = $configuration->get('volatile.core.finalization.quotaActions', null);

		$quotaActions = is_array($quotaActions) ? $quotaActions : [
			LocalQuotas::class,
			RemoteQuotas::class,
			ObsoleteRecordsQuotas::class,
		];

		// Get the default finalization actions
		$defaultActions = array_merge(
			[
				RemoveTemporaryFiles::class,
				UpdateStatistics::class,
				UpdateFileSizes::class,
				PostProcessing::class,
				UploadKickstart::class,
			],
			$quotaActions,
			[
				MailAdministrators::class,
				// Run it a second time to update the backup end time after post-processing, emails, etc
				UpdateStatistics::class,
			]
		);

		// Populate the actions queue, if it's not already set in a subclass
		$this->actionQueue = $this->actionQueue ?: $defaultActions;

		// Apply action queue customisations
		$customQueue       = $configuration->get('volatile.core.finalization.action_queue', null);
		$customQueueBefore = $configuration->get('volatile.core.finalization.action_queue_before', null);
		$customQueueAfter  = $configuration->get('volatile.core.finalization.action_queue_after', null);

		if (is_array($customQueue) && !empty($customQueue))
		{
			Factory::getLog()->debug('Overriding action queue');
			$this->actionQueue = $customQueue;
		}
		else
		{
			if (is_array($customQueueBefore) && !empty($customQueueBefore))
			{
				Factory::getLog()->debug('Adding finalization actions before post-processing');
				$before = array_slice($this->actionQueue, 0, 3);
				$after  = array_slice($this->actionQueue, 3);

				$this->actionQueue = array_merge($before, $customQueueBefore, $after);
			}

			if (is_array($customQueueAfter) && !empty($customQueueAfter))
			{
				Factory::getLog()->debug('Adding finalization actions at the end of the queue');
				$before = array_slice($this->actionQueue, 0, -1);
				$after  = array_slice($this->actionQueue, -1, 1);

				$this->actionQueue = array_merge($before, $customQueueAfter, $after);
			}
		}

		// Log the actions queue
		Factory::getLog()->debug('Finalization action queue: ' . implode(', ', $this->actionQueue));

		// Initialise actions processing
		$this->stepsTotal    = count($this->actionQueue);
		$this->stepsDone     = 0;
		$this->subStepsTotal = 0;
		$this->subStepsDone  = 0;

		// Seed the method
		$this->currentActionClass = array_shift($this->actionQueue);

		// Set ourselves to running state
		$this->setState(self::STATE_RUNNING);
	}

	/**
	 * Implements the abstract method
	 *
	 * @return  void
	 */
	protected function _run()
	{
		$configuration = Factory::getConfiguration();

		if ($this->getState() == self::STATE_POSTRUN)
		{
			return;
		}

		$finished = (empty($this->actionQueue)) && ($this->currentActionClass == '');

		if ($finished)
		{
			$this->setState(self::STATE_POSTRUN);

			return;
		}

		$this->setState(self::STATE_RUNNING);

		$timer = Factory::getTimer();

		// Continue processing while we have still enough time and stuff to do
		while (($timer->getTimeLeft() > 0) && (!$finished) && (!$configuration->get('volatile.breakflag', false)))
		{
			if (empty($this->currentActionObject))
			{
				$className = $this->currentActionClass;

				Factory::getLog()->debug(__CLASS__ . "::_run() Running new finalization object $className");

				$this->currentActionObject = new $className($this);
			}
			else
			{
				Factory::getLog()->debug(__CLASS__ . "::_run() Resuming finalization object $this->currentActionClass");
			}

			$finalizer = $this->currentActionObject;

			if ($finalizer() !== true)
			{
				continue;
			}

			$this->currentActionClass  = '';
			$this->currentActionObject = null;
			$this->stepsDone++;
			$finished = empty($this->actionQueue);

			if ($finished)
			{
				continue;
			}

			$this->currentActionClass = array_shift($this->actionQueue);
			$this->subStepsTotal      = 0;
			$this->subStepsDone       = 0;
		}

		if ($finished)
		{
			$this->setState(self::STATE_POSTRUN);
			$this->setStep('');
			$this->setSubstep('');
		}
	}
}
PK�!]�^�vbvbBackupEngine/Core/Kettenrad.phpnu&1i�<?php
/**
 * Akeeba Engine
 *
 * @package   akeebaengine
 * @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License version 3, or later
 *
 * 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, version 3.
 *
 * 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
 * <https://www.gnu.org/licenses/>.
 */

namespace Akeeba\Engine\Core;

defined('AKEEBAENGINE') || die();

use Akeeba\Engine\Base\Part;
use Akeeba\Engine\Factory;
use Akeeba\Engine\Platform;
use Exception;
use RuntimeException;
use Throwable;

/**
 * Kettenrad is the main controller of Akeeba Engine. It's responsible for setting the engine into motion, running each
 * and all domain objects to their completion.
 */
class Kettenrad extends Part
{
	/**
	 * Set to true when akeebaBackupErrorHandler is registered as an error handler
	 *
	 * @var bool
	 */
	public static $registeredErrorHandler = false;

	/**
	 * Set to true when deadOnTimeout is registered as a shutdown function
	 *
	 * @var bool
	 */
	public static $registeredShutdownCallback = false;

	/**
	 * Cached copy of the response array
	 *
	 * @var array
	 */
	private $array_cache = null;

	/**
	 * A unique backup ID which allows us to run multiple parallel backups using the same backup origin (tag)
	 *
	 * @var string
	 */
	private $backup_id = '';

	/**
	 * The active domain's class name
	 *
	 * @var string
	 */
	private $class = '';

	/**
	 * The current domain's name
	 *
	 * @var string
	 */
	private $domain = '';

	/**
	 * The list of remaining steps
	 *
	 * @var array
	 */
	private $domain_chain = [];

	/**
	 * The current backup's tag (actually: the backup's origin)
	 *
	 * @var string
	 */
	private $tag = null;

	/**
	 * How many steps the domain_chain array contained when the backup began. Used for percentage calculations.
	 *
	 * @var int
	 */
	private $total_steps = 0;

	/**
	 * Set to true when there are warnings available when getStatusArray() is called. This is used at the end of the
	 * backup to send a different push message depending on whether the backup completed with or without warnings.
	 *
	 * @var  bool
	 */
	private $warnings_issued = false;

	/**
	 * Kettenrad constructor.
	 *
	 * Overrides the Part constructor to initialize Kettenrad-specific properties.
	 *
	 * @return void
	 */
	public function __construct()
	{
		parent::__construct();

		// Register the error handler
		if (!static::$registeredErrorHandler)
		{
			static::$registeredErrorHandler = true;
			set_error_handler('\\Akeeba\\Engine\\Core\\akeebaEngineErrorHandler');
		}
	}

	public function _onSerialize()
	{
		parent::_onSerialize();

		$this->array_cache = null;
	}


	/**
	 * Returns the unique Backup ID
	 *
	 * @return string
	 */
	public function getBackupId()
	{
		return $this->backup_id;
	}

	/**
	 * Sets the unique backup ID.
	 *
	 * @param   string  $backup_id
	 *
	 * @return void
	 */
	public function setBackupId($backup_id = null)
	{
		$this->backup_id = $backup_id;
	}

	/**
	 * Gets the percentage of the backup process done so far.
	 *
	 * @return string
	 */
	public function getProgress()
	{
		// Get the overall percentage (based on domains complete so far)
		$remainingSteps = count($this->domain_chain) + 1;
		$totalSteps     = max($this->total_steps, 1);
		$overall        = 1 - ($remainingSteps / $totalSteps);

		// How much is this step worth?
		$currentStepMaxContribution = 1 / $totalSteps;

		// Get the percentage reported from the domain object, zero if we can't get a domain object.
		$object = !empty($this->class) ? Factory::getDomainObject($this->class) : null;
		$local  = is_object($object) ? $object->getProgress() : 0;

		// Calculate the percentage and apply [0, 100] bounds.
		$percentage = (int) (100 * ($overall + $local * $currentStepMaxContribution));
		$percentage = max(0, $percentage);
		$percentage = min(100, $percentage);

		return $percentage;
	}

	/**
	 * Returns a copy of the class's status array
	 *
	 * @return array
	 */
	public function getStatusArray()
	{
		// Get the cached array
		if (!empty($this->array_cache))
		{
			return $this->array_cache;
		}

		// Get the default table
		$array = $this->makeReturnTable();

		// Add the warnings
		$array['Warnings'] = Factory::getLog()->getWarnings();

		// Did we have warnings?
		if (is_array($array['Warnings']) || $array['Warnings'] instanceof \Countable ? count($array['Warnings']) : 0)
		{
			$this->warnings_issued = true;
		}

		// Get the current step number
		$stepCounter = Factory::getConfiguration()->get('volatile.step_counter', 0);

		// Add the archive name
		$statistics       = Factory::getStatistics();
		$record           = $statistics->getRecord();
		$array['Archive'] = $record['archivename'] ?? '';

		// Translate HasRun to what the rest of the suite expects
		$array['HasRun'] = ($this->getState() == self::STATE_FINISHED) ? 1 : 0;

		$array['Error']      = is_null($array['ErrorException']) ? '' : $array['Error'];
		$array['tag']        = $this->tag;
		$array['Progress']   = $this->getProgress();
		$array['backupid']   = $this->getBackupId();
		$array['sleepTime']  = $this->waitTimeMsec;
		$array['stepNumber'] = $stepCounter;
		$array['stepState']  = $this->stateToString($this->getState());

		$this->array_cache = $array;

		return $this->array_cache;
	}

	/**
	 * Returns the current backup tag. If none is specified, it sets it to be the
	 * same as the current backup origin and returns the new setting.
	 *
	 * @return string
	 */
	public function getTag()
	{
		if (empty($this->tag))
		{
			// If no tag exists, we resort to the pre-set backup origin
			$tag       = Platform::getInstance()->get_backup_origin();
			$this->tag = $tag;
		}

		return $this->tag;
	}

	/**
	 * Obsolete method.
	 *
	 * @deprecated 7.0
	 */
	public function resetWarnings()
	{
		Factory::getLog()->debug('DEPRECATED: Akeeba Engine consumers must remove calls to resetWarnings()');
	}

	/**
	 * The public interface to Kettenrad.
	 *
	 * Internally it calls Part::tick(), wrapped in a try-catch block which traps any runaway Exception (PHP 5) or
	 * Throwable we didn't manage to successfully suppress yet.
	 *
	 * @param   int  $nesting
	 *
	 * @return  array  A response array
	 */
	public function tick($nesting = 0)
	{
		$ret = null;
		$e   = null;

		// PHP 7.x -- catch any unhandled Throwable, including PHP fatal errors
		try
		{
			$ret = parent::tick($nesting);
		}
		catch (Throwable $e)
		{
			$this->setState(self::STATE_ERROR);
			$this->lastException = $e;
		}

		// If an error occurred we don't have a return table. If that's the case create one and do log our errors.
		if (!isset($ret))
		{
			// Log the existence of an unhandled exception
			Factory::getLog()->warning("Kettenrad :: Caught unhandled exception. The backup will now fail.");

			// Recursively log unhandled exceptions
			self::logErrorsFromException($e);

			// Create the missing return table
			$ret               = $this->makeReturnTable();
			$this->array_cache = array_merge(is_null($this->array_cache) ? [] : $this->array_cache, $ret);
		}

		return $ret;
	}

	/**
	 * Finalization. Sets the state to STATE_FINISHED.
	 *
	 * @return  void
	 */
	protected function _finalize()
	{
		// Open the log
		$logTag = $this->getLogTag();
		Factory::getLog()->open($logTag);

		// Kill the cached array
		$this->array_cache = null;

		// Remove the memory file
		$tempVarsTag = $this->tag . (empty($this->backup_id) ? '' : ('.' . $this->backup_id));
		Factory::getFactoryStorage()->reset($tempVarsTag);

		// All done.
		Factory::getLog()->debug("Kettenrad :: Just finished");
		$this->setState(self::STATE_FINISHED);

		// Send a push message to mark the end of backup
		$pushSubjectKey = $this->warnings_issued ? 'COM_AKEEBA_PUSH_ENDBACKUP_WARNINGS_SUBJECT' : 'COM_AKEEBA_PUSH_ENDBACKUP_SUCCESS_SUBJECT';
		$pushBodyKey    = $this->warnings_issued ? 'COM_AKEEBA_PUSH_ENDBACKUP_WARNINGS_BODY' : 'COM_AKEEBA_PUSH_ENDBACKUP_SUCCESS_BODY';
		$platform       = Platform::getInstance();
		$timeStamp      = date($platform->translate('DATE_FORMAT_LC2'));
		$pushSubject    = sprintf($platform->translate($pushSubjectKey), $platform->get_site_name(), $platform->get_host());
		$pushDetails    = sprintf($platform->translate($pushBodyKey), $platform->get_site_name(), $platform->get_host(), $timeStamp);

		try
		{
			Factory::getPush()->message($pushSubject, $pushDetails);
		}
		catch (Throwable $e)
		{
			Factory::getLog()->notice(sprintf("Sending push notification failed: %s", $e->getMessage()));
		}
	}

	/**
	 * Initialization. Sets the state to STATE_PREPARED.
	 *
	 * @return  void
	 */
	protected function _prepare()
	{
		// Initialize the timer class. Do not remove, even though we don't use the object it needs to be initialized!
		$timer = Factory::getTimer();

		// Do we have a tag?
		if (!empty($this->_parametersArray['tag']))
		{
			$this->tag = $this->_parametersArray['tag'];
		}

		// Make sure a tag exists (or create a new one)
		$this->tag = $this->getTag();

		// Reset the log
		$logTag = $this->getLogTag();
		Factory::getLog()->open($logTag);
		Factory::getLog()->reset($logTag);

		// Reset the storage
		$factoryStorageTag = $this->tag . (empty($this->backup_id) ? '' : ('.' . $this->backup_id));
		Factory::getFactoryStorage()->reset($factoryStorageTag);

		// Apply the configuration overrides
		$overrides = Platform::getInstance()->configOverrides;

		if (is_array($overrides) && @count($overrides))
		{
			$registry       = Factory::getConfiguration();
			$protected_keys = $registry->getProtectedKeys();
			$registry->resetProtectedKeys();

			foreach ($overrides as $k => $v)
			{
				$registry->set($k, $v);
			}

			$registry->setProtectedKeys($protected_keys);
		}

		// Get the domain chain
		$this->domain_chain = Factory::getEngineParamsProvider()->getDomainChain();
		$this->total_steps  = count($this->domain_chain) - 1; // Init shouldn't count in the progress bar

		// Mark this engine for Nesting Logging
		$this->nest_logging = true;

		// Preparation is over
		$this->array_cache = null;
		$this->setState(self::STATE_PREPARED);

		// Send a push message to mark the start of backup
		$platform    = Platform::getInstance();
		$timeStamp   = date($platform->translate('DATE_FORMAT_LC2'));
		$pushSubject = sprintf($platform->translate('COM_AKEEBA_PUSH_STARTBACKUP_SUBJECT'), $platform->get_site_name(), $platform->get_host());
		$pushDetails = sprintf($platform->translate('COM_AKEEBA_PUSH_STARTBACKUP_BODY'), $platform->get_site_name(), $platform->get_host(), $timeStamp, $this->getLogTag());

		try
		{
			Factory::getPush()->message($pushSubject, $pushDetails);
		}
		catch (Throwable $e)
		{
			Factory::getLog()->notice(sprintf("Sending push notification failed: %s", $e->getMessage()));
		}
	}

	/**
	 * Main backup process. Sets the state to STATE_RUNNING or STATE_POSTRUN.
	 *
	 * @return  void
	 */
	protected function _run()
	{
		$result = null;
		$logTag = $this->getLogTag();
		$logger = Factory::getLog();
		$logger->open($logTag);

		// Maybe we're already done or in an error state?
		if (in_array($this->getState(), [self::STATE_POSTRUN, self::STATE_ERROR]))
		{
			return;
		}

		// Set running state
		$this->setState(self::STATE_RUNNING);

		// Do I even have enough time...?
		$timer    = Factory::getTimer();
		$registry = Factory::getConfiguration();

		if (($timer->getTimeLeft() <= 0))
		{
			// We need to set the break flag for the part processing to not batch successive steps
			$registry->set('volatile.breakflag', true);

			return;
		}

		// Initialize operation counter
		$registry->set('volatile.operation_counter', 0);

		// Advance step counter
		$stepCounter = $registry->get('volatile.step_counter', 0);
		$registry->set('volatile.step_counter', ++$stepCounter);

		// Log step start number
		$logger->debug('====== Starting Step number ' . $stepCounter . ' ======');

		if (defined('AKEEBADEBUG'))
		{
			$root = Platform::getInstance()->get_site_root();
			$logger->debug('Site root: ' . $root);
		}

		$finished = false;
		$error    = false;
		// BREAKFLAG is optionally passed by domains to force-break current operation
		$breakFlag = false;

		// Apply an infinite time limit if required
		if ($registry->get('akeeba.tuning.settimelimit', 0))
		{
			if (function_exists('ini_set'))
			{
				@ini_set('max_execution_time', 844000);
			}

			if (function_exists('set_time_limit'))
			{
				set_time_limit(0);
			}
		}

		// Apply a large memory limit (1Gb) if required
		if ($registry->get('akeeba.tuning.setmemlimit', 0))
		{
			if (function_exists('ini_set'))
			{
				ini_set('memory_limit', '17179869184');
			}
		}

		// Update statistics, marking the backup as currently processing a backup step.
		Factory::getStatistics()->updateInStep(true);

		// Loop until time's up, we're done or an error occurred, or BREAKFLAG is set
		$this->array_cache = null;
		$object            = null;

		while (($timer->getTimeLeft() > 0) && (!$finished) && (!$error) && (!$breakFlag))
		{
			// Reset the break flag
			$registry->set('volatile.breakflag', false);

			// Do we have to switch domains? This only happens if there is no active
			// domain, or the current domain has finished
			$have_to_switch = false;
			$object         = null;

			if ($this->class == '')
			{
				$have_to_switch = true;
			}
			else
			{
				$object = Factory::getDomainObject($this->class);

				if (!is_object($object))
				{
					$have_to_switch = true;
				}
				elseif (!in_array('getState', get_class_methods($object)))
				{
					$have_to_switch = true;
				}
				elseif ($object->getState() == self::STATE_FINISHED)
				{
					$have_to_switch = true;
				}
			}

			// Switch domain if necessary
			if ($have_to_switch)
			{
				$logger->debug('Kettenrad :: Switching domains');

				if (!Factory::getConfiguration()->get('akeeba.tuning.nobreak.domains', 0))
				{
					$logger->debug("Kettenrad :: BREAKING STEP BEFORE SWITCHING DOMAIN");
					$registry->set('volatile.breakflag', true);
				}

				// Free last domain
				$object = null;

				if (empty($this->domain_chain))
				{
					// Aw, we're done! No more domains to run.
					$this->setState(self::STATE_POSTRUN);
					$logger->debug("Kettenrad :: No more domains to process");
					$logger->debug('====== Finished Step number ' . $stepCounter . ' ======');
					$this->array_cache = null;

					return;
				}

				// Shift the next definition off the stack
				$this->array_cache = null;
				$new_definition    = array_shift($this->domain_chain);

				if (array_key_exists('class', $new_definition))
				{
					$logger->debug("Switching to domain {$new_definition['domain']}, class {$new_definition['class']}");
					$this->domain = $new_definition['domain'];
					$this->class  = $new_definition['class'];
					// Get a working object
					$object = Factory::getDomainObject($this->class);
					$object->setup($this->_parametersArray);
				}
				else
				{
					$logger->warning("Kettenrad :: No class defined trying to switch domains. The backup will crash.");
					$this->domain = null;
					$this->class  = null;
				}
			}
			elseif (!is_object($object))
			{
				$logger->debug("Kettenrad :: Getting domain object of class {$this->class}");
				$object = Factory::getDomainObject($this->class);
			}


			// Tick the object
			$logger->debug('Kettenrad :: Ticking the domain object');
			$this->lastException = null;

			try
			{
				// We ask the domain object to execute and return its output array
				$result = $object->tick();

				$hasErrorException = array_key_exists('ErrorException', $result) && is_object($result['ErrorException']);
				$hasErrorString    = array_key_exists('Error', $result) && !empty($result['Error']);

				/**
				 * Legacy objects may not be throwing exceptions on error, instead returning an Error string in the
				 * output array. The code below addresses this discrepancy.
				 */
				if (!$hasErrorException && $hasErrorString)
				{
					$result['ErrorException'] = new RuntimeException($result['Error']);
					$hasErrorException        = true;
				}

				/**
				 * Some domain objects may be acting as nested Parts, e.g. the Database domain. In this case the
				 * internal Engine (itself a Part object) is absorbing the thrown exception and relays it in the output
				 * table's ErrorException key. This means that the code above will NOT catch the error. This code below
				 * addresses that situation by rethrowing the exception.
				 *
				 * Practical example: cannot connect to MySQL is thrown by the MySQL Dump engine. The Native database
				 * backup engine absorbs the exception and reports it back to the Database domain object through the
				 * returned output array. However, the Database domain object does not rethrow it, simply relaying it
				 * back to Kettenrad through its own returned output array. As a result we enter an infinite loop where
				 * Kettenrad asks the Database domain to tick, it asks the Native engine to tick which asks the MySQL
				 * Dump object to tick. However the latter fails again to connect to MySQL and the whole process is
				 * repeated ad nauseam. By rethrowing the propagated ErrorException we alleviate this problem.
				 */
				if ($hasErrorException)
				{
					throw $result['ErrorException'];
				}

				$logger->debug('Kettenrad :: Domain object returned without errors; propagating');
			}
			catch (Exception $e)
			{
				/**
				 * Exceptions are used to propagate error conditions through the engine. Catching them and storing them
				 * in $this->lastException lets us detect and report the error condition in Kettenrad, the integration-
				 * facing interface of the backup engine.
				 */
				$this->lastException = $e;

				$logger->debug('Kettenrad :: Domain object returned with errors; propagating');

				self::logErrorsFromException($this->lastException);

				$this->setState(self::STATE_ERROR);
			}

			// Advance operation counter
			$currentOperationNumber = $registry->get('volatile.operation_counter', 0);
			$currentOperationNumber++;
			$registry->set('volatile.operation_counter', $currentOperationNumber);

			// Process return array
			$this->setDomain($this->domain);
			$this->setStep($result['Step']);
			$this->setSubstep($result['Substep']);

			// Check for BREAKFLAG
			$breakFlag = $registry->get('volatile.breakflag', false);
			$logger->debug("Kettenrad :: Break flag status: " . ($breakFlag ? 'YES' : 'no'));

			// Process errors
			$error = $this->getState() === self::STATE_ERROR;

			// Check if the backup procedure should finish now
			$finished = $error ? true : !($result['HasRun']);

			// Log operation end
			$logger->debug('----- Finished operation ' . $currentOperationNumber . ' ------');
		}

		// Log the result
		$objectStepType = is_object($object) ? get_class($object) : 'INVALID OBJECT';

		if (!is_object($object))
		{
			$reason = ($timer->getTimeLeft() <= 0)
				? 'we already ran out of time'
				: 'a step break has already been requested';
			$logger->debug(
				sprintf(
					"Finishing step immediately because %s", $reason
				)
			);
		}
		elseif (!$error)
		{
			$logger->debug("Successful Smart algorithm on " . $objectStepType);
		}
		else
		{
			$logger->error("Failed Smart algorithm on " . $objectStepType);
		}

		// Log if we have to do more work or not
		/**
		 * The domain object is not set in the following cases:
		 *
		 * - There is no time left, the while loop never ran.
		 * - The break flag was already set, the while loop never ran.
		 * - We are already finished, the while loop never ran. Shouldn't happen, the step status is set to POSTRUN.
		 * - There was an error, the while loop never ran. Shouldn't happen, we return immediately upon an error.
		 * - We tried to go to the next domain but something went wrong. Shouldn't happen.
		 *
		 * If we get to a condition that shouldn't happen we will throw a Runtime exception. In any other case we let
		 * the step finish.
		 */
		if (!is_object($object) && ($timer->getTimeLeft() > 0) && !$breakFlag)
		{
			throw new RuntimeException(
				sprintf(
					"Kettenrad :: Empty object found when processing domain '%s'. This should never happen.",
					$this->domain
				)
			);
		}
		/** @noinspection PhpStatementHasEmptyBodyInspection */
		elseif (!is_object($object))
		{
			// This is an expected case.
			// I have to use an empty case because $object->getState() below would cause a PHP error on a NULL variable.
		}
		elseif ($object->getState() == self::STATE_RUNNING)
		{
			$logger->debug("Kettenrad :: More work required in domain '" . $this->domain . "'");
			// We need to set the break flag for the part processing to not batch successive steps
			$registry->set('volatile.breakflag', true);
		}
		elseif ($object->getState() == self::STATE_FINISHED)
		{
			$logger->debug("Kettenrad :: Domain '" . $this->domain . "' has finished.");
			$registry->set('volatile.breakflag', false);
		}
		elseif ($object->getState() == self::STATE_ERROR)
		{
			$logger->debug("Kettenrad :: Domain '" . $this->domain . "' has experienced an error.");
			$registry->set('volatile.breakflag', false);
		}

		// Log step end
		$logger->debug('====== Finished Step number ' . $stepCounter . ' ======');

		// Update statistics, marking the backup as having just finished processing a backup step.
		Factory::getStatistics()->updateInStep(false);

		if (!$registry->get('akeeba.tuning.nobreak.domains', 0))
		{
			// Force break between steps
			$logger->debug('Kettenrad :: Setting the break flag between domains');
			$registry->set('volatile.breakflag', true);
		}
	}

	/**
	 * Returns the tag used to open the correct log file
	 *
	 * @return string
	 */
	protected function getLogTag()
	{
		$tag = $this->getTag();

		if (!empty($this->backup_id))
		{
			$tag .= '.' . $this->backup_id;
		}

		return $tag;
	}
}

/**
 * Timeout error handler
 */
function akeebaEnginePHPTimeoutHandler()
{
	if (connection_status() == 1)
	{
		Factory::getLog()->error('The process was aborted on user\'s request');

		return;
	}

	if (connection_status() >= 2)
	{
		Factory::getLog()->error('Akeeba Backup has timed out. Please read the documentation.');

		return;
	}
}

// Register the timeout error handler
if (!Kettenrad::$registeredShutdownCallback)
{
	Kettenrad::$registeredShutdownCallback = true;

	register_shutdown_function("\\Akeeba\\Engine\\Core\\akeebaEnginePHPTimeoutHandler");
}

/**
 * Custom PHP error handler to log catchable PHP errors to the backup log file
 *
 * @param   int     $errno
 * @param   string  $errstr
 * @param   string  $errfile
 * @param   int     $errline
 *
 * @return bool|null
 */
function akeebaEngineErrorHandler($errno, $errstr, $errfile, $errline)
{
	// Sanity check
	if (!function_exists('error_reporting'))
	{
		return false;
	}

	// Do not proceed if the error springs from an @function() construct, or if
	// the overall error reporting level is set to report no errors.
	$error_reporting = error_reporting();

	if ($error_reporting == 0)
	{
		return false;
	}

	$loggable = false;
	$type     = '';
	$logMode  = 'debug';

	switch ($errno)
	{
		case E_ERROR:
		case E_USER_ERROR:
		case E_RECOVERABLE_ERROR:
			/**
			 * This will only work for E_RECOVERABLE_ERROR and E_USER_ERROR, not E_ERROR. In PHP 7 all errors throw an
			 * Error throwable (a special kind of exception) which propagates nicely within our architecture.
			 */
			Factory::getLog()->error("PHP FATAL ERROR on line $errline in file $errfile:");
			Factory::getLog()->error($errstr);
			Factory::getLog()->error("Execution aborted due to PHP fatal error");
			break;

		case E_WARNING:
			$loggable = true;
			$type     = 'WARNING';
			$logMode  = defined('AKEEBADEBUG') ? 'warning' : 'debug';

			break;

		case E_USER_WARNING:
			$loggable = defined('AKEEBADEBUG');
			$type     = 'User Warning';
			break;

		case E_NOTICE:
			$loggable = defined('AKEEBADEBUG');
			$type     = 'Notice';
			break;

		case E_USER_NOTICE:
			$loggable = defined('AKEEBADEBUG');
			$type     = 'User Notice';
			break;

		case E_DEPRECATED:
			$loggable = defined('AKEEBADEBUG');
			$type     = 'Deprecated';
			break;

		case E_USER_DEPRECATED:
			$loggable = defined('AKEEBADEBUG');
			$type     = 'User Deprecated';
			break;

		case E_STRICT:
			$loggable = defined('AKEEBADEBUG');
			$type     = 'Strict Notice';
			break;

		default:
			// These are E_DEPRECATED, E_STRICT etc. Let PHP handle them
			return false;

			break;
	}

	if ($loggable)
	{
		Factory::getLog()->{$logMode}("PHP $type (not an error; you can ignore) on line $errline in file $errfile:");
		Factory::getLog()->{$logMode}($errstr);
	}

	// Uncomment to prevent the execution of PHP's internal error handler
	//return true;

	// Let PHP's internal error handler take care of the error.
	return false;
}
PK�!]ɷ|Q��"BackupEngine/Core/02.advanced.jsonnu&1i�{
    "_group": {
        "description": "COM_AKEEBA_CONFIG_ADVANCED"
    },
    "akeeba.advanced.dump_engine": {
        "default": "native",
        "type": "engine",
        "subtype": "dump",
        "protected": "1",
        "title": "COM_AKEEBA_CONFIG_DUMPENGINE_TITLE",
        "description": "COM_AKEEBA_CONFIG_DUMPENGINE_DESCRIPTION"
    },
    "akeeba.advanced.scan_engine": {
        "default": "smart",
        "type": "engine",
        "subtype": "scan",
        "protected": "1",
        "title": "COM_AKEEBA_CONFIG_SCANENGINE_TITLE",
        "description": "COM_AKEEBA_CONFIG_SCANENGINE_DESCRIPTION"
    },
    "akeeba.advanced.archiver_engine": {
        "default": "jpa",
        "type": "engine",
        "subtype": "archiver",
        "title": "COM_AKEEBA_CONFIG_ARCHIVERENGINE_TITLE",
        "description": "COM_AKEEBA_CONFIG_ARCHIVERENGINE_DESCRIPTION"
    },
    "akeeba.advanced.postproc_engine": {
        "default": "none",
        "type": "none",
        "protected": "1"
    },
    "akeeba.advanced.embedded_installer": {
        "default": "angie",
        "type": "none",
        "protected": "1"
    },
    "engine.installer.angie.key": {
        "default": "",
        "type": "none",
        "protected": "1"
    }
}PK�!]U��;r*r*BackupEngine/Core/Filters.phpnu&1i�<?php
/**
 * Akeeba Engine
 *
 * @package   akeebaengine
 * @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License version 3, or later
 *
 * 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, version 3.
 *
 * 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
 * <https://www.gnu.org/licenses/>.
 */

namespace Akeeba\Engine\Core;

defined('AKEEBAENGINE') || die();

use Akeeba\Engine\Factory;
use Akeeba\Engine\Filter\Base as FilterBase;
use Akeeba\Engine\Platform;
use DirectoryIterator;
use RuntimeException;

/**
 * Akeeba filtering feature
 */
class Filters
{
	/** @var array An array holding data for all defined filters */
	private $filter_registry = [];

	/** @var FilterBase[] Hash array with instances of all filters as $filter_name => filter_object */
	private $filters = [];

	/** @var bool True after the filter clean up has run */
	private $cleanup_has_run = false;

	/**
	 * Public constructor, loads filter data and filter classes
	 */
	public function __construct()
	{
		// Load filter data from platform's database
		Factory::getLog()->debug('Fetching filter data from database');
		$this->filter_registry = Platform::getInstance()->load_filters();

		// Load platform, plugin and core filters
		$this->filters = [];

		$locations = [
			Factory::getAkeebaRoot() . '/Filter',
		];

		$platform_paths = Platform::getInstance()->getPlatformDirectories();

		foreach ($platform_paths as $p)
		{
			$locations[] = $p . '/Filter';
		}

		Factory::getLog()->debug('Loading filters');

		foreach ($locations as $folder)
		{
			if (!@is_dir($folder))
			{
				continue;
			}

			if (!@is_readable($folder))
			{
				continue;
			}

			$di = new DirectoryIterator($folder);

			foreach ($di as $file)
			{
				if (!$file->isFile())
				{
					continue;
				}

				// PHP 5.3.5 and earlier do not support getExtension
				if ($file->getExtension() != 'php')
				{
					continue;
				}

				$filename = $file->getFilename();

				// Skip filter files starting with dot or dash
				if (in_array(substr($filename, 0, 1), ['.', '_']))
				{
					continue;
				}

				// Some hosts copy .json and .php files, renaming them (ie foobar.1.php)
				// We need to exclude them, otherwise we'll get a fatal error for declaring the same class twice
				$bare_name = $file->getBasename('.php');

				if (preg_match('/[^a-zA-Z0-9]/', $bare_name))
				{
					continue;
				}

				// Extract filter base name
				$filter_name = ucfirst($bare_name);

				// This is an abstract class; do not try to create instance
				if ($filter_name == 'Base')
				{
					continue;
				}

				// Skip already loaded filters
				if (array_key_exists($filter_name, $this->filters))
				{
					continue;
				}

				Factory::getLog()->debug('-- Loading filter ' . $filter_name);

				// Add the filter
				$this->filters[$filter_name] = Factory::getFilterObject($filter_name);
			}
		}

		// Load platform, plugin and core stacked filters
		$locations = [
			Factory::getAkeebaRoot() . '/Filter/Stack',
		];

		$platform_paths       = Platform::getInstance()->getPlatformDirectories();
		$platform_stack_paths = [];

		foreach ($platform_paths as $p)
		{
			$locations[]            = $p . '/Filter';
			$locations[]            = $p . '/Filter/Stack';
			$platform_stack_paths[] = $p . '/Filter/Stack';
		}

		$config = Factory::getConfiguration();
		Factory::getLog()->debug('Loading optional filters');

		foreach ($locations as $folder)
		{
			if (!@is_dir($folder))
			{
				continue;
			}

			if (!@is_readable($folder))
			{
				continue;
			}

			$di = new DirectoryIterator($folder);

			/** @var DirectoryIterator $file */
			foreach ($di as $file)
			{
				if (!$file->isFile())
				{
					continue;
				}

				// PHP 5.3.5 and earlier do not support getExtension
				// if ($file->getExtension() != 'php')
				if (substr($file->getBasename(), -4) != '.php')
				{
					continue;
				}

				// Some hosts copy .json and .php files, renaming them (ie foobar.1.php)
				// We need to exclude them, otherwise we'll get a fatal error for declaring the same class twice
				$bare_name = strtolower($file->getBasename('.php'));

				if (preg_match('/[^A-Za-z0-9]/', $bare_name))
				{
					continue;
				}

				// Extract filter base name
				if (substr($bare_name, 0, 5) == 'stack')
				{
					$bare_name = substr($bare_name, 5);
				}

				$filter_name = 'Stack\\Stack' . ucfirst($bare_name);

				// Skip already loaded filters
				if (array_key_exists($filter_name, $this->filters))
				{
					continue;
				}

				// Make sure the JSON file also exists
				if (!file_exists($folder . '/' . $bare_name . '.json'))
				{
					continue;
				}

				$key = "core.filters.$bare_name.enabled";

				if ($config->get($key, 0))
				{
					Factory::getLog()->debug('-- Loading optional filter ' . $filter_name);
					// Add the filter
					$this->filters[$filter_name] = Factory::getFilterObject($filter_name);
				}
			}
		}
	}

	/**
	 * Extended filtering information of a given object. Applies only to exclusion filters.
	 *
	 * @param   string|array  $test       The string to check for filter status (e.g. filename, dir name, table name,
	 *                                    etc)
	 * @param   string        $root       The exclusion root test belongs to
	 * @param   string        $object     What type of object is it? dir|file|dbobject
	 * @param   string        $subtype    Filter subtype (all|content|children)
	 * @param   string        $by_filter  [out] The filter name which first matched $test, or an empty string
	 *
	 * @return  bool  True if it is a filtered element
	 */
	public function isFilteredExtended($test, $root, $object, $subtype, &$by_filter)
	{
		if (!$this->cleanup_has_run)
		{
			// Loop the filters and clean up those with no data
			/**
			 * @var string     $filter_name
			 * @var FilterBase $filter
			 */
			foreach ($this->filters as $filter_name => $filter)
			{
				if (!$filter->hasFilters())
				{
					unset($this->filters[$filter_name]);
				} // Remove empty filters
			}
			$this->cleanup_has_run = true;
		}

		$by_filter = '';
		if (!empty($this->filters))
		{
			foreach ($this->filters as $filter_name => $filter)
			{
				if ($filter->isFiltered($test, $root, $object, $subtype))
				{
					$by_filter = strtolower($filter_name);

					return true;
				}
			}

			// If we are still here, no filter matched
			return false;
		}
		else
		{
			return false;
		}
	}

	/**
	 * Returns the filtering status of a given object
	 *
	 * @param   string|array  $test     The string to check for filter status (e.g. filename, dir name, table name, etc)
	 * @param   string        $root     The exclusion root test belongs to
	 * @param   string        $object   What type of object is it? dir|file|dbobject
	 * @param   string        $subtype  Filter subtype (all|content|children)
	 *
	 * @return  bool  True if it is a filtered element
	 */
	public function isFiltered($test, $root, $object, $subtype)
	{
		$by_filter = '';

		return $this->isFilteredExtended($test, $root, $object, $subtype, $by_filter);
	}

	/**
	 * Returns the inclusion filters for a specific object type
	 *
	 * @param   string  $object  The inclusion object (dir|db)
	 *
	 * @return array
	 */
	public function &getInclusions($object)
	{
		$inclusions = [];

		if (!empty($this->filters))
		{
			/**
			 * @var string     $filter_name
			 * @var FilterBase $filter
			 */
			foreach ($this->filters as $filter_name => $filter)
			{
				if (!is_object($filter))
				{
					throw new RuntimeException("Object for filter $filter_name not found. The engine will now crash.");
				}

				$new_inclusions = $filter->getInclusions($object);

				if (!empty($new_inclusions))
				{
					$inclusions = array_merge($inclusions, $new_inclusions);
				}
			}
		}

		return $inclusions;
	}

	/**
	 * Returns the filter registry information for a specified filter class
	 *
	 * @param   string  $filter_name  The name of the filter we want data for
	 *
	 * @return    array    The filter data for the requested filter
	 */
	public function &getFilterData($filter_name)
	{
		if (array_key_exists($filter_name, $this->filter_registry))
		{
			return $this->filter_registry[$filter_name];
		}
		else
		{
			$dummy = [];

			return $dummy;
		}
	}

	/**
	 * Replaces the filter data of a specific filter with the new data
	 *
	 * @param   string  $filter_name  The filter for which to modify the stored data
	 * @param   string  $data         The new data
	 */
	public function setFilterData($filter_name, &$data)
	{
		$this->filter_registry[$filter_name] = $data;
	}

	/**
	 * Saves all filters to the platform defined database
	 *
	 * @return bool    True on success
	 */
	public function save()
	{
		return Platform::getInstance()->save_filters($this->filter_registry);
	}

	/**
	 * Get SQL statements to append to the database backup file
	 *
	 * @param   string  $root
	 *
	 * @return  array
	 */
	public function getExtraSQL(string $root): array
	{
		if (count($this->filters) < 1)
		{
			return [];
		}

		$ret = [];

		/**
		 * @var FilterBase $filter
		 */
		foreach ($this->filters as $filter)
		{
			$ret = array_merge($ret, $filter->getExtraSQL($root));
		}

		return $ret;
	}

	public function filterDatabaseRowContent(string $root, string $tableAbstract, array &$row): void
	{
		foreach ($this->filters as $filter)
		{
			$filter->filterDatabaseRowContent($root, $tableAbstract, $row);
		}
	}

	public function canFilterDatabaseRowContent(): bool
	{
		return array_reduce($this->filters, function (bool $carry, FilterBase $filter) {
			return $carry || $filter->canFilterDatabaseRowContent;
		}, false);
	}

	/**
	 * Checks if there is an active filter for the object/subtype requested.
	 *
	 * @param   string  $object   The filtering object: dir|file|dbobject|db
	 * @param   string  $subtype  The filtering subtype: all|content|children|inclusion
	 *
	 * @return bool
	 */
	public function hasFilterType($object, $subtype = null)
	{
		foreach ($this->filters as $filter_name => $filter)
		{
			if ($filter->object == $object)
			{
				if (is_null($subtype))
				{
					return true;
				}
				elseif ($filter->subtype == $subtype)
				{
					return true;
				}
			}
		}

		return false;
	}

	/**
	 * Resets all filters, reverting them to a blank state
	 *
	 * @return  void
	 *
	 * @since   5.4.0
	 */
	public function reset()
	{
		$this->filter_registry = [];
	}
}
PK�!]��3��BackupEngine/Core/01.basic.jsonnu&1i�{
    "_group": {
        "description": "COM_AKEEBA_CONFIG_HEADER_BASIC"
    },
    "akeeba.basic.output_directory": {
        "default": "[DEFAULT_OUTPUT]",
        "type": "browsedir",
        "title": "COM_AKEEBA_CONFIG_OUTDIR_TITLE",
        "description": "COM_AKEEBA_CONFIG_OUTDIR_DESCRIPTION"
    },
    "akeeba.basic.log_level": {
        "default": "4",
        "type": "enum",
        "enumkeys": "COM_AKEEBA_CONFIG_LOGLEVEL_NONE|COM_AKEEBA_CONFIG_LOGLEVEL_WARNING|COM_AKEEBA_CONFIG_LOGLEVEL_DEBUG",
        "enumvalues": "0|2|4",
        "title": "COM_AKEEBA_CONFIG_LOGLEVEL_TITLE",
        "description": "COM_AKEEBA_CONFIG_LOGLEVEL_DESCRIPTION"
    },
    "akeeba.basic.archive_name": {
        "default": "site-[HOST]-[DATE]-[TIME_TZ]-[RANDOM]",
        "type": "string",
        "title": "COM_AKEEBA_CONFIG_ARCHIVENAME_TITLE",
        "description": "COM_AKEEBA_CONFIG_ARCHIVENAME_DESCRIPTION"
    },
    "akeeba.basic.backup_type": {
        "default": "full",
        "type": "enum",
        "enumkeys": "COM_AKEEBA_CONFIG_BACKUPTYPE_FULL|COM_AKEEBA_CONFIG_BACKUPTYPE_DBONLY",
        "enumvalues": "full|dbonly",
        "title": "COM_AKEEBA_CONFIG_BACKUPTYPE_TITLE",
        "description": "COM_AKEEBA_CONFIG_BACKUPTYPE_DESCRIPTION"
    },
    "akeeba.basic.clientsidewait": {
        "default": "0",
        "type": "bool",
        "title": "COM_AKEEBA_CONFIG_CLIENTSIDEWAIT_TITLE",
        "description": "COM_AKEEBA_CONFIG_CLIENTSIDEWAIT_DESCRIPTION"
    }
}PK�!]��Y	Y	
akeeba.xmlnu&1i�<?xml version="1.0" encoding="utf-8"?>
<!--~
  ~ @package   akeebabackup
  ~ @copyright Copyright (c)2006-2023 Nicholas K. Dionysopoulos / Akeeba Ltd
  ~ @license   GNU General Public License version 3, or later
  -->

<extension version="3.9.0" type="component" method="upgrade">
	<name>Akeeba</name>
	<creationDate>2025-05-09</creationDate>
	<author>Nicholas K. Dionysopoulos</author>
	<authorEmail>nicholas@dionysopoulos.me</authorEmail>
	<authorUrl>https://www.akeeba.com</authorUrl>
	<copyright>Copyright (c)2006-2019 Akeeba Ltd / Nicholas K. Dionysopoulos</copyright>
	<license>GNU GPL v3 or later</license>
	<version>8.4.1</version>
	<description>Akeeba Backup Core - Full Joomla! site backup solution, Core Edition.</description>

	<!-- Public front end files -->
	<files folder="frontend">
		<folder>Dispatcher</folder>
		<filename>akeeba.php</filename>
	</files>

	<!-- Front end translation files -->
	<languages folder="language/frontend">
		<language tag="en-GB">en-GB/en-GB.com_akeeba.ini</language>
	</languages>

	<!-- Media files -->
	<media destination="com_akeeba" folder="media">
		<folder>css</folder>
		<folder>fonts</folder>
		<folder>icons</folder>
		<folder>js</folder>
	</media>

	<!-- Administrator back-end section -->
	<administration>
		<!-- Administration menu -->
		<menu>COM_AKEEBA</menu>

		<!-- Back-end files -->
		<files folder="backend">
			<folder>backup</folder>
			<folder>BackupEngine</folder>
			<folder>BackupPlatform</folder>
			<folder>CliCommands</folder>
			<folder>Controller</folder>
			<folder>Dispatcher</folder>
			<folder>fields</folder>
			<folder>Helper</folder>
			<folder>Master</folder>
			<folder>Model</folder>
			<folder>sql</folder>
			<folder>Toolbar</folder>
			<folder>View</folder>
			<folder>tmpl</folder>

			<filename>akeeba.php</filename>
			<filename>fof.xml</filename>
			<filename>version.php</filename>
			<filename>config.xml</filename>
			<filename>access.xml</filename>
			<filename>CHANGELOG.php</filename>
			<filename>Container.php</filename>
		</files>

		<!-- Back-end translation files -->
		<languages folder="language/backend">
			<language tag="en-GB">en-GB/en-GB.com_akeeba.ini</language>
			<language tag="en-GB">en-GB/en-GB.com_akeeba.sys.ini</language>
		</languages>

	</administration>

	<!-- Installation / uninstallation script file -->
	<scriptfile>script.com_akeeba.php</scriptfile>
</extension>
PK�!]qv�����script.com_akeeba.phpnu&1i�PK�!]|��N�fields/web.confignu&1i�PK�!]�J���u�fields/urlencoded.phpnu&1i�PK�!]��ѡ����fields/.htaccessnu&1i�PK�!]	�}^��fields/fancyradio.phpnu&1i�PK�!]�s�����fields/oauth2url.phpnu&1i�PK�!]�r��==��fields/backupprofiles.phpnu&1i�PK�!]7=v3`�fields/akencrypted.phpnu&1i�PK�!]/y-g��
��Container.phpnu&1i�PK�!]
��}}$��views/Configuration/tmpl/default.xmlnu�[���PK�!]����oo#X�views/ControlPanel/tmpl/default.xmlnu�[���PK�!]��S�oo�views/Manage/tmpl/default.xmlnu�[���PK�!]�Aͼ���views/Backup/tmpl/default.xmlnu�[���PK�!]
�!�CliCommands/ProfileCopy.phpnu&1i�PK�!]:�llCCliCommands/FilterList.phpnu&1i�PK�!]�ɮ�tt�.CliCommands/BackupDownload.phpnu&1i�PK�!]�X�����DCliCommands/ProfileImport.phpnu&1i�PK�!]Qx���
�
�TCliCommands/SysconfigGet.phpnu&1i�PK�!]�u	u	`CliCommands/BackupInfo.phpnu&1i�PK�!]����
�
�iCliCommands/ProfileDelete.phpnu&1i�PK�!]k2��..uCliCommands/SysconfigSet.phpnu&1i�PK�!]wt�`W
W
��CliCommands/ProfileReset.phpnu&1i�PK�!]��]�YY(�CliCommands/FilterDelete.phpnu&1i�PK�!]TFz_??͠CliCommands/MixIt/IsPro.phpnu&1i�PK�!]E!|
MM!W�CliCommands/MixIt/FilterRoots.phpnu&1i�PK�!]��c2��'��CliCommands/MixIt/JsonGuiDataParser.phpnu&1i�PK�!]~�1�!!)�CliCommands/MixIt/PrintFormattedArray.phpnu&1i�PK�!]�5K�EE&\�CliCommands/MixIt/ComponentOptions.phpnu&1i�PK�!]��\!��CliCommands/MixIt/ConfigureIO.phpnu&1i�PK�!]7^P���f�CliCommands/MixIt/TimeInfo.phpnu&1i�PK�!]�I��{{'c�CliCommands/MixIt/ArgumentUtilities.phpnu&1i�PK�!]���}} 5�CliCommands/MixIt/MemoryInfo.phpnu&1i�PK�!]i<��'	'	�CliCommands/SysconfigList.phpnu&1i�PK�!]�[��v�CliCommands/BackupModify.phpnu&1i�PK�!]EK��a�CliCommands/ProfileCreate.phpnu&1i�PK�!]_Eۦ���CliCommands/OptionsList.phpnu&1i�PK�!]@iG0�CliCommands/BackupList.phpnu&1i�PK�!]����]]3CliCommands/FilterExclude.phpnu&1i�PK�!]�g)����CCliCommands/ProfileModify.phpnu&1i�PK�!]&ފc�PCliCommands/ProfileExport.phpnu&1i�PK�!]MI[��5]CliCommands/LogList.phpnu&1i�PK�!]cVA��	�	lCliCommands/ProfileList.phpnu&1i�PK�!]�ST��vCliCommands/OptionsGet.phpnu&1i�PK�!]
�����CliCommands/BackupDelete.phpnu&1i�PK�!]�=İY
Y
ҒCliCommands/LogGet.phpnu&1i�PK�!]\�7�q�CliCommands/OptionsSet.phpnu&1i�PK�!]��ѡ��δMaster/.htaccessnu&1i�PK�!]|��N��Master/web.confignu&1i�PK�!]h�+��(
�Master/Installers/kickstart.transfer.phpnu&1i�PK�!].Vߙ��
�Master/Installers/angie.jpanu&1i�PK�!]!4�y�y�j�Master/Installers/kickstart.txtnu&1i�PK�!]A�{Aii2�Master/Installers/angie.jsonnu&1i�PK�!]k��66�Master/Installers/index.htmlnu&1i�PK�!]y~�=hhi�Master/Installers/none.ininu&1i�PK�!])���cqcq"�Master/Installers/angie-joomla.jpanu&1i�PK�!]A�{Aii#�Master/Installers/angie-joomla.jsonnu&1i�PK�!]չQ���Master/Installers/none.jsonnu&1i�PK�!]kX0q���Master/Installers/web.confignu&1i�PK�!]�����Master/Installers/.htaccessnu&1i�PK�!]蕿���yMaster/Installers/angie.ininu&1i�PK�!]-c=:pp� Master/Stats/usagestats.phpnu&1i�PK�!]��ѡ��d)BackupPlatform/.htaccessnu&1i�PK�!]���2�t�t$R*BackupPlatform/Joomla3x/Platform.phpnu&1i�PK�!]�ܛKK)y�BackupPlatform/Joomla3x/Driver/Joomla.phpnu&1i�PK�!]s��b��/�BackupPlatform/Joomla3x/Config/02.advanced.jsonnu&1i�PK�!]�aa2V�BackupPlatform/Joomla3x/Filter/Joomlaskipfiles.phpnu&1i�PK�!]�x����3�BackupPlatform/Joomla3x/Filter/Excludetabledata.phpnu&1i�PK�!]��EJ``1�BackupPlatform/Joomla3x/Filter/Joomlaskipdirs.phpnu&1i�PK�!]Q��))-��BackupPlatform/Joomla3x/Filter/Cvsfolders.phpnu&1i�PK�!]ެ�n��+f�BackupPlatform/Joomla3x/Filter/Siteroot.phpnu&1i�PK�!]n�hh3V�BackupPlatform/Joomla3x/Filter/Systemcachefiles.phpnu&1i�PK�!]�L���,!�BackupPlatform/Joomla3x/Filter/Libraries.phpnu&1i�PK�!]ώ{�0e�BackupPlatform/Joomla3x/Filter/Stack/finder.jsonnu&1i�PK�!]�FW�  4��BackupPlatform/Joomla3x/Filter/Stack/actionlogs.jsonnu&1i�PK�!]�:

4]�BackupPlatform/Joomla3x/Filter/Stack/StackFinder.phpnu&1i�PK�!]{O998�BackupPlatform/Joomla3x/Filter/Stack/StackActionlogs.phpnu&1i�PK�!]G�
y�
�
)iBackupPlatform/Joomla3x/Filter/Sitedb.phpnu&1i�PK�!]1ɠ
dd/�BackupPlatform/Joomla3x/Filter/Excludefiles.phpnu&1i�PK�!]�3)�1oBackupPlatform/Joomla3x/Filter/Excludefolders.phpnu&1i�PK�!]|��N�BackupPlatform/web.confignu&1i�PK�!]/���::<fof.xmlnu&1i�PK�!]�<�s^�^�
�!CHANGELOG.phpnu&1i�PK�!]��ѡ��
H�sql/.htaccessnu&1i�PK�!]#v�jj+�sql/xml/mysql.xmlnu&1i�PK�!]|��N��sql/web.confignu&1i�PK�!]L�J���)�sql/index.htmlnu&1i�PK�!]X�%!33
Q�config.xmlnu&1i�PK�!]���cdd��View/FileFilters/Html.phpnu&1i�PK�!]��6�#�#:View/errorhandler.phpnu&1i�PK�!]�,�_$#$#Q4View/ControlPanel/Html.phpnu&1i�PK�!]� �U	U	�WView/Browser/Html.phpnu&1i�PK�!]�2��

YaView/fof.phpnu&1i�PK�!]�b�,�,�kView/wrongphp.phpnu&1i�PK�!]l�?���View/DatabaseFilters/Html.phpnu&1i�PK�!]��C!!
#�View/hhvm.phpnu&1i�PK�!]�|A@@��View/ViewTraits/ProfileList.phpnu&1i�PK�!]<B&�ww$�View/ViewTraits/ProfileIdAndName.phpnu&1i�PK�!]]�!��!۸View/ConfigurationWizard/Html.phpnu&1i�PK�!]s+��#
#
ԿView/Log/Html.phpnu&1i�PK�!]�s[tt8�View/Log/Raw.phpnu&1i�PK�!]�Eȥg:g:��View/Manage/Html.phpnu&1i�PK�!]X/GvLL�View/Backup/Html.phpnu&1i�PK�!]���؟�'"View/Configuration/Html.phpnu&1i�PK�!]��ѡ��1View/.htaccessnu&1i�PK�!]�q�99�1View/fef.phpnu&1i�PK�!]|��Nj8View/web.confignu&1i�PK�!]�ҥ.�:View/Profiles/Html.phpnu&1i�PK�!]��r^nn @View/Profiles/Json.phpnu&1i�PK�!]����
�
�AView/RegExFileFilter/Html.phpnu&1i�PK�!]<�rӘ���Mscript.akeeba.phpnu&1i�PK�!]㘉ħ���backup/web.confignu&1i�PK�!]����55��backup/.htaccessnu&1i�PK�!]�r���8�backup/index.htmnu&1i�PK�!]k��66'�backup/index.htmlnu&1i�PK�!]��h�77��backup/akeeba.log.phpnu�[���PK�!]��ѡ���Helper/.htaccessnu&1i�PK�!]�ْjVV�Helper/Utils.phpnu&1i�PK�!]޺��Helper/Upgrade.phpnu&1i�PK�!]|��N�Helper/web.confignu&1i�PK�!]qfd��6Helper/SecretWord.phpnu&1i�PK�!]#��__HHelper/Status.phpnu&1i�PK�!]f����-�/tmpl/ControlPanel/backup8_uninstall.blade.phpnu&1i�PK�!]�;z6��#�4tmpl/ControlPanel/profile.blade.phpnu&1i�PK�!]����oo�;tmpl/ControlPanel/default.xmlnu&1i�PK�!]�$>$+�=tmpl/ControlPanel/backup9_install.blade.phpnu&1i�PK�!]��))'�Qtmpl/ControlPanel/icons_basic.blade.phpnu&1i�PK�!]�{a�=	=	*rYtmpl/ControlPanel/sidebar_status.blade.phpnu&1i�PK�!]1A�331	ctmpl/ControlPanel/icons_troubleshooting.blade.phpnu&1i�PK�!]����6&6&$�gtmpl/ControlPanel/warnings.blade.phpnu&1i�PK�!]�����#'�tmpl/ControlPanel/upgrade.blade.phpnu&1i�PK�!]�VT���*j�tmpl/ControlPanel/sidebar_backup.blade.phpnu&1i�PK�!]n,&�}}$��tmpl/ControlPanel/oneclick.blade.phpnu&1i�PK�!]Զ��GG#��tmpl/ControlPanel/default.blade.phpnu&1i�PK�!]F^ɥ�0�tmpl/ControlPanel/icons_includeexclude.blade.phpnu&1i�PK�!]P��ff*"�tmpl/ControlPanel/icons_advanced.blade.phpnu&1i�PK�!]�ן���.�tmpl/ControlPanel/warning_phpversion.blade.phpnu&1i�PK�!],�"<�tmpl/ControlPanel/footer.blade.phpnu&1i�PK�!][���  !��tmpl/Profiles/item_json.blade.phpnu&1i�PK�!]�`��tmpl/Profiles/form.blade.phpnu&1i�PK�!]\�6���u�tmpl/Profiles/default.blade.phpnu&1i�PK�!]W^f��tmpl/Manage/comment.blade.phpnu&1i�PK�!]q���  #��tmpl/Manage/manage_column.blade.phpnu&1i�PK�!]�@n�1�1f�tmpl/Manage/default.blade.phpnu&1i�PK�!]�����(�/tmpl/Manage/howtorestore_modal.blade.phpnu&1i�PK�!]��S�oo�6tmpl/Manage/default.xmlnu&1i�PK�!]{�-_55&w8tmpl/DatabaseFilters/tabular.blade.phpnu&1i�PK�!]
��^^&?tmpl/DatabaseFilters/default.blade.phpnu&1i�PK�!]�cɸ���Dtmpl/Backup/script.blade.phpnu&1i�PK�!]�Aͼ���Ptmpl/Backup/default.xmlnu&1i�PK�!]�3�3Wtmpl/Backup/default.blade.phpnu&1i�PK�!]�G�i��"��tmpl/FileFilters/default.blade.phpnu&1i�PK�!]�1��uu"�tmpl/FileFilters/tabular.blade.phpnu&1i�PK�!]CCǚtmpl/Browser/default.blade.phpnu&1i�PK�!]9j�[)))X�tmpl/CommonTemplates/FTPBrowser.blade.phpnu&1i�PK�!]Xk/!!ڹtmpl/CommonTemplates/hhvm.phpnu&1i�PK�!]N8Ҍ��0H�tmpl/CommonTemplates/FTPConnectionTest.blade.phpnu&1i�PK�!]�oy�h�tmpl/CommonTemplates/fof.phpnu&1i�PK�!]�;����)��tmpl/CommonTemplates/ErrorModal.blade.phpnu&1i�PK�!]�BB*��tmpl/CommonTemplates/SFTPBrowser.blade.phpnu&1i�PK�!]�;�99i�tmpl/CommonTemplates/fef.phpnu&1i�PK�!]T�#_�+�+%��tmpl/CommonTemplates/errorhandler.phpnu&1i�PK�!]�EĜ��,6tmpl/CommonTemplates/FolderBrowser.blade.phpnu&1i�PK�!]�g��*&tmpl/CommonTemplates/ProfileName.blade.phpnu&1i�PK�!]â_���&tmpl/RegExFileFilter/default.blade.phpnu&1i�PK�!])�u�nn)7tmpl/ConfigurationWizard/wizard.blade.phpnu&1i�PK�!]
��}}�%tmpl/Configuration/default.xmlnu&1i�PK�!]=�^^*�'tmpl/Configuration/confwiz_modal.blade.phpnu&1i�PK�!]�gF[[$�0tmpl/Configuration/default.blade.phpnu&1i�PK�!]����	�	0=tmpl/Log/default.blade.phpnu&1i�PK�!]<Z�__Gtmpl/Log/raw.blade.phpnu&1i�PK�!]J6p).).�NToolbar/Toolbar.phpnu&1i�PK�!]��ѡ��}Toolbar/.htaccessnu&1i�PK�!]|��N�}Toolbar/web.confignu&1i�PK�!]��3OOS�Model/SFTPBrowser.phpnu&1i�PK�!]h�]]'�Model/Exceptions/TransferFatalError.phpnu&1i�PK�!]��Q�\\&��Model/Exceptions/FrozenRecordError.phpnu&1i�PK�!]��8[aa+M�Model/Exceptions/TransferIgnorableError.phpnu&1i�PK�!]|��N	�Model/web.confignu&1i�PK�!]���t>t>^�Model/ConfigurationWizard.phpnu&1i�PK�!]6�ب/�/�Model/FileFilters.phpnu&1i�PK�!]��ۆ�	Model/DatabaseFilters.phpnu&1i�PK�!]��yf;*;*�&Model/Updates.phpnu&1i�PK�!]���wHHWQModel/Backup.phpnu&1i�PK�!]��ѡ����Model/.htaccessnu&1i�PK�!]��ռOFOF��Model/Statistics.phpnu&1i�PK�!]i"�L��.�Model/Browser.phpnu&1i�PK�!]��aa��Model/ControlPanel.phpnu&1i�PK�!]��JU��'WNModel/Mixin/GetErrorsFromExceptions.phpnu&1i�PK�!]Kq{
���SModel/Mixin/Chmod.phpnu&1i�PK�!]E�ZU���ZModel/Mixin/ExclusionFilter.phpnu&1i�PK�!]Ӳ����gModel/Configuration.phpnu&1i�PK�!]�/{/
�~Model/Log.phpnu&1i�PK�!]*:�h����Model/FTPBrowser.phpnu&1i�PK�!]]�b�

��Model/Profiles.phpnu&1i�PK�!]ܣa%%	�Model/UsageStatistics.phpnu&1i�PK�!]
�=�
w�access.xmlnu&1i�PK�!]��ѡ����Dispatcher/.htaccessnu&1i�PK�!]\yO�'�'��Dispatcher/Dispatcher.phpnu&1i�PK�!]|��N��Dispatcher/web.confignu&1i�PK�!]S�%�WW��version.phpnu&1i�PK�!]X���"��Controller/ConfigurationWizard.phpnu&1i�PK�!]��ѡ���Controller/.htaccessnu&1i�PK�!]�y.����Controller/DatabaseFilters.phpnu&1i�PK�!]�x9rS#S#qController/ControlPanel.phpnu&1i�PK�!]����2Controller/Profiles.phpnu&1i�PK�!]XR�

�>Controller/Log.phpnu&1i�PK�!]��7��7LController/FTPBrowser.phpnu&1i�PK�!]!|�f��RController/Mixin/CustomACL.phpnu&1i�PK�!]O��k��'FZController/Mixin/PredefinedTaskList.phpnu&1i�PK�!]} �<66*aController/Backup.phpnu&1i�PK�!]�P�SCC�yController/Profile.phpnu&1i�PK�!]���,��.{Controller/SFTPBrowser.phpnu&1i�PK�!]����%�%��Controller/Manage.phpnu&1i�PK�!]|��N�Controller/web.confignu&1i�PK�!]����o"o"D�Controller/Configuration.phpnu&1i�PK�!]%���""��Controller/Browser.phpnu&1i�PK�!]���*��g�Controller/FileFilters.phpnu&1i�PK�!]�e�	�	
4�akeeba.phpnu&1i�PK�!]��߯��
Z�index.htmlnu&1i�PK�!]���q q ~�BackupEngine/Platform.phpnu&1i�PK�!]��0\#\#8�BackupEngine/Driver/None.phpnu&1i�PK�!]*�Z~N~N�"BackupEngine/Driver/Sqlite.phpnu&1i�PK�!]�4��%g%g�qBackupEngine/Driver/Mysql.phpnu&1i�PK�!]��==�BackupEngine/Driver/Mysqli.phpnu&1i�PK�!]&)����&�BackupEngine/Driver/QueryException.phpnu&1i�PK�!]�������"�BackupEngine/Driver/Query/Base.phpnu&1i�PK�!]@1�٤�$бBackupEngine/Driver/Query/Mysqli.phpnu&1i�PK�!]U*����&ȽBackupEngine/Driver/Query/Pdomysql.phpnu&1i�PK�!]��}�		'�BackupEngine/Driver/Query/Limitable.phpnu&1i�PK�!]'�����#��BackupEngine/Driver/Query/Mysql.phpnu&1i�PK�!]�Kd�
�
%��BackupEngine/Driver/Query/Element.phpnu&1i�PK�!]`#�
��$��BackupEngine/Driver/Query/Sqlite.phpnu&1i�PK�!]��7
7
(��BackupEngine/Driver/Query/Preparable.phpnu&1i�PK�!]�b�\�\�k�BackupEngine/Driver/Base.phpnu&1i�PK�!]�t��I�I �BackupEngine/Driver/Pdomysql.phpnu&1i�PK�!]|��N0�BackupEngine/web.confignu&1i�PK�!]��3��7��BackupEngine/Platform/Exception/DecryptionException.phpnu&1i�PK�!]~����0�0+}�BackupEngine/Platform/PlatformInterface.phpnu&1i�PK�!]l)qtqt�BackupEngine/Platform/Base.phpnu&1i�PK�!]��@^^q�BackupEngine/Scan/Base.phpnu&1i�PK�!]n��PP�BackupEngine/Scan/Smart.phpnu&1i�PK�!]�c������BackupEngine/Scan/smart.jsonnu&1i�PK�!]�Y��!!/�BackupEngine/Base/Exceptions/ErrorException.phpnu&1i�PK�!]h}�1e�BackupEngine/Base/Exceptions/WarningException.phpnu&1i�PK�!]��\U�:�:��BackupEngine/Base/Part.phpnu&1i�PK�!]+0"���'�BackupEngine/Psr/Log/AbstractLogger.phpnu&1i�PK�!]�	~�>	>	1� BackupEngine/Psr/Log/InvalidArgumentException.phpnu&1i�PK�!]�ͭVV$� BackupEngine/Psr/Log/LoggerTrait.phpnu&1i�PK�!]�G;�

)-1 BackupEngine/Psr/Log/LoggerAwareTrait.phpnu&1i�PK�!]w����	�	-�; BackupEngine/Psr/Log/LoggerAwareInterface.phpnu&1i�PK�!]�<^�	�	!�E BackupEngine/Psr/Log/LogLevel.phpnu&1i�PK�!]7'�L��(=P BackupEngine/Psr/Log/LoggerInterface.phpnu&1i�PK�!]>	��BB#Fd BackupEngine/Psr/Log/NullLogger.phpnu&1i�PK�!]|�"yy%�o BackupEngine/Postproc/Onedriveapp.phpnu&1i�PK�!]��	�	3�� BackupEngine/Postproc/onedrivebusiness.ini-disablednu&1i�PK�!]��8�VV�� BackupEngine/Postproc/Email.phpnu&1i�PK�!]��y��d� BackupEngine/Postproc/Base.phpnu&1i�PK�!]����+2� BackupEngine/Postproc/PostProcInterface.phpnu&1i�PK�!]��4�00 �� BackupEngine/Postproc/email.jsonnu&1i�PK�!]�̵��� BackupEngine/Postproc/None.phpnu&1i�PK�!]����	�	$�� BackupEngine/Postproc/ProxyAware.phpnu&1i�PK�!]�4����A
� BackupEngine/Postproc/Exception/DownloadToBrowserNotSupported.phpnu&1i�PK�!]R����5=� BackupEngine/Postproc/Exception/OAuthNotSupported.phpnu&1i�PK�!]Č��@�� BackupEngine/Postproc/Exception/DownloadToServerNotSupported.phpnu&1i�PK�!]��2x��=�� BackupEngine/Postproc/Exception/RangeDownloadNotSupported.phpnu&1i�PK�!]�6��4�� BackupEngine/Postproc/Exception/BadConfiguration.phpnu&1i�PK�!]����GG3*� BackupEngine/Postproc/Exception/EngineException.phpnu&1i�PK�!]j���6�!BackupEngine/Postproc/Exception/DeleteNotSupported.phpnu&1i�PK�!]Ğ^����!BackupEngine/Postproc/none.jsonnu&1i�PK�!]�p]�u	u	&�
!BackupEngine/Postproc/onedriveapp.jsonnu&1i�PK�!]�'�i1�!BackupEngine/Util/Log/WarningsLoggerInterface.phpnu&1i�PK�!]�
<�rr-�!BackupEngine/Util/Log/WarningsLoggerAware.phpnu&1i�PK�!]�c�nn&�*!BackupEngine/Util/Log/LogInterface.phpnu&1i�PK�!]���atftf&�6!BackupEngine/Util/EngineParameters.phpnu&1i�PK�!]C�rr$W�!BackupEngine/Util/FactoryStorage.phpnu&1i�PK�!]�Ôii6�!BackupEngine/Util/Transfer/RemoteResourceInterface.phpnu&1i�PK�!]R=G��C�C"�!BackupEngine/Util/Transfer/Ftp.phpnu&1i�PK�!]Nj�dd0$"BackupEngine/Util/Transfer/TransferInterface.phpnu&1i�PK�!]�=�c�L�L&�"BackupEngine/Util/Transfer/FtpCurl.phpnu&1i�PK�!]�;f��:�:#=h"BackupEngine/Util/Transfer/Sftp.phpnu&1i�PK�!]�du�M�M'r�"BackupEngine/Util/Transfer/SftpCurl.phpnu&1i�PK�!]����I�"BackupEngine/Util/Utf8.phpnu&1i�PK�!]i/C�=�= ��"BackupEngine/Util/Complexify.phpnu&1i�PK�!]���KK$�;#BackupEngine/Util/SecureSettings.phpnu&1i�PK�!]&����+pR#BackupEngine/Util/PushMessagesInterface.phpnu&1i�PK�!]��Y�bb$�Z#BackupEngine/Util/TemporaryFiles.phpnu&1i�PK�!]�K]ZQQ Qs#BackupEngine/Util/FileLister.phpnu&1i�PK�!]��-7�l�l�#BackupEngine/Util/Encrypt.phpnu&1i�PK�!]��0PWW 4�#BackupEngine/Util/Statistics.phpnu&1i�PK�!]Ă��;;&�
$BackupEngine/Util/ProfileMigration.phpnu&1i�PK�!]�G�+;;l&$BackupEngine/Util/Logger.phpnu&1i�PK�!]�Siz�-�-#�a$BackupEngine/Util/ListingParser.phpnu&1i�PK�!]���9�9(ӏ$BackupEngine/Util/ConfigurationCheck.phpnu&1i�PK�!]�-�X��1��$BackupEngine/Util/AesAdapter/AdapterInterface.phpnu&1i�PK�!]n
_��
�
'��$BackupEngine/Util/AesAdapter/Mcrypt.phpnu&1i�PK�!]ju,�	�	0��$BackupEngine/Util/AesAdapter/AbstractAdapter.phpnu&1i�PK�!]Qҙ�gg(�$BackupEngine/Util/AesAdapter/OpenSSL.phpnu&1i�PK�!]���LKK�%BackupEngine/Util/CRC32.phpnu&1i�PK�!]ƈ���!X
%BackupEngine/Util/RandomValue.phpnu&1i�PK�!]�"����%BackupEngine/Util/Buffer.phpnu&1i�PK�!]ݢq  "�2%BackupEngine/Util/PushMessages.phpnu&1i�PK�!]��ttD%BackupEngine/Util/ParseIni.phpnu&1i�PK�!]ju@E��-�c%BackupEngine/Util/Pushbullet/ApiException.phpnu&1i�PK�!]�π1�1*h%BackupEngine/Util/Pushbullet/Connector.phpnu&1i�PK�!]�<��^^$�%BackupEngine/Util/FileCloseAware.phpnu&1i�PK�!]����?8?8 ��%BackupEngine/Util/FileSystem.phpnu&1i�PK�!]e�?�""!�%BackupEngine/Util/HashTrait.phpnu&1i�PK�!]4�ٹBtBt��%BackupEngine/Factory.phpnu&1i�PK�!]��ѡ��X&BackupEngine/.htaccessnu&1i�PK�!]N���zz!Y&BackupEngine/FixMySQLHostname.phpnu&1i�PK�!]��#���u&BackupEngine/Autoloader.phpnu&1i�PK�!]d��cvv�&BackupEngine/serverkey.phpnu&1i�PK�!]��dYdY&Ѓ&BackupEngine/Archiver/BaseArchiver.phpnu&1i�PK�!]�3�pPP��&BackupEngine/Archiver/jpa.jsonnu&1i�PK�!]��y�
[
[(�&BackupEngine/Archiver/Base.phpnu&1i�PK�!]_�

�A'BackupEngine/Archiver/zip.jsonnu&1i�PK�!]��4��,�K'BackupEngine/Archiver/BaseFileManagement.phpnu&1i�PK�!]g�.����e'BackupEngine/Archiver/Zip.phpnu&1i�PK�!]N:q��K�K��'BackupEngine/Archiver/Jpa.phpnu&1i�PK�!]Qr�3��%8(BackupEngine/Filter/Regexskipdirs.phpnu&1i�PK�!]�-Ҷ�%G=(BackupEngine/Filter/Stack/README.htmlnu&1i�PK�!]
��yYY2RF(BackupEngine/Filter/Stack/StackDateconditional.phpnu&1i�PK�!]�j�,
O(BackupEngine/Filter/Stack/StackErrorlogs.phpnu&1i�PK�!]	��w��,{U(BackupEngine/Filter/Stack/StackHoststats.phpnu&1i�PK�!]�����.�[(BackupEngine/Filter/Stack/dateconditional.jsonnu&1i�PK�!]�D(u^(BackupEngine/Filter/Stack/hoststats.jsonnu&1i�PK�!]&Y�(�_(BackupEngine/Filter/Stack/errorlogs.jsonnu&1i�PK�!]s�j[�� _a(BackupEngine/Filter/Skipdirs.phpnu&1i�PK�!]�M�|��(mf(BackupEngine/Filter/Regexdirectories.phpnu&1i�PK�!]��	���k(BackupEngine/Filter/Tables.phpnu&1i�PK�!].�A��&�p(BackupEngine/Filter/Regexskipfiles.phpnu&1i�PK�!]��7��#�u(BackupEngine/Filter/Regextables.phpnu&1i�PK�!]�̍�PP&�z(BackupEngine/Filter/Regextabledata.phpnu&1i�PK�!]�@�A??��(BackupEngine/Filter/Base.phpnu&1i�PK�!]�*���(BackupEngine/Filter/Multidb.phpnu&1i�PK�!]j����!��(BackupEngine/Filter/Skipfiles.phpnu&1i�PK�!]r���LL!�(BackupEngine/Filter/Tabledata.phpnu&1i�PK�!]P���#��(BackupEngine/Filter/Directories.phpnu&1i�PK�!]�R���"��(BackupEngine/Filter/Regexfiles.phpnu&1i�PK�!]��V\��!��(BackupEngine/Filter/Extradirs.phpnu&1i�PK�!]6�o3gg#��(BackupEngine/Filter/Incremental.phpnu&1i�PK�!]���+��(BackupEngine/Filter/Tablesalwaysskipped.phpnu&1i�PK�!]�튮�(�(BackupEngine/Filter/Files.phpnu&1i�PK�!]5Xc�
�
#�(BackupEngine/Dump/native.jsonnu&1i�PK�!]�~v�__H)BackupEngine/Dump/Native.phpnu&1i�PK�!]56T������)BackupEngine/Dump/Base.phpnu&1i�PK�!]�ԫ%QQ#̪)BackupEngine/Dump/Native/Sqlite.phpnu&1i�PK�!]����!p�)BackupEngine/Dump/Native/None.phpnu&1i�PK�!]@s�->>"��)BackupEngine/Dump/Native/Mysql.phpnu&1i�PK�!]Z2R��8�8K�*BackupEngine/Configuration.phpnu&1i�PK�!]/×W		�+BackupEngine/Core/04.quota.jsonnu&1i�PK�!]�y��

 �+BackupEngine/Core/05.tuning.jsonnu&1i�PK�!]|M0�JJi+BackupEngine/Core/Database.phpnu&1i�PK�!]N����� ,+BackupEngine/Core/scripting.jsonnu&1i�PK�!]^�E\��:;+BackupEngine/Core/Timer.phpnu&1i�PK�!]qE�*�*.T+BackupEngine/Core/Domain/Db.phpnu&1i�PK�!]�|B�h�h�!)+BackupEngine/Core/Domain/Pack.phpnu&1i�PK�!]��8:8:!�,BackupEngine/Core/Domain/Init.phpnu&1i�PK�!]��q!!&kG,BackupEngine/Core/Domain/Installer.phpnu&1i�PK�!]�-���;�a,BackupEngine/Core/Domain/Finalizer/RemoveTemporaryFiles.phpnu&1i�PK�!]���yy9h,BackupEngine/Core/Domain/Finalizer/FinalizerInterface.phpnu&1i�PK�!]�^�m�7�7>�n,BackupEngine/Core/Domain/Finalizer/AbstractQuotaManagement.phpnu&1i�PK�!]\#Co^^3<�,BackupEngine/Core/Domain/Finalizer/RemoteQuotas.phpnu&1i�PK�!]8�.M��2��,BackupEngine/Core/Domain/Finalizer/LocalQuotas.phpnu&1i�PK�!]0���<*�,BackupEngine/Core/Domain/Finalizer/ObsoleteRecordsQuotas.phpnu&1i�PK�!]�O�n
n
6^�,BackupEngine/Core/Domain/Finalizer/UpdateFileSizes.phpnu&1i�PK�!]�f�1�	�	82�,BackupEngine/Core/Domain/Finalizer/AbstractFinalizer.phpnu&1i�PK�!]
���#�#9��,BackupEngine/Core/Domain/Finalizer/MailAdministrators.phpnu&1i�PK�!]����	�	7�!-BackupEngine/Core/Domain/Finalizer/UpdateStatistics.phpnu&1i�PK�!]��V-�&�&5�+-BackupEngine/Core/Domain/Finalizer/PostProcessing.phpnu&1i�PK�!]#��*6�R-BackupEngine/Core/Domain/Finalizer/UploadKickstart.phpnu&1i�PK�!]��O�yy)B_-BackupEngine/Core/Domain/Finalization.phpnu&1i�PK�!]�^�vbvb~-BackupEngine/Core/Kettenrad.phpnu&1i�PK�!]ɷ|Q��"��-BackupEngine/Core/02.advanced.jsonnu&1i�PK�!]U��;r*r*�-BackupEngine/Core/Filters.phpnu&1i�PK�!]��3���.BackupEngine/Core/01.basic.jsonnu&1i�PK�!]��Y	Y	
�.akeeba.xmlnu&1i�PKyy��v .