phplibrary/src/Path.php

520 lines
18 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
/**
* Класс для работы с папками и путями
* Для итерации над файлами возможно лучше использовать SPL
*
*/
namespace ctiso;
class Path
{
const SEPARATOR = "/";
protected array $path = [];
protected array $url = [];
protected bool $absolute = false;
/**
* @param string $path Путь (Тип указан в doccomments т.к откудато приходит null)
*/
public function __construct($path = '')
{
$this->url = parse_url($path);
if (isset($this->url['path'])) {
$path = $this->url['path'];
// $path = preg_replace('/\/{2,}/', '/', $path);
$list = self::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 = self::optimize($list);
}
}
static function factory(string $path): Path {
return new Path($path);
}
public function getParts(): array
{
return $this->path;
}
public static function normalize(string $pathName): string
{
$path = new Path($pathName);
return $path->__toString();
}
/**
* Базовое имя
* @param string $path
* @return string
*/
public static function basename($path)
{
$list = preg_split('#\\\\|/#s', $path);
return end($list);
}
/**
* Возвращает расширение файла
*
* @param string $fileName Полное имя файла
* @return string
*/
static function getExtension($fileName)
{
return pathinfo($fileName, PATHINFO_EXTENSION);
}
/**
* Проверяет расширение файла
* @param string $fileName Полное имя файла
* @param string|array $extension Расширение файла
* @return bool
*/
static function isType($fileName, $extension)
{
if (is_array($extension)) {
return in_array(pathinfo($fileName, PATHINFO_EXTENSION), $extension);
} else {
return (pathinfo($fileName, PATHINFO_EXTENSION) == $extension);
}
}
/**
* Полное имя файла без расширения
*/
static function skipExtension(string $fileName): string
{
$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(string $fileName)
{
return pathinfo($fileName, PATHINFO_FILENAME);
}
/**
* Часть конструктора
* Преобразует строку пути в массив
*
* @param string $path Путь
* @return list<string>|false
*/
public static function listFromString(string $path): array|false
{
$list = preg_split('#\\\\|/#s', $path);
return $list;
}
/**
* Преобразует относительный путь в абсолютный
* @param array<string> $path Путь
* @return array<string>
*/
public static function optimize($path) //
{
$result = [];
foreach ($path as $n) {
switch ($n) {
// Может быть относительным или абсолютным путем
case "": case ".": break;
case "..":
if (count($result) > 0 && $result[count($result) - 1] != '..') {
array_pop($result); break;
}
default:
$result[] = $n;
}
}
return $result;
}
/**
* Сравнение двух путей на равентство
*/
public function equal(Path $path): bool
{
$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;
}
/**
* Преобразует путь в строку
* @param array{ host?: string, path?: string, query?: string, fragment?: string, scheme?: string, user?: string, pass?: string, port?: int} $path Путь
*/
public static function makeUrl($path): string
{
$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'] : '');
}
/**
* Преобразует путь в строку
*/
public function __toString(): string
{
$result = (($this->absolute) ? '/' : '') . implode(self::SEPARATOR, $this->path);
$this->url['path'] = $result;
return self::makeUrl($this->url);
}
/**
* Проверяет является ли папка родительской для другой папки
*
* @param Path $path
*/
public function isParent($path): bool
{
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(string $path1, string $path2): bool
{
$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();
}
/**
* Вычисляет относительный путь в виде строки
* @param string $rpath Путь относительно которого вычисляется относительный путь
* @param string $lpath Путь к которому вычисляется относительный путь
* @return string Относительный путь
*/
static function relative($rpath, $lpath) {
// Нужно проверять диск!!
$self = new Path($rpath);
$list = new Path($lpath);
$self_path = $self->getParts();
$list_path = $list->getParts();
$result = [];
$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);
}
/**
* @param string $path
* @return string
*/
public function append($path)
{
$base = $this->__toString();
return self::join($base, $path);
}
/**
* Обьединяет строки в Path соединяя необходимым разделителем
* fixme не обрабатывает параметры урла, решение Path::join(SITE_WWW_PATH) . '?param=pampam'
* @param string ...$args
* @return string
*/
static function fromJoin(...$args) {
$result = [];
$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'
* @param string ...$args
* @return string
*/
static function join(...$args)
{
$path = call_user_func_array([self::class, "fromJoin"], $args);
return self::makeUrl($path->url);
}
// Проверка структуры имени файла
static function checkName(string $name, string $extension): bool
{
return (strlen(pathinfo($name, PATHINFO_FILENAME)) > 0) && (pathinfo($name, PATHINFO_EXTENSION) == $extension);
}
static function isCharName(string $char): bool
{
$ch = ord($char);
return ((($ch >= ord('a')) && ($ch <= ord('z'))) || (strpos('-._', $char) !== false) || (($ch >= ord('0')) && ($ch <= ord('9'))));
}
// Проверка имени файла
static function isName(string $name): bool
{
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(): bool
{
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 ?string[] $allow массив расширений для файлов
* @param string[] $ignore массив имен пааок которые не нужно обрабатывать
*
* @return string[]
*/
public function getContent(?array $allow = null, array $ignore = [])
{
$ignore = array_merge([".", ".."], $ignore);
return self::fileList($this->__toString(), $allow, $ignore);
}
/**
* Обьединяет строки в путь соединяя необходимым разделителем
*
* @param ?string[] $allow массив расширений разрешеных для файлов
* @param string[] $ignore массив имен папок которые не нужно обрабатывать
*
* @return string[]
*/
function getContentRec(?array $allow = null, array $ignore = [])
{
$result = [];
$ignore = array_merge([".", ".."], $ignore);
self::fileListAll($result, $this->__toString(), $allow, $ignore);
return $result;
}
/**
* Список файлов в директории
*
* @param string $base Базовый путь
* @param ?string[] $allow массив расширений для файлов
* @param string[] $ignore массив имен папок которые не нужно обрабатывать
*
* @return string[]
*/
protected static function fileList(string $base, ?array &$allow, array &$ignore): array
{
if ($base == '') $base = '.';
$result = [];
$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(array &$result, string $base, ?array &$allow, array &$ignore): void
{
$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(string $dst, bool $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(string $relativePath, string $srcFile, string $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 string
*/
static function updateRelativePathOnDirectoryMove(string $relativePath, string $fileDir, string $srcDir, string $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);
}
}
}