diff --git a/src/infrastructure/celerity/CelerityStaticResourceResponse.php b/src/infrastructure/celerity/CelerityStaticResourceResponse.php
index 21ad591e87..feba9ffac1 100644
--- a/src/infrastructure/celerity/CelerityStaticResourceResponse.php
+++ b/src/infrastructure/celerity/CelerityStaticResourceResponse.php
@@ -1,304 +1,304 @@
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(),
$source_name = 'phabricator') {
$this->requireResource('javelin-behavior-'.$behavior, $source_name);
if (empty($this->behaviors[$behavior])) {
$this->behaviors[$behavior] = array();
}
if ($config) {
$this->behaviors[$behavior][] = $config;
}
return $this;
}
public function requireResource($symbol, $source_name) {
if (isset($this->symbols[$source_name][$symbol])) {
return $this;
}
// Verify that the resource exists.
$map = CelerityResourceMap::getNamedInstance($source_name);
$name = $map->getResourceNameForSymbol($symbol);
if ($name === null) {
throw new Exception(
pht(
'No resource with symbol "%s" exists in source "%s"!',
$symbol,
$source_name));
}
$this->symbols[$source_name][$symbol] = true;
$this->needsResolve = true;
return $this;
}
private function resolveResources() {
if ($this->needsResolve) {
$this->packaged = array();
foreach ($this->symbols as $source_name => $symbols_map) {
$symbols = array_keys($symbols_map);
$map = CelerityResourceMap::getNamedInstance($source_name);
$packaged = $map->getPackagedNamesForSymbols($symbols);
$this->packaged[$source_name] = $packaged;
}
$this->needsResolve = false;
}
return $this;
}
public function renderSingleResource($symbol, $source_name) {
$map = CelerityResourceMap::getNamedInstance($source_name);
$packaged = $map->getPackagedNamesForSymbols(array($symbol));
return $this->renderPackagedResources($map, $packaged);
}
public function renderResourcesOfType($type) {
$this->resolveResources();
$result = array();
foreach ($this->packaged as $source_name => $resource_names) {
$map = CelerityResourceMap::getNamedInstance($source_name);
$resources_of_type = array();
foreach ($resource_names as $resource_name) {
$resource_type = $map->getResourceTypeForName($resource_name);
if ($resource_type == $type) {
$resources_of_type[] = $resource_name;
}
}
$result[] = $this->renderPackagedResources($map, $resources_of_type);
}
return phutil_implode_html('', $result);
}
private function renderPackagedResources(
CelerityResourceMap $map,
array $resources) {
$output = array();
foreach ($resources as $name) {
if (isset($this->hasRendered[$name])) {
continue;
}
$this->hasRendered[$name] = true;
$output[] = $this->renderResource($map, $name);
}
return $output;
}
private function renderResource(
CelerityResourceMap $map,
$name) {
$uri = $this->getURI($map, $name);
$type = $map->getResourceTypeForName($name);
switch ($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(
pht(
'Unable to render resource "%s", which has unknown type "%s".',
$name,
$type));
}
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',
'history-install',
);
$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 self::renderInlineScript($data);
} else {
return '';
}
}
public static function renderInlineScript($data) {
if (stripos($data, '') !== false) {
throw new Exception(
'Literal is not allowed inside inline script.');
}
if (strpos($data, ' because it is ignored by HTML parsers. We
// would need to send the document with XHTML content type.
return phutil_tag(
'script',
array('type' => 'text/javascript'),
phutil_safe_html($data));
}
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 $source_name => $resource_names) {
$map = CelerityResourceMap::getNamedInstance($source_name);
foreach ($resource_names as $resource_name) {
$resources[] = $this->getURI($map, $resource_name);
}
}
if ($resources) {
$response['javelin_resources'] = $resources;
}
return $response;
}
- private function getURI(
+ public function getURI(
CelerityResourceMap $map,
$name) {
$uri = $map->getURIForName($name);
// 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')) {
$mtime = $map->getModifiedTimeForName($name);
$uri = preg_replace('@^/res/@', '/res/'.$mtime.'T/', $uri);
}
return PhabricatorEnv::getCDNURI($uri);
}
}
diff --git a/src/view/page/PhabricatorStandardPageView.php b/src/view/page/PhabricatorStandardPageView.php
index 58f96579d0..093287eb6a 100644
--- a/src/view/page/PhabricatorStandardPageView.php
+++ b/src/view/page/PhabricatorStandardPageView.php
@@ -1,448 +1,453 @@
applicationMenu = $application_menu;
return $this;
}
public function getApplicationMenu() {
return $this->applicationMenu;
}
public function setApplicationName($application_name) {
$this->applicationName = $application_name;
return $this;
}
public function setDisableConsole($disable) {
$this->disableConsole = $disable;
return $this;
}
public function getApplicationName() {
return $this->applicationName;
}
public function setBaseURI($base_uri) {
$this->baseURI = $base_uri;
return $this;
}
public function getBaseURI() {
return $this->baseURI;
}
public function setShowChrome($show_chrome) {
$this->showChrome = $show_chrome;
return $this;
}
public function getShowChrome() {
return $this->showChrome;
}
public function appendPageObjects(array $objs) {
foreach ($objs as $obj) {
$this->pageObjects[] = $obj;
}
}
public function getTitle() {
$use_glyph = true;
$request = $this->getRequest();
if ($request) {
$user = $request->getUser();
if ($user && $user->loadPreferences()->getPreference(
PhabricatorUserPreferences::PREFERENCE_TITLES) !== 'glyph') {
$use_glyph = false;
}
}
$title = parent::getTitle();
$prefix = null;
if ($use_glyph) {
$prefix = $this->getGlyph();
} else {
$application_name = $this->getApplicationName();
if (strlen($application_name)) {
$prefix = '['.$application_name.']';
}
}
if (strlen($prefix)) {
$title = $prefix.' '.$title;
}
return $title;
}
protected function willRenderPage() {
parent::willRenderPage();
if (!$this->getRequest()) {
throw new Exception(
pht(
"You must set the Request to render a PhabricatorStandardPageView."));
}
$console = $this->getConsole();
require_celerity_resource('phabricator-core-css');
require_celerity_resource('phabricator-zindex-css');
require_celerity_resource('phui-button-css');
require_celerity_resource('phui-spacing-css');
require_celerity_resource('phui-form-css');
require_celerity_resource('sprite-gradient-css');
require_celerity_resource('phabricator-standard-page-view');
Javelin::initBehavior('workflow', array());
$request = $this->getRequest();
$user = null;
if ($request) {
$user = $request->getUser();
}
if ($user) {
$default_img_uri =
PhabricatorEnv::getCDNURI(
'/rsrc/image/icon/fatcow/document_black.png');
$download_form = phabricator_form(
$user,
array(
'action' => '#',
'method' => 'POST',
'class' => 'lightbox-download-form',
'sigil' => 'download',
),
phutil_tag(
'button',
array(),
pht('Download')));
Javelin::initBehavior(
'lightbox-attachments',
array(
'defaultImageUri' => $default_img_uri,
'downloadForm' => $download_form,
));
}
Javelin::initBehavior('aphront-form-disable-on-submit');
Javelin::initBehavior('toggle-class', array());
Javelin::initBehavior('konami', array());
Javelin::initBehavior('history-install');
Javelin::initBehavior('phabricator-gesture');
$current_token = null;
if ($user) {
$current_token = $user->getCSRFToken();
}
Javelin::initBehavior(
'refresh-csrf',
array(
'tokenName' => AphrontRequest::getCSRFTokenName(),
'header' => AphrontRequest::getCSRFHeaderName(),
'current' => $current_token,
));
Javelin::initBehavior('device');
if ($user->hasSession()) {
$hisec = ($user->getSession()->getHighSecurityUntil() - time());
if ($hisec > 0) {
$remaining_time = phabricator_format_relative_time($hisec);
Javelin::initBehavior(
'high-security-warning',
array(
'uri' => '/auth/session/downgrade/',
'message' => pht(
'Your session is in high security mode. When you '.
'finish using it, click here to leave.',
$remaining_time),
));
}
}
if ($console) {
require_celerity_resource('aphront-dark-console-css');
$headers = array();
if (DarkConsoleXHProfPluginAPI::isProfilerStarted()) {
$headers[DarkConsoleXHProfPluginAPI::getProfilerHeader()] = 'page';
}
if (DarkConsoleServicesPlugin::isQueryAnalyzerRequested()) {
$headers[DarkConsoleServicesPlugin::getQueryAnalyzerHeader()] = true;
}
Javelin::initBehavior(
'dark-console',
array(
// NOTE: We use a generic label here to prevent input reflection
// and mitigate compression attacks like BREACH. See discussion in
// T3684.
'uri' => pht('Main Request'),
'selected' => $user ? $user->getConsoleTab() : null,
'visible' => $user ? (int)$user->getConsoleVisible() : true,
'headers' => $headers,
));
// Change this to initBehavior when there is some behavior to initialize
require_celerity_resource('javelin-behavior-error-log');
}
if ($user) {
$viewer = $user;
} else {
$viewer = new PhabricatorUser();
}
$menu = id(new PhabricatorMainMenuView())
->setUser($viewer);
if ($this->getController()) {
$menu->setController($this->getController());
}
if ($this->getApplicationMenu()) {
$menu->setApplicationMenu($this->getApplicationMenu());
}
$this->menuContent = $menu->render();
}
protected function getHead() {
$monospaced = PhabricatorEnv::getEnvConfig('style.monospace');
$monospaced_win = PhabricatorEnv::getEnvConfig('style.monospace.windows');
$request = $this->getRequest();
if ($request) {
$user = $request->getUser();
if ($user) {
$pref = $user->loadPreferences()->getPreference(
PhabricatorUserPreferences::PREFERENCE_MONOSPACED);
$monospaced = nonempty($pref, $monospaced);
$monospaced_win = nonempty($pref, $monospaced_win);
}
}
$response = CelerityAPI::getStaticResourceResponse();
return hsprintf(
'%s%s',
parent::getHead(),
phutil_safe_html($monospaced),
phutil_safe_html($monospaced_win),
$response->renderSingleResource('javelin-magical-init', 'phabricator'));
}
public function setGlyph($glyph) {
$this->glyph = $glyph;
return $this;
}
public function getGlyph() {
return $this->glyph;
}
protected function willSendResponse($response) {
$request = $this->getRequest();
$response = parent::willSendResponse($response);
$console = $request->getApplicationConfiguration()->getConsole();
if ($console) {
$response = PhutilSafeHTML::applyFunction(
'str_replace',
hsprintf(''),
$console->render($request),
$response);
}
return $response;
}
protected function getBody() {
$console = $this->getConsole();
$user = null;
$request = $this->getRequest();
if ($request) {
$user = $request->getUser();
}
$header_chrome = null;
if ($this->getShowChrome()) {
$header_chrome = $this->menuContent;
}
$developer_warning = null;
if (PhabricatorEnv::getEnvConfig('phabricator.developer-mode') &&
DarkConsoleErrorLogPluginAPI::getErrors()) {
$developer_warning = phutil_tag_div(
'aphront-developer-error-callout',
pht(
'This page raised PHP errors. Find them in DarkConsole '.
'or the error log.'));
}
// Render the "you have unresolved setup issues..." warning.
$setup_warning = null;
if ($user && $user->getIsAdmin()) {
$open = PhabricatorSetupCheck::getOpenSetupIssueCount();
if ($open) {
$setup_warning = phutil_tag_div(
'setup-warning-callout',
phutil_tag(
'a',
array(
'href' => '/config/issue/',
),
pht('You have %d unresolved setup issue(s)...', $open)));
}
}
return
phutil_tag(
'div',
array(
'id' => 'base-page',
'class' => 'phabricator-standard-page',
),
array(
$developer_warning,
$setup_warning,
$header_chrome,
phutil_tag_div('phabricator-standard-page-body', array(
($console ? hsprintf('') : null),
parent::getBody(),
phutil_tag('div', array('style' => 'clear: both;')),
)),
));
}
protected function getTail() {
$request = $this->getRequest();
$user = $request->getUser();
$tail = array(
parent::getTail(),
);
+ $response = CelerityAPI::getStaticResourceResponse();
+
if (PhabricatorEnv::getEnvConfig('notification.enabled')) {
if ($user && $user->isLoggedIn()) {
$aphlict_object_id = celerity_generate_unique_node_id();
$aphlict_container_id = celerity_generate_unique_node_id();
$client_uri = PhabricatorEnv::getEnvConfig('notification.client-uri');
$client_uri = new PhutilURI($client_uri);
if ($client_uri->getDomain() == 'localhost') {
$this_host = $this->getRequest()->getHost();
$this_host = new PhutilURI('http://'.$this_host.'/');
$client_uri->setDomain($this_host->getDomain());
}
+ $map = CelerityResourceMap::getNamedInstance('phabricator');
+ $swf_uri = $response->getURI($map, 'rsrc/swf/aphlict.swf');
+
$enable_debug = PhabricatorEnv::getEnvConfig('notification.debug');
Javelin::initBehavior(
'aphlict-listen',
array(
'id' => $aphlict_object_id,
'containerID' => $aphlict_container_id,
'server' => $client_uri->getDomain(),
'port' => $client_uri->getPort(),
'debug' => $enable_debug,
+ 'swfURI' => $swf_uri,
'pageObjects' => array_fill_keys($this->pageObjects, true),
));
$tail[] = phutil_tag(
'div',
array(
'id' => $aphlict_container_id,
'style' =>
'position: absolute; width: 0; height: 0; overflow: hidden;',
),
'');
}
}
- $response = CelerityAPI::getStaticResourceResponse();
$tail[] = $response->renderHTMLFooter();
return $tail;
}
protected function getBodyClasses() {
$classes = array();
if (!$this->getShowChrome()) {
$classes[] = 'phabricator-chromeless-page';
}
$agent = AphrontRequest::getHTTPHeader('User-Agent');
// Try to guess the device resolution based on UA strings to avoid a flash
// of incorrectly-styled content.
$device_guess = 'device-desktop';
if (preg_match('@iPhone|iPod|(Android.*Chrome/[.0-9]* Mobile)@', $agent)) {
$device_guess = 'device-phone device';
} else if (preg_match('@iPad|(Android.*Chrome/)@', $agent)) {
$device_guess = 'device-tablet device';
}
$classes[] = $device_guess;
if (preg_match('@Windows@', $agent)) {
$classes[] = 'platform-windows';
} else if (preg_match('@Macintosh@', $agent)) {
$classes[] = 'platform-mac';
} else if (preg_match('@X11@', $agent)) {
$classes[] = 'platform-linux';
}
if ($this->getRequest()->getStr('__print__')) {
$classes[] = 'printable';
}
if ($this->getRequest()->getStr('__aural__')) {
$classes[] = 'audible';
}
return implode(' ', $classes);
}
private function getConsole() {
if ($this->disableConsole) {
return null;
}
return $this->getRequest()->getApplicationConfiguration()->getConsole();
}
}
diff --git a/webroot/rsrc/js/application/aphlict/behavior-aphlict-listen.js b/webroot/rsrc/js/application/aphlict/behavior-aphlict-listen.js
index 284534b262..84c221b6ac 100644
--- a/webroot/rsrc/js/application/aphlict/behavior-aphlict-listen.js
+++ b/webroot/rsrc/js/application/aphlict/behavior-aphlict-listen.js
@@ -1,108 +1,108 @@
/**
* @provides javelin-behavior-aphlict-listen
* @requires javelin-behavior
* javelin-aphlict
* javelin-stratcom
* javelin-request
* javelin-uri
* javelin-dom
* javelin-json
* javelin-router
* phabricator-notification
*/
JX.behavior('aphlict-listen', function(config) {
var showing_reload = false;
function onready() {
var client = new JX.Aphlict(config.id, config.server, config.port)
.setHandler(onaphlictmessage)
.start();
}
// Respond to a notification from the Aphlict notification server. We send
// a request to Phabricator to get notification details.
function onaphlictmessage(type, message) {
switch (type) {
case 'error':
new JX.Notification()
.setContent('(Aphlict) ' + message)
.alterClassName('jx-notification-error', true)
.show();
break;
case 'receive':
var request = new JX.Request(
'/notification/individual/',
onnotification);
var routable = request
.addData({key: message.key})
.getRoutable();
routable
.setType('notification')
.setPriority(250);
JX.Router.getInstance().queue(routable);
break;
default:
if (__DEV__ && config.debug) {
var details = message ? JX.JSON.stringify(message) : '';
new JX.Notification()
.setContent('(Aphlict) [' + type + '] ' + details)
.alterClassName('jx-notification-debug', true)
.setDuration(3000)
.show();
}
}
}
// Respond to a response from Phabricator about a specific notification.
function onnotification(response) {
if (!response.pertinent) {
return;
}
JX.Stratcom.invoke('notification-panel-update', null, {});
// Show the notification itself.
new JX.Notification()
.setContent(JX.$H(response.content))
.show();
// If the notification affected an object on this page, show a
// permanent reload notification if we aren't already.
if ((response.primaryObjectPHID in config.pageObjects) && !showing_reload) {
var reload = new JX.Notification()
.setContent('Page updated, click to reload.')
.alterClassName('jx-notification-alert', true)
.setDuration(0);
reload.listen('activate', function(e) { JX.$U().go(); });
reload.show();
showing_reload = true;
}
}
// Wait for the element to load, and don't do anything if it never loads.
// If we just go crazy and start making calls to it before it loads, its
// interfaces won't be registered yet.
JX.Stratcom.listen('aphlict-component-ready', null, onready);
// Add Flash object to page
JX.$(config.containerID).innerHTML =
''; //Evan sanctioned
});