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

AssetMacro::formatOutput()   B

Complexity

Conditions 6
Paths 1

Size

Total Lines 27
Code Lines 22

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 20
CRAP Score 6

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 27
ccs 20
cts 20
cp 1
rs 8.439
cc 6
eloc 22
nc 1
nop 4
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 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
44
		// Validate arguments count
45 1
		$argsCount = $args === '' ? 0 : (substr_count($args, ',') + 1);
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 1
		return $writer->write(
54
			'echo %escape(' . self::class . '::generateAssetPath(' .
55 1
			'%node.word, %node.array, $basePath, $template->global->' . self::CONFIG_PROVIDER . '))');
56
	}
57
58
59
	/**
60
	 * @param string $path
61
	 * @param array $args
62
	 * @param string $basePath
63
	 * @param array $config
64
	 * @return string
65
	 */
66 1
	public static function generateAssetPath($path, array $args, $basePath, $config)
67
	{
68 1
		list($relativePath, $format, $needed) = self::processArguments($path, $args);
69 1
		list($relativePath, $absolutePath, $wwwDir) = self::processPaths($relativePath, $needed, $config);
70 1
		if ($absolutePath === FALSE) {
71 1
			return '';
72
		}
73
74
		// Get asset version
75 1
		$versions = $config['versions'] === self::VERSIONS_AUTODETECT ?
76 1
			self::autodetectVersions($absolutePath, $wwwDir, $config['autodetectPaths']) :
77 1
			$config['versions'];
78 1
		$version = self::getAssetVersion($versions, $absolutePath, $config);
79
80 1
		return self::formatOutput($format, $basePath, $relativePath, $version);
81
	}
82
83
84
	/**
85
     * Generate output according given format
86
	 * @param string $format
87
	 * @param string $basePath
88
	 * @param string $relativePath
89
	 * @param string $version
90
	 * @return string
91
	 */
92
	private static function formatOutput($format, $basePath, $relativePath, $version)
93
	{
94 1
		return Strings::replace($format,
95 1
			'/%([^%]+)%/',
96 1
			function ($matches) use ($basePath, $format, $relativePath, $version) {
97 1
				switch ($matches[1]) {
98 1
					case 'url':
99 1
						return sprintf("%s/%s?v=%s", $basePath, $relativePath, $version);
100 1
					case 'version':
101 1
						return $version;
102 1
					case 'basePath':
103 1
						return $basePath;
104 1
					case 'dir':
105 1
						return dirname($relativePath);
106 1
					case 'file':
107 1
						return basename($relativePath);
108
					default:
109 1
						$msg = sprintf(
110
							"Asset macro: Invalid variable '%s' in format '%s'. " .
111 1
							"Use one of allowed variables: %%url%%, %%version%%, %%basePath%%, %%dir%%, %%file%%.",
112 1
							$matches[1],
113 1
							$format
114
						);
115 1
						throw new InvalidVariableException($msg);
116
				}
117 1
			});
118
	}
119
120
121
	/**
122
	 * @param string $path
123
	 * @param array $args
124
	 * @return array
125
	 */
126 1
	private static function processArguments($path, array $args)
