SmartySharedPlugins   F
last analyzed

Complexity

Total Complexity 87

Size/Duplication

Total Lines 882
Duplicated Lines 0 %

Test Coverage

Coverage 0.72%

Importance

Changes 9
Bugs 0 Features 0
Metric Value
eloc 235
c 9
b 0
f 0
dl 0
loc 882
ccs 2
cts 279
cp 0.0072
rs 2
wmc 87

42 Methods

Rating   Name   Duplication   Size   Complexity  
A deprecatedTags() 0 3 1
A apply() 0 3 1
A favicon() 0 3 1
A googleCode() 0 28 3
A registerDeprecatedTags() 0 4 1
A markdown() 0 4 1
A is_logged_in() 0 3 1
A __construct() 0 58 2
A googleTagManager() 0 3 1
A canonical() 0 3 1
B setting() 0 14 8
A neonBodyBegin() 0 3 1
A cmp() 0 15 5
A image_meta() 0 6 2
A image() 0 24 4
A style() 0 18 2
A js() 0 6 2
A json() 0 3 1
A registerAsset() 0 14 2
A image_src() 0 6 2
A on_page() 0 6 3
A cssFile() 0 9 2
A generateSrcset() 0 13 4
A jsFile() 0 10 2
A csrf_meta_tags() 0 3 1
A firefly_url() 0 4 2
A url() 0 8 2
A htmlAttributes() 0 3 1
A page_url() 0 20 4
A firefly_image() 0 9 3
A image_alt() 0 12 2
A image_srcset() 0 9 3
A hasPermission() 0 8 3
A time() 0 6 2
A neonHead() 0 3 1
A css() 0 6 2
A protectHtmlEntities() 0 8 2
A csrf_element() 0 3 1
A asset() 0 4 1
A neonBodyEnd() 0 3 1
A uuid() 0 2 1
A getAssetPosition() 0 14 2

How to fix   Complexity   

Complex Class

Complex classes like SmartySharedPlugins often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use SmartySharedPlugins, and based on these observations, apply Extract Interface, too.

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
	/**
33
	 * Default set of images breakpoints to be availablle for srcsets generated automatically
34
	 * @todo NJ-20210625 - see if can determine 'ideal' default set of widths for images. Using the idea of
35
	 * constant wasted bytes between widths, should have more widths as the images get larger. Using performance
36
	 * budget (no image breakpoint more than e.g. 20K than the previous) epends on the actual image.
37
	 * @see https://cloudfour.com/thinks/responsive-images-101-part-9-image-breakpoints/
38
	 */
39
	const IMG_SRCSET_DEFAULT_WIDTHS = "200,360,400,500,640,700,768,800,900,1024,1100,1280,1300,1536,1600,1700,1920,2000,2200,2400,2592";
40
41
	/**
42
	 * Apply these plugin functions to a smarty object
43
	 * @param mixed $parent
44
	 * @param Smarty $smarty
45
	 * @throws \SmartyException
46
	 */
47
	public static function apply($parent, $smarty)
48
	{
49
		new self($parent, $smarty);
50
	}
51
52
	/**
53
	 * Maintain a list of tags that will be deprecated so we can aid upgrades and provide sensible error messages
54
	 * @return array
55
	 */
56
	public static function deprecatedTags()
57
	{
58
		return ['cosmos_head_script', 'cosmos_body_script'];
59
	}
60
61
	/**
62
	 * SmartySharedPlugins constructor.
63
	 * @param mixed $parent - should implement a parent page interface
64
	 * @param \Smarty $smarty
65
	 * @throws \SmartyException
66
	 */
67
	public function __construct($parent, $smarty)
