1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-11-14 19:02:41 +01:00
phorge-phorge/src/infrastructure/celerity/CelerityStaticResourceResponse.php
2013-02-13 14:51:18 -08:00

248 lines
6.7 KiB
PHP

<?php
/**
* Tracks and resolves dependencies the page declares with
* @{function:require_celerity_resource}, and then builds appropriate HTML or
* Ajax responses.
*
* @group celerity
*/
final class CelerityStaticResourceResponse {
private $symbols = array();
private $needsResolve = true;
private $resolved;
private $packaged;
private $metadata = array();
private $metadataBlock = 0;
private $behaviors = array();
private $hasRendered = array();
public function __construct() {
if (isset($_REQUEST['__metablock__'])) {
$this->metadataBlock = (int)$_REQUEST['__metablock__'];
}
}
public function addMetadata($metadata) {
$id = count($this->metadata);
$this->metadata[$id] = $metadata;
return $this->metadataBlock.'_'.$id;
}
public function getMetadataBlock() {
return $this->metadataBlock;
}
/**
* Register a behavior for initialization. NOTE: if $config is empty,
* a behavior will execute only once even if it is initialized multiple times.
* If $config is nonempty, the behavior will be invoked once for each config.
*/
public function initBehavior($behavior, array $config = array()) {
$this->requireResource('javelin-behavior-'.$behavior);
if (empty($this->behaviors[$behavior])) {
$this->behaviors[$behavior] = array();
}
if ($config) {
$this->behaviors[$behavior][] = $config;
}
return $this;
}
public function requireResource($symbol) {
$this->symbols[$symbol] = true;
$this->needsResolve = true;
return $this;
}
private function resolveResources() {
if ($this->needsResolve) {
$map = CelerityResourceMap::getInstance();
$this->resolved = $map->resolveResources(array_keys($this->symbols));
$this->packaged = $map->packageResources($this->resolved);
$this->needsResolve = false;
}
return $this;
}
public function renderSingleResource($symbol) {
$map = CelerityResourceMap::getInstance();
$resolved = $map->resolveResources(array($symbol));
$packaged = $map->packageResources($resolved);
return $this->renderPackagedResources($packaged);
}
public function renderResourcesOfType($type) {
$this->resolveResources();
$resources = array();
foreach ($this->packaged as $resource) {
if ($resource['type'] == $type) {
$resources[] = $resource;
}
}
return $this->renderPackagedResources($resources);
}
private function renderPackagedResources(array $resources) {
$output = array();
foreach ($resources as $resource) {
if (isset($this->hasRendered[$resource['uri']])) {
continue;
}
$this->hasRendered[$resource['uri']] = true;
$output[] = $this->renderResource($resource);
$output[] = "\n";
}
return phutil_implode_html('', $output);
}
private function renderResource(array $resource) {
$uri = $this->getURI($resource);
switch ($resource['type']) {
case 'css':
return phutil_tag(
'link',
array(
'rel' => 'stylesheet',
'type' => 'text/css',
'href' => $uri,
));
case 'js':
return phutil_tag(
'script',
array(
'type' => 'text/javascript',
'src' => $uri,
),
'');
}
throw new Exception("Unable to render resource.");
}
public function renderHTMLFooter() {
$data = array();
if ($this->metadata) {
$json_metadata = AphrontResponse::encodeJSONForHTTPResponse(
$this->metadata);
$this->metadata = array();
} else {
$json_metadata = '{}';
}
// Even if there is no metadata on the page, Javelin uses the mergeData()
// call to start dispatching the event queue.
$data[] = 'JX.Stratcom.mergeData('.$this->metadataBlock.', '.
$json_metadata.');';
$onload = array();
if ($this->behaviors) {
$behaviors = $this->behaviors;
$this->behaviors = array();
$higher_priority_names = array(
'refresh-csrf',
'aphront-basic-tokenizer',
'dark-console',
);
$higher_priority_behaviors = array_select_keys(
$behaviors,
$higher_priority_names);
foreach ($higher_priority_names as $name) {
unset($behaviors[$name]);
}
$behavior_groups = array(
$higher_priority_behaviors,
$behaviors);
foreach ($behavior_groups as $group) {
if (!$group) {
continue;
}
$group_json = AphrontResponse::encodeJSONForHTTPResponse(
$group);
$onload[] = 'JX.initBehaviors('.$group_json.')';
}
}
if ($onload) {
foreach ($onload as $func) {
$data[] = 'JX.onload(function(){'.$func.'});';
}
}
if ($data) {
$data = implode("\n", $data);
return hsprintf(
'<script type="text/javascript">//<![CDATA['."\n".'%s//]]></script>',
phutil_safe_html($data));
} else {
return '';
}
}
public function buildAjaxResponse($payload, $error = null) {
$response = array(
'error' => $error,
'payload' => $payload,
);
if ($this->metadata) {
$response['javelin_metadata'] = $this->metadata;
$this->metadata = array();
}
if ($this->behaviors) {
$response['javelin_behaviors'] = $this->behaviors;
$this->behaviors = array();
}
$this->resolveResources();
$resources = array();
foreach ($this->packaged as $resource) {
$resources[] = $this->getURI($resource);
}
if ($resources) {
$response['javelin_resources'] = $resources;
}
return $response;
}
private function getURI($resource) {
$uri = $resource['uri'];
// In developer mode, we dump file modification times into the URI. When a
// page is reloaded in the browser, any resources brought in by Ajax calls
// do not trigger revalidation, so without this it's very difficult to get
// changes to Ajaxed-in CSS to work (you must clear your cache or rerun
// the map script). In production, we can assume the map script gets run
// after changes, and safely skip this.
if (PhabricatorEnv::getEnvConfig('phabricator.developer-mode')) {
$root = dirname(phutil_get_library_root('phabricator')).'/webroot/';
if (isset($resource['disk'])) {
$mtime = (int)filemtime($root.$resource['disk']);
} else {
$mtime = 0;
foreach ($resource['symbols'] as $symbol) {
$map = CelerityResourceMap::getInstance();
$symbol_info = $map->lookupSymbolInformation($symbol);
$mtime = max($mtime, (int)filemtime($root.$symbol_info['disk']));
}
}
$uri = preg_replace('@^/res/@', '/res/'.$mtime.'T/', $uri);
}
return PhabricatorEnv::getCDNURI($uri);
}
}