Completed
Push — master ( 8bc3a6...c5a72f )
by Sang
75:03 queued 63:15
created

Assets::attributeElement()   B

Complexity

Conditions 8
Paths 6

Size

Total Lines 19

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 19
rs 8.4444
c 0
b 0
f 0
cc 8
nc 6
nop 2
1
<?php
2
3
namespace Botble\Assets;
4
5
use Illuminate\Config\Repository;
6
use Illuminate\Support\HtmlString;
7
use Illuminate\Contracts\Routing\UrlGenerator;
8
9
/**
10
 * Class Assets.
11
 *
12
 * @since 22/07/2015 11:23 PM
13
 */
14
class Assets
15
{
16
    /**
17
     * @var Repository
18
     */
19
    protected $config;
20
21
    /**
22
     * The URL generator instance.
23
     *
24
     * @var \Illuminate\Contracts\Routing\UrlGenerator
25
     */
26
    protected $url;
27
28
    /**
29
     * @var array
30
     */
31
    protected $scripts = [];
32
33
    /**
34
     * @var array
35
     */
36
    protected $styles = [];
37
38
    /**
39
     * @var array
40
     */
41
    protected $appendedScripts = [
42
        'header' => [],
43
        'footer' => [],
44
    ];
45
46
    /**
47
     * @var array
48
     */
49
    protected $appendedStyles = [];
50
51
    /**
52
     * @var string
53
     */
54
    protected $build = '';
55
56
    const ASSETS_SCRIPT_POSITION_HEADER = 'header';
57
58
    const ASSETS_SCRIPT_POSITION_FOOTER = 'footer';
59
60
    /**
61
     * Assets constructor.
62
     *
63
     * @param  Repository $config
64
     * @param UrlGenerator $urlGenerator
65
     */
66
    public function __construct(Repository $config, UrlGenerator $urlGenerator)
67
    {
68
        $this->config = $config->get('assets');
69
70
        $this->scripts = $this->config['scripts'];
71
72
        $this->styles = $this->config['styles'];
73
74
        $this->url = $urlGenerator;
75
    }
76
77
    /**
78
     * Add scripts to current module.
79
     *
80
     * @param  array $assets
81
     * @return $this
82
     */
83
    public function addScripts($assets)
84
    {
85
        if (!is_array($assets)) {
86
            $assets = [$assets];
87
        }
88
89
        $this->scripts = array_merge($this->scripts, $assets);
90
91
        return $this;
92
    }
93
94
    /**
95
     * Add Css to current module.
96
     *
97
     * @param  array $assets
98
     * @return $this
99
     */
100
    public function addStyles($assets)
101
    {
102
        if (!is_array($assets)) {
103
            $assets = [$assets];
104
        }
105
106
        $this->styles = array_merge($this->styles, $assets);
107
108
        return $this;
109
    }
110
111
    /**
112
     * Add styles directly.
113
     *
114
     * @param  array|string $assets
115
     * @return $this
116
     */
117
    public function addStylesDirectly($assets)
118
    {
119
        if (!is_array($assets)) {
120
            $assets = [$assets];
121
        }
122
123 View Code Duplication
        foreach ($assets as &$item) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
124
            if (!in_array($item, $this->appendedStyles)) {
125
                $this->appendedStyles[] = [
126
                    'src'        => $item,
127
                    'attributes' => [],
128
                ];
129
            }
130
        }
131
132
        return $this;
133
    }
134
135
    /**
136
     * Add scripts directly.
137
     *
138
     * @param  string|array $assets
139
     * @param  string $location
140
     * @return $this
141
     */
142
    public function addScriptsDirectly($assets, $location = self::ASSETS_SCRIPT_POSITION_FOOTER)
143
    {
144
        if (!is_array($assets)) {
145
            $assets = [$assets];
146
        }
147
148 View Code Duplication
        foreach ($assets as &$item) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
149
            if (!in_array($item, $this->appendedScripts[$location])) {
150
                $this->appendedScripts[$location][] = [
151
                    'src'        => $item,
152
                    'attributes' => [],
153
                ];
154
            }
155
        }
156
157
        return $this;
158
    }
159
160
    /**
161
     * Remove Css to current module.
162
     *
163
     * @param  array $assets
164
     * @return $this
165
     */
166 View Code Duplication
    public function removeStyles($assets)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
167
    {
168
        if (!is_array($assets)) {
169
            $assets = [$assets];
170
        }
171
172
        foreach ($assets as $rem) {
173
            array_forget($this->styles, array_search($rem, $this->styles));
174
        }
175
176
        return $this;
177
    }
178
179
    /**
180
     * Add scripts.
181
     *
182
     * @param  array $assets
183
     * @return $this
184
     */
185 View Code Duplication
    public function removeScripts($assets)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
186
    {
187
        if (!is_array($assets)) {
188
            $assets = [$assets];
189
        }
190
191
        foreach ($assets as $rem) {
192
            array_forget($this->scripts, array_search($rem, $this->scripts));
193
        }
194
195
        return $this;
196
    }
