Completed
Push — master ( f548dd...46b15a )
by Hamish
23s
created

ThemeResourceLoader::instance()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 1 Features 0
Metric Value
cc 2
eloc 2
c 1
b 1
f 0
nc 2
nop 0
dl 0
loc 3
rs 10
1
<?php
2
3
namespace SilverStripe\View;
4
5
use Deprecation;
6
7
/**
8
 * Handles finding templates from a stack of template manifest objects.
9
 *
10
 * @package framework
11
 * @subpackage view
12
 */
13
class ThemeResourceLoader {
14
15
	/**
16
	 * @var ThemeResourceLoader
17
	 */
18
	private static $instance;
19
20
	protected $base;
21
22
	/**
23
	 * List of template "sets" that contain a test manifest, and have an alias.
24
	 * E.g. '$default'
25
	 *
26
	 * @var ThemeList[]
27
	 */
28
	protected $sets = [];
29
30
	/**
31
	 * @return ThemeResourceLoader
32
	 */
33
	public static function instance() {
34
		return self::$instance ? self::$instance : self::$instance = new self();
35
	}
36
37
	/**
38
	 * Set instance
39
	 *
40
	 * @param ThemeResourceLoader $instance
41
	 */
42
	public static function set_instance(ThemeResourceLoader $instance) {
43
		self::$instance = $instance;
44
	}
45
46
	public function __construct($base = null) {
47
		$this->base = $base ? $base : BASE_PATH;
48
	}
49
50
	/**
51
	 * Add a new theme manifest for a given identifier. E.g. '$default'
52
	 *
53
	 * @param string $set
54
	 * @param ThemeList $manifest
55
	 */
56
	public function addSet($set, ThemeList $manifest) {
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
		if(isset($this->sets[$set])) {
68
			return $this->sets[$set];
69
		}
70
		return null;
71
	}
72
73
	/**
74
	 * Given a theme identifier, determine the path from the root directory
75
	 *
76
	 * The mapping from $identifier to path follows these rules:
77
	 * - A simple theme name ('mytheme') which maps to the standard themes dir (/themes/mytheme)
78
	 * - A theme path with a leading slash ('/mymodule/themes/mytheme') which maps directly to that path.
79
	 * - or a vendored theme path. (vendor/mymodule:mytheme) which maps to the nested 'theme' within
80
	 *   that module. ('/mymodule/themes/mytheme').
81
	 * - A vendored module with no nested theme (vendor/mymodule) which maps to the root directory
82
	 *   of that module. ('/mymodule').
83
	 *
84
	 * @param string $identifier Theme identifier.
85
	 * @return string Path from root, not including leading or trailing forward slash. E.g. themes/mytheme
86
	 */
87
	public function getPath($identifier) {
88
		$slashPos = strpos($identifier, '/');
89
90
		// If identifier starts with "/", it's a path from root
91
		if ($slashPos === 0) {
92
			return substr($identifier, 1);
93
		}
94
		// Otherwise if there is a "/", identifier is a vendor'ed module
95
		elseif ($slashPos !== false) {
96
			// Extract from <vendor>/<module>:<theme> format.
97
			// <vendor> is optional, and if <theme> is omitted it defaults to the module root dir.
98
			// If <theme> is included, this is the name of the directory under moduleroot/themes/
99
			// which contains the theme.
100
			// <module> is always the name of the install directory, not necessarily the composer name.
101
			$parts = explode(':', $identifier, 2);
102
103
			list($vendor, $module) = explode('/', $parts[0], 2);
0 ignored issues
show
Unused Code introduced by
The assignment to $vendor is unused. Consider omitting it like so list($first,,$third).

This checks looks for assignemnts to variables using the list(...) function, where not all assigned variables are subsequently used.

Consider the following code example.

<?php

function returnThreeValues() {
    return array('a', 'b', 'c');
}

list($a, $b, $c) = returnThreeValues();

print $a . " - " . $c;

Only the variables $a and $c are used. There was no need to assign $b.

Instead, the list call could have been.

list($a,, $c) = returnThreeValues();
Loading history...
104
			$theme = count($parts) > 1 ? $parts[1] : '';
105
106
			$path = $module . ($theme ? '/themes/'.$theme : '');
107
108
			// Right now we require $module to be a silverstripe module (in root) or theme (in themes dir)
109
			// If both exist, we prefer theme
110
			if (is_dir(THEMES_PATH . '/' .$path)) {
111
				return THEMES_DIR . '/' . $path;
112
			}
113
			else {
114
				return $path;
115
			}
116
		}
117
		// Otherwise it's a (deprecated) old-style "theme" identifier
118
		else {
119
			return THEMES_DIR.'/'.$identifier;
120
		}
121
	}
122
123
	/**
124
	 * Attempts to find possible candidate templates from a set of template
125
	 * names from modules, current theme directory and finally the application
126
	 * folder.
127
	 *
128
	 * The template names can be passed in as plain strings, or be in the
129
	 * format "type/name", where type is the type of template to search for
130
	 * (e.g. Includes, Layout).
131
	 *
132
	 * @param string|array $template Template name, or template spec in array format with the keys
133
	 * 'type' (type string) and 'templates' (template hierarchy in order of precedence).
134
	 * If 'templates' is ommitted then any other item in the array will be treated as the template
135
	 * list.
136
	 * Templates with an .ss extension will be treated as file paths, and will bypass
137
	 * theme-coupled resolution.
138
	 * @param array $themes List of themes to use to resolve themes. In most cases
139
	 * you should pass in {@see SSViewer::get_themes()}
140
	 * @return string Absolute path to resolved template file, or null if not resolved.
141
	 * File location will be in the format themes/<theme>/templates/<directories>/<type>/<basename>.ss
142
	 * Note that type (e.g. 'Layout') is not the root level directory under 'templates'.
143
	 */
144
	public function findTemplate($template, $themes) {
145
		$type = '';
146
		if(is_array($template)) {
147
			// Check if templates has type specified
148
			if (array_key_exists('type', $template)) {
149
				$type = $template['type'];
150
				unset($template['type']);
151
			}
152
			// Templates are either nested in 'templates' or just the rest of the list
153
			$templateList = array_key_exists('templates', $template) ? $template['templates'] : $template;
154
		} else {
155
			$templateList = array($template);
156
		}
157
158
		// If we have an .ss extension, this is a path, not a template name. We should
159
		// pass in templates without extensions in order for template manifest to find
160
		// files dynamically.
161
		if(count($templateList) == 1 && substr($templateList[0], -3) == '.ss') {
162
			return $templateList[0];
163
		}
164
165
		foreach($templateList as $i => $template) {
166
			$template = str_replace('\\', '/', $template);
167
			$parts = explode('/', $template);
168
169
			$tail = array_pop($parts);
170
			$head = implode('/', $parts);
171
172
			$themePaths = $this->getThemePaths($themes);
173
			foreach($themePaths as $themePath) {
174
				// Join path
175
				$pathParts = [ $this->base, $themePath, 'templates', $head, $type, $tail ];
176
				$path = implode('/', array_filter($pathParts)) . '.ss';
177
				if (file_exists($path)) {
178
					return $path;
179
				}
180
			}
181
		}
182
183
		// No template found
184
		return null;
185
	}
186
187
	/**
188
	 * Resolve themed CSS path
189
	 *
190
	 * @param string $name Name of CSS file without extension
191
	 * @param array $themes List of themes
192
	 * @return string Path to resolved CSS file (relative to base dir)
193
	 */
194
	public function findThemedCSS($name, $themes)
195
	{
196
		$css = "/css/$name.css";
197
		$paths = $this->getThemePaths($themes);
198
		foreach ($paths as $themePath) {
199
			$abspath = $this->base . '/' . $themePath;
200
201
			if (file_exists($abspath . $css)) {
202
				return $themePath . $css;
203
			}
204
		}
205
206
		// CSS exists in no context
207
		return null;
208
	}
209
210
	/**
211
	 * Registers the given themeable javascript as required.
212
	 *
213
	 * A javascript file in the current theme path name 'themename/javascript/$name.js' is first searched for,
214
	 * and it that doesn't exist and the module parameter is set then a javascript file with that name in
215
	 * the module is used.
216
	 *
217
	 * @param string $name The name of the file - eg '/js/File.js' would have the name 'File'
218
	 * @param array $themes List of themes
219
	 * @return string Path to resolved javascript file (relative to base dir)
220
	 */
221
	public function findThemedJavascript($name, $themes) {
222
        $js = "/javascript/$name.js";
223
		$paths = $this->getThemePaths($themes);
224
		foreach ($paths as $themePath) {
225
			$abspath = $this->base . '/' . $themePath;
226
227
			if (file_exists($abspath . $js)) {
228
				return $themePath . $js;
229
			}
230
		}
231
232
		// js exists in no context
233
		return null;
234
	}
235
236
	/**
237
	 * Resolve all themes to the list of root folders relative to site root
238
	 *
239
	 * @param array $themes List of themes to resolve. Supports named theme sets.
240
	 * @return array List of root-relative folders in order of precendence.
241
	 */
242
	public function getThemePaths($themes) {
243
		$paths = [];
244
		foreach($themes as $themename) {
245
			// Expand theme sets
246
			$set = $this->getSet($themename);
247
			$subthemes = $set ? $set->getThemes() : [$themename];
248
249
			// Resolve paths
250
			foreach ($subthemes as $theme) {
251
				$paths[] = $this->getPath($theme);
252
			}
253
		}
254
		return $paths;
255
	}
256
}
257