Completed
Push — master ( 437e57...ab0a93 )
by Michal
03:33 queued 10s
created

AssetMacro::getAssetVersion()   B

Complexity

Conditions 5
Paths 7

Size

Total Lines 22
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 5

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 22
ccs 11
cts 11
cp 1
rs 8.6737
cc 5
eloc 11
nc 7
nop 4
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\AssetVersionNotFound;
12
use Webrouse\AssetMacro\Exceptions\DirNotFoundException;
13
use Webrouse\AssetMacro\Exceptions\ManifestNotFoundException;
14
use Webrouse\AssetMacro\Exceptions\InvalidVariableException;
15
16
17 1
class AssetMacro extends MacroSet
18
{
19
20
	const REV_MANIFEST_AUTO = 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
        try {
77 1
            $versions = $config['revManifest'] ?:
78 1
                self::autodetectVersions($absolutePath, $wwwDir, $config['autodetect']);
79 1
            $version = self::getAssetVersion($versions, $absolutePath, $needed, $config);
80 1
        } catch(ManifestNotFoundException $e) {
81 1
		    self::throwError($e, $config['missingManifest'], $needed);
82 1
            $version = 'unknown';
83
        }
84
85 1
		return self::formatOutput($format, $basePath, $relativePath, $version);
86
	}
87
88
89
	/**
90
	 * Generate output according given format
91
	 * @param string $format
92
	 * @param string $basePath
93
	 * @param string $relativePath
94
	 * @param string $version
95
	 * @return string
96
	 */
97
	private static function formatOutput($format, $basePath, $relativePath, $version)
98
	{
99 1
		return Strings::replace($format,
100 1
			'/%([^%]+)%/',
101 1
			function ($matches) use ($basePath, $format, $relativePath, $version) {
102 1
				switch ($matches[1]) {
103 1
					case 'url':
104 1
						return sprintf("%s/%s?v=%s", $basePath, $relativePath, $version);
105 1
                    case 'path':
106 1
                        return sprintf("%s/%s", $basePath, $relativePath);
107 1
					case 'version':
108 1
						return $version;
109 1
					case 'basePath':
110 1
						return $basePath;
111 1
					case 'dir':
112 1
						return dirname($relativePath);
113 1
					case 'file':
114 1
						return basename($relativePath);
115
					default:
116 1
						$msg = sprintf(
117
							"Asset macro: Invalid variable '%s' in format '%s'. " .
118 1
							"Use one of allowed variables: %%url%%, %%version%%, %%basePath%%, %%dir%%, %%file%%.",
119 1
							$matches[1],
120 1
							$format
121
						);
122 1
						throw new InvalidVariableException($msg);
123
				}
124 1
			});
125
	}
126
127
128
	/**
129
	 * @param string $path
130
	 * @param array $args
131
	 * @return array
132
	 */
133 1
	private static function processArguments($path, array $args)
134
	{
135 1
		$format = isset($args['format']) ? $args['format'] : (isset($args[0]) ? $args[0] : '%url%');
136 1
		$needed = isset($args['needed']) ? $args['needed'] : (isset($args[1]) ? $args[1] : TRUE);
137
138 1
		Validators::assert($path, 'string', 'path');
139 1
		Validators::assert($format, 'string', 'format');
140 1
		Validators::assert($needed, 'bool', 'needed');
141
142 1
		return [$path, $format, $needed];
143
	}
144
145
146
	/**
147
	 * @param $relativePath
148
	 * @param $needed
149
	 * @param array $config
150
	 * @return array
151
	 */
152 1
	private static function processPaths($relativePath, $needed, array $config)
153
	{
154 1
		$relativePath = ltrim($relativePath, '\\/');
155 1
		if (($wwwDir = realpath($config['wwwDir'])) === FALSE) {
156 1
			throw new DirNotFoundException(sprintf("Www dir '%s' not found.", $config['wwwDir']));
157
		}
158
159 1
		if (($absolutePath = realpath($wwwDir . DIRECTORY_SEPARATOR . $relativePath)) === FALSE) {
160 1
            $msg = sprintf("Asset '%s' not found.", $relativePath);
161 1
            self::throwError(new AssetNotFoundException($msg), $config['missingAsset'], $needed);
162
		}
163
164 1
		return [$relativePath, $absolutePath, $wwwDir];
165
	}
166
167
168
	/**
169
	 * @param mixed $assetsVersions
170
	 * @param string $absolutePath
171
     * @param bool $needed
172
	 * @param array $config
173
	 * @return mixed|string
174
	 */
175 1
	private static function getAssetVersion($assetsVersions, $absolutePath, $needed, 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 ManifestNotFoundException(sprintf("Revision manifest 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
        self::throwError(new AssetVersionNotFound($msg), $config['missingVersion'], $needed);
194
195 1
        return 'unknown';
196
	}
197
198
199
    /**
200
     * @param $absolutePath
201
     * @param $wwwDir
202
     * @param array $paths
203
     * @return mixed|string
204
     */
205 1
	private static function autodetectVersions($absolutePath, $wwwDir, array $paths)
206
	{
207
		// Iterate over parent directories (stop in www dir)
208 1
		$dir = dirname($absolutePath);
209 1
		while (Strings::startsWith($dir, $wwwDir)) {
210 1
			foreach ($paths as $path) {
211 1
				$path = $dir . DIRECTORY_SEPARATOR . $path;
212 1
				if (file_exists($path)) {
213 1
					return $path;
214
				}
215
			}
216
217
			// Get parent directory
218 1
			$dir = dirname($dir);
219
		}
220
221 1
		throw new ManifestNotFoundException(
222 1
			sprintf("Rev. manifest not found in expected paths: (%s)." .
223 1
				"Create one of these files or set 'revManifest' in configuration.",
224 1
				implode(', ', $paths)
225
			)
226
		);
227
	}
228
229
    /**
230
     * @param \Exception $e
231
     * @param string $action
232
     * @param bool $needed
233
     * @throws \Exception
234
     */
235 1
	private static function throwError(\Exception $e, $action = 'exception', $needed = TRUE) {
236 1
        if ($needed) {
237 1
            if ($action === 'exception') {
238 1
                throw $e;
239 1
            } elseif ($action === 'notice') {
240 1
                trigger_error($e->getMessage(), E_USER_NOTICE);
241
            }
242
        }
243 1
    }
244
245
}
246