68
	{
69
		// app plugin smarty directories
70
		$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...
71
		foreach ($coreApps as $key=>$app) {
72
			$smarty->addPluginsDir(Neon::getAlias("@neon/$key/plugins/smarty"));
73
		}
74
		// plugins
75
		$smarty->registerPlugin(Smarty::PLUGIN_FUNCTION, 'firefly_url', [$this, 'firefly_url']);
76
		$smarty->registerPlugin(Smarty::PLUGIN_FUNCTION, 'firefly_image', [$this, 'firefly_image']);
77
		$smarty->registerPlugin(Smarty::PLUGIN_FUNCTION, 'image', [$this, 'image']);
78
		$smarty->registerPlugin(Smarty::PLUGIN_FUNCTION, 'image_src', [$this, 'image_src']);
79
		$smarty->registerPlugin(Smarty::PLUGIN_FUNCTION, 'image_alt', [$this, 'image_alt']);
80
		$smarty->registerPlugin(Smarty::PLUGIN_FUNCTION, 'image_srcset', [$this, 'image_srcset']);
81
		$smarty->registerPlugin(Smarty::PLUGIN_FUNCTION, 'setting', [$this, 'setting']);
82
		$smarty->registerPlugin(Smarty::PLUGIN_FUNCTION, 'time', [$this, 'time']);
83
		$smarty->registerPlugin(Smarty::PLUGIN_FUNCTION, 'csrf_element', [$this, 'csrf_element']);
84
		$smarty->registerPlugin(Smarty::PLUGIN_FUNCTION, 'csrf_meta_tags', [$this, 'csrf_meta_tags']);
85
		$smarty->registerPlugin(Smarty::PLUGIN_FUNCTION, 'url', [$this, 'url']);
86
		$smarty->registerPlugin(Smarty::PLUGIN_FUNCTION, 'registerAsset', [$this, 'registerAsset']);
87
		$smarty->registerPlugin(Smarty::PLUGIN_FUNCTION, 'jsFile', [$this, 'jsFile']);
88
		$smarty->registerPlugin(Smarty::PLUGIN_FUNCTION, 'cssFile', [$this, 'cssFile']);
89
		$smarty->registerPlugin(Smarty::PLUGIN_FUNCTION, 'neon_head', [$this, 'neonHead']);
90
		$smarty->registerPlugin(Smarty::PLUGIN_FUNCTION, 'neon_body_begin', [$this, 'neonBodyBegin']);
91
		$smarty->registerPlugin(Smarty::PLUGIN_FUNCTION, 'neon_body_end', [$this, 'neonBodyEnd']);
92
		$smarty->registerPlugin(Smarty::PLUGIN_FUNCTION, 'hasPermission', [$this, 'hasPermission']);
93
		$smarty->registerPlugin(Smarty::PLUGIN_FUNCTION, 'style', [$this, 'style']);
94
		$smarty->registerPlugin(Smarty::PLUGIN_FUNCTION, 'uuid', [$this, 'uuid']);
95
		$smarty->registerPlugin(Smarty::PLUGIN_FUNCTION, 'asset', [$this, 'asset']);
96
		$smarty->registerPlugin(Smarty::PLUGIN_FUNCTION, 'assets', [$this, 'asset']);
97
		$smarty->registerPlugin(Smarty::PLUGIN_FUNCTION, 'is_logged_in', [$this, 'is_logged_in']);
98
		$smarty->registerPlugin(Smarty::PLUGIN_FUNCTION, 'page_url', [$this, 'page_url']);
99
		$smarty->registerPlugin(Smarty::PLUGIN_FUNCTION, 'on_page', [$this, 'on_page']);
100
		$smarty->registerPlugin(Smarty::PLUGIN_FUNCTION, 'canonical', [$this, 'canonical']);
101
		$smarty->registerPlugin(Smarty::PLUGIN_FUNCTION, 'googleCode', [$this, 'googleCode']);
102
		$smarty->registerPlugin(Smarty::PLUGIN_FUNCTION, 'protectHtmlEntities', [$this, 'protectHtmlEntities']);
103
		$smarty->registerPlugin(Smarty::PLUGIN_FUNCTION, 'favicon', [$this, 'favicon']);
104
105
106
		// modifiers
107
		$smarty->registerPlugin(Smarty::PLUGIN_MODIFIER, 'htmlAttributes', [$this, 'htmlAttributes']);
108
		$smarty->registerPlugin(Smarty::PLUGIN_MODIFIER, 'humanize', '\neon\core\helpers\Str::humanize');
109
		$smarty->registerPlugin(Smarty::PLUGIN_MODIFIER, 'json', [$this, 'json']);
110
111
		// blocks
112
		$smarty->registerPlugin(Smarty::PLUGIN_BLOCK, 'markdown', [$this, 'markdown']);
113
		// All the same - ways to register a block of js :
114
		$smarty->registerPlugin(Smarty::PLUGIN_BLOCK, 'script', [$this, 'js']);
115
		$smarty->registerPlugin(Smarty::PLUGIN_BLOCK, 'js', [$this, 'js']);
116
		$smarty->registerPlugin(Smarty::PLUGIN_BLOCK, 'Js', [$this, 'js']);
117
		// Register a block of css
118
		$smarty->registerPlugin(Smarty::PLUGIN_BLOCK, 'css', [$this, 'css']);
119
120
		// experimental
121
		$smarty->registerPlugin(Smarty::PLUGIN_BLOCK, 'cmp', [$this, 'cmp']);
122
123
		// deprecated tags
124
		$this->registerDeprecatedTags($smarty);
125
	}
