Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
66.67% covered (warning)
66.67%
62 / 93
75.00% covered (warning)
75.00%
3 / 4
CRAP
0.00% covered (danger)
0.00%
0 / 1
AssetFunction
66.67% covered (warning)
66.67%
62 / 93
75.00% covered (warning)
75.00%
3 / 4
80.37
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isIgnoreCaching
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
4
 getFunctionName
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 functionBodyImpl
62.20% covered (warning)
62.20%
51 / 82
0.00% covered (danger)
0.00%
0 / 1
74.44
1<?php
2
3declare(strict_types=1);
4
5namespace PeServer\Core\Mvc\Template\Plugin;
6
7use PeServer\Core\Environment;
8use PeServer\Core\IO\IOUtility;
9use PeServer\Core\IO\Path;
10use PeServer\Core\IO\File;
11use PeServer\Core\Collection\Arr;
12use PeServer\Core\Text;
13use PeServer\Core\TypeUtility;
14use PeServer\Core\Html\HtmlDocument;
15use PeServer\Core\Html\HtmlTagElement;
16use PeServer\Core\Throws\TemplateException;
17use PeServer\Core\Mvc\Template\Plugin\TemplateFunctionBase;
18use PeServer\Core\Mvc\Template\Plugin\TemplatePluginArgument;
19use PeServer\Core\Web\UrlUtility;
20
21/**
22 * 指定されたリソースをHTMLとして読み込む。
23 *
24 *  * 本番環境であればミニファイされたリソースを読もうとする
25 *  * リビジョンをキャッシュバスターとして適用する
26 *
27 * $params
28 *  * file: 対象リソース
29 *  * auto_size: true/false trueの場合に実イメージサイズを読み込む(未指定は false)。
30 *  * include: true/false trueの場合にファイルの中身を使用する(結構適当)(未指定は false)。
31 *  * その他: 全部設定される
32 */
33class AssetFunction extends TemplateFunctionBase
34{
35    public function __construct(TemplatePluginArgument $argument)
36    {
37        parent::__construct($argument);
38    }
39
40    #region function
41
42    /**
43     * キャッシュ考慮不要な(HTTP)パスか。
44     *
45     * @param string $path
46     * @return bool
47     */
48    private function isIgnoreCaching(string $path): bool
49    {
50        $isExternal =
51            Text::startsWith($path, '//', false)
52            ||
53            Text::startsWith($path, 'https://', false)
54            ||
55            Text::startsWith($path, 'http://', false)
56            ||
57            Text::contains($path, '?', false);
58
59        return $isExternal;
60    }
61
62    #endregion
63
64    #region TemplateFunctionBase
65
66    public function getFunctionName(): string
67    {
68        return 'asset';
69    }
70
71    /**
72     * @SuppressWarnings(PHPMD.CyclomaticComplexity)
73     */
74    protected function functionBodyImpl(): string
75    {
76        /** @var string */
77        $sourcePath = $this->params['file'] ?? Text::EMPTY;
78        if (Text::isNullOrEmpty($sourcePath)) {
79            return Text::EMPTY;
80        }
81
82        $isProduction = $this->argument->environment->isProduction();
83
84        $fileExtension = Path::getFileExtension($sourcePath);
85        $extension = Text::toLower($fileExtension);
86
87        $ignoreAsset = $this->isIgnoreCaching($sourcePath);
88
89        $resourcePath = $sourcePath;
90        if (!$ignoreAsset) {
91            if ($isProduction) {
92                $minimalPath = Text::trim(Path::setEnvironmentName($sourcePath, 'min'));
93                $physicalPath = Path::combine($this->argument->programContext->publicDirectory, $minimalPath);
94                if (IOUtility::exists($physicalPath)) {
95                    $resourcePath = Text::replace($minimalPath, "\\", '/');
96                }
97            }
98
99            $resourcePath .= '?' . $this->argument->environment->getRevision();
100        }
101
102        $dom = new HtmlDocument();
103        if (!$isProduction) {
104            $dom->addComment(Text::dump($this->params));
105        }
106
107        $autoSize = TypeUtility::parseBoolean($this->params['auto_size'] ?? false);
108        $include = TypeUtility::parseBoolean($this->params['include'] ?? false);
109
110        $filePath = Path::combine($this->argument->rootDirectoryPath, $sourcePath);
111        if (($autoSize || $include) || !FIle::exists($filePath)) {
112            // @phpstan-ignore-next-line nullは全取得だからOK
113            foreach ($this->argument->engine->getTemplateDir(null) as $dir) {
114                $path = Path::combine($dir, $sourcePath);
115                if (IOUtility::exists($path)) {
116                    $filePath = $path;
117                    break;
118                }
119            }
120        }
121
122        $skipAttributes = [
123            'file',
124            'auto_size',
125            'include',
126        ];
127        /** @var HtmlTagElement|null */
128        $element = null;
129
130        if (Arr::tryGet($this->params, 'rel', $relValue)) {
131            switch ($relValue) {
132                case 'icon':
133                    $element = $dom->addTagElement('link');
134                    $element->setAttribute('href', $resourcePath);
135                    $skipAttributes = array_merge($skipAttributes, ['href']);
136                    break;
137            }
138        }
139
140        if ($element === null) {
141            switch ($extension) {
142                case 'css':
143                    if ($include) {
144                        $element = $dom->addTagElement('style');
145
146                        $content = File::readContent($filePath);
147                        $element->addText($content->toString());
148                    } else {
149                        $element = $dom->addTagElement('link');
150
151                        $element->setAttribute('rel', 'stylesheet');
152                        $element->setAttribute('href', $resourcePath);
153                        $skipAttributes = array_merge($skipAttributes, ['rel', 'href']);
154                    }
155                    break;
156
157                case 'js':
158                    $element = $dom->addTagElement('script');
159
160                    if ($include) {
161                        $content = File::readContent($filePath);
162                        $element->addText($content->toString());
163                    } else {
164                        $element->setAttribute('src', $resourcePath);
165                        $skipAttributes = array_merge($skipAttributes, ['src']);
166                    }
167                    break;
168
169                case 'png':
170                case 'jpeg':
171                case 'jpg':
172                    $element = $dom->addTagElement('img');
173
174                    $element->setAttribute('src', $resourcePath);
175                    $skipAttributes = array_merge($skipAttributes, ['src']);
176
177                    if (!$ignoreAsset && ($autoSize || $include)) {
178                        $imageSize = getimagesize($filePath);
179                        if ($imageSize !== false) {
180                            $element->setAttribute('width', strval($imageSize[0]));
181                            $element->setAttribute('height', strval($imageSize[1]));
182                            $skipAttributes = array_merge($skipAttributes, ['width', 'height']);
183
184                            if ($include) {
185                                $content = File::readContent($filePath);
186                                $base64 = $content->toBase64();
187                                $inline = 'data:' . $imageSize['mime'] . ';base64,' . $base64;
188                                $element->setAttribute('src', $inline);
189                            }
190                        }
191                    }
192                    break;
193
194                default:
195                    throw new TemplateException($resourcePath);
196            }
197        }
198
199        foreach ($this->params as $key => $value) {
200            if (Arr::containsValue($skipAttributes, $key)) {
201                continue;
202            }
203            $element->setAttribute($key, $value);
204        }
205
206        return $dom->build();
207    }
208
209    #endregion
210}