510 lines
16 KiB
PHP
510 lines
16 KiB
PHP
<?php
|
||
|
||
/**
|
||
* Класс для работы с папками и путями
|
||
* Для итерации над файлами возможно лучше использовать SPL
|
||
*
|
||
*/
|
||
|
||
namespace ctiso;
|
||
|
||
class Path
|
||
{
|
||
const SEPARATOR = "/";
|
||
|
||
protected $path = array();
|
||
protected $url = array();
|
||
protected $absolute = false;
|
||
|
||
public function __construct($path = '')
|
||
{
|
||
//assert(is_string($path));
|
||
$this->url = parse_url($path ?? '');
|
||
|
||
if (isset($this->url['path'])) {
|
||
$path = $this->url['path'];
|
||
// $path = preg_replace('/\/{2,}/', '/', $path);
|
||
$list = $this->listFromString($path);
|
||
|
||
if (isset($this->url['scheme']) && !isset($this->url['host'])) {
|
||
$this->absolute = false;
|
||
} else if ($list[0] == '' && count($list) > 1) {
|
||
$this->absolute = true;
|
||
}
|
||
|
||
$this->path = $this->optimize($list);
|
||
}
|
||
}
|
||
|
||
static function factory($path) {
|
||
return new Path($path);
|
||
}
|
||
|
||
public function getParts()
|
||
{
|
||
return $this->path;
|
||
}
|
||
|
||
public static function normalize($pathName)
|
||
{
|
||
$path = new Path($pathName);
|
||
return $path->__toString();
|
||
}
|
||
|
||
/**
|
||
* Базовое имя
|
||
* @param $path
|
||
* @return mixed
|
||
*/
|
||
public static function basename($path)
|
||
{
|
||
$list = preg_split('#\\\\|/#s', $path);
|
||
return end($list);
|
||
}
|
||
|
||
/**
|
||
* Возвращает расширение файла
|
||
*
|
||
* @param string $fileName Полное имя файла
|
||
*
|
||
* @return string
|
||
*/
|
||
static function getExtension($fileName)
|
||
{
|
||
assert(is_string($fileName) || is_null($fileName));
|
||
|
||
return pathinfo($fileName, PATHINFO_EXTENSION);
|
||
}
|
||
|
||
static function isType($fileName, $extension)
|
||
{
|
||
if (is_array($extension)) {
|
||
return in_array(pathinfo($fileName, PATHINFO_EXTENSION), $extension);
|
||
} else {
|
||
return (pathinfo($fileName, PATHINFO_EXTENSION) == $extension);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Полное имя файла без расширения
|
||
*
|
||
* @param string $fileName Имя файла
|
||
*
|
||
* @return string
|
||
*/
|
||
static function skipExtension($fileName)
|
||
{
|
||
assert(is_string($fileName));
|
||
|
||
$path = pathinfo($fileName);
|
||
if ($path['dirname'] == ".") {
|
||
return $path['filename'];
|
||
} else {
|
||
return self::join($path['dirname'], $path['filename']);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Возвращает имя файла без расширения
|
||
*
|
||
* @param string $fileName Полное имя файла
|
||
*
|
||
* @return string
|
||
*/
|
||
static function getFileName($fileName)
|
||
{
|
||
assert(is_string($fileName));
|
||
|
||
return pathinfo($fileName, PATHINFO_FILENAME);
|
||
}
|
||
|
||
|
||
/**
|
||
* Часть конструктора
|
||
* Преобразует строку пути в массив
|
||
*
|
||
* @param string $path Путь
|
||
*
|
||
* @return array
|
||
*/
|
||
public static function listFromString($path)
|
||
{
|
||
assert(is_string($path));
|
||
|
||
$list = preg_split('#\\\\|/#s', $path);
|
||
|
||
return $list;
|
||
}
|
||
|
||
/**
|
||
* Преобразует относительный путь в абсолютный
|
||
*/
|
||
public static function optimize($path) //
|
||
{
|
||
$result = array();
|
||
foreach ($path as $n) {
|
||
switch ($n) {
|
||
// Может быть относительным или абсолютным путем
|
||
case "": break;
|
||
case ".": break;
|
||
case "..":
|
||
if (count($result) > 0 && $result[count($result) - 1] != '..') {
|
||
array_pop($result); break;
|
||
}
|
||
default:
|
||
array_push($result, $n);
|
||
}
|
||
}
|
||
return $result;
|
||
}
|
||
|
||
// Сравнение двух путей на равентство
|
||
public function equal($path/*: Path*/)
|
||
{
|
||
$count = count($this->path);
|
||
if ($count == count($path->path)) {
|
||
for ($i = 0; $i < $count; $i++) {
|
||
if ($this->path[$i] != $path->path[$i]) {
|
||
return false;
|
||
}
|
||
}
|
||
return true;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
public static function makeUrl($path)
|
||
{
|
||
$slash = (isset($path['host']) && (strlen($path['path']) > 0) && ($path['path'][0] != '/')) ? '/' : '';
|
||
|
||
return (isset($path['scheme']) ? $path['scheme'] . ':/' : '')
|
||
. (isset($path['host']) ? ('/'
|
||
. (isset($path['user']) ? $path['user'] . (isset($path['pass']) ? ':' . $path['pass'] : '') . '@' : '')
|
||
. $path['host']
|
||
. (isset($path['port']) ? ':' . $path['port'] : '')) : '')
|
||
. $slash
|
||
. $path['path']
|
||
. (isset($path['query']) ? '?' . $path['query'] : '')
|
||
. (isset($path['fragment']) ? '#' . $path['fragment'] : '');
|
||
}
|
||
|
||
/**
|
||
* Преобразует путь в строку
|
||
*
|
||
* @return string
|
||
*/
|
||
public function __toString()
|
||
{
|
||
$result = (($this->absolute) ? '/' : '') . implode(self::SEPARATOR, $this->path);
|
||
$this->url['path'] = $result;
|
||
return self::makeUrl($this->url);
|
||
}
|
||
|
||
/**
|
||
* Проверяет является ли папка родительской для другой папки
|
||
*
|
||
* @parma Path $path
|
||
*
|
||
* @return boolean
|
||
*/
|
||
public function isParent($path/*: Path*/)
|
||
{
|
||
if (isset($this->url['host']) && isset($path->url['host'])
|
||
&& ($this->url['host'] != $path->url['host'])) return false;
|
||
|
||
$count = count($this->path);
|
||
if (count($path->path) > $count) {
|
||
for ($i = 0; $i < $count; $i++) {
|
||
if ($path->path[$i] != $this->path[$i]) {
|
||
return false;
|
||
}
|
||
}
|
||
return true;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
public static function _isParent($path1, $path2)
|
||
{
|
||
$path = new Path($path1);
|
||
return $path->isParent(new Path($path2));
|
||
}
|
||
|
||
/**
|
||
* Находит путь относительно текущего путя
|
||
*
|
||
* @param string $name Полный путь к файлу
|
||
*
|
||
* @return string Относительный путь к файлу
|
||
*/
|
||
public function relPath($name)
|
||
{
|
||
$path = new Path($name);
|
||
unset($path->url['scheme']);
|
||
// $this->absolute = false;
|
||
$path->absolute = false;
|
||
foreach ($this->path as $n) {
|
||
array_shift($path->path);
|
||
}
|
||
return $path->__toString();
|
||
}
|
||
|
||
// Вычисляет относительный путь в виде строки
|
||
static function relative($rpath, $lpath) {
|
||
// Нужно проверять диск!!
|
||
$self = new Path($rpath);
|
||
$list = new Path($lpath);
|
||
$self_path = $self->getParts();
|
||
$list_path = $list->getParts();
|
||
|
||
$result = array();
|
||
$count = count($list_path);
|
||
for ($i = 0; $i < $count; $i++) {
|
||
if (($i >= count($self_path)) || $list_path[$i] != $self_path[$i]) {
|
||
break;
|
||
}
|
||
}
|
||
$list_count = count($list_path);
|
||
for($j = $i; $j < $list_count; $j++) {
|
||
array_push($result, '..');
|
||
}
|
||
$self_count = count($self_path);
|
||
for($j = $i; $j < $self_count; $j++) {
|
||
array_push($result, $self_path[$j]);
|
||
}
|
||
return implode("/", $result);
|
||
}
|
||
|
||
public function append($path)
|
||
{
|
||
$base = $this->__toString();
|
||
return self::join($base, $path);
|
||
}
|
||
|
||
/**
|
||
* Обьединяет строки в Path соединяя необходимым разделителем
|
||
* fixme не обрабатывает параметры урла, решение Path::join(SITE_WWW_PATH) . '?param=pampam'
|
||
* @return string
|
||
*/
|
||
static function fromJoin($_rest) {
|
||
$args = func_get_args();
|
||
|
||
$result = array();
|
||
$parts0 = new Path(array_shift($args));
|
||
$result [] = $parts0->getParts();
|
||
foreach ($args as $file) {
|
||
$parts = new Path($file);
|
||
$result [] = $parts->getParts();
|
||
}
|
||
|
||
// При обьединении ссылок можно обьеденить path, query, fragment
|
||
$path = implode(self::SEPARATOR, self::optimize(call_user_func_array('array_merge', $result)));
|
||
$parts0->url['path'] = ($parts0->isAbsolute()) ? '/' . $path : $path;
|
||
return $parts0;
|
||
}
|
||
|
||
/**
|
||
* Обьединяет строки в строку пути соединяя необходимым разделителем
|
||
* fixme не обрабатывает параметры урла, решение Path::join(SITE_WWW_PATH) . '?param=pampam'
|
||
* @return string
|
||
*/
|
||
static function join($_rest)
|
||
{
|
||
$args = func_get_args();
|
||
$path = call_user_func_array([self::class, "fromJoin"], $args);
|
||
return self::makeUrl($path->url);
|
||
}
|
||
|
||
// Проверка структуры имени файла
|
||
static function checkName($name, $extension)
|
||
{
|
||
return (strlen(pathinfo($name, PATHINFO_FILENAME)) > 0) && (pathinfo($name, PATHINFO_EXTENSION) == $extension);
|
||
}
|
||
|
||
static function isCharName($char)
|
||
{
|
||
$ch = ord($char);
|
||
return ((($ch >= ord('a')) && ($ch <= ord('z'))) || (strpos('-._', $char) !== false) || (($ch >= ord('0')) && ($ch <= ord('9'))));
|
||
}
|
||
|
||
// Проверка имени файла
|
||
static function isName($name) {
|
||
if (strlen(trim($name)) == 0) {
|
||
return false;
|
||
}
|
||
for ($i = 0; $i < strlen($name); $i++) {
|
||
if (!self::isCharName($name[$i])) {
|
||
return false;
|
||
}
|
||
}
|
||
return true;
|
||
}
|
||
|
||
public function isAbsolute()
|
||
{
|
||
return $this->absolute;
|
||
}
|
||
|
||
/**
|
||
* Подбирает новое временное имя для файла
|
||
*
|
||
* @param string $dst Предпологаемое имя файла
|
||
*
|
||
* @return string Новое имя файла
|
||
*/
|
||
static function resolveFile($dst)
|
||
{
|
||
$i = 0;
|
||
$file = self::skipExtension($dst);
|
||
$suffix = self::getExtension($dst);
|
||
$temp = $dst;
|
||
while (file_exists($temp)) {
|
||
$i ++;
|
||
$temp = $file . "." . $i . "." . $suffix;
|
||
}
|
||
return $temp;
|
||
}
|
||
|
||
|
||
/**
|
||
* Список файлов в директории
|
||
*
|
||
* @param array $allow массив расширений для файлов
|
||
* @param array $ignore массив имен пааок которые не нужно обрабатывать
|
||
*
|
||
* @returnarray
|
||
*/
|
||
public function getContent($allow = null, $ignore = array())
|
||
{
|
||
$ignore = array_merge(array(".", ".."), $ignore);
|
||
return self::fileList($this->__toString(), $allow, $ignore);
|
||
}
|
||
|
||
/**
|
||
* Обьединяет строки в путь соединяя необходимым разделителем
|
||
*
|
||
* @param array $allow массив расширений разрешеных для файлов
|
||
* @param array $ignore массив имен пааок которые не нужно обрабатывать
|
||
*
|
||
* @return array
|
||
*/
|
||
function getContentRec($allow = null, $ignore = array())
|
||
{
|
||
$result = array ();
|
||
$ignore = array_merge(array (".", ".."), $ignore);
|
||
self::fileListAll($result, $this->__toString(), $allow, $ignore);
|
||
return $result;
|
||
}
|
||
|
||
// Использовать SPL ???
|
||
protected static function fileList($base, &$allow, &$ignore)
|
||
{
|
||
if ($base == '') $base = '.';
|
||
$result = array ();
|
||
$handle = opendir($base);
|
||
if (is_resource($handle)) {
|
||
while (true) {
|
||
$file = readdir($handle);
|
||
if (is_string($file)) {
|
||
if (! in_array($file, $ignore)) {
|
||
$isDir = is_dir(Path::join($base, $file));
|
||
if ($isDir || ($allow == null) || in_array(self::getExtension($file), $allow)) {
|
||
$result[] = $file;
|
||
}
|
||
}
|
||
continue;
|
||
}
|
||
break;
|
||
}
|
||
closedir($handle);
|
||
}
|
||
// При обьединении ссылок можно обьеденить path, query, fragment
|
||
return $result;
|
||
}
|
||
|
||
protected static function fileListAll(&$result, $base, &$allow, &$ignore)
|
||
{
|
||
$files = self::fileList($base, $allow, $ignore);
|
||
foreach ($files as $name) {
|
||
$fullname = self::join($base, $name);
|
||
if (is_dir($fullname)) {
|
||
self::fileListAll($result, $fullname, $allow, $ignore);
|
||
} else {
|
||
array_push($result, $fullname);
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Создает недастающие папки для записи файла
|
||
*
|
||
* @param string $dst Полное имя файла
|
||
*
|
||
* @return void
|
||
*/
|
||
static function prepare($dst, $filename = true)
|
||
{
|
||
if ($filename) {
|
||
$path_dst = pathinfo($dst, PATHINFO_DIRNAME);
|
||
} else {
|
||
$path_dst = $dst;
|
||
}
|
||
if (! file_exists($path_dst)) {
|
||
mkdir($path_dst, 0777, true);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Обновить относительную ссылку при переносе файла
|
||
*
|
||
* @param String $relativePath - относительная ссылка до переноса
|
||
* @param String $srcFile - абсолютный путь к папке содержащей файл со ссылкой до переноса
|
||
* @param String $dstFile - абсолютный путь к папке содержащей файл со ссылкой после переноса
|
||
*
|
||
* @return String
|
||
*/
|
||
static function updateRelativePathOnFileMove($relativePath, $srcFile, $dstFile) {
|
||
$srcToDst = self::relative($srcFile, $dstFile);
|
||
return self::normalize(self::join($srcToDst, $relativePath));
|
||
}
|
||
|
||
/**
|
||
* Обновить относительную ссылку в файле при переносе папки
|
||
*
|
||
* @param String $relativePath - относительная ссылка до переноса
|
||
* @param String $fileDir - абсолютный путь к директории файла содержащего ссылку до переноса
|
||
* @param String $srcDir - абсолютный путь к переносимой директории до переноса
|
||
* @param String $dstDir - абсолютный путь к переносимой директории после переноса
|
||
*
|
||
* @return
|
||
*/
|
||
static function updateRelativePathOnDirectoryMove($relativePath, $fileDir, $srcDir, $dstDir) {
|
||
$relativePath = self::normalize($relativePath);
|
||
$fileDir = self::normalize($fileDir);
|
||
$srcDir = self::normalize($srcDir);
|
||
$dstDir = self::normalize($dstDir);
|
||
|
||
$stepsUpTotal = 0;
|
||
$relativePathParts = self::listFromString($relativePath);
|
||
foreach ($relativePathParts as $part) {
|
||
$stepsUpTotal += ($part==".."?1:0);
|
||
}
|
||
|
||
$fileDepthInFolder = count(self::listFromString($fileDir)) - count(self::listFromString($srcDir));
|
||
|
||
//проверка того, что путь выходит за пределы переносимой папки
|
||
if($stepsUpTotal <= $fileDepthInFolder) {
|
||
//возврат изначального пути если не выходит
|
||
return $relativePath;
|
||
} else {
|
||
//если выходит
|
||
//путь от корня к файлу, на который указывает ссылка
|
||
$oldAbsoluteLinkDstPath = self::normalize(self::join($fileDir, $relativePath));
|
||
$pathFromDir = self::relative($fileDir, $srcDir);
|
||
$newAbsoluteLinkSrcPath = self::normalize(self::join($dstDir, $pathFromDir));
|
||
return self::relative($oldAbsoluteLinkDstPath, $newAbsoluteLinkSrcPath);
|
||
}
|
||
|
||
}
|
||
}
|
||
|