diff --git a/scripts/celerity_mapper.php b/scripts/celerity_mapper.php index a7a388c087..d281da41ce 100755 --- a/scripts/celerity_mapper.php +++ b/scripts/celerity_mapper.php @@ -1,102 +1,160 @@ #!/usr/bin/env php array( + 'phabricator-core-css', + 'phabricator-core-buttons-css', + 'phabricator-standard-page-view', + 'aphront-dialog-view-css', + 'aphront-form-view-css', + 'aphront-panel-view-css', + 'aphront-side-nav-view-css', + 'aphront-table-view-css', + 'aphront-tokenizer-control-css', + 'aphront-typeahead-control-css', + + 'phabricator-directory-css', + ), + 'differential.pkg.css' => array( + 'differential-core-view-css', + 'differential-changeset-view-css', + 'differential-revision-detail-css', + 'differential-revision-history-css', + 'differential-table-of-contents-css', + ), +); + + require_once dirname(__FILE__).'/__init_script__.php'; if ($argc != 2) { $self = basename($argv[0]); echo "usage: {$self} \n"; exit(1); } phutil_require_module('phutil', 'filesystem'); phutil_require_module('phutil', 'filesystem/filefinder'); phutil_require_module('phutil', 'future/exec'); phutil_require_module('phutil', 'parser/docblock'); $root = Filesystem::resolvePath($argv[1]); echo "Finding static resources...\n"; $files = id(new FileFinder($root)) ->withType('f') ->withSuffix('js') ->withSuffix('css') ->setGenerateChecksums(true) ->find(); echo "Processing ".count($files)." files"; $file_map = array(); foreach ($files as $path => $hash) { echo "."; $name = '/'.Filesystem::readablePath($path, $root); $file_map[$name] = array( 'hash' => $hash, 'disk' => $path, ); } echo "\n"; $runtime_map = array(); +$hash_map = array(); + $parser = new PhutilDocblockParser(); foreach ($file_map as $path => $info) { $data = Filesystem::readFile($info['disk']); $matches = array(); $ok = preg_match('@/[*][*].*?[*]/@s', $data, $matches); if (!$ok) { throw new Exception( "File {$path} does not have a header doc comment. Encode dependency ". "data in a header docblock."); } list($description, $metadata) = $parser->parse($matches[0]); $provides = preg_split('/\s+/', trim(idx($metadata, 'provides'))); $requires = preg_split('/\s+/', trim(idx($metadata, 'requires'))); $provides = array_filter($provides); $requires = array_filter($requires); if (count($provides) !== 1) { throw new Exception( "File {$path} must @provide exactly one Celerity target."); } $provides = reset($provides); $type = 'js'; if (preg_match('/\.css$/', $path)) { $type = 'css'; } - $path = '/res/'.substr($info['hash'], 0, 8).$path; + $uri = '/res/'.substr($info['hash'], 0, 8).$path; + + $hash_map[$provides] = $info['hash']; $runtime_map[$provides] = array( - 'path' => $path, + 'uri' => $uri, 'type' => $type, 'requires' => $requires, + 'disk' => $path, + ); +} + +$package_map = array(); +foreach ($package_spec as $name => $package) { + $hashes = array(); + foreach ($package as $symbol) { + if (empty($hash_map[$symbol])) { + throw new Exception( + "Package specification for '{$name}' includes '{$symbol}', but that ". + "symbol is not defined anywhere."); + } + $hashes[] = $symbol.':'.$hash_map[$symbol]; + } + $key = substr(md5(implode("\n", $hashes)), 0, 8); + $package_map['packages'][$key] = array( + 'name' => $name, + 'symbols' => $package, + 'uri' => '/res/pkg/'.$key.'/'.$name, + 'type' => 'css', // TODO LOL ); + foreach ($package as $symbol) { + $package_map['reverse'][$symbol] = $key; + } } + $runtime_map = var_export($runtime_map, true); $runtime_map = preg_replace('/\s+$/m', '', $runtime_map); $runtime_map = preg_replace('/array \(/', 'array(', $runtime_map); +$package_map = var_export($package_map, true); +$pacakge_map = preg_replace('/\s+$/m', '', $package_map); +$package_map = preg_replace('/array \(/', 'array(', $package_map); + $resource_map = << array( - 'path' => '/res/771b987d/rsrc/css/aphront/dialog-view.css', + 'uri' => '/res/771b987d/rsrc/css/aphront/dialog-view.css', 'type' => 'css', 'requires' => array( ), + 'disk' => '/rsrc/css/aphront/dialog-view.css', ), 'aphront-form-view-css' => array( - 'path' => '/res/785ac1c6/rsrc/css/aphront/form-view.css', + 'uri' => '/res/785ac1c6/rsrc/css/aphront/form-view.css', 'type' => 'css', 'requires' => array( ), + 'disk' => '/rsrc/css/aphront/form-view.css', ), 'aphront-panel-view-css' => array( - 'path' => '/res/fe62e634/rsrc/css/aphront/panel-view.css', + 'uri' => '/res/fe62e634/rsrc/css/aphront/panel-view.css', 'type' => 'css', 'requires' => array( ), + 'disk' => '/rsrc/css/aphront/panel-view.css', ), 'aphront-side-nav-view-css' => array( - 'path' => '/res/0fc0545c/rsrc/css/aphront/side-nav-view.css', + 'uri' => '/res/0fc0545c/rsrc/css/aphront/side-nav-view.css', 'type' => 'css', 'requires' => array( ), + 'disk' => '/rsrc/css/aphront/side-nav-view.css', ), 'aphront-table-view-css' => array( - 'path' => '/res/52b0191f/rsrc/css/aphront/table-view.css', + 'uri' => '/res/52b0191f/rsrc/css/aphront/table-view.css', 'type' => 'css', 'requires' => array( ), + 'disk' => '/rsrc/css/aphront/table-view.css', ), 'aphront-tokenizer-control-css' => array( - 'path' => '/res/a3d23074/rsrc/css/aphront/tokenizer.css', + 'uri' => '/res/a3d23074/rsrc/css/aphront/tokenizer.css', 'type' => 'css', 'requires' => array( 0 => 'aphront-typeahead-control-css', ), + 'disk' => '/rsrc/css/aphront/tokenizer.css', ), 'aphront-typeahead-control-css' => array( - 'path' => '/res/928df9f0/rsrc/css/aphront/typeahead.css', + 'uri' => '/res/928df9f0/rsrc/css/aphront/typeahead.css', 'type' => 'css', 'requires' => array( ), + 'disk' => '/rsrc/css/aphront/typeahead.css', ), 'phabricator-standard-page-view' => array( - 'path' => '/res/1f93ada7/rsrc/css/application/base/standard-page-view.css', + 'uri' => '/res/1f93ada7/rsrc/css/application/base/standard-page-view.css', 'type' => 'css', 'requires' => array( ), + 'disk' => '/rsrc/css/application/base/standard-page-view.css', ), 'differential-changeset-view-css' => array( - 'path' => '/res/658d181a/rsrc/css/application/differential/changeset-view.css', + 'uri' => '/res/658d181a/rsrc/css/application/differential/changeset-view.css', 'type' => 'css', 'requires' => array( ), + 'disk' => '/rsrc/css/application/differential/changeset-view.css', ), 'differential-core-view-css' => array( - 'path' => '/res/525d1a12/rsrc/css/application/differential/core.css', + 'uri' => '/res/525d1a12/rsrc/css/application/differential/core.css', 'type' => 'css', 'requires' => array( ), + 'disk' => '/rsrc/css/application/differential/core.css', ), 'differential-revision-detail-css' => array( - 'path' => '/res/11a36dad/rsrc/css/application/differential/revision-detail.css', + 'uri' => '/res/230a67c6/rsrc/css/application/differential/revision-detail.css', 'type' => 'css', 'requires' => array( ), + 'disk' => '/rsrc/css/application/differential/revision-detail.css', ), 'differential-revision-history-css' => array( - 'path' => '/res/755f3da3/rsrc/css/application/differential/revision-history.css', + 'uri' => '/res/755f3da3/rsrc/css/application/differential/revision-history.css', 'type' => 'css', 'requires' => array( ), + 'disk' => '/rsrc/css/application/differential/revision-history.css', ), 'differential-table-of-contents-css' => array( - 'path' => '/res/a4a7b2b5/rsrc/css/application/differential/table-of-contents.css', + 'uri' => '/res/a4a7b2b5/rsrc/css/application/differential/table-of-contents.css', 'type' => 'css', 'requires' => array( ), + 'disk' => '/rsrc/css/application/differential/table-of-contents.css', ), 'phabricator-directory-css' => array( - 'path' => '/res/6a000601/rsrc/css/application/directory/phabricator-directory.css', + 'uri' => '/res/6a000601/rsrc/css/application/directory/phabricator-directory.css', 'type' => 'css', 'requires' => array( ), + 'disk' => '/rsrc/css/application/directory/phabricator-directory.css', ), 'phabricator-core-buttons-css' => array( - 'path' => '/res/6e348ba4/rsrc/css/core/buttons.css', + 'uri' => '/res/6e348ba4/rsrc/css/core/buttons.css', 'type' => 'css', 'requires' => array( ), + 'disk' => '/rsrc/css/core/buttons.css', ), 'phabricator-core-css' => array( - 'path' => '/res/39ce37c2/rsrc/css/core/core.css', + 'uri' => '/res/39ce37c2/rsrc/css/core/core.css', 'type' => 'css', 'requires' => array( ), + 'disk' => '/rsrc/css/core/core.css', ), 'syntax-highlighting-css' => array( - 'path' => '/res/fb673ece/rsrc/css/core/syntax.css', + 'uri' => '/res/fb673ece/rsrc/css/core/syntax.css', 'type' => 'css', 'requires' => array( ), + 'disk' => '/rsrc/css/core/syntax.css', ), 'javelin-behavior-aphront-basic-tokenizer' => array( - 'path' => '/res/8317d761/rsrc/js/application/core/behavior-tokenizer.js', + 'uri' => '/res/8317d761/rsrc/js/application/core/behavior-tokenizer.js', 'type' => 'js', 'requires' => array( 0 => 'javelin-lib-dev', ), + 'disk' => '/rsrc/js/application/core/behavior-tokenizer.js', ), 'javelin-behavior-differential-populate' => array( - 'path' => '/res/9982573c/rsrc/js/application/differential/behavior-populate.js', + 'uri' => '/res/9982573c/rsrc/js/application/differential/behavior-populate.js', 'type' => 'js', 'requires' => array( 0 => 'javelin-lib-dev', ), + 'disk' => '/rsrc/js/application/differential/behavior-populate.js', ), 'javelin-init-dev' => array( - 'path' => '/res/c57a9e89/rsrc/js/javelin/init.dev.js', + 'uri' => '/res/c57a9e89/rsrc/js/javelin/init.dev.js', 'type' => 'js', 'requires' => array( ), + 'disk' => '/rsrc/js/javelin/init.dev.js', ), 'javelin-init-prod' => array( - 'path' => '/res/f0172c54/rsrc/js/javelin/init.min.js', + 'uri' => '/res/f0172c54/rsrc/js/javelin/init.min.js', 'type' => 'js', 'requires' => array( ), + 'disk' => '/rsrc/js/javelin/init.min.js', ), 'javelin-lib-dev' => array( - 'path' => '/res/3e747182/rsrc/js/javelin/javelin.dev.js', + 'uri' => '/res/3e747182/rsrc/js/javelin/javelin.dev.js', 'type' => 'js', 'requires' => array( ), + 'disk' => '/rsrc/js/javelin/javelin.dev.js', ), 'javelin-lib-prod' => array( - 'path' => '/res/9438670e/rsrc/js/javelin/javelin.min.js', + 'uri' => '/res/9438670e/rsrc/js/javelin/javelin.min.js', 'type' => 'js', 'requires' => array( ), + 'disk' => '/rsrc/js/javelin/javelin.min.js', ), 'javelin-typeahead-dev' => array( - 'path' => '/res/c81c0f01/rsrc/js/javelin/typeahead.dev.js', + 'uri' => '/res/c81c0f01/rsrc/js/javelin/typeahead.dev.js', 'type' => 'js', 'requires' => array( ), + 'disk' => '/rsrc/js/javelin/typeahead.dev.js', ), 'javelin-typeahead-prod' => array( - 'path' => '/res/1da2d984/rsrc/js/javelin/typeahead.min.js', + 'uri' => '/res/1da2d984/rsrc/js/javelin/typeahead.min.js', 'type' => 'js', 'requires' => array( ), + 'disk' => '/rsrc/js/javelin/typeahead.min.js', + ), +), array ( + 'packages' => + array ( + '4efe7b58' => + array ( + 'name' => 'core.pkg.css', + 'symbols' => + array ( + 0 => 'phabricator-core-css', + 1 => 'phabricator-core-buttons-css', + 2 => 'phabricator-standard-page-view', + 3 => 'aphront-dialog-view-css', + 4 => 'aphront-form-view-css', + 5 => 'aphront-panel-view-css', + 6 => 'aphront-side-nav-view-css', + 7 => 'aphront-table-view-css', + 8 => 'aphront-tokenizer-control-css', + 9 => 'aphront-typeahead-control-css', + 10 => 'phabricator-directory-css', + ), + 'uri' => '/res/pkg/4efe7b58/core.pkg.css', + 'type' => 'css', + ), + '69b11588' => + array ( + 'name' => 'differential.pkg.css', + 'symbols' => + array ( + 0 => 'differential-core-view-css', + 1 => 'differential-changeset-view-css', + 2 => 'differential-revision-detail-css', + 3 => 'differential-revision-history-css', + 4 => 'differential-table-of-contents-css', + ), + 'uri' => '/res/pkg/69b11588/differential.pkg.css', + 'type' => 'css', + ), + ), + 'reverse' => + array ( + 'phabricator-core-css' => '4efe7b58', + 'phabricator-core-buttons-css' => '4efe7b58', + 'phabricator-standard-page-view' => '4efe7b58', + 'aphront-dialog-view-css' => '4efe7b58', + 'aphront-form-view-css' => '4efe7b58', + 'aphront-panel-view-css' => '4efe7b58', + 'aphront-side-nav-view-css' => '4efe7b58', + 'aphront-table-view-css' => '4efe7b58', + 'aphront-tokenizer-control-css' => '4efe7b58', + 'aphront-typeahead-control-css' => '4efe7b58', + 'phabricator-directory-css' => '4efe7b58', + 'differential-core-view-css' => '69b11588', + 'differential-changeset-view-css' => '69b11588', + 'differential-revision-detail-css' => '69b11588', + 'differential-revision-history-css' => '69b11588', + 'differential-table-of-contents-css' => '69b11588', ), )); diff --git a/src/aphront/default/configuration/AphrontDefaultApplicationConfiguration.php b/src/aphront/default/configuration/AphrontDefaultApplicationConfiguration.php index 9f482deb13..a5553423ee 100644 --- a/src/aphront/default/configuration/AphrontDefaultApplicationConfiguration.php +++ b/src/aphront/default/configuration/AphrontDefaultApplicationConfiguration.php @@ -1,159 +1,159 @@ array( '$' => 'RepositoryListController', 'new/$' => 'RepositoryEditController', 'edit/(?\d+)/$' => 'RepositoryEditController', 'delete/(?\d+)/$' => 'RepositoryDeleteController', ), '/' => array( '$' => 'PhabricatorDirectoryMainController', ), '/directory/' => array( 'item/$' => 'PhabricatorDirectoryItemListController', 'item/edit/(?:(?\d+)/)?$' => 'PhabricatorDirectoryItemEditController', 'item/delete/(?\d+)/' => 'PhabricatorDirectoryItemDeleteController', 'category/$' => 'PhabricatorDirectoryCategoryListController', 'category/edit/(?:(?\d+)/)?$' => 'PhabricatorDirectoryCategoryEditController', 'category/delete/(?\d+)/' => 'PhabricatorDirectoryCategoryDeleteController', ), '/file/' => array( '$' => 'PhabricatorFileListController', 'upload/$' => 'PhabricatorFileUploadController', '(?info)/(?[^/]+)/' => 'PhabricatorFileViewController', '(?view)/(?[^/]+)/' => 'PhabricatorFileViewController', '(?download)/(?[^/]+)/' => 'PhabricatorFileViewController', ), '/phid/' => array( '$' => 'PhabricatorPHIDLookupController', 'list/$' => 'PhabricatorPHIDListController', 'type/$' => 'PhabricatorPHIDTypeListController', 'type/edit/(?:(?\d+)/)?$' => 'PhabricatorPHIDTypeEditController', 'new/$' => 'PhabricatorPHIDAllocateController', ), '/people/' => array( '$' => 'PhabricatorPeopleListController', 'edit/(?:(?\w+)/)?$' => 'PhabricatorPeopleEditController', ), '/p/(?\w+)/$' => 'PhabricatorPeopleProfileController', '/conduit/' => array( '$' => 'PhabricatorConduitConsoleController', 'method/(?[^/]+)$' => 'PhabricatorConduitConsoleController', 'log/$' => 'PhabricatorConduitLogController', ), '/api/(?[^/]+)$' => 'PhabricatorConduitAPIController', '/D(?\d+)' => 'DifferentialRevisionViewController', '/differential/' => array( '$' => 'DifferentialRevisionListController', 'filter/(?\w+)/$' => 'DifferentialRevisionListController', 'diff/(?\d+)/$' => 'DifferentialDiffViewController', 'changeset/(?\d+)/$' => 'DifferentialChangesetViewController', 'revision/edit/(?:(?\d+)/)?$' => 'DifferentialRevisionEditController', ), '/res/' => array( - '(?[a-f0-9]{8})/(?.+\.(?:css|js))$' + '(?pkg/)?(?[a-f0-9]{8})/(?.+\.(?:css|js))$' => 'CelerityResourceController', ), '/typeahead/' => array( 'common/(?\w+)/$' => 'PhabricatorTypeaheadCommonDatasourceController', ), '/mail/' => array( '$' => 'PhabricatorMetaMTAListController', 'send/$' => 'PhabricatorMetaMTASendController', 'view/(?\d+)/$' => 'PhabricatorMetaMTAViewController', 'lists/$' => 'PhabricatorMetaMTAMailingListsController', 'lists/edit/(?:(?\d+)/)?$' => 'PhabricatorMetaMTAMailingListEditController', ), '/login/' => 'PhabricatorLoginController', ); } public function buildRequest() { $request = new AphrontRequest($this->getHost(), $this->getPath()); $request->setRequestData($_GET + $_POST); return $request; } public function handleException(Exception $ex) { $class = phutil_escape_html(get_class($ex)); $message = phutil_escape_html($ex->getMessage()); $content = '
'. '

