Passed
Push — develop ( 319bd8...330c7a )
by Neill
16:41 queued 15s
created

SmartySharedPlugins::generateSrcset()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 13
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 20

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 4
eloc 9
c 1
b 0
f 0
nc 3
nop 3
dl 0
loc 13
ccs 0
cts 10
cp 0
crap 20
rs 9.9666
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
		// loading external resource so just return
240
		if (Str::startsWith($path, 'http')) return $path;
241
		$options = [];
242
243
		$themeAssets = neon()->cms->getThemeAlias().'/assets';
244
		list($assetPath, $assetUrl) = neon()->assetManager->publish($themeAssets, $options);
245
		$path = ltrim($path, '/');
246
		$assetUrl = ltrim($assetUrl, '/');
247
		$timestamp = @filemtime("$assetPath/$path");
248
		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

248
		return neon()->urlManager->getHostInfo().'/'.$assetUrl.'/'.$path.(($timestamp > 0) ? '?v='./** @scrutinizer ignore-type */ $timestamp : '');
Loading history...
249
	}
250
251
	/**
252
	 * @param $params
253
	 * @param $content
254
	 * @param $template
255
	 * @param $repeat
256
	 * @return string
257
	 */
258
	public function cmp($params, $content, $template, &$repeat)
259
	{
260
		// only output on the closing tag
261
		if (!$repeat) {
262
			$class = Arr::remove($params, 'class');
263
			$params['content'] = $content;
264
			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

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

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

645
			/** @scrutinizer ignore-call */ 
646
   $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...
646
			if ($assign)
647
				$smarty->assign($assign, $value);
648
			else
649
				return $value;
650
		} else {
651
			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

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

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

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

804
		$smarty->assign(/** @scrutinizer ignore-type */ $assign, $bundle->baseUrl);
Loading history...
805
	}
806
807
	/**
808
	 * @param array $params
809
	 * - [src|url]    string - the js src attribute
810
	 * - [attributes] array  - additional html attributes for the script tag also supports the special `depends`
811
	 *                           attribute enabling this file to depend on an asset bundle
812
	 * - [depends]    string - asset bundle dependencies
813
	 * - [key]        string - key to uniquely identify this file - optional
814
	 */
815
	public static function jsFile($params)
816
	{
817
		$src = Arr::get($params, 'src', Arr::get($params, 'url', null));
818
		if ($src === null) {
819
			trigger_error("jsFile: missing 'url' or 'src' parameter");
820
		}
821
		$attributes = Arr::get($params, 'attributes', []);
822
		$attributes['depends'] = Arr::get($params, 'depends', null);
823
		$key = Arr::get($params, 'key', null);
824
		neon()->view->registerJsFile($src, $attributes, $key);
825
	}
826
827
	/**
828
	 * @param array $params
829
	 * - [src|url]    string - the js src attribute
830
	 * - [attributes] array  - additional html attributes for the script tag also supports the special `depends`
831
	 *                           attribute enabling this file to depend on an asset bundle
832
	 * - [depends]    string - asset bundle dependencies
833
	 * - [key]        string - key to uniquely identify this file - optional
834
	 */
835
	public static function cssFile($params)
836
	{
837
		$src = Arr::remove($params, 'src', Arr::remove($params, 'url', null));
838
		if ($src === null) {
839
			trigger_error("jsFile: missing 'url' or 'src' parameter");
840
		}
841
		$attributes = Arr::remove($params, 'attributes', []);
842
		$key = Arr::remove($params, 'key', null);
843
		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

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