diff --git a/core/includes/common.inc b/core/includes/common.inc index 97b73f4..0c1da26 100644 --- a/core/includes/common.inc +++ b/core/includes/common.inc @@ -2521,6 +2521,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) { @@ -3738,6 +3739,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..c202288 --- /dev/null +++ b/core/lib/Drupal/Core/Asset/AssetBag.php @@ -0,0 +1,281 @@ +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(); + + $this->sorted['js'] = $this->_sort($this->getUnsortedJs()); + $this->sorted['css'] = $this->_sort($this->getUnsortedCss()); + + $all = array(); + + + $this->needsSorting = FALSE; + } + + protected function _sort($items) { + $g = new DirectedAcyclicGraph(); + foreach ($items as $item) { + $from_id = spl_object_hash($item); + $g->addNode($from_id, $item); + $deps = $js->getDependencies(); + foreach ($deps as $dep) { + $to_id = spl_object_hash($dep); + $g->addNode($to_id, $dep); + $g->addLink($from_id, $to_id); + } + } + $tsl = $g->getTSL(); + $result = arrau(); + foreach($tsl as $id) { + $result[] = $g->getNodeData($id); + } + return $result; + } + + /** + * 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..97f8a80 --- /dev/null +++ b/core/lib/Drupal/Core/Asset/BaseAsset.php @@ -0,0 +1,278 @@ +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; + } + + /** + * @todo can we do this with a filter? + */ + public function setBrowsers() { + // TODO: Implement setBrowsers() method. + } + + public function getBrowsers() { + // TODO: Implement getBrowsers() method. + } + + /** + * Declares that the current asset must follow the provided asset. + * + * This method describes sequencing, not a dependency. That is, passing an + * asset to this method does NOT declare that it should be used on the page; + * only that it IF that asset is declared elsewhere for use on the page, THEN + * this asset should always follow it. + * + * However, declaring an explicit dependency will trigger this method + * internally, so there is no need for client code to call it directly. + * + * @param AssetInterface $asset + * The asset which the current asset should follow when rendered onto the + * page. + * + * @return void + * @throws \UnexpectedValueException + * If the current asset is of a different type (e.g. CSS vs. Javascript), an + * exception will be thrown. + */ + public function after(AssetInterface $asset) { + // TODO: Implement after() method. + } + + /** + * Indicates whether this asset has any sequencing requirements. + * + * Sequencing requirements are declared by calling AssetInterface::after(). + * + * @return bool + */ + public function hasSequencing() { + // TODO: Implement hasSequencing() method. + } + + /** + * Gets any assets that must precede this one in the rendering sequence. + * + * @return array + * An array of AssetInterface objects that should precede this one. + */ + public function getPreceders() { + // TODO: Implement getPreceders() method. + } + + /** + * Declares a dependency on the provided asset library. + * + * @param AssetLibraryReference $reference + * + * @return void + */ + public function addDependency(AssetLibraryReference $reference) { + // TODO: Implement addDependency() method. + } + + /** + * Indicates whether or not this asset has library dependencies. + * + * @return bool + */ + public function hasDependencies() { + // TODO: Implement hasDependencies() method. + } + + /** + * Gets the library dependencies attached to this asset, if any. + * + * This method may trigger some compilation steps, so if you only need to know + * whether or not the asset has dependencies but not what they are, call + * AssetInterface::hasDependencies(). + * + * @return array + */ + public function getDependencies() { + // TODO: Implement getDependencies() 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..ca4c220 --- /dev/null +++ b/core/lib/Drupal/Core/Asset/JavascriptAssetInterface.php @@ -0,0 +1,22 @@ +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 035f282..0564cdc 100644 --- a/core/lib/Drupal/Core/CoreBundle.php +++ b/core/lib/Drupal/Core/CoreBundle.php @@ -270,6 +270,9 @@ class CoreBundle extends Bundle { $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/lib/Drupal/Core/CoreBundle.php.orig b/core/lib/Drupal/Core/CoreBundle.php.orig new file mode 100644 index 0000000..035f282 --- /dev/null +++ b/core/lib/Drupal/Core/CoreBundle.php.orig @@ -0,0 +1,370 @@ +register('config.cachedstorage.storage', 'Drupal\Core\Config\FileStorage') + ->addArgument(config_get_config_directory(CONFIG_ACTIVE_DIRECTORY)); + // @todo Replace this with a cache.factory service plus 'config' argument. + $container + ->register('cache.config', 'Drupal\Core\Cache\CacheBackendInterface') + ->setFactoryClass('Drupal\Core\Cache\CacheFactory') + ->setFactoryMethod('get') + ->addArgument('config'); + + $container + ->register('config.storage', 'Drupal\Core\Config\CachedStorage') + ->addArgument(new Reference('config.cachedstorage.storage')) + ->addArgument(new Reference('cache.config')); + + $container->register('config.factory', 'Drupal\Core\Config\ConfigFactory') + ->addArgument(new Reference('config.storage')) + ->addArgument(new Reference('event_dispatcher')) + ->addTag('persist'); + + // Register staging configuration storage. + $container + ->register('config.storage.staging', 'Drupal\Core\Config\FileStorage') + ->addArgument(config_get_config_directory(CONFIG_STAGING_DIRECTORY)); + + // Register the typed configuration data manager. + $container->register('config.typed', 'Drupal\Core\Config\TypedConfigManager') + ->addArgument(new Reference('config.storage')); + + // Register the service for the default database connection. + $container->register('database', 'Drupal\Core\Database\Connection') + ->setFactoryClass('Drupal\Core\Database\Database') + ->setFactoryMethod('getConnection') + ->addArgument('default'); + // Register the KeyValueStore factory. + $container + ->register('keyvalue', 'Drupal\Core\KeyValueStore\KeyValueFactory') + ->addArgument(new Reference('service_container')); + $container + ->register('keyvalue.database', 'Drupal\Core\KeyValueStore\KeyValueDatabaseFactory') + ->addArgument(new Reference('database')); + + $container->register('settings', 'Drupal\Component\Utility\Settings') + ->setFactoryClass('Drupal\Component\Utility\Settings') + ->setFactoryMethod('getSingleton'); + + // Register the State k/v store as a service. + $container->register('state', 'Drupal\Core\KeyValueStore\KeyValueStoreInterface') + ->setFactoryService(new Reference('keyvalue')) + ->setFactoryMethod('get') + ->addArgument('state'); + + // Register the Queue factory. + $container + ->register('queue', 'Drupal\Core\Queue\QueueFactory') + ->addArgument(new Reference('settings')) + ->addMethodCall('setContainer', array(new Reference('service_container'))); + $container + ->register('queue.database', 'Drupal\Core\Queue\QueueDatabaseFactory') + ->addArgument(new Reference('database')); + + $container->register('path.alias_manager', 'Drupal\Core\Path\AliasManager') + ->addArgument(new Reference('database')) + ->addArgument(new Reference('state')) + ->addArgument(new Reference('language_manager')); + + $container->register('http_client_simpletest_subscriber', 'Drupal\Core\Http\Plugin\SimpletestHttpRequestSubscriber'); + $container->register('http_default_client', 'Guzzle\Http\Client') + ->addArgument(NULL) + ->addArgument(array( + 'curl.CURLOPT_TIMEOUT' => 30.0, + 'curl.CURLOPT_MAXREDIRS' => 3, + )) + ->addMethodCall('addSubscriber', array(new Reference('http_client_simpletest_subscriber'))) + ->addMethodCall('setUserAgent', array('Drupal (+http://drupal.org/)')); + + // Register the EntityManager. + $container->register('plugin.manager.entity', 'Drupal\Core\Entity\EntityManager'); + + // The 'request' scope and service enable services to depend on the Request + // object and get reconstructed when the request object changes (e.g., + // during a subrequest). + $container->addScope(new Scope('request')); + $container->register('request', 'Symfony\Component\HttpFoundation\Request'); + + $container->register('event_dispatcher', 'Symfony\Component\EventDispatcher\ContainerAwareEventDispatcher') + ->addArgument(new Reference('service_container')); + $container->register('controller_resolver', 'Drupal\Core\ControllerResolver') + ->addArgument(new Reference('service_container')); + + $container + ->register('cache.cache', 'Drupal\Core\Cache\CacheBackendInterface') + ->setFactoryClass('Drupal\Core\Cache\CacheFactory') + ->setFactoryMethod('get') + ->addArgument('cache'); + $container + ->register('cache.bootstrap', 'Drupal\Core\Cache\CacheBackendInterface') + ->setFactoryClass('Drupal\Core\Cache\CacheFactory') + ->setFactoryMethod('get') + ->addArgument('bootstrap'); + + $this->registerModuleHandler($container); + + $container->register('http_kernel', 'Drupal\Core\HttpKernel') + ->addArgument(new Reference('event_dispatcher')) + ->addArgument(new Reference('service_container')) + ->addArgument(new Reference('controller_resolver')); + + // Register the 'language_manager' service. + $container->register('language_manager', 'Drupal\Core\Language\LanguageManager'); + + $container->register('database.slave', 'Drupal\Core\Database\Connection') + ->setFactoryClass('Drupal\Core\Database\Database') + ->setFactoryMethod('getConnection') + ->addArgument('slave'); + $container->register('typed_data', 'Drupal\Core\TypedData\TypedDataManager') + ->addMethodCall('setValidationConstraintManager', array(new Reference('validation.constraint'))); + $container->register('validation.constraint', 'Drupal\Core\Validation\ConstraintManager'); + + // Add the user's storage for temporary, non-cache data. + $container->register('lock', 'Drupal\Core\Lock\DatabaseLockBackend'); + $container->register('user.tempstore', 'Drupal\user\TempStoreFactory') + ->addArgument(new Reference('database')) + ->addArgument(new Reference('lock')); + + $this->registerTwig($container); + $this->registerRouting($container); + + // Add the entity query factories. + $container->register('entity.query', 'Drupal\Core\Entity\Query\QueryFactory') + ->addArgument(new Reference('plugin.manager.entity')) + ->addMethodCall('setContainer', array(new Reference('service_container'))); + $container->register('entity.query.config', 'Drupal\Core\Config\Entity\Query\QueryFactory') + ->addArgument(new Reference('config.storage')); + + $container->register('router.dumper', 'Drupal\Core\Routing\MatcherDumper') + ->addArgument(new Reference('database')); + $container->register('router.builder', 'Drupal\Core\Routing\RouteBuilder') + ->addArgument(new Reference('router.dumper')) + ->addArgument(new Reference('lock')) + ->addArgument(new Reference('event_dispatcher')) + ->addArgument(new Reference('module_handler')); + + $container + ->register('cache.path', 'Drupal\Core\Cache\CacheBackendInterface') + ->setFactoryClass('Drupal\Core\Cache\CacheFactory') + ->setFactoryMethod('get') + ->addArgument('path'); + + $container->register('path.alias_manager.cached', 'Drupal\Core\CacheDecorator\AliasManagerCacheDecorator') + ->addArgument(new Reference('path.alias_manager')) + ->addArgument(new Reference('cache.path')); + + $container->register('path.crud', 'Drupal\Core\Path\Path') + ->addArgument(new Reference('database')) + ->addArgument(new Reference('path.alias_manager')); + + // Add password hashing service. The argument to PhpassHashedPassword + // constructor is the log2 number of iterations for password stretching. + // This should increase by 1 every Drupal version in order to counteract + // increases in the speed and power of computers available to crack the + // hashes. The current password hashing method was introduced in Drupal 7 + // with a log2 count of 15. + $container->register('password', 'Drupal\Core\Password\PhpassHashedPassword') + ->addArgument(16); + + // The following services are tagged as 'route_filter' services and are + // processed in the RegisterRouteFiltersPass compiler pass. + $container->register('mime_type_matcher', 'Drupal\Core\Routing\MimeTypeMatcher') + ->addTag('route_filter'); + + $container->register('paramconverter_manager', 'Drupal\Core\ParamConverter\ParamConverterManager') + ->addTag('route_enhancer'); + $container->register('paramconverter.entity', 'Drupal\Core\ParamConverter\EntityConverter') + ->addArgument(new Reference('plugin.manager.entity')) + ->addTag('paramconverter'); + + $container->register('router_processor_subscriber', 'Drupal\Core\EventSubscriber\RouteProcessorSubscriber') + ->addArgument(new Reference('content_negotiation')) + ->addTag('event_subscriber'); + $container->register('router_listener', 'Symfony\Component\HttpKernel\EventListener\RouterListener') + ->addArgument(new Reference('router')) + ->addTag('event_subscriber'); + $container->register('content_negotiation', 'Drupal\Core\ContentNegotiation'); + $container->register('view_subscriber', 'Drupal\Core\EventSubscriber\ViewSubscriber') + ->addArgument(new Reference('content_negotiation')) + ->addTag('event_subscriber'); + $container->register('legacy_access_subscriber', 'Drupal\Core\EventSubscriber\LegacyAccessSubscriber') + ->addTag('event_subscriber'); + $container->register('access_manager', 'Drupal\Core\Access\AccessManager') + ->addArgument(new Reference('request')) + ->addMethodCall('setContainer', array(new Reference('service_container'))); + $container->register('access_subscriber', 'Drupal\Core\EventSubscriber\AccessSubscriber') + ->addArgument(new Reference('access_manager')) + ->addTag('event_subscriber'); + $container->register('access_check.default', 'Drupal\Core\Access\DefaultAccessCheck') + ->addTag('access_check'); + $container->register('access_check.permission', 'Drupal\Core\Access\PermissionAccessCheck') + ->addTag('access_check'); + $container->register('maintenance_mode_subscriber', 'Drupal\Core\EventSubscriber\MaintenanceModeSubscriber') + ->addTag('event_subscriber'); + $container->register('path_subscriber', 'Drupal\Core\EventSubscriber\PathSubscriber') + ->addArgument(new Reference('path.alias_manager.cached')) + ->addTag('event_subscriber'); + $container->register('legacy_request_subscriber', 'Drupal\Core\EventSubscriber\LegacyRequestSubscriber') + ->addTag('event_subscriber'); + $container->register('legacy_controller_subscriber', 'Drupal\Core\EventSubscriber\LegacyControllerSubscriber') + ->addTag('event_subscriber'); + $container->register('finish_response_subscriber', 'Drupal\Core\EventSubscriber\FinishResponseSubscriber') + ->addArgument(new Reference('language_manager')) + ->setScope('request') + ->addTag('event_subscriber'); + $container->register('request_close_subscriber', 'Drupal\Core\EventSubscriber\RequestCloseSubscriber') + ->addArgument(new Reference('module_handler')) + ->addTag('event_subscriber'); + $container->register('config_global_override_subscriber', 'Drupal\Core\EventSubscriber\ConfigGlobalOverrideSubscriber') + ->addTag('event_subscriber'); + $container->register('language_request_subscriber', 'Drupal\Core\EventSubscriber\LanguageRequestSubscriber') + ->addArgument(new Reference('language_manager')) + ->addTag('event_subscriber'); + + $container->register('exception_controller', 'Drupal\Core\ExceptionController') + ->addArgument(new Reference('content_negotiation')) + ->addMethodCall('setContainer', array(new Reference('service_container'))); + $container->register('exception_listener', 'Drupal\Core\EventSubscriber\ExceptionListener') + ->addTag('event_subscriber') + ->addArgument(array(new Reference('exception_controller'), 'execute')); + + $container + ->register('transliteration', 'Drupal\Core\Transliteration\PHPTransliteration'); + + $container->register('flood', 'Drupal\Core\Flood\DatabaseBackend') + ->addArgument(new Reference('database')); + + $container->addCompilerPass(new RegisterMatchersPass()); + $container->addCompilerPass(new RegisterRouteFiltersPass()); + // Add a compiler pass for registering event subscribers. + $container->addCompilerPass(new RegisterKernelListenersPass(), PassConfig::TYPE_AFTER_REMOVING); + // Add a compiler pass for registering event subscribers. + $container->addCompilerPass(new RegisterKernelListenersPass(), PassConfig::TYPE_AFTER_REMOVING); + $container->addCompilerPass(new RegisterAccessChecksPass()); + // Add a compiler pass for upcasting of entity route parameters. + $container->addCompilerPass(new RegisterParamConvertersPass()); + $container->addCompilerPass(new RegisterRouteEnhancersPass()); + } + + /** + * Registers the module handler. + */ + protected function registerModuleHandler(ContainerBuilder $container) { + // The ModuleHandler manages enabled modules and provides the ability to + // invoke hooks in all enabled modules. + if ($container->getParameter('kernel.environment') == 'install') { + // During installation we use the non-cached version. + $container->register('module_handler', 'Drupal\Core\Extension\ModuleHandler') + ->addArgument('%container.modules%'); + } + else { + $container->register('module_handler', 'Drupal\Core\Extension\CachedModuleHandler') + ->addArgument('%container.modules%') + ->addArgument(new Reference('state')) + ->addArgument(new Reference('cache.bootstrap')); + } + } + + /** + * Registers the various services for the routing system. + * + * @param \Symfony\Component\DependencyInjection\ContainerBuilder $container + */ + protected function registerRouting(ContainerBuilder $container) { + $container->register('router.request_context', 'Symfony\Component\Routing\RequestContext') + ->addMethodCall('fromRequest', array(new Reference('request'))); + + $container->register('router.route_provider', 'Drupal\Core\Routing\RouteProvider') + ->addArgument(new Reference('database')); + $container->register('router.matcher.final_matcher', 'Drupal\Core\Routing\UrlMatcher'); + $container->register('router.matcher', 'Symfony\Cmf\Component\Routing\NestedMatcher\NestedMatcher') + ->addArgument(new Reference('router.route_provider')) + ->addMethodCall('setFinalMatcher', array(new Reference('router.matcher.final_matcher'))); + $container->register('router.generator', 'Drupal\Core\Routing\UrlGenerator') + ->addArgument(new Reference('router.route_provider')) + ->addArgument(new Reference('path.alias_manager.cached')); + $container->register('router.dynamic', 'Symfony\Cmf\Component\Routing\DynamicRouter') + ->addArgument(new Reference('router.request_context')) + ->addArgument(new Reference('router.matcher')) + ->addArgument(new Reference('router.generator')); + + $container->register('legacy_generator', 'Drupal\Core\Routing\NullGenerator'); + $container->register('legacy_url_matcher', 'Drupal\Core\LegacyUrlMatcher'); + $container->register('legacy_router', 'Symfony\Cmf\Component\Routing\DynamicRouter') + ->addArgument(new Reference('router.request_context')) + ->addArgument(new Reference('legacy_url_matcher')) + ->addArgument(new Reference('legacy_generator')); + + $container->register('router', 'Symfony\Cmf\Component\Routing\ChainRouter') + ->addMethodCall('setContext', array(new Reference('router.request_context'))) + ->addMethodCall('add', array(new Reference('router.dynamic'))) + ->addMethodCall('add', array(new Reference('legacy_router'))); + } + + /** + * Registers Twig services. + */ + protected function registerTwig(ContainerBuilder $container) { + $container->register('twig.loader.filesystem', 'Twig_Loader_Filesystem') + ->addArgument(DRUPAL_ROOT); + $container->setAlias('twig.loader', 'twig.loader.filesystem'); + + $container->register('twig', 'Drupal\Core\Template\TwigEnvironment') + ->addArgument(new Reference('twig.loader')) + ->addArgument(array( + // This is saved / loaded via drupal_php_storage(). + // All files can be refreshed by clearing caches. + // @todo ensure garbage collection of expired files. + 'cache' => settings()->get('twig_cache', TRUE), + 'base_template_class' => 'Drupal\Core\Template\TwigTemplate', + // @todo Remove in followup issue + // @see http://drupal.org/node/1712444. + 'autoescape' => FALSE, + // @todo Remove in followup issue + // @see http://drupal.org/node/1806538. + 'strict_variables' => FALSE, + 'debug' => settings()->get('twig_debug', FALSE), + 'auto_reload' => settings()->get('twig_auto_reload', NULL), + )) + ->addMethodCall('addExtension', array(new Definition('Drupal\Core\Template\TwigExtension'))) + // @todo Figure out what to do about debugging functions. + // @see http://drupal.org/node/1804998 + ->addMethodCall('addExtension', array(new Definition('Twig_Extension_Debug'))); + } +} 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..d52e760 --- /dev/null +++ b/core/modules/system/lib/Drupal/system/Tests/Asset/AssetAssemblyTest.php @@ -0,0 +1,147 @@ + '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(); + + // Populate a mock AssetLibraryManager with the jquery library. + $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); + + // Calling getJs() should dereference the dependency into the object + // instance contained in the AssetLibraryManager. + $this->assertEqual(array($alm->get('jquery'), $js1), $bag->getJs()); + } + + /** + * Tests ensuring that multiple asset bags are combined together correctly. + * + * @todo introduce some dependency interweaving for real robustness. + */ + public function testMultiBagAssetAssemblies() { + $outer_bag = new AssetBag(); + + $css1 = new StylesheetFileAsset('core/vendor/kriswallsmith/assetic/tests/Assetic/Test/Filter/fixtures/cssmin/main.css'); + $js1 = new JavascriptFileAsset('core/vendor/kriswallsmith/assetic/tests/Assetic/Test/Filter/fixtures/jsmin/js.js'); + + $outer_bag->add($css1); + $outer_bag->add($js1); + + $inner_bag_one = new AssetBag(); + $inner_bag_two = new AssetBag(); + + $css2 = new StylesheetFileAsset('core/vendor/kriswallsmith/assetic/tests/Assetic/Test/Filter/fixtures/cssmin/fonts.css'); + $js2 = new JavascriptFileAsset('core/vendor/kriswallsmith/assetic/tests/Assetic/Test/Filter/fixtures/packer/example.js'); + $css3 = new StylesheetExternalAsset('http://example.com/style.css'); + $css4 = new StylesheetFileAsset('core/vendor/kriswallsmith/assetic/tests/Assetic/Test/Filter/fixtures/cssimport/import.css'); + + $inner_bag_one->add($css2); + $inner_bag_one->add($js2); + // The third asset goes to the inner-inner bag in order to test that it is + // spliced in correctly. + $inner_bag_two->add($css3); + $inner_bag_one->add($css4); + + $inner_bag_one->addAssetBag($inner_bag_two); + $outer_bag->addAssetBag($inner_bag_one); + + // Adding the bag will, by default, mark it as terminated and incapable of + // receiving new assets. + try { + $inner_bag_one->add(new JavascriptExternalAsset('http://example.com/script.js')); + $this->fail('Terminated AssetBag did not throw expected exception when attempting to add a new asset.'); + } + catch (\LogicException $e) { + $this->pass('Terminated AssetBag threw expected LogicException when attempting to add a new asset.'); + } + + $this->assertIdentical(array($css1, $css2, $css3, $css4), $outer_bag->getCss()); + $this->assertIdentical(array($js1, $js2), $outer_bag->getJs()); + } + + /** + * Tests to ensure expected variables for use by templates are retrievable. + * + * @todo variable-generated stage has not yet been dealt with. + */ + public function testTemplateVariableRetrieval() { + + } +} diff --git a/core/vendor/composer/autoload_namespaces.php b/core/vendor/composer/autoload_namespaces.php index 54b2551..3bff62e 100644 --- a/core/vendor/composer/autoload_namespaces.php +++ b/core/vendor/composer/autoload_namespaces.php @@ -26,5 +26,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/', ); diff --git a/core/vendor/composer/autoload_namespaces.php.orig b/core/vendor/composer/autoload_namespaces.php.orig new file mode 100644 index 0000000..54b2551 --- /dev/null +++ b/core/vendor/composer/autoload_namespaces.php.orig @@ -0,0 +1,30 @@ + $vendorDir . '/twig/twig/lib/', + 'Symfony\\Component\\Yaml\\' => $vendorDir . '/symfony/yaml/', + 'Symfony\\Component\\Validator\\' => $vendorDir . '/symfony/validator/', + 'Symfony\\Component\\Translation\\' => $vendorDir . '/symfony/translation/', + 'Symfony\\Component\\Serializer\\' => $vendorDir . '/symfony/serializer/', + 'Symfony\\Component\\Routing\\' => $vendorDir . '/symfony/routing/', + 'Symfony\\Component\\Process' => $vendorDir . '/symfony/process/', + 'Symfony\\Component\\HttpKernel\\' => $vendorDir . '/symfony/http-kernel/', + 'Symfony\\Component\\HttpFoundation\\' => $vendorDir . '/symfony/http-foundation/', + 'Symfony\\Component\\EventDispatcher\\' => $vendorDir . '/symfony/event-dispatcher/', + 'Symfony\\Component\\DependencyInjection\\' => $vendorDir . '/symfony/dependency-injection/', + 'Symfony\\Component\\ClassLoader\\' => $vendorDir . '/symfony/class-loader/', + 'Symfony\\Cmf\\Component\\Routing' => $vendorDir . '/symfony-cmf/routing/', + 'Psr\\Log\\' => $vendorDir . '/psr/log/', + 'Guzzle\\Stream' => $vendorDir . '/guzzle/stream/', + 'Guzzle\\Parser' => $vendorDir . '/guzzle/parser/', + 'Guzzle\\Http' => $vendorDir . '/guzzle/http/', + 'Guzzle\\Common' => $vendorDir . '/guzzle/common/', + 'EasyRdf_' => $vendorDir . '/easyrdf/easyrdf/lib/', + 'Doctrine\\Common' => $vendorDir . '/doctrine/common/lib/', + 'Assetic' => $vendorDir . '/kriswallsmith/assetic/src/', +); diff --git a/core/vendor/graph/graph/LICENSE.txt b/core/vendor/graph/graph/LICENSE.txt new file mode 100644 index 0000000..acb6082 --- /dev/null +++ b/core/vendor/graph/graph/LICENSE.txt @@ -0,0 +1,31 @@ +Copyright (c) 2009-2013 by the Graph Team, see AUTHORS for more details. + +Some rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + + * The names of the contributors may not be used to endorse or + promote products derived from this software without specific + prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/core/vendor/graph/graph/README.md b/core/vendor/graph/graph/README.md new file mode 100644 index 0000000..7a8c448 --- /dev/null +++ b/core/vendor/graph/graph/README.md @@ -0,0 +1,4 @@ +graph +===== + +PHP Graph classes \ No newline at end of file diff --git a/core/vendor/graph/graph/lib/Graph/DirectedAcyclicGraph.class.php b/core/vendor/graph/graph/lib/Graph/DirectedAcyclicGraph.class.php new file mode 100644 index 0000000..567ceea --- /dev/null +++ b/core/vendor/graph/graph/lib/Graph/DirectedAcyclicGraph.class.php @@ -0,0 +1,107 @@ +subGraph(array($to_id)); + $p = $sub->getParticipants(); + if (in_array($from_id, $p)) { + throw new \Exception("Cannot add Cycle '$from_id' -> '$to_id' to " . $this); + } + else { + parent::addLink($from_id, $to_id); + } + } + + /** + * Converts a DirectedGraph into a DirectedAcyclicGraph. + * + * A DirectedGraph can be converted into a DirectedAcyclicGraph when + * is does not contain any cycle. + * + * @param \GraphAPI\Component\Graph\DirectedGraph $g + * @param type $ignore_exception + * @throws \GraphAPI\Component\Graph\Exception + */ + static function fromDirectedGraph(DirectedGraph $g, $ignore_exception = FALSE) { + $d = new DirectedAcyclicGraph(); + foreach ($g->getNodeIds() as $id) { + $links = $g->getLinks($id); + foreach ($links as $link) { + try { + $d->addLink($id, $link); + } + catch (\Exception $exc) { + if (!$ignore_exception) { + throw $exc; + } + } + } + } + return $d; + } + + /** + * Calculates the Topological Sorted List. + * + * A Topological Sorted List is a Depth First Search (DFS) ordered + * list of participants. + * + * TODO: Do we need this method for DirectedGraph? + * If there are cycles/loops then the algorithme does not loop forever. + * But the TSL is not really a TSL. + * + * The algorithme is based on the Iterator example from + * the book Higher Order Perl where a recusive function + * can be rewritten into a loop. + * + * @param array $ids + * List of nodes interested in. + * @return array + * The TSL ordered list of participants + */ + public function getTSL($ids = array()) { + $g = $this->subGraph($ids); + // By adding a root the DFS is more cleaner/predictable for tests + $root = $g->getId(); + $g->addRoot($root); + $agenda = array($root); + + $visited = array(); + $tsl = array(); + while ($inspect = array_pop($agenda)) { + if (!isset($visited[$inspect])) { + $visited[$inspect] = TRUE; + $links = $g->getLinks($inspect); + if (!empty($links)) { + array_push($agenda, $inspect); + foreach ($links as $id) { + if (!isset($visited[$id])) { + $agenda[] = $id; + } + } + } + else { + // We are done with this node. + $tsl[] = $inspect; + } + } + else { + // Already inspected so spit it out. + $tsl[] = $inspect; + } + } + return array_diff($tsl, array($root)); + } + +} diff --git a/core/vendor/graph/graph/lib/Graph/DirectedGraph.class.php b/core/vendor/graph/graph/lib/Graph/DirectedGraph.class.php new file mode 100644 index 0000000..4245ddb --- /dev/null +++ b/core/vendor/graph/graph/lib/Graph/DirectedGraph.class.php @@ -0,0 +1,72 @@ +_list) as $from_id) { + $g->addNode($from_id); + foreach ($this->getLinks($from_id) as $to_id) { + $g->addLink($to_id, $from_id); + } + } + return $g; + } + + /** + * A subgraph is calculated based on the participants collected based on the given node ids. + * + * @param array $ids + * The nodes interested in. + * @return DirectedGraph + * The subgraph with all participants + */ + public function subGraph($ids = array()) { + $g = new DirectedGraph(); + $participants = $this->getParticipants($ids); + foreach ($participants as $id) { + $g->addNode($id); + // Only participating links are added. + $links = $this->getLinks($id); + if (is_array($links)) { + $g->addLinks($id, array_intersect($participants, $links)); + } + } + return $g; + } + + /** + * Implementation of uni directed link between two nodes + * + * @see addLink() + * + * @param string $from_id + * @param string $to_id + */ + protected function _addLink($from_id, $to_id, $data, $key) { + $this->_list[$from_id][Graph::GRAPH_LINKS][$to_id][$key]['_id'] = $to_id; + $this->_setLinkData($from_id, $to_id, $data, $key); + } + + protected function _setLinkData($from_id, $to_id, $data, $key) { + $this->_list[$from_id][Graph::GRAPH_LINKS][$to_id][$key][GRAPH::GRAPH_DATA] = $data; + } + +} diff --git a/core/vendor/graph/graph/lib/Graph/Graph.class.php b/core/vendor/graph/graph/lib/Graph/Graph.class.php new file mode 100644 index 0000000..b54064d --- /dev/null +++ b/core/vendor/graph/graph/lib/Graph/Graph.class.php @@ -0,0 +1,326 @@ +_data; + } + + public function setData($data) { + $this->_data = $data; + } + + /** + * Adds id to the list of nodes. + * + * The data is only added when the node was non-existing yet. + * + * @param String $id + * The Id of the node to add. + * @return Graph + */ + public function addNode($id, $data = NULL) { + if (!isset($this->_list[$id])) { + $this->_list[$id] = array(); + $this->_list[$id][Graph::GRAPH_LINKS] = array(); + $this->_list[$id][Graph::GRAPH_DATA] = $data; + } + return $this; + } + + /** + * Deletes a node from the graph + * + * @param type $id + * @return Graph + */ + public function deleteNode($id) { + if (isset($this->_list[$id])) { + unset($this->_list[$id]); + foreach ($this->getNodeIds() as $nid) { + unset($this->_list[$nid][Graph::GRAPH_LINKS][$id]); + } + } + return $this; + } + + public function hasNode($id) { + return isset($this->_list[$id]); + } + + /** + * Returns the IDs of the added nodes. + * + * @return array with IDs as values + */ + public function getNodeIds() { + return array_keys($this->_list); + } + + public function getNodeData($id) { + return $this->_list[$id][Graph::GRAPH_DATA]; + } + + public function setNodeData($id, $data) { + $this->_list[$id][Graph::GRAPH_DATA] = $data; + } + + /** + * Adds a link between two node ids. + * + * We allow for multigraph vertices or multiple links between 2 nodes + * + * @param String $from_id + * The start point of the link. + * @param String $to_id + * The end point of the link. + * @param any $data + * Can hold anything. Note this get's duplicate unless it's a reference. + * @param string $key + * Unique key to identify this particular link relation. + * @return Graph + */ + public function addLink($from_id, $to_id, $data = NULL, $key = GRAPH::GRAPH_LINK_NO_KEY) { + $this->addNode($from_id); + $this->addNode($to_id); + $this->_addLink($from_id, $to_id, $data, $key); + return $this; + } + + /** + * Returns the array keys of the multigraph between given node ids. + * + * @param string $from_id + * @param string $to_id + * @return array + */ + public function getLinkIds($from_id, $to_id) { + $result = $this->getLinks($from_id); + if (is_array($result) && isset($this->_list[$from_id][Graph::GRAPH_LINKS][$to_id])) { + return array_keys($this->_list[$from_id][Graph::GRAPH_LINKS][$to_id]); + } + } + + /** + * Returns the data element of the given node ids and link id. + * + * @param string $from_id + * @param string $to_id + * @param string $key + * + * @return any + */ + public function getLinkData($from_id, $to_id, $key = GRAPH::GRAPH_LINK_NO_KEY) { + return $this->_list[$from_id][Graph::GRAPH_LINKS][$to_id][$key][GRAPH::GRAPH_DATA]; + } + + public function setLinkData($from_id, $to_id, $data, $key = GRAPH::GRAPH_LINK_NO_KEY) { + $this->_setLinkData($from_id, $to_id, $data, $key); + } + + /** + * Adds a list of links to a node. + * + * This is a utility function ignoring data or multilinks. + * + * @param string $from_id + * @param array $to_ids + * + * @return $this + */ + public function addLinks($from_id, $to_ids) { + foreach ($to_ids as $to_id) { + $this->addLink($from_id, $to_id); + } + return $this; + } + + /** + * Returns all links from the give node id. + * + * @param string $from_id + * @return array of node ids leaving the given node id. + */ + public function getLinks($id) { + if (isset($this->_list[$id])) { + return array_keys($this->_list[$id][Graph::GRAPH_LINKS]); + } + } + + /** + * Gives all participants related to the given node(s). + * + * @param array $list + * The list of Ids interested in. + * @return array + * All Ids related to the given list. + */ + public function getParticipants($list = array()) { + if (empty($list)) { + return $this->getNodeIds(); + } + $visited = array(); + $agenda = array_values($list); + while ($id = array_shift($agenda)) { + if (!$this->hasNode($id)) { + continue; + } + // Prevent infinite looping + if (!isset($visited[$id])) { + $visited[$id] = TRUE; + $links = $this->getLinks($id); + if (is_array($links)) { + $agenda = array_merge($agenda, $links); + } + } + } + return array_keys($visited); + } + + public function isCircularMember($id) { + $route = $this->getParticipants(array($id)); + foreach ($route as $visited_id) { + $links = $this->getLinks($visited_id); + if (is_array($links) && in_array($id, $links)) { + return TRUE; + } + } + return FALSE; + } + + /** + * Adds a root to the graph connecting islands or loops + * + * A graph may have disconnected sub graphs or be one big loop. So there is + * no single entry point into the graph. + * + * @param type $root_id + */ + public function addRoot($root_id) { + $p = $this->getParts(); + foreach ($p as $nid) { + $this->addLink($root_id, $nid); + } + return $this; + } + + /** + * A Graph is split when having disconnected subgraphs. + * + * @return type + */ + public function isSplit() { + return count($this->getParts()) > 1; + } + + /** + * Gets list or IDs one for each subgraph. + * + * @return type + */ + public function getParts() { + $nids = $this->getNodeIds(); + $parts = array(); + while (!empty($nids)) { + $nid = array_shift($nids); + $p = $this->getParticipants(array($nid)); + $nids = array_diff($nids, $p); + $parts = array_diff($parts, $p); + $parts[] = $nid; + } + return $parts; + } + + /** + * Get a simple representation of the graph as a string. + * + * This contains a(b,c) like fragments indicating a connects to b and c. + * + * @return string + */ + public function __toString() { + $result = array(); + foreach (array_keys($this->_list) as $id) { + $row = $id . '('; + $links = $this->getLinks($id); + if (is_array($links)) { + $row .= join(',', $links); + } + $row .= ')'; + $result[] = $row; + } + return join(",", $result); + } + + /** + * Implementation of bidirection links between the given node ids. + * + * A Graph is bidirectional so we need to store the link twice. + * + * @param string $from_id + * @param string $to_id + * @param any $data + * Can hold anything. Note this get's duplicate unless it's a reference. + * @param string $key + * Unique key to identify this particular link relation. + */ + protected function _addLink($from_id, $to_id, $data, $key) { + $this->_list[$from_id][Graph::GRAPH_LINKS][$to_id][$key]['_id'] = $to_id; + $this->_list[$to_id][Graph::GRAPH_LINKS][$from_id][$key]['_id'] = $from_id; + $this->_setLinkData($from_id, $to_id, $data, $key); + } + + protected function _setLinkData($from_id, $to_id, $data, $key) { + $this->_list[$from_id][Graph::GRAPH_LINKS][$to_id][$key][GRAPH::GRAPH_DATA] = $data; + $this->_list[$to_id][Graph::GRAPH_LINKS][$from_id][$key][GRAPH::GRAPH_DATA] = $data; + } + + protected function getId() { + return spl_object_hash($this); + } +}