Passed
Push — 2.6.0 ( ...f22176 )
by steve
18:56
created

SmartySharedPlugins::setting()   B

Complexity

Conditions 8
Paths 48

Size

Total Lines 14
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 72

Importance

Changes 0
Metric Value
eloc 12
dl 0
loc 14
rs 8.4444
c 0
b 0
f 0
cc 8
nc 48
nop 2
ccs 0
cts 11
cp 0
crap 72
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, 'canonical', [$this, 'canonical']);
94
		$smarty->registerPlugin(Smarty::PLUGIN_FUNCTION, 'googleCode', [$this, 'googleCode']);
95
		$smarty->registerPlugin(Smarty::PLUGIN_FUNCTION, 'protectHtmlEntities', [$this, 'protectHtmlEntities']);
96
97
		// modifiers
98
		$smarty->registerPlugin(Smarty::PLUGIN_MODIFIER, 'htmlAttributes', [$this, 'htmlAttributes']);
99
		$smarty->registerPlugin(Smarty::PLUGIN_MODIFIER, 'humanize', '\neon\core\helpers\Str::humanize');
100
		$smarty->registerPlugin(Smarty::PLUGIN_MODIFIER, 'json', [$this, 'json']);
101
102
		// blocks
103
		$smarty->registerPlugin(Smarty::PLUGIN_BLOCK, 'markdown', [$this, 'markdown']);
104
		// All the same - ways to register a block of js :
105
		$smarty->registerPlugin(Smarty::PLUGIN_BLOCK, 'script', [$this, 'js']);
106
		$smarty->registerPlugin(Smarty::PLUGIN_BLOCK, 'js', [$this, 'js']);
107
		$smarty->registerPlugin(Smarty::PLUGIN_BLOCK, 'Js', [$this, 'js']);
108
		// Register a block of css
109
		$smarty->registerPlugin(Smarty::PLUGIN_BLOCK, 'css', [$this, 'css']);
110
111
		// experimental
112
		$smarty->registerPlugin(Smarty::PLUGIN_BLOCK, 'cmp', [$this, 'cmp']);
113
114
		// deprecated tags
115
		$this->registerDeprecatedTags($smarty);
116
	}
117
118
	/**
119
	 * Output the standard gtag google code - if a google_analytics code is provided
120
	 * or a google tag manager code is provided (GTM-) for tag manager.
121
	 * It starts with UA for standard analytics.
122
	 * If Google tag manager is installed - you do not need the analytics code.
123
	 * https://developers.google.com/tag-manager/quickstart
124
	 * https://developers.google.com/gtagjs/devguide/snippet#google-analytics
125
	 */
126
	public function googleCode($params)
127
	{
128
		$id = setting('cms', 'google_code');
129
		if (empty($id)) return '';
130
		// if using google tag manager
131
		if (substr($id, 0, 3) === 'GTM') {
132
			$head = <<<HEADJS
133
				<!-- Google Tag Manager -->
134
				<script>(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
135
				new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
136
				j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
137
				'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
138
				})(window,document,'script','dataLayer','$id');</script>
139
				<!-- End Google Tag Manager -->
140
			HEADJS;
141
			$body = <<<BODYJS
142
				<!-- Google Tag Manager (noscript) -->
143
				<noscript><iframe src="https://www.googletagmanager.com/ns.html?id=$id" height="0" width="0" style="display:none;visibility:hidden"></iframe></noscript>
144
				<!-- End Google Tag Manager (noscript) -->
145
			BODYJS;
146
			neon()->view->registerHtml($head, View::POS_HEAD);
147
			neon()->view->registerHtml($body, View::POS_END);
148
			return '';
149
		}
150
		// else we use standard gtag
151
		// this can accept ID's like GA_MEASUREMENT_ID / AW-CONVERSION_ID / DC-FLOODIGHT_ID / UA-XXX
152
		neon()->view->registerHtml("<script async src=\"https://www.googletagmanager.com/gtag/js?id=$id\"></script>\n"
153
		. "<script>window.dataLayer = window.dataLayer || [];function gtag(){dataLayer.push(arguments);};gtag('js', new Date());gtag('config', '$id');</script>", View::POS_HEAD);
154
	}
155
156
	public function googleTagManager()
157
	{
158
		$id = setting('cms', 'google_analytics');
0 ignored issues
show
Unused Code introduced by
The assignment to $id is dead and can be removed.
Loading history...
159
	}
160
161
	/**
162
	 * @param \Smarty $smarty
163
	 * @throws \SmartyException
164
	 */
165
	public function registerDeprecatedTags($smarty)