127
	{
128 1
		$relativePath = $path;
129
130
		// Format argument
131 1 View Code Duplication
		if (isset($args['format'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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.

Loading history...
132 1
			$format = $args['format'];
133
134 1
		} elseif (isset($args[0])) {
135 1
			$format = $args[0];
136
137
		} else {
138 1
			$format = '%url%';
139
		}
140
141
		// Needed argument
142 1 View Code Duplication
		if (isset($args['needed'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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.

Loading history...
143 1
			$needed = $args['needed'];
144
145 1
		} elseif (isset($args[1])) {
146 1
			$needed = $args[1];
147
148
		} else {
149 1
			$needed = TRUE;
150
		}
151
152
		// Validate arguments
153 1
		if ( ! is_string($relativePath)) {
154 1
			throw new InvalidArgumentException(sprintf(
155 1
				"Asset macro: Invalid type of the path argument. Required string. Given %s.",
156 1
				gettype($relativePath)
157
			));
158
		}
159 1
		if ( ! is_string($format)) {
160 1
			throw new InvalidArgumentException(sprintf(
161 1
				"Asset macro: Invalid type of the format argument. Required string. Given %s.",
162 1
				gettype($format)
163
			));
164
		}
165 1
		if ( ! is_bool($needed)) {
166 1
			throw new InvalidArgumentException(sprintf(
167 1
				"Asset macro: Invalid type of the needed argument. Required boolean. Given %s.",
168 1
				gettype($needed)
169
			));
170
		}
171
172 1
		return [$relativePath, $format, $needed];
173
	}
174
175
176
	/**
177
	 * @param $relativePath
178
	 * @param $needed
179
	 * @param array $config
180
	 * @return array
181
	 */
182 1
	private static function processPaths($relativePath, $needed, array $config)
183
	{
184 1
		$relativePath = ltrim($relativePath, '\\/');
185 1
		if (($wwwDir = realpath($config['wwwDir'])) === FALSE) {
186 1
			throw new DirNotFoundException(sprintf("Www dir '%s' not found.", $config['wwwDir']));
187
		}
188
189 1
		if (($absolutePath = realpath($wwwDir . DIRECTORY_SEPARATOR . $relativePath)) === FALSE) {
190 1
			if ($needed) {
191 1
				$msg = sprintf("Asset '%s' not found.", $relativePath);
192 1
				if ($config['missingAsset'] === 'exception') {
193 1
					throw new FileNotFoundException($msg);
194
195 1
				} elseif ($config['missingAsset'] === 'notice') {
196 1
					trigger_error($msg, E_USER_NOTICE);
197
				}
198
			}
199
		}
200
201 1
		return [$relativePath, $absolutePath, $wwwDir];
202
	}
203
204
205
	/**
206
	 * @param mixed $assetsVersions
207
	 * @param string $absolutePath
208
	 * @param array $config
209
	 * @return mixed|string
210
	 */
211 1
	private static function getAssetVersion($assetsVersions, $absolutePath, array $config)
212
	{
213
		// Versions can be array or path to JSON file
214 1
		if ( ! is_array($assetsVersions)) {
215 1
			if ( ! file_exists($assetsVersions)) {
216 1
				throw new FileNotFoundException(sprintf("Asset versions file not found: '%s'.", $assetsVersions));
217
			}
218 1
			$assetsVersions = Json::decode(file_get_contents($assetsVersions), Json::FORCE_ARRAY);
219
		}
220
221 1
		foreach ($assetsVersions as $path => $hash) {
222
			// Test if path from version file (may be relative) is in asset path
223 1
			if (Strings::endsWith($absolutePath, $path)) {
224 1
				return $hash;
225
			}
226
		}
227
228 1
		$msg = sprintf("Asset macro: version of asset '%s' not found.", $absolutePath);
229 1
		switch ($config['missingVersion']) {
230 1
			case 'exception':
231 1
				throw new AssetVersionNotFound($msg);
232 1
			case 'notice':
233 1
				trigger_error($msg, E_USER_NOTICE);
234
				// no break
235
			default:
236 1
				return 'unknown';
237
		}
238
	}
239
240
241
	/**
242
	 * @param string $absolutePath
243
	 * @param string $wwwDir
244
	 * @param array $paths
245
	 * @return string
246
	 */
247 1
	private static function autodetectVersions($absolutePath, $wwwDir, array $paths)
248
	{
249
		// Iterate over parent directories (stop in www dir)
250 1
		$dir = dirname($absolutePath);
251 1
		while (Strings::startsWith($dir, $wwwDir)) {
252 1
			foreach ($paths as $path) {
253 1
				$path = $dir . DIRECTORY_SEPARATOR . $path;
254 1
				if (file_exists($path)) {
255 1
					return $path;
256
				}
257
			}
258
259
			// Get parent directory
260 1
			$dir = dirname($dir);
261
		}
262
263 1
		throw new FileNotFoundException(
264 1
			sprintf("None of the version files (%s) can be found in '%s' and parent directories up to www dir '%s'. " .
265 1
				"Create one of these files or set 'versions' in configuration.",
266 1
				implode(', ', $paths),
267 1
				dirname($absolutePath),
268 1
				$wwwDir
269
			)
270
		);
271
	}
272
273
}
274