1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace Webrouse\AssetMacro; |
4
|
|
|
|
5
|
|
|
use Latte; |
6
|
|
|
use Latte\Macros\MacroSet; |
7
|
|
|
use Nette\Utils\Json; |
8
|
|
|
use Nette\Utils\Strings; |
9
|
|
|
use Nette\Utils\Validators; |
10
|
|
|
use Webrouse\AssetMacro\Exceptions\AssetNotFoundException; |
11
|
|
|
use Webrouse\AssetMacro\Exceptions\AssetVersionNotFound; |
12
|
|
|
use Webrouse\AssetMacro\Exceptions\DirNotFoundException; |
13
|
|
|
use Webrouse\AssetMacro\Exceptions\InvalidVariableException; |
14
|
|
|
use Webrouse\AssetMacro\Exceptions\ManifestNotFoundException; |
15
|
|
|
|
16
|
|
|
|
17
|
1 |
|
class AssetMacro extends MacroSet |
18
|
|
|
{ |
19
|
|
|
|
20
|
|
|
const CONFIG_PROVIDER = 'assetMacroConfig'; |
21
|
|
|
|
22
|
|
|
|
23
|
|
|
/** |
24
|
|
|
* @param Latte\Compiler $compiler |
25
|
|
|
*/ |
26
|
1 |
|
public static function install(Latte\Compiler $compiler) |
27
|
|
|
{ |
28
|
1 |
|
$me = new self($compiler); |
29
|
1 |
|
$me->addMacro('asset', [$me, 'macroAsset']); |
30
|
1 |
|
} |
31
|
|
|
|
32
|
|
|
|
33
|
|
|
/** |
34
|
|
|
* @param Latte\MacroNode $node |
35
|
|
|
* @param Latte\PhpWriter $writer |
36
|
|
|
* @return string |
37
|
|
|
* @throws Latte\CompileException |
38
|
|
|
*/ |
39
|
1 |
|
public function macroAsset(Latte\MacroNode $node, Latte\PhpWriter $writer) |
40
|
|
|
{ |
41
|
1 |
|
$args = trim($node->args); |
42
|
|
|
|
43
|
|
|
// Validate arguments count |
44
|
1 |
|
$argsCount = $args === '' ? 0 : (substr_count($args, ',') + 1); |
45
|
1 |
|
if ($argsCount === 0) { |
46
|
1 |
|
throw new Latte\CompileException("Asset macro requires at least one argument."); |
47
|
|
|
} |
48
|
1 |
|
if ($argsCount > 3) { |
49
|
1 |
|
throw new Latte\CompileException("Asset macro must have no more than 3 arguments."); |
50
|
|
|
} |
51
|
|
|
|
52
|
1 |
|
return $writer->write( |
53
|
|
|
'echo %escape(' . self::class . '::resolveAssetPath(' . |
54
|
1 |
|
'%node.word, %node.array, $basePath, $template->global->' . self::CONFIG_PROVIDER . '))'); |
55
|
|
|
} |
56
|
|
|
|
57
|
|
|
/** |
58
|
|
|
* @param string $asset Asset relative path |
59
|
|
|
* @param array $args Other macro arguments |
60
|
|
|
* @param string $basePath Base path |
61
|
|
|
* @param array $config Macro configuration |
62
|
|
|
* @return string |
63
|
|
|
*/ |
64
|
1 |
|
public static function resolveAssetPath($asset, array $args, $basePath, array $config) { |
65
|
1 |
|
list($path, $format, $need) = self::processArguments($asset, $args); |
66
|
1 |
|
$wwwDir = Utils::normalizePath($config['wwwDir']); |
67
|
1 |
|
$manifest = self::resolveManifest($path, $need, $wwwDir, $config); |
68
|
1 |
|
$revision = $manifest === NULL ? NULL : self::resolveRevision($manifest, $path, $need, $config); |
69
|
|
|
|
70
|
|
|
// Is revision only version (query parameter) or full path to asset? |
71
|
1 |
|
$isVersion = $revision === NULL || strspn($revision,"./") === 0; |
72
|
|
|
|
73
|
|
|
// Check if asset exists |
74
|
1 |
|
if ($config['existCheck'] === TRUE) { |
75
|
1 |
|
$ds = DIRECTORY_SEPARATOR; |
76
|
1 |
|
$filePath = $isVersion ? ($wwwDir . $ds . $path) : ($wwwDir . $ds . Utils::normalizePath($revision)); |
77
|
1 |
View Code Duplication |
if (! file_exists($filePath)) { |
|
|
|
|
78
|
1 |
|
$msg = sprintf("Asset '%s' not found.", $filePath); |
79
|
1 |
|
Utils::throwError(new AssetNotFoundException($msg), $config['missingAsset'], $need); |
80
|
1 |
|
return ''; |
81
|
|
|
} |
82
|
|
|
} |
83
|
|
|
|
84
|
|
|
// Format output |
85
|
1 |
|
return self::formatOutput($format, $basePath, $path, $revision, $isVersion); |
86
|
|
|
} |
87
|
|
|
|
88
|
|
|
/** |
89
|
|
|
* @param string $format Output format |
90
|
|
|
* @param string $basePath Base path |
91
|
|
|
* @param string $path Asset relative path |
92
|
|
|
* @param string|null $revision Asset revision (version or path to file) |
93
|
|
|
* @param bool $isVersion Is revision only version or full path? |
94
|
|
|
* @return string |
95
|
|
|
*/ |
96
|
|
|
private static function formatOutput($format, $basePath, $path, $revision, $isVersion) |
97
|
|
|
{ |
98
|
1 |
|
$revision = $revision ?: 'unknown'; |
99
|
1 |
|
$path = $isVersion ? |
100
|
1 |
|
sprintf("%s?v=%s", $path, $revision) : |
101
|
1 |
|
$revision; |
102
|
|
|
|
103
|
1 |
|
return Strings::replace($format, |
104
|
1 |
|
'/%([^%]+)%/', |
105
|
1 |
|
function ($matches) use ($basePath, $format, $path, $revision) { |
106
|
1 |
|
switch ($matches[1]) { |
107
|
1 |
|
case 'raw': |
108
|
|
|
return $revision; |
109
|
1 |
|
case 'basePath': |
110
|
1 |
|
return $basePath; |
111
|
1 |
|
case 'path': |
112
|
1 |
|
return $path; |
113
|
1 |
|
case 'url': |
114
|
1 |
|
return sprintf("%s/%s", $basePath, $path); |
115
|
|
|
default: |
116
|
1 |
|
$msg = sprintf( |
117
|
|
|
"Asset macro: Invalid variable '%s' in format '%s'. " . |
118
|
1 |
|
"Use one of allowed variables: %%raw%%, %%basePath%%, %%path%%, %%url%%.", |
119
|
1 |
|
$matches[1], |
120
|
1 |
|
$format |
121
|
|
|
); |
122
|
1 |
|
throw new InvalidVariableException($msg); |
123
|
|
|
} |
124
|
1 |
|
}); |
125
|
|
|
} |
126
|
|
|
|
127
|
|
|
/** |
128
|
|
|
* @param string $asset Asset path specified in macro |
129
|
|
|
* @param array $args Macro arguments |
130
|
|
|
* @return array |
131
|
|
|
*/ |
132
|
1 |
|
private static function processArguments($asset, array $args) |
133
|
|
|
{ |
134
|
1 |
|
$format = isset($args['format']) ? $args['format'] : (isset($args[0]) ? $args[0] : '%url%'); |
135
|
1 |
|
$need = isset($args['need']) ? $args['need'] : (isset($args[1]) ? $args[1] : TRUE); |
136
|
|
|
|
137
|
1 |
|
Validators::assert($asset, 'string', 'path'); |
138
|
1 |
|
Validators::assert($format, 'string', 'format'); |
139
|
1 |
|
Validators::assert($need, 'bool', 'need'); |
140
|
|
|
|
141
|
1 |
|
$path = Utils::normalizePath($asset); |
142
|
|
|
|
143
|
1 |
|
return [$path, $format, $need]; |
144
|
|
|
} |
145
|
|
|
|
146
|
|
|
/** |
147
|
|
|
* @param string $asset Asset path specified in macro |
148
|
|
|
* @param bool $need Fail if manifest doesn't exist? |
149
|
|
|
* @param string $wwwDir Public www dir |
150
|
|
|
* @param array $config Macro configuration |
151
|
|
|
* @return null|array |
152
|
|
|
*/ |
153
|
1 |
|
private static function resolveManifest($asset, $need, $wwwDir, array $config) { |
154
|
1 |
|
$manifest = $config['manifest']; |
155
|
|
|
|
156
|
|
|
// Asset revisions specified directly in configuration |
157
|
1 |
|
if (is_array($manifest)) { |
158
|
|
|
return $manifest; |
159
|
|
|
} |
160
|
|
|
|
161
|
|
|
// Path to JSON manifest |
162
|
1 |
|
if (is_string($manifest)) { |
163
|
1 |
View Code Duplication |
if ( ! file_exists($manifest)) { |
|
|
|
|
164
|
1 |
|
$msg = sprintf("Manifest file not found: '%s'.", $manifest); |
165
|
1 |
|
Utils::throwError(new ManifestNotFoundException($msg), $config['missingManifest'], $need); |
166
|
1 |
|
return NULL; |
167
|
|
|
} |
168
|
1 |
|
return Json::decode(file_get_contents($manifest), Json::FORCE_ARRAY); |
169
|
|
|
} |
170
|
|
|
|
171
|
|
|
// Autodetect manifest path |
172
|
1 |
|
return self::autodetectManifest($asset, $wwwDir, $need, $config); |
173
|
|
|
} |
174
|
|
|
|
175
|
|
|
/** |
176
|
|
|
* @param string $asset Asset path specified in macro |
177
|
|
|
* @param string $wwwDir Public www dir |
178
|
|
|
* @param bool $need Fail if asset/manifest doesn't exist? |
179
|
|
|
* @param array $config Macro configuration |
180
|
|
|
* @return null|array |
181
|
|
|
*/ |
182
|
1 |
|
private static function autodetectManifest($asset, $wwwDir, $need, array $config) { |
183
|
|
|
// Get asset dir, there begins search manifest |
184
|
1 |
|
$dir = realpath($wwwDir . DIRECTORY_SEPARATOR . dirname($asset)); |
185
|
1 |
|
if ($dir === FALSE) { |
186
|
|
|
$msg = sprintf("Parent dir of asset '%s' not found.", $asset); |
187
|
|
|
Utils::throwError(new DirNotFoundException($msg), $config['missingAsset'], $need); |
188
|
|
|
return NULL; |
189
|
|
|
} |
190
|
|
|
|
191
|
|
|
// Autodetect manifest |
192
|
1 |
|
$autodetectPaths = $config['autodetect']; |
193
|
1 |
|
while (Strings::startsWith($dir, $wwwDir)) { |
194
|
1 |
|
foreach ($autodetectPaths as $path) { |
195
|
1 |
|
$path = $dir . DIRECTORY_SEPARATOR . $path; |
196
|
1 |
|
if (file_exists($path)) { |
197
|
1 |
|
return Json::decode(file_get_contents($path), Json::FORCE_ARRAY); |
198
|
|
|
} |
199
|
|
|
} |
200
|
|
|
|
201
|
1 |
|
$dir = dirname($dir); // go up |
202
|
|
|
} |
203
|
|
|
|
204
|
1 |
|
$msg = sprintf("Manifest not found in: %s.", implode(', ', $autodetectPaths)); |
205
|
1 |
|
Utils::throwError(new ManifestNotFoundException($msg), $config['missingManifest'], $need); |
206
|
|
|
return NULL; |
207
|
|
|
} |
208
|
|
|
|
209
|
|
|
/** |
210
|
|
|
* @param null|array $manifest Array of revisions |
211
|
|
|
* @param string $path Asset path |
212
|
|
|
* @param bool $need Fail if revision doesn't exist? |
213
|
|
|
* @param array $config Macro configuration |
214
|
|
|
* @return null|string |
215
|
|
|
*/ |
216
|
1 |
|
private static function resolveRevision($manifest, $path, $need, array $config) { |
217
|
1 |
|
$revision = isset($manifest[$path]) ? $manifest[$path] : NULL; |
218
|
|
|
|
219
|
1 |
|
if ($revision === NULL) { |
220
|
1 |
|
$msg = sprintf("Revision for asset '%s' not found in manifest.", $path); |
221
|
1 |
|
Utils::throwError(new AssetVersionNotFound($msg), $config['missingRevision'], $need); |
222
|
|
|
} |
223
|
|
|
|
224
|
1 |
|
return $revision; |
225
|
|
|
} |
226
|
|
|
|
227
|
|
|
} |
228
|
|
|
|
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.