Completed
Push — master ( e5ab6e...b77dd2 )
by Michal
03:26
created

AssetMacro::processArguments()   B

Complexity

Conditions 5
Paths 16

Size

Total Lines 13
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 5

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 13
ccs 8
cts 8
cp 1
rs 8.8571
cc 5
eloc 8
nc 16
nop 2
crap 5
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\RevisionNotFound;
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
	/**
59
	 * @param string $asset     Asset relative path
60
	 * @param array $args       Other macro arguments
61
	 * @param string $basePath  Base path
62
	 * @param array $config     Macro configuration
63
	 * @return string
64
	 */
65 1
	public static function resolveAssetPath($asset, array $args, $basePath, array $config)
66
	{
67 1
		list($path, $format, $need) = self::processArguments($asset, $args);
68 1
		$wwwDir = Utils::normalizePath($config['wwwDir']);
69 1
		$manifest = self::resolveManifest($path, $need, $wwwDir, $config);
70 1
		$revision = $manifest === NULL ? NULL : self::resolveRevision($manifest, $path, $need, $config);
71
72
		// Is revision only version (query parameter) or full path to asset?
73 1
		$isVersion = $revision === NULL || strspn($revision, "./") === 0;
74
75
		// Check if asset exists
76 1
		if ($config['existCheck'] === TRUE) {
77 1
			$ds = DIRECTORY_SEPARATOR;
78 1
			$filePath = $isVersion ? ($wwwDir . $ds . $path) : ($wwwDir . $ds . Utils::normalizePath($revision));
79 1
			if ( ! file_exists($filePath)) {
80 1
				Utils::throwError(
81 1
				    new AssetNotFoundException(sprintf("Asset '%s' not found.", $filePath)),
82 1
                    $config['missingAsset'],
83 1
                    $need
84
                );
85 1
				return '';
86
			}
87
		}
88
89
		// Format output
90 1
		return self::formatOutput($format, $basePath, $path, $revision, $isVersion);
91
	}
92
93
94
	/**
95
	 * @param string $format         Output format
96
	 * @param string $basePath       Base path
97
	 * @param string $path           Asset relative path
98
	 * @param string|null $revision  Asset revision (version or path to file)
99
	 * @param bool   $isVersion      Is revision only version or full path?
100
	 * @return string
101
	 */
102
	private static function formatOutput($format, $basePath, $path, $revision, $isVersion)
103
	{
104 1
		$revision = $revision ?: 'unknown';
105 1
		$path = $isVersion ? $path : $revision;
106
107 1
		return Strings::replace($format,
108 1
			'/%([^%]+)%/',
109 1
			function ($matches) use ($basePath, $format, $path, $revision, $isVersion) {
110 1
				switch ($matches[1]) {
111 1
					case 'raw':
112 1
						return $revision;
113 1
					case 'basePath':
114 1
						return $basePath;
115 1
					case 'path':
116 1
						return $path;
117 1
					case 'url':
118 1
						return $isVersion ?
119 1
							sprintf("%s/%s?v=%s", $basePath, $path, $revision) :
120 1
							sprintf("%s/%s", $basePath, $path);
121
					default:
122 1
						$msg = sprintf(
123
							"Asset macro: Invalid variable '%s' in format '%s'. " .
124 1
							"Use one of allowed variables: %%raw%%, %%basePath%%, %%path%%, %%url%%.",
125 1
							$matches[1],
126 1
							$format
127
						);
128 1
						throw new InvalidVariableException($msg);
129
				}
130 1
			});
131
	}
132
133
134
	/**
135
	 * @param string $asset  Asset path specified in macro
136
	 * @param array $args    Macro arguments
137
	 * @return array
138
	 */
139 1
	private static function processArguments($asset, array $args)
