Completed
Push — master ( 68b8eb...1548d7 )
by Michal
02:11
created

AssetMacro::processPaths()   B

Complexity

Conditions 6
Paths 6

Size

Total Lines 21
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 6

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 21
ccs 12
cts 12
cp 1
rs 8.7624
cc 6
eloc 12
nc 6
nop 3
crap 6
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 Tracy\Debugger;
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 1
		$argsCount = $args === '' ? 0 : (substr_count($args, ',') + 1);
45
46
		// Validate arguments count
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
		$content =
55
			self::class . '::generateAssetPath(' .
56
			'%node.word, ' .
57
			'%node.array, ' .
58
			'$basePath, ' .
59 1
			'$template->global->' . self::CONFIG_PROVIDER . ')';
60
61 1
		return $writer->write('echo %escape(' . $content . ')');
62
	}
63
64
65
	/**
66
	 * @param string $path
67
	 * @param array $args
68
	 * @param string $basePath
69
	 * @param array $config
70
	 * @return string
71
	 */
72 1
	public static function generateAssetPath($path, array $args, $basePath, $config)
73
	{
74 1
		list($relativePath, $format, $needed) = self::processArguments($path, $args);
75 1
		list($relativePath, $absolutePath, $wwwDir) = self::processPaths($relativePath, $needed, $config);
76 1
		if ($absolutePath === FALSE) {
77 1
			return '';
78
		}
79
80
		// Get asset version
81 1
		$version = self::getAssetVersion(
82 1
			$config['versions'] === self::VERSIONS_AUTODETECT ?
83 1
				self::autodetectVersions($absolutePath, $wwwDir, $config['autodetectPaths']) :
84 1
				$config['versions'],
85 1
			$absolutePath,
86 1
			$config
87
		);
88
89
		// Generate output according format argument
90 1
		return Strings::replace($format,
91 1
			'/%([^%]+)%/',
92 1
			function ($matches) use ($basePath, $format, $relativePath, $version) {
93 1
				switch ($matches[1]) {
94 1
					case 'url':
95 1
						return sprintf("%s/%s?v=%s", $basePath, $relativePath, $version);
96 1
					case 'version':
97 1
						return $version;
98 1
					case 'basePath':
99 1
						return $basePath;
100 1
					case 'dir':
101 1
						return dirname($relativePath);
102 1
					case 'file':
103 1
						return basename($relativePath);
104
					default:
105 1
						$msg = sprintf(
106
							"Asset macro: Invalid variable '%s' in format '%s'. " .
107 1
							"Use one of allowed variables: %%url%%, %%version%%, %%basePath%%, %%dir%%, %%file%%.",
108 1
							$matches[1],
109 1
							$format
110
						);
111 1
						throw new InvalidVariableException($msg);
112
				}
113 1
			});
114
	}
115
116
117
	/**
118
	 * @param string $path
119
	 * @param array $args
120
	 * @return array
121
	 */
122 1
	private static function processArguments($path, array $args)
123
	{
124 1
		$relativePath = $path;
125
126
		// Format argument
127 1
		$format = '%url%';
128 1
		if (isset($args['format'])) {
129 1
			$format = $args['format'];
130
131 1
		} elseif (isset($args[0])) {
132 1
			$format = $args[0];
133
		}
134
135
		// Needed argument
136 1
		$needed = TRUE;
137 1
		if (isset($args['needed'])) {
138 1
			$needed = $args['needed'];
139
140 1
		} elseif (isset($args[1])) {
141 1
			$needed = $args[1];
142
		}
143
144
		// Validate arguments
145 1
		if ( ! is_string($relativePath)) {
146 1
			throw new InvalidArgumentException(sprintf(
147 1
				"Asset macro: Invalid type of the path argument. Required string. Given %s.",
148 1
				gettype($relativePath)
149
			));
150
		}
151 1
		if ( ! is_string($format)) {
152 1
			throw new InvalidArgumentException(sprintf(
153 1
				"Asset macro: Invalid type of the format argument. Required string. Given %s.",
154 1
				gettype($format)
155
			));
156
		}
157 1
		if ( ! is_bool($needed)) {
158 1
			throw new InvalidArgumentException(sprintf(
159 1
				"Asset macro: Invalid type of the needed argument. Required boolean. Given %s.",
160 1
				gettype($needed)
161
			));
162
		}
163
164 1
		return [$relativePath, $format, $needed];
165
	}
