[ Index ]

PHP Cross Reference of Phabricator

title

Body

[close]

/src/applications/celerity/ -> CelerityStaticResourceResponse.php (source)

   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  }


Generated: Sun Nov 30 09:20:46 2014 Cross-referenced by PHPXref 0.7.1