140
	{
141 1
		$format = isset($args['format']) ? $args['format'] : (isset($args[0]) ? $args[0] : '%url%');
142 1
		$need = isset($args['need']) ? $args['need'] : (isset($args[1]) ? $args[1] : TRUE);
143
144 1
		Validators::assert($asset, 'string', 'path');
145 1
		Validators::assert($format, 'string', 'format');
146 1
		Validators::assert($need, 'bool', 'need');
147
148 1
		$path = Utils::normalizePath($asset);
149
150 1
		return [$path, $format, $need];
151
	}
152
153
154
	/**
155
	 * @param string $asset   Asset path specified in macro
156
	 * @param bool $need      Fail if manifest doesn't exist?
157
	 * @param string $wwwDir  Public www dir
158
	 * @param array $config   Macro configuration
159
	 * @return null|array
160
	 */
161 1
	private static function resolveManifest($asset, $need, $wwwDir, array $config)
162
	{
163 1
		$manifest = $config['manifest'];
164
165
		// Asset revisions specified directly in configuration
166 1
		if (is_array($manifest)) {
167 1
			return $manifest;
168
		}
169
170
		// Path to JSON manifest
171 1
		if (is_string($manifest)) {
172 1
			if ( ! file_exists($manifest)) {
173 1
				Utils::throwError(
174 1
				    new ManifestNotFoundException(sprintf("Manifest file not found: '%s'.", $manifest)),
175 1
                    $config['missingManifest'],
176 1
                    $need
177
                );
178 1
				return NULL;
179
			}
180 1
			return Json::decode(file_get_contents($manifest), Json::FORCE_ARRAY);
181
		}
182
183
		// Autodetect manifest path
184 1
		return self::autodetectManifest($asset, $wwwDir, $need, $config);
185
	}
186
187
188
	/**
189
	 * @param string $asset   Asset path specified in macro
190
	 * @param string $wwwDir  Public www dir
191
	 * @param bool $need      Fail if asset/manifest doesn't exist?
192
	 * @param array $config   Macro configuration
193
	 * @return null|array
194
	 */
195 1
	private static function autodetectManifest($asset, $wwwDir, $need, array $config)
196
	{
197
		// Get asset dir, there begins search manifest
198 1
		$dir = realpath($wwwDir . DIRECTORY_SEPARATOR . dirname($asset));
199 1
		if ($dir === FALSE) {
200
			Utils::throwError(
201
			    new DirNotFoundException(sprintf("Parent dir of asset '%s' not found.", $asset)),
202
                $config['missingAsset'],
203
                $need
204
            );
205
			return NULL;
206
		}
207
208
		// Autodetect manifest
209 1
		$autodetectPaths = $config['autodetect'];
210 1
		while (Strings::startsWith($dir, $wwwDir)) {
211 1
			foreach ($autodetectPaths as $path) {
212 1
				$path = $dir . DIRECTORY_SEPARATOR . $path;
213 1
				if (file_exists($path)) {
214 1
					return Json::decode(file_get_contents($path), Json::FORCE_ARRAY);
215
				}
216
			}
217
218 1
			$dir = dirname($dir); // go up
219
		}
220
221 1
		Utils::throwError(
222 1
		    new ManifestNotFoundException(
223 1
		        sprintf("Manifest not found in: %s.", implode(', ', $autodetectPaths))),
224 1
                $config['missingManifest'],
225 1
                $need
226
            );
227
		return NULL;
228
	}
229
230
231
	/**
232
	 * @param null|array $manifest  Array of revisions
233
	 * @param string $path          Asset path
234
	 * @param bool $need            Fail if revision doesn't exist?
235
	 * @param array $config         Macro configuration
236
	 * @return null|string
237
	 */
238 1
	private static function resolveRevision($manifest, $path, $need, array $config)
239
	{
240 1
		$revision = isset($manifest[$path]) ? $manifest[$path] : NULL;
241
242 1
		if ($revision === NULL) {
243 1
			Utils::throwError(
244 1
			    new RevisionNotFound(sprintf("Revision for asset '%s' not found in manifest.", $path)),
245 1
                $config['missingRevision'],
246 1
                $need
247
            );
248
		}
249
250 1
		return $revision;
251
	}
252
253
}
254