126
127
	/**
128
	 * Output the standard gtag google code - if a google_analytics code is provided
129
	 * or a google tag manager code is provided (GTM-) for tag manager.
130
	 * It starts with UA for standard analytics.
131
	 * If Google tag manager is installed - you do not need the analytics code.
132
	 * https://developers.google.com/tag-manager/quickstart
133
	 * https://developers.google.com/gtagjs/devguide/snippet#google-analytics
134
	 */
135
	public function googleCode($params)
136
	{
137
		$id = setting('cms', 'google_code');
138
		if (empty($id)) return '';
139
		// if using google tag manager
140
		if (substr($id, 0, 3) === 'GTM') {
141
			$head = <<<HEADJS
142
				<!-- Google Tag Manager -->
143
				<script>(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
144
				new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
145
				j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
146
				'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
147
				})(window,document,'script','dataLayer','$id');</script>
148
				<!-- End Google Tag Manager -->
149
			HEADJS;
150
			$body = <<<BODYJS
151
				<!-- Google Tag Manager (noscript) -->
152
				<noscript><iframe src="https://www.googletagmanager.com/ns.html?id=$id" height="0" width="0" style="display:none;visibility:hidden"></iframe></noscript>
153
				<!-- End Google Tag Manager (noscript) -->
154
			BODYJS;
155
			neon()->view->registerHtml($head, View::POS_HEAD);
156
			neon()->view->registerHtml($body, View::POS_END);
157
			return '';
158
		}
159
		// else we use standard gtag
160
		// this can accept ID's like GA_MEASUREMENT_ID / AW-CONVERSION_ID / DC-FLOODIGHT_ID / UA-XXX
161
		neon()->view->registerHtml("<script async src=\"https://www.googletagmanager.com/gtag/js?id=$id\"></script>\n"
162
		. "<script>window.dataLayer = window.dataLayer || [];function gtag(){dataLayer.push(arguments);};gtag('js', new Date());gtag('config', '$id');</script>", View::POS_HEAD);
163
	}
164
165
	/**
166
	 * Generate the html meta tags for a favicon
167
	 */
168
	public function favicon()
169
	{
170
		return Html::favicon(setting('cms', 'favicon'));
171
	}
172
173
	public function googleTagManager()
174
	{
175
		$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...
176
	}
177
178
	/**
179
	 * @param \Smarty $smarty
180
	 * @throws \SmartyException
181
	 */
182
	public function registerDeprecatedTags($smarty)
183
	{
184
		$smarty->registerPlugin(Smarty::PLUGIN_FUNCTION, 'cosmos_head_script', [$this, 'neonHead']);
185
		$smarty->registerPlugin(Smarty::PLUGIN_FUNCTION, 'cosmos_body_script', [$this, 'neonBodyEnd']);
186
	}