197
198
    /**
199
     * Get All scripts in current module.
200
     *
201
     * @param  string $location `header` or `footer`
202
     * @return array
203
     */
204
    public function getScripts($location = null)
205
    {
206
        $scripts = [];
207
208
        $this->scripts = array_unique($this->scripts);
209
210
        foreach ($this->scripts as $script) {
211
            $configName = 'resources.scripts.' . $script;
212
213
            if (array_has($this->config, $configName)) {
214
                if (!empty($location) && $location !== array_get($this->config, $configName . '.location')) {
215
                    continue; // Skip assets that don't match this location
216
                }
217
218
                $scripts = array_merge($scripts, $this->getScriptItem($location, $configName, $script));
219
            }
220
        }
221
222
        if (isset($this->appendedScripts[$location])) {
223
            $scripts = array_merge($scripts, $this->appendedScripts[$location]);
224
        }
225
226
        return $scripts;
227
    }
228
229
    /**
230
     * Get All CSS in current module.
231
     *
232
     * @param  array $lastStyles Append last CSS to current module
233
     * @return array
234
     */
235
    public function getStyles($lastStyles = [])
236
    {
237
        $styles = [];
238
        if (!empty($lastStyles)) {
239
            $this->styles = array_merge($this->styles, $lastStyles);
240
        }
241
242
        $this->styles = array_unique($this->styles);
243
244
        foreach ($this->styles as $style) {
245
            $configName = 'resources.styles.' . $style;
246
247
            if (array_has($this->config, $configName)) {
248
                $src = array_get($this->config, $configName . '.src.local');
249
250
                $attributes = array_get($this->config, $configName . '.attributes', []);
251
252 View Code Duplication
                if (array_get($this->config, $configName . '.use_cdn') && !$this->config['offline']) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
253
                    $src = array_get($this->config, $configName . '.src.cdn');
254
255
                    $attributes = [];
256
                }
257
258
                foreach ((array)$src as $s) {
259
                    $styles[] = [
260
                        'src'        => $s,
261
                        'attributes' => $attributes,
262
                    ];
263
                }
264
            }
265
        }
266
267
        return array_merge($styles, $this->appendedStyles);
268
    }
269
270
    /**
271
     * Convert script to html.
272
     *
273
     * @param  string $name
274
     * @return  string|null
275
     */
276
    public function scriptToHtml($name)
277
    {
278
        return $this->itemToHtml($name, 'script');
279
    }
280
281
    /**
282
     * Convert style to html.
283
     *
284
     * @param  string $name
285
     */
286
    public function styleToHtml($name)
287
    {
288
        return $this->itemToHtml($name, 'style');
289
    }
290
291
    /**
292
     * Render assets to header.
293
     *
294
     * @param array $lastStyles
295
     * @return string
296
     * @throws \Throwable
297
     */
298
    public function renderHeader($lastStyles = [])
299
    {
300
        $styles = $this->getStyles($lastStyles);
301
302
        $headScripts = $this->getScripts(self::ASSETS_SCRIPT_POSITION_HEADER);
303
304
        return view('assets::header', compact('styles', 'headScripts'))->render();
0 ignored issues
show
Bug introduced by
The method render does only exist in Illuminate\View\View, but not in Illuminate\Contracts\View\Factory.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
305
    }
306
307
    /**
308
     * Render assets to footer.
309
     *
310
     * @return string
311
     * @throws \Throwable
312
     */
313
    public function renderFooter()
314
    {
315
        $bodyScripts = $this->getScripts(self::ASSETS_SCRIPT_POSITION_FOOTER);
316
317
        return view('assets::footer', compact('bodyScripts'))->render();
0 ignored issues
show
Bug introduced by
The method render does only exist in Illuminate\View\View, but not in Illuminate\Contracts\View\Factory.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
318
    }
319
320
    /**
321
     * Get script item.
322
     *
323
     * @param string $location
324
     * @param string $configName
325
     * @param string $script
326
     * @return array
327
     */
328
    protected function getScriptItem($location, $configName, $script)
329
    {
330
        $scripts = [];
331
332
        $src = array_get($this->config, $configName . '.src.local');
333
334
        $cdn = false;
335
336
        $attributes = array_get($this->config, $configName . '.attributes', []);
337
338 View Code Duplication
        if (array_get($this->config, $configName . '.use_cdn') && !$this->config['offline']) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
339
            $src = array_get($this->config, $configName . '.src.cdn');
340
341
            $cdn = true;
342
343
            $attributes = [];
344
        }
345
346
        if (!is_array($src)) {
347
            $scripts[] = [
348
                'src'        => $src,
349
                'attributes' => $attributes,
350
            ];
351
        } else {
352
            foreach ($src as $s) {
353
                $scripts[] = [
354
                    'src'        => $s,
355
                    'attributes' => $attributes,
356
                ];
357
            }
358
        }
