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