diff --git a/core/includes/common.inc b/core/includes/common.inc index fef9175..06941b2 100644 --- a/core/includes/common.inc +++ b/core/includes/common.inc @@ -2516,6 +2516,7 @@ function drupal_add_html_head_link($attributes, $header = FALSE) { * @return * An array of queued cascading stylesheets. * + * @deprecated * @see drupal_get_css() */ function drupal_add_css($data = NULL, $options = NULL) { @@ -3733,6 +3734,7 @@ function drupal_add_js($data = NULL, $options = NULL) { * * @see drupal_get_js() * @see drupal_add_js() + * @deprecated */ function drupal_js_defaults($data = NULL) { return array( diff --git a/core/lib/Drupal/Core/Asset/AssetBag.php b/core/lib/Drupal/Core/Asset/AssetBag.php new file mode 100644 index 0000000..61a6bb7 --- /dev/null +++ b/core/lib/Drupal/Core/Asset/AssetBag.php @@ -0,0 +1,257 @@ +javascript['settings'] = array( +// 'type' => 'setting', +// 'scope' => 'header', +// 'group' => JS_SETTING, +// 'every_page' => TRUE, +// 'weight' => 0, +// 'browsers' => array(), +// ); + // @todo this stuff made sense in drupal_add_js(), but seems redundant to have in each AssetBag. + // url() generates the script and prefix using hook_url_outbound_alter(). + // Instead of running the hook_url_outbound_alter() again here, extract + // them from url(). + // @todo Make this less hacky: http://drupal.org/node/1547376. +// $scriptPath = $GLOBALS['script_path']; +// $pathPrefix = ''; +// url('', array('script' => &$scriptPath, 'prefix' => &$pathPrefix)); +// $this->javascript['settings']['data'][] = array( +// 'basePath' => base_path(), +// 'scriptPath' => $scriptPath, +// 'pathPrefix' => $pathPrefix, +// 'currentPath' => current_path(), +// ); + } + + public function add(AssetInterface $asset) { + if ($this->terminated) { + throw new \LogicException('Assets cannot be added to a terminated AssetBag.', E_ERROR); + } + + $this->needsSorting = TRUE; + + $this->assets[] = $asset; + if ($asset instanceof JavascriptAssetInterface) { + $this->hasJs = TRUE; + } + if ($asset instanceof StylesheetAssetInterface) { + $this->hasCss = TRUE; + } + } + + /** + * Adds another AssetBag to this one. + * + * @param \Drupal\Core\Asset\AssetBagInterface $bag + * @param bool $terminate + * Whether or not the provided bag should be flagged as terminated. + * + * @return mixed|void + * @throws \LogicException + */ + public function addAssetBag(AssetBagInterface $bag, $terminate = TRUE) { + if ($this->terminated) { + throw new \LogicException('Assets cannot be added to a terminated AssetBag.', E_ERROR); + } + + foreach ($bag->all() as $asset) { + $this->add($asset); + } + + if ($terminate) { + $bag->terminate(); + } + } + + /** + * Indicates whether this object contains any CSS assets. + * + * @return bool + */ + public function hasCss() { + return $this->hasCss; + } + + /** + * Returns the CSS assets in this bag, fully resolved into page order. + * + * @todo see if we can make this return an AssetCollection + * + * @return array + */ + public function getCss() { + $this->sort(); + return $this->sorted['css']; + } + + + /** + * Returns all CSS assets contained directly in the bag. + * + * Note that this will NOT include assets that have been brought in as part of + * dependency declarations. + * + * @return array + * An array of CSS assets, ordered by the sequence in which they entered the + * bag. + */ + public function getUnsortedCss() { + $css = array(); + foreach ($this->assets as $asset) { + if ($asset instanceof StylesheetAssetInterface) { + $css[] = $asset; + } + } + + return $css; + } + + + /** + * Sorts all assets in this bag into their final rendering order. + */ + protected function sort() { + if (!$this->needsSorting) { + return; + } + + $this->sorted['js'] = $this->sorted['css'] = array(); + + $all = array(); + + + $this->needsSorting = FALSE; + } + + + /** + * Returns all assets contained in this object. + * + * The assets are returned in the order in which they were added, which is + * unlikely to be the final correct rendering order. + * + * @return array + */ + public function all() { + return $this->assets; + } + + + /** + * Adds configuration settings for eventual inclusion in drupalSettings. + * + * @todo decide how to handle these in a fully-classed asset system + * + * @param $data + * An associative array containing configuration settings, to be eventually + * merged into drupalSettings. Settings should be be wrapped in another + * variable, typically by module name, in order to avoid conflicts in the + * drupalSettings namespace. Items added with a string key will replace + * existing settings with that key; items with numeric array keys will be + * added to the existing settings array. + * + * @return AssetBagInterface $this + * Returns the current AssetBagInterface object for method chaining. + */ + public function addJsSetting($data) { + $this->javascript['settings']['data'][] = $data; + } + + /** + * Indicates whether this AssetBagInterface contains any JavaScript assets. + * + * @return bool + */ + public function hasJs() { + return $this->hasJs; + } + + /** + * Returns the JavaScript assets in this bag, fully resolved into page order. + * + * @todo see if we can make this return an AssetCollection + * + * @return array + */ + public function getJs() { + $this->sort(); + return $this->sorted['js']; + } + + + /** + * Returns all JavaScript assets contained directly in the bag. + * + * Note that this will NOT include assets that have been brought in as part of + * dependency declarations. + * + * @return array + * An array of JavaScript assets, ordered by the sequence in which they + * entered the bag. + */ + public function getUnsortedJs() { + $js = array(); + foreach ($this->assets as $asset) { + if ($asset instanceof JavascriptAssetInterface) { + $js[] = $asset; + } + } + + return $js; + } + + public function terminate() { + $this->terminated = TRUE; + } +} diff --git a/core/lib/Drupal/Core/Asset/AssetBagInterface.php b/core/lib/Drupal/Core/Asset/AssetBagInterface.php new file mode 100644 index 0000000..8f0356d --- /dev/null +++ b/core/lib/Drupal/Core/Asset/AssetBagInterface.php @@ -0,0 +1,129 @@ +title = $title; + return $this; + } + + public function getTitle() { + return $this->title; + } + + /** + * @todo omgwtfbbqsigh - do we really need this? + * + * @param $website + */ + public function setWebsite($website) { + $this->website = $website; + return $this; + } + + public function getWebsite() { + return $this->website; + } + + public function setVersion($version) { + $this->version = $version; + return $this; + } + + public function getVersion() { + return $this->version; + } + + public function add(AsseticAssetInterface $asset) { + // @todo ugh. + if (!$asset instanceof DrupalAssetInterface) { + throw new \InvalidArgumentException('Drupal library assets must conform to Drupal\'s AssetInterface.'); + } + + return parent::add($asset); + } +} diff --git a/core/lib/Drupal/Core/Asset/AssetLibraryManager.php b/core/lib/Drupal/Core/Asset/AssetLibraryManager.php new file mode 100644 index 0000000..b3ff5bb --- /dev/null +++ b/core/lib/Drupal/Core/Asset/AssetLibraryManager.php @@ -0,0 +1,14 @@ +manager = drupal_container()->get('asset_library_manager'); + } + } + + public function getAssets() { + + } +} diff --git a/core/lib/Drupal/Core/Asset/BaseAsset.php b/core/lib/Drupal/Core/Asset/BaseAsset.php new file mode 100644 index 0000000..c7fa521 --- /dev/null +++ b/core/lib/Drupal/Core/Asset/BaseAsset.php @@ -0,0 +1,230 @@ +filters = new FilterCollection($filters); + $this->sourceRoot = $sourceRoot; + $this->sourcePath = $sourcePath; + $this->vars = $vars; + $this->values = array(); + $this->loaded = FALSE; + } + + public function __clone() { + $this->filters = clone $this->filters; + } + + public function ensureFilter(FilterInterface $filter) { + $this->filters->ensure($filter); + } + + public function getFilters() { + return $this->filters->all(); + } + + public function clearFilters() { + $this->filters->clear(); + } + + /** + * Encapsulates asset loading logic. + * + * @param string $content The asset content + * @param FilterInterface $additionalFilter An additional filter + */ + protected function doLoad($content, FilterInterface $additionalFilter = NULL) { + $filter = clone $this->filters; + if ($additionalFilter) { + $filter->ensure($additionalFilter); + } + + $asset = clone $this; + $asset->setContent($content); + + $filter->filterLoad($asset); + $this->content = $asset->getContent(); + + $this->loaded = TRUE; + } + + public function dump(FilterInterface $additionalFilter = NULL) { + if (!$this->loaded) { + $this->load(); + } + + $filter = clone $this->filters; + if ($additionalFilter) { + $filter->ensure($additionalFilter); + } + + $asset = clone $this; + $filter->filterDump($asset); + + return $asset->getContent(); + } + + public function getContent() { + return $this->content; + } + + public function setContent($content) { + $this->content = $content; + } + + public function getSourceRoot() { + return $this->sourceRoot; + } + + public function getSourcePath() { + return $this->sourcePath; + } + + public function getTargetPath() { + return $this->targetPath; + } + + public function setTargetPath($targetPath) { + if ($this->vars) { + foreach ($this->vars as $var) { + if (FALSE === strpos($targetPath, $var)) { + throw new \RuntimeException(sprintf('The asset target path "%s" must contain the variable "{%s}".', $targetPath, $var)); + } + } + } + + $this->targetPath = $targetPath; + } + + public function getVars() { + return $this->vars; + } + + public function setValues(array $values) { + foreach ($values as $var => $v) { + if (!in_array($var, $this->vars, TRUE)) { + throw new \InvalidArgumentException(sprintf('The asset with source path "%s" has no variable named "%s".', $this->sourcePath, $var)); + } + } + + $this->values = $values; + $this->loaded = FALSE; + } + + public function getValues() { + return $this->values; + } + + /** + * Sets the flag indicating preprocessability. + * + * @param bool $preprocess + * + * @return AssetInterface + * + * @see AssetInterface::isPreprocessable() + */ + public function setPreprocessable($preprocess = TRUE) { + $this->preprocess = $preprocess; + } + + /** + * Indicates whether or not this asset is eligible for preprocessing. + * + * Assets that are marked as not preprocessable will always be passed directly + * through to the browser without aggregation. Assets that are marked as + * eligible for preprocessing will be included in any broader aggregation + * logic that has been configured. + * + * @return bool + */ + public function isPreprocessable() { + return (bool) $this->preprocess; + } + + /** + * Set an explicit weight for this asset. + * + * Weight is used to determine asset ordering. Explicitly setting a weight + * is generally not a good idea, as weights are dynamically calculated. It is + * generally better to let the system determine the appropriate weight by + * looking at the entry point at which your asset was added to the stack. + * + * @param int $weight + * + * @return void + */ + public function setWeight($weight) { + $this->explicitWeight = (int) $weight; + } + + public function getWeight($raw = FALSE) { + // @todo All of this is crap. + $weight = empty($this->explicitWeight) ? $this->weight : $this->explicitWeight; + if ($raw) { + return $weight; + } + else { + return $weight + $this->weightOffset; + } + } + + /** + * @todo can we do this with a filter? + */ + public function setBrowsers() { + // TODO: Implement setBrowsers() method. + } + + public function getBrowsers() { + // TODO: Implement getBrowsers() method. + } +} diff --git a/core/lib/Drupal/Core/Asset/BaseExternalAsset.php b/core/lib/Drupal/Core/Asset/BaseExternalAsset.php new file mode 100644 index 0000000..c61dbcc --- /dev/null +++ b/core/lib/Drupal/Core/Asset/BaseExternalAsset.php @@ -0,0 +1,82 @@ +sourceUrl = $sourceUrl; + $this->ignoreErrors = $ignoreErrors; + + list($scheme, $url) = explode('://', $sourceUrl, 2); + list($host, $path) = explode('/', $url, 2); + + parent::__construct($filters, $scheme.'://'.$host, $path, $vars); + } + /** + * Returns the time the current asset was last modified. + * + * @todo copied right from Assetic. needs to be made more Drupalish. + * + * @return integer|null A UNIX timestamp + */ + public function getLastModified() { + if (false !== @file_get_contents($this->sourceUrl, false, stream_context_create(array('http' => array('method' => 'HEAD'))))) { + foreach ($http_response_header as $header) { + if (0 === stripos($header, 'Last-Modified: ')) { + list(, $mtime) = explode(':', $header, 2); + + return strtotime(trim($mtime)); + } + } + } + } + + /** + * Loads the asset into memory and applies load filters. + * + * You may provide an additional filter to apply during load. + * + * @todo copied right from Assetic. needs to be made more Drupalish. + * + * @param FilterInterface $additionalFilter An additional filter + */ + public function load(FilterInterface $additionalFilter = NULL) { + if (false === $content = @file_get_contents(PathUtils::resolvePath( + $this->sourceUrl, $this->getVars(), $this->getValues()))) { + if ($this->ignoreErrors) { + return; + } else { + throw new \RuntimeException(sprintf('Unable to load asset from URL "%s"', $this->sourceUrl)); + } + } + + $this->doLoad($content, $additionalFilter); + } + +} diff --git a/core/lib/Drupal/Core/Asset/BaseFileAsset.php b/core/lib/Drupal/Core/Asset/BaseFileAsset.php new file mode 100644 index 0000000..ee3c52a --- /dev/null +++ b/core/lib/Drupal/Core/Asset/BaseFileAsset.php @@ -0,0 +1,75 @@ +source = $source; + + parent::__construct($filters, $sourceRoot, $sourcePath, $vars); + } + + /** + * Returns the time the current asset was last modified. + * + * @todo copied right from Assetic. needs to be made more Drupalish. + * + * @return integer|null A UNIX timestamp + */ + public function getLastModified() { + $source = PathUtils::resolvePath($this->source, $this->getVars(), + $this->getValues()); + + if (!is_file($source)) { + throw new \RuntimeException(sprintf('The source file "%s" does not exist.', $source)); + } + + return filemtime($source); + } + + /** + * Loads the asset into memory and applies load filters. + * + * You may provide an additional filter to apply during load. + * + * @todo copied right from Assetic. needs to be made more Drupalish. + * + * @param FilterInterface $additionalFilter An additional filter + */ + public function load(FilterInterface $additionalFilter = NULL) { + $source = PathUtils::resolvePath($this->source, $this->getVars(), + $this->getValues()); + + if (!is_file($source)) { + throw new \RuntimeException(sprintf('The source file "%s" does not exist.', $source)); + } + + $this->doLoad(file_get_contents($source), $additionalFilter); + } + +} diff --git a/core/lib/Drupal/Core/Asset/BaseStringAsset.php b/core/lib/Drupal/Core/Asset/BaseStringAsset.php new file mode 100644 index 0000000..4f7b4b3 --- /dev/null +++ b/core/lib/Drupal/Core/Asset/BaseStringAsset.php @@ -0,0 +1,36 @@ +content = $content; + + parent::__construct($filters, $sourceRoot, $sourcePath); + } + + public function setLastModified($last_modified) { + $this->lastModified = $last_modified; + } + + public function getLastModified() { + return $this->lastModified; + } + + public function load(FilterInterface $additionalFilter = NULL) { + $this->doLoad($this->content, $additionalFilter); + } +} diff --git a/core/lib/Drupal/Core/Asset/JavascriptAssetInterface.php b/core/lib/Drupal/Core/Asset/JavascriptAssetInterface.php new file mode 100644 index 0000000..01f4930 --- /dev/null +++ b/core/lib/Drupal/Core/Asset/JavascriptAssetInterface.php @@ -0,0 +1,28 @@ +scope = $scope; + } + + public function getScope() { + return empty($this->scope) ? $this->scopeDefault : $this->scope; + } + + public function getScopeDefault() { + return $this->scopeDefault; + } +} diff --git a/core/lib/Drupal/Core/Asset/JavascriptFileAsset.php b/core/lib/Drupal/Core/Asset/JavascriptFileAsset.php new file mode 100644 index 0000000..3235e14 --- /dev/null +++ b/core/lib/Drupal/Core/Asset/JavascriptFileAsset.php @@ -0,0 +1,39 @@ +scope = $scope; + } + + public function getScope() { + return empty($this->scope) ? $this->scopeDefault : $this->scope; + } + + public function getScopeDefault() { + return $this->scopeDefault; + } +} diff --git a/core/lib/Drupal/Core/Asset/JavascriptStringAsset.php b/core/lib/Drupal/Core/Asset/JavascriptStringAsset.php new file mode 100644 index 0000000..c8f3767 --- /dev/null +++ b/core/lib/Drupal/Core/Asset/JavascriptStringAsset.php @@ -0,0 +1,38 @@ +scope = $scope; + } + + public function getScope() { + return empty($this->scope) ? $this->scopeDefault : $this->scope; + } + + public function getScopeDefault() { + return $this->scopeDefault; + } +} diff --git a/core/lib/Drupal/Core/Asset/StylesheetAssetInterface.php b/core/lib/Drupal/Core/Asset/StylesheetAssetInterface.php new file mode 100644 index 0000000..9a81daf --- /dev/null +++ b/core/lib/Drupal/Core/Asset/StylesheetAssetInterface.php @@ -0,0 +1,40 @@ +media; + } + + /** + * Returns the default value of the media property on this stylesheet asset. + * + * @return mixed + */ + public function getMediaDefault() { + return $this->mediaDefault; + } + + /** + * Sets the media property to be applied on this stylesheet asset. + * + * @param string $type + * Either a media type, or a media query. + * + * @return NULL + */ + public function setMedia($type) { + $this->media = $type; + } +} diff --git a/core/lib/Drupal/Core/Asset/StylesheetFileAsset.php b/core/lib/Drupal/Core/Asset/StylesheetFileAsset.php new file mode 100644 index 0000000..38b3a6b --- /dev/null +++ b/core/lib/Drupal/Core/Asset/StylesheetFileAsset.php @@ -0,0 +1,55 @@ +media; + } + + /** + * Returns the default value of the media property on this stylesheet asset. + * + * @return mixed + */ + public function getMediaDefault() { + return $this->mediaDefault; + } + + /** + * Sets the media property to be applied on this stylesheet asset. + * + * @param string $type + * Either a media type, or a media query. + * + * @return NULL + */ + public function setMedia($type) { + $this->media = $type; + } +} diff --git a/core/lib/Drupal/Core/Asset/StylesheetStringAsset.php b/core/lib/Drupal/Core/Asset/StylesheetStringAsset.php new file mode 100644 index 0000000..639f6c1 --- /dev/null +++ b/core/lib/Drupal/Core/Asset/StylesheetStringAsset.php @@ -0,0 +1,57 @@ +media; + } + + /** + * Returns the default value of the media property on this stylesheet asset. + * + * @return mixed + */ + public function getMediaDefault() { + return $this->mediaDefault; + } + + /** + * Sets the media property to be applied on this stylesheet asset. + * + * @param string $type + * Either a media type, or a media query. + * + * @return NULL + */ + public function setMedia($type) { + $this->media = $type; + } +} diff --git a/core/lib/Drupal/Core/CoreBundle.php b/core/lib/Drupal/Core/CoreBundle.php index 2076707..1f27569 100644 --- a/core/lib/Drupal/Core/CoreBundle.php +++ b/core/lib/Drupal/Core/CoreBundle.php @@ -236,6 +236,9 @@ public function build(ContainerBuilder $container) { $container->register('flood', 'Drupal\Core\Flood\DatabaseBackend') ->addArgument(new Reference('database')); + // Add the AssetLibraryManager, which manages versioned libraries as dependencies. + $container->register('asset_library_manager', 'Drupal\Core\Asset\AssetLibraryManager'); + $container->addCompilerPass(new RegisterMatchersPass()); $container->addCompilerPass(new RegisterRouteFiltersPass()); // Add a compiler pass for registering event subscribers. diff --git a/core/modules/system/lib/Drupal/system/Tests/Asset/AssetAssemblyTest.php b/core/modules/system/lib/Drupal/system/Tests/Asset/AssetAssemblyTest.php new file mode 100644 index 0000000..9c89370 --- /dev/null +++ b/core/modules/system/lib/Drupal/system/Tests/Asset/AssetAssemblyTest.php @@ -0,0 +1,89 @@ + 'Asset Assembly tests', + 'description' => 'Tests to ensure assets declared via the various possible approaches come out with the correct properties, in the proper order.', + 'group' => 'Asset', + ); + } + + public function createJQueryAssetLibrary() { + $library = new AssetLibrary(array(new JavascriptFileAsset('core/misc/jquery.js'))); + return $library->setTitle('jQuery') + ->setVersion('1.8.2') + ->setWebsite('http://jquery.com'); + } + + /** + * Tests various simple single-bag asset assembly scenarios. + * + * Much of the real complexity of asset ordering in AssetBags comes from + * nesting them, but these tests are focused on the basic mechanics of + * assembly within a single bag. + */ + public function testSingleBagAssetAssemblies() { + // Dead-simple bag - contains just one css and one js assets, both local files. + $bag = new AssetBag(); + + $css1 = new StylesheetFileAsset(DRUPAL_ROOT . '/core/misc/vertical-tabs.css'); + $js1 = new JavascriptFileAsset(DRUPAL_ROOT . '/core/misc/ajax.js'); + + $bag->add($css1); + $bag->add($js1); + + $this->assertTrue($bag->hasCss(), 'AssetBag correctly reports that it contains CSS assets.'); + $this->assertTrue($bag->hasJs(), 'AssetBag correctly reports that it contains javascript assets.'); + + $this->assertIdentical(array($css1), $bag->getUnsortedCss()); + $this->assertIdentical(array($js1), $bag->getUnsortedJs()); + + $css2 = new StylesheetFileAsset(DRUPAL_ROOT . 'core/misc/dropbutton/dropbutton.base.css'); + $bag->add($css2); + + $this->assertIdentical(array($css1, $css2), $bag->getCss()); + + $this->assertIdentical(array($css1, $js1, $css2), $bag->all()); + } + + public function testSortingAndDependencyResolution() { + $bag = new AssetBag(); + + $alm = new AssetLibraryManager(); + $alm->set('jquery', $this->createJQueryAssetLibrary()); + $dep = new AssetLibraryReference('jquery', $alm); + + $css1 = new StylesheetFileAsset(DRUPAL_ROOT . '/core/misc/vertical-tabs.css'); + $js1 = new JavascriptFileAsset(DRUPAL_ROOT . '/core/misc/ajax.js'); + // $js1->addDependency($dep); + + $bag->add($css1); + $bag->add($js1); + + $this->assertEqual(array(new JavascriptFileAsset('core/misc/jquery.js'), $js1), $bag->getJs()); + } +} diff --git a/core/vendor/composer/autoload_namespaces.php b/core/vendor/composer/autoload_namespaces.php index 913d6b8..43be7ec 100644 --- a/core/vendor/composer/autoload_namespaces.php +++ b/core/vendor/composer/autoload_namespaces.php @@ -23,5 +23,5 @@ 'Guzzle\\Common' => $vendorDir . '/guzzle/common/', 'EasyRdf_' => $vendorDir . '/easyrdf/easyrdf/lib/', 'Doctrine\\Common' => $vendorDir . '/doctrine/common/lib/', - 'Assetic' => $vendorDir . '/kriswallsmith/assetic/src/', + 'Assetic\\' => $vendorDir . '/kriswallsmith/assetic/src/', );