166
	{
167
		$smarty->registerPlugin(Smarty::PLUGIN_FUNCTION, 'cosmos_head_script', [$this, 'neonHead']);
168
		$smarty->registerPlugin(Smarty::PLUGIN_FUNCTION, 'cosmos_body_script', [$this, 'neonBodyEnd']);
169
	}
170
171
	/**
172
	 * Parse a block of content as markdown
173
	 *
174
	 * ```smarty
175
	 * {markdown} ***markdown content*** {/markdown}
176
	 * ```
177
	 *
178
	 * @param $params
179
	 * @param $content
180
	 * @return string
181
	 */
182
	public function markdown($params, $content)
183
	{
184
		$parser = new MarkdownNeon();
185
		return $parser->parse($content);
186
	}
187
188
	/**
189
	 * Gets the url for a canonical version of the current url
190
	 * This also uses the cms canonical setting otherwise uses getHostInfo
191
	 * 
192
	 * @return string - the url
193
	 */
194
	public function canonical($params, $template)
195
	{
196
		return neon()->urlManager->getCanonical();
197
	}
198
199
	/**
200
	 * See if a user is logged in
201
	 * usage: {if {is_logged_in}} ...
202
	 * @return boolean
203
	 */
204
	public function is_logged_in($params, $smarty)
205
	{
206
		return neon()->user->isGuest === false;
207
	}
208
209
	/**
210
	 * Will force copy assets when in dev mode
211
	 * This is specific to the cms module
212
	 * ```{asset path="/path/within/assets/theme/directory"}```
213
	 * ```{asset path="http://something"}``` - the path is output as is this is useful when either an internal or external image
214
	 * can be supplied to a component
215
	 *
216
	 * @param $params
217
	 * @return string
218
	 */
219
	public function asset($params)
220
	{
221
		$path = Arr::getRequired($params, 'path');
222
		// loading external resource so just return
223
		if (Str::startsWith($path, 'http')) return $path;
224
		$options = [];
225
226
		$themeAssets = neon()->cms->getThemeAlias().'/assets';
227
		list($assetPath, $assetUrl) = neon()->assetManager->publish($themeAssets, $options);
228
		$path = ltrim($path, '/');
229
		$timestamp = @filemtime("$assetPath/$path");
230
		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

230
		return  neon()->urlManager->getHostInfo() . $assetUrl . '/' . $path . (($timestamp > 0) ? '?v='./** @scrutinizer ignore-type */ $timestamp : '');
Loading history...
231
	}
232
233
	/**
234
	 * @param $params
235
	 * @param $content
236
	 * @param $template
237
	 * @param $repeat
238
	 * @return string
239
	 */
240
	public function cmp($params, $content, $template, &$repeat)
241
	{
242
		// only output on the closing tag
243
		if (!$repeat) {
244
			$class = Arr::remove($params, 'class');
245
			$params['content'] = $content;
246
			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

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

650
			/** @scrutinizer ignore-call */ 
651
   $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...
651
			if ($assign)
652
				$smarty->assign($assign, $value);
653
			else
654
				return $value;
655
		} else {
656
			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

656
			return "Usage: you must provide an 'app' and a 'name' parameter. You provided "./** @scrutinizer ignore-type */ print_r($params,true);
Loading history...
657
		}
658
	}
659
660
	/**
661
	 * tag: time
662
	 *
663
	 * Convert a timestamp into a YYYY-MM-DD HH:MM:SS string
664
	 *
665
	 * Usage:
666
	 * ```
667
	 *   {time} - returns the current time
668
	 *   {time stamp='123456'} - returns the timestamp formatted
669
	 * ```
670
	 *
671
	 * @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...
672
	 * @return string YYYY-MM-DD HH:MM:SS
673
	 */
674
	public function time($params)
675
	{
676
		$format = 'Y-m-d H:i:s';
677
		if (isset($params['stamp']))
678
			return date($format, (integer)$params['stamp']);
679
		return date($format);
680
	}
681
682
	/**
683
	 * Output a pre tag of nicely formatted json - useful for debugging
684
	 *
685
	 * @param $params
686
	 * @return string
687
	 */
688
	public function json($params)
689
	{
690
		return '<pre>' . json_encode($params, JSON_PRETTY_PRINT) . '</pre>';
691
	}
692
693
	/**
694
	 * Looks for a `position` or `pos` key in the params and returns the integer value for the string position
695
	 * ```
696
	 * $this->getAssetPosition(['pos' => 'end']) // gives: 3
697
	 * ```
698
	 * @see SmartySharedPlugins::registerAsset
699
	 * @see SmartySharedPlugins::js()
700
	 * @param array $params
701
	 * @param string $default - defaults to 'end'
702
	 * @return int
703
	 * @throws \Exception
704
	 */
