/** * This is more of an acceptance test case instead of a unit test. It verifies * that the Celerity map is up-to-date. */ public function testCelerityMaps() { $resources_map = CelerityPhysicalResources::getAll(); foreach ($resources_map as $resources) { $old_map = new CelerityResourceMap($resources); $new_map = id(new CelerityResourceMapGenerator($resources))->generate(); // Don't actually compare these values with assertEqual(), since the diff // isn't helpful and is often enormously huge. $maps_are_identical = $new_map->getNameMap() === $old_map->getNameMap() && $new_map->getSymbolMap() === $old_map->getSymbolMap() && $new_map->getRequiresMap() === $old_map->getRequiresMap() && $new_map->getPackageMap() === $old_map->getPackageMap(); $this->assertTrue($maps_are_identical, pht('When this test fails, it means the Celerity resource map is out ' . 'of date. Run `%s` to rebuild it.', 'bin/celerity map')); } }
/** * Get the versioned URI for a raw resource, like an image. * * @param string Path to the raw image. * @return string Versioned path to the image, if one is available. */ function celerity_get_resource_uri($resource, $source = 'phabricator') { $resource = ltrim($resource, '/'); $map = CelerityResourceMap::getNamedInstance($source); $response = CelerityAPI::getStaticResourceResponse(); return $response->getURI($map, $resource); }
public function processRequest() { $root = dirname(phutil_get_library_root('phabricator')); require_once $root . '/support/phame/libskin.php'; $this->cssResources = array(); $css = $this->getPath('css/'); if (Filesystem::pathExists($css)) { foreach (Filesystem::listDirectory($css) as $path) { if (!preg_match('/.css$/', $path)) { continue; } $this->cssResources[] = phutil_tag('link', array('rel' => 'stylesheet', 'type' => 'text/css', 'href' => $this->getResourceURI('css/' . $path))); } } $map = CelerityResourceMap::getNamedInstance('phabricator'); $resource_symbol = 'syntax-highlighting-css'; $resource_uri = $map->getURIForSymbol($resource_symbol); $this->cssResources[] = phutil_tag('link', array('rel' => 'stylesheet', 'type' => 'text/css', 'href' => PhabricatorEnv::getCDNURI($resource_uri))); $this->cssResources = phutil_implode_html("\n", $this->cssResources); $request = $this->getRequest(); // Render page parts in order so the templates execute in order, if we're // using templates. $header = $this->renderHeader(); $content = $this->renderContent($request); $footer = $this->renderFooter(); if (!$content) { $content = $this->render404Page(); } $content = array($header, $content, $footer); $response = new AphrontWebpageResponse(); $response->setContent(phutil_implode_html("\n", $content)); return $response; }
public function renderSingleResource($symbol) { $map = CelerityResourceMap::getInstance(); $resolved = $map->resolveResources(array($symbol)); $packaged = $map->packageResources($resolved); return $this->renderPackagedResources($packaged); }
/** * Get the versioned URI for a raw resource, like an image. * * @param string Path to the raw image. * @return string Versioned path to the image, if one is available. * * @group celerity */ function celerity_get_resource_uri($resource) { $map = CelerityResourceMap::getInstance(); $info = $map->lookupFileInformation($resource); if ($info) { return $info['uri']; } else { return $resource; } }
public static function getInstance() { if (empty(self::$instance)) { self::$instance = new CelerityResourceMap(); $root = phutil_get_library_root('phabricator'); $ok = (include_once $root . '/__celerity_resource_map__.php'); if (!$ok) { throw new Exception("Failed to load Celerity resource map!"); } } return self::$instance; }
public static function getInstance() { if (empty(self::$instance)) { self::$instance = new CelerityResourceMap(); $root = phutil_get_library_root('phabricator'); $path = PhabricatorEnv::getEnvConfig('celerity.resource-path'); $ok = (include_once $root . '/' . $path); if (!$ok) { throw new Exception("Failed to load Celerity resource map! Check the " . "'celerity.resource-path' setting in your configuration."); } } return self::$instance; }
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."); } if (isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) && !PhabricatorEnv::getEnvConfig('celerity.force-disk-reads')) { // Return a "304 Not Modified". We don't care about the value of this // field since we never change what resource is served by a given URI. return $this->makeResponseCacheable(new Aphront304Response()); } $type = $matches[1]; $root = dirname(phutil_get_library_root('phabricator')); 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; } return $this->makeResponseCacheable($response); }
public function processRequest() { $path = $this->path; // Sanity checking to keep this from exposing anything sensitive, since it // ultimately boils down to disk reads. if (preg_match('@(//|\\.\\.)@', $path)) { return new Aphront400Response(); } $type = CelerityResourceTransformer::getResourceType($path); $type_map = $this->getSupportedResourceTypes(); if (empty($type_map[$type])) { throw new Exception("Only static resources may be served."); } if (isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) && !PhabricatorEnv::getEnvConfig('celerity.force-disk-reads')) { // Return a "304 Not Modified". We don't care about the value of this // field since we never change what resource is served by a given URI. return $this->makeResponseCacheable(new Aphront304Response()); } $root = dirname(phutil_get_library_root('phabricator')); if ($this->package) { $map = CelerityResourceMap::getInstance(); $paths = $map->resolvePackage($this->hash); if (!$paths) { return new Aphront404Response(); } try { $data = array(); foreach ($paths as $package_path) { $data[] = Filesystem::readFile($root . '/webroot/' . $package_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(); } } $xformer = new CelerityResourceTransformer(); $xformer->setMinify(PhabricatorEnv::getEnvConfig('celerity.minify')); $xformer->setCelerityMap(CelerityResourceMap::getInstance()); $data = $xformer->transformResource($path, $data); $response = new AphrontFileResponse(); $response->setContent($data); $response->setMimeType($type_map[$type]); return $this->makeResponseCacheable($response); }
/** * NOTE: This is an ADVANCED feature that improves performance but adds a lot * of complexity! This is only suitable for production servers because workers * won't pick up changes between when they spawn and when they handle a request. * * Phabricator spends a significant portion of its runtime loading classes * and functions, even with APC enabled. Since we have very rigidly-defined * rules about what can go in a module (specifically: no side effects), it * is safe to load all the libraries *before* we receive a request. * * Normally, SAPIs don't provide a way to do this, but with a patched PHP-FPM * SAPI you can provide a warmup file that it will execute before a request * is received. * * We're limited in what we can do here, since request information won't * exist yet, but we can load class and function definitions, which is what * we're really interested in. * * Once this file exists, the FCGI process will drop into its normal accept loop * and eventually process a request. */ function __warmup__() { $root = dirname(dirname(dirname(dirname(__FILE__)))); require_once $root . '/libphutil/src/__phutil_library_init__.php'; require_once $root . '/arcanist/src/__phutil_library_init__.php'; require_once $root . '/phabricator/src/__phutil_library_init__.php'; // Load every symbol. We could possibly refine this -- we don't need to load // every Controller, for instance. $loader = new PhutilSymbolLoader(); $loader->selectAndLoadSymbols(); // Force load of the Celerity map. CelerityResourceMap::getInstance(); define('__WARMUP__', true); }
public function getURI(CelerityResourceMap $map, $name, $use_primary_domain = false) { $uri = $map->getURIForName($name); // If we have a postprocessor selected, add it to the URI. $postprocessor_key = $this->getPostprocessorKey(); if ($postprocessor_key) { $uri = preg_replace('@^/res/@', '/res/' . $postprocessor_key . 'X/', $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')) { $mtime = $map->getModifiedTimeForName($name); $uri = preg_replace('@^/res/@', '/res/' . $mtime . 'T/', $uri); } if ($use_primary_domain) { return PhabricatorEnv::getURI($uri); } else { return PhabricatorEnv::getCDNURI($uri); } }
public function getCelerityResourceMap() { return CelerityResourceMap::getNamedInstance($this->library); }
public function lintPath($path) { if ($this->shouldIgnorePath($path)) { return; } if (!$this->symbolsBinary) { if (!$this->haveWarnedAboutBinary) { $this->haveWarnedAboutBinary = true; // TODO: Write build documentation for the Javelin binaries and point // the user at it. $this->raiseLintAtLine(1, 0, self::LINT_MISSING_BINARY, pht("The '%s' binary in the Javelin project is not available in %s, " . "so the Javelin linter can't run. This isn't a big concern, " . "but means some Javelin problems can't be automatically detected.", 'javelinsymbols', '$PATH')); } return; } list($uses, $installs) = $this->getUsedAndInstalledSymbolsForPath($path); foreach ($uses as $symbol => $line) { $parts = explode('.', $symbol); foreach ($parts as $part) { if ($part[0] == '_' && $part[1] != '_') { $base = implode('.', array_slice($parts, 0, 2)); if (!array_key_exists($base, $installs)) { $this->raiseLintAtLine($line, 0, self::LINT_PRIVATE_ACCESS, pht("This file accesses private symbol '%s' across file " . "boundaries. You may only access private members and methods " . "from the file where they are defined.", $symbol)); } break; } } } $external_classes = array(); foreach ($uses as $symbol => $line) { $parts = explode('.', $symbol); $class = implode('.', array_slice($parts, 0, 2)); if (!array_key_exists($class, $external_classes) && !array_key_exists($class, $installs)) { $external_classes[$class] = $line; } } $celerity = CelerityResourceMap::getNamedInstance('phabricator'); $path = preg_replace('@^externals/javelinjs/src/@', 'webroot/rsrc/js/javelin/', $path); $need = $external_classes; $resource_name = substr($path, strlen('webroot/')); $requires = $celerity->getRequiredSymbolsForName($resource_name); if (!$requires) { $requires = array(); } foreach ($requires as $key => $requires_symbol) { $requires_name = $celerity->getResourceNameForSymbol($requires_symbol); if ($requires_name === null) { $this->raiseLintAtLine(0, 0, self::LINT_UNKNOWN_DEPENDENCY, pht("This file %s component '%s', but it does not exist. " . "You may need to rebuild the Celerity map.", '@requires', $requires_symbol)); unset($requires[$key]); continue; } if (preg_match('/\\.css$/', $requires_name)) { // If JS requires CSS, just assume everything is fine. unset($requires[$key]); } else { $symbol_path = 'webroot/' . $requires_name; list($ignored, $req_install) = $this->getUsedAndInstalledSymbolsForPath($symbol_path); if (array_intersect_key($req_install, $external_classes)) { $need = array_diff_key($need, $req_install); unset($requires[$key]); } } } foreach ($need as $class => $line) { $this->raiseLintAtLine($line, 0, self::LINT_MISSING_DEPENDENCY, pht("This file uses '%s' but does not @requires the component " . "which installs it. You may need to rebuild the Celerity map.", $class)); } foreach ($requires as $component) { $this->raiseLintAtLine(0, 0, self::LINT_UNNECESSARY_DEPENDENCY, pht("This file %s component '%s' but does not use anything it provides.", '@requires', $component)); } }
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', true); $enable_debug = PhabricatorEnv::getEnvConfig('notification.debug'); $subscriptions = $this->pageObjects; if ($user) { $subscriptions[] = $user->getPHID(); } 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), 'subscriptions' => $subscriptions)); $tail[] = phutil_tag('div', array('id' => $aphlict_container_id, 'style' => 'position: absolute; width: 0; height: 0; overflow: hidden;'), ''); } } $tail[] = $response->renderHTMLFooter(); return $tail; }
public function lintPath($path) { if (!$this->haveSymbolsBinary) { if (!$this->haveWarnedAboutBinary) { $this->haveWarnedAboutBinary = true; // TODO: Write build documentation for the Javelin binaries and point // the user at it. $this->raiseLintAtLine(1, 0, self::LINT_MISSING_BINARY, "The 'javelinsymbols' binary in the Javelin project has not been " . "built, so the Javelin linter can't run. This isn't a big concern, " . "but means some Javelin problems can't be automatically detected."); } return; } list($uses, $installs) = $this->getUsedAndInstalledSymbolsForPath($path); foreach ($uses as $symbol => $line) { $parts = explode('.', $symbol); foreach ($parts as $part) { if ($part[0] == '_' && $part[1] != '_') { $base = implode('.', array_slice($parts, 0, 2)); if (!array_key_exists($base, $installs)) { $this->raiseLintAtLine($line, 0, self::LINT_PRIVATE_ACCESS, "This file accesses private symbol '{$symbol}' across file " . "boundaries. You may only access private members and methods " . "from the file where they are defined."); } break; } } } if ($this->getEngine()->getCommitHookMode()) { // Don't do the dependency checks in commit-hook mode because we won't // have an available working copy. return; } $external_classes = array(); foreach ($uses as $symbol => $line) { $parts = explode('.', $symbol); $class = implode('.', array_slice($parts, 0, 2)); if (!array_key_exists($class, $external_classes) && !array_key_exists($class, $installs)) { $external_classes[$class] = $line; } } $celerity = CelerityResourceMap::getInstance(); $path = preg_replace('@^externals/javelin/src/@', 'webroot/rsrc/js/javelin/', $path); $need = $external_classes; $info = $celerity->lookupFileInformation(substr($path, strlen('webroot'))); if (!$info) { $info = array(); } $requires = idx($info, 'requires', array()); foreach ($requires as $key => $name) { $symbol_info = $celerity->lookupSymbolInformation($name); if (!$symbol_info) { $this->raiseLintAtLine(0, 0, self::LINT_UNKNOWN_DEPENDENCY, "This file @requires component '{$name}', but it does not " . "exist. You may need to rebuild the Celerity map."); unset($requires[$key]); continue; } $symbol_path = 'webroot' . $symbol_info['disk']; list($ignored, $req_install) = $this->getUsedAndInstalledSymbolsForPath($symbol_path); if (array_intersect_key($req_install, $external_classes)) { $need = array_diff_key($need, $req_install); unset($requires[$key]); } } foreach ($need as $class => $line) { $this->raiseLintAtLine($line, 0, self::LINT_MISSING_DEPENDENCY, "This file uses '{$class}' but does not @requires the component " . "which installs it. You may need to rebuild the Celerity map."); } foreach ($requires as $component) { $this->raiseLintAtLine(0, 0, self::LINT_UNNECESSARY_DEPENDENCY, "This file @requires component '{$component}' but does not use " . "anything it provides."); } }
/** * Registers a resource map for Celerity. This is glue code between the Celerity * mapper script and @{class:CelerityResourceMap}. * * @group celerity */ function celerity_register_resource_map(array $map, array $package_map) { $instance = CelerityResourceMap::getInstance(); $instance->setResourceMap($map); $instance->setPackageMap($package_map); }