Completed
Push — master ( 121335...68b8eb )
by Michal
03:32
created

AssetMacro::processArguments()   C

Complexity

Conditions 7
Paths 20

Size

Total Lines 33
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 20
CRAP Score 7

Importance

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