diff --git a/core/lib/Drupal/Component/Plugin/Derivative/DerivativeInterface.php b/core/lib/Drupal/Component/Plugin/Derivative/DerivativeInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..bb0706907582c1288c6ae06258600f92dc0f0f23 --- /dev/null +++ b/core/lib/Drupal/Component/Plugin/Derivative/DerivativeInterface.php @@ -0,0 +1,44 @@ +decorated = $decorated; + } + + /** + * Implements Drupal\Component\Plugin\Discovery\DiscoveryInterface::getDefinition(). + */ + public function getDefinition($plugin_id) { + + list($base_plugin_id, $derivative_id) = $this->decodePluginId($plugin_id); + + $plugin_definition = $this->decorated->getDefinition($base_plugin_id); + if (isset($plugin_definition)) { + $derivative_fetcher = $this->getDerivativeFetcher($base_plugin_id, $plugin_definition); + if ($derivative_fetcher) { + $plugin_definition = $derivative_fetcher->getDerivativeDefinition($derivative_id, $plugin_definition); + } + } + + return $plugin_definition; + } + + /** + * Implements Drupal\Component\Plugin\Discovery\DiscoveryInterface::getDefinitions(). + */ + public function getDefinitions() { + $plugin_definitions = $this->decorated->getDefinitions(); + return $this->getDerivatives($plugin_definitions); + } + + /** + * Adds derivatives to a list of plugin definitions. + * + * This should be called by the class extending this in + * DiscoveryInterface::getDefinitions(). + */ + protected function getDerivatives(array $base_plugin_definitions) { + $plugin_definitions = array(); + foreach ($base_plugin_definitions as $base_plugin_id => $plugin_definition) { + $derivative_fetcher = $this->getDerivativeFetcher($base_plugin_id, $plugin_definition); + if ($derivative_fetcher) { + $derivative_definitions = $derivative_fetcher->getDerivativeDefinitions($plugin_definition); + foreach ($derivative_definitions as $derivative_id => $derivative_definition) { + $plugin_id = $this->encodePluginId($base_plugin_id, $derivative_id); + $plugin_definitions[$plugin_id] = $derivative_definition; + } + } + else { + $plugin_definitions[$base_plugin_id] = $plugin_definition; + } + } + + return $plugin_definitions; + } + + /** + * Decodes derivative id and plugin id from a string. + * + * @param string $plugin_id + * Plugin identifier that may point to a derivative plugin. + * + * @return array + * An array with the base plugin id as the first index and the derivative id + * as the second. If there is no derivative id it will be null. + */ + protected function decodePluginId($plugin_id) { + // Try and split the passed plugin definition into a plugin and a + // derivative id. We don't need to check for !== FALSE because a leading + // colon would break the derivative system and doesn't makes sense. + if (strpos($plugin_id, ':')) { + return explode(':', $plugin_id, 2); + } + + return array($plugin_id, NULL); + } + + /** + * Encodes plugin and derivative id's into a string. + * + * @param string $base_plugin_id + * The base plugin identifier. + * @param string $derivative_id + * The derivative identifier. + * + * @return string + * A uniquely encoded combination of the $base_plugin_id and $derivative_id. + */ + protected function encodePluginId($base_plugin_id, $derivative_id) { + if ($derivative_id) { + return "$base_plugin_id:$derivative_id"; + } + + // By returning the unmerged plugin_id, we are able to support derivative + // plugins that support fetching the base definitions. + return $base_plugin_id; + } + + /** + * Finds a Drupal\Component\Plugin\Discovery\DerivativeInterface. + * + * This Drupal\Component\Plugin\Discovery\DerivativeInterface can fetch + * derivatives for the plugin. + * + * @param string $base_plugin_id + * The base plugin id of the plugin. + * @param array $base_definition + * The base plugin definition to build derivatives. + * + * @return Drupal\Component\Plugin\Discovery\DerivativeInterface|null + * A DerivativeInterface or null if none exists for the plugin. + */ + protected function getDerivativeFetcher($base_plugin_id, array $base_definition) { + if (!isset($this->derivativeFetchers[$base_plugin_id])) { + $this->derivativeFetchers[$base_plugin_id] = FALSE; + if (isset($base_definition['derivative'])) { + $class = $base_definition['derivative']; + $this->derivativeFetchers[$base_plugin_id] = new $class($base_plugin_id); + } + } + return $this->derivativeFetchers[$base_plugin_id] ?: NULL; + } + + /** + * Passes through all unknown calls onto the decorated object. + */ + public function __call($method, $args) { + return call_user_func_array(array($this->decorated, $method), $args); + } +} diff --git a/core/lib/Drupal/Component/Plugin/Discovery/DiscoveryInterface.php b/core/lib/Drupal/Component/Plugin/Discovery/DiscoveryInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..dee79eae09ec8d3b63d0d3702f230c641f7ae45e --- /dev/null +++ b/core/lib/Drupal/Component/Plugin/Discovery/DiscoveryInterface.php @@ -0,0 +1,35 @@ +definitions[$base_plugin_id]) ? $this->definitions[$base_plugin_id] : NULL; + } + + /** + * Implements Drupal\Component\Plugin\Discovery\DiscoveryInterface::getDefinitions(). + */ + public function getDefinitions() { + return $this->definitions; + } + + /** + * Sets a plugin definition. + */ + public function setDefinition($plugin, array $definition) { + $this->definitions[$plugin] = $definition; + } + + /** + * Deletes a plugin definition. + */ + public function deleteDefinition($plugin) { + unset($this->definitions[$plugin]); + } +} diff --git a/core/lib/Drupal/Component/Plugin/Exception/ExceptionInterface.php b/core/lib/Drupal/Component/Plugin/Exception/ExceptionInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..57fb25bfc528702ab6eb18dff414bfbfde19b721 --- /dev/null +++ b/core/lib/Drupal/Component/Plugin/Exception/ExceptionInterface.php @@ -0,0 +1,12 @@ +discovery = $discovery; + } + + /** + * Implements Drupal\Component\Plugin\Factory\FactoryInterface::createInstance(). + */ + public function createInstance($plugin_id, array $configuration) { + $plugin_class = $this->getPluginClass($plugin_id); + return new $plugin_class($configuration, $plugin_id, $this->discovery); + } + + /** + * Finds the class relevant for a given plugin. + * + * @param array $plugin_id + * The id of a plugin. + * + * @return string + * The appropriate class name. + */ + protected function getPluginClass($plugin_id) { + $plugin_definition = $this->discovery->getDefinition($plugin_id); + if (empty($plugin_definition['class'])) { + throw new PluginException('The plugin did not specify an instance class.'); + } + + $class = $plugin_definition['class']; + + if (!class_exists($class)) { + throw new PluginException(sprintf('Plugin instance class "%s" does not exist.', $class)); + } + + return $class; + } +} diff --git a/core/lib/Drupal/Component/Plugin/Factory/FactoryInterface.php b/core/lib/Drupal/Component/Plugin/Factory/FactoryInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..7954745a1cb3230799ee703b2fe1e80b88f5f824 --- /dev/null +++ b/core/lib/Drupal/Component/Plugin/Factory/FactoryInterface.php @@ -0,0 +1,29 @@ +getPluginClass($plugin_id); + + // Lets figure out of there's a constructor for this class and pull + // arguments from the $options array if so to populate it. + $reflector = new ReflectionClass($plugin_class); + if ($reflector->hasMethod('__construct')) { + $arguments = $this->getInstanceArguments($reflector, $plugin_id, $configuration); + $instance = $reflector->newInstanceArgs($arguments); + } + else { + $instance = new $plugin_class(); + } + + return $instance; + } + + /** + * Inspects the plugin class and build a list of arguments for the constructor. + * + * This is provided as a helper method so factories extending this class can + * replace this and insert their own reflection logic. + * + * @param ReflectionClass $reflector + * The reflector object being used to inspect the plugin class. + * @param string $plugin_id + * The identifier of the plugin implementation. + * @param array $configuration + * An array of configuration that may be passed to the instance. + * + * @return array + * An array of arguments to be passed to the constructor. + */ + protected function getInstanceArguments(ReflectionClass $reflector, $plugin_id, array $configuration) { + + $arguments = array(); + foreach ($reflector->getMethod('__construct')->getParameters() as $param) { + $param_name = $param->getName(); + $param_class = $param->getClass(); + + if ($param_name == 'plugin_id') { + $arguments[] = $plugin_id; + } + elseif ($param_name == 'configuration') { + $arguments[] = $configuration; + } + elseif ($param_class && $param_class->isInstance($this->discovery)) { + $arguments[] = $this->discovery; + } + elseif (isset($configuration[$param_name]) || array_key_exists($param_name, $configuration)) { + $arguments[] = $configuration[$param_name]; + } + elseif ($param->isDefaultValueAvailable()) { + $arguments[] = $param->getDefaultValue(); + } + else { + $arguments[] = NULL; + } + } + return $arguments; + } +} diff --git a/core/lib/Drupal/Component/Plugin/Mapper/MapperInterface.php b/core/lib/Drupal/Component/Plugin/Mapper/MapperInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..03610e6490cc1e50cca035440d367f7ce467620e --- /dev/null +++ b/core/lib/Drupal/Component/Plugin/Mapper/MapperInterface.php @@ -0,0 +1,33 @@ +configuration = $configuration; + $this->plugin_id = $plugin_id; + $this->discovery = $discovery; + } + + /** + * Implements Drupal\Component\Plugin\PluginInterface::getPluginId(). + */ + public function getPluginId() { + return $this->plugin_id; + } + + /** + * Implements Drupal\Component\Plugin\PluginInterface::getDefinition(). + */ + public function getDefinition() { + return $this->discovery->getDefinition($this->plugin_id); + } + + // Note: Plugin configuration is optional so its left to the plugin type to + // require a getter as part of its interface. +} diff --git a/core/lib/Drupal/Component/Plugin/PluginInspectionInterface.php b/core/lib/Drupal/Component/Plugin/PluginInspectionInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..9157d7c43179a722ff10514befc0044323104057 --- /dev/null +++ b/core/lib/Drupal/Component/Plugin/PluginInspectionInterface.php @@ -0,0 +1,33 @@ +processDefinition() if + * additional processing of plugins is necessary or helpful for development + * purposes. + * + * @var array + */ + protected $defaults = array(); + + /** + * Implements Drupal\Component\Plugin\PluginManagerInterface::getDefinition(). + */ + public function getDefinition($plugin_id) { + $definition = $this->discovery->getDefinition($plugin_id); + if (isset($definition)) { + $this->processDefinition($definition, $plugin_id); + } + return $definition; + } + + /** + * Implements Drupal\Component\Plugin\PluginManagerInterface::getDefinitions(). + */ + public function getDefinitions() { + $definitions = $this->discovery->getDefinitions(); + foreach ($definitions as $plugin_id => &$definition) { + $this->processDefinition($definition, $plugin_id); + } + + return $definitions; + } + + /** + * Implements Drupal\Component\Plugin\PluginManagerInterface::createInstance(). + */ + public function createInstance($plugin_id, array $configuration = array()) { + return $this->factory->createInstance($plugin_id, $configuration); + } + + /** + * Implements Drupal\Component\Plugin\PluginManagerInterface::getInstance(). + */ + public function getInstance(array $options) { + return $this->mapper->getInstance($options); + } + + /** + * Performs extra processing on plugin definitions. + * + * By default we add defaults for the type to the definition. If a type has + * additional processing logic they can do that by replacing or extending the + * method. + */ + protected function processDefinition(&$definition, $plugin_id) { + $definition += $this->defaults; + } +} diff --git a/core/lib/Drupal/Component/Plugin/PluginManagerInterface.php b/core/lib/Drupal/Component/Plugin/PluginManagerInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..a7d74a8092e197e7f7ba90786f8fc06e75e6c8e6 --- /dev/null +++ b/core/lib/Drupal/Component/Plugin/PluginManagerInterface.php @@ -0,0 +1,31 @@ +decorated = $decorated; + $this->cacheKey = $cache_key; + $this->cacheBin = $cache_bin; + } + + /** + * Implements Drupal\Component\Plugin\Discovery\DicoveryInterface::getDefinition(). + */ + public function getDefinition($plugin_id) { + $definitions = $this->getCachedDefinitions(); + if (isset($definitions)) { + $definition = isset($definitions[$plugin_id]) ? $definitions[$plugin_id] : NULL; + } + else { + $definition = $this->decorated->getDefinition($plugin_id); + } + return $definition; + } + + /** + * Implements Drupal\Component\Plugin\Discovery\DicoveryInterface::getDefinitions(). + */ + public function getDefinitions() { + $definitions = $this->getCachedDefinitions(); + if (!isset($definitions)) { + $definitions = $this->decorated->getDefinitions(); + $this->setCachedDefinitions($definitions); + } + return $definitions; + } + + /** + * Returns the cached plugin definitions of the decorated discovery class. + * + * @return mixed + * On success this will return an array of plugin definitions. On failure + * this should return NULL, indicating to other methods that this has not + * yet been defined. Success with no values should return as an empty array + * and would actually be returned by the getDefinitions() method. + */ + protected function getCachedDefinitions() { + if (!isset($this->definitions) && isset($this->cacheKey) && $cache = cache($this->cacheBin)->get($this->cacheKey)) { + $this->definitions = $cache->data; + } + return $this->definitions; + } + + /** + * Sets a cache of plugin definitions for the decorated discovery class. + * + * @param array $definitions + * List of definitions to store in cache. + */ + protected function setCachedDefinitions($definitions) { + if (isset($this->cacheKey)) { + cache($this->cacheBin)->set($this->cacheKey, $definitions); + } + $this->definitions = $definitions; + } + + /** + * Passes through all unknown calls onto the decorated object. + */ + public function __call($method, $args) { + return call_user_func_array(array($this->decorated, $method), $args); + } +} diff --git a/core/lib/Drupal/Core/Plugin/Discovery/HookDiscovery.php b/core/lib/Drupal/Core/Plugin/Discovery/HookDiscovery.php new file mode 100644 index 0000000000000000000000000000000000000000..3ff05f9197ff8326384c8b53b56040a18b9799c0 --- /dev/null +++ b/core/lib/Drupal/Core/Plugin/Discovery/HookDiscovery.php @@ -0,0 +1,57 @@ +hook = $hook; + } + + /** + * Implements Drupal\Component\Plugin\Discovery\DicoveryInterface::getDefinition(). + */ + public function getDefinition($plugin_id) { + $plugins = $this->getDefinitions(); + return isset($plugins[$plugin_id]) ? $plugins[$plugin_id] : array(); + } + + /** + * Implements Drupal\Component\Plugin\Discovery\DicoveryInterface::getDefinitions(). + */ + public function getDefinitions() { + foreach (module_implements($this->hook) as $module) { + $function = $module . '_' . $this->hook; + foreach ($function() as $plugin_id => $definition) { + $definition['module'] = $module; + $definitions[$plugin_id] = $definition; + } + } + drupal_alter($this->hook, $definitions); + return $definitions; + } +} diff --git a/core/modules/aggregator/aggregator.admin.inc b/core/modules/aggregator/aggregator.admin.inc index 7759750ba3a9699c1887cb1558931bd4c462c3fb..0ee5ee61f197171cd6a309751e594b87b4094a6b 100644 --- a/core/modules/aggregator/aggregator.admin.inc +++ b/core/modules/aggregator/aggregator.admin.inc @@ -6,6 +6,7 @@ */ use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; +use Drupal\aggregator\Plugin\FetcherManager; /** * Page callback: Displays the aggregator administration page. @@ -442,16 +443,11 @@ function aggregator_admin_form($form, $form_state) { aggregator_sanitize_configuration(); // Get all available fetchers. - $fetchers = module_implements('aggregator_fetch'); - foreach ($fetchers as $k => $module) { - if ($info = module_invoke($module, 'aggregator_fetch_info')) { - $label = $info['title'] . ' ' . $info['description'] . ''; - } - else { - $label = $module; - } - unset($fetchers[$k]); - $fetchers[$module] = $label; + $fetcher_manager = new FetcherManager(); + $fetchers = array(); + foreach ($fetcher_manager->getDefinitions() as $id => $definition) { + $label = $definition['title'] . ' ' . $definition['description'] . ''; + $fetchers[$id] = $label; } // Get all available parsers. diff --git a/core/modules/aggregator/aggregator.api.php b/core/modules/aggregator/aggregator.api.php index 0f708eb85ef3423825763aafcd1c4f6622b4063d..3d7f4996fd05efcbb19e9ac6f6aaeab0a04335a3 100644 --- a/core/modules/aggregator/aggregator.api.php +++ b/core/modules/aggregator/aggregator.api.php @@ -11,58 +11,29 @@ */ /** - * Create an alternative fetcher for aggregator.module. - * - * A fetcher downloads feed data to a Drupal site. The fetcher is called at the - * first of the three aggregation stages: first, data is downloaded by the - * active fetcher; second, it is converted to a common format by the active - * parser; and finally, it is passed to all active processors, which manipulate - * or store the data. - * - * Modules that define this hook can be set as active fetcher on - * admin/config/services/aggregator. Only one fetcher can be active at a time. - * - * @param $feed - * A feed object representing the resource to be downloaded. $feed->url - * contains the link to the feed. Download the data at the URL and expose it - * to other modules by attaching it to $feed->source_string. - * - * @return - * TRUE if fetching was successful, FALSE otherwise. - * - * @see hook_aggregator_fetch_info() - * @see hook_aggregator_parse() - * @see hook_aggregator_process() - * - * @ingroup aggregator - */ -function hook_aggregator_fetch($feed) { - $feed->source_string = mymodule_fetch($feed->url); -} - -/** - * Specify the title and short description of your fetcher. + * Specify the class, title, and short description of your fetcher plugins. * * The title and the description provided are shown on - * admin/config/services/aggregator among other places. Use as title the human - * readable name of the fetcher and as description a brief (40 to 80 characters) - * explanation of the fetcher's functionality. - * - * This hook is only called if your module implements hook_aggregator_fetch(). - * If this hook is not implemented aggregator will use your module's file name - * as title and there will be no description. + * admin/config/services/aggregator among other places. * * @return - * An associative array defining a title and a description string. - * - * @see hook_aggregator_fetch() + * An associative array whose keys define the fetcher id and whose values + * contain the fetcher definitions. Each fetcher definition is itself an + * associative array, with the following key-value pairs: + * - class: (required) The PHP class containing the fetcher implementation. + * - title: (required) A human readable name of the fetcher. + * - description: (required) A brief (40 to 80 characters) explanation of the + * fetcher's functionality. * * @ingroup aggregator */ function hook_aggregator_fetch_info() { return array( - 'title' => t('Default fetcher'), - 'description' => t('Default fetcher for resources available by URL.'), + 'aggregator' => array( + 'class' => 'Drupal\aggregator\Plugin\aggregator\fetcher\DefaultFetcher', + 'title' => t('Default fetcher'), + 'description' => t('Downloads data from a URL using Drupal\'s HTTP request handler.'), + ), ); } diff --git a/core/modules/aggregator/aggregator.module b/core/modules/aggregator/aggregator.module index 0626f726e3c80582c0549d9066ec802f7bc54036..2d37b73de9bde235ed6cc71c3662b27e7aad817a 100644 --- a/core/modules/aggregator/aggregator.module +++ b/core/modules/aggregator/aggregator.module @@ -5,6 +5,8 @@ * Used to aggregate syndicated content (RSS, RDF, and Atom). */ +use Drupal\aggregator\Plugin\FetcherManager; + /** * Denotes that a feed's items should never expire. */ @@ -594,19 +596,18 @@ function aggregator_remove($feed) { * An array containing the fetcher, parser, and processors. */ function _aggregator_get_variables() { - // Fetch the feed. $fetcher = variable_get('aggregator_fetcher', 'aggregator'); - if ($fetcher == 'aggregator') { - include_once DRUPAL_ROOT . '/' . drupal_get_path('module', 'aggregator') . '/aggregator.fetcher.inc'; - } + $parser = variable_get('aggregator_parser', 'aggregator'); if ($parser == 'aggregator') { include_once DRUPAL_ROOT . '/' . drupal_get_path('module', 'aggregator') . '/aggregator.parser.inc'; } + $processors = variable_get('aggregator_processors', array('aggregator')); if (in_array('aggregator', $processors)) { include_once DRUPAL_ROOT . '/' . drupal_get_path('module', 'aggregator') . '/aggregator.processor.inc'; } + return array($fetcher, $parser, $processors); } @@ -620,9 +621,16 @@ function aggregator_refresh($feed) { // Store feed URL to track changes. $feed_url = $feed->url; - // Fetch the feed. list($fetcher, $parser, $processors) = _aggregator_get_variables(); - $success = module_invoke($fetcher, 'aggregator_fetch', $feed); + + // Fetch the feed. + $fetcher_manager = new FetcherManager(); + try { + $success = $fetcher_manager->createInstance($fetcher)->fetch($feed); + } + catch (PluginException $e) { + $success = FALSE; + } // We store the hash of feed data in the database. When refreshing a // feed we compare stored hash and new hash calculated from downloaded @@ -788,6 +796,19 @@ function _aggregator_items($count) { return format_plural($count, '1 item', '@count items'); } +/** + * Implements hook_aggregator_fetch_info(). + */ +function aggregator_aggregator_fetch_info() { + return array( + 'aggregator' => array( + 'class' => 'Drupal\aggregator\Plugin\aggregator\fetcher\DefaultFetcher', + 'title' => t('Default fetcher'), + 'description' => t('Downloads data from a URL using Drupal\'s HTTP request handler.'), + ), + ); +} + /** * Implements hook_preprocess_HOOK() for block.tpl.php. */ diff --git a/core/modules/aggregator/lib/Drupal/aggregator/Plugin/FetcherInterface.php b/core/modules/aggregator/lib/Drupal/aggregator/Plugin/FetcherInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..39fc19df2aee5a89e01e1f8de1d3fe709eae81f7 --- /dev/null +++ b/core/modules/aggregator/lib/Drupal/aggregator/Plugin/FetcherInterface.php @@ -0,0 +1,33 @@ +url + * contains the link to the feed. Download the data at the URL and expose it + * to other modules by attaching it to $feed->source_string. + * + * @return + * TRUE if fetching was successful, FALSE otherwise. + */ + public function fetch(&$feed); + +} diff --git a/core/modules/aggregator/lib/Drupal/aggregator/Plugin/FetcherManager.php b/core/modules/aggregator/lib/Drupal/aggregator/Plugin/FetcherManager.php new file mode 100644 index 0000000000000000000000000000000000000000..ae24934d1934305cf5af88a90554691a1b2def24 --- /dev/null +++ b/core/modules/aggregator/lib/Drupal/aggregator/Plugin/FetcherManager.php @@ -0,0 +1,23 @@ +discovery = new HookDiscovery('aggregator_fetch_info'); + $this->factory = new DefaultFactory($this->discovery); + } +} diff --git a/core/modules/aggregator/lib/Drupal/aggregator/Plugin/aggregator/fetcher/DefaultFetcher.php b/core/modules/aggregator/lib/Drupal/aggregator/Plugin/aggregator/fetcher/DefaultFetcher.php new file mode 100644 index 0000000000000000000000000000000000000000..e4b663ba47bb1d5ac32d1aad34421da1dddee30b --- /dev/null +++ b/core/modules/aggregator/lib/Drupal/aggregator/Plugin/aggregator/fetcher/DefaultFetcher.php @@ -0,0 +1,63 @@ +source_string = FALSE; + + // Generate conditional GET headers. + $headers = array(); + if ($feed->etag) { + $headers['If-None-Match'] = $feed->etag; + } + if ($feed->modified) { + $headers['If-Modified-Since'] = gmdate(DATE_RFC1123, $feed->modified); + } + + // Request feed. + $result = drupal_http_request($feed->url, array('headers' => $headers)); + + // Process HTTP response code. + switch ($result->code) { + case 304: + break; + case 301: + $feed->url = $result->redirect_url; + // Do not break here. + case 200: + case 302: + case 307: + if (!isset($result->data)) { + $result->data = ''; + } + if (!isset($result->headers)) { + $result->headers = array(); + } + $feed->source_string = $result->data; + $feed->http_headers = $result->headers; + break; + default: + watchdog('aggregator', 'The feed from %site seems to be broken due to "%error".', array('%site' => $feed->title, '%error' => $result->code . ' ' . $result->error), WATCHDOG_WARNING); + drupal_set_message(t('The feed from %site seems to be broken because of error "%error".', array('%site' => $feed->title, '%error' => $result->code . ' ' . $result->error))); + } + + return !($feed->source_string === FALSE); + } +} diff --git a/core/modules/simpletest/simpletest.info b/core/modules/simpletest/simpletest.info index 46f00c3a32bb4b39af9c1b5f5c9eb06bd9886484..6e18c42bd3afeec7d5de00f8872ec073db066e46 100644 --- a/core/modules/simpletest/simpletest.info +++ b/core/modules/simpletest/simpletest.info @@ -4,3 +4,4 @@ package = Core version = VERSION core = 8.x configure = admin/config/development/testing/settings + diff --git a/core/modules/system/lib/Drupal/system/Tests/Plugin/DerivativeTest.php b/core/modules/system/lib/Drupal/system/Tests/Plugin/DerivativeTest.php new file mode 100644 index 0000000000000000000000000000000000000000..226b28d6f290c537def3ff2bfe5cf2a75d98a9f5 --- /dev/null +++ b/core/modules/system/lib/Drupal/system/Tests/Plugin/DerivativeTest.php @@ -0,0 +1,42 @@ + 'Derivative Discovery', + 'description' => 'Tests that derivative plugins are correctly discovered.', + 'group' => 'Plugin API', + ); + } + + /** + * Tests getDefinitions() and getDefinition() with a derivativeDecorator. + */ + function testDerivativeDecorator() { + // Ensure that getDefinitions() returns the expected definitions. + $this->assertIdentical($this->mockBlockManager->getDefinitions(), $this->mockBlockExpectedDefinitions); + + // Ensure that getDefinition() returns the expected definition. + foreach ($this->mockBlockExpectedDefinitions as $id => $definition) { + $this->assertIdentical($this->mockBlockManager->getDefinition($id), $definition); + } + + // Ensure that NULL is returned as the definition of a non-existing base + // plugin, a non-existing derivative plugin, or a base plugin that may not + // be used without deriving. + $this->assertIdentical($this->mockBlockManager->getDefinition('non_existing'), NULL, 'NULL returned as the definition of a non-existing base plugin.'); + $this->assertIdentical($this->mockBlockManager->getDefinition('menu:non_existing'), NULL, 'NULL returned as the definition of a non-existing derivative plugin.'); + $this->assertIdentical($this->mockBlockManager->getDefinition('menu'), NULL, 'NULL returned as the definition of a base plugin that may not be used without deriving.'); + } +} diff --git a/core/modules/system/lib/Drupal/system/Tests/Plugin/DiscoveryTest.php b/core/modules/system/lib/Drupal/system/Tests/Plugin/DiscoveryTest.php new file mode 100644 index 0000000000000000000000000000000000000000..32637c672887204c846c7af8c43b18978a217b0a --- /dev/null +++ b/core/modules/system/lib/Drupal/system/Tests/Plugin/DiscoveryTest.php @@ -0,0 +1,38 @@ + 'Discovery', + 'description' => 'Tests that plugins are correctly discovered.', + 'group' => 'Plugin API', + ); + } + + /** + * Tests getDefinitions() and getDefinition(). + */ + function testDiscoveryInterface() { + // Ensure that getDefinitions() returns the expected definitions. + $this->assertIdentical($this->testPluginManager->getDefinitions(), $this->testPluginExpectedDefinitions); + + // Ensure that getDefinition() returns the expected definition. + foreach ($this->testPluginExpectedDefinitions as $id => $definition) { + $this->assertIdentical($this->testPluginManager->getDefinition($id), $definition); + } + + // Ensure that NULL is returned as the definition of a non-existing plugin. + $this->assertIdentical($this->testPluginManager->getDefinition('non_existing'), NULL, 'NULL returned as the definition of a non-existing base plugin.'); + } +} diff --git a/core/modules/system/lib/Drupal/system/Tests/Plugin/FactoryTest.php b/core/modules/system/lib/Drupal/system/Tests/Plugin/FactoryTest.php new file mode 100644 index 0000000000000000000000000000000000000000..55c160f974ef29c79c35269a536630b70c82ede0 --- /dev/null +++ b/core/modules/system/lib/Drupal/system/Tests/Plugin/FactoryTest.php @@ -0,0 +1,86 @@ + 'Factory', + 'description' => 'Tests that plugins are correctly instantiated.', + 'group' => 'Plugin API', + ); + } + + /** + * Test that DefaultFactory can create a plugin instance. + */ + function testDefaultFactory() { + // Ensure a non-derivative plugin can be instantiated. + $plugin = $this->testPluginManager->createInstance('user_login', array('title' => 'Please enter your login name and password')); + $this->assertIdentical(get_class($plugin), 'Drupal\plugin_test\Plugin\plugin_test\mock_block\MockUserLoginBlock', 'Correct plugin class instantiated with default factory.'); + $this->assertIdentical($plugin->getTitle(), 'Please enter your login name and password', 'Plugin instance correctly configured.'); + + // Ensure that attempting to instantiate non-existing plugins throws a + // PluginException. + try { + $this->testPluginManager->createInstance('non_existing'); + $this->fail('Drupal\Component\Plugin\Exception\ExceptionInterface expected'); + } + catch (ExceptionInterface $e) { + $this->pass('Drupal\Component\Plugin\Exception\ExceptionInterface expected and caught.'); + } + catch (Exception $e) { + $this->fail('Drupal\Component\Plugin\Exception\ExceptionInterface expected, but ' . get_class($e) . ' was thrown.'); + } + } + + /** + * Test that the Reflection factory can create a plugin instance. + * + * The mock plugin classes use different values for their constructors + * allowing us to test the reflection capabilities as well. + * + * We use derivative classes here because the block test type has the + * reflection factory and it provides some additional variety in plugin + * object creation. + */ + function testReflectionFactory() { + // Ensure a non-derivative plugin can be instantiated. + $plugin = $this->mockBlockManager->createInstance('user_login', array('title' => 'Please enter your login name and password')); + $this->assertIdentical(get_class($plugin), 'Drupal\plugin_test\Plugin\plugin_test\mock_block\MockUserLoginBlock', 'Correct plugin class instantiated.'); + $this->assertIdentical($plugin->getTitle(), 'Please enter your login name and password', 'Plugin instance correctly configured.'); + + // Ensure a derivative plugin can be instantiated. + $plugin = $this->mockBlockManager->createInstance('menu:main_menu', array('depth' => 2)); + $this->assertIdentical($plugin->getContent(), '