705
	public function getAssetPosition($params, $default='end')
706
	{
707
		$position = Arr::get($params, 'pos', Arr::get($params, 'position', $default));
708
		$positions = [
709
			'head'  => View::POS_HEAD,
710
			'begin' => View::POS_BEGIN,
711
			'end'   => View::POS_END,
712
			'ready' => View::POS_READY,
713
			'load'  => View::POS_LOAD,
714
		];
715
		if (!array_key_exists($position, $positions)) {
716
			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

716
			throw new \Exception('The javascript position specified must be one of ' . /** @scrutinizer ignore-type */ print_r(array_keys($positions)) . ', you specified "'.$position.'"');
Loading history...
717
		}
718
		return $positions[$position];
719
	}
720
721
	/**
722
	 * Smarty block function plugin
723
	 * Usage is the following:
724
	 *
725
	 * ```
726
	 * {Js} Javascript code here {/Js}
727
	 * {Js pos='end'} Javascript code here {/Js}
728
	 * {Js pos='ready'} jquery on ready code here {/Js}
729
	 * ```
730
	 *
731
	 * @param $params
732
	 *
733
	 *   [position|pos] with values:
734
	 *   The position param specified where the javascript block will be rendered on the page it can have
735
	 *   the following values:
736
	 *
737
	 *   - "head"  : This means the javascript is rendered in the head section.
738
	 *   - "begin" : This means the javascript is rendered at the beginning of the body section.
739
	 *   - "end"   : This means the javascript is rendered at the end of the body section.
740
	 *   - "ready" : This means the JavaScript code block will be enclosed within `jQuery(document).ready()`.
741
	 *   - "load"  : This means the JavaScript code block will be enclosed within `jQuery(window).load()`.
742
	 *
743
	 *
744
	 * @param $content
745
	 * @return void
746
	 * @throws \Exception
747
	 */
748
	public function js($params, $content)
749
	{
750
		if ($content == null) return;
751
		$key = Arr::get($params, 'key', null);
752
		$content = str_replace(['<script>', '</script>'], '', $content);
753
		neon()->view->registerJs($content, $this->getAssetPosition($params), $key);
754
	}
755
756
	/**
757
	 * Register a block of css code
758
	 *
759
	 * ```
760
	 * {css position='head'}
761
	 * .myStyle {color:'red'}
762
	 * {/css}
763
	 * ```
764
	 *
765
	 * @param $params
766
	 * [position|pos] string - the string position in the page where css should be output - defaults to 'head'
767
	 * [key] string - a key to uniquely identify this css block so it will not be rendered twice
768
	 * @param $content
769
	 * @throws \Exception
770
	 */
771
	public function css($params, $content)
772
	{
773
		if ($content == null) return;
774
		$key = Arr::get($params, 'key', null);
775
		$content = str_replace(['<style>', '</style>'], '', $content);
776
		neon()->view->registerCss($content, ['position'=>$this->getAssetPosition($params, 'head')], $key);
777
	}
778
779
	/**
780
	 * Register Asset bundle
781
	 *
782
	 * ```
783
	 * {registerAsset name='\yii\web\JqueryAsset'} // jquery will be loaded before the end body tag
784
	 * {registerAsset path='\yii\web\JqueryAsset' pos='end'} // jquery will be loaded before the end body tag
785
	 * {registerAsset path='\yii\web\ThemeBootstrap' assignUrl='themeUrl'} // jquery will be loaded before the end body tag
786
	 *```
787
	 *
788
	 * @param array $params
789
	 * - [name|path] {string} = a asset bundle path to register for e.g. \yii\web\JqueryAsset
790
	 * - [pos|position] {string:head|begin|end|ready|load} = the position where the bundle should be output on the page defaults to 'end'
791
	 * @param Smarty $smarty
792
	 * @throws InvalidConfigException if the asset bundle does not exist or a circular dependency is detected
793
	 * @throws \Exception if incorrect asset position is given
794
	 * @return void
795
	 */
796
	public function registerAsset($params, $smarty)
797
	{
798
		// get the bundle - will look for `name` or `bundle` or `path` keys
799
		$class = Arr::get($params, 'name', Arr::get($params, 'path', null));
800
		if ($class === null) {
801
			trigger_error("registerAsset: missing 'name' or 'path' parameter");
802
		}
803
804
		// get the position - will looks for `pos` or `position` keys - defaults View::POS_END
805
		$position = Arr::get($params, 'pos', Arr::get($params, 'position', 'end'));
806
807
		$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

807
		$bundle = neon()->view->registerAssetBundle($class, $this->getAssetPosition(/** @scrutinizer ignore-type */ $position));
Loading history...
808
		$assign = Arr::get($params, 'assignUrl', Arr::get($params, 'assign', false));
809
		$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

809
		$smarty->assign(/** @scrutinizer ignore-type */ $assign, $bundle->baseUrl);
Loading history...
810
	}
