Completed
Push — master ( e7db14...c031fe )
by Michal
05:13
created

AssetMacro::install()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 5
rs 9.4285
cc 1
eloc 3
nc 1
nop 1
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\DirNotFoundException;
10
use Webrouse\AssetMacro\Exceptions\FileNotFoundException;
11
12
13
class AssetMacro extends MacroSet
14
{
15
16
	const VERSIONS_AUTODETECT = NULL;
17
18
	const VERSIONS_AUTODETECT_PATHS = [
19
		'busters.json',
20
		'versions.json',
21
		'rev-manifest.json'
22
	];
23
24
	const VERSIONS_PROVIDER = 'assetMacroVersions';
25
26
	const WWW_DIR_PROVIDER = 'assetMacroWwwDir';
27
28
	/**
29
	 * @param Latte\Compiler $compiler
30
	 */
31
	public static function install(Latte\Compiler $compiler)
32
	{
33
		$me = new self($compiler);
34
		$me->addMacro('asset', [$me, 'macroAsset']);
35
	}
36
37
38
	/**
39
	 * @param Latte\MacroNode $node
40
	 * @param Latte\PhpWriter $writer
41
	 * @return string
42
	 */
43
	public function macroAsset(Latte\MacroNode $node, Latte\PhpWriter $writer)
44
	{
45
		$content =
46
			self::class .
47
			'::appendVersion(' .
48
			'%node.word, ' .
49
			'$template->global->' . self::VERSIONS_PROVIDER . ', ' .
50
			'$template->global->' . self::WWW_DIR_PROVIDER  .
51
			')';
52
		return $writer->write('echo $basePath . "/" . %escape(' . $content .')');
53
	}
54
55
56
	/**
57
	 * Append version (from JSON file or versions array) to asset relative path.
58
	 *
59
	 * @param string $relativePath
60
	 * @param string|array $assetVersions
61
	 * @param string $wwwDir
62
	 * @return string
63
	 */
64
	public static function appendVersion($relativePath, $assetVersions, $wwwDir)
65
	{
66
		$relativePath = ltrim($relativePath, '\\/');
67
68
		if (($normalizedWwwDir = realpath($wwwDir)) === FALSE) {
69
			throw new DirNotFoundException(sprintf("Www dir '%s' not found.", $wwwDir));
70
		}
71
		if (($absolutePath = realpath($normalizedWwwDir . DIRECTORY_SEPARATOR . $relativePath)) === FALSE) {
72
			throw new FileNotFoundException(sprintf("Asset '%s' not found.", $relativePath));
73
		}
74
75
		if ($assetVersions === self::VERSIONS_AUTODETECT) {
76
			$assetVersions = self::autodetectVersionsFile($absolutePath, $normalizedWwwDir);
77
		}
78
79
		return $relativePath . '?v=' . self::getAssetVersion($assetVersions, $absolutePath);
80
	}
81
82
83
	/**
84
	 * Get asset version hash.
85
	 * If the record can not be found, then version is 'unknown'.
86
	 *
87
	 * @param string|array $assetsVersions
88
	 * @param string $absolutePath
89
	 * @return mixed|string
90
	 */
91
	private static function getAssetVersion($assetsVersions, $absolutePath)
92
	{
93
		// Versions can be array or path to JSON file
94
		if ( ! is_array($assetsVersions)) {
95
			if ( ! file_exists($assetsVersions)) {
96
				throw new FileNotFoundException(sprintf("Asset versions file not found: '%s'.", $assetsVersions));
97
			}
98
			$assetsVersions = Json::decode(file_get_contents($assetsVersions), Json::FORCE_ARRAY);
99
		}
100
101
		foreach($assetsVersions as $path => $hash) {
102
			// Test if path from version file (may be relative) is in asset path
103
			if (Strings::endsWith($absolutePath, $path)) {
104
				return $hash;
105
			}
106
		}
107
108
		return 'unknown';
109
	}
110
111
112
	/**
113
	 * Autodetect version file path.
114
	 * It searches the asset directory and all parent directories up to www dir for files:
115
	 * - busters.json
116
	 * - versions.json
117
	 * - rev-manifest.json
118
	 * It is also possible to use asset name with the extension JSON (eg. some/path/main.js.json).
119
	 *
120
	 * @param string $absolutePath
121
	 * @param string $wwwDir
122
	 * @return mixed|string
123
	 */
124
	private static function autodetectVersionsFile($absolutePath, $wwwDir)
125
	{
126
		// First, test if there asset filename + '.json'
127
		if (file_exists($absolutePath . '.json')) {
128
			return $absolutePath . '.json';
129
		}
130
131
		// Iterate over parent directories (stop in www dir)
132
		$dir = dirname($absolutePath);
133
		while(Strings::startsWith($dir, $wwwDir)) {
134
			foreach(self::VERSIONS_AUTODETECT_PATHS as $path) {
135
				$path = $dir . DIRECTORY_SEPARATOR . $path;
136
				if (file_exists($path)) {
137
					return $path;
138
				}
139
			}
140
141
			// Get parent directory
142
			$dir = dirname($dir);
143
		}
144
145
		throw new FileNotFoundException(
146
			sprintf("None of the version files (%s) can be found in '%s' and parent directories up to www dir '%s'" .
147
				"Create one of these files or set 'assetMacro.versions' in configuration.",
148
				implode(', ', self::VERSIONS_AUTODETECT_PATHS),
149
				dirname($absolutePath),
150
				$wwwDir
151
			)
152
		);
153
	}
154
155
}
156