187
188
	/**
189
	 * Parse a block of content as markdown
190
	 *
191
	 * ```smarty
192
	 * {markdown} ***markdown content*** {/markdown}
193
	 * ```
194
	 *
195
	 * @param $params
196
	 * @param $content
197
	 * @return string
198
	 */
199
	public function markdown($params, $content)
200
	{
201
		$parser = new MarkdownNeon();
202
		return $parser->parse($content);
203
	}
204
205
	/**
206
	 * Gets the url for a canonical version of the current url
207
	 * This also uses the cms canonical setting otherwise uses getHostInfo
208
	 *
209
	 * @return string - the url
210
	 */
211
	public function canonical($params, $template)
212
	{
213
		return neon()->urlManager->getCanonical();
214
	}
215
216
	/**
217
	 * See if a user is logged in
218
	 * usage: {if {is_logged_in}} ...
219
	 * @return boolean
220
	 */
221
	public function is_logged_in($params, $smarty)
222
	{
223
		return neon()->user->isGuest === false;
224
	}
225
226
	/**
227
	 * Will force copy assets when in dev mode
228
	 * This is specific to the cms module
229
	 * ```{asset path="/path/within/assets/theme/directory"}```
230
	 * ```{asset path="http://something"}``` - the path is output as is this is useful when either an internal or external image
231
	 * can be supplied to a component
232
	 *
233
	 * @param $params
234
	 * @return string
235
	 */
236
	public function asset($params)
237
	{
238
		$path = Arr::getRequired($params, 'path');
239
		return neon()->cms->themeAssetUrl($path);
240
	}
241
242
	/**
243
	 * @param $params
244
	 * @param $content
245
	 * @param $template
246
	 * @param $repeat
247
	 * @return string
248
	 */
249
	public function cmp($params, $content, $template, &$repeat)
250
	{
251
		// only output on the closing tag
252
		if (!$repeat) {
253
			$class = Arr::remove($params, 'class');
254
			$params['content'] = $content;
255
			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

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

606
			$img = Arr::defaults(/** @scrutinizer ignore-type */ $imgOptions, ['id' => $id, 'q' => 90, 'w' => (int) $width]);
Loading history...
607
			$imageUrl = $this->firefly_image($img);
608
			$srcsetParts[$width] = $imageUrl;
609
		}
610
		return $srcsetParts;
611
	}
612
613
614
	/**
615
	 * tag: setting
616
	 *
617
	 * Get a setting value from the settings manager
618
	 *
619
	 * ```{setting app='aaa' name='nnn' [default='xxx' assign='yyy']}```
620
	 *
621
	 * @param array $params
622
	 *   app - required - the settings app area
623
	 *   name - required - the setting required
624
	 *   default - optional - a default value if it's not set
625
	 *   assign - optional - if set assigns to this variable else returns the value
626
	 * @param object $smarty
627
	 * @return string
628
	 */
629
	public function setting($params, $smarty)