359
360
        if (empty($src) &&
361
            $cdn &&
362
            $location === self::ASSETS_SCRIPT_POSITION_HEADER &&
363
            array_has($this->config, $configName . '.fallback')) {
364
            $scripts[] = $this->getFallbackScript($src, $configName);
365
        }
366
367
        if (array_get($this->config, $configName . '.include_style')) {
368
            $this->addStyles([$script]);
369
        }
370
371
        return $scripts;
372
    }
373
374
    /**
375
     * Fallback to local script if CDN fails.
376
     *
377
     * @param  string $src
378
     * @param  string $configName
379
     * @return array
380
     */
381
    protected function getFallbackScript($src, $configName)
382
    {
383
        return [
384
            'src'         => $src,
385
            'fallback'    => array_get($this->config, $configName . '.fallback'),
386
            'fallbackURL' => array_get($this->config, $configName . '.src.local'),
387
        ];
388
    }
389
390
    /**
391
     * Convert item to html.
392
     *
393
     * @param  string $name
394
     * @param  string $type
395
     * @return null|string
396
     */
397
    protected function itemToHtml($name, $type = 'style')
398
    {
399
        $html = '';
400
401
        if (!in_array($type, ['style', 'script'])) {
402
            return $html;
403
        }
404
405
        $config = 'resources.styles.' . $name;
406
407
        if ($type === 'script') {
408
            $config = 'resources.scripts.' . $name;
409
        }
410
411
        if (array_has($this->config, $config)) {
412
            $src = array_get($this->config, $config . '.src.local');
413
414 View Code Duplication
            if (array_get($this->config, $config . '.use_cdn') && !$this->config['offline']) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
415
                $src = array_get($this->config, $config . '.src.cdn');
416
            }
417
418
            if (!is_array($src)) {
419
                $src = [$src];
420
            }
421
422
            foreach ($src as $item) {
423
                $html .= $this->{$type}($item, ['class' => 'hidden'])->toHtml();
424
            }
425
        }
426
427
        return $html;
428
    }
429
430
    /**
431
     * @return string
432
     */
433
    public function getBuildVersion()
434
    {
435
        return $this->build = $this->config['enable_version'] ? '?v=' . $this->config['version'] : '';
436
    }
437
438
    /**
439
     * Generate a link to a JavaScript file.
440
     *
441
     * @param string $url
442
     * @param array $attributes
443
     * @param bool $secure
444
     *
445
     * @return \Illuminate\Support\HtmlString
446
     */
447
    public function script($url, $attributes = [], $secure = null)
448
    {
449
        $attributes['src'] = $this->url->asset($url, $secure);
450
451
        return $this->toHtmlString('<script' . $this->attributes($attributes) . '></script>');
452
    }
453
454
    /**
455
     * Generate a link to a CSS file.
456
     *
457
     * @param string $url
458
     * @param array $attributes
459
     * @param bool $secure
460
     *
461
     * @return \Illuminate\Support\HtmlString
462
     */
463
    public function style($url, $attributes = [], $secure = null)
464
    {
465
        $defaults = ['media' => 'all', 'type' => 'text/css', 'rel' => 'stylesheet'];
466
467
        $attributes = array_merge($defaults, $attributes);
468
469
        $attributes['href'] = $this->url->asset($url, $secure);
470
471
        return $this->toHtmlString('<link' . $this->attributes($attributes) . '>');
472
    }
473
474
    /**
475
     * Transform the string to an Html serializable object.
476
     *
477
     * @param $html
478
     *
479
     * @return \Illuminate\Support\HtmlString
480
     */
481
    protected function toHtmlString($html)
482
    {
483
        return new HtmlString($html);
484
    }
485
486
    /**
487
     * Build an HTML attribute string from an array.
488
     *
489
     * @param array $attributes
490
     *
491
     * @return string
492
     */
493
    public function attributes($attributes)
494
    {
495
        $html = [];
496
497
        foreach ((array)$attributes as $key => $value) {
498
            $element = $this->attributeElement($key, $value);
499
500
            if (!empty($element)) {
501
                $html[] = $element;
502
            }
503
        }
504
505
        return count($html) > 0 ? ' ' . implode(' ', $html) : '';
506
    }
507
508
    /**
509
     * Build a single attribute element.
510
     *
511
     * @param string $key
512
     * @param string $value
513
     *
514
     * @return string
515
     */
516
    protected function attributeElement($key, $value)
517
    {
518
        if (is_numeric($key)) {
519
            return $value;
520
        }
521
522
        // Treat boolean attributes as HTML properties
523
        if (is_bool($value) && $key !== 'value') {
524
            return $value ? $key : '';
525
        }
526
527
        if (is_array($value) && $key === 'class') {
528
            return 'class="' . implode(' ', $value) . '"';
529
        }
530
531
        if (!empty($value)) {
532
            return $key . '="' . e($value, false) . '"';
533
        }
534
    }
535
}
536