166
167
168
	/**
169
	 * @param $relativePath
170
	 * @param $needed
171
	 * @param array $config
172
	 * @return array
173
	 */
174 1
	private static function processPaths($relativePath, $needed, array $config)
175
	{
176 1
		$relativePath = ltrim($relativePath, '\\/');
177 1
		if (($wwwDir = realpath($config['wwwDir'])) === FALSE) {
178 1
			throw new DirNotFoundException(sprintf("Www dir '%s' not found.", $config['wwwDir']));
179
		}
180
181 1
		if (($absolutePath = realpath($wwwDir . DIRECTORY_SEPARATOR . $relativePath)) === FALSE) {
182 1
			if ($needed) {
183 1
				$msg = sprintf("Asset '%s' not found.", $relativePath);
184 1
				if ($config['missingAsset'] === 'exception') {
185 1
					throw new FileNotFoundException($msg);
186
187 1
				} elseif ($config['missingAsset'] === 'notice') {
188 1
					trigger_error($msg, E_USER_NOTICE);
189
				}
190
			}
191
		}
192
193 1
		return [$relativePath, $absolutePath, $wwwDir];
194
	}
195
196
197
	/**
198
	 * @param mixed $assetsVersions
199
	 * @param string $absolutePath
200
	 * @param array $config
201
	 * @return mixed|string
202
	 */
203 1
	private static function getAssetVersion($assetsVersions, $absolutePath, array $config)
204
	{
205
		// Versions can be array or path to JSON file
206 1
		if ( ! is_array($assetsVersions)) {
207 1
			if ( ! file_exists($assetsVersions)) {
208 1
				throw new FileNotFoundException(sprintf("Asset versions file not found: '%s'.", $assetsVersions));
209
			}
210 1
			$assetsVersions = Json::decode(file_get_contents($assetsVersions), Json::FORCE_ARRAY);
211
		}
212
213 1
		foreach ($assetsVersions as $path => $hash) {
214
			// Test if path from version file (may be relative) is in asset path
215 1
			if (Strings::endsWith($absolutePath, $path)) {
216 1
				return $hash;
217
			}
218
		}
219
220 1
		$msg = sprintf("Asset macro: version of asset '%s' not found.", $absolutePath);
221 1
		switch ($config['missingVersion']) {
222 1
			case 'exception':
223 1
				throw new AssetVersionNotFound($msg);
224 1
			case 'notice':
225 1
				trigger_error($msg, E_USER_NOTICE);
226
				// no break
227
			default:
228 1
				return 'unknown';
229
		}
230
	}
231
232
233
	/**
234
	 * @param string $absolutePath
235
	 * @param string $wwwDir
236
	 * @param array $paths
237
	 * @return string
238
	 */
239 1
	private static function autodetectVersions($absolutePath, $wwwDir, array $paths)
240
	{
241
		// Iterate over parent directories (stop in www dir)
242 1
		$dir = dirname($absolutePath);
243 1
		while (Strings::startsWith($dir, $wwwDir)) {
244 1
			foreach ($paths as $path) {
245 1
				$path = $dir . DIRECTORY_SEPARATOR . $path;
246 1
				if (file_exists($path)) {
247 1
					return $path;
248
				}
249
			}
250
251
			// Get parent directory
252 1
			$dir = dirname($dir);
253
		}
254
255 1
		throw new FileNotFoundException(
256 1
			sprintf("None of the version files (%s) can be found in '%s' and parent directories up to www dir '%s'. " .
257 1
				"Create one of these files or set 'versions' in configuration.",
258 1
				implode(', ', $paths),
259 1
				dirname($absolutePath),
260 1
				$wwwDir
261
			)
262
		);
263
	}
264
265
}
266