Current Path : /var/www/html/wp-content/plugins/duplicator-pro/src/Models/Storages/ |
Current File : /var/www/html/wp-content/plugins/duplicator-pro/src/Models/Storages/AbstractStorageEntity.php |
<?php namespace Duplicator\Models\Storages; use DUP_PRO_Global_Entity; use DUP_PRO_Log; use DUP_PRO_Package; use DUP_PRO_Package_Upload_Info; use DUP_PRO_Schedule_Entity; use DUP_PRO_Storage_Entity; use Duplicator\Core\Views\TplMng; use Duplicator\Libs\Snap\SnapIO; use Duplicator\Libs\Snap\SnapURL; use Duplicator\Libs\Snap\SnapUtil; use Duplicator\Utils\Crypt\CryptBlowfish; use Duplicator\Utils\IncrementalStatusMessage; use Duplicator\Utils\Settings\ModelMigrateSettingsInterface; use Exception; use ReflectionClass; use VendorDuplicator\Amk\JsonSerialize\JsonSerialize; abstract class AbstractStorageEntity extends DUP_PRO_Storage_Entity implements AbstractStorageEntityInterface, ModelMigrateSettingsInterface { /** @var array<int,string> Class list registered */ private static $storageTypes = []; /** @var string */ protected $name = ''; /** @var string */ protected $notes = ''; /** @var int */ protected $storage_type = 0; /** @var array<string,scalar> Storage configuration data */ protected $config = []; /** @var bool this value is true on wakeup of old storages entities, for new storages is false*/ protected $legacyEntity = true; /** @var IncrementalStatusMessage Inclemental messages system */ protected $testLog = null; /** * Class constructor */ public function __construct() { $this->name = __('New Storage', "duplicator-pro"); $this->storage_type = static::getSType(); $this->legacyEntity = false; $this->testLog = new IncrementalStatusMessage(); $this->config = static::getDefaultCoinfig(); } /** * Entity type * * @return string */ public static function getType() { return 'DUP_PRO_Storage_Entity'; } /** * Initizalize entity from JSON * * @param string $json JSON string * @param array<string,scalar> $rowData DB row data * @param ?string $overwriteClass Overwrite class object, class must extend AbstractEntity * * @return static */ protected static function getEntityFromJson($json, $rowData, $overwriteClass = null) { if ($overwriteClass === null) { $tmp = JsonSerialize::unserialize($json); $overwriteClass = AbstractStorageEntity::getSTypePHPClass($tmp); } return parent::getEntityFromJson($json, $rowData, $overwriteClass); } /** * Get default config * * @return array<string,scalar> */ protected static function getDefaultCoinfig() { return [ 'storage_folder' => self::getDefaultStorageFolder(), 'max_packages' => 10, ]; } /** * Will be called, automatically, when Serialize * * @return array<string, mixed> */ public function __serialize() // phpcs:ignore PHPCompatibility.FunctionNameRestrictions.NewMagicMethods.__serializeFound { $data = parent::__serialize(); if (DUP_PRO_Global_Entity::getInstance()->crypt) { if (($dataString = JsonSerialize::serialize($data['config'])) == false) { throw new Exception('Error serialize storage config'); } $data['config'] = CryptBlowfish::encrypt($dataString, null, true); } unset($data['testLog']); return $data; } /** * Serialize * * Wakeup method. * * @return void */ public function __wakeup() { parent::__wakeup(); if (is_string($this->config)) { // if si encrypted config is a string else is an array $config = CryptBlowfish::decrypt($this->config, null, true); $config = JsonSerialize::unserialize($config); $this->config = static::getDefaultCoinfig(); // Update only existing keys foreach (array_keys($this->config) as $key) { if (!isset($config[$key])) { continue; } $this->config[$key] = $config[$key]; } } $this->testLog = new IncrementalStatusMessage(); } /** * Get name * * @return string */ public function getName() { return $this->name; } /** * Get notes * * @return string */ public function getNotes() { return $this->notes; } /** * Get priority, used to sort storages. * 100 is neutral value, 0 is the highest priority * * @return int */ public static function getPriority() { return 100; } /** * Register storage type * * @return void */ public static function registerType() { if (isset(self::$storageTypes[static::getSType()])) { throw new Exception("Storage type " . static::getSType() . " already registered with class " . self::$storageTypes[static::getSType()]); } self::$storageTypes[static::getSType()] = static::class; } /** * Get storages types * * @return int[] */ final public static function getResisteredTypes() { return array_keys(self::$storageTypes); } /** * Get storages types sorted by priority * * @return int[] */ final public static function getResisteredTypesByPriority() { $types = self::getResisteredTypes(); usort($types, function ($a, $b) { $aClass = self::$storageTypes[$a]; $bClass = self::$storageTypes[$b]; if ($aClass::getPriority() == $bClass::getPriority()) { return 0; } return ($aClass::getPriority() < $bClass::getPriority()) ? -1 : 1; }); return $types; } /** * Get storage type class * * @param int|array<string,mixed> $data Storage data or storage type id * * @return string */ final public static function getSTypePHPClass($data) { if (is_array($data)) { $type = (isset($data['storage_type']) ? $data['storage_type'] : UnknownStorage::getSType()); $class = isset(self::$storageTypes[$type]) ? self::$storageTypes[$type] : UnknownStorage::class; } else { $type = (int) $data; $class = isset(self::$storageTypes[$type]) ? self::$storageTypes[$type] : UnknownStorage::class; $data = []; } return apply_filters('duplicator_pro_storage_type_class', $class, $type, $data); } /** * Get new storage object by type * * @param int $type Storage type * * @return self */ final public static function getNewStorageByType($type) { $class = self::getSTypePHPClass($type); /** @var self */ return new $class(); } /** * Render config fields by storage type * * @param int|self $type Storage type or storage object * @param bool $echo Echo or return * * @return string */ final public static function renderSTypeConfigFields($type, $echo = true) { if ($type instanceof self) { $storage = $type; } else { $class = self::getSTypePHPClass($type); /** @var self */ $storage = new $class(); } return $storage->renderConfigFields($echo); } /** * Update data from http request, this method don't save data, just update object properties * * @param string $message Message * * @return bool True if success and all data is valid, false otherwise */ public function updateFromHttpRequest(&$message = '') { $this->name = SnapUtil::sanitizeTextInput(SnapUtil::INPUT_REQUEST, 'name', ''); if (strlen($this->name) == 0) { $message = __('Storage name is required.', 'duplicator-pro'); return false; } $this->notes = SnapUtil::sanitizeDefaultInput(SnapUtil::INPUT_REQUEST, 'notes', ''); return true; } /** * Sanitize storage folder * * @param string $inputKey Input key * @param string $root add,remove,none (add root, remove root, do nothing) * * @return string */ protected static function getSanitizedInputFolder($inputKey, $root = 'none') { $folder = SnapUtil::sanitizeTextInput(SnapUtil::INPUT_REQUEST, $inputKey, ''); $folder = trim(stripslashes($folder)); $folder = SnapIO::safePathUntrailingslashit($folder); $folder = ltrim($folder, '/\\'); switch ($root) { case 'add': $folder = ltrim($folder, '/\\'); $folder = '/' . $folder; break; case 'remove': $folder = ltrim($folder, '/\\'); break; case 'none': default: break; } return $folder; } /** * Is type selectable, if false the storage can't be selected so can't be created new storage of this type * * @return bool */ public static function isSelectable() { return true; } /** * If storage is default can't be deleted and the name can't be changed * * @return bool */ public static function isDefault() { return false; } /** * Check if storage is supported * * @return bool */ public static function isSupported() { return true; } /** * Get supported notice, displayed if storage isn't supported * * @return string html string or empty if storage is supported */ public static function getNotSupportedNotice() { if (self::isSupported()) { return ''; } $result = sprintf( __( 'The Storage %s is not supported on this server.', 'duplicator-pro' ), static::getStypeName() ); return esc_html($result); } /** * Returns true if storage type is local * * @return bool */ public static function isLocal() { return false; } /** * Get storage folder * * @return string */ protected function getStorageFolder() { /** @var string */ return $this->config['storage_folder']; } /** * Get storage location string * * @return string */ abstract public function getLocationString(); /** * Returns an html anchor tag of location * * @return string Returns an html anchor tag with the storage location as a hyperlink. * * @example * OneDrive Example return * <a target="_blank" href="https://1drv.ms/f/sAFrQtasdrewasyghg">https://1drv.ms/f/sAFrQtasdrewasyghg</a> */ public function getHtmlLocationLink() { return '<a href="' . esc_url($this->getLocationString()) . '" target="_blank" >' . esc_html($this->getLocationString()) . '</a>'; } /** * Check if storage is valid * * @return bool Return true if storage is valid and ready to use, false otherwise */ abstract public function isValid(); /** * Return max storage packages, 0 unlimited * * @return int<0,max> */ public function getMaxPackages() { /** @var int<0,max> */ return $this->config['max_packages']; } /** * Copies the package files from the default local storage to another local storage location * * @param DUP_PRO_Package $package the package * @param DUP_PRO_Package_Upload_Info $upload_info the upload info * * @return void */ abstract public function copyFromDefault(DUP_PRO_Package $package, DUP_PRO_Package_Upload_Info $upload_info); /** * Purge old packages * * @return bool true if success, false otherwise */ abstract public function purgeOldPackages(); /** * List quick view * * @param bool $echo Echo or return * * @return string HTML string */ public function getListQuickView($echo = true) { ob_start(); ?> <div> <label><?php esc_html_e('Location', 'duplicator-pro') ?>:</label> <?php echo esc_html($this->getLocationString()); ?> </div> <?php if ($echo) { ob_end_flush(); return ''; } else { return (string) ob_get_clean(); } } /** * List quick view * * @param bool $echo Echo or return * * @return string HTML string */ public function getDeleteView($echo = true) { ob_start(); ?> <div class="item"> <span class="lbl">Name:</span><?php echo esc_html($this->getName()); ?><br> <span class="lbl">Type:</span> <?php echo $this->getStypeIcon(); ?> <?php echo esc_html($this->getStypeName()); ?> </div> <?php if ($echo) { ob_end_flush(); return ''; } else { return (string) ob_get_clean(); } } /** * Get action key text * * @param string $key Key name (action, pending, failed, cancelled, success) * * @return string */ protected function getActionKeyText($key) { switch ($key) { case 'action': return sprintf( __('Transferring to %1$s folder:<br/> <i>%2$s</i>', "duplicator-pro"), $this->getStypeName(), $this->getStorageFolder() ); case 'pending': return sprintf( __('Transfer to %1$s folder %2$s is pending', "duplicator-pro"), $this->getStypeName(), $this->getStorageFolder() ); case 'failed': return sprintf( __('Failed to transfer to %1$s folder %2$s', "duplicator-pro"), $this->getStypeName(), $this->getStorageFolder() ); case 'cancelled': return sprintf( __('Cancelled before could transfer to %1$s folder %2$s', "duplicator-pro"), $this->getStypeName(), $this->getStorageFolder() ); case 'success': return sprintf( __('Transferred package to %1$s folder %2$s', "duplicator-pro"), $this->getStypeName(), $this->getStorageFolder() ); default: throw new Exception('Invalid key'); } } /** * Get action text * * @return string */ public function getActionText() { return $this->getActionKeyText('action'); } /** * Get pending action text * * @return string */ public function getPendingText() { return $this->getActionKeyText('pending'); } /** * Returns the text to display when the package has failed to copy to the storage location * * @return string */ public function getFailedText() { return $this->getActionKeyText('failed'); } /** * Returns the text to display when the package has been cancelled before it could be copied to the storage location * * @return string */ public function getCancelledText() { return $this->getActionKeyText('cancelled'); } /** * Returns the text to display when the package has been successfully copied to the storage location * * @return string */ public function getSuccessText() { return $this->getActionKeyText('success'); } /** * * @return string */ protected static function getDefaultStorageFolder() { /** @var array<string,scalar> */ $parsetUrl = SnapURL::parseUrl(get_home_url()); if (is_string($parsetUrl['host']) && strlen($parsetUrl['host']) > 0) { $parsetUrl['host'] = preg_replace("([^\w\d\-_~,;\[\]\(\)\/\.])", '', $parsetUrl['host']); } $parsetUrl['scheme'] = false; $parsetUrl['port'] = false; $parsetUrl['query'] = false; $parsetUrl['fragment'] = false; $parsetUrl['user'] = false; $parsetUrl['pass'] = false; if (is_string($parsetUrl['path']) && strlen($parsetUrl['path']) > 0) { $parsetUrl['path'] = preg_replace("([^\w\d\-_~,;\[\]\(\)\/\.])", '', $parsetUrl['path']); } return ltrim(SnapURL::buildUrl($parsetUrl), '/\\'); } /** * Render form config fields * * @param bool $echo Echo or return * * @return string */ abstract public function renderConfigFields($echo = true); /** * Render remote localtion info * * @param bool $failed Failed upload * @param bool $cancelled Cancelled upload * @param bool $echo Echo or return * * @return string */ public function renderRemoteLocationInfo($failed = false, $cancelled = false, $echo = true) { return TplMng::getInstance()->render( 'admin_pages/storages/parts/remote_localtion_info', [ 'failed' => $failed, 'cancelled' => $cancelled, 'storage' => $this, ], $echo ); } /** * Storages test * * @param string $message Test message * * @return bool return true if success, false otherwise */ public function test(&$message = '') { $this->testLog->reset(); $message = sprintf(__('Testing %s storage...', 'duplicator-pro'), $this->getStypeName()); $this->testLog->addMessage($message); if ($this->isSupported() == false) { $message = sprintf(__('Storage %s isn\'t supported on current server', 'duplicator-pro'), $this->getStypeName()); $this->testLog->addMessage($message); return false; } if ($this->isValid() == false) { $message = sprintf(__('Storage %s config data isn\'t valid', 'duplicator-pro'), $this->getStypeName()); $this->testLog->addMessage($message); return false; } return true; } /** * Get last test messages * * @return string */ public function getTestLog() { return (string) $this->testLog; } /** * Get copied storage from source id. * If destId is existing storage is accepted source id with only the same type * * @param int $sourceId Source storage id * @param int $targetId Target storage id, if <= 0 create new storage * * @return false|static Return false on failure or storage object with updated value */ public static function getCopyStorage($sourceId, $targetId = -1) { if (($source = static::getById($sourceId)) === false) { return false; } if ($targetId <= 0) { $class = get_class($source); /** @var static */ $target = new $class(); } else { /** @var false|static */ $target = static::getById($targetId); if ($target == false) { return false; } if ($source->getSType() != $target->getSType()) { return false; } } $skipProps = [ 'id', 'testLog', ]; $reflect = new ReflectionClass($source); foreach ($reflect->getProperties() as $prop) { if (in_array($prop->getName(), $skipProps)) { continue; } if ($prop->isStatic()) { continue; } $prop->setAccessible(true); if ($prop->getName() == 'name') { $newName = sprintf(__('%1$s - Copy', "duplicator-pro"), $prop->getValue($source)); $prop->setValue($target, $newName); } else { $prop->setValue($target, $prop->getValue($source)); } } return $target; } /** * Get all storages by type * * @param int $sType Storage type * * @return self[]|false return entities list of false on failure */ public static function getAllBySType($sType) { return self::getAll(0, 0, null, function (self $storage) use ($sType) { return ($storage->getSType() == $sType); }); } /** * To export data * * @return array<string, mixed> */ public function settingsExport() { $data = JsonSerialize::serializeToData($this, JsonSerialize::JSON_SKIP_MAGIC_METHODS | JsonSerialize::JSON_SKIP_CLASS_NAME); unset($data['testLog']); return $data; } /** * Update object properties from import data * * @param array<string, mixed> $data data to import * @param string $dataVersion version of data * @param array<string, mixed> $extraData extra data, useful form id mapping etc. * * @return bool True if success, otherwise false */ public function settingsImport($data, $dataVersion, array $extraData = []) { $skipProps = ['id']; $reflect = new ReflectionClass(self::class); $props = $reflect->getProperties(); foreach ($props as $prop) { if (in_array($prop->getName(), $skipProps)) { continue; } if (!isset($data[$prop->getName()])) { continue; } $prop->setAccessible(true); $prop->setValue($this, $data[$prop->getName()]); } return true; } /** * Save new storage to DB * * @return int|false The id or false on failure */ protected function insert() { if (($id = parent::insert()) === false) { return false; } do_action('duplicator_pro_after_storage_create', $id); return $id; } /** * Delete this storage * * @return bool True on success, or false on error. */ public function delete() { $id = $this->id; if (parent::delete() === false) { return false; } DUP_PRO_Package::by_status_callback(function (DUP_PRO_Package $package) use ($id) { foreach ($package->upload_infos as $key => $upload_info) { if ($upload_info->getStorageId() == $id) { DUP_PRO_Log::traceObject("deleting uploadinfo from package $package->ID", $upload_info); unset($package->upload_infos[$key]); $package->save(); break; } } }); DUP_PRO_Schedule_Entity::listCallback(function (DUP_PRO_Schedule_Entity $schedule) use ($id) { if (($key = array_search($id, $schedule->storage_ids)) !== false) { $key = (int) $key; //use array_splice() instead of unset() to reset keys array_splice($schedule->storage_ids, $key, 1); if (count($schedule->storage_ids) === 0) { $schedule->active = false; } $schedule->save(); } }); do_action('duplicator_pro_after_storage_delete', $id); return true; } }