630
	{
631
		$app = isset($params['app']) ? $params['app'] : null;
632
		$name = isset($params['name']) ? $params['name'] : null;
633
		$default = isset($params['default']) ? $params['default'] : null;
634
		$assign = isset($params['assign']) ? $params['assign'] : null;
635
		if ($app && $name) {
636
			$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

636
			/** @scrutinizer ignore-call */ 
637
   $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...
637
			if ($assign)
638
				$smarty->assign($assign, $value);
639
			else
640
				return $value;
641
		} else {
642
			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

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

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

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

795
		$smarty->assign(/** @scrutinizer ignore-type */ $assign, $bundle->baseUrl);
Loading history...
796
	}
797
798
	/**
799
	 * @param array $params
800
	 * - [src|url]    string - the js src attribute
801
	 * - [attributes] array  - additional html attributes for the script tag also supports the special `depends`
802
	 *                           attribute enabling this file to depend on an asset bundle
803
	 * - [depends]    string - asset bundle dependencies
804
	 * - [key]        string - key to uniquely identify this file - optional
805
	 */
806
	public static function jsFile($params)
807
	{
808
		$src = Arr::get($params, 'src', Arr::get($params, 'url', null));
809
		if ($src === null) {
810
			trigger_error("jsFile: missing 'url' or 'src' parameter");
811
		}
812
		$attributes = Arr::get($params, 'attributes', []);
813
		$attributes['depends'] = Arr::get($params, 'depends', null);
814
		$key = Arr::get($params, 'key', null);
815
		neon()->view->registerJsFile($src, $attributes, $key);
816
	}
817
818
	/**
819
	 * @param array $params
820
	 * - [src|url]    string - the js src attribute
821
	 * - [attributes] array  - additional html attributes for the script tag also supports the special `depends`
822
	 *                           attribute enabling this file to depend on an asset bundle
823
	 * - [depends]    string - asset bundle dependencies
824
	 * - [key]        string - key to uniquely identify this file - optional
825
	 */
826
	public static function cssFile($params)
827
	{
828
		$src = Arr::remove($params, 'src', Arr::remove($params, 'url', null));
829
		if ($src === null) {
830
			trigger_error("jsFile: missing 'url' or 'src' parameter");
831
		}
832
		$attributes = Arr::remove($params, 'attributes', []);
833
		$key = Arr::remove($params, 'key', null);
834
		neon()->view->registerCssFile($src, array_merge($params,$attributes), $key);
0 ignored issues
show
Bug introduced by
It seems like $attributes can also be of type null; however, parameter $arrays of array_merge() 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

834
		neon()->view->registerCssFile($src, array_merge($params,/** @scrutinizer ignore-type */ $attributes), $key);
Loading history...
835
	}
836
837
838
	/**
839
	 * Generates a UUID
840
	 * return string $UUID
841
	 */
842
	public function uuid($params, $template) {
843
		return Hash::uuid64();
844
	}
845
846
	/**
847
	 * Outputs scripts at the head
848
	 * - this tag marks where the - 'head' - View::POS_HEAD - position is
849
	 */
850
	public function neonHead()
851
	{
852
		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...
853
	}
854
855
	/**
856
	 * Outputs scripts after the first body tag
857
	 * - this tag marks where the - 'begin' - View::POS_BEGIN - position is
858
	 */
859
	public function neonBodyBegin()
860
	{
861
		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...
862
	}
863
864
	/**
865
	 * Outputs scripts at the end body tag position
866
	 * - this tag marks where the - 'end' - View::POS_END - position is
867
	 */
868
	public function neonBodyEnd()
869
	{
870
		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...
871
	}
872
873
	/**
874
	 * check if a user has permission to do something
875
	 * @param array $params
876
	 * - [permission] string - the permission to check
877
	 * - [assign] string  - if set then the result is set to this parameter.
878
	 *   if not set then it is set to the same name as the permission
879
	 * - additional params - any additional params will be passed through to the
880
	 *   permission test
881
	 * @param Smarty $smarty
882
	 */
883
	public function hasPermission($params, $smarty)
884
	{
885
		if (empty($params['permission']))
886
			trigger_error("permission: missing 'permission' parameter");
887
		$permission = $params['permission'];
888
		$assign = empty($params['assign']) ? $permission : $params['assign'];
889
		$canDo = neon()->user->can($permission, $params);
890
		$smarty->assign($assign, $canDo);
891
	}
892
893
	/**
894
	 * Protects any HtmlEntities that are interpreted into javascript by
895
	 * via HTML. E.g. &lt; will be conerted to < by the browser. So this further
896
	 * protects by converting it to &amp;lt; so it beomes &lt; in the browser
897
	 * @param array $params
898
	 * - [input] string - the input string to protect
899
	 * - [assign] string (optional) - if set, then assign it to this parameter. If
900
	 *   not set, then return the converted string
901
	 * @param Smarty $smarty
902
	 * @return string  if no assign parameter provided, return the result
903
	 */
904
	public function protectHtmlEntities($params, $smarty)
905
	{
906
		$input = $params['input'];
907
		$output = str_replace('&','&amp;', $input);
908
		if (!empty($params['assign'])) {
909
			$smarty->assign($params['assign'], $output);
910
		} else {
911
			return $output;
912
		}
913
	}
914
}
915