Passed
Push — master ( 7dcb2c...27a2d0 )
by Daniel
11:00 queued 01:58
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') {
0 ignored issues
show
Bug Best Practice introduced by
The expression $nonceStyle of type null|string is loosely compared to true; this is ambiguous if the string can be empty. 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 string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
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
        } else {
73
            // Save querystring for later
74
            if (strpos($relativePath, '?') !== false) {
75
                list($relativePath, $query) = explode('?', $relativePath);
76
            }
77
78
            // Determine lookup mechanism based on existence of public/ folder.
79
            // From 5.0 onwards only resolvePublicResource() will be used.
80
            if (!Director::publicDir()) {
81
                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

81
                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...
82
            } else {
83
                list($exists, $absolutePath, $relativePath) = $this->resolvePublicResource($relativePath);
84
            }
85
        }
86
        if (!$exists) {
87
            trigger_error("File {$relativePath} does not exist", E_USER_NOTICE);
88
        }
89
90
        // Switch slashes for URL
91
        $relativeURL = Convert::slashes($relativePath, '/');
92
93
        // Apply url rewrites
94
        $rules = Config::inst()->get(static::class, 'url_rewrites') ?: [];
95
        foreach ($rules as $from => $to) {
96
            $relativeURL = preg_replace($from, $to, $relativeURL);
97
        }
98
99
        // Apply nonce
100
        // Don't add nonce to directories
101
        if ($this->nonceStyle && $exists && is_file($absolutePath)) {
102
            switch ($this->nonceStyle) {
103
                case 'mtime':
104
                    if ($query) {
105
                        $query .= '&';
106
                    }
107
                    $query .= "m=" . filemtime($absolutePath);
108
                    break;
109
            }
110
        }
111
112
        // Add back querystring
113
        if ($query) {
114
            $relativeURL .= '?' . $query;
115
        }
116
117
        return Director::baseURL() . $relativeURL;
118
    }
119
120
    /**
121
     * Update relative path for a module resource
122
     *
123
     * @param ModuleResource $resource
124
     * @return array List of [$exists, $absolutePath, $relativePath]
125
     */
126
    protected function resolveModuleResource(ModuleResource $resource)
127
    {
128
        // Load from module resource
129
        $relativePath = $resource->getRelativePath();
130
        $exists = $resource->exists();
131
        $absolutePath = $resource->getPath();
132
133
        // Rewrite to resources with public directory
134
        if (Director::publicDir()) {
135
            // All resources mapped directly to resources/
136
            $relativePath = Path::join(ManifestFileFinder::RESOURCES_DIR, $relativePath);
0 ignored issues
show
Bug introduced by
SilverStripe\Core\Manife...leFinder::RESOURCES_DIR of type string is incompatible with the type array expected by parameter $parts of SilverStripe\Core\Path::join(). ( Ignorable by Annotation )

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

136
            $relativePath = Path::join(/** @scrutinizer ignore-type */ ManifestFileFinder::RESOURCES_DIR, $relativePath);
Loading history...
137
        } elseif (stripos($relativePath, ManifestFileFinder::VENDOR_DIR . DIRECTORY_SEPARATOR) === 0) {
138
            // @todo Non-public dir support will be removed in 5.0, so remove this block there
139
            // If there is no public folder, map to resources/ but trim leading vendor/ too (4.0 compat)
140
            $relativePath = Path::join(
141
                ManifestFileFinder::RESOURCES_DIR,
142
                substr($relativePath, strlen(ManifestFileFinder::VENDOR_DIR))
143
            );
144
        }
145
        return [$exists, $absolutePath, $relativePath];
146
    }
147
148
    /**
149
     * Resolve resource in the absence of a public/ folder
150
     *
151
     * @deprecated 4.1.0...5.0.0 Will be removed in 5.0 when public/ folder becomes mandatory
152
     * @param string $relativePath
153
     * @return array List of [$exists, $absolutePath, $relativePath]
154
     */
155
    protected function resolveUnsecuredResource($relativePath)
