Passed
Push — develop ( 5f0710...340c0b )
by Neill
12:23 queued 14s
created

SmartySharedPlugins::css()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
eloc 4
dl 0
loc 6
rs 10
c 0
b 0
f 0
cc 2
nc 2
nop 2
ccs 0
cts 5
cp 0
crap 6
1
<?php
2
/**
3
 * Created by PhpStorm.
4
 * User: newicon
5
 * Date: 17/03/2017
6
 * Time: 13:14
7
 */
8
9
namespace neon\core\view;
10
11
use \Neon;
12
use neon\core\helpers\Html;
13
use neon\core\helpers\Hash;
14
use neon\core\helpers\Page;
15
use neon\core\helpers\Str;
16
use neon\core\helpers\Url;
17
use neon\core\helpers\Arr;
18
use neon\core\validators\NumberValidator;
19
use neon\core\web\View;
20
21
use \Smarty;
22
use yii\base\DynamicModel;
23
use yii\base\InvalidConfigException;
24
25
/**
26
 * Class SmartySharedPlugins
27
 * This class provides smarty plugins that can be shared between the admin smarty renderer and cosmos
28
 * @package neon\core\view
29
 */
30
class SmartySharedPlugins
31
{
32
	const IMG_SRCSET_DEFAULT_WIDTHS = "200,350,400,500,640,700,768,800,900,1024,1100,1280,1300,1536,1600,1700,1920,2000,2200,2400,2592";
33
34
	/**
35
	 * Apply these plugin functions to a smarty object
36
	 * @param mixed $parent
37
	 * @param Smarty $smarty
38
	 * @throws \SmartyException
39
	 */
40
	public static function apply($parent, $smarty)
41
	{
42
		new self($parent, $smarty);
43
	}
44
45
	/**
46
	 * Maintain a list of tags that will be deprecated so we can aid upgrades and provide sensible error messages
47
	 * @return array
48
	 */
49
	public static function deprecatedTags()
50
	{
51
		return ['cosmos_head_script', 'cosmos_body_script'];
52
	}
53
54
	/**
55
	 * SmartySharedPlugins constructor.
56
	 * @param mixed $parent - should implement a parent page interface
57
	 * @param \Smarty $smarty
58
	 * @throws \SmartyException
59
	 */
60
	public function __construct($parent, $smarty)
61
	{
62
		// app plugin smarty directories
63
		$coreApps = neon()->coreApps;
0 ignored issues
show
Bug Best Practice introduced by
The property coreApps does not exist on neon\core\ApplicationWeb. Since you implemented __get, consider adding a @property annotation.
Loading history...
64
		foreach ($coreApps as $key=>$app) {
65
			$smarty->addPluginsDir(Neon::getAlias("@neon/$key/plugins/smarty"));
66
		}
67
		// plugins
68
		$smarty->registerPlugin(Smarty::PLUGIN_FUNCTION, 'firefly_url', [$this, 'firefly_url']);
69
		$smarty->registerPlugin(Smarty::PLUGIN_FUNCTION, 'firefly_image', [$this, 'firefly_image']);
70
		$smarty->registerPlugin(Smarty::PLUGIN_FUNCTION, 'image', [$this, 'image']);
71
		$smarty->registerPlugin(Smarty::PLUGIN_FUNCTION, 'image_src', [$this, 'image_src']);
72
		$smarty->registerPlugin(Smarty::PLUGIN_FUNCTION, 'image_alt', [$this, 'image_src']);
73
		$smarty->registerPlugin(Smarty::PLUGIN_FUNCTION, 'image_srcset', [$this, 'image_srcset']);
74
		$smarty->registerPlugin(Smarty::PLUGIN_FUNCTION, 'setting', [$this, 'setting']);
75
		$smarty->registerPlugin(Smarty::PLUGIN_FUNCTION, 'time', [$this, 'time']);
76
		$smarty->registerPlugin(Smarty::PLUGIN_FUNCTION, 'csrf_element', [$this, 'csrf_element']);
77
		$smarty->registerPlugin(Smarty::PLUGIN_FUNCTION, 'csrf_meta_tags', [$this, 'csrf_meta_tags']);
78
		$smarty->registerPlugin(Smarty::PLUGIN_FUNCTION, 'url', [$this, 'url']);
79
		$smarty->registerPlugin(Smarty::PLUGIN_FUNCTION, 'registerAsset', [$this, 'registerAsset']);
80
		$smarty->registerPlugin(Smarty::PLUGIN_FUNCTION, 'jsFile', [$this, 'jsFile']);
81
		$smarty->registerPlugin(Smarty::PLUGIN_FUNCTION, 'cssFile', [$this, 'cssFile']);
82
		$smarty->registerPlugin(Smarty::PLUGIN_FUNCTION, 'neon_head', [$this, 'neonHead']);
83
		$smarty->registerPlugin(Smarty::PLUGIN_FUNCTION, 'neon_body_begin', [$this, 'neonBodyBegin']);
84
		$smarty->registerPlugin(Smarty::PLUGIN_FUNCTION, 'neon_body_end', [$this, 'neonBodyEnd']);
85
		$smarty->registerPlugin(Smarty::PLUGIN_FUNCTION, 'hasPermission', [$this, 'hasPermission']);
86
		$smarty->registerPlugin(Smarty::PLUGIN_FUNCTION, 'style', [$this, 'style']);
87
		$smarty->registerPlugin(Smarty::PLUGIN_FUNCTION, 'uuid', [$this, 'uuid']);
88
		$smarty->registerPlugin(Smarty::PLUGIN_FUNCTION, 'asset', [$this, 'asset']);
89
		$smarty->registerPlugin(Smarty::PLUGIN_FUNCTION, 'assets', [$this, 'asset']);
90
		$smarty->registerPlugin(Smarty::PLUGIN_FUNCTION, 'is_logged_in', [$this, 'is_logged_in']);
91
		$smarty->registerPlugin(Smarty::PLUGIN_FUNCTION, 'page_url', [$this, 'page_url']);
92
		$smarty->registerPlugin(Smarty::PLUGIN_FUNCTION, 'on_page', [$this, 'on_page']);
93
		$smarty->registerPlugin(Smarty::PLUGIN_FUNCTION, 'protectHtmlEntities', [$this, 'protectHtmlEntities']);
94
95
		// modifiers
96
		$smarty->registerPlugin(Smarty::PLUGIN_MODIFIER, 'htmlAttributes', [$this, 'htmlAttributes']);
97
		$smarty->registerPlugin(Smarty::PLUGIN_MODIFIER, 'humanize', '\neon\core\helpers\Str::humanize');
98
		$smarty->registerPlugin(Smarty::PLUGIN_MODIFIER, 'json', [$this, 'json']);
99
100
		// blocks
101
		$smarty->registerPlugin(Smarty::PLUGIN_BLOCK, 'markdown', [$this, 'markdown']);
102
		// All the same - ways to register a block of js :
103
		$smarty->registerPlugin(Smarty::PLUGIN_BLOCK, 'script', [$this, 'js']);
104
		$smarty->registerPlugin(Smarty::PLUGIN_BLOCK, 'js', [$this, 'js']);
105
		$smarty->registerPlugin(Smarty::PLUGIN_BLOCK, 'Js', [$this, 'js']);
106
		// Register a block of css
107
		$smarty->registerPlugin(Smarty::PLUGIN_BLOCK, 'css', [$this, 'css']);
108
109
		// experimental
110
		$smarty->registerPlugin(Smarty::PLUGIN_BLOCK, 'cmp', [$this, 'cmp']);
111
112
		// deprecated tags
113
		$this->registerDeprecatedTags($smarty);
114
	}
115
116
	/**
117
	 * @param \Smarty $smarty
118
	 * @throws \SmartyException
119
	 */
120
	public function registerDeprecatedTags($smarty)
121
	{
122
		$smarty->registerPlugin(Smarty::PLUGIN_FUNCTION, 'cosmos_head_script', [$this, 'neonHead']);
123
		$smarty->registerPlugin(Smarty::PLUGIN_FUNCTION, 'cosmos_body_script', [$this, 'neonBodyEnd']);
124
	}
125
126
	/**
127
	 * Parse a block of content as markdown
128
	 *
129
	 * ```smarty
130
	 * {markdown} ***markdown content*** {/markdown}
131
	 * ```
132
	 *
133
	 * @param $params
134
	 * @param $content
135
	 * @return string
136
	 */
137
	public function markdown($params, $content)
138
	{
139
		$parser = new MarkdownNeon();
140
		return $parser->parse($content);
141
	}
142
143
	/**
144
	 * See if a user is logged in
145
	 * usage: {if {is_logged_in}} ...
146
	 * @return boolean
147
	 */
148
	public function is_logged_in($params, $smarty)
149
	{
150
		return neon()->user->isGuest === false;
151
	}
152
153
	/**
154
	 * Will force copy assets when in dev mode
155
	 * This is specific to the cms module
156
	 * ```{asset path="/path/within/assets/theme/directory"}```
157
	 * ```{asset path="http://something"}``` - the path is output as is this is useful when either an internal or external image
158
	 * can be supplied to a component
159
	 *
160
	 * @param $params
161
	 * @return string
162
	 */
163
	public function asset($params)
164
	{
165
		$path = Arr::getRequired($params, 'path');
166
		// loading external resource so just return
167
		if (Str::startsWith($path, 'http')) return $path;
168
		$options = [];
169
170
		$themeAssets = neon()->cms->getThemeAlias().'/assets';
171
		list($assetPath, $assetUrl) = neon()->assetManager->publish($themeAssets, $options);
172
		$path = ltrim($path, '/');
173
		$timestamp = @filemtime("$assetPath/$path");
174
		return  neon()->urlManager->getHostInfo() . $assetUrl . '/' . $path . (($timestamp > 0) ? '?v='.$timestamp : '');
0 ignored issues
show
Bug introduced by
Are you sure $timestamp of type false|integer can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

174
		return  neon()->urlManager->getHostInfo() . $assetUrl . '/' . $path . (($timestamp > 0) ? '?v='./** @scrutinizer ignore-type */ $timestamp : '');
Loading history...
175
	}
176
177
	/**
178
	 * @param $params
179
	 * @param $content
180
	 * @param $template
181
	 * @param $repeat
182
	 * @return string
183
	 */
184
	public function cmp($params, $content, $template, &$repeat)
185
	{
186
		// only output on the closing tag
187
		if (!$repeat) {
188
			$class = Arr::remove($params, 'class');
189
			$params['content'] = $content;
190
			if (class_exists($class)) {
0 ignored issues
show
Bug introduced by
It seems like $class can also be of type null; however, parameter $class of class_exists() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

190
			if (class_exists(/** @scrutinizer ignore-type */ $class)) {
Loading history...
191
				$widget = new $class();
192
				foreach($params as $key => $val) {
193
					if ($widget->hasProperty($key))
194
						$widget->$key = $val;
195
				}
196
				return $widget->run();
197
			} else {
198
				return "<div class=\"neonWidgetError alert alert-danger\">Widget class '$class' not found in cmp plugin</div>";
199
			}
200
		}
201
	}
202
203
	/**
204
	 * Register a style configurable parameter
205
	 *
206
	 * @param array $params
207
	 * @param Smarty $smarty
208
	 */
209
	public function style($params, $smarty)
210
	{
211
		// possible vue style implementation
212
//		Arr::props($params, [
213
//			'name' => ['required' => true, 'type' => 'string'],
214
//			'type' => ['required' => true, 'type' => 'string'],
215
//			'options' => ['required' => false, 'type' => 'array', 'default' => []]
216
//		]);
217
218
		$name = Arr::getRequired($params, 'name');
219
		$type = Arr::getRequired($params, 'type');
220
		$options = Arr::get($params, 'options', []);
221
		if (!is_array($options))
222
			throw new \InvalidArgumentException('"options" property must be an array "' . gettype($options) . '" given');
223
		$default = Arr::get($params, 'default', '');
224
225
		$value = neon()->view->styleManager->add($name, $default, $type, $options);
226
		$smarty->assign($name, $value);
227
	}
228
229
	/**
230
	 * Function to expand an array into html attributes
231
	 *
232
	 * @param array $params
233
	 * @param Object $smarty
234
	 *
235
	 * @return string
236
	 */
237
	public function htmlAttributes($params, $smarty)
238
	{
239
		dp($params);
240
	}
241
242
	/**
243
	 * Smarty template function to get absolute URL for using in links
244
	 *
245
	 * Usage is the following:
246
	 *
247
	 * {url route='blog/view' alias=$post.alias user=$user.id}
248
	 *
249
	 * where route is Yii route and the rest of parameters are passed as is.
250
	 *
251
	 * @param array $params
252
	 * @return string
253
	 */
254
	public function url($params)
255
	{
256
		if (!isset($params['route'])) {
257
			trigger_error("path: missing 'route' parameter");
258
		}
259
		array_unshift($params, $params['route']) ;
260
		unset($params['route']);
261
		return Url::toRoute($params, true);
262
	}
263
264
	/**
265
	 * tag: csrf_element
266
	 *
267
	 * Output a form element with the csrf key in it.
268
	 * This is a hidden field and allows the form to post to yii
269
	 *
270
	 * ```{csrf_element}```
271
	 *
272
	 * @param array $params ['id']
273
	 * @return string html
274
	 */
275
	public function csrf_element($params)
276
	{
277
		return '<input type="hidden" name="'.neon()->request->csrfParam.'" value="' . neon()->request->getCsrfToken() . '" />';
278
	}
279
280
	/**
281
	 * Output Csrf meta tags
282
	 * @param $params
283
	 * @param $smarty
284
	 * @return string
285
	 */
286
	public function csrf_meta_tags($params, $smarty)
287
	{
288
		return Html::csrfMetaTags();
289
	}
290
291
	/**
292
	 * tag: firefly_url
293
	 *
294
	 * Get a url for a file managed by firefly from it's id
295
	 *
296
	 * ```{firefly_url id="the file uuid"}```
297
	 *
298
	 * @param array $params ['id']
299
	 * @return string  the URL to the file
300
	 * @throws \InvalidArgumentException
301
	 */
302
	public function firefly_url($params)
303
	{
304
		if (isset($params['id']))
305
			return neon()->firefly->getUrl($params['id']);
306
	}
307
308
	/**
309
	 * tag: firefly_image
310
	 *
311
	 * Get a url for an image managed by firefly from it's id
312
	 *
313
	 * ```{firefly_url id="the file uuid" ...}```
314
	 *
315
	 * @param array $params
316
	 *   'id' - the image id
317
	 *   'w' - the image width
318
	 *   'h' - the image height
319
	 *   'q' - quality of compression (0-100)
320
	 *   'fit' - fit the image into the shape
321
	 *   'rotate' - angle to rotate image by
322
	 *   'flip' - flip image horizontally (h) or vertically (v)
323
	 *   'crop' - crop the image
324
	 *
325
	 * @return string  the URL to the file
326
	 * @throws \InvalidArgumentException
327
	 */
328
	public function firefly_image($params)
329
	{
330
		if (isset($params['id'])) {
331
			if (!Hash::isUuid64($params['id'])) {
332
				$params['id'] = $this->asset(['path' => $params['id']]);
333
			}
334
			return neon()->firefly->getImage(Arr::pull($params, 'id'), $params);
335
		}
336
	}
337
338
	/**
339
	 * tag: page_url
340
	 *
341
	 * Get a url for a page from it's id
342
	 * Usage:
343
	 *
344
	 * ```
345
	 *   {page_url} for the url of the current page
346
	 *   {page_url id="the page id"} if you know the page id
347
	 *   {page_url nice="the page nice id"} if you know the nice id
348
	 * ```
349
	 * Optional parameters:
350
	 *   full=true if you want the full URL include server
351
	 *   prefix=string  if you want to prefix the url with something
352
	 *     e.g. for alternative permissions on routing
353
	 *
354
	 * @param array $params
355
	 * @param object $smarty
356
	 * @throws \InvalidArgumentException if no id or nice parameter defined
357
	 * @return string  the page URL
358
	 */
359
	public function page_url($params, $smarty=null)
360
	{
361
		// get the nice_id by either 'id' or 'nice' - should deprecate usage of 'nice' in future versions
362
		$id = Arr::get($params, 'id', Arr::get($params, 'nice', false));
363
		if ($id === false)
364
			throw new \InvalidArgumentException('No id or nice parameter was specified');
365
366
		// this is a bit strange calling {page_url id=#} returns '#' - keeping for backwards compatibility
367
		if ($id === '#') return '#';
368
369
		// whether to make this url absolute defaults to false
370
		$absolute = Arr::get($params, 'full', false);
371
372
		// the unique route of the page is:
373
		$url = url(['/cms/render/page', 'nice_id'=>$id], $absolute);
374
375
		if (isset($params['prefix']))
376
			$url = $params['prefix'].$url;
377
378
		return $url;
379
	}
380
381
	/**
382
	 * Determine if we are on the current page.
383
	 * @see Url::isUrl
384
	 * Usage:
385
	 * ```
386
	 *   {on_page id='my-page-id'} for the url of the current page
387
	 *   {on_page id=['my-page', 'home', 'uuid']} if you know the page id
388
	 * ```
389
	 * @param $params
390
	 * @return boolean - if the current url matches one of the page urls
391
	 * @throws \yii\base\InvalidConfigException
392
	 */
393
	public function on_page($params)
394
	{
395
		$pages = Arr::getRequired($params, 'id');
396
		$allPages = is_array($pages) ? $pages : [$pages];
397
		$urls = []; foreach ($allPages as $page) $urls[] =  $this->page_url(['id' => $page]);
398
		return Url::isUrl($urls);
399
	}
400
401
	/**
402
	 * Produce a firefly url for the asset
403
	 * @param $params
404
	 * @return string
405
	 */
406
	public function image_src($params)
407
	{
408
		$src = Arr::getRequired($params, 'src');
409
		if (!Hash::isUuid64($src))
410
			$params['src'] = $this->asset(['path' => $params['src']]);
411
		return neon()->firefly->getImage(Arr::pull($params, 'src'), $params);
412
	}
413
414
	/**
415
	 * Lookup an images src attribute
416
	 *
417
	 * @param $params - contains a 'src' parameter
0 ignored issues
show
Documentation Bug introduced by
The doc comment - at position 0 could not be parsed: Unknown type name '-' at position 0 in -.
Loading history...
418
	 * @return mixed|string
419
	 */
420
	public function image_alt($params)
421
	{
422
		$uuid = Arr::getRequired($params, 'src');
423
		// we can't lookup alt info for images loaded form disk atm.
424
		// a images loaded from disk still get pulled through fireflys imageManager - we could add these
425
		// to a firefly holding area - you could then decide whether to import into firefly at this point
426
		// the image would get a uuid - but it would also still need to be found by its local disk path.
427
		// Therefore firefly would need to be expanded with the concept of a duel lookup - this feels knarly.
428
		// probably makes sense for a seperate system to manage these files.
429
		if (!Hash::isUuid64($uuid))
430
			return '';
431
		return Arr::get(neon()->firefly->getMeta($uuid), 'alt', '');
432
	}
433
434
	/**
435
	 * tag: image_srcset
436
	 *
437
	 * Gets a string suitable for placing inside an image tag srcset, works in conjunction with firefly_image_srcset
438
	 *
439
	 * ```{image_srcset firefly_id="the file uuid" ...}```
440
	 *
441
	 * Examples:
442
	 * ```{image_srcset firefly_id='z08MMpEHcW27bd_diYLIMw'}```
443
	 * ```{image_srcset firefly_id='z08MMpEHcW27bd_diYLIMw' id='image-tag-id' alt='image alt text' style='object-fit:cover' class='imgsetclass'}```
444
	 * ```{image_srcset firefly_id='z08MMpEHcW27bd_diYLIMw' widths='1000,2000'}```
445
	 * ```{image_srcset firefly_id='z08MMpEHcW27bd_diYLIMw' widths='1000,2000' breaks='50vw,100vw' min-break='600px'}```
446
	 * ```{image_srcset firefly_id='z08MMpEHcW27bd_diYLIMw' widths='300,600,900' resolutions='2x,3x'}```
447
	 *
448
	 * @todo: consider supporting additional firefly parameters
449
	 * @param $params
450
	 *    'firefly_id   - the image id
451
	 *    'quality'     - quality parameter is passed through as a firefly q parameter
452
	 *    'widths'      - comma separated list of widths in pixels as ints
453
	 *    'src-width'   - width to use for the img src (if unspecified the the smallest from widths will be used) in pixels as int
454
	 *    'breaks'      - comma separated list of break point sizes (will be automatically generated if not present) as int with 'px' or 'vw' specified
455
	 *    'min-break'   - force use of a specific min-width as int with 'px' or 'vw' specified
456
	 *    'srcset-name' - when using js based lazy loading you will often need to rename the srcset attribute
457
	 *    'src-name'    - when using js based lazy loading you will often need to rename the src attribute,
458
	 *                    allowing js to to detect and create the correct attribute when necessary
459
	 *    + any other parameters passed in will be built into the resulting tag string (these are not validated)
460
	 * @return string the srcset string
461
	 * @throws \InvalidArgumentException
462
	 * @see https://developer.mozilla.org/en-US/docs/Learn/HTML/Multimedia_and_embedding/Responsive_images
463
	 */
464
	public function image($params)
465
	{
466
		if (!isset($params['firefly_id']) && !isset($params['src'])) {
467
			throw new \InvalidArgumentException("'firefly_id' or 'src' is a required parameter");
468
		}
469
470
		$id = Arr::pull($params,'firefly_id', Arr::pull($params,'src'));
471
		$params['loading'] = Arr::pull($params, 'loading', 'lazy');
472
		$widths = array_map('trim',explode(',',Arr::pull($params,'widths')));
0 ignored issues
show
Unused Code introduced by
The assignment to $widths is dead and can be removed.
Loading history...
473
		$srcsetParts = [];
474
		$srcSizeParts = [];
475
		$useResolutions = [];
0 ignored issues
show
Unused Code introduced by
The assignment to $useResolutions is dead and can be removed.
Loading history...
476
		$minBreak = false;
477
		if (isset($params['min-break'])) {
478
			$minBreak = Arr::pull($params,'min-break');
479
			if (!Str::validateNumericWithUnits($minBreak,['px','vw'])) {
480
				throw new \InvalidArgumentException("The min-break value '".$minBreak."' should be numeric and end with 'px' or 'vw'");
481
			}
482
		}
483
		if (isset($params['breaks'])) {
484
			// optionally validate & collate the breaks for the 'sizes' attribute
485
			$breaks = array_map('trim',explode(',',Arr::pull($params,'breaks')));
486
			foreach ($breaks as $idx=>$break) {
487
				if (!Str::validateNumericWithUnits($break, ['px','vw'])) {
488
					throw new \InvalidArgumentException("The break value '".$break."' should be numeric and end with 'px' or 'vw'");
489
				}
490
				if ($idx==0 && $minBreak) {
491
					$break = "(min-width: ".$minBreak.") ".$break;
492
				}
493
				$srcSizeParts[]=$break;
494
			}
495
		}
496
497
		else {
498
			// if neither 'breaks' or 'resolutions' was specified then load the best fit image for the screen width
499
			$srcSizeParts[] = "100vw";
500
		}
501
502
		$quality = Arr::pull($params, 'quality', 90);
503
		$sources = $this->generateSrcset($id, Arr::pull($params,'widths'), $quality);
504
		foreach ($sources as $width=>$url) $srcsetParts[] = $url.' '.$width.'w';
505
		if (!isset($params['src-width']) && !isset($params['src'])) {
506
			$params[Arr::get($params, 'src-name', 'src')] = reset($sources);
507
		}
508
509
		// collate the tag attributes
510
		if (isset($params['src-width'])) {
511
			$srcWidth = Arr::pull($params,'src-width');
512
			if (!is_numeric($srcWidth)) throw new \InvalidArgumentException("src-width '".$srcWidth."' must be numeric");
513
			if (!isset($params['src'])) {
514
				$params[Arr::get($params, 'src-name', 'src')] = $this->firefly_image(['id' => $id, 'w' => (int)$srcWidth, 'q' => $quality]);
515
			}
516
		}
517
		if ($srcSizeParts && !isset($params['sizes'])) {
518
			$params['sizes'] = implode(', ', $srcSizeParts);
519
		}
520
		if ($srcsetParts && !isset($params['srcset'])) {
521
			$params[Arr::get($params, 'srcset-name', 'srcset')] = implode(', ', $srcsetParts);
522
		}
523
524
		// finally generate the tag ... note that these can overwrite the previously generated params
525
		$tagAttributes =  [];
526
		foreach ($params as $key=>$value) {
527
			$tagAttributes[] = $key."='".htmlspecialchars($value)."'";
528
		}
529
530
		return "<img ".implode(' ',$tagAttributes).">";
531
	}
532
533
	/**
534
	 * Generates a srcset string
535
	 * @param $params
536
	 * @return string
537
	 */
538 2
	public function image_srcset($params)
539
	{
540 2
		$src = Arr::getRequired($params, 'src');
541
		// if this is not a firefly id then assume it is a path loaded via asset
542
		if (!Hash::isUuid64($src)) $src = $this->asset(['path' => $src]);
543
		$sources = $this->generateSrcset($src, Arr::get($params, 'widths'), Arr::get($params, 'quality', 90));
544
		$srcset = '';
545
		foreach ($sources as $width=>$url) $srcset .= $url.' '.$width.'w, ';
546
		return $srcset;
547
	}
548
549
	/**
550
	 * Generate a sensible default srcset attribute string
551
	 * @param $id
552
	 * @param null $widths
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $widths is correct as it would always require null to be passed?
Loading history...
553
	 * @param int $quality
554
	 * @return array
555
	 */
556
	public function generateSrcset($id, $widths=null, $quality=90)
557
	{
558
		$widths = $widths ?: self::IMG_SRCSET_DEFAULT_WIDTHS;
0 ignored issues
show
introduced by
$widths is of type null, thus it always evaluated to false.
Loading history...
559
		// validate and collate the widths
560
		$widths = array_map('trim',explode(',',$widths));
561
		if (!(is_numeric($quality) && $quality >= 0 && $quality <= 100)) throw new \InvalidArgumentException("the quality '".$quality."' must be numeric a value between 0 and 100");
562
		$srcsetParts = [];
563
		foreach ($widths as $idx=>$width) {
564
			if (!is_numeric($width)) throw new \InvalidArgumentException("One of the widths specified was not numeric '".$width."'");
565
			$imageUrl = $this->firefly_image(['id' => $id, 'w' => (int)$width, 'q' => $quality]);
566
			$srcsetParts[$width] = $imageUrl;
567
		}
568
		return $srcsetParts;
569
	}
570
571
572
	/**
573
	 * tag: setting
574
	 *
575
	 * Get a setting value from the settings manager
576
	 *
577
	 * ```{setting app='aaa' name='nnn' [default='xxx' assign='yyy']}```
578
	 *
579
	 * @param array $params
580
	 *   app - required - the settings app area
581
	 *   name - required - the setting required
582
	 *   default - optional - a default value if it's not set
583
	 *   assign - optional - if set assigns to this variable else returns the value
584
	 * @param object $smarty
585
	 * @return string
586
	 */
587
	public function setting($params, $smarty)
588
	{
589
		$app = isset($params['app']) ? $params['app'] : null;
590
		$name = isset($params['name']) ? $params['name'] : null;
591
		$default = isset($params['default']) ? $params['default'] : null;
592
		$assign = isset($params['assign']) ? $params['assign'] : null;
593
		if ($app && $name) {
594
			$value = neon('settings')->manager->get($app, $name, $default);
0 ignored issues
show
Bug introduced by
The method get() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

594
			/** @scrutinizer ignore-call */ 
595
   $value = neon('settings')->manager->get($app, $name, $default);

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.

Loading history...
Bug Best Practice introduced by
The property manager does not exist on neon\core\ApplicationWeb. Since you implemented __get, consider adding a @property annotation.
Loading history...
595
			if ($assign)
596
				$smarty->assign($assign, $value);
597
			else
598
				return $value;
599
		} else {
600
			return "Usage: you must provide an 'app' and a 'name' parameter. You provided ".print_r($params,true);
0 ignored issues
show
Bug introduced by
Are you sure print_r($params, true) of type string|true can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

600
			return "Usage: you must provide an 'app' and a 'name' parameter. You provided "./** @scrutinizer ignore-type */ print_r($params,true);
Loading history...
601
		}
602
	}
603
604
	/**
605
	 * tag: time
606
	 *
607
	 * Convert a timestamp into a YYYY-MM-DD HH:MM:SS string
608
	 *
609
	 * Usage:
610
	 * ```
611
	 *   {time} - returns the current time
612
	 *   {time stamp='123456'} - returns the timestamp formatted
613
	 * ```
614
	 *
615
	 * @param $params empty for now and integer timestamp otherwise
0 ignored issues
show
Bug introduced by
The type neon\core\view\empty was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
616
	 * @return string YYYY-MM-DD HH:MM:SS
617
	 */
618
	public function time($params)
619
	{
620
		$format = 'Y-m-d H:i:s';
621
		if (isset($params['stamp']))
622
			return date($format, (integer)$params['stamp']);
623
		return date($format);
624
	}
625
626
	/**
627
	 * Output a pre tag of nicely formatted json - useful for debugging
628
	 *
629
	 * @param $params
630
	 * @return string
631
	 */
632
	public function json($params)
633
	{
634
		return '<pre>' . json_encode($params, JSON_PRETTY_PRINT) . '</pre>';
635
	}
636
637
	/**
638
	 * Looks for a `position` or `pos` key in the params and returns the integer value for the string position
639
	 * ```
640
	 * $this->getAssetPosition(['pos' => 'end']) // gives: 3
641
	 * ```
642
	 * @see SmartySharedPlugins::registerAsset
643
	 * @see SmartySharedPlugins::js()
644
	 * @param array $params
645
	 * @param string $default - defaults to 'end'
646
	 * @return int
647
	 * @throws \Exception
648
	 */
649
	public function getAssetPosition($params, $default='end')
650
	{
651
		$position = Arr::get($params, 'pos', Arr::get($params, 'position', $default));
652
		$positions = [
653
			'head'  => View::POS_HEAD,
654
			'begin' => View::POS_BEGIN,
655
			'end'   => View::POS_END,
656
			'ready' => View::POS_READY,
657
			'load'  => View::POS_LOAD,
658
		];
659
		if (!array_key_exists($position, $positions)) {
660
			throw new \Exception('The javascript position specified must be one of ' . print_r(array_keys($positions)) . ', you specified "'.$position.'"');
0 ignored issues
show
Bug introduced by
Are you sure print_r(array_keys($positions)) of type string|true can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

660
			throw new \Exception('The javascript position specified must be one of ' . /** @scrutinizer ignore-type */ print_r(array_keys($positions)) . ', you specified "'.$position.'"');
Loading history...
661
		}
662
		return $positions[$position];
663
	}
664
665
	/**
666
	 * Smarty block function plugin
667
	 * Usage is the following:
668
	 *
669
	 * ```
670
	 * {Js} Javascript code here {/Js}
671
	 * {Js pos='end'} Javascript code here {/Js}
672
	 * {Js pos='ready'} jquery on ready code here {/Js}
673
	 * ```
674
	 *
675
	 * @param $params
676
	 *
677
	 *   [position|pos] with values:
678
	 *   The position param specified where the javascript block will be rendered on the page it can have
679
	 *   the following values:
680
	 *
681
	 *   - "head"  : This means the javascript is rendered in the head section.
682
	 *   - "begin" : This means the javascript is rendered at the beginning of the body section.
683
	 *   - "end"   : This means the javascript is rendered at the end of the body section.
684
	 *   - "ready" : This means the JavaScript code block will be enclosed within `jQuery(document).ready()`.
685
	 *   - "load"  : This means the JavaScript code block will be enclosed within `jQuery(window).load()`.
686
	 *
687
	 *
688
	 * @param $content
689
	 * @return void
690
	 * @throws \Exception
691
	 */
692
	public function js($params, $content)
693
	{
694
		if ($content == null) return;
695
		$key = Arr::get($params, 'key', null);
696
		$content = str_replace(['<script>', '</script>'], '', $content);
697
		neon()->view->registerJs($content, $this->getAssetPosition($params), $key);
698
	}
699
700
	/**
701
	 * Register a block of css code
702
	 *
703
	 * ```
704
	 * {css position='head'}
705
	 * .myStyle {color:'red'}
706
	 * {/css}
707
	 * ```
708
	 *
709
	 * @param $params
710
	 * [position|pos] string - the string position in the page where css should be output - defaults to 'head'
711
	 * [key] string - a key to uniquely identify this css block so it will not be rendered twice
712
	 * @param $content
713
	 * @throws \Exception
714
	 */
715
	public function css($params, $content)
716
	{
717
		if ($content == null) return;
718
		$key = Arr::get($params, 'key', null);
719
		$content = str_replace(['<style>', '</style>'], '', $content);
720
		neon()->view->registerCss($content, ['position'=>$this->getAssetPosition($params, 'head')], $key);
721
	}
722
723
	/**
724
	 * Register Asset bundle
725
	 *
726
	 * ```
727
	 * {registerAsset name='\yii\web\JqueryAsset'} // jquery will be loaded before the end body tag
728
	 * {registerAsset path='\yii\web\JqueryAsset' pos='end'} // jquery will be loaded before the end body tag
729
	 * {registerAsset path='\yii\web\ThemeBootstrap' assignUrl='themeUrl'} // jquery will be loaded before the end body tag
730
	 *```
731
	 *
732
	 * @param array $params
733
	 * - [name|path] {string} = a asset bundle path to register for e.g. \yii\web\JqueryAsset
734
	 * - [pos|position] {string:head|begin|end|ready|load} = the position where the bundle should be output on the page defaults to 'end'
735
	 * @param Smarty $smarty
736
	 * @throws InvalidConfigException if the asset bundle does not exist or a circular dependency is detected
737
	 * @throws \Exception if incorrect asset position is given
738
	 * @return void
739
	 */
740
	public function registerAsset($params, $smarty)
741
	{
742
		// get the bundle - will look for `name` or `bundle` or `path` keys
743
		$class = Arr::get($params, 'name', Arr::get($params, 'path', null));
744
		if ($class === null) {
745
			trigger_error("registerAsset: missing 'name' or 'path' parameter");
746
		}
747
748
		// get the position - will looks for `pos` or `position` keys - defaults View::POS_END
749
		$position = Arr::get($params, 'pos', Arr::get($params, 'position', 'end'));
750
751
		$bundle = neon()->view->registerAssetBundle($class, $this->getAssetPosition($position));
0 ignored issues
show
Bug introduced by
It seems like $position can also be of type string; however, parameter $params of neon\core\view\SmartySha...ins::getAssetPosition() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

751
		$bundle = neon()->view->registerAssetBundle($class, $this->getAssetPosition(/** @scrutinizer ignore-type */ $position));
Loading history...
752
		$assign = Arr::get($params, 'assignUrl', Arr::get($params, 'assign', false));
753
		$smarty->assign($assign, $bundle->baseUrl);
0 ignored issues
show
Bug introduced by
It seems like $assign can also be of type false; however, parameter $tpl_var of Smarty_Internal_Data::assign() does only seem to accept array|string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

753
		$smarty->assign(/** @scrutinizer ignore-type */ $assign, $bundle->baseUrl);
Loading history...
754
	}
755
756
	/**
757
	 * @param array $params
758
	 * - [src|url]    string - the js src attribute
759
	 * - [attributes] array  - additional html attributes for the script tag also supports the special `depends`
760
	 *                           attribute enabling this file to depend on an asset bundle
761
	 * - [depends]    string - asset bundle dependencies
762
	 * - [key]        string - key to uniquely identify this file - optional
763
	 */
764
	public static function jsFile($params)
765
	{
766
		$src = Arr::get($params, 'src', Arr::get($params, 'url', null));
767
		if ($src === null) {
768
			trigger_error("jsFile: missing 'url' or 'src' parameter");
769
		}
770
		$attributes = Arr::get($params, 'attributes', []);
771
		$attributes['depends'] = Arr::get($params, 'depends', null);
772
		$key = Arr::get($params, 'key', null);
773
		neon()->view->registerJsFile($src, $attributes, $key);
774
	}
775
776
	/**
777
	 * @param array $params
778
	 * - [src|url]    string - the js src attribute
779
	 * - [attributes] array  - additional html attributes for the script tag also supports the special `depends`
780
	 *                           attribute enabling this file to depend on an asset bundle
781
	 * - [depends]    string - asset bundle dependencies
782
	 * - [key]        string - key to uniquely identify this file - optional
783
	 */
784
	public static function cssFile($params)
785
	{
786
		$src = Arr::get($params, 'src', Arr::get($params, 'url', null));
787
		if ($src === null) {
788
			trigger_error("jsFile: missing 'url' or 'src' parameter");
789
		}
790
		$attributes = Arr::get($params, 'attributes', []);
791
		$attributes['depends'] = Arr::get($params, 'depends', null);
792
		$key = Arr::get($params, 'key', null);
793
		neon()->view->registerCssFile($src, $attributes, $key);
794
	}
795
796
797
	/**
798
	 * Generates a UUID
799
	 * return string $UUID
800
	 */
801
	public function uuid($params, $template) {
802
		return Hash::uuid64();
803
	}
804
805
	/**
806
	 * Outputs scripts at the head
807
	 * - this tag marks where the - 'head' - View::POS_HEAD - position is
808
	 */
809
	public function neonHead()
810
	{
811
		return neon()->view->head();
0 ignored issues
show
Bug introduced by
Are you sure the usage of neon()->view->head() targeting yii\web\View::head() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
812
	}
813
814
	/**
815
	 * Outputs scripts after the first body tag
816
	 * - this tag marks where the - 'begin' - View::POS_BEGIN - position is
817
	 */
818
	public function neonBodyBegin()
819
	{
820
		return neon()->view->beginBody();
0 ignored issues
show
Bug introduced by
Are you sure the usage of neon()->view->beginBody() targeting yii\web\View::beginBody() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
821
	}
822
823
	/**
824
	 * Outputs scripts at the end body tag position
825
	 * - this tag marks where the - 'end' - View::POS_END - position is
826
	 */
827
	public function neonBodyEnd()
828
	{
829
		return neon()->view->endBody();
0 ignored issues
show
Bug introduced by
Are you sure the usage of neon()->view->endBody() targeting yii\web\View::endBody() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
830
	}
831
832
	/**
833
	 * check if a user has permission to do something
834
	 * @param array $params
835
	 * - [permission] string - the permission to check
836
	 * - [assign] string  - if set then the result is set to this parameter.
837
	 *   if not set then it is set to the same name as the permission
838
	 * - additional params - any additional params will be passed through to the
839
	 *   permission test
840
	 * @param Smarty $smarty
841
	 */
842
	public function hasPermission($params, $smarty)
843
	{
844
		if (empty($params['permission']))
845
			trigger_error("permission: missing 'permission' parameter");
846
		$permission = $params['permission'];
847
		$assign = empty($params['assign']) ? $permission : $params['assign'];
848
		$canDo = neon()->user->can($permission, $params);
849
		$smarty->assign($assign, $canDo);
850
	}
851
852
	/**
853
	 * Protects any HtmlEntities that are interpreted into javascript by
854
	 * via HTML. E.g. &lt; will be conerted to < by the browser. So this further
855
	 * protects by converting it to &amp;lt; so it beomes &lt; in the browser
856
	 * @param array $params
857
	 * - [input] string - the input string to protect
858
	 * - [assign] string (optional) - if set, then assign it to this parameter. If
859
	 *   not set, then return the converted string
860
	 * @param Smarty $smarty
861
	 * @return string  if no assign parameter provided, return the result
862
	 */
863
	public function protectHtmlEntities($params, $smarty)
864
	{
865
		$input = $params['input'];
866
		$output = str_replace('&','&amp;', $input);
867
		if (!empty($params['assign'])) {
868
			$smarty->assign($params['assign'], $output);
869
		} else {
870
			return $output;
871
		}
872
	}
873
}
874