Files
carpseeds.pl/wp-content/plugins/akeebabackupwp/app/Solo/engine/Platform/Base.php
2024-07-15 11:28:08 +02:00

1127 lines
28 KiB
PHP

<?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\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 = $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 = $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 = $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 = $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 = $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 = $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 = $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 = $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') . ' ASC');
$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 = $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 = $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 = $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 = $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 = $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 = $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 = $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 = $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 = $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 = $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()
{
}
}