Completed
Push — master ( bc3e86...3980d1 )
by Michal
03:02
created

AssetMacro::install()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 5
ccs 4
cts 4
cp 1
rs 9.4285
cc 1
eloc 3
nc 1
nop 1
crap 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 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\InvalidVariableException;
14
use Webrouse\AssetMacro\Exceptions\ManifestNotFoundException;
15
16
17 1
class AssetMacro extends MacroSet
18
{
19
20
	const CONFIG_PROVIDER = 'assetMacroConfig';
21
22
23
	/**
24
	 * @param Latte\Compiler $compiler
25
	 */
26 1
	public static function install(Latte\Compiler $compiler)
27
	{
28 1
		$me = new self($compiler);
29 1
		$me->addMacro('asset', [$me, 'macroAsset']);
30 1
	}
31
32
33
	/**
34
	 * @param Latte\MacroNode $node
35
	 * @param Latte\PhpWriter $writer
36
	 * @return string
37
	 * @throws Latte\CompileException
38
	 */
39 1
	public function macroAsset(Latte\MacroNode $node, Latte\PhpWriter $writer)
40
	{
41 1
		$args = trim($node->args);
42
43
		// Validate arguments count
44 1
		$argsCount = $args === '' ? 0 : (substr_count($args, ',') + 1);
45 1
		if ($argsCount === 0) {
46 1
			throw new Latte\CompileException("Asset macro requires at least one argument.");
47
		}
48 1
		if ($argsCount > 3) {
49 1
			throw new Latte\CompileException("Asset macro must have no more than 3 arguments.");
50
		}
51
52 1
		return $writer->write(
53
			'echo %escape(' . self::class . '::resolveAssetPath(' .
54 1
			'%node.word, %node.array, $basePath, $template->global->' . self::CONFIG_PROVIDER . '))');
55
	}
56
57
    /**
58
     * @param string $asset     Asset relative path
59
     * @param array $args       Other macro arguments
60
     * @param string $basePath  Base path
61
     * @param array $config     Macro configuration
62
     * @return string
63
     */
64 1
	public static function resolveAssetPath($asset, array $args, $basePath, array $config) {
65 1
        list($path, $format, $need) = self::processArguments($asset, $args);
66 1
	    $wwwDir = Utils::normalizePath($config['wwwDir']);
67 1
        $manifest = self::resolveManifest($path, $need, $wwwDir, $config);
68 1
        $revision = $manifest === NULL ? NULL : self::resolveRevision($manifest, $path, $need, $config);
69
70
        // Is revision only version (query parameter) or full path to asset?
71 1
        $isVersion = $revision === NULL || strspn($revision,"./") === 0;
72
73
        // Check if asset exists
74 1
        if ($config['existCheck'] === TRUE) {
75 1
            $ds = DIRECTORY_SEPARATOR;
76 1
            $filePath = $isVersion ? ($wwwDir . $ds . $path) : ($wwwDir . $ds . Utils::normalizePath($revision));
77 1 View Code Duplication
            if (! file_exists($filePath)) {
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...
78 1
                $msg = sprintf("Asset '%s' not found.", $filePath);
79 1
                Utils::throwError(new AssetNotFoundException($msg), $config['missingAsset'], $need);
80 1
                return '';
81
            }
82
        }
83
84
        // Format output
85 1
        return self::formatOutput($format, $basePath, $path, $revision, $isVersion);
86
    }
87
88
    /**
89
     * @param string $format         Output format
90
     * @param string $basePath       Base path
91
     * @param string $path           Asset relative path
92
     * @param string|null $revision  Asset revision (version or path to file)
93
     * @param bool   $isVersion      Is revision only version or full path?
94
     * @return string
95
     */
96
    private static function formatOutput($format, $basePath, $path, $revision, $isVersion)
97
    {
98 1
        $revision = $revision ?: 'unknown';
99 1
        $path = $isVersion ?
100 1
            sprintf("%s?v=%s", $path, $revision) :
101 1
            $revision;
102
103 1
        return Strings::replace($format,
104 1
            '/%([^%]+)%/',
105 1
            function ($matches) use ($basePath, $format, $path, $revision) {
106 1
                switch ($matches[1]) {
107 1
                    case 'raw':
108
                        return $revision;
109 1
                    case 'basePath':
110 1
                        return $basePath;
111 1
                    case 'path':
112 1
                        return $path;
113 1
                    case 'url':
114 1
                        return sprintf("%s/%s", $basePath, $path);
115
                    default:
116 1
                        $msg = sprintf(
117
                            "Asset macro: Invalid variable '%s' in format '%s'. " .
118 1
                            "Use one of allowed variables: %%raw%%, %%basePath%%, %%path%%, %%url%%.",
119 1
                            $matches[1],
120 1
                            $format
121
                        );
122 1
                        throw new InvalidVariableException($msg);
123
                }
124 1
            });
125
    }
126
127
    /**
128
     * @param string $asset  Asset path specified in macro
129
     * @param array $args    Macro arguments
130
     * @return array
131
     */
132 1
    private static function processArguments($asset, array $args)
133
    {
134 1
        $format = isset($args['format']) ? $args['format'] : (isset($args[0]) ? $args[0] : '%url%');
135 1
        $need = isset($args['need']) ? $args['need'] : (isset($args[1]) ? $args[1] : TRUE);
136
137 1
        Validators::assert($asset, 'string', 'path');
138 1
        Validators::assert($format, 'string', 'format');
139 1
        Validators::assert($need, 'bool', 'need');
140
141 1
        $path = Utils::normalizePath($asset);
142
143 1
        return [$path, $format, $need];
144
    }
145
146
    /**
147
     * @param string $asset   Asset path specified in macro
148
     * @param bool $need      Fail if manifest doesn't exist?
149
     * @param string $wwwDir  Public www dir
150
     * @param array $config   Macro configuration
151
     * @return null|array
152
     */
153 1
    private static function resolveManifest($asset, $need, $wwwDir, array $config) {
154 1
        $manifest = $config['manifest'];
155
156
	    // Asset revisions specified directly in configuration
157 1
        if (is_array($manifest)) {
158
            return $manifest;
159
        }
160
161
        // Path to JSON manifest
162 1
        if (is_string($manifest)) {
163 1 View Code Duplication
            if ( ! file_exists($manifest)) {
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...
164 1
                $msg = sprintf("Manifest file not found: '%s'.", $manifest);
165 1
                Utils::throwError(new ManifestNotFoundException($msg), $config['missingManifest'], $need);
166 1
                return NULL;
167
            }
168 1
            return Json::decode(file_get_contents($manifest), Json::FORCE_ARRAY);
169
        }
170
171
        // Autodetect manifest path
172 1
        return self::autodetectManifest($asset, $wwwDir, $need, $config);
173
    }
174
175
    /**
176
     * @param string $asset   Asset path specified in macro
177
     * @param string $wwwDir  Public www dir
178
     * @param bool $need      Fail if asset/manifest doesn't exist?
179
     * @param array $config   Macro configuration
180
     * @return null|array
181
     */
182 1
    private static function autodetectManifest($asset, $wwwDir, $need, array $config) {
183
        // Get asset dir, there begins search manifest
184 1
        $dir = realpath($wwwDir . DIRECTORY_SEPARATOR . dirname($asset));
185 1
        if ($dir === FALSE) {
186
            $msg = sprintf("Parent dir of asset '%s' not found.", $asset);
187
            Utils::throwError(new DirNotFoundException($msg), $config['missingAsset'], $need);
188
            return NULL;
189
        }
190
191
        // Autodetect manifest
192 1
        $autodetectPaths = $config['autodetect'];
193 1
        while (Strings::startsWith($dir, $wwwDir)) {
194 1
            foreach ($autodetectPaths as $path) {
195 1
                $path = $dir . DIRECTORY_SEPARATOR . $path;
196 1
                if (file_exists($path)) {
197 1
                    return Json::decode(file_get_contents($path), Json::FORCE_ARRAY);
198
                }
199
            }
200
201 1
            $dir = dirname($dir); // go up
202
        }
203
204 1
        $msg = sprintf("Manifest not found in: %s.", implode(', ', $autodetectPaths));
205 1
        Utils::throwError(new ManifestNotFoundException($msg), $config['missingManifest'], $need);
206
        return NULL;
207
    }
208
209
    /**
210
     * @param null|array $manifest  Array of revisions
211
     * @param string $path          Asset path
212
     * @param bool $need            Fail if revision doesn't exist?
213
     * @param array $config         Macro configuration
214
     * @return null|string
215
     */
216 1
    private static function resolveRevision($manifest, $path, $need, array $config) {
217 1
        $revision = isset($manifest[$path]) ? $manifest[$path] : NULL;
218
        
219 1
        if ($revision === NULL) {
220 1
            $msg = sprintf("Revision for asset '%s' not found in manifest.", $path);
221 1
            Utils::throwError(new AssetVersionNotFound($msg), $config['missingRevision'], $need);
222
        }
223
        
224 1
        return $revision;
225
    }
226
227
}
228