811
812
	/**
813
	 * @param array $params
814
	 * - [src|url]    string - the js src attribute
815
	 * - [attributes] array  - additional html attributes for the script tag also supports the special `depends`
816
	 *                           attribute enabling this file to depend on an asset bundle
817
	 * - [depends]    string - asset bundle dependencies
818
	 * - [key]        string - key to uniquely identify this file - optional
819
	 */
820
	public static function jsFile($params)
821
	{
822
		$src = Arr::get($params, 'src', Arr::get($params, 'url', null));
823
		if ($src === null) {
824
			trigger_error("jsFile: missing 'url' or 'src' parameter");
825
		}
826
		$attributes = Arr::get($params, 'attributes', []);
827
		$attributes['depends'] = Arr::get($params, 'depends', null);
828
		$key = Arr::get($params, 'key', null);
829
		neon()->view->registerJsFile($src, $attributes, $key);
830
	}
831
832
	/**
833
	 * @param array $params
834
	 * - [src|url]    string - the js src attribute
835
	 * - [attributes] array  - additional html attributes for the script tag also supports the special `depends`
836
	 *                           attribute enabling this file to depend on an asset bundle
837
	 * - [depends]    string - asset bundle dependencies
838
	 * - [key]        string - key to uniquely identify this file - optional
839
	 */
840
	public static function cssFile($params)
841
	{
842
		$src = Arr::get($params, 'src', Arr::get($params, 'url', null));
843
		if ($src === null) {
844
			trigger_error("jsFile: missing 'url' or 'src' parameter");
845
		}
846
		$attributes = Arr::get($params, 'attributes', []);
847
		$attributes['depends'] = Arr::get($params, 'depends', null);
848
		$key = Arr::get($params, 'key', null);
849
		neon()->view->registerCssFile($src, $attributes, $key);
850
	}
851
852
853
	/**
854
	 * Generates a UUID
855
	 * return string $UUID
856
	 */
857
	public function uuid($params, $template) {
858
		return Hash::uuid64();
859
	}
860
861
	/**
862
	 * Outputs scripts at the head
863
	 * - this tag marks where the - 'head' - View::POS_HEAD - position is
864
	 */
865
	public function neonHead()
866
	{
867
		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...
868
	}
869
870
	/**
871
	 * Outputs scripts after the first body tag
872
	 * - this tag marks where the - 'begin' - View::POS_BEGIN - position is
873
	 */
874
	public function neonBodyBegin()
875
	{
876
		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...
877
	}
878
879
	/**
880
	 * Outputs scripts at the end body tag position
881
	 * - this tag marks where the - 'end' - View::POS_END - position is
882
	 */
883
	public function neonBodyEnd()
884
	{
885
		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...
886
	}
887
888
	/**
889
	 * check if a user has permission to do something
890
	 * @param array $params
891
	 * - [permission] string - the permission to check
892
	 * - [assign] string  - if set then the result is set to this parameter.
893
	 *   if not set then it is set to the same name as the permission
894
	 * - additional params - any additional params will be passed through to the
895
	 *   permission test
896
	 * @param Smarty $smarty
897
	 */
898
	public function hasPermission($params, $smarty)
899
	{
900
		if (empty($params['permission']))
901
			trigger_error("permission: missing 'permission' parameter");
902
		$permission = $params['permission'];
903
		$assign = empty($params['assign']) ? $permission : $params['assign'];
904
		$canDo = neon()->user->can($permission, $params);
905
		$smarty->assign($assign, $canDo);
906
	}
907
908
	/**
909
	 * Protects any HtmlEntities that are interpreted into javascript by
910
	 * via HTML. E.g. &lt; will be conerted to < by the browser. So this further
911
	 * protects by converting it to &amp;lt; so it beomes &lt; in the browser
912
	 * @param array $params
913
	 * - [input] string - the input string to protect
914
	 * - [assign] string (optional) - if set, then assign it to this parameter. If
915
	 *   not set, then return the converted string
916
	 * @param Smarty $smarty
917
	 * @return string  if no assign parameter provided, return the result
918
	 */
919
	public function protectHtmlEntities($params, $smarty)
920
	{
921
		$input = $params['input'];
922
		$output = str_replace('&','&amp;', $input);
923
		if (!empty($params['assign'])) {
924
			$smarty->assign($params['assign'], $output);
925
		} else {
926
			return $output;
927
		}
928
	}
929
}
930