Unhandled Exception "'.$class.'": '.$message.'

'. ''.phutil_escape_html((string)$ex).''. '
'; $view = new PhabricatorStandardPageView(); $view->appendChild($content); $response = new AphrontWebpageResponse(); $response->setContent($view->render()); return $response; } public function willSendResponse(AphrontResponse $response) { $request = $this->getRequest(); if ($response instanceof AphrontDialogResponse) { if (!$request->isAjax()) { $view = new PhabricatorStandardPageView(); $view->appendChild( '
'. $response->buildResponseString(). '
'); $response = new AphrontWebpageResponse(); $response->setContent($view->render()); return $response; } } return $response; } } diff --git a/src/infratructure/celerity/controller/CelerityResourceController.php b/src/infratructure/celerity/controller/CelerityResourceController.php index 8634cf2ed4..198a23cc04 100644 --- a/src/infratructure/celerity/controller/CelerityResourceController.php +++ b/src/infratructure/celerity/controller/CelerityResourceController.php @@ -1,66 +1,85 @@ path = $data['path']; $this->hash = $data['hash']; + $this->package = !empty($data['package']); } public function processRequest() { $path = $this->path; // Sanity checking to keep this from exposing anything sensitive. $path = preg_replace('@(//|\\.\\.)@', '', $path); $matches = null; if (!preg_match('/\.(css|js)$/', $path, $matches)) { throw new Exception("Only CSS and JS resources may be served."); } - + $type = $matches[1]; - $root = dirname(phutil_get_library_root('phabricator')); - try { - $data = Filesystem::readFile($root.'/webroot/'.$path); - } catch (Exception $ex) { - return new Aphront404Response(); + if ($this->package) { + $map = CelerityResourceMap::getInstance(); + $paths = $map->resolvePackage($this->hash); + if (!$paths) { + return new Aphront404Response(); + } + + try { + $data = array(); + foreach ($paths as $path) { + $data[] = Filesystem::readFile($root.'/webroot/'.$path); + } + $data = implode("\n\n", $data); + } catch (Exception $ex) { + return new Aphront404Response(); + } + } else { + try { + $data = Filesystem::readFile($root.'/webroot/'.$path); + } catch (Exception $ex) { + return new Aphront404Response(); + } } $response = new AphrontFileResponse(); $response->setContent($data); switch ($type) { case 'css': $response->setMimeType("text/css; charset=utf-8"); break; case 'js': $response->setMimeType("text/javascript; charset=utf-8"); break; } $response->setCacheDurationInSeconds(60 * 60 * 24 * 30); return $response; } } diff --git a/src/infratructure/celerity/map/CelerityResourceMap.php b/src/infratructure/celerity/map/CelerityResourceMap.php index ee3ea1ca17..6ea9d8bdb9 100644 --- a/src/infratructure/celerity/map/CelerityResourceMap.php +++ b/src/infratructure/celerity/map/CelerityResourceMap.php @@ -1,75 +1,116 @@ resourceMap = $resource_map; return $this; } public function resolveResources(array $symbols) { $map = array(); foreach ($symbols as $symbol) { if (!empty($map[$symbol])) { continue; } $this->resolveResource($map, $symbol); } return $map; } private function resolveResource(array &$map, $symbol) { if (empty($this->resourceMap[$symbol])) { throw new Exception( "Attempting to resolve unknown resource, '{$symbol}'."); } $info = $this->resourceMap[$symbol]; foreach ($info['requires'] as $requires) { if (!empty($map[$requires])) { continue; } $this->resolveResource($map, $requires); } $map[$symbol] = $info; } + + public function setPackageMap($package_map) { + $this->packageMap = $package_map; + } + + public function packageResources(array $resolved_map) { + $packaged = array(); + $handled = array(); + foreach ($resolved_map as $symbol => $info) { + if (isset($handled[$symbol])) { + continue; + } + if (empty($this->packageMap['reverse'][$symbol])) { + $packaged[$symbol] = $info; + } else { + $package = $this->packageMap['reverse'][$symbol]; + $package_info = $this->packageMap['packages'][$package]; + $packaged[$package_info['name']] = $package_info; + foreach ($package_info['symbols'] as $symbol) { + $handled[$symbol] = true; + } + } + } + return $packaged; + } + + public function resolvePackage($package_hash) { + $package = idx($this->packageMap['packages'], $package_hash); + if (!$package) { + return null; + } + + $paths = array(); + foreach ($package['symbols'] as $symbol) { + $paths[] = $this->resourceMap[$symbol]['disk']; + } + + return $paths; + } } -function celerity_register_resource_map(array $map) { +function celerity_register_resource_map(array $map, array $package_map) { $instance = CelerityResourceMap::getInstance(); $instance->setResourceMap($map); + $instance->setPackageMap($package_map); } diff --git a/src/infratructure/celerity/response/CelerityStaticResourceResponse.php b/src/infratructure/celerity/response/CelerityStaticResourceResponse.php index 2207602a8a..c8956afaaa 100644 --- a/src/infratructure/celerity/response/CelerityStaticResourceResponse.php +++ b/src/infratructure/celerity/response/CelerityStaticResourceResponse.php @@ -1,145 +1,147 @@ 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; } public function initBehavior($behavior, array $config = array()) { $this->requireResource('javelin-behavior-'.$behavior); $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 renderResourcesOfType($type) { $this->resolveResources(); $output = array(); - foreach ($this->resolved as $resource) { + foreach ($this->packaged as $resource) { if ($resource['type'] == $type) { $output[] = $this->renderResource($resource); } } return implode("\n", $output); } private function renderResource(array $resource) { switch ($resource['type']) { case 'css': - $path = phutil_escape_html($resource['path']); + $path = phutil_escape_html($resource['uri']); return ''; case 'js': - $path = phutil_escape_html($resource['path']); + $path = phutil_escape_html($resource['uri']); return ''; } throw new Exception("Unable to render resource."); } public function renderHTMLFooter() { $data = array(); if ($this->metadata) { $json_metadata = json_encode($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) { $behavior = json_encode($this->behaviors); $onload[] = 'JX.initBehaviors('.$behavior.')'; $this->behaviors = array(); } if ($onload) { foreach ($onload as $func) { $data[] = 'JX.onload(function(){'.$func.'});'; } } if ($data) { $data = implode("\n", $data); return ''; } else { return ''; } } public function renderAjaxResponse($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(); } $response = 'for (;;);'.json_encode($response); return $response; } }