Файловый менеджер - Редактировать - /home/wuectly/www/03cbe/Base.tar
Назад
Exceptions/ErrorException.php 0000604 00000002041 15245604442 0012345 0 ustar 00 <?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 { } Exceptions/WarningException.php 0000604 00000002020 15245604442 0012656 0 ustar 00 <?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 { } Part.php 0000604 00000035320 15245604442 0006170 0 ustar 00 <?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; } } Platform.php 0000604 00000024071 15245613324 0007046 0 ustar 00 <?php /** * @package FOF * @copyright Copyright (c)2010-2020 Nicholas K. Dionysopoulos / Akeeba Ltd * @license GNU General Public License version 2, or later */ namespace FOF30\Platform\Base; defined('_JEXEC') || die; use Exception; use FOF30\Container\Container; use FOF30\Input\Input; use FOF30\Platform\PlatformInterface; use Joomla\CMS\Document\Document; use Joomla\CMS\User\User; /** * Abstract implementation of the Platform integration * * @package FOF30\Platform\Base */ abstract class Platform implements PlatformInterface { /** @var Container The component container */ protected $container = null; /** @var bool Are plugins allowed to run in CLI mode? */ protected $allowPluginsInCli = false; /** * Public constructor. * * @param Container $c The component container */ public function __construct(Container $c) { $this->container = $c; } /** * Returns the base (root) directories for a given component. * * @param string $component The name of the component. For Joomla! this * is something like "com_example" * * @return array A hash array with keys main, alt, site and admin. * @see F0FPlatformInterface::getComponentBaseDirs() * */ public function getComponentBaseDirs($component) { return [ 'main' => '', 'alt' => '', 'site' => '', 'admin' => '', ]; } /** * Returns the application's template name * * @param boolean|array $params An optional associative array of configuration settings * * @return string The template name. System is the fallback. */ public function getTemplate($params = false) { return 'system'; } /** * Get application-specific suffixes to use with template paths. This allows * you to look for view template overrides based on the application version. * * @return array A plain array of suffixes to try in template names */ public function getTemplateSuffixes() { return []; } /** * Return the absolute path to the application's template overrides * directory for a specific component. We will use it to look for template * files instead of the regular component directories. If the application * does not have such a thing as template overrides return an empty string. * * @param string $component The name of the component for which to fetch the overrides * @param boolean $absolute Should I return an absolute or relative path? * * @return string The path to the template overrides directory */ public function getTemplateOverridePath($component, $absolute = true) { return ''; } /** * Load the translation files for a given component. * * @param string $component The name of the component. For Joomla! this * is something like "com_example" * * @return void * @see F0FPlatformInterface::loadTranslations() * */ public function loadTranslations($component) { return null; } /** * Authorise access to the component in the back-end. * * @param string $component The name of the component. * * @return boolean True to allow loading the component, false to halt loading * @see F0FPlatformInterface::authorizeAdmin() * */ public function authorizeAdmin($component) { return true; } /** * Returns the JUser object for the current user * * @param integer $id The ID of the user to fetch * * @return Document * @see F0FPlatformInterface::getUser() * */ public function getUser($id = null) { return null; } /** * Returns the JDocument object which handles this component's response. * * @return Document * @see F0FPlatformInterface::getDocument() * */ public function getDocument() { return null; } /** * This method will try retrieving a variable from the request (input) data. * * @param string $key The user state key for the variable * @param string $request The request variable name for the variable * @param Input $input The Input object with the request (input) data * @param mixed $default The default value. Default: null * @param string $type The filter type for the variable data. Default: none (no filtering) * @param boolean $setUserState Should I set the user state with the fetched value? * * @return mixed The value of the variable * @see F0FPlatformInterface::getUserStateFromRequest() * */ public function getUserStateFromRequest($key, $request, $input, $default = null, $type = 'none', $setUserState = true) { return $input->get($request, $default, $type); } /** * Load plugins of a specific type. Obviously this seems to only be required * in the Joomla! CMS. * * @param string $type The type of the plugins to be loaded * * @return void * @see F0FPlatformInterface::importPlugin() * */ public function importPlugin($type) { } /** * Execute plugins (system-level triggers) and fetch back an array with * their return values. * * @param string $event The event (trigger) name, e.g. onBeforeScratchMyEar * @param array $data A hash array of data sent to the plugins as part of the trigger * * @return array A simple array containing the results of the plugins triggered * @see F0FPlatformInterface::runPlugins() * */ public function runPlugins($event, $data) { return []; } /** * Perform an ACL check. * * @param string $action The ACL privilege to check, e.g. core.edit * @param string $assetname The asset name to check, typically the component's name * * @return boolean True if the user is allowed this action * @see F0FPlatformInterface::authorise() * */ public function authorise($action, $assetname) { return true; } /** * Is this the administrative section of the component? * * @return boolean * @see F0FPlatformInterface::isBackend() * */ public function isBackend() { return true; } /** * Is this the public section of the component? * * @return boolean * @see F0FPlatformInterface::isFrontend() * */ public function isFrontend() { return true; } /** * Is this a component running in a CLI application? * * @return boolean * @see F0FPlatformInterface::isCli() * */ public function isCli() { return true; } /** * Is AJAX re-ordering supported? This is 100% Joomla!-CMS specific. All * other platforms should return false and never ask why. * * @return boolean * @see F0FPlatformInterface::supportsAjaxOrdering() * */ public function supportsAjaxOrdering() { return true; } /** * Saves something to the cache. This is supposed to be used for system-wide * F0F data, not application data. * * @param string $key The key of the data to save * @param string $content The actual data to save * * @return boolean True on success */ public function setCache($key, $content) { return false; } /** * Retrieves data from the cache. This is supposed to be used for system-side * F0F data, not application data. * * @param string $key The key of the data to retrieve * @param string $default The default value to return if the key is not found or the cache is not populated * * @return string The cached value */ public function getCache($key, $default = null) { return false; } /** * Is the global F0F cache enabled? * * @return boolean */ public function isGlobalFOFCacheEnabled() { return true; } /** * Clears the cache of system-wide F0F data. You are supposed to call this in * your components' installation script post-installation and post-upgrade * methods or whenever you are modifying the structure of database tables * accessed by F0F. Please note that F0F's cache never expires and is not * purged by Joomla!. You MUST use this method to manually purge the cache. * * @return boolean True on success */ public function clearCache() { return false; } /** * logs in a user * * @param array $authInfo Authentication information * * @return boolean True on success */ public function loginUser($authInfo) { return true; } /** * logs out a user * * @return boolean True on success */ public function logoutUser() { return true; } /** * Logs a deprecated practice. In Joomla! this results in the $message being output in the * deprecated log file, found in your site's log directory. * * @param string $message The deprecated practice log message * * @return void */ public function logDeprecated($message) { // The default implementation does nothing. Override this in your platform classes. } /** * Adds a message * * @param string|array $title A title, or an array of additional fields to add to the log entry * @param string $logText The translation key to the log text * @param string $extension The name of the extension logging this entry * @param User|null $user The user the action is being logged for * * @return void */ public function logUserAction($title, $logText, $extension, $user = null) { // The default implementation does nothing. Override this in your platform classes. } /** * Returns the version number string of the platform, e.g. "4.5.6". If * implementation integrates with a CMS or a versioned foundation (e.g. * a framework) it is advisable to return that version. * * @return string * * @since 2.1.2 */ public function getPlatformVersion() { return ''; } /** * Handle an exception in a way that results to an error page. * * @param Exception $exception The exception to handle * * @throws Exception Possibly rethrown exception */ public function showErrorPage(Exception $exception) { throw $exception; } /** * Are plugins allowed to run in CLI mode? * * @return bool */ public function isAllowPluginsInCli() { return $this->allowPluginsInCli; } /** * Set whether plugins are allowed to run in CLI mode * * @param bool $allowPluginsInCli */ public function setAllowPluginsInCli($allowPluginsInCli) { $this->allowPluginsInCli = $allowPluginsInCli; } } Filesystem.php 0000604 00000007060 15245613324 0007405 0 ustar 00 <?php /** * @package FOF * @copyright Copyright (c)2010-2020 Nicholas K. Dionysopoulos / Akeeba Ltd * @license GNU General Public License version 2, or later */ namespace FOF30\Platform\Base; defined('_JEXEC') || die; use FOF30\Container\Container; use FOF30\Platform\FilesystemInterface; abstract class Filesystem implements FilesystemInterface { /** * The list of paths where platform class files will be looked for * * @var array */ protected static $paths = []; /** @var Container The component container */ protected $container = null; /** * Public constructor. * * @param Container $c The component container */ public function __construct(Container $c) { $this->container = $c; } /** * This method will crawl a starting directory and get all the valid files that will be analyzed by getInstance. * Then it organizes them into an associative array. * * @param string $path Folder where we should start looking * @param array $ignoreFolders Folder ignore list * @param array $ignoreFiles File ignore list * * @return array Associative array, where the `fullpath` key contains the path to the file, * and the `classname` key contains the name of the class */ protected static function getFiles($path, array $ignoreFolders = [], array $ignoreFiles = []) { $return = []; $files = self::scanDirectory($path, $ignoreFolders, $ignoreFiles); // Ok, I got the files, now I have to organize them foreach ($files as $file) { $clean = str_replace($path, '', $file); $clean = trim(str_replace('\\', '/', $clean), '/'); $parts = explode('/', $clean); // If I have less than 3 fragments, it means that the file was inside the generic folder // (interface + abstract) so I have to skip it if (count($parts) < 3) { continue; } $return[] = [ 'fullpath' => $file, 'classname' => 'F0FPlatform' . ucfirst($parts[0]) . ucfirst(basename($parts[1], '.php')), ]; } return $return; } /** * Recursive function that will scan every directory unless it's in the ignore list. Files that aren't in the * ignore list are returned. * * @param string $path Folder where we should start looking * @param array $ignoreFolders Folder ignore list * @param array $ignoreFiles File ignore list * * @return array List of all the files */ protected static function scanDirectory($path, array $ignoreFolders = [], array $ignoreFiles = []) { $return = []; $handle = @opendir($path); if (!$handle) { return $return; } while (($file = readdir($handle)) !== false) { if ($file == '.' || $file == '..') { continue; } $fullpath = $path . '/' . $file; if ((is_dir($fullpath) && in_array($file, $ignoreFolders)) || (is_file($fullpath) && in_array($file, $ignoreFiles))) { continue; } if (is_dir($fullpath)) { $return = array_merge(self::scanDirectory($fullpath, $ignoreFolders, $ignoreFiles), $return); } else { $return[] = $path . '/' . $file; } } return $return; } /** * Gets the extension of a file name * * @param string $file The file name * * @return string The file extension */ public function getExt($file) { $dot = strrpos($file, '.') + 1; return substr($file, $dot); } /** * Strips the last extension off of a file name * * @param string $file The file name * * @return string The file name without the extension */ public function stripExt($file) { return preg_replace('#\.[^.]*$#', '', $file); } }
| ver. 1.4 |
Github
|
.
| PHP 8.1.34 | Генерация страницы: 0 |
proxy
|
phpinfo
|
Настройка