<?php namespace MatthiasMullie\PathConverter; /** * Convert paths relative from 1 file to another. * * E.g. * ../../images/icon.jpg relative to /css/imports/icons.css * becomes * ../images/icon.jpg relative to /css/minified.css * * Please report bugs on https://github.com/matthiasmullie/path-converter/issues * * @author Matthias Mullie <pathconverter@mullie.eu> * @copyright Copyright (c) 2015, Matthias Mullie. All rights reserved * @license MIT License */ class Converter implements ConverterInterface { /** * @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) === './') { * Example: > $path = substr($path, 2); * /home/forkcms/frontend/cache/compiled_templates/../../core/layout/css/../images/img.gif > } * to > * /home/forkcms/frontend/core/layout/images/img.gif > // remove references to current directory in the path. */ > $path = str_replace('/./', '/', $path); 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) : array(); $path2 = $path2 ? explode('/', $path2) : array(); $shared = array(); // 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; } }