/**
  * 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'));
     }
 }
Example #2
0
/**
 * 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);
 }
Example #5
0
/**
 * 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);
 }
Example #10
0
/**
 * 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.");
     }
 }
Example #16
0
/**
 * 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);
}