Passed
Pull Request — 4 (#9509)
by Brett
07:12
created

SimpleResourceURLGenerator::urlForResource()   F

Complexity

Conditions 16
Paths 361

Size

Total Lines 62
Code Lines 35

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 16
eloc 35
nc 361
nop 1
dl 0
loc 62
rs 2.7208
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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 && !in_array($nonceStyle, ['mtime', 'sha1', 'md5'])) {
54
            throw new InvalidArgumentException("NonceStyle '$nonceStyle' is not supported");
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
                    $method = 'filemtime';
108
                    break;
109
                case 'sha1':
110
                    $method = 'sha1_file';
111
                    break;
112
                case 'md5':
113
                    $method = 'md5_file';
114
                    break;
115
            }
116
117
            if ($query) {
118
                $query .= '&';
119
            }
120
            $query .= "m=" . call_user_func($method, $absolutePath);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $method does not seem to be defined for all execution paths leading up to this point.
Loading history...
121
        }
122
123
        // Add back querystring
124
        if ($query) {
125
            $relativeURL .= '?' . $query;
126
        }
127
128
        return Director::baseURL() . $relativeURL;
129
    }
130
131
    /**
132
     * Update relative path for a module resource
133
     *
134
     * @param ModuleResource $resource
135
     * @return array List of [$exists, $absolutePath, $relativePath]
136
     */
137
    protected function resolveModuleResource(ModuleResource $resource)
138
    {
139
        // Load from module resource
140
        $relativePath = $resource->getRelativePath();
141
        $exists = $resource->exists();
142
        $absolutePath = $resource->getPath();
143
144
        // Rewrite to _resources with public directory
145
        if (Director::publicDir()) {
146
            // All resources mapped directly to _resources/
147
            $relativePath = Path::join(RESOURCES_DIR, $relativePath);
148
        } elseif (stripos($relativePath, ManifestFileFinder::VENDOR_DIR . DIRECTORY_SEPARATOR) === 0) {
149
            // @todo Non-public dir support will be removed in 5.0, so remove this block there
150
            // If there is no public folder, map to _resources/ but trim leading vendor/ too (4.0 compat)
151
            $relativePath = Path::join(
152
                RESOURCES_DIR,
153
                substr($relativePath, strlen(ManifestFileFinder::VENDOR_DIR))
154
            );
155
        }
156
        return [$exists, $absolutePath, $relativePath];
157
    }
158
159
    /**
160
     * Resolve resource in the absence of a public/ folder
161
     *
162
     * @deprecated 4.1.0:5.0.0 Will be removed in 5.0 when public/ folder becomes mandatory
163
     * @param string $relativePath
164
     * @return array List of [$exists, $absolutePath, $relativePath]
165
     */
166
    protected function resolveUnsecuredResource($relativePath)
167
    {
168
        // Check if the path requested is public-only, but we have no public folder
169
        $publicOnly = $this->inferPublicResourceRequired($relativePath);
170
        if ($publicOnly) {
171
            trigger_error('Requesting a public resource without a public folder has no effect', E_USER_WARNING);
172
        }
173
174
        // Resolve path to base
175
        $absolutePath = Path::join(Director::baseFolder(), $relativePath);
176
        $exists = file_exists($absolutePath);
177
178
        // Rewrite vendor/ to _resources/ folder
179
        if (stripos($relativePath, ManifestFileFinder::VENDOR_DIR . DIRECTORY_SEPARATOR) === 0) {
180
            $relativePath = Path::join(
181
                RESOURCES_DIR,
182
                substr($relativePath, strlen(ManifestFileFinder::VENDOR_DIR))
183
            );
184
        }
185
        return [$exists, $absolutePath, $relativePath];
186
    }
187
188
    /**
189
     * Determine if the requested $relativePath requires a public-only resource.
190
     * An error will occur if this file isn't immediately available in the public/ assets folder.
191
     *
192
     * @param string $relativePath Requested relative path which may have a public/ prefix.
193
     * This prefix will be removed if exists. This path will also be normalised to match DIRECTORY_SEPARATOR
194
     * @return bool True if the resource must be a public resource
195
     */
196
    protected function inferPublicResourceRequired(&$relativePath)
197
    {
198
        // Normalise path
199
        $relativePath = Path::normalise($relativePath, true);
200
201
        // Detect public-only request
202
        $publicOnly = stripos($relativePath, 'public' . DIRECTORY_SEPARATOR) === 0;
203
        if ($publicOnly) {
204
            $relativePath = substr($relativePath, strlen(Director::publicDir() . DIRECTORY_SEPARATOR));
205
        }
206
        return $publicOnly;
207
    }
208
209
    /**
210
     * Resolve a resource that may either exist in a public/ folder, or be exposed from the base path to
211
     * public/_resources/
212
     *
213
     * @param string $relativePath
214
     * @return array List of [$exists, $absolutePath, $relativePath]
215
     */
216
    protected function resolvePublicResource($relativePath)
217
    {
218
        // Determine if we should search both public and base resources, or only public
219
        $publicOnly = $this->inferPublicResourceRequired($relativePath);
220
221
        // Search public folder first, and unless `public/` is prefixed, also private base path
222
        $publicPath = Path::join(Director::publicFolder(), $relativePath);
223
        if (file_exists($publicPath)) {
224
            // String is a literal url comitted directly to public folder
225
            return [true, $publicPath, $relativePath];
226
        }
227
228
        // Fall back to private path (and assume expose will make this available to _resources/)
229
        $privatePath = Path::join(Director::baseFolder(), $relativePath);
230
        if (!$publicOnly && file_exists($privatePath)) {
231
            // String is private but exposed to _resources/, so rewrite to the symlinked base
232
            $relativePath = Path::join(RESOURCES_DIR, $relativePath);
233
            return [true, $privatePath, $relativePath];
234
        }
235
236
        // File doesn't exist, fail
237
        return [false, null, $relativePath];
238
    }
239
}
240