Passed
Push — 4.1.1 ( 01ed8a )
by Robbie
09:45
created

resolvePublicResource()   B

Complexity

Conditions 4
Paths 3

Size

Total Lines 22
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 9
nc 3
nop 1
dl 0
loc 22
rs 8.9197
c 0
b 0
f 0
1
<?php
2
3
namespace SilverStripe\Control;
4
5
use InvalidArgumentException;
6
use SilverStripe\Core\Config\Config;
7
use SilverStripe\Core\Convert;
8
use SilverStripe\Core\Manifest\ManifestFileFinder;
9
use SilverStripe\Core\Manifest\ModuleResource;
10
use SilverStripe\Core\Manifest\ResourceURLGenerator;
11
use SilverStripe\Core\Path;
12
13
/**
14
 * Generate URLs assuming that BASE_PATH is also the webroot
15
 * Standard SilverStripe 3 operation
16
 */
17
class SimpleResourceURLGenerator implements ResourceURLGenerator
18
{
19
    /**
20
     * Rewrites applied after generating url.
21
     * Note: requires either silverstripe/vendor-plugin-helper or silverstripe/vendor-plugin
22
     * to ensure the file is available.
23
     *
24
     * @config
25
     * @var array
26
     */
27
    private static $url_rewrites = [];
0 ignored issues
show
introduced by
The private property $url_rewrites is not used, and could be removed.
Loading history...
28
29
    /*
30
     * @var string
31
     */
32
    private $nonceStyle;
33
34
    /*
35
     * Get the style of nonce-suffixes to use, or null if disabled
36
     *
37
     * @return string|null
38
     */
39
    public function getNonceStyle()
40
    {
41
        return $this->nonceStyle;
42
    }
43
44
    /*
45
     * Set the style of nonce-suffixes to use, or null to disable
46
     * Currently only "mtime" is allowed
47
     *
48
     * @param string|null $nonceStyle The style of nonces to apply, or null to disable
49
     * @return $this
50
     */
51
    public function setNonceStyle($nonceStyle)
52
    {
53
        if ($nonceStyle && $nonceStyle !== 'mtime') {
54
            throw new InvalidArgumentException('The only allowed NonceStyle is mtime');
55
        }
56
        $this->nonceStyle = $nonceStyle;
57
        return $this;
58
    }
59
60
    /**
61
     * Return the URL for a resource, prefixing with Director::baseURL() and suffixing with a nonce
62
     *
63
     * @param string|ModuleResource $relativePath File or directory path relative to BASE_PATH
64
     * @return string Doman-relative URL
65
     * @throws InvalidArgumentException If the resource doesn't exist
66
     */
67
    public function urlForResource($relativePath)
68
    {
69
        $query = '';
70
        if ($relativePath instanceof ModuleResource) {
71
            list($exists, $absolutePath, $relativePath) = $this->resolveModuleResource($relativePath);
72
        } elseif (Director::is_absolute_url($relativePath)) {
73
            // Path is not relative, and probably not of this site
74
            return $relativePath;
75
        } else {
76
            // Save querystring for later
77
            if (strpos($relativePath, '?') !== false) {
78
                list($relativePath, $query) = explode('?', $relativePath);
79
            }
80
81
            // Determine lookup mechanism based on existence of public/ folder.
82
            // From 5.0 onwards only resolvePublicResource() will be used.
83
            if (!Director::publicDir()) {
84
                list($exists, $absolutePath, $relativePath) = $this->resolveUnsecuredResource($relativePath);
0 ignored issues
show
Deprecated Code introduced by
The function SilverStripe\Control\Sim...olveUnsecuredResource() has been deprecated: 4.1.0...5.0.0 Will be removed in 5.0 when public/ folder becomes mandatory ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

84
                list($exists, $absolutePath, $relativePath) = /** @scrutinizer ignore-deprecated */ $this->resolveUnsecuredResource($relativePath);

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
85
            } else {
86
                list($exists, $absolutePath, $relativePath) = $this->resolvePublicResource($relativePath);
87
            }
88
        }
89
        if (!$exists) {
90
            trigger_error("File {$relativePath} does not exist", E_USER_NOTICE);
91
        }
92
93
        // Switch slashes for URL
94
        $relativeURL = Convert::slashes($relativePath, '/');
95
96
        // Apply url rewrites
97
        $rules = Config::inst()->get(static::class, 'url_rewrites') ?: [];
98
        foreach ($rules as $from => $to) {
99
            $relativeURL = preg_replace($from, $to, $relativeURL);
100
        }
101
102
        // Apply nonce
103
        // Don't add nonce to directories
104
        if ($this->nonceStyle && $exists && is_file($absolutePath)) {
105
            switch ($this->nonceStyle) {
106
                case 'mtime':
107
                    if ($query) {
108
                        $query .= '&';
109
                    }
110
                    $query .= "m=" . filemtime($absolutePath);
111
                    break;
112
            }
113
        }
114
115
        // Add back querystring
116
        if ($query) {
117
            $relativeURL .= '?' . $query;
118
        }
119
120
        return Director::baseURL() . $relativeURL;
121
    }
122
123
    /**
124
     * Update relative path for a module resource
125
     *
126
     * @param ModuleResource $resource
127
     * @return array List of [$exists, $absolutePath, $relativePath]
128
     */
129
    protected function resolveModuleResource(ModuleResource $resource)
130
    {
131
        // Load from module resource
132
        $relativePath = $resource->getRelativePath();
133
        $exists = $resource->exists();
134
        $absolutePath = $resource->getPath();
135
136
        // Rewrite to resources with public directory
137
        if (Director::publicDir()) {
138
            // All resources mapped directly to resources/
139
            $relativePath = Path::join(ManifestFileFinder::RESOURCES_DIR, $relativePath);
140
        } elseif (stripos($relativePath, ManifestFileFinder::VENDOR_DIR . DIRECTORY_SEPARATOR) === 0) {
141
            // @todo Non-public dir support will be removed in 5.0, so remove this block there
142
            // If there is no public folder, map to resources/ but trim leading vendor/ too (4.0 compat)
143
            $relativePath = Path::join(
144
                ManifestFileFinder::RESOURCES_DIR,
145
                substr($relativePath, strlen(ManifestFileFinder::VENDOR_DIR))
146
            );
147
        }
148
        return [$exists, $absolutePath, $relativePath];
149
    }
150
151
    /**
152
     * Resolve resource in the absence of a public/ folder
153
     *
154
     * @deprecated 4.1.0...5.0.0 Will be removed in 5.0 when public/ folder becomes mandatory
155
     * @param string $relativePath
156
     * @return array List of [$exists, $absolutePath, $relativePath]
157
     */
158
    protected function resolveUnsecuredResource($relativePath)
159
    {
160
        // Check if the path requested is public-only, but we have no public folder
161
        $publicOnly = $this->inferPublicResourceRequired($relativePath);
162
        if ($publicOnly) {
163
            trigger_error('Requesting a public resource without a public folder has no effect', E_USER_WARNING);
164
        }
165
166
        // Resolve path to base
167
        $absolutePath = Path::join(Director::baseFolder(), $relativePath);
168
        $exists = file_exists($absolutePath);
169
170
        // Rewrite vendor/ to resources/ folder
171
        if (stripos($relativePath, ManifestFileFinder::VENDOR_DIR . DIRECTORY_SEPARATOR) === 0) {
172
            $relativePath = Path::join(
173
                ManifestFileFinder::RESOURCES_DIR,
174
                substr($relativePath, strlen(ManifestFileFinder::VENDOR_DIR))
175
            );
176
        }
177
        return [$exists, $absolutePath, $relativePath];
178
    }
179
180
    /**
181
     * Determine if the requested $relativePath requires a public-only resource.
182
     * An error will occur if this file isn't immediately available in the public/ assets folder.
183
     *
184
     * @param string $relativePath Requested relative path which may have a public/ prefix.
185
     * This prefix will be removed if exists. This path will also be normalised to match DIRECTORY_SEPARATOR
186
     * @return bool True if the resource must be a public resource
187
     */
188
    protected function inferPublicResourceRequired(&$relativePath)
189
    {
190
        // Normalise path
191
        $relativePath = Path::normalise($relativePath, true);
192
193
        // Detect public-only request
194
        $publicOnly = stripos($relativePath, 'public' . DIRECTORY_SEPARATOR) === 0;
195
        if ($publicOnly) {
196
            $relativePath = substr($relativePath, strlen(Director::publicDir() . DIRECTORY_SEPARATOR));
197
        }
198
        return $publicOnly;
199
    }
200
201
    /**
202
     * Resolve a resource that may either exist in a public/ folder, or be exposed from the base path to
203
     * public/resources/
204
     *
205
     * @param string $relativePath
206
     * @return array List of [$exists, $absolutePath, $relativePath]
207
     */
208
    protected function resolvePublicResource($relativePath)
209
    {
210
        // Determine if we should search both public and base resources, or only public
211
        $publicOnly = $this->inferPublicResourceRequired($relativePath);
212
213
        // Search public folder first, and unless `public/` is prefixed, also private base path
214
        $publicPath = Path::join(Director::publicFolder(), $relativePath);
215
        if (file_exists($publicPath)) {
216
            // String is a literal url comitted directly to public folder
217
            return [true, $publicPath, $relativePath];
218
        }
219
220
        // Fall back to private path (and assume expose will make this available to resources/)
221
        $privatePath = Path::join(Director::baseFolder(), $relativePath);
222
        if (!$publicOnly && file_exists($privatePath)) {
223
            // String is private but exposed to resources/, so rewrite to the symlinked base
224
            $relativePath = Path::join(ManifestFileFinder::RESOURCES_DIR, $relativePath);
225
            return [true, $privatePath, $relativePath];
226
        }
227
228
        // File doesn't exist, fail
229
        return [false, null, $relativePath];
230
    }
231
}
232