156
    {
157
        // Check if the path requested is public-only, but we have no public folder
158
        $publicOnly = $this->inferPublicResourceRequired($relativePath);
159
        if ($publicOnly) {
160
            trigger_error('Requesting a public resource without a public folder has no effect', E_USER_WARNING);
161
        }
162
163
        // Resolve path to base
164
        $absolutePath = Path::join(Director::baseFolder(), $relativePath);
0 ignored issues
show
Bug introduced by
SilverStripe\Control\Director::baseFolder() of type string is incompatible with the type array expected by parameter $parts of SilverStripe\Core\Path::join(). ( Ignorable by Annotation )

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

164
        $absolutePath = Path::join(/** @scrutinizer ignore-type */ Director::baseFolder(), $relativePath);
Loading history...
165
        $exists = file_exists($absolutePath);
166
167
        // Rewrite vendor/ to resources/ folder
168
        if (stripos($relativePath, ManifestFileFinder::VENDOR_DIR . DIRECTORY_SEPARATOR) === 0) {
169
            $relativePath = Path::join(
170
                ManifestFileFinder::RESOURCES_DIR,
171
                substr($relativePath, strlen(ManifestFileFinder::VENDOR_DIR))
172
            );
173
        }
174
        return [$exists, $absolutePath, $relativePath];
175
    }
176
177
    /**
178
     * Determine if the requested $relativePath requires a public-only resource.
179
     * An error will occur if this file isn't immediately available in the public/ assets folder.
180
     *
181
     * @param string $relativePath Requested relative path which may have a public/ prefix.
182
     * This prefix will be removed if exists. This path will also be normalised to match DIRECTORY_SEPARATOR
183
     * @return bool True if the resource must be a public resource
184
     */
185
    protected function inferPublicResourceRequired(&$relativePath)
186
    {
187
        // Normalise path
188
        $relativePath = Path::normalise($relativePath, true);
189
190
        // Detect public-only request
191
        $publicOnly = stripos($relativePath, 'public' . DIRECTORY_SEPARATOR) === 0;
192
        if ($publicOnly) {
193
            $relativePath = substr($relativePath, strlen(Director::publicDir() . DIRECTORY_SEPARATOR));
194
        }
195
        return $publicOnly;
196
    }
197
198
    /**
199
     * Resolve a resource that may either exist in a public/ folder, or be exposed from the base path to
200
     * public/resources/
201
     *
202
     * @param string $relativePath
203
     * @return array List of [$exists, $absolutePath, $relativePath]
204
     */
205
    protected function resolvePublicResource($relativePath)
206
    {
207
        // Determine if we should search both public and base resources, or only public
208
        $publicOnly = $this->inferPublicResourceRequired($relativePath);
209
210
        // Search public folder first, and unless `public/` is prefixed, also private base path
211
        $publicPath = Path::join(Director::publicFolder(), $relativePath);
0 ignored issues
show
Bug introduced by
SilverStripe\Control\Director::publicFolder() of type string is incompatible with the type array expected by parameter $parts of SilverStripe\Core\Path::join(). ( Ignorable by Annotation )

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

211
        $publicPath = Path::join(/** @scrutinizer ignore-type */ Director::publicFolder(), $relativePath);
Loading history...
212
        if (file_exists($publicPath)) {
213
            // String is a literal url comitted directly to public folder
214
            return [true, $publicPath, $relativePath];
215
        }
216
217
        // Fall back to private path (and assume expose will make this available to resources/)
218
        $privatePath = Path::join(Director::baseFolder(), $relativePath);
219
        if (!$publicOnly && file_exists($privatePath)) {
220
            // String is private but exposed to resources/, so rewrite to the symlinked base
221
            $relativePath = Path::join(ManifestFileFinder::RESOURCES_DIR, $relativePath);
222
            return [true, $privatePath, $relativePath];
223
        }
224
225
        // File doesn't exist, fail
226
        return [false, null, $relativePath];
227
    }
228
}
229