Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/phabricator
Path: blob/master/src/applications/celerity/CelerityResourceTransformer.php
12249 views
1
<?php
2
3
final class CelerityResourceTransformer extends Phobject {
4
5
private $minify;
6
private $rawURIMap;
7
private $celerityMap;
8
private $translateURICallback;
9
private $currentPath;
10
private $postprocessorKey;
11
private $variableMap;
12
13
public function setPostprocessorKey($postprocessor_key) {
14
$this->postprocessorKey = $postprocessor_key;
15
return $this;
16
}
17
18
public function getPostprocessorKey() {
19
return $this->postprocessorKey;
20
}
21
22
public function setTranslateURICallback($translate_uricallback) {
23
$this->translateURICallback = $translate_uricallback;
24
return $this;
25
}
26
27
public function setMinify($minify) {
28
$this->minify = $minify;
29
return $this;
30
}
31
32
public function setCelerityMap(CelerityResourceMap $celerity_map) {
33
$this->celerityMap = $celerity_map;
34
return $this;
35
}
36
37
public function setRawURIMap(array $raw_urimap) {
38
$this->rawURIMap = $raw_urimap;
39
return $this;
40
}
41
42
public function getRawURIMap() {
43
return $this->rawURIMap;
44
}
45
46
/**
47
* @phutil-external-symbol function jsShrink
48
*/
49
public function transformResource($path, $data) {
50
$type = self::getResourceType($path);
51
52
switch ($type) {
53
case 'css':
54
$data = $this->replaceCSSPrintRules($path, $data);
55
$data = $this->replaceCSSVariables($path, $data);
56
$data = preg_replace_callback(
57
'@url\s*\((\s*[\'"]?.*?)\)@s',
58
nonempty(
59
$this->translateURICallback,
60
array($this, 'translateResourceURI')),
61
$data);
62
break;
63
}
64
65
if (!$this->minify) {
66
return $data;
67
}
68
69
// Some resources won't survive minification (like d3.min.js), and are
70
// marked so as not to be minified.
71
if (strpos($data, '@'.'do-not-minify') !== false) {
72
return $data;
73
}
74
75
switch ($type) {
76
case 'css':
77
// Remove comments.
78
$data = preg_replace('@/\*.*?\*/@s', '', $data);
79
// Remove whitespace around symbols.
80
$data = preg_replace('@\s*([{}:;,])\s*@', '\1', $data);
81
// Remove unnecessary semicolons.
82
$data = preg_replace('@;}@', '}', $data);
83
// Replace #rrggbb with #rgb when possible.
84
$data = preg_replace(
85
'@#([a-f0-9])\1([a-f0-9])\2([a-f0-9])\3@i',
86
'#\1\2\3',
87
$data);
88
$data = trim($data);
89
break;
90
case 'js':
91
92
// If `jsxmin` is available, use it. jsxmin is the Javelin minifier and
93
// produces the smallest output, but is complicated to build.
94
if (Filesystem::binaryExists('jsxmin')) {
95
$future = new ExecFuture('jsxmin __DEV__:0');
96
$future->write($data);
97
list($err, $result) = $future->resolve();
98
if (!$err) {
99
$data = $result;
100
break;
101
}
102
}
103
104
// If `jsxmin` is not available, use `JsShrink`, which doesn't compress
105
// quite as well but is always available.
106
$root = dirname(phutil_get_library_root('phabricator'));
107
require_once $root.'/externals/JsShrink/jsShrink.php';
108
$data = jsShrink($data);
109
110
break;
111
}
112
113
return $data;
114
}
115
116
public static function getResourceType($path) {
117
return last(explode('.', $path));
118
}
119
120
public function translateResourceURI(array $matches) {
121
$uri = trim($matches[1], "'\" \r\t\n");
122
$tail = '';
123
124
// If the resource URI has a query string or anchor, strip it off before
125
// we go looking for the resource. We'll stitch it back on later. This
126
// primarily affects FontAwesome.
127
128
$parts = preg_split('/(?=[?#])/', $uri, 2);
129
if (count($parts) == 2) {
130
$uri = $parts[0];
131
$tail = $parts[1];
132
}
133
134
$alternatives = array_unique(
135
array(
136
$uri,
137
ltrim($uri, '/'),
138
));
139
140
foreach ($alternatives as $alternative) {
141
if ($this->rawURIMap !== null) {
142
if (isset($this->rawURIMap[$alternative])) {
143
$uri = $this->rawURIMap[$alternative];
144
break;
145
}
146
}
147
148
if ($this->celerityMap) {
149
$resource_uri = $this->celerityMap->getURIForName($alternative);
150
if ($resource_uri) {
151
// Check if we can use a data URI for this resource. If not, just
152
// use a normal Celerity URI.
153
$data_uri = $this->generateDataURI($alternative);
154
if ($data_uri) {
155
$uri = $data_uri;
156
} else {
157
$uri = $resource_uri;
158
}
159
break;
160
}
161
}
162
}
163
164
return 'url('.$uri.$tail.')';
165
}
166
167
private function replaceCSSVariables($path, $data) {
168
$this->currentPath = $path;
169
return preg_replace_callback(
170
'/{\$([^}]+)}/',
171
array($this, 'replaceCSSVariable'),
172
$data);
173
}
174
175
private function replaceCSSPrintRules($path, $data) {
176
$this->currentPath = $path;
177
return preg_replace_callback(
178
'/!print\s+(.+?{.+?})/s',
179
array($this, 'replaceCSSPrintRule'),
180
$data);
181
}
182
183
public function getCSSVariableMap() {
184
$postprocessor_key = $this->getPostprocessorKey();
185
$postprocessor = CelerityPostprocessor::getPostprocessor(
186
$postprocessor_key);
187
188
if (!$postprocessor) {
189
$postprocessor = CelerityPostprocessor::getPostprocessor(
190
CelerityDefaultPostprocessor::POSTPROCESSOR_KEY);
191
}
192
193
return $postprocessor->getVariables();
194
}
195
196
public function replaceCSSVariable($matches) {
197
if (!$this->variableMap) {
198
$this->variableMap = $this->getCSSVariableMap();
199
}
200
201
$var_name = $matches[1];
202
if (empty($this->variableMap[$var_name])) {
203
$path = $this->currentPath;
204
throw new Exception(
205
pht(
206
"CSS file '%s' has unknown variable '%s'.",
207
$path,
208
$var_name));
209
}
210
211
return $this->variableMap[$var_name];
212
}
213
214
public function replaceCSSPrintRule($matches) {
215
$rule = $matches[1];
216
217
$rules = array();
218
$rules[] = '.printable '.$rule;
219
$rules[] = "@media print {\n ".str_replace("\n", "\n ", $rule)."\n}\n";
220
221
return implode("\n\n", $rules);
222
}
223
224
225
/**
226
* Attempt to generate a data URI for a resource. We'll generate a data URI
227
* if the resource is a valid resource of an appropriate type, and is
228
* small enough. Otherwise, this method will return `null` and we'll end up
229
* using a normal URI instead.
230
*
231
* @param string Resource name to attempt to generate a data URI for.
232
* @return string|null Data URI, or null if we declined to generate one.
233
*/
234
private function generateDataURI($resource_name) {
235
$ext = last(explode('.', $resource_name));
236
switch ($ext) {
237
case 'png':
238
$type = 'image/png';
239
break;
240
case 'gif':
241
$type = 'image/gif';
242
break;
243
case 'jpg':
244
$type = 'image/jpeg';
245
break;
246
default:
247
return null;
248
}
249
250
// In IE8, 32KB is the maximum supported URI length.
251
$maximum_data_size = (1024 * 32);
252
253
$data = $this->celerityMap->getResourceDataForName($resource_name);
254
if (strlen($data) >= $maximum_data_size) {
255
// If the data is already too large on its own, just bail before
256
// encoding it.
257
return null;
258
}
259
260
$uri = 'data:'.$type.';base64,'.base64_encode($data);
261
if (strlen($uri) >= $maximum_data_size) {
262
return null;
263
}
264
265
return $uri;
266
}
267
268
}
269
270