You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
361 lines
11 KiB
361 lines
11 KiB
<?php
|
|
|
|
/**
|
|
*
|
|
* @package Duplicator
|
|
* @copyright (c) 2022, Snap Creek LLC
|
|
*/
|
|
|
|
namespace Duplicator\Libs\Snap;
|
|
|
|
/**
|
|
* Original installer files manager
|
|
*
|
|
* This class saves a file or folder in the original files folder and saves the original location persistent.
|
|
* By entry we mean a file or a folder but not the files contained within it.
|
|
* In this way it is possible, for example, to move an entire plugin to restore it later.
|
|
*/
|
|
class SnapOrigFileManager
|
|
{
|
|
const MODE_MOVE = 'move';
|
|
const MODE_COPY = 'copy';
|
|
const ORIG_FOLDER_PREFIX = 'original_files_';
|
|
const PERSISTANCE_FILE_NAME = 'entries_stored.json';
|
|
|
|
/** @var string */
|
|
protected $persistanceFile = null;
|
|
/** @var string */
|
|
protected $origFilesFolder = null;
|
|
/** @var array<string, array{baseName:string, source:string, stored: string, mode:string, isRelative: bool}> */
|
|
protected $origFolderEntries = array();
|
|
/** @var string */
|
|
protected $rootPath = null;
|
|
|
|
/**
|
|
* Class constructor
|
|
*
|
|
* @param string $root wordpress root path
|
|
* @param string $origFolderParentPath orig files folder path
|
|
* @param string $hash package hash
|
|
*/
|
|
public function __construct($root, $origFolderParentPath, $hash)
|
|
{
|
|
$this->rootPath = SnapIO::safePathUntrailingslashit($root, true);
|
|
$this->origFilesFolder = SnapIO::safePathTrailingslashit($origFolderParentPath, true) . self::ORIG_FOLDER_PREFIX . $hash;
|
|
$this->persistanceFile = $this->origFilesFolder . '/' . self::PERSISTANCE_FILE_NAME;
|
|
|
|
if (file_exists($this->persistanceFile)) {
|
|
$this->load();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Create a main folder if don't exist and load the entries
|
|
*
|
|
* @param boolean $reset if strue reset orig file folder
|
|
*
|
|
* @return void
|
|
*/
|
|
public function init($reset = false)
|
|
{
|
|
$this->createMainFolder($reset);
|
|
$this->load();
|
|
}
|
|
|
|
/**
|
|
* Create orig file folder
|
|
*
|
|
* @param boolean $reset if true delete current folder
|
|
*
|
|
* @return boolean return true if succeded
|
|
*
|
|
* @throws \Exception
|
|
*/
|
|
protected function createMainFolder($reset = false)
|
|
{
|
|
if ($reset) {
|
|
$this->deleteMainFolder();
|
|
}
|
|
|
|
if (!file_exists($this->origFilesFolder)) {
|
|
if (!SnapIO::mkdir($this->origFilesFolder, 'u+rwx')) {
|
|
throw new \Exception('Can\'t create the original files folder ' . SnapLog::v2str($this->origFilesFolder));
|
|
}
|
|
}
|
|
|
|
$htaccessFile = $this->origFilesFolder . '/.htaccess';
|
|
if (!file_exists($htaccessFile)) {
|
|
$content = <<<HTACCESS
|
|
Order Allow,Deny
|
|
Deny from All
|
|
HTACCESS;
|
|
@file_put_contents($htaccessFile, $content);
|
|
}
|
|
|
|
if (!file_exists($this->persistanceFile)) {
|
|
$this->save();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* @return string Main folder path
|
|
* @throws \Exception
|
|
*/
|
|
public function getMainFolder()
|
|
{
|
|
if (!file_exists($this->origFilesFolder)) {
|
|
throw new \Exception('Can\'t get the original files folder ' . SnapLog::v2str($this->origFilesFolder));
|
|
}
|
|
|
|
return $this->origFilesFolder;
|
|
}
|
|
|
|
/**
|
|
* delete origianl files folder
|
|
*
|
|
* @return boolean
|
|
* @throws \Exception
|
|
*/
|
|
public function deleteMainFolder()
|
|
{
|
|
if (file_exists($this->origFilesFolder) && !SnapIO::rrmdir($this->origFilesFolder)) {
|
|
throw new \Exception('Can\'t delete the original files folder ' . SnapLog::v2str($this->origFilesFolder));
|
|
}
|
|
$this->origFolderEntries = array();
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* add a entry on original folder.
|
|
*
|
|
* @param string $identifier entry identifier
|
|
* @param string $path entry path. can be a file or a folder
|
|
* @param string $mode MODE_MOVE move the item in original folder
|
|
* MODE_COPY copy the item in original folder
|
|
* @param bool|string $rename if rename is a string the item is renamed in original folder.
|
|
*
|
|
* @return boolean true if succeded
|
|
*/
|
|
public function addEntry($identifier, $path, $mode = self::MODE_MOVE, $rename = false)
|
|
{
|
|
if (!file_exists($path)) {
|
|
return false;
|
|
}
|
|
|
|
$baseName = empty($rename) ? basename($path) : $rename;
|
|
|
|
if (($relativePath = SnapIO::getRelativePath($path, $this->rootPath)) === false) {
|
|
$isRelative = false;
|
|
} else {
|
|
$isRelative = true;
|
|
}
|
|
$parentFolder = $isRelative ? dirname($relativePath) : SnapIO::removeRootPath(dirname($path));
|
|
if (empty($parentFolder) || $parentFolder === '.') {
|
|
$parentFolder = '';
|
|
} else {
|
|
$parentFolder .= '/';
|
|
}
|
|
$targetFolder = $this->origFilesFolder . '/' . $parentFolder;
|
|
if (!file_exists($targetFolder)) {
|
|
SnapIO::mkdirP($targetFolder);
|
|
}
|
|
$dest = $targetFolder . $baseName;
|
|
|
|
switch ($mode) {
|
|
case self::MODE_MOVE:
|
|
// Don't use rename beacause new files must have the current script owner
|
|
if (!SnapIO::rcopy($path, $dest)) {
|
|
throw new \Exception('Can\'t copy the original file ' . SnapLog::v2str($path));
|
|
}
|
|
if (!SnapIO::rrmdir($path)) {
|
|
throw new \Exception('Can\'t remove the original file ' . SnapLog::v2str($path));
|
|
}
|
|
break;
|
|
case self::MODE_COPY:
|
|
if (!SnapIO::rcopy($path, $dest)) {
|
|
throw new \Exception('Can\'t copy the original file ' . SnapLog::v2str($path));
|
|
}
|
|
break;
|
|
default:
|
|
throw new \Exception('invalid mode addEntry');
|
|
}
|
|
|
|
$this->origFolderEntries[$identifier] = array(
|
|
'baseName' => $baseName,
|
|
'source' => $isRelative ? $relativePath : $path,
|
|
'stored' => $parentFolder . $baseName,
|
|
'mode' => $mode,
|
|
'isRelative' => $isRelative
|
|
);
|
|
|
|
$this->save();
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Get entry info from identifier
|
|
*
|
|
* @param string $identifier orig file identifier
|
|
*
|
|
* @return false|array{baseName:string, source:string, stored: string, mode:string, isRelative: bool} false if entry don't exists
|
|
*/
|
|
public function getEntry($identifier)
|
|
{
|
|
if (isset($this->origFolderEntries[$identifier])) {
|
|
return $this->origFolderEntries[$identifier];
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get entry stored path in original folder
|
|
*
|
|
* @param string $identifier orig file identifier
|
|
*
|
|
* @return boolean|string false if entry don't exists
|
|
*/
|
|
public function getEntryStoredPath($identifier)
|
|
{
|
|
if (isset($this->origFolderEntries[$identifier])) {
|
|
return $this->origFilesFolder . '/' . $this->origFolderEntries[$identifier]['stored'];
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Return true if identifier org file is relative path
|
|
*
|
|
* @param string $identifier orig file identifier
|
|
*
|
|
* @return boolean
|
|
*/
|
|
public function isRelative($identifier)
|
|
{
|
|
if (isset($this->origFolderEntries[$identifier])) {
|
|
return $this->origFolderEntries[$identifier]['isRelative'];
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get entry target restore path
|
|
*
|
|
* @param string $identifier orig file identifier
|
|
* @param null|string $defaultIfIsAbsolute if isn't null return the value if path is absolute
|
|
*
|
|
* @return false|string false if entry don't exists
|
|
*/
|
|
public function getEntryTargetPath($identifier, $defaultIfIsAbsolute = null)
|
|
{
|
|
if (isset($this->origFolderEntries[$identifier])) {
|
|
if ($this->origFolderEntries[$identifier]['isRelative']) {
|
|
return $this->rootPath . '/' . $this->origFolderEntries[$identifier]['source'];
|
|
} else {
|
|
if (is_null($defaultIfIsAbsolute)) {
|
|
return $this->origFolderEntries[$identifier]['source'];
|
|
} else {
|
|
return $defaultIfIsAbsolute;
|
|
}
|
|
}
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* this function restore current entry in original position.
|
|
* If mode is copy it simply delete the entry else move the entry in original position
|
|
*
|
|
* @param string $identifier identified of current entrye
|
|
* @param boolean $save update saved entries
|
|
* @param null|string $defaultIfIsAbsolute if isn't null return the value if path is absolute
|
|
*
|
|
* @return boolean true if succeded
|
|
*/
|
|
public function restoreEntry($identifier, $save = true, $defaultIfIsAbsolute = null)
|
|
{
|
|
if (!isset($this->origFolderEntries[$identifier])) {
|
|
return false;
|
|
}
|
|
|
|
$stored = $this->getEntryStoredPath($identifier);
|
|
if (($original = $this->getEntryTargetPath($identifier, $defaultIfIsAbsolute)) === false) {
|
|
return false;
|
|
}
|
|
|
|
switch ($this->origFolderEntries[$identifier]['mode']) {
|
|
case self::MODE_MOVE:
|
|
if (!SnapIO::rename($stored, $original)) {
|
|
throw new \Exception('Can\'t move the original file ' . SnapLog::v2str($stored));
|
|
}
|
|
break;
|
|
case self::MODE_COPY:
|
|
if (!SnapIO::rrmdir($stored)) {
|
|
throw new \Exception('Can\'t delete entry ' . SnapLog::v2str($stored));
|
|
}
|
|
break;
|
|
default:
|
|
throw new \Exception('invalid mode addEntry');
|
|
}
|
|
|
|
unset($this->origFolderEntries[$identifier]);
|
|
if ($save) {
|
|
$this->save();
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Put all entries on original position and empty original folder
|
|
*
|
|
* @param string[] $exclude identifiers list t exclude
|
|
*
|
|
* @return boolean
|
|
*/
|
|
public function restoreAll($exclude = array())
|
|
{
|
|
foreach (array_keys($this->origFolderEntries) as $ident) {
|
|
if (in_array($ident, $exclude)) {
|
|
continue;
|
|
}
|
|
$this->restoreEntry($ident, false);
|
|
}
|
|
$this->save();
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Save notices from json file
|
|
*
|
|
* @return bool
|
|
*/
|
|
public function save()
|
|
{
|
|
if (!file_put_contents($this->persistanceFile, SnapJson::jsonEncodePPrint($this->origFolderEntries))) {
|
|
throw new \Exception('Can\'t write persistence file');
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Load notice from json file
|
|
*
|
|
* @return bool
|
|
*/
|
|
private function load()
|
|
{
|
|
if (file_exists($this->persistanceFile)) {
|
|
$json = file_get_contents($this->persistanceFile);
|
|
$this->origFolderEntries = json_decode($json, true);
|
|
} else {
|
|
$this->origFolderEntries = array();
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
|