Commit 0eaa35d5 authored by Dries's avatar Dries

Issue #1839468 by ParisLiakos, damiankloip, dawehner, jibran: Replace...

Issue #1839468 by ParisLiakos, damiankloip, dawehner, jibran: Replace aggregator rss parsing with Zend Feed.
parent e4c24df2
......@@ -3,7 +3,7 @@
"This file locks the dependencies of your project to a known state",
"Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file"
],
"hash": "e2836b171e18ace7a3f758875041815a",
"hash": "9cad5a32fc0b4c0fac16fbda1b8ead16",
"packages": [
{
"name": "doctrine/common",
......@@ -791,27 +791,27 @@
},
{
"name": "symfony-cmf/routing",
"version": "1.1.0-alpha2",
"version": "1.1.0-beta1",
"target-dir": "Symfony/Cmf/Component/Routing",
"source": {
"type": "git",
"url": "https://github.com/symfony-cmf/Routing.git",
"reference": "1.1.0-alpha2"
"reference": "1.1.0-beta1"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony-cmf/Routing/zipball/1.1.0-alpha2",
"reference": "1.1.0-alpha2",
"url": "https://api.github.com/repos/symfony-cmf/Routing/zipball/1.1.0-beta1",
"reference": "1.1.0-beta1",
"shasum": ""
},
"require": {
"php": ">=5.3.2",
"php": ">=5.3.3",
"psr/log": ">=1.0,<2.0",
"symfony/http-kernel": ">=2.2,<2.4-dev",
"symfony/routing": ">=2.2,<2.4-dev"
"symfony/http-kernel": ">=2.2,<3.0",
"symfony/routing": ">=2.2,<3.0"
},
"suggest": {
"symfony/http-foundation": "ChainRouter/DynamicRouter have optional support for Request instances, several enhancers require a Request instances, >=2.2,<2.3-dev"
"symfony/http-foundation": "ChainRouter/DynamicRouter have optional support for Request instances, several enhancers require a Request instances, ~2.2"
},
"type": "library",
"extra": {
......@@ -840,7 +840,7 @@
"database",
"routing"
],
"time": "2013-05-28 19:50:20"
"time": "2013-06-03 17:23:01"
},
{
"name": "symfony/class-loader",
......@@ -1448,17 +1448,17 @@
},
{
"name": "symfony/yaml",
"version": "v2.3.0",
"version": "v2.3.1",
"target-dir": "Symfony/Component/Yaml",
"source": {
"type": "git",
"url": "https://github.com/symfony/Yaml.git",
"reference": "v2.3.0-RC1"
"reference": "v2.3.1"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/Yaml/zipball/v2.3.0-RC1",
"reference": "v2.3.0-RC1",
"url": "https://api.github.com/repos/symfony/Yaml/zipball/v2.3.1",
"reference": "v2.3.1",
"shasum": ""
},
"require": {
......@@ -1541,6 +1541,138 @@
"templating"
],
"time": "2013-04-08 12:40:11"
},
{
"name": "zendframework/zend-escaper",
"version": "2.2.1",
"target-dir": "Zend/Escaper",
"source": {
"type": "git",
"url": "https://github.com/zendframework/Component_ZendEscaper.git",
"reference": "release-2.2.1"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/zendframework/Component_ZendEscaper/zipball/release-2.2.1",
"reference": "release-2.2.1",
"shasum": ""
},
"require": {
"php": ">=5.3.3"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.2-dev",
"dev-develop": "2.3-dev"
}
},
"autoload": {
"psr-0": {
"Zend\\Escaper\\": ""
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"keywords": [
"escaper",
"zf2"
],
"time": "2013-05-01 21:53:03"
},
{
"name": "zendframework/zend-feed",
"version": "2.2.1",
"target-dir": "Zend/Feed",
"source": {
"type": "git",
"url": "https://github.com/zendframework/Component_ZendFeed.git",
"reference": "release-2.2.1"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/zendframework/Component_ZendFeed/zipball/release-2.2.1",
"reference": "release-2.2.1",
"shasum": ""
},
"require": {
"php": ">=5.3.3",
"zendframework/zend-escaper": "self.version",
"zendframework/zend-stdlib": "self.version"
},
"suggest": {
"zendframework/zend-http": "Zend\\Http for PubSubHubbub, and optionally for use with Zend\\Feed\\Reader",
"zendframework/zend-servicemanager": "Zend\\ServiceManager component, for default/recommended ExtensionManager implementations",
"zendframework/zend-validator": "Zend\\Validator component"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.2-dev",
"dev-develop": "2.3-dev"
}
},
"autoload": {
"psr-0": {
"Zend\\Feed\\": ""
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"description": "provides functionality for consuming RSS and Atom feeds",
"keywords": [
"feed",
"zf2"
],
"time": "2013-06-12 19:45:31"
},
{
"name": "zendframework/zend-stdlib",
"version": "2.2.1",
"target-dir": "Zend/Stdlib",
"source": {
"type": "git",
"url": "https://github.com/zendframework/Component_ZendStdlib.git",
"reference": "release-2.2.1"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/zendframework/Component_ZendStdlib/zipball/release-2.2.1",
"reference": "release-2.2.1",
"shasum": ""
},
"require": {
"php": ">=5.3.3"
},
"suggest": {
"zendframework/zend-eventmanager": "To support aggregate hydrator usage",
"zendframework/zend-servicemanager": "To support hydrator plugin manager usage"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.2-dev",
"dev-develop": "2.3-dev"
}
},
"autoload": {
"psr-0": {
"Zend\\Stdlib\\": ""
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"keywords": [
"stdlib",
"zf2"
],
"time": "2013-06-12 19:46:58"
}
],
"packages-dev": [
......
......@@ -465,3 +465,57 @@ services:
date:
class: Drupal\Core\Datetime\Date
arguments: ['@config.factory', '@language_manager']
feed.bridge.reader:
class: Drupal\Component\Bridge\ZfExtensionManagerSfContainer
calls:
- [setContainer, ['@service_container']]
arguments: ['feed.reader.']
feed.bridge.writer:
class: Drupal\Component\Bridge\ZfExtensionManagerSfContainer
calls:
- [setContainer, ['@service_container']]
arguments: ['feed.writer.']
# Zend Feed reader plugins
feed.reader.dublincoreentry:
class: Zend\Feed\Reader\Extension\DublinCore\Entry
feed.reader.dublincorefeed:
class: Zend\Feed\Reader\Extension\DublinCore\Feed
feed.reader.contententry:
class: Zend\Feed\Reader\Extension\Content\Entry
feed.reader.atomentry:
class: Zend\Feed\Reader\Extension\Atom\Entry
feed.reader.atomfeed:
class: Zend\Feed\Reader\Extension\Atom\Feed
feed.reader.slashentry:
class: Zend\Feed\Reader\Extension\Slash\Entry
feed.reader.wellformedwebentry:
class: Zend\Feed\Reader\Extension\WellFormedWeb\Entry
feed.reader.threadentry:
class: Zend\Feed\Reader\Extension\Thread\Entry
feed.reader.podcastentry:
class: Zend\Feed\Reader\Extension\Podcast\Entry
feed.reader.podcastfeed:
class: Zend\Feed\Reader\Extension\Podcast\Feed
# Zend Feed writer plugins
feed.writer.atomrendererfeed:
class: Zend\Feed\Writer\Extension\Atom\Renderer\Feed
feed.writer.contentrendererentry:
class: Zend\Feed\Writer\Extension\Content\Renderer\Entry
feed.writer.dublincorerendererentry:
class: Zend\Feed\Writer\Extension\DublinCore\Renderer\Entry
feed.writer.dublincorerendererfeed:
class: Zend\Feed\Writer\Extension\DublinCore\Renderer\Feed
feed.writer.itunesentry:
class: Zend\Feed\Writer\Extension\ITunes\Entry
feed.writer.itunesfeed:
class: Zend\Feed\Writer\Extension\ITunes\Feed
feed.writer.itunesrendererentry:
class: Zend\Feed\Writer\Extension\ITunes\Renderer\Entry
feed.writer.itunesrendererfeed:
class: Zend\Feed\Writer\Extension\ITunes\Renderer\Feed
feed.writer.slashrendererentry:
class: Zend\Feed\Writer\Extension\Slash\Renderer\Entry
feed.writer.threadingrendererentry:
class: Zend\Feed\Writer\Extension\Threading\Renderer\Entry
feed.writer.wellformedwebrendererentry:
class: Zend\Feed\Writer\Extension\WellFormedWeb\Renderer\Entry
......@@ -22,6 +22,8 @@
use Drupal\Core\Routing\GeneratorNotInitializedException;
use Drupal\Core\SystemListingInfo;
use Drupal\Core\Template\Attribute;
use Zend\Feed\Writer\Writer;
use Zend\Feed\Reader\Reader;
/**
* @file
......@@ -3988,6 +3990,10 @@ function _drupal_bootstrap_code() {
// Load all enabled modules
Drupal::moduleHandler()->loadAll();
// Set our bridge extension manager to Zend Feed.
Reader::setExtensionManager(Drupal::service('feed.bridge.reader'));
Writer::setExtensionManager(Drupal::service('feed.bridge.writer'));
// Make sure all stream wrappers are registered.
file_get_stream_wrappers();
......
<?php
/**
* @file
* Contains \Drupal\Component\Bridge\ZfExtensionManagerSfContainer
*/
namespace Drupal\Component\Bridge;
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Zend\Feed\Reader\ExtensionManagerInterface as ReaderManagerInterface;
use Zend\Feed\Writer\ExtensionManagerInterface as WriterManagerInterface;
/**
* Defines a bridge between the ZF2 service manager to Symfony container.
*/
class ZfExtensionManagerSfContainer implements ReaderManagerInterface, WriterManagerInterface, ContainerAwareInterface {
/**
* This property was based from Zend Framework (http://framework.zend.com/)
*
* @link http://github.com/zendframework/zf2 for the canonical source repository
* @copyright Copyright (c) 2005-2013 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*
* A map of characters to be replaced through strtr.
*
* @var array
*
* @see \Drupal\Component\Bridge\ZfExtensionManagerSfContainer::canonicalizeName().
*/
protected $canonicalNamesReplacements = array('-' => '', '_' => '', ' ' => '', '\\' => '', '/' => '');
/**
* The prefix to be used when retrieving plugins from the container.
*
* @var string
*/
protected $prefix = '';
/**
* Constructs a ZfExtensionManagerSfContainer object.
*
* @param string $prefix
* The prefix to be used when retrieving plugins from the container.
*/
public function __construct($prefix = '') {
return $this->prefix = $prefix;
}
/**
* {@inheritdoc}
*/
public function get($extension) {
return $this->container->get($this->prefix . $this->canonicalizeName($extension));
}
/**
* {@inheritdoc}
*/
public function has($extension) {
return $this->container->has($this->prefix . $this->canonicalizeName($extension));
}
/**
* This method was based from Zend Framework (http://framework.zend.com/)
*
* @link http://github.com/zendframework/zf2 for the canonical source repository
* @copyright Copyright (c) 2005-2013 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*
* Canonicalize the extension name to a service name.
*
* @param string $name
* The extension name.
*
* @return string
* The service name, without the prefix.
*/
protected function canonicalizeName($name) {
if (isset($this->canonicalNames[$name])) {
return $this->canonicalNames[$name];
}
// This is just for performance instead of using str_replace().
return $this->canonicalNames[$name] = strtolower(strtr($name, $this->canonicalNamesReplacements));
}
/**
* {@inheritdoc}
*/
public function setContainer(ContainerInterface $container = NULL) {
$this->container = $container;
}
}
......@@ -11,6 +11,9 @@
use Drupal\aggregator\Plugin\Core\Entity\Feed;
use Drupal\aggregator\Annotation\AggregatorParser;
use Drupal\Core\Annotation\Translation;
use Drupal\Core\Cache\Cache;
use Zend\Feed\Reader\Reader;
use Zend\Feed\Reader\Exception\ExceptionInterface;
/**
* Defines a default parser implementation.
......@@ -26,353 +29,50 @@
class DefaultParser implements ParserInterface {
/**
* The extracted channel info.
*
* @var array
*/
protected $channel = array();
/**
* The extracted image info.
*
* @var array
*/
protected $image = array();
/**
* The extracted items.
*
* @var array
*/
protected $items = array();
/**
* The element that is being processed.
*
* @var array
*/
protected $element = array();
/**
* The tag that is being processed.
*
* @var string
*/
protected $tag = '';
/**
* Key that holds the number of processed "entry" and "item" tags.
*
* @var int
*/
protected $item;
/**
* Implements \Drupal\aggregator\Plugin\ParserInterface::parse().
* {@inheritdoc}
*/
public function parse(Feed $feed) {
// Filter the input data.
if ($this->parseFeed($feed->source_string, $feed)) {
// Prepare the channel data.
foreach ($this->channel as $key => $value) {
$this->channel[$key] = trim($value);
}
// Prepare the image data (if any).
foreach ($this->image as $key => $value) {
$this->image[$key] = trim($value);
}
// Add parsed data to the feed object.
$feed->link->value = !empty($channel['link']) ? $channel['link'] : '';
$feed->description->value = !empty($channel['description']) ? $channel['description'] : '';
$feed->image->value = !empty($image['url']) ? $image['url'] : '';
// Clear the page and block caches.
cache_invalidate_tags(array('content' => TRUE));
return TRUE;
try {
$channel = Reader::importString($feed->source_string);
}
catch (ExceptionInterface $e) {
watchdog_exception('aggregator', $e);
drupal_set_message(t('The feed from %site seems to be broken because of error "%error".', array('%site' => $feed->label(), '%error' => $e->getMessage())), 'error');
return FALSE;
}
/**
* Parses a feed and stores its items.
*
* @param string $data
* The feed data.
* @param \Drupal\aggregator\Plugin\Core\Entity\Feed $feed
* An object describing the feed to be parsed.
*
* @return bool
* FALSE on error, TRUE otherwise.
*/
protected function parseFeed(&$data, Feed $feed) {
// Parse the data.
$xml_parser = drupal_xml_parser_create($data);
xml_set_element_handler($xml_parser, array($this, 'elementStart'), array($this, 'elementEnd'));
xml_set_character_data_handler($xml_parser, array($this, 'elementData'));
if (!xml_parse($xml_parser, $data, 1)) {
watchdog('aggregator', 'The feed from %site seems to be broken due to an error "%error" on line %line.', array('%site' => $feed->label(), '%error' => xml_error_string(xml_get_error_code($xml_parser)), '%line' => xml_get_current_line_number($xml_parser)), WATCHDOG_WARNING);
drupal_set_message(t('The feed from %site seems to be broken because of error "%error" on line %line.', array('%site' => $feed->label(), '%error' => xml_error_string(xml_get_error_code($xml_parser)), '%line' => xml_get_current_line_number($xml_parser))), 'error');
return FALSE;
}
xml_parser_free($xml_parser);
// We reverse the array such that we store the first item last, and the last
// item first. In the database, the newest item should be at the top.
$this->items = array_reverse($this->items);
$feed->link->value = $channel->getLink();
$feed->description->value = $channel->getDescription();
if ($image = $channel->getImage()) {
$feed->image->value = $image['uri'];
}
// Initialize items array.
$feed->items = array();
foreach ($this->items as $item) {
// Prepare the item:
foreach ($item as $key => $value) {
$item[$key] = trim($value);
}
// Resolve the item's title. If no title is found, we use up to 40
// characters of the description ending at a word boundary, but not
// splitting potential entities.
if (!empty($item['title'])) {
$item['title'] = $item['title'];
}
elseif (!empty($item['description'])) {
$item['title'] = preg_replace('/^(.*)[^\w;&].*?$/', "\\1", truncate_utf8($item['description'], 40));
}
else {
$item['title'] = '';
}
// Resolve the items link.
if (!empty($item['link'])) {
$item['link'] = $item['link'];
foreach ($channel as $item) {
// Reset the parsed item.
$parsed_item = array();
// Move the values to an array as expected by processors.
$parsed_item['title'] = $item->getTitle();
$parsed_item['guid'] = $item->getId();
$parsed_item['link'] = $item->getLink();
$parsed_item['description'] = $item->getDescription();
$parsed_item['author'] = '';
if ($author = $item->getAuthor()) {
$parsed_item['author'] = $author['name'];
}
$parsed_item['timestamp'] = '';
if ($date = $item->getDateModified()) {
$parsed_item['timestamp'] = $date->getTimestamp();
}
else {
$item['link'] = $feed->link->value;
}
// Atom feeds have an ID tag instead of a GUID tag.
if (!isset($item['guid'])) {
$item['guid'] = isset($item['id']) ? $item['id'] : '';
}
// Atom feeds have a content and/or summary tag instead of a description tag.
if (!empty($item['content:encoded'])) {
$item['description'] = $item['content:encoded'];
}
elseif (!empty($item['summary'])) {
$item['description'] = $item['summary'];
}
elseif (!empty($item['content'])) {
$item['description'] = $item['content'];
}