[ Index ] |
PHP Cross Reference of Phabricator |
[Summary view] [Print] [Text view]
1 <?php 2 3 /** 4 * Tracks and resolves dependencies the page declares with 5 * @{function:require_celerity_resource}, and then builds appropriate HTML or 6 * Ajax responses. 7 */ 8 final class CelerityStaticResourceResponse { 9 10 private $symbols = array(); 11 private $needsResolve = true; 12 private $resolved; 13 private $packaged; 14 private $metadata = array(); 15 private $metadataBlock = 0; 16 private $behaviors = array(); 17 private $hasRendered = array(); 18 19 public function __construct() { 20 if (isset($_REQUEST['__metablock__'])) { 21 $this->metadataBlock = (int)$_REQUEST['__metablock__']; 22 } 23 } 24 25 public function addMetadata($metadata) { 26 $id = count($this->metadata); 27 $this->metadata[$id] = $metadata; 28 return $this->metadataBlock.'_'.$id; 29 } 30 31 public function getMetadataBlock() { 32 return $this->metadataBlock; 33 } 34 35 /** 36 * Register a behavior for initialization. NOTE: if $config is empty, 37 * a behavior will execute only once even if it is initialized multiple times. 38 * If $config is nonempty, the behavior will be invoked once for each config. 39 */ 40 public function initBehavior( 41 $behavior, 42 array $config = array(), 43 $source_name) { 44 45 $this->requireResource('javelin-behavior-'.$behavior, $source_name); 46 47 if (empty($this->behaviors[$behavior])) { 48 $this->behaviors[$behavior] = array(); 49 } 50 51 if ($config) { 52 $this->behaviors[$behavior][] = $config; 53 } 54 55 return $this; 56 } 57 58 public function requireResource($symbol, $source_name) { 59 if (isset($this->symbols[$source_name][$symbol])) { 60 return $this; 61 } 62 63 // Verify that the resource exists. 64 $map = CelerityResourceMap::getNamedInstance($source_name); 65 $name = $map->getResourceNameForSymbol($symbol); 66 if ($name === null) { 67 throw new Exception( 68 pht( 69 'No resource with symbol "%s" exists in source "%s"!', 70 $symbol, 71 $source_name)); 72 } 73 74 $this->symbols[$source_name][$symbol] = true; 75 $this->needsResolve = true; 76 77 return $this; 78 } 79 80 private function resolveResources() { 81 if ($this->needsResolve) { 82 $this->packaged = array(); 83 foreach ($this->symbols as $source_name => $symbols_map) { 84 $symbols = array_keys($symbols_map); 85 86 $map = CelerityResourceMap::getNamedInstance($source_name); 87 $packaged = $map->getPackagedNamesForSymbols($symbols); 88 89 $this->packaged[$source_name] = $packaged; 90 } 91 $this->needsResolve = false; 92 } 93 return $this; 94 } 95 96 public function renderSingleResource($symbol, $source_name) { 97 $map = CelerityResourceMap::getNamedInstance($source_name); 98 $packaged = $map->getPackagedNamesForSymbols(array($symbol)); 99 return $this->renderPackagedResources($map, $packaged); 100 } 101 102 public function renderResourcesOfType($type) { 103 $this->resolveResources(); 104 105 $result = array(); 106 foreach ($this->packaged as $source_name => $resource_names) { 107 $map = CelerityResourceMap::getNamedInstance($source_name); 108 109 $resources_of_type = array(); 110 foreach ($resource_names as $resource_name) { 111 $resource_type = $map->getResourceTypeForName($resource_name); 112 if ($resource_type == $type) { 113 $resources_of_type[] = $resource_name; 114 } 115 } 116 117 $result[] = $this->renderPackagedResources($map, $resources_of_type); 118 } 119 120 return phutil_implode_html('', $result); 121 } 122 123 private function renderPackagedResources( 124 CelerityResourceMap $map, 125 array $resources) { 126 127 $output = array(); 128 foreach ($resources as $name) { 129 if (isset($this->hasRendered[$name])) { 130 continue; 131 } 132 $this->hasRendered[$name] = true; 133 134 $output[] = $this->renderResource($map, $name); 135 } 136 137 return $output; 138 } 139 140 private function renderResource( 141 CelerityResourceMap $map, 142 $name) { 143 144 $uri = $this->getURI($map, $name); 145 $type = $map->getResourceTypeForName($name); 146 147 switch ($type) { 148 case 'css': 149 return phutil_tag( 150 'link', 151 array( 152 'rel' => 'stylesheet', 153 'type' => 'text/css', 154 'href' => $uri, 155 )); 156 case 'js': 157 return phutil_tag( 158 'script', 159 array( 160 'type' => 'text/javascript', 161 'src' => $uri, 162 ), 163 ''); 164 } 165 166 throw new Exception( 167 pht( 168 'Unable to render resource "%s", which has unknown type "%s".', 169 $name, 170 $type)); 171 } 172 173 public function renderHTMLFooter() { 174 $data = array(); 175 if ($this->metadata) { 176 $json_metadata = AphrontResponse::encodeJSONForHTTPResponse( 177 $this->metadata); 178 $this->metadata = array(); 179 } else { 180 $json_metadata = '{}'; 181 } 182 // Even if there is no metadata on the page, Javelin uses the mergeData() 183 // call to start dispatching the event queue. 184 $data[] = 'JX.Stratcom.mergeData('.$this->metadataBlock.', '. 185 $json_metadata.');'; 186 187 $onload = array(); 188 if ($this->behaviors) { 189 $behaviors = $this->behaviors; 190 $this->behaviors = array(); 191 192 $higher_priority_names = array( 193 'refresh-csrf', 194 'aphront-basic-tokenizer', 195 'dark-console', 196 'history-install', 197 ); 198 199 $higher_priority_behaviors = array_select_keys( 200 $behaviors, 201 $higher_priority_names); 202 203 foreach ($higher_priority_names as $name) { 204 unset($behaviors[$name]); 205 } 206 207 $behavior_groups = array( 208 $higher_priority_behaviors, 209 $behaviors, 210 ); 211 212 foreach ($behavior_groups as $group) { 213 if (!$group) { 214 continue; 215 } 216 $group_json = AphrontResponse::encodeJSONForHTTPResponse( 217 $group); 218 $onload[] = 'JX.initBehaviors('.$group_json.')'; 219 } 220 } 221 222 if ($onload) { 223 foreach ($onload as $func) { 224 $data[] = 'JX.onload(function(){'.$func.'});'; 225 } 226 } 227 228 if ($data) { 229 $data = implode("\n", $data); 230 return self::renderInlineScript($data); 231 } else { 232 return ''; 233 } 234 } 235 236 public static function renderInlineScript($data) { 237 if (stripos($data, '</script>') !== false) { 238 throw new Exception( 239 'Literal </script> is not allowed inside inline script.'); 240 } 241 if (strpos($data, '<!') !== false) { 242 throw new Exception('Literal <! is not allowed inside inline script.'); 243 } 244 // We don't use <![CDATA[ ]]> because it is ignored by HTML parsers. We 245 // would need to send the document with XHTML content type. 246 return phutil_tag( 247 'script', 248 array('type' => 'text/javascript'), 249 phutil_safe_html($data)); 250 } 251 252 public function buildAjaxResponse($payload, $error = null) { 253 $response = array( 254 'error' => $error, 255 'payload' => $payload, 256 ); 257 258 if ($this->metadata) { 259 $response['javelin_metadata'] = $this->metadata; 260 $this->metadata = array(); 261 } 262 263 if ($this->behaviors) { 264 $response['javelin_behaviors'] = $this->behaviors; 265 $this->behaviors = array(); 266 } 267 268 $this->resolveResources(); 269 $resources = array(); 270 foreach ($this->packaged as $source_name => $resource_names) { 271 $map = CelerityResourceMap::getNamedInstance($source_name); 272 foreach ($resource_names as $resource_name) { 273 $resources[] = $this->getURI($map, $resource_name); 274 } 275 } 276 if ($resources) { 277 $response['javelin_resources'] = $resources; 278 } 279 280 return $response; 281 } 282 283 public function getURI( 284 CelerityResourceMap $map, 285 $name, 286 $use_primary_domain = false) { 287 288 $uri = $map->getURIForName($name); 289 290 // In developer mode, we dump file modification times into the URI. When a 291 // page is reloaded in the browser, any resources brought in by Ajax calls 292 // do not trigger revalidation, so without this it's very difficult to get 293 // changes to Ajaxed-in CSS to work (you must clear your cache or rerun 294 // the map script). In production, we can assume the map script gets run 295 // after changes, and safely skip this. 296 if (PhabricatorEnv::getEnvConfig('phabricator.developer-mode')) { 297 $mtime = $map->getModifiedTimeForName($name); 298 $uri = preg_replace('@^/res/@', '/res/'.$mtime.'T/', $uri); 299 } 300 301 if ($use_primary_domain) { 302 return PhabricatorEnv::getURI($uri); 303 } else { 304 return PhabricatorEnv::getCDNURI($uri); 305 } 306 } 307 308 }
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 |