Completed
Push — resourceloader ( bd9fec )
by Sam
09:26
created

ResourceLoader::getResourceURL()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 2
dl 0
loc 4
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace SilverStripe\Core\Manifest;
4
5
/**
6
 * Finds resources from 1 or more themes/modules.
7
 */
8
class ResourceLoader
9
{
10
11
    /**
12
     * @var ResourceLoader
13
     */
14
    private static $instance;
15
16
    protected $base;
17
18
    /**
19
     * List of template "sets" that contain a test manifest, and have an alias.
20
     * E.g. '$default'
21
     *
22
     * @var ThemeList[]
23
     */
24
    protected $sets = [];
25
26
    /**
27
     * @return ResourceLoader
28
     */
29
    public static function inst()
30
    {
31
        return self::$instance ? self::$instance : self::$instance = new self();
32
    }
33
34
    /**
35
     * Set instance
36
     *
37
     * @param ResourceLoader $instance
38
     */
39
    public static function set_instance(ResourceLoader $instance)
40
    {
41
        self::$instance = $instance;
42
    }
43
44
    public function __construct($base = null)
45
    {
46
        $this->base = $base ? $base : BASE_PATH;
47
    }
48
49
    /**
50
     * Add a new theme manifest for a given identifier. E.g. '$default'
51
     *
52
     * @param string $set
53
     * @param ThemeList $manifest
54
     */
55
    public function addSet($set, ThemeList $manifest)
56
    {
57
        $this->sets[$set] = $manifest;
58
    }
59
60
    /**
61
     * Get a named theme set
62
     *
63
     * @param string $set
64
     * @return ThemeList
65
     */
66
    public function getSet($set)
67
    {
68
        if (isset($this->sets[$set])) {
69
            return $this->sets[$set];
70
        }
71
        return null;
72
    }
73
74
    /**
75
     * Returns the absolute path to the given resource
76
     */
77
    public function getResourcePath($theme, $resource)
0 ignored issues
show
Unused Code introduced by
The parameter $theme is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $resource is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
78
    {
79
80
    }
81
82
    /**
83
     * Returns the URL of the given resource.
84
     * The URL will be relative to the domain root (it will start with "/") unless it exists on a different domain.
85
     */
86
    public function getResourceURL($theme, $resource)
0 ignored issues
show
Unused Code introduced by
The parameter $theme is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $resource is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
87
    {
88
89
    }
90
91
    /**
92
     * Given a theme identifier, determine the path from the root directory
93
     *
94
     * The mapping from $identifier to path follows these rules:
95
     * - A simple theme name ('mytheme') which maps to the standard themes dir (/themes/mytheme)
96
     * - A theme path with a leading slash ('/mymodule/themes/mytheme') which maps directly to that path.
97
     * - or a vendored theme path. (vendor/mymodule:mytheme) which maps to the nested 'theme' within
98
     *   that module. ('/mymodule/themes/mytheme').
99
     * - A vendored module with no nested theme (vendor/mymodule) which maps to the root directory
100
     *   of that module. ('/mymodule').
101
     *
102
     * @param string $identifier Theme identifier.
103
     * @return string Path from root, not including leading or trailing forward slash. E.g. themes/mytheme
104
     */
105
    public function getPath($identifier)
106
    {
107
        $slashPos = strpos($identifier, '/');
108
109
        // If identifier starts with "/", it's a path from root
110
        if ($slashPos === 0) {
111
            return substr($identifier, 1);
112
        } // Otherwise if there is a "/", identifier is a vendor'ed module
113
        elseif ($slashPos !== false) {
114
            // Extract from <vendor>/<module>:<theme> format.
115
            // <vendor> is optional, and if <theme> is omitted it defaults to the module root dir.
116
            // If <theme> is included, this is the name of the directory under moduleroot/themes/
117
            // which contains the theme.
118
            // <module> is always the name of the install directory, not necessarily the composer name.
119
            $parts = explode(':', $identifier, 2);
120
121
            if (count($parts) > 1) {
122
                $theme = $parts[1];
123
                // "module/vendor:/sub/path"
124
                if ($theme[0] === '/') {
125
                    $subpath = $theme;
126
127
                // "module/vendor:subtheme"
128
                } else {
129
                    $subpath = '/themes/' . $theme;
130
                }
131
132
            // "module/vendor"
133
            } else {
134
                $subpath = '';
135
            }
136
137
            $package = $parts[0];
138
139
            // Find matching module for this package
140
            $module = ModuleLoader::inst()->getManifest()->getModule($package);
141
            if ($module) {
142
                $modulePath = $module->getRelativePath();
143
            } else {
144
                // fall back to dirname
145
                list(, $modulePath) = explode('/', $parts[0], 2);
146
147
                // If the module is in the themes/<module>/ prefer that
148
                if (is_dir(THEMES_PATH . '/' .$modulePath)) {
149
                    $modulePath = THEMES_DIR . '/' . $$modulePath;
150
                }
151
            }
152
153
            return ltrim($modulePath . $subpath, '/');
154
        } // Otherwise it's a (deprecated) old-style "theme" identifier
155
        else {
156
            return THEMES_DIR.'/'.$identifier;
157
        }
158
    }
159
160
    /**
161
     * Attempts to find possible candidate templates from a set of template
162
     * names from modules, current theme directory and finally the application
163
     * folder.
164
     *
165
     * The template names can be passed in as plain strings, or be in the
166
     * format "type/name", where type is the type of template to search for
167
     * (e.g. Includes, Layout).
168
     *
169
     * @param string|array $template Template name, or template spec in array format with the keys
170
     * 'type' (type string) and 'templates' (template hierarchy in order of precedence).
171
     * If 'templates' is ommitted then any other item in the array will be treated as the template
172
     * list, or list of templates each in the array spec given.
173
     * Templates with an .ss extension will be treated as file paths, and will bypass
174
     * theme-coupled resolution.
175
     * @param array $themes List of themes to use to resolve themes. In most cases
176
     * you should pass in {@see SSViewer::get_themes()}
177
     * @return string Absolute path to resolved template file, or null if not resolved.
178
     * File location will be in the format themes/<theme>/templates/<directories>/<type>/<basename>.ss
179
     * Note that type (e.g. 'Layout') is not the root level directory under 'templates'.
180
     */
181
    public function findTemplate($template, $themes)
182
    {
183
        $type = '';
184
        if (is_array($template)) {
185
            // Check if templates has type specified
186
            if (array_key_exists('type', $template)) {
187
                $type = $template['type'];
188
                unset($template['type']);
189
            }
190
            // Templates are either nested in 'templates' or just the rest of the list
191
            $templateList = array_key_exists('templates', $template) ? $template['templates'] : $template;
192
        } else {
193
            $templateList = array($template);
194
        }
195
196
        foreach ($templateList as $i => $template) {
197
            // Check if passed list of templates in array format
198
            if (is_array($template)) {
199
                $path = $this->findTemplate($template, $themes);
200
                if ($path) {
201
                    return $path;
202
                }
203
                continue;
204
            }
205
206
            // If we have an .ss extension, this is a path, not a template name. We should
207
            // pass in templates without extensions in order for template manifest to find
208
            // files dynamically.
209
            if (substr($template, -3) == '.ss' && file_exists($template)) {
210
                return $template;
211
            }
212
213
            // Check string template identifier
214
            $template = str_replace('\\', '/', $template);
215
            $parts = explode('/', $template);
216
217
            $tail = array_pop($parts);
218
            $head = implode('/', $parts);
219
220
            $themePaths = $this->getThemePaths($themes);
221
            foreach ($themePaths as $themePath) {
222
                // Join path
223
                $pathParts = [ $this->base, $themePath, 'templates', $head, $type, $tail ];
224
                $path = implode('/', array_filter($pathParts)) . '.ss';
225
                if (file_exists($path)) {
226
                    return $path;
227
                }
228
            }
229
        }
230
231
        // No template found
232
        return null;
233
    }
234
235
    /**
236
     * Resolve themed CSS path
237
     *
238
     * @param string $name Name of CSS file without extension
239
     * @param array $themes List of themes
240
     * @return string Path to resolved CSS file (relative to base dir)
241
     */
242 View Code Duplication
    public function findThemedCSS($name, $themes)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
243
    {
244
        if (substr($name, -4) !== '.css') {
245
            $name .= '.css';
246
        }
247
248
        $filename = $this->findThemedResource("css/$name", $themes);
249
        if ($filename === null) {
250
            $filename = $this->findThemedResource($name, $themes);
251
        }
252
253
        return $filename;
254
    }
255
256
    /**
257
     * Resolve themed javascript path
258
     *
259
     * A javascript file in the current theme path name 'themename/javascript/$name.js' is first searched for,
260
     * and it that doesn't exist and the module parameter is set then a javascript file with that name in
261
     * the module is used.
262
     *
263
     * @param string $name The name of the file - eg '/js/File.js' would have the name 'File'
264
     * @param array $themes List of themes
265
     * @return string Path to resolved javascript file (relative to base dir)
266
     */
267 View Code Duplication
    public function findThemedJavascript($name, $themes)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
268
    {
269
        if (substr($name, -3) !== '.js') {
270
            $name .= '.js';
271
        }
272
273
        $filename = $this->findThemedResource("javascript/$name", $themes);
274
        if ($filename === null) {
275
            $filename = $this->findThemedResource($name, $themes);
276
        }
277
278
        return $filename;
279
    }
280
281
    /**
282
     * Resolve a themed resource
283
     *
284
     * A themed resource and be any file that resides in a theme folder.
285
     *
286
     * @param string $resource A file path relative to the root folder of a theme
287
     * @param array $themes An order listed of themes to search
288
     */
289
    public function findThemedResource($resource, $themes)
290
    {
291
        if ($resource[0] !== '/') {
292
            $resource = '/' . $resource;
293
        }
294
295
        $paths = $this->getThemePaths($themes);
296
297
        foreach ($paths as $themePath) {
298
            $abspath = $this->base . '/' . $themePath;
299
            if (file_exists($abspath . $resource)) {
300
                return $themePath . $resource;
301
            }
302
        }
303
304
        // Resource exists in no context
305
        return null;
306
    }
307
308
    /**
309
     * Resolve all themes to the list of root folders relative to site root
310
     *
311
     * @param array $themes List of themes to resolve. Supports named theme sets.
312
     * @return array List of root-relative folders in order of precendence.
313
     */
314
    public function getThemePaths($themes)
315
    {
316
        $paths = [];
317
        foreach ($themes as $themename) {
318
            // Expand theme sets
319
            $set = $this->getSet($themename);
320
            $subthemes = $set ? $set->getThemes() : [$themename];
321
322
            // Resolve paths
323
            foreach ($subthemes as $theme) {
324
                $paths[] = $this->getPath($theme);
325
            }
326
        }
327
        return $paths;
328
    }
329
}
330