phplibrary/src/Path.php

507 lines
16 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 $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)
{
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 = [];
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 = [];
$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 = [];
$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 = [])
{
$ignore = array_merge([".", ".."], $ignore);
return self::fileList($this->__toString(), $allow, $ignore);
}
/**
* Обьединяет строки в путь соединяя необходимым разделителем
*
* @param array $allow массив расширений разрешеных для файлов
* @param array $ignore массив имен пааок которые не нужно обрабатывать
*
* @return array
*/
function getContentRec($allow = null, $ignore = [])
{
$result = [];
$ignore = array_merge([".", ".."], $ignore);
self::fileListAll($result, $this->__toString(), $allow, $ignore);
return $result;
}
// Использовать SPL ???
protected static function fileList($base, &$allow, &$ignore)
{
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(&$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 string
*/
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);
}
}
}