1 <?php
2 /**
3 * @package Joomla.Platform
4 * @subpackage Feed
5 *
6 * @copyright Copyright (C) 2005 - 2017 Open Source Matters, Inc. All rights reserved.
7 * @license GNU General Public License version 2 or later; see LICENSE
8 */
9
10 defined('JPATH_PLATFORM') or die;
11
12 /**
13 * Feed factory class.
14 *
15 * @since 12.3
16 */
17 class JFeedFactory
18 {
19 /**
20 * @var array The list of registered parser classes for feeds.
21 * @since 12.3
22 */
23 protected $parsers = array('rss' => 'JFeedParserRss', 'feed' => 'JFeedParserAtom');
24
25 /**
26 * Method to load a URI into the feed reader for parsing.
27 *
28 * @param string $uri The URI of the feed to load. Idn uris must be passed already converted to punycode.
29 *
30 * @return JFeedReader
31 *
32 * @since 12.3
33 * @throws InvalidArgumentException
34 * @throws RuntimeException
35 */
36 public function getFeed($uri)
37 {
38 // Create the XMLReader object.
39 $reader = new XMLReader;
40
41 // Open the URI within the stream reader.
42 if (!@$reader->open($uri, null, LIBXML_NOERROR | LIBXML_ERR_NONE | LIBXML_NOWARNING))
43 {
44 // Retry with JHttpFactory that allow using CURL and Sockets as alternative method when available
45
46 // Adding a valid user agent string, otherwise some feed-servers returning an error
47 $options = new \joomla\Registry\Registry;
48 $options->set('userAgent', 'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:41.0) Gecko/20100101 Firefox/41.0');
49
50 $connector = JHttpFactory::getHttp($options);
51 $feed = $connector->get($uri);
52
53 if ($feed->code != 200)
54 {
55 throw new RuntimeException('Unable to open the feed.');
56 }
57
58 // Set the value to the XMLReader parser
59 if (!$reader->xml($feed->body, null, LIBXML_NOERROR | LIBXML_ERR_NONE | LIBXML_NOWARNING))
60 {
61 throw new RuntimeException('Unable to parse the feed.');
62 }
63
64 }
65
66 try
67 {
68 // Skip ahead to the root node.
69 while ($reader->read())
70 {
71 if ($reader->nodeType == XMLReader::ELEMENT)
72 {
73 break;
74 }
75 }
76 }
77 catch (Exception $e)
78 {
79 throw new RuntimeException('Error reading feed.', $e->getCode(), $e);
80 }
81
82 // Setup the appopriate feed parser for the feed.
83 $parser = $this->_fetchFeedParser($reader->name, $reader);
84
85 return $parser->parse();
86 }
87
88 /**
89 * Method to register a JFeedParser class for a given root tag name.
90 *
91 * @param string $tagName The root tag name for which to register the parser class.
92 * @param string $className The JFeedParser class name to register for a root tag name.
93 * @param boolean $overwrite True to overwrite the parser class if one is already registered.
94 *
95 * @return JFeedFactory
96 *
97 * @since 12.3
98 * @throws InvalidArgumentException
99 */
100 public function registerParser($tagName, $className, $overwrite = false)
101 {
102 // Verify that the class exists.
103 if (!class_exists($className))
104 {
105 throw new InvalidArgumentException('The feed parser class ' . $className . ' does not exist.');
106 }
107
108 // Validate that the tag name is valid.
109 if (!preg_match('/\A(?!XML)[a-z][\w0-9-]*/i', $tagName))
110 {
111 throw new InvalidArgumentException('The tag name ' . $tagName . ' is not valid.');
112 }
113
114 // Register the given parser class for the tag name if nothing registered or the overwrite flag set.
115 if (empty($this->parsers[$tagName]) || (bool) $overwrite)
116 {
117 $this->parsers[(string) $tagName] = (string) $className;
118 }
119
120 return $this;
121 }
122
123 /**
124 * Method to return a new JFeedParser object based on the registered parsers and a given type.
125 *
126 * @param string $type The name of parser to return.
127 * @param XMLReader $reader The XMLReader instance for the feed.
128 *
129 * @return JFeedParser
130 *
131 * @since 12.3
132 * @throws LogicException
133 */
134 private function _fetchFeedParser($type, XMLReader $reader)
135 {
136 // Look for a registered parser for the feed type.
137 if (empty($this->parsers[$type]))
138 {
139 throw new LogicException('No registered feed parser for type ' . $type . '.');
140 }
141
142 return new $this->parsers[$type]($reader);
143 }
144 }
145