183 lines
5.3 KiB
Plaintext
183 lines
5.3 KiB
Plaintext
|
/**
|
||
|
* @var string
|
||
|
*/
|
||
|
protected $from;
|
||
|
|
||
|
/**
|
||
|
* @var string
|
||
|
*/
|
||
|
protected $to;
|
||
|
|
||
|
/**
|
||
|
* @param string $from The original base path (directory, not file!)
|
||
|
* @param string $to The new base path (directory, not file!)
|
||
|
* @param string $root Root directory (defaults to `getcwd`)
|
||
|
*/
|
||
|
public function __construct($from, $to, $root = '')
|
||
|
{
|
||
|
$shared = $this->shared($from, $to);
|
||
|
if ($shared === '') {
|
||
|
// when both paths have nothing in common, one of them is probably
|
||
|
// absolute while the other is relative
|
||
|
$root = $root ?: getcwd();
|
||
|
$from = strpos($from, $root) === 0 ? $from : preg_replace('/\/+/', '/', $root . '/' . $from);
|
||
|
$to = strpos($to, $root) === 0 ? $to : preg_replace('/\/+/', '/', $root . '/' . $to);
|
||
|
|
||
|
// or traveling the tree via `..`
|
||
|
// attempt to resolve path, or assume it's fine if it doesn't exist
|
||
|
$from = @realpath($from) ?: $from;
|
||
|
$to = @realpath($to) ?: $to;
|
||
|
}
|
||
|
|
||
|
$from = $this->dirname($from);
|
||
|
$to = $this->dirname($to);
|
||
|
|
||
|
$from = $this->normalize($from);
|
||
|
$to = $this->normalize($to);
|
||
|
|
||
|
$this->from = $from;
|
||
|
$this->to = $to;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Normalize path.
|
||
|
*
|
||
|
* @param string $path
|
||
|
*
|
||
|
* @return string
|
||
|
*/
|
||
|
protected function normalize($path)
|
||
|
{
|
||
|
// deal with different operating systems' directory structure
|
||
|
$path = rtrim(str_replace(DIRECTORY_SEPARATOR, '/', $path), '/');
|
||
|
|
||
|
// remove leading current directory.
|
||
|
if (substr($path, 0, 2) === './') {
|
||
|
$path = substr($path, 2);
|
||
|
}
|
||
|
|
||
|
// remove references to current directory in the path.
|
||
|
$path = str_replace('/./', '/', $path);
|
||
|
|
||
|
/*
|
||
|
* Example:
|
||
|
* /home/forkcms/frontend/cache/compiled_templates/../../core/layout/css/../images/img.gif
|
||
|
* to
|
||
|
* /home/forkcms/frontend/core/layout/images/img.gif
|
||
|
*/
|
||
|
do {
|
||
|
$path = preg_replace('/[^\/]+(?<!\.\.)\/\.\.\//', '', $path, -1, $count);
|
||
|
} while ($count);
|
||
|
|
||
|
return $path;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Figure out the shared path of 2 locations.
|
||
|
*
|
||
|
* Example:
|
||
|
* /home/forkcms/frontend/core/layout/images/img.gif
|
||
|
* and
|
||
|
* /home/forkcms/frontend/cache/minified_css
|
||
|
* share
|
||
|
* /home/forkcms/frontend
|
||
|
*
|
||
|
* @param string $path1
|
||
|
* @param string $path2
|
||
|
*
|
||
|
* @return string
|
||
|
*/
|
||
|
protected function shared($path1, $path2)
|
||
|
{
|
||
|
// $path could theoretically be empty (e.g. no path is given), in which
|
||
|
// case it shouldn't expand to array(''), which would compare to one's
|
||
|
// root /
|
||
|
$path1 = $path1 ? explode('/', $path1) : [];
|
||
|
$path2 = $path2 ? explode('/', $path2) : [];
|
||
|
|
||
|
$shared = [];
|
||
|
|
||
|
// compare paths & strip identical ancestors
|
||
|
foreach ($path1 as $i => $chunk) {
|
||
|
if (isset($path2[$i]) && $path1[$i] == $path2[$i]) {
|
||
|
$shared[] = $chunk;
|
||
|
} else {
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return implode('/', $shared);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Convert paths relative from 1 file to another.
|
||
|
*
|
||
|
* E.g.
|
||
|
* ../images/img.gif relative to /home/forkcms/frontend/core/layout/css
|
||
|
* should become:
|
||
|
* ../../core/layout/images/img.gif relative to
|
||
|
* /home/forkcms/frontend/cache/minified_css
|
||
|
*
|
||
|
* @param string $path The relative path that needs to be converted
|
||
|
*
|
||
|
* @return string The new relative path
|
||
|
*/
|
||
|
public function convert($path)
|
||
|
{
|
||
|
// quit early if conversion makes no sense
|
||
|
if ($this->from === $this->to) {
|
||
|
return $path;
|
||
|
}
|
||
|
|
||
|
$path = $this->normalize($path);
|
||
|
// if we're not dealing with a relative path, just return absolute
|
||
|
if (strpos($path, '/') === 0) {
|
||
|
return $path;
|
||
|
}
|
||
|
|
||
|
// normalize paths
|
||
|
$path = $this->normalize($this->from . '/' . $path);
|
||
|
|
||
|
// strip shared ancestor paths
|
||
|
$shared = $this->shared($path, $this->to);
|
||
|
$path = mb_substr($path, mb_strlen($shared));
|
||
|
$to = mb_substr($this->to, mb_strlen($shared));
|
||
|
|
||
|
// add .. for every directory that needs to be traversed to new path
|
||
|
$to = str_repeat('../', count(array_filter(explode('/', $to))));
|
||
|
|
||
|
return $to . ltrim($path, '/');
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Attempt to get the directory name from a path.
|
||
|
*
|
||
|
* @param string $path
|
||
|
*
|
||
|
* @return string
|
||
|
*/
|
||
|
protected function dirname($path)
|
||
|
{
|
||
|
if (@is_file($path)) {
|
||
|
return dirname($path);
|
||
|
}
|
||
|
|
||
|
if (@is_dir($path)) {
|
||
|
return rtrim($path, '/');
|
||
|
}
|
||
|
|
||
|
// no known file/dir, start making assumptions
|
||
|
|
||
|
// ends in / = dir
|
||
|
if (mb_substr($path, -1) === '/') {
|
||
|
return rtrim($path, '/');
|
||
|
}
|
||
|
|
||
|
// has a dot in the name, likely a file
|
||
|
if (preg_match('/.*\..*$/', basename($path)) !== 0) {
|
||
|
return dirname($path);
|
||
|
}
|
||
|
|
||
|
// you're on your own here!
|
||
|
return $path;
|
||
|
}
|