Test Failed
Push — develop ( ff58ad...b8f9b2 )
by steve
13:44 queued 12s
created

SmartySharedPlugins::image()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 26
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 10.75

Importance

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

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

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

625
			/** @scrutinizer ignore-call */ 
626
   $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...
626
			if ($assign)
627
				$smarty->assign($assign, $value);
628
			else
629
				return $value;
630
		} else {
631
			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

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

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

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

784
		$smarty->assign(/** @scrutinizer ignore-type */ $assign, $bundle->baseUrl);
Loading history...
785
	}
786
787
	/**
788
	 * @param array $params
789
	 * - [src|url]    string - the js src attribute
790
	 * - [attributes] array  - additional html attributes for the script tag also supports the special `depends`
791
	 *                           attribute enabling this file to depend on an asset bundle
792
	 * - [depends]    string - asset bundle dependencies
793
	 * - [key]        string - key to uniquely identify this file - optional
794
	 */
795
	public static function jsFile($params)
796
	{
797
		$src = Arr::get($params, 'src', Arr::get($params, 'url', null));
798
		if ($src === null) {
799
			trigger_error("jsFile: missing 'url' or 'src' parameter");
800
		}
801
		$attributes = Arr::get($params, 'attributes', []);
802
		$attributes['depends'] = Arr::get($params, 'depends', null);
803
		$key = Arr::get($params, 'key', null);
804
		neon()->view->registerJsFile($src, $attributes, $key);
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 cssFile($params)
816
	{
817
		$src = Arr::remove($params, 'src', Arr::remove($params, 'url', null));
818
		if ($src === null) {
819
			trigger_error("jsFile: missing 'url' or 'src' parameter");
820
		}
821
		$attributes = Arr::remove($params, 'attributes', []);
822
		$key = Arr::remove($params, 'key', null);
823
		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

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