Completed
Push — master ( 367234...fa945f )
by Michal
02:29
created

AssetMacro::processArguments()   B

Complexity

Conditions 5
Paths 16

Size

Total Lines 11
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 5

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 11
ccs 7
cts 7
cp 1
rs 8.8571
cc 5
eloc 7
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\AssetVersionNotFound;
11
use Webrouse\AssetMacro\Exceptions\DirNotFoundException;
12
use Webrouse\AssetMacro\Exceptions\FileNotFoundException;
13
use Webrouse\AssetMacro\Exceptions\InvalidArgumentException;
14
use Webrouse\AssetMacro\Exceptions\InvalidVariableException;
15
16
17 1
class AssetMacro extends MacroSet
18
{
19
20
	const VERSIONS_AUTODETECT = NULL;
21
22
	const CONFIG_PROVIDER = 'assetMacroConfig';
23
24
25
	/**
26
	 * @param Latte\Compiler $compiler
27
	 */
28 1
	public static function install(Latte\Compiler $compiler)
29
	{
30 1
		$me = new self($compiler);
31 1
		$me->addMacro('asset', [$me, 'macroAsset']);
32 1
	}
33
34
35
	/**
36
	 * @param Latte\MacroNode $node
37
	 * @param Latte\PhpWriter $writer
38
	 * @return string
39
	 * @throws Latte\CompileException
40
	 */
41 1
	public function macroAsset(Latte\MacroNode $node, Latte\PhpWriter $writer)
42
	{
43 1
		$args = trim($node->args);
44
45
		// Validate arguments count
46 1
		$argsCount = $args === '' ? 0 : (substr_count($args, ',') + 1);
47 1
		if ($argsCount === 0) {
48 1
			throw new Latte\CompileException("Asset macro requires at least one argument.");
49
		}
50 1
		if ($argsCount > 3) {
51 1
			throw new Latte\CompileException("Asset macro must have no more than 3 arguments.");
52
		}
53
54 1
		return $writer->write(
55
			'echo %escape(' . self::class . '::generateAssetPath(' .
56 1
			'%node.word, %node.array, $basePath, $template->global->' . self::CONFIG_PROVIDER . '))');
57
	}
58
59
60
	/**
61
	 * @param string $path
62
	 * @param array $args
63
	 * @param string $basePath
64
	 * @param array $config
65
	 * @return string
66
	 */
67 1
	public static function generateAssetPath($path, array $args, $basePath, $config)
68
	{
69 1
		list($relativePath, $format, $needed) = self::processArguments($path, $args);
70 1
		list($relativePath, $absolutePath, $wwwDir) = self::processPaths($relativePath, $needed, $config);
71 1
		if ($absolutePath === FALSE) {
72 1
			return '';
73
		}
74
75
		// Get asset version
76 1
		$versions = $config['versions'] === self::VERSIONS_AUTODETECT ?
77 1
			self::autodetectVersions($absolutePath, $wwwDir, $config['autodetectPaths']) :
78 1
			$config['versions'];
79 1
		$version = self::getAssetVersion($versions, $absolutePath, $config);
80
81 1
		return self::formatOutput($format, $basePath, $relativePath, $version);
82
	}
83
84
85
	/**
86
	 * Generate output according given format
87
	 * @param string $format
88
	 * @param string $basePath
89
	 * @param string $relativePath
90
	 * @param string $version
91
	 * @return string
92
	 */
93
	private static function formatOutput($format, $basePath, $relativePath, $version)
94
	{
95 1
		return Strings::replace($format,
96 1
			'/%([^%]+)%/',
97 1
			function ($matches) use ($basePath, $format, $relativePath, $version) {
98 1
				switch ($matches[1]) {
99 1
					case 'url':
100 1
						return sprintf("%s/%s?v=%s", $basePath, $relativePath, $version);
101 1
					case 'version':
102 1
						return $version;
103 1
					case 'basePath':
104 1
						return $basePath;
105 1
					case 'dir':
106 1
						return dirname($relativePath);
107 1
					case 'file':
108 1
						return basename($relativePath);
109
					default:
110 1
						$msg = sprintf(
111
							"Asset macro: Invalid variable '%s' in format '%s'. " .
112 1
							"Use one of allowed variables: %%url%%, %%version%%, %%basePath%%, %%dir%%, %%file%%.",
113 1
							$matches[1],
114 1
							$format
115
						);
116 1
						throw new InvalidVariableException($msg);
117
				}
118 1
			});
119
	}
120
121
122
	/**
123
	 * @param string $path
124
	 * @param array $args
125
	 * @return array
126
	 */
127 1
	private static function processArguments($path, array $args)
128
	{
129 1
		$format = isset($args['format']) ? $args['format'] : (isset($args[0]) ?  $args[0] : '%url%');
130 1
		$needed = isset($args['needed']) ? $args['needed'] : (isset($args[1]) ?  $args[1] : TRUE);
131
132 1
		Validators::assert($path, 'string', 'path');
133 1
		Validators::assert($format, 'string', 'format');
134 1
		Validators::assert($needed, 'bool', 'needed');
135
136 1
		return [$path, $format, $needed];
137
	}
138
139
140
	/**
141
	 * @param $relativePath
142
	 * @param $needed
143
	 * @param array $config
144
	 * @return array
145
	 */
146 1
	private static function processPaths($relativePath, $needed, array $config)
147
	{
148 1
		$relativePath = ltrim($relativePath, '\\/');
149 1
		if (($wwwDir = realpath($config['wwwDir'])) === FALSE) {
150 1
			throw new DirNotFoundException(sprintf("Www dir '%s' not found.", $config['wwwDir']));
151
		}
152
153 1
		if (($absolutePath = realpath($wwwDir . DIRECTORY_SEPARATOR . $relativePath)) === FALSE) {
154 1
			if ($needed) {
155 1
				$msg = sprintf("Asset '%s' not found.", $relativePath);
156 1
				if ($config['missingAsset'] === 'exception') {
157 1
					throw new FileNotFoundException($msg);
158
159 1
				} elseif ($config['missingAsset'] === 'notice') {
160 1
					trigger_error($msg, E_USER_NOTICE);
161
				}
162
			}
163
		}
164
165 1
		return [$relativePath, $absolutePath, $wwwDir];
166
	}
167
168
169
	/**
170
	 * @param mixed $assetsVersions
171
	 * @param string $absolutePath
172
	 * @param array $config
173
	 * @return mixed|string
174
	 */
175 1
	private static function getAssetVersion($assetsVersions, $absolutePath, array $config)
176
	{
177
		// Versions can be array or path to JSON file
178 1
		if ( ! is_array($assetsVersions)) {
179 1
			if ( ! file_exists($assetsVersions)) {
180 1
				throw new FileNotFoundException(sprintf("Asset versions file not found: '%s'.", $assetsVersions));
181
			}
182 1
			$assetsVersions = Json::decode(file_get_contents($assetsVersions), Json::FORCE_ARRAY);
183
		}
184
185 1
		foreach ($assetsVersions as $path => $hash) {
186
			// Test if path from version file (may be relative) is in asset path
187 1
			if (Strings::endsWith($absolutePath, $path)) {
188 1
				return $hash;
189
			}
190
		}
191
192 1
		$msg = sprintf("Asset macro: version of asset '%s' not found.", $absolutePath);
193 1
		switch ($config['missingVersion']) {
194 1
			case 'exception':
195 1
				throw new AssetVersionNotFound($msg);
196 1
			case 'notice':
197 1
				trigger_error($msg, E_USER_NOTICE);
198
				// no break
199
			default:
200 1
				return 'unknown';
201
		}
202
	}
203
204
205
	/**
206
	 * @param string $absolutePath
207
	 * @param string $wwwDir
208
	 * @param array $paths
209
	 * @return string
210
	 */
211 1
	private static function autodetectVersions($absolutePath, $wwwDir, array $paths)
212
	{
213
		// Iterate over parent directories (stop in www dir)
214 1
		$dir = dirname($absolutePath);
215 1
		while (Strings::startsWith($dir, $wwwDir)) {
216 1
			foreach ($paths as $path) {
217 1
				$path = $dir . DIRECTORY_SEPARATOR . $path;
218 1
				if (file_exists($path)) {
219 1
					return $path;
220
				}
221
			}
222
223
			// Get parent directory
224 1
			$dir = dirname($dir);
225
		}
226
227 1
		throw new FileNotFoundException(
228 1
			sprintf("None of the version files (%s) can be found in '%s' and parent directories up to www dir '%s'. " .
229 1
				"Create one of these files or set 'versions' in configuration.",
230 1
				implode(', ', $paths),
231 1
				dirname($absolutePath),
232 1
				$wwwDir
233
			)
234
		);
235
	}
236
237
}
238