Converter.php 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204
  1. <?php
  2. namespace MatthiasMullie\PathConverter;
  3. /**
  4. * Convert paths relative from 1 file to another.
  5. *
  6. * E.g.
  7. * ../../images/icon.jpg relative to /css/imports/icons.css
  8. * becomes
  9. * ../images/icon.jpg relative to /css/minified.css
  10. *
  11. * Please report bugs on https://github.com/matthiasmullie/path-converter/issues
  12. *
  13. * @author Matthias Mullie <pathconverter@mullie.eu>
  14. * @copyright Copyright (c) 2015, Matthias Mullie. All rights reserved
  15. * @license MIT License
  16. */
  17. class Converter implements ConverterInterface
  18. {
  19. /**
  20. * @var string
  21. */
  22. protected $from;
  23. /**
  24. * @var string
  25. */
  26. protected $to;
  27. /**
  28. * @param string $from The original base path (directory, not file!)
  29. * @param string $to The new base path (directory, not file!)
  30. * @param string $root Root directory (defaults to `getcwd`)
  31. */
  32. public function __construct($from, $to, $root = '')
  33. {
  34. $shared = $this->shared($from, $to);
  35. if ($shared === '') {
  36. // when both paths have nothing in common, one of them is probably
  37. // absolute while the other is relative
  38. $root = $root ?: getcwd();
  39. $from = strpos($from, $root) === 0 ? $from : preg_replace('/\/+/', '/', $root.'/'.$from);
  40. $to = strpos($to, $root) === 0 ? $to : preg_replace('/\/+/', '/', $root.'/'.$to);
  41. // or traveling the tree via `..`
  42. // attempt to resolve path, or assume it's fine if it doesn't exist
  43. $from = @realpath($from) ?: $from;
  44. $to = @realpath($to) ?: $to;
  45. }
  46. $from = $this->dirname($from);
  47. $to = $this->dirname($to);
  48. $from = $this->normalize($from);
  49. $to = $this->normalize($to);
  50. $this->from = $from;
  51. $this->to = $to;
  52. }
  53. /**
  54. * Normalize path.
  55. *
  56. * @param string $path
  57. *
  58. * @return string
  59. */
  60. protected function normalize($path)
  61. {
  62. // deal with different operating systems' directory structure
  63. $path = rtrim(str_replace(DIRECTORY_SEPARATOR, '/', $path), '/');
  64. // remove leading current directory.
  65. if (substr($path, 0, 2) === './') {
  66. $path = substr($path, 2);
  67. }
  68. // remove references to current directory in the path.
  69. $path = str_replace('/./', '/', $path);
  70. /*
  71. * Example:
  72. * /home/forkcms/frontend/cache/compiled_templates/../../core/layout/css/../images/img.gif
  73. * to
  74. * /home/forkcms/frontend/core/layout/images/img.gif
  75. */
  76. do {
  77. $path = preg_replace('/[^\/]+(?<!\.\.)\/\.\.\//', '', $path, -1, $count);
  78. } while ($count);
  79. return $path;
  80. }
  81. /**
  82. * Figure out the shared path of 2 locations.
  83. *
  84. * Example:
  85. * /home/forkcms/frontend/core/layout/images/img.gif
  86. * and
  87. * /home/forkcms/frontend/cache/minified_css
  88. * share
  89. * /home/forkcms/frontend
  90. *
  91. * @param string $path1
  92. * @param string $path2
  93. *
  94. * @return string
  95. */
  96. protected function shared($path1, $path2)
  97. {
  98. // $path could theoretically be empty (e.g. no path is given), in which
  99. // case it shouldn't expand to array(''), which would compare to one's
  100. // root /
  101. $path1 = $path1 ? explode('/', $path1) : array();
  102. $path2 = $path2 ? explode('/', $path2) : array();
  103. $shared = array();
  104. // compare paths & strip identical ancestors
  105. foreach ($path1 as $i => $chunk) {
  106. if (isset($path2[$i]) && $path1[$i] == $path2[$i]) {
  107. $shared[] = $chunk;
  108. } else {
  109. break;
  110. }
  111. }
  112. return implode('/', $shared);
  113. }
  114. /**
  115. * Convert paths relative from 1 file to another.
  116. *
  117. * E.g.
  118. * ../images/img.gif relative to /home/forkcms/frontend/core/layout/css
  119. * should become:
  120. * ../../core/layout/images/img.gif relative to
  121. * /home/forkcms/frontend/cache/minified_css
  122. *
  123. * @param string $path The relative path that needs to be converted
  124. *
  125. * @return string The new relative path
  126. */
  127. public function convert($path)
  128. {
  129. // quit early if conversion makes no sense
  130. if ($this->from === $this->to) {
  131. return $path;
  132. }
  133. $path = $this->normalize($path);
  134. // if we're not dealing with a relative path, just return absolute
  135. if (strpos($path, '/') === 0) {
  136. return $path;
  137. }
  138. // normalize paths
  139. $path = $this->normalize($this->from.'/'.$path);
  140. // strip shared ancestor paths
  141. $shared = $this->shared($path, $this->to);
  142. $path = mb_substr($path, mb_strlen($shared));
  143. $to = mb_substr($this->to, mb_strlen($shared));
  144. // add .. for every directory that needs to be traversed to new path
  145. $to = str_repeat('../', count(array_filter(explode('/', $to))));
  146. return $to.ltrim($path, '/');
  147. }
  148. /**
  149. * Attempt to get the directory name from a path.
  150. *
  151. * @param string $path
  152. *
  153. * @return string
  154. */
  155. protected function dirname($path)
  156. {
  157. if (@is_file($path)) {
  158. return dirname($path);
  159. }
  160. if (@is_dir($path)) {
  161. return rtrim($path, '/');
  162. }
  163. // no known file/dir, start making assumptions
  164. // ends in / = dir
  165. if (mb_substr($path, -1) === '/') {
  166. return rtrim($path, '/');
  167. }
  168. // has a dot in the name, likely a file
  169. if (preg_match('/.*\..*$/', basename($path)) !== 0) {
  170. return dirname($path);
  171. }
  172. // you're on your own here!
  173. return $path;
  174. }
  175. }