Converter.php 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195
  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
  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. */
  31. public function __construct($from, $to)
  32. {
  33. $shared = $this->shared($from, $to);
  34. if ($shared === '') {
  35. // when both paths have nothing in common, one of them is probably
  36. // absolute while the other is relative
  37. $cwd = getcwd();
  38. $from = strpos($from, $cwd) === 0 ? $from : $cwd.'/'.$from;
  39. $to = strpos($to, $cwd) === 0 ? $to : $cwd.'/'.$to;
  40. // or traveling the tree via `..`
  41. // attempt to resolve path, or assume it's fine if it doesn't exist
  42. $from = realpath($from) ?: $from;
  43. $to = realpath($to) ?: $to;
  44. }
  45. $from = $this->dirname($from);
  46. $to = $this->dirname($to);
  47. $from = $this->normalize($from);
  48. $to = $this->normalize($to);
  49. $this->from = $from;
  50. $this->to = $to;
  51. }
  52. /**
  53. * Normalize path.
  54. *
  55. * @param string $path
  56. *
  57. * @return string
  58. */
  59. protected function normalize($path)
  60. {
  61. // deal with different operating systems' directory structure
  62. $path = rtrim(str_replace(DIRECTORY_SEPARATOR, '/', $path), '/');
  63. /*
  64. * Example:
  65. * /home/forkcms/frontend/cache/compiled_templates/../../core/layout/css/../images/img.gif
  66. * to
  67. * /home/forkcms/frontend/core/layout/images/img.gif
  68. */
  69. do {
  70. $path = preg_replace('/[^\/]+(?<!\.\.)\/\.\.\//', '', $path, -1, $count);
  71. } while ($count);
  72. return $path;
  73. }
  74. /**
  75. * Figure out the shared path of 2 locations.
  76. *
  77. * Example:
  78. * /home/forkcms/frontend/core/layout/images/img.gif
  79. * and
  80. * /home/forkcms/frontend/cache/minified_css
  81. * share
  82. * /home/forkcms/frontend
  83. *
  84. * @param string $path1
  85. * @param string $path2
  86. *
  87. * @return string
  88. */
  89. protected function shared($path1, $path2)
  90. {
  91. // $path could theoretically be empty (e.g. no path is given), in which
  92. // case it shouldn't expand to array(''), which would compare to one's
  93. // root /
  94. $path1 = $path1 ? explode('/', $path1) : array();
  95. $path2 = $path2 ? explode('/', $path2) : array();
  96. $shared = array();
  97. // compare paths & strip identical ancestors
  98. foreach ($path1 as $i => $chunk) {
  99. if (isset($path2[$i]) && $path1[$i] == $path2[$i]) {
  100. $shared[] = $chunk;
  101. } else {
  102. break;
  103. }
  104. }
  105. return implode('/', $shared);
  106. }
  107. /**
  108. * Convert paths relative from 1 file to another.
  109. *
  110. * E.g.
  111. * ../images/img.gif relative to /home/forkcms/frontend/core/layout/css
  112. * should become:
  113. * ../../core/layout/images/img.gif relative to
  114. * /home/forkcms/frontend/cache/minified_css
  115. *
  116. * @param string $path The relative path that needs to be converted.
  117. *
  118. * @return string The new relative path.
  119. */
  120. public function convert($path)
  121. {
  122. // quit early if conversion makes no sense
  123. if ($this->from === $this->to) {
  124. return $path;
  125. }
  126. $path = $this->normalize($path);
  127. // if we're not dealing with a relative path, just return absolute
  128. if (strpos($path, '/') === 0) {
  129. return $path;
  130. }
  131. // normalize paths
  132. $path = $this->normalize($this->from.'/'.$path);
  133. // strip shared ancestor paths
  134. $shared = $this->shared($path, $this->to);
  135. $path = mb_substr($path, mb_strlen($shared));
  136. $to = mb_substr($this->to, mb_strlen($shared));
  137. // add .. for every directory that needs to be traversed to new path
  138. $to = str_repeat('../', mb_substr_count($to, '/'));
  139. return $to.ltrim($path, '/');
  140. }
  141. /**
  142. * Attempt to get the directory name from a path.
  143. *
  144. * @param string $path
  145. *
  146. * @return string
  147. */
  148. public function dirname($path)
  149. {
  150. if (is_file($path)) {
  151. return dirname($path);
  152. }
  153. if (is_dir($path)) {
  154. return rtrim($path, '/');
  155. }
  156. // no known file/dir, start making assumptions
  157. // ends in / = dir
  158. if (mb_substr($path, -1) === '/') {
  159. return rtrim($path, '/');
  160. }
  161. // has a dot in the name, likely a file
  162. if (preg_match('/.*\..*$/', basename($path)) !== 0) {
  163. return dirname($path);
  164. }
  165. // you're on your own here!
  166. return $path;
  167. }
  168. }