Subversion Repositories oidplus

Rev

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
}