|
1
|
|
|
<?php |
|
2
|
|
|
|
|
3
|
|
|
namespace Charcoal\View; |
|
4
|
|
|
|
|
5
|
|
|
use InvalidArgumentException; |
|
6
|
|
|
|
|
7
|
|
|
// From PSR-3 |
|
8
|
|
|
use Psr\Log\LoggerAwareInterface; |
|
9
|
|
|
use Psr\Log\LoggerAwareTrait; |
|
10
|
|
|
|
|
11
|
|
|
// From 'charcoal-view' |
|
12
|
|
|
use Charcoal\View\LoaderInterface; |
|
13
|
|
|
|
|
14
|
|
|
/** |
|
15
|
|
|
* Base Template Loader |
|
16
|
|
|
* |
|
17
|
|
|
* Finds a template file in a collection of directory paths. |
|
18
|
|
|
*/ |
|
19
|
|
|
abstract class AbstractLoader implements |
|
20
|
|
|
LoggerAwareInterface, |
|
21
|
|
|
LoaderInterface |
|
22
|
|
|
{ |
|
23
|
|
|
use LoggerAwareTrait; |
|
24
|
|
|
|
|
25
|
|
|
/** |
|
26
|
|
|
* @var string |
|
27
|
|
|
*/ |
|
28
|
|
|
private $basePath = ''; |
|
29
|
|
|
|
|
30
|
|
|
/** |
|
31
|
|
|
* @var string[] |
|
32
|
|
|
*/ |
|
33
|
|
|
private $paths = []; |
|
34
|
|
|
|
|
35
|
|
|
/** |
|
36
|
|
|
* @var array |
|
37
|
|
|
*/ |
|
38
|
|
|
private $dynamicTemplates = []; |
|
39
|
|
|
|
|
40
|
|
|
/** |
|
41
|
|
|
* The cache of searched template files. |
|
42
|
|
|
* |
|
43
|
|
|
* @var array |
|
44
|
|
|
*/ |
|
45
|
|
|
private $fileCache = []; |
|
46
|
|
|
|
|
47
|
|
|
/** |
|
48
|
|
|
* Default constructor, if none is provided by the concrete class implementations. |
|
49
|
|
|
* |
|
50
|
|
|
* ## Required dependencies |
|
51
|
|
|
* - `logger` A PSR-3 logger |
|
52
|
|
|
* |
|
53
|
|
|
* @param array $data The class dependencies map. |
|
54
|
|
|
*/ |
|
55
|
|
|
public function __construct(array $data = null) |
|
56
|
|
|
{ |
|
57
|
|
|
$this->setLogger($data['logger']); |
|
58
|
|
|
$this->setBasePath($data['base_path']); |
|
59
|
|
|
$this->setPaths($data['paths']); |
|
60
|
|
|
} |
|
61
|
|
|
|
|
62
|
|
|
/** |
|
63
|
|
|
* Load a template content |
|
64
|
|
|
* |
|
65
|
|
|
* @deprecated $GLOBALS['widget_template'] |
|
66
|
|
|
* |
|
67
|
|
|
* @param string $ident The template ident to load and render. |
|
68
|
|
|
* @throws InvalidArgumentException If the dynamic template identifier is not a string. |
|
69
|
|
|
* @return string |
|
70
|
|
|
*/ |
|
71
|
|
|
public function load($ident) |
|
72
|
|
|
{ |
|
73
|
|
|
// Handle dynamic template |
|
74
|
|
|
if (substr($ident, 0, 1) === '$') { |
|
75
|
|
|
$tryLegacy = ($ident === '$widget_template'); |
|
76
|
|
|
|
|
77
|
|
|
$ident = $this->dynamicTemplate(substr($ident, 1)); |
|
78
|
|
|
|
|
79
|
|
|
// Legacy dynamic template hack |
|
80
|
|
|
if ($tryLegacy) { |
|
81
|
|
|
$ident = empty($GLOBALS['widget_template']) ? $ident : $GLOBALS['widget_template']; |
|
82
|
|
|
$this->logger->warning(sprintf( |
|
83
|
|
|
'%s is deprecated in favor of %s: %s', |
|
84
|
|
|
'$GLOBALS[\'widget_template\']', |
|
85
|
|
|
'setDynamicTemplate()', |
|
86
|
|
|
$ident |
|
87
|
|
|
)); |
|
88
|
|
|
if (!is_string($ident)) { |
|
89
|
|
|
throw new InvalidArgumentException( |
|
90
|
|
|
'Dynamic template ident (from "$widget_template") must be a string' |
|
91
|
|
|
); |
|
92
|
|
|
} |
|
93
|
|
|
} |
|
94
|
|
|
} |
|
95
|
|
|
|
|
96
|
|
|
$file = $this->findTemplateFile($ident); |
|
97
|
|
|
if ($file === null || $file === '') { |
|
98
|
|
|
return $ident; |
|
99
|
|
|
} |
|
100
|
|
|
|
|
101
|
|
|
return file_get_contents($file); |
|
102
|
|
|
} |
|
103
|
|
|
|
|
104
|
|
|
/** |
|
105
|
|
|
* @param string $varName The name of the variable to get template ident from. |
|
106
|
|
|
* @throws InvalidArgumentException If the var name is not a string. |
|
107
|
|
|
* @return string |
|
108
|
|
|
*/ |
|
109
|
|
|
public function dynamicTemplate($varName) |
|
110
|
|
|
{ |
|
111
|
|
|
if (!is_string($varName)) { |
|
112
|
|
|
throw new InvalidArgumentException( |
|
113
|
|
|
'Can not get dynamic template: var name is not a string.' |
|
114
|
|
|
); |
|
115
|
|
|
} |
|
116
|
|
|
|
|
117
|
|
|
if (!isset($this->dynamicTemplates[$varName])) { |
|
118
|
|
|
return ''; |
|
119
|
|
|
} |
|
120
|
|
|
|
|
121
|
|
|
return $this->dynamicTemplates[$varName]; |
|
122
|
|
|
} |
|
123
|
|
|
|
|
124
|
|
|
/** |
|
125
|
|
|
* @deprecated $GLOBALS['widget_template'] |
|
126
|
|
|
* |
|
127
|
|
|
* @param string $varName The name of the variable to set this template unto. |
|
128
|
|
|
* @param string|null $templateIdent The "dynamic template" to set or NULL to clear. |
|
129
|
|
|
* @throws InvalidArgumentException If var name is not a string |
|
130
|
|
|
* or if the template is not a string (and not null). |
|
131
|
|
|
* @return void |
|
132
|
|
|
*/ |
|
133
|
|
|
public function setDynamicTemplate($varName, $templateIdent) |
|
134
|
|
|
{ |
|
135
|
|
|
if (!is_string($varName)) { |
|
136
|
|
|
throw new InvalidArgumentException( |
|
137
|
|
|
'Can not set dynamic template: var name is not a string.' |
|
138
|
|
|
); |
|
139
|
|
|
} |
|
140
|
|
|
|
|
141
|
|
|
if ($templateIdent === null) { |
|
142
|
|
|
$this->removeDynamicTemplate($varName); |
|
|
|
|
|
|
143
|
|
|
return; |
|
144
|
|
|
} |
|
145
|
|
|
|
|
146
|
|
|
if (!is_string($templateIdent)) { |
|
147
|
|
|
throw new InvalidArgumentException( |
|
148
|
|
|
'Can not set dynamic template. Must be a a string, or null.' |
|
149
|
|
|
); |
|
150
|
|
|
} |
|
151
|
|
|
|
|
152
|
|
|
// Legacy dynamic template hack |
|
153
|
|
|
if ($varName === 'widget_template') { |
|
154
|
|
|
$GLOBALS['widget_template'] = $templateIdent; |
|
155
|
|
|
} |
|
156
|
|
|
|
|
157
|
|
|
$this->dynamicTemplates[$varName] = $templateIdent; |
|
158
|
|
|
} |
|
159
|
|
|
|
|
160
|
|
|
/** |
|
161
|
|
|
* @deprecated $GLOBALS['widget_template'] |
|
162
|
|
|
* |
|
163
|
|
|
* @param string $varName The name of the variable to remove. |
|
164
|
|
|
* @throws InvalidArgumentException If var name is not a string. |
|
165
|
|
|
* @return void |
|
166
|
|
|
*/ |
|
167
|
|
|
public function removeDynamicTemplate($varName) |
|
168
|
|
|
{ |
|
169
|
|
|
if (!is_string($varName)) { |
|
170
|
|
|
throw new InvalidArgumentException( |
|
171
|
|
|
'Can not set dynamic template: var name is not a string.' |
|
172
|
|
|
); |
|
173
|
|
|
} |
|
174
|
|
|
|
|
175
|
|
|
// Legacy dynamic template hack |
|
176
|
|
|
if ($varName === 'widget_template') { |
|
177
|
|
|
$GLOBALS['widget_template'] = null; |
|
178
|
|
|
} |
|
179
|
|
|
|
|
180
|
|
|
unset($this->dynamicTemplates[$varName]); |
|
181
|
|
|
} |
|
182
|
|
|
|
|
183
|
|
|
/** |
|
184
|
|
|
* @deprecated $GLOBALS['widget_template'] |
|
185
|
|
|
* |
|
186
|
|
|
* @return void |
|
187
|
|
|
*/ |
|
188
|
|
|
public function clearDynamicTemplates() |
|
189
|
|
|
{ |
|
190
|
|
|
// Legacy dynamic template hack |
|
191
|
|
|
$GLOBALS['widget_template'] = null; |
|
192
|
|
|
|
|
193
|
|
|
$this->dynamicTemplates = []; |
|
194
|
|
|
} |
|
195
|
|
|
|
|
196
|
|
|
/** |
|
197
|
|
|
* @return string |
|
198
|
|
|
*/ |
|
199
|
|
|
protected function basePath() |
|
200
|
|
|
{ |
|
201
|
|
|
return $this->basePath; |
|
202
|
|
|
} |
|
203
|
|
|
|
|
204
|
|
|
/** |
|
205
|
|
|
* @param string $basePath The base path to set. |
|
206
|
|
|
* @throws InvalidArgumentException If the base path parameter is not a string. |
|
207
|
|
|
* @return LoaderInterface Chainable |
|
208
|
|
|
*/ |
|
209
|
|
|
private function setBasePath($basePath) |
|
210
|
|
|
{ |
|
211
|
|
|
if (!is_string($basePath)) { |
|
212
|
|
|
throw new InvalidArgumentException( |
|
213
|
|
|
'Base path must be a string' |
|
214
|
|
|
); |
|
215
|
|
|
} |
|
216
|
|
|
$basePath = realpath($basePath); |
|
217
|
|
|
$this->basePath = rtrim($basePath, '/\\').DIRECTORY_SEPARATOR; |
|
218
|
|
|
return $this; |
|
219
|
|
|
} |
|
220
|
|
|
|
|
221
|
|
|
/** |
|
222
|
|
|
* @return string[] |
|
223
|
|
|
*/ |
|
224
|
|
|
protected function paths() |
|
225
|
|
|
{ |
|
226
|
|
|
return $this->paths; |
|
227
|
|
|
} |
|
228
|
|
|
|
|
229
|
|
|
/** |
|
230
|
|
|
* @param string[] $paths The list of path to add. |
|
231
|
|
|
* @return LoaderInterface Chainable |
|
232
|
|
|
*/ |
|
233
|
|
|
private function setPaths(array $paths) |
|
234
|
|
|
{ |
|
235
|
|
|
$this->paths = []; |
|
236
|
|
|
|
|
237
|
|
|
foreach ($paths as $path) { |
|
238
|
|
|
$this->addPath($path); |
|
239
|
|
|
} |
|
240
|
|
|
|
|
241
|
|
|
return $this; |
|
242
|
|
|
} |
|
243
|
|
|
|
|
244
|
|
|
/** |
|
245
|
|
|
* @param string $path The path to add to the load. |
|
246
|
|
|
* @return LoaderInterface Chainable |
|
247
|
|
|
*/ |
|
248
|
|
|
private function addPath($path) |
|
249
|
|
|
{ |
|
250
|
|
|
$this->paths[] = $this->resolvePath($path); |
|
251
|
|
|
|
|
252
|
|
|
return $this; |
|
253
|
|
|
} |
|
254
|
|
|
|
|
255
|
|
|
/** |
|
256
|
|
|
* @param string $path The path to resolve. |
|
257
|
|
|
* @throws InvalidArgumentException If the path argument is not a string. |
|
258
|
|
|
* @return string |
|
259
|
|
|
*/ |
|
260
|
|
|
private function resolvePath($path) |
|
261
|
|
|
{ |
|
262
|
|
|
if (!is_string($path)) { |
|
263
|
|
|
throw new InvalidArgumentException( |
|
264
|
|
|
'Path needs to be a string' |
|
265
|
|
|
); |
|
266
|
|
|
} |
|
267
|
|
|
|
|
268
|
|
|
$basePath = $this->basePath(); |
|
269
|
|
|
$path = rtrim($path, '/\\').DIRECTORY_SEPARATOR; |
|
270
|
|
|
if ($basePath && strpos($path, $basePath) === false) { |
|
271
|
|
|
$path = $basePath.$path; |
|
272
|
|
|
} |
|
273
|
|
|
|
|
274
|
|
|
return $path; |
|
275
|
|
|
} |
|
276
|
|
|
|
|
277
|
|
|
/** |
|
278
|
|
|
* Get the template file (full path + filename) to load from an ident. |
|
279
|
|
|
* |
|
280
|
|
|
* This method first generates the filename for an identifier and search for it in all of the loader's paths. |
|
281
|
|
|
* |
|
282
|
|
|
* @param string $ident The template identifier to load. |
|
283
|
|
|
* @throws InvalidArgumentException If the template ident is not a string. |
|
284
|
|
|
* @return string|null The full path + filename of the found template. NULL if nothing was found. |
|
285
|
|
|
*/ |
|
286
|
|
|
protected function findTemplateFile($ident) |
|
287
|
|
|
{ |
|
288
|
|
|
$key = hash('sha256', $ident, false); |
|
289
|
|
|
|
|
290
|
|
|
if (array_key_exists($key, $this->fileCache)) { |
|
291
|
|
|
return $this->fileCache[$key]; |
|
292
|
|
|
} |
|
293
|
|
|
|
|
294
|
|
|
if (!is_string($ident)) { |
|
295
|
|
|
throw new InvalidArgumentException(sprintf( |
|
296
|
|
|
'Template ident must be a string, received %s', |
|
297
|
|
|
is_object($ident) ? get_class($ident) : gettype($ident) |
|
298
|
|
|
)); |
|
299
|
|
|
} |
|
300
|
|
|
|
|
301
|
|
|
$filename = $this->filenameFromIdent($ident); |
|
302
|
|
|
$searchPaths = $this->paths(); |
|
303
|
|
|
foreach ($searchPaths as $searchPath) { |
|
304
|
|
|
$filepath = realpath($searchPath).'/'.strtolower($filename); |
|
305
|
|
|
if (file_exists($filepath)) { |
|
306
|
|
|
$this->fileCache[$key] = $filepath; |
|
307
|
|
|
return $filepath; |
|
308
|
|
|
} |
|
309
|
|
|
} |
|
310
|
|
|
|
|
311
|
|
|
$filepath = null; |
|
312
|
|
|
$this->fileCache[$key] = $filepath; |
|
313
|
|
|
return $filepath; |
|
314
|
|
|
} |
|
315
|
|
|
|
|
316
|
|
|
/** |
|
317
|
|
|
* @param string $ident The template identifier to convert to a filename. |
|
318
|
|
|
* @return string |
|
319
|
|
|
*/ |
|
320
|
|
|
abstract protected function filenameFromIdent($ident); |
|
321
|
|
|
} |
|
322
|
|
|
|
This method has been deprecated. The supplier of the class has supplied an explanatory message.
The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.