Completed
Push — master ( 9f83d0...83609c )
by Matthias
02:56
created

Converter::__construct()   B

Complexity

Conditions 6
Paths 17

Size

Total Lines 25
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 5
Bugs 2 Features 0
Metric Value
c 5
b 2
f 0
dl 0
loc 25
rs 8.439
cc 6
eloc 14
nc 17
nop 2
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 <[email protected]>
16
 * @copyright Copyright (c) 2015, Matthias Mullie. All rights reserved.
17
 * @license MIT License
18
 */
19
class Converter
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
     */
35
    public function __construct($from, $to)
36
    {
37
        $shared = $this->shared($from, $to);
38
        if ($shared === '') {
39
            // when both paths have nothing in common, one of them is probably
40
            // absolute while the other is relative
41
            $cwd = getcwd();
42
            $from = strpos($from, $cwd) === 0 ? $from : $cwd.'/'.$from;
43
            $to = strpos($to, $cwd) === 0 ? $to : $cwd.'/'.$to;
44
45
            // or traveling the tree via `..`
46
            // attempt to resolve path, or assume it's fine if it doesn't exist
47
            $from = realpath($from) ?: $from;
48
            $to = realpath($to) ?: $to;
49
        }
50
51
        $from = $this->normalize($from);
52
        $to = $this->normalize($to);
53
54
        $from = $this->dirname($from);
55
        $to = $this->dirname($to);
56
57
        $this->from = $from;
58
        $this->to = $to;
59
    }
60
61
    /**
62
     * Normalize path.
63
     *
64
     * @param string $path
65
     *
66
     * @return string
67
     */
68
    protected function normalize($path)
69
    {
70
        // deal with different operating systems' directory structure
71
        $path = rtrim(str_replace(DIRECTORY_SEPARATOR, '/', $path), '/');
72
73
        /*
74
         * Example:
75
         *     /home/forkcms/frontend/cache/compiled_templates/../../core/layout/css/../images/img.gif
76
         * to
77
         *     /home/forkcms/frontend/core/layout/images/img.gif
78
         */
79
        do {
80
            $path = preg_replace('/[^\/]+(?<!\.\.)\/\.\.\//', '', $path, -1, $count);
81
        } while ($count);
0 ignored issues
show
Bug Best Practice introduced by
The expression $count of type integer|null is loosely compared to true; this is ambiguous if the integer can be zero. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
82
83
        return $path;
84
    }
85
86
    /**
87
     * Figure out the shared path of 2 locations.
88
     *
89
     * Example:
90
     *     /home/forkcms/frontend/core/layout/images/img.gif
91
     * and
92
     *     /home/forkcms/frontend/cache/minified_css
93
     * share
94
     *     /home/forkcms/frontend
95
     *
96
     * @param string $path1
97
     * @param string $path2
98
     *
99
     * @return string
100
     */
101
    protected function shared($path1, $path2)
102
    {
103
        // $path could theoretically be empty (e.g. no path is given), in which
104
        // case it shouldn't expand to array(''), which would compare to one's
105
        // root /
106
        $path1 = $path1 ? explode('/', $path1) : array();
107
        $path2 = $path2 ? explode('/', $path2) : array();
108
109
        $shared = array();
110
111
        // compare paths & strip identical ancestors
112
        foreach ($path1 as $i => $chunk) {
113
            if (isset($path2[$i]) && $path1[$i] == $path2[$i]) {
114
                $shared[] = $chunk;
115
            } else {
116
                break;
117
            }
118
        }
119
120
        return implode('/', $shared);
121
    }
122
123
    /**
124
     * Convert paths relative from 1 file to another.
125
     *
126
     * E.g.
127
     *     ../images/img.gif relative to /home/forkcms/frontend/core/layout/css
128
     * should become:
129
     *     ../../core/layout/images/img.gif relative to
130
     *     /home/forkcms/frontend/cache/minified_css
131
     *
132
     * @param string $path The relative path that needs to be converted.
133
     *
134
     * @return string The new relative path.
135
     */
136
    public function convert($path)
137
    {
138
        // quit early if conversion makes no sense
139
        if ($this->from === $this->to) {
140
            return $path;
141
        }
142
143
        $path = $this->normalize($path);
144
        // if we're not dealing with a relative path, just return absolute
145
        if (strpos($path, '/') === 0) {
146
            return $path;
147
        }
148
149
        // normalize paths
150
        $path = $this->normalize($this->from.'/'.$path);
151
152
        // strip shared ancestor paths
153
        $shared = $this->shared($path, $this->to);
154
        $path = mb_substr($path, mb_strlen($shared));
155
        $to = mb_substr($this->to, mb_strlen($shared));
156
157
        // add .. for every directory that needs to be traversed to new path
158
        $to = str_repeat('../', mb_substr_count($to, '/'));
159
160
        return $to.ltrim($path, '/');
161
    }
162
163
    /**
164
     * Attempt to get the directory name from a path.
165
     *
166
     * @param string $path
167
     *
168
     * @return string
169
     */
170
    public function dirname($path)
171
    {
172
        if (is_file($path)) {
173
            return dirname($path);
174
        }
175
176
        if (is_dir($path)) {
177
            return rtrim($path, '/');
178
        }
179
180
        // no known file/dir, start making assumptions
181
182
        // ends in / = dir
183
        if (mb_substr($path, -1) === '/') {
184
            return rtrim($path, '/');
185
        }
186
187
        // has a dot in the name, likely a file
188
        if (preg_match('/.*\..*$/', basename($path)) !== 0) {
189
            return dirname($path);
190
        }
191
192
        // you're on your own here!
193
        return $path;
194
    }
195
}
196