[ Index ] |
PHP Cross Reference of Phabricator |
[Summary view] [Print] [Text view]
1 <?php 2 3 final class CelerityResourceTransformer { 4 5 private $minify; 6 private $rawURIMap; 7 private $celerityMap; 8 private $translateURICallback; 9 private $currentPath; 10 11 public function setTranslateURICallback($translate_uricallback) { 12 $this->translateURICallback = $translate_uricallback; 13 return $this; 14 } 15 16 public function setMinify($minify) { 17 $this->minify = $minify; 18 return $this; 19 } 20 21 public function setCelerityMap(CelerityResourceMap $celerity_map) { 22 $this->celerityMap = $celerity_map; 23 return $this; 24 } 25 26 public function setRawURIMap(array $raw_urimap) { 27 $this->rawURIMap = $raw_urimap; 28 return $this; 29 } 30 31 public function getRawURIMap() { 32 return $this->rawURIMap; 33 } 34 35 /** 36 * @phutil-external-symbol function jsShrink 37 */ 38 public function transformResource($path, $data) { 39 $type = self::getResourceType($path); 40 41 switch ($type) { 42 case 'css': 43 $data = $this->replaceCSSPrintRules($path, $data); 44 $data = $this->replaceCSSVariables($path, $data); 45 $data = preg_replace_callback( 46 '@url\s*\((\s*[\'"]?.*?)\)@s', 47 nonempty( 48 $this->translateURICallback, 49 array($this, 'translateResourceURI')), 50 $data); 51 break; 52 } 53 54 if (!$this->minify) { 55 return $data; 56 } 57 58 // Some resources won't survive minification (like Raphael.js), and are 59 // marked so as not to be minified. 60 if (strpos($data, '@'.'do-not-minify') !== false) { 61 return $data; 62 } 63 64 switch ($type) { 65 case 'css': 66 // Remove comments. 67 $data = preg_replace('@/\*.*?\*/@s', '', $data); 68 // Remove whitespace around symbols. 69 $data = preg_replace('@\s*([{}:;,])\s*@', '\1', $data); 70 // Remove unnecessary semicolons. 71 $data = preg_replace('@;}@', '}', $data); 72 // Replace #rrggbb with #rgb when possible. 73 $data = preg_replace( 74 '@#([a-f0-9])\1([a-f0-9])\2([a-f0-9])\3@i', 75 '#\1\2\3', 76 $data); 77 $data = trim($data); 78 break; 79 case 'js': 80 81 // If `jsxmin` is available, use it. jsxmin is the Javelin minifier and 82 // produces the smallest output, but is complicated to build. 83 if (Filesystem::binaryExists('jsxmin')) { 84 $future = new ExecFuture('jsxmin __DEV__:0'); 85 $future->write($data); 86 list($err, $result) = $future->resolve(); 87 if (!$err) { 88 $data = $result; 89 break; 90 } 91 } 92 93 // If `jsxmin` is not available, use `JsShrink`, which doesn't compress 94 // quite as well but is always available. 95 $root = dirname(phutil_get_library_root('phabricator')); 96 require_once $root.'/externals/JsShrink/jsShrink.php'; 97 $data = jsShrink($data); 98 99 break; 100 } 101 102 return $data; 103 } 104 105 public static function getResourceType($path) { 106 return last(explode('.', $path)); 107 } 108 109 public function translateResourceURI(array $matches) { 110 $uri = trim($matches[1], "'\" \r\t\n"); 111 $tail = ''; 112 113 // If the resource URI has a query string or anchor, strip it off before 114 // we go looking for the resource. We'll stitch it back on later. This 115 // primarily affects FontAwesome. 116 117 $parts = preg_split('/(?=[?#])/', $uri, 2); 118 if (count($parts) == 2) { 119 $uri = $parts[0]; 120 $tail = $parts[1]; 121 } 122 123 $alternatives = array_unique( 124 array( 125 $uri, 126 ltrim($uri, '/'), 127 )); 128 129 foreach ($alternatives as $alternative) { 130 if ($this->rawURIMap !== null) { 131 if (isset($this->rawURIMap[$alternative])) { 132 $uri = $this->rawURIMap[$alternative]; 133 break; 134 } 135 } 136 137 if ($this->celerityMap) { 138 $resource_uri = $this->celerityMap->getURIForName($alternative); 139 if ($resource_uri) { 140 // Check if we can use a data URI for this resource. If not, just 141 // use a normal Celerity URI. 142 $data_uri = $this->generateDataURI($alternative); 143 if ($data_uri) { 144 $uri = $data_uri; 145 } else { 146 $uri = $resource_uri; 147 } 148 break; 149 } 150 } 151 } 152 153 return 'url('.$uri.$tail.')'; 154 } 155 156 private function replaceCSSVariables($path, $data) { 157 $this->currentPath = $path; 158 return preg_replace_callback( 159 '/{\$([^}]+)}/', 160 array($this, 'replaceCSSVariable'), 161 $data); 162 } 163 164 private function replaceCSSPrintRules($path, $data) { 165 $this->currentPath = $path; 166 return preg_replace_callback( 167 '/!print\s+(.+?{.+?})/s', 168 array($this, 'replaceCSSPrintRule'), 169 $data); 170 } 171 172 public static function getCSSVariableMap() { 173 return array( 174 // Base Colors 175 'red' => '#c0392b', 176 'lightred' => '#f4dddb', 177 'orange' => '#e67e22', 178 'lightorange' => '#f7e2d4', 179 'yellow' => '#f1c40f', 180 'lightyellow' => '#fdf5d4', 181 'green' => '#139543', 182 'lightgreen' => '#d7eddf', 183 'blue' => '#2980b9', 184 'lightblue' => '#daeaf3', 185 'sky' => '#3498db', 186 'lightsky' => '#ddeef9', 187 'indigo' => '#6e5cb6', 188 'lightindigo' => '#eae6f7', 189 'pink' => '#da49be', 190 'lightpink' => '#fbeaf8', 191 'violet' => '#8e44ad', 192 'lightviolet' => '#ecdff1', 193 'charcoal' => '#4b4d51', 194 'backdrop' => '#dadee7', 195 'hovergrey' => '#c5cbcf', 196 'hoverblue' => '#eceff5', 197 'hoverborder' => '#dfe1e9', 198 'hoverselectedgrey' => '#bbc4ca', 199 'hoverselectedblue' => '#e6e9ee', 200 201 // Base Greys 202 'lightgreyborder' => '#C7CCD9', 203 'greyborder' => '#A1A6B0', 204 'darkgreyborder' => '#676A70', 205 'lightgreytext' => '#92969D', 206 'greytext' => '#74777D', 207 'darkgreytext' => '#4B4D51', 208 'lightgreybackground' => '#F7F7F7', 209 'greybackground' => '#EBECEE', 210 'darkgreybackground' => '#DFE0E2', 211 212 // Base Blues 213 'thinblueborder' => '#DDE8EF', 214 'lightblueborder' => '#BFCFDA', 215 'blueborder' => '#8C98B8', 216 'darkblueborder' => '#626E82', 217 'lightbluebackground' => '#F8F9FC', 218 'bluebackground' => '#DAE7FF', 219 'lightbluetext' => '#8C98B8', 220 'bluetext' => '#6B748C', 221 'darkbluetext' => '#464C5C', 222 223 // Base Greens 224 'lightgreenborder' => '#bfdac1', 225 'greenborder' => '#8cb89c', 226 'greentext' => '#3e6d35', 227 'lightgreenbackground' => '#e6f2e4', 228 229 // Base Red 230 'lightredborder' => '#f4c6c6', 231 'redborder' => '#eb9797', 232 'redtext' => '#802b2b', 233 'lightredbackground' => '#f5e1e1', 234 235 // Base Violet 236 'lightvioletborder' => '#cfbddb', 237 'violetborder' => '#b589ba', 238 'violettext' => '#603c73', 239 'lightvioletbackground' => '#e9dfee', 240 241 // Shades are a more muted set of our base colors 242 // better suited to blending into other UIs. 243 244 // Shade Red 245 'sh-lightredborder' => '#efcfcf', 246 'sh-redborder' => '#d1abab', 247 'sh-redicon' => '#c85a5a', 248 'sh-redtext' => '#a53737', 249 'sh-redbackground' => '#f7e6e6', 250 251 // Shade Orange 252 'sh-lightorangeborder' => '#f8dcc3', 253 'sh-orangeborder' => '#dbb99e', 254 'sh-orangeicon' => '#e78331', 255 'sh-orangetext' => '#ba6016', 256 'sh-orangebackground' => '#fbede1', 257 258 // Shade Yellow 259 'sh-lightyellowborder' => '#e9dbcd', 260 'sh-yellowborder' => '#c9b8a8', 261 'sh-yellowicon' => '#9b946e', 262 'sh-yellowtext' => '#726f56', 263 'sh-yellowbackground' => '#fdf3da', 264 265 // Shade Green 266 'sh-lightgreenborder' => '#c6e6c7', 267 'sh-greenborder' => '#a0c4a1', 268 'sh-greenicon' => '#4ca74e', 269 'sh-greentext' => '#326d34', 270 'sh-greenbackground' => '#ddefdd', 271 272 // Shade Blue 273 'sh-lightblueborder' => '#cfdbe3', 274 'sh-blueborder' => '#a7b5bf', 275 'sh-blueicon' => '#6b748c', 276 'sh-bluetext' => '#464c5c', 277 'sh-bluebackground' => '#dee7f8', 278 279 // Shade Indigo 280 'sh-lightindigoborder' => '#d1c9ee', 281 'sh-indigoborder' => '#bcb4da', 282 'sh-indigoicon' => '#8672d4', 283 'sh-indigotext' => '#6e5cb6', 284 'sh-indigobackground' => '#eae6f7', 285 286 // Shade Violet 287 'sh-lightvioletborder' => '#e0d1e7', 288 'sh-violetborder' => '#bcabc5', 289 'sh-violeticon' => '#9260ad', 290 'sh-violettext' => '#69427f', 291 'sh-violetbackground' => '#efe8f3', 292 293 // Shade Pink 294 'sh-lightpinkborder' => '#f6d5ef', 295 'sh-pinkborder' => '#d5aecd', 296 'sh-pinkicon' => '#e26fcb', 297 'sh-pinktext' => '#da49be', 298 'sh-pinkbackground' => '#fbeaf8', 299 300 // Shade Grey 301 'sh-lightgreyborder' => '#d8d8d8', 302 'sh-greyborder' => '#b2b2b2', 303 'sh-greyicon' => '#757575', 304 'sh-greytext' => '#555555', 305 'sh-greybackground' => '#e7e7e7', 306 307 // Shade Disabled 308 'sh-lightdisabledborder' => '#e5e5e5', 309 'sh-disabledborder' => '#cbcbcb', 310 'sh-disabledicon' => '#bababa', 311 'sh-disabledtext' => '#a6a6a6', 312 'sh-disabledbackground' => '#f3f3f3', 313 314 ); 315 } 316 317 318 public function replaceCSSVariable($matches) { 319 static $map; 320 if (!$map) { 321 $map = self::getCSSVariableMap(); 322 } 323 324 $var_name = $matches[1]; 325 if (empty($map[$var_name])) { 326 $path = $this->currentPath; 327 throw new Exception( 328 "CSS file '{$path}' has unknown variable '{$var_name}'."); 329 } 330 331 return $map[$var_name]; 332 } 333 334 public function replaceCSSPrintRule($matches) { 335 $rule = $matches[1]; 336 337 $rules = array(); 338 $rules[] = '.printable '.$rule; 339 $rules[] = "@media print {\n ".str_replace("\n", "\n ", $rule)."\n}\n"; 340 341 return implode("\n\n", $rules); 342 } 343 344 345 /** 346 * Attempt to generate a data URI for a resource. We'll generate a data URI 347 * if the resource is a valid resource of an appropriate type, and is 348 * small enough. Otherwise, this method will return `null` and we'll end up 349 * using a normal URI instead. 350 * 351 * @param string Resource name to attempt to generate a data URI for. 352 * @return string|null Data URI, or null if we declined to generate one. 353 */ 354 private function generateDataURI($resource_name) { 355 $ext = last(explode('.', $resource_name)); 356 switch ($ext) { 357 case 'png': 358 $type = 'image/png'; 359 break; 360 case 'gif': 361 $type = 'image/gif'; 362 break; 363 case 'jpg': 364 $type = 'image/jpeg'; 365 break; 366 default: 367 return null; 368 } 369 370 // In IE8, 32KB is the maximum supported URI length. 371 $maximum_data_size = (1024 * 32); 372 373 $data = $this->celerityMap->getResourceDataForName($resource_name); 374 if (strlen($data) >= $maximum_data_size) { 375 // If the data is already too large on its own, just bail before 376 // encoding it. 377 return null; 378 } 379 380 $uri = 'data:'.$type.';base64,'.base64_encode($data); 381 if (strlen($uri) >= $maximum_data_size) { 382 return null; 383 } 384 385 return $uri; 386 } 387 388 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Sun Nov 30 09:20:46 2014 | Cross-referenced by PHPXref 0.7.1 |