This project does not seem to handle request data directly as such no vulnerable execution paths were found.
include
, or for example
via PHP's auto-loading mechanism.
These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | /** |
||
3 | * @author Todd Burry <[email protected]> |
||
4 | * @copyright 2009-2017 Vanilla Forums Inc. |
||
5 | * @license MIT |
||
6 | */ |
||
7 | |||
8 | namespace Ebi; |
||
9 | |||
10 | |||
11 | class Ebi { |
||
12 | /** |
||
13 | * @var string |
||
14 | */ |
||
15 | protected $cachePath; |
||
16 | /** |
||
17 | * @var callable[] |
||
18 | */ |
||
19 | protected $functions; |
||
20 | /** |
||
21 | * @var TemplateLoaderInterface |
||
22 | */ |
||
23 | private $templateLoader; |
||
24 | /** |
||
25 | * @var CompilerInterface |
||
26 | */ |
||
27 | private $compiler; |
||
28 | /** |
||
29 | * @var callable[] |
||
30 | */ |
||
31 | private $components = []; |
||
32 | /** |
||
33 | * @var array |
||
34 | */ |
||
35 | private $meta; |
||
36 | |||
37 | /** |
||
38 | * Ebi constructor. |
||
39 | * |
||
40 | * @param TemplateLoaderInterface $templateLoader Used to load template sources from component names. |
||
41 | * @param string $cachePath The path to cache compiled templates. |
||
42 | * @param CompilerInterface $compiler The compiler used to compile templates. |
||
0 ignored issues
–
show
|
|||
43 | */ |
||
44 | 83 | public function __construct(TemplateLoaderInterface $templateLoader, $cachePath, CompilerInterface $compiler = null) { |
|
45 | 83 | $this->templateLoader = $templateLoader; |
|
46 | 83 | $this->cachePath = $cachePath; |
|
0 ignored issues
–
show
Equals sign not aligned with surrounding assignments; expected 6 spaces but found 1 space
This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line. To visualize $a = "a";
$ab = "ab";
$abc = "abc";
will produce issues in the first and second line, while this second example $a = "a";
$ab = "ab";
$abc = "abc";
will produce no issues. ![]() |
|||
47 | 83 | $this->compiler = $compiler ?: new Compiler(); |
|
0 ignored issues
–
show
It seems like
$compiler ?: new \Ebi\Compiler() can also be of type object<Ebi\Compiler> . However, the property $compiler is declared as type object<Ebi\CompilerInterface> . Maybe add an additional type check?
Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly. For example, imagine you have a variable Either this assignment is in error or a type check should be added for that assignment. class Id
{
public $id;
public function __construct($id)
{
$this->id = $id;
}
}
class Account
{
/** @var Id $id */
public $id;
}
$account_id = false;
if (starsAreRight()) {
$account_id = new Id(42);
}
$account = new Account();
if ($account instanceof Id)
{
$account->id = $account_id;
}
![]() Equals sign not aligned with surrounding assignments; expected 7 spaces but found 1 space
This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line. To visualize $a = "a";
$ab = "ab";
$abc = "abc";
will produce issues in the first and second line, while this second example $a = "a";
$ab = "ab";
$abc = "abc";
will produce no issues. ![]() |
|||
48 | |||
49 | 83 | $this->defineFunction('abs'); |
|
50 | 83 | $this->defineFunction('arrayColumn', 'array_column'); |
|
51 | 83 | $this->defineFunction('arrayKeyExists', 'array_key_exists'); |
|
52 | 83 | $this->defineFunction('arrayKeys', 'array_keys'); |
|
53 | 83 | $this->defineFunction('arrayMerge', 'array_merge'); |
|
54 | 83 | $this->defineFunction('arrayMergeRecursive', 'array_merge_recursive'); |
|
55 | 83 | $this->defineFunction('arrayReplace', 'array_replace'); |
|
56 | 83 | $this->defineFunction('arrayReplaceRecursive', 'array_replace_recursive'); |
|
57 | 83 | $this->defineFunction('arrayReverse', 'array_reverse'); |
|
58 | 83 | $this->defineFunction('arrayValues', 'array_values'); |
|
59 | 83 | $this->defineFunction('base64Encode', 'base64_encode'); |
|
60 | 83 | $this->defineFunction('ceil'); |
|
61 | 83 | $this->defineFunction('componentExists', [$this, 'componentExists']); |
|
62 | 83 | $this->defineFunction('count'); |
|
63 | 83 | $this->defineFunction('empty'); |
|
64 | 83 | $this->defineFunction('floor'); |
|
65 | 83 | $this->defineFunction('formatDate', [$this, 'formatDate']); |
|
66 | 83 | $this->defineFunction('formatNumber', 'number_format'); |
|
67 | 83 | $this->defineFunction('htmlEncode', 'htmlspecialchars'); |
|
68 | 83 | $this->defineFunction('isArray', 'is_array'); |
|
69 | 83 | $this->defineFunction('isBool', 'is_bool'); |
|
70 | 83 | $this->defineFunction('isInt', 'is_int'); |
|
71 | 83 | $this->defineFunction('isScalar', 'is_scalar'); |
|
72 | 83 | $this->defineFunction('isString', 'is_string'); |
|
73 | 83 | $this->defineFunction('join'); |
|
74 | 83 | $this->defineFunction('lcase', $this->mb('strtolower')); |
|
75 | 83 | $this->defineFunction('lcfirst'); |
|
76 | 83 | $this->defineFunction('ltrim'); |
|
77 | 83 | $this->defineFunction('max'); |
|
78 | 83 | $this->defineFunction('min'); |
|
79 | 83 | $this->defineFunction('queryEncode', 'http_build_query'); |
|
80 | 83 | $this->defineFunction('round'); |
|
81 | 83 | $this->defineFunction('rtrim'); |
|
82 | 83 | $this->defineFunction('sprintf'); |
|
83 | 83 | $this->defineFunction('strlen', $this->mb('strlen')); |
|
84 | 83 | $this->defineFunction('substr', $this->mb('substr')); |
|
85 | 83 | $this->defineFunction('trim'); |
|
86 | 83 | $this->defineFunction('ucase', $this->mb('strtoupper')); |
|
87 | 83 | $this->defineFunction('ucfirst'); |
|
88 | 83 | $this->defineFunction('ucwords'); |
|
89 | 83 | $this->defineFunction('urlencode', 'rawurlencode'); |
|
90 | |||
91 | 83 | $this->defineFunction('@class', [$this, 'attributeClass']); |
|
92 | 83 | $this->defineFunction('@style', [$this, 'attributeStyle']); |
|
93 | |||
94 | // Define a simple component not found component to help troubleshoot. |
||
95 | $this->defineComponent('@component-not-found', function ($props) { |
||
96 | 1 | echo '<!-- Ebi component "'.htmlspecialchars($props['component']).'" not found. -->'; |
|
97 | 83 | }); |
|
98 | |||
99 | // Define a simple component exception. |
||
100 | $this->defineComponent('@exception', function ($props) { |
||
101 | 1 | echo "\n<!--\nEbi exception in component \"".htmlspecialchars($props['component'])."\".\n". |
|
102 | 1 | htmlspecialchars($props['message'])."\n-->\n"; |
|
103 | |||
104 | 83 | }); |
|
105 | |||
106 | 83 | $this->defineComponent('@compile-exception', [$this, 'writeCompileException']); |
|
107 | 83 | } |
|
108 | |||
109 | /** |
||
110 | * Register a runtime function. |
||
111 | * |
||
112 | * @param string $name The name of the function. |
||
113 | * @param callable $function The function callback. |
||
0 ignored issues
–
show
Should the type for parameter
$function not be callable|null ?
This check looks for It makes a suggestion as to what type it considers more descriptive. Most often this is a case of a parameter that can be null in addition to its declared types. ![]() |
|||
114 | */ |
||
115 | 83 | public function defineFunction($name, $function = null) { |
|
116 | 83 | if ($function === null) { |
|
117 | 83 | $function = $name; |
|
118 | 83 | } |
|
119 | |||
120 | 83 | $this->functions[strtolower($name)] = $function; |
|
121 | 83 | $this->compiler->defineFunction($name, $function); |
|
0 ignored issues
–
show
The method
defineFunction() does not seem to exist on object<Ebi\CompilerInterface> .
This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces. This is most likely a typographical error or the method has been renamed. ![]() |
|||
122 | 83 | } |
|
123 | |||
124 | 83 | private function mb($func) { |
|
0 ignored issues
–
show
The return type could not be reliably inferred; please add a
@return annotation.
Our type inference engine in quite powerful, but sometimes the code does not
provide enough clues to go by. In these cases we request you to add a ![]() |
|||
125 | 83 | return function_exists("mb_$func") ? "mb_$func" : $func; |
|
126 | } |
||
127 | |||
128 | /** |
||
129 | * Write a component to the output buffer. |
||
130 | * |
||
131 | * @param string $component The name of the component. |
||
132 | * @param array ...$args |
||
133 | */ |
||
134 | 23 | public function write($component, ...$args) { |
|
135 | 23 | $component = strtolower($component); |
|
136 | |||
137 | try { |
||
138 | 23 | $callback = $this->lookup($component); |
|
139 | |||
140 | 23 | if (is_callable($callback)) { |
|
141 | 23 | call_user_func($callback, ...$args); |
|
142 | 23 | } else { |
|
143 | 1 | $this->write('@component-not-found', ['component' => $component]); |
|
144 | } |
||
145 | 23 | } catch (\Throwable $ex) { |
|
0 ignored issues
–
show
The class
Throwable does not exist. Did you forget a USE statement, or did you not list all dependencies?
Scrutinizer analyzes your It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis. ![]() |
|||
146 | $this->write('@exception', ['message' => $ex->getMessage(), 'code', $ex->getCode(), 'component' => $component]); |
||
147 | return; |
||
148 | 1 | } catch (\Exception $ex) { |
|
149 | 1 | $this->write('@exception', ['message' => $ex->getMessage(), 'code', $ex->getCode(), 'component' => $component]); |
|
150 | 1 | return; |
|
151 | } |
||
152 | 23 | } |
|
153 | |||
154 | /** |
||
155 | * Lookup a component with a given name. |
||
156 | * |
||
157 | * @param string $component The component to lookup. |
||
158 | * @return callable|null Returns the component function or **null** if the component is not found. |
||
159 | */ |
||
160 | 78 | public function lookup($component) { |
|
161 | 78 | $component = strtolower($component); |
|
162 | 78 | $key = $this->componentKey($component); |
|
0 ignored issues
–
show
Equals sign not aligned with surrounding assignments; expected 7 spaces but found 1 space
This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line. To visualize $a = "a";
$ab = "ab";
$abc = "abc";
will produce issues in the first and second line, while this second example $a = "a";
$ab = "ab";
$abc = "abc";
will produce no issues. ![]() |
|||
163 | |||
164 | 78 | if (!array_key_exists($key, $this->components)) { |
|
165 | 73 | $this->loadComponent($component); |
|
166 | 73 | } |
|
167 | |||
168 | 78 | if (isset($this->components[$key])) { |
|
169 | 77 | return $this->components[$key]; |
|
170 | } else { |
||
171 | // Mark a tombstone to the component array so it doesn't keep getting loaded. |
||
172 | 2 | $this->components[$key] = null; |
|
173 | 2 | return null; |
|
174 | } |
||
175 | } |
||
176 | |||
177 | /** |
||
178 | * Check to see if a component exists. |
||
179 | * |
||
180 | * @param string $component The name of the component. |
||
181 | * @param bool $loader Whether or not to use the component loader or just look in the component cache. |
||
182 | * @return bool Returns **true** if the component exists or **false** otherwise. |
||
183 | */ |
||
184 | 2 | public function componentExists($component, $loader = true) { |
|
185 | 2 | $componentKey = $this->componentKey($component); |
|
186 | 2 | if (array_key_exists($componentKey, $this->components)) { |
|
187 | 1 | return $this->components[$componentKey] !== null; |
|
188 | 2 | } elseif ($loader) { |
|
189 | 2 | return !empty($this->templateLoader->cacheKey($component)); |
|
190 | } |
||
191 | 1 | return false; |
|
192 | } |
||
193 | |||
194 | /** |
||
195 | * Strip the namespace off a component name to get the component key. |
||
196 | * |
||
197 | * @param string $component The full name of the component with a possible namespace. |
||
198 | * @return string Returns the component key. |
||
199 | */ |
||
200 | 80 | protected function componentKey($component) { |
|
201 | 80 | if (false !== $pos = strpos($component, ':')) { |
|
202 | 1 | $component = substr($component, $pos + 1); |
|
203 | 1 | } |
|
204 | 80 | return strtolower($component); |
|
205 | } |
||
206 | |||
207 | /** |
||
208 | * Load a component. |
||
209 | * |
||
210 | * @param string $component The name of the component to load. |
||
211 | * @return callable|null Returns the component or **null** if the component isn't found. |
||
212 | */ |
||
213 | 73 | protected function loadComponent($component) { |
|
214 | 73 | $cacheKey = $this->templateLoader->cacheKey($component); |
|
215 | // The template loader can tell us a template doesn't exist when giving the cache key. |
||
216 | 73 | if (empty($cacheKey)) { |
|
217 | 2 | return null; |
|
218 | } |
||
219 | |||
220 | 71 | $cachePath = "{$this->cachePath}/$cacheKey.php"; |
|
0 ignored issues
–
show
Equals sign not aligned with surrounding assignments; expected 4 spaces but found 1 space
This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line. To visualize $a = "a";
$ab = "ab";
$abc = "abc";
will produce issues in the first and second line, while this second example $a = "a";
$ab = "ab";
$abc = "abc";
will produce no issues. ![]() |
|||
221 | 71 | $componentKey = $this->componentKey($component); |
|
222 | |||
223 | 71 | if (!file_exists($cachePath)) { |
|
224 | 71 | $src = $this->templateLoader->load($component); |
|
225 | try { |
||
226 | 71 | return $this->compile($componentKey, $src, $cacheKey); |
|
227 | 8 | } catch (CompileException $ex) { |
|
228 | 8 | $props = ['message' => $ex->getMessage()] + $ex->getContext(); |
|
0 ignored issues
–
show
Equals sign not aligned with surrounding assignments; expected 34 spaces but found 1 space
This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line. To visualize $a = "a";
$ab = "ab";
$abc = "abc";
will produce issues in the first and second line, while this second example $a = "a";
$ab = "ab";
$abc = "abc";
will produce no issues. ![]() |
|||
229 | 8 | return $this->components[$componentKey] = function() use ($props) { |
|
230 | 8 | $this->write('@compile-exception', $props); |
|
231 | 8 | }; |
|
232 | } |
||
233 | } else { |
||
234 | return $this->includeComponent($componentKey, $cachePath); |
||
235 | } |
||
236 | } |
||
237 | |||
238 | 8 | protected function writeCompileException($props) { |
|
239 | 8 | echo "\n<section class=\"ebi-ex\">\n", |
|
240 | 8 | '<h2>Error compiling '.htmlspecialchars($props['path'])." near line {$props['line']}.</h2>\n"; |
|
241 | |||
242 | 8 | echo '<p class="ebi-ex-message">'.htmlspecialchars($props['message'])."</p>\n"; |
|
243 | |||
244 | 8 | if (!empty($props['source'])) { |
|
245 | 6 | $source = $props['source']; |
|
246 | 6 | if (isset($props['sourcePosition'])) { |
|
247 | 3 | $pos = $props['sourcePosition']; |
|
248 | 3 | $len = isset($props['sourceLength']) ? $props['sourceLength'] : 1; |
|
249 | |||
250 | 3 | if ($len === 1) { |
|
251 | // Small kludge to select a viewable character. |
||
252 | 3 | for (; $pos >= 0 && isset($source[$pos]) && in_array($source[$pos], [' ', "\n"], true); $pos--, $len++) { |
|
0 ignored issues
–
show
|
|||
253 | // It's all in the loop. |
||
254 | 1 | } |
|
255 | 3 | } |
|
256 | |||
257 | 3 | $source = htmlspecialchars(substr($source, 0, $pos)). |
|
258 | 3 | '<mark class="ebi-ex-highlight">'.htmlspecialchars(substr($source, $pos, $len)).'</mark>'. |
|
259 | 3 | htmlspecialchars(substr($source, $pos + $len)); |
|
260 | 3 | } else { |
|
261 | 3 | $source = htmlspecialchars($source); |
|
262 | } |
||
263 | |||
264 | 6 | echo '<pre class="ebi-ex-source ebi-ex-context"><code>', |
|
265 | $source, |
||
266 | "</code></pre>\n"; |
||
267 | 6 | } |
|
268 | |||
269 | 8 | if (!empty($props['lines'])) { |
|
270 | 8 | echo '<pre class="ebi-ex-source ebi-ex-lines">'; |
|
271 | |||
272 | 8 | foreach ($props['lines'] as $i => $line) { |
|
273 | 8 | echo '<code class="ebi-ex-line">'; |
|
274 | |||
275 | 8 | $str = sprintf("%3d. %s", $i, htmlspecialchars($line)); |
|
276 | 8 | if ($i === $props['line']) { |
|
277 | 8 | echo "<mark class=\"ebi-ex-highlight\">$str</mark>"; |
|
278 | 8 | } else { |
|
279 | 6 | echo $str; |
|
280 | } |
||
281 | |||
282 | 8 | echo "</code>\n"; |
|
283 | 8 | } |
|
284 | |||
285 | 8 | echo "</pre>\n"; |
|
286 | 8 | } |
|
287 | |||
288 | 8 | echo "</section>\n"; |
|
289 | 8 | } |
|
290 | |||
291 | /** |
||
292 | * Check to see if a specific cache key exists in the cache. |
||
293 | * |
||
294 | * @param string $cacheKey The cache key to check. |
||
295 | * @return bool Returns **true** if there is a cache key at the file or **false** otherwise. |
||
296 | */ |
||
297 | 1 | public function cacheKeyExists($cacheKey) { |
|
298 | 1 | $cachePath = "{$this->cachePath}/$cacheKey.php"; |
|
299 | 1 | return file_exists($cachePath); |
|
300 | } |
||
301 | |||
302 | /** |
||
303 | * Compile a component from source, cache it and include it. |
||
304 | * |
||
305 | * @param string $component The name of the component. |
||
306 | * @param string $src The component source. |
||
307 | * @param string $cacheKey The cache key of the component. |
||
308 | * @return callable|null Returns the compiled component closure. |
||
309 | */ |
||
310 | 75 | public function compile($component, $src, $cacheKey) { |
|
311 | 75 | $cachePath = "{$this->cachePath}/$cacheKey.php"; |
|
312 | 75 | $component = strtolower($component); |
|
313 | |||
314 | 75 | $php = $this->compiler->compile($src, ['basename' => $component, 'path' => $cacheKey]); |
|
0 ignored issues
–
show
Equals sign not aligned with surrounding assignments; expected 5 spaces but found 1 space
This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line. To visualize $a = "a";
$ab = "ab";
$abc = "abc";
will produce issues in the first and second line, while this second example $a = "a";
$ab = "ab";
$abc = "abc";
will produce no issues. ![]() |
|||
315 | 67 | $comment = "/*\n".str_replace('*/', '❄/', trim($src))."\n*/"; |
|
316 | |||
317 | 67 | $this->filePutContents($cachePath, "<?php\n$comment\n$php"); |
|
318 | |||
319 | 67 | return $this->includeComponent($component, $cachePath); |
|
320 | } |
||
321 | |||
322 | /** |
||
323 | * Include a cached component. |
||
324 | * |
||
325 | * @param string $component The component key. |
||
326 | * @param string $cachePath The path to the component. |
||
327 | * @return callable|null Returns the component function or **null** if the component wasn't properly defined. |
||
328 | */ |
||
329 | 67 | private function includeComponent($component, $cachePath) { |
|
330 | 67 | unset($this->components[$component]); |
|
331 | 67 | $fn = $this->requireFile($cachePath); |
|
332 | |||
333 | 67 | if (isset($this->components[$component])) { |
|
334 | 67 | return $this->components[$component]; |
|
335 | } elseif (is_callable($fn)) { |
||
336 | $this->defineComponent($component, $fn); |
||
337 | return $fn; |
||
338 | } else { |
||
339 | $this->components[$component] = null; |
||
340 | return null; |
||
341 | } |
||
342 | } |
||
343 | |||
344 | /** |
||
345 | * A safe version of {@link file_put_contents()} that also clears op caches. |
||
346 | * |
||
347 | * @param string $path The path to save to. |
||
348 | * @param string $contents The contents of the file. |
||
349 | * @return bool Returns **true** on success or **false** on failure. |
||
350 | */ |
||
351 | 67 | private function filePutContents($path, $contents) { |
|
352 | 67 | if (!file_exists(dirname($path))) { |
|
353 | 4 | mkdir(dirname($path), 0777, true); |
|
354 | 4 | } |
|
355 | 67 | $tmpPath = tempnam(dirname($path), 'ebi-'); |
|
356 | 67 | $r = false; |
|
0 ignored issues
–
show
Equals sign not aligned with surrounding assignments; expected 7 spaces but found 1 space
This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line. To visualize $a = "a";
$ab = "ab";
$abc = "abc";
will produce issues in the first and second line, while this second example $a = "a";
$ab = "ab";
$abc = "abc";
will produce no issues. ![]() |
|||
357 | 67 | if (file_put_contents($tmpPath, $contents) !== false) { |
|
358 | 67 | chmod($tmpPath, 0664); |
|
359 | 67 | $r = rename($tmpPath, $path); |
|
360 | 67 | } |
|
361 | |||
362 | 67 | if (function_exists('apc_delete_file')) { |
|
363 | // This fixes a bug with some configurations of apc. |
||
364 | @apc_delete_file($path); |
||
0 ignored issues
–
show
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.
If you suppress an error, we recommend checking for the error condition explicitly: // For example instead of
@mkdir($dir);
// Better use
if (@mkdir($dir) === false) {
throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
![]() |
|||
365 | 67 | } elseif (function_exists('opcache_invalidate')) { |
|
366 | 67 | @opcache_invalidate($path); |
|
0 ignored issues
–
show
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.
If you suppress an error, we recommend checking for the error condition explicitly: // For example instead of
@mkdir($dir);
// Better use
if (@mkdir($dir) === false) {
throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
![]() |
|||
367 | 67 | } |
|
368 | |||
369 | 67 | return $r; |
|
370 | } |
||
371 | |||
372 | /** |
||
373 | * Include a file. |
||
374 | * |
||
375 | * This is method is useful for including a file bound to this object instance. |
||
376 | * |
||
377 | * @param string $path The path to the file to include. |
||
378 | * @return mixed Returns the result of the include. |
||
379 | */ |
||
380 | 67 | public function requireFile($path) { |
|
381 | 67 | return require $path; |
|
382 | } |
||
383 | |||
384 | /** |
||
385 | * Register a component. |
||
386 | * |
||
387 | * @param string $name The name of the component to register. |
||
388 | * @param callable $component The component function. |
||
389 | */ |
||
390 | 83 | public function defineComponent($name, callable $component) { |
|
391 | 83 | $this->components[$name] = $component; |
|
392 | 83 | } |
|
393 | |||
394 | /** |
||
395 | * Render a component to a string. |
||
396 | * |
||
397 | * @param string $component The name of the component to render. |
||
398 | * @param array ...$args Arguments to pass to the component. |
||
399 | * @return string|null Returns the rendered component or **null** if the component was not found. |
||
400 | */ |
||
401 | 74 | public function render($component, ...$args) { |
|
402 | 74 | if ($callback = $this->lookup($component)) { |
|
403 | 74 | ob_start(); |
|
404 | 74 | $errs = error_reporting(error_reporting() & ~E_NOTICE & ~E_WARNING); |
|
405 | 74 | call_user_func($callback, ...$args); |
|
406 | 74 | error_reporting($errs); |
|
407 | 74 | $str = ob_get_clean(); |
|
408 | 74 | return $str; |
|
409 | } else { |
||
410 | trigger_error("Could not find component $component.", E_USER_NOTICE); |
||
411 | return null; |
||
412 | } |
||
413 | } |
||
414 | |||
415 | /** |
||
416 | * Set the error reporting appropriate for template rendering. |
||
417 | * |
||
418 | * @return int Returns the previous error level. |
||
419 | */ |
||
420 | public function setErrorReporting() { |
||
421 | $errs = error_reporting(error_reporting() & ~E_NOTICE & ~E_WARNING); |
||
422 | return $errs; |
||
423 | } |
||
424 | |||
425 | /** |
||
426 | * Call a function registered with **defineFunction()**. |
||
427 | * |
||
428 | * If a static or global function is registered then it's simply rendered in the compiled template. |
||
429 | * This method is for closures or callbacks. |
||
430 | * |
||
431 | * @param string $name The name of the registered function. |
||
432 | * @param array ...$args The function's argument. |
||
433 | * @return mixed Returns the result of the function |
||
434 | * @throws RuntimeException Throws an exception when the function isn't found. |
||
435 | */ |
||
436 | 4 | public function call($name, ...$args) { |
|
437 | 4 | if (!isset($this->functions[$name])) { |
|
438 | 1 | throw new RuntimeException("Call to undefined function $name.", 500); |
|
439 | } else { |
||
440 | 3 | return $this->functions[$name](...$args); |
|
441 | } |
||
442 | } |
||
443 | |||
444 | /** |
||
445 | * Render a variable appropriately for CSS. |
||
446 | * |
||
447 | * This is a convenience runtime function. |
||
448 | * |
||
449 | * @param string|array $expr A CSS class, an array of CSS classes, or an associative array where the keys are class |
||
450 | * names and the values are truthy conditions to include the class (or not). |
||
451 | * @return string Returns a space-delimited CSS class string. |
||
452 | */ |
||
453 | 11 | public function attributeClass($expr) { |
|
454 | 11 | if (is_array($expr)) { |
|
455 | 4 | $classes = []; |
|
456 | 4 | foreach ($expr as $i => $val) { |
|
457 | 4 | if (is_array($val)) { |
|
458 | 1 | $classes[] = $this->attributeClass($val); |
|
459 | 4 | } elseif (is_int($i)) { |
|
460 | 1 | $classes[] = $val; |
|
461 | 4 | } elseif (!empty($val)) { |
|
462 | 3 | $classes[] = $i; |
|
463 | 3 | } |
|
464 | 4 | } |
|
465 | 4 | return implode(' ', $classes); |
|
466 | } else { |
||
467 | 7 | return (string)$expr; |
|
468 | } |
||
469 | } |
||
470 | |||
471 | /** |
||
472 | * Render a variable appropriately for a style attribute. |
||
473 | * |
||
474 | * This function expects a string or an array. If an array is supplied then the keys represent style properties and |
||
475 | * the values are style values. |
||
476 | * |
||
477 | * @param mixed $expr The expression to render. |
||
478 | * @return string Returns a style attribute. |
||
479 | */ |
||
480 | 4 | public function attributeStyle($expr) { |
|
481 | 4 | static $false = ['display' => 'none', 'visibility' => 'hidden', 'border' => 'none', 'box-shadow' => 'none']; |
|
482 | 4 | static $true = ['visibility' => 'visible']; |
|
483 | |||
484 | 4 | if (is_array($expr)) { |
|
485 | 4 | $style = []; |
|
486 | 4 | foreach ($expr as $prop => $value) { |
|
487 | 4 | if ($value === false && isset($false[$prop])) { |
|
488 | 1 | $value = $false[$prop]; |
|
489 | 4 | } elseif ($value === true && isset($true[$prop])) { |
|
490 | 1 | $value = $true[$prop]; |
|
491 | 3 | } elseif (in_array($value, [null, '', [], false], true)) { |
|
492 | continue; |
||
493 | 2 | } elseif (is_array($value)) { |
|
494 | 1 | if ($prop === 'font-family') { |
|
495 | 1 | $value = "'".implode("','", $value)."'"; |
|
496 | 1 | } else { |
|
497 | 1 | $value = implode(' ', $value); |
|
498 | } |
||
499 | 1 | } |
|
500 | |||
501 | 4 | $style[] = "$prop: $value"; |
|
502 | 4 | } |
|
503 | 4 | return implode('; ', $style); |
|
504 | } else { |
||
505 | return (string)$expr; |
||
506 | } |
||
507 | } |
||
508 | |||
509 | /** |
||
510 | * Format a data. |
||
511 | * |
||
512 | * @param mixed $date The date to format. This can be a string data, a timestamp or an instance of **DateTimeInterface**. |
||
513 | * @param string $format The format of the date. |
||
514 | * @return string Returns the formatted data. |
||
515 | * @see date_format() |
||
516 | */ |
||
517 | 1 | public function formatDate($date, $format = 'c') { |
|
518 | 1 | if (is_string($date)) { |
|
519 | try { |
||
520 | 1 | $date = new \DateTimeImmutable($date); |
|
521 | 1 | } catch (\Exception $ex) { |
|
522 | return '#error#'; |
||
523 | } |
||
524 | 1 | } elseif (empty($date)) { |
|
525 | return ''; |
||
526 | } elseif (is_int($date)) { |
||
527 | try { |
||
528 | $date = new \DateTimeImmutable('@'.$date); |
||
529 | } catch (\Exception $ex) { |
||
530 | return '#error#'; |
||
531 | } |
||
532 | } elseif (!$date instanceof \DateTimeInterface) { |
||
533 | return '#error#'; |
||
534 | } |
||
535 | |||
536 | 1 | return $date->format($format); |
|
537 | } |
||
538 | |||
539 | /** |
||
540 | * Get a single item from the meta array. |
||
541 | * |
||
542 | * @param string $name The key to get from. |
||
543 | * @param mixed $default The default value if no item at the key exists. |
||
544 | * @return mixed Returns the meta value. |
||
545 | */ |
||
546 | public function getMeta($name, $default = null) { |
||
547 | return isset($this->meta[$name]) ? $this->meta[$name] : $default; |
||
548 | } |
||
549 | |||
550 | /** |
||
551 | * Set a single item to the meta array. |
||
552 | * |
||
553 | * @param string $name The key to set. |
||
554 | * @param mixed $value The new value. |
||
555 | * @return $this |
||
556 | */ |
||
557 | 1 | public function setMeta($name, $value) { |
|
558 | 1 | $this->meta[$name] = $value; |
|
559 | 1 | return $this; |
|
560 | } |
||
561 | |||
562 | /** |
||
563 | * Get the template loader. |
||
564 | * |
||
565 | * The template loader translates component names into template contents. |
||
566 | * |
||
567 | * @return TemplateLoaderInterface Returns the template loader. |
||
568 | */ |
||
569 | 1 | public function getTemplateLoader() { |
|
570 | 1 | return $this->templateLoader; |
|
571 | } |
||
572 | |||
573 | /** |
||
574 | * Set the template loader. |
||
575 | * |
||
576 | * The template loader translates component names into template contents. |
||
577 | * |
||
578 | * @param TemplateLoaderInterface $templateLoader The new template loader. |
||
579 | * @return $this |
||
580 | */ |
||
581 | public function setTemplateLoader($templateLoader) { |
||
582 | $this->templateLoader = $templateLoader; |
||
583 | return $this; |
||
584 | } |
||
585 | |||
586 | /** |
||
587 | * Get the entire meta array. |
||
588 | * |
||
589 | * @return array Returns the meta. |
||
590 | */ |
||
591 | public function getMetaArray() { |
||
592 | return $this->meta; |
||
593 | } |
||
594 | |||
595 | /** |
||
596 | * Set the entire meta array. |
||
597 | * |
||
598 | * @param array $meta The new meta array. |
||
599 | * @return $this |
||
600 | */ |
||
601 | public function setMetaArray(array $meta) { |
||
602 | $this->meta = $meta; |
||
603 | return $this; |
||
604 | } |
||
605 | |||
606 | /** |
||
607 | * Return a dynamic attribute. |
||
608 | * |
||
609 | * The attribute renders differently depending on the value. |
||
610 | * |
||
611 | * - If the value is **true** then it will render as an HTML5 boolean attribute. |
||
612 | * - If the value is **false** or **null** then the attribute will not render. |
||
613 | * - Other values render as attribute values. |
||
614 | * - Attributes that start with **aria-** render **true** and **false** as values. |
||
615 | * |
||
616 | * @param string $name The name of the attribute. |
||
617 | * @param mixed $value The value of the attribute. |
||
618 | * @return string Returns the attribute definition or an empty string. |
||
619 | */ |
||
620 | 24 | public function attribute($name, $value) { |
|
621 | 24 | if (substr($name, 0, 5) === 'aria-' && is_bool($value)) { |
|
622 | 2 | $value = $value ? 'true' : 'false'; |
|
623 | 2 | } |
|
624 | |||
625 | 24 | if ($value === true) { |
|
626 | 1 | return ' '.$name; |
|
627 | 23 | } elseif (!in_array($value, [null, false], true)) { |
|
628 | 20 | return " $name=\"".htmlspecialchars($value).'"'; |
|
629 | } |
||
630 | 4 | return ''; |
|
631 | } |
||
632 | |||
633 | /** |
||
634 | * Escape a value for echoing to HTML with a bit of non-scalar checking. |
||
635 | * |
||
636 | * @param mixed $val The value to escape. |
||
637 | * @return string The escaped value. |
||
638 | */ |
||
639 | 27 | public function escape($val = null) { |
|
640 | 27 | if (is_array($val)) { |
|
641 | 1 | return '[array]'; |
|
642 | 27 | } elseif ($val instanceof \DateTimeInterface) { |
|
643 | 1 | return htmlspecialchars($val->format(\DateTime::RFC3339)); |
|
644 | 27 | } elseif (is_object($val) && !method_exists($val, '__toString')) { |
|
645 | 1 | return '{object}'; |
|
646 | } else { |
||
647 | 27 | return htmlspecialchars($val); |
|
648 | } |
||
649 | } |
||
650 | |||
651 | /** |
||
652 | * Write children blocks. |
||
653 | * |
||
654 | * @param array|callable|null $children The children blocks to write. |
||
655 | */ |
||
656 | 4 | public function writeChildren($children) { |
|
657 | 4 | if (empty($children)) { |
|
658 | return; |
||
659 | 4 | } elseif (is_array($children)) { |
|
660 | 1 | array_map([$this, 'writeChildren'], $children); |
|
661 | 1 | } else { |
|
662 | 4 | $children(); |
|
663 | } |
||
664 | 4 | } |
|
665 | |||
666 | /** |
||
667 | * Massage a dynamic tag name. |
||
668 | * |
||
669 | * @param mixed $expr The result of the tag name expression. |
||
670 | * @param string $tag The default tag name if **$expr** is true. |
||
671 | * @return string Returns the expected tag name. |
||
672 | */ |
||
673 | 5 | protected function tagName($expr, $tag) { |
|
674 | 5 | if ($expr === true) { |
|
675 | 3 | return $tag; |
|
676 | } |
||
677 | 4 | return $expr; |
|
678 | } |
||
679 | } |
||
680 |
This check looks for
@param
annotations where the type inferred by our type inference engine differs from the declared type.It makes a suggestion as to what type it considers more descriptive.
Most often this is a case of a parameter that can be null in addition to its declared types.