db backup prior to drupal security update
[yaffs-website] / vendor / zendframework / zend-feed / src / Reader / Feed / Rss.php
1 <?php
2 /**
3  * Zend Framework (http://framework.zend.com/)
4  *
5  * @link      http://github.com/zendframework/zf2 for the canonical source repository
6  * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
7  * @license   http://framework.zend.com/license/new-bsd New BSD License
8  */
9
10 namespace Zend\Feed\Reader\Feed;
11
12 use DateTime;
13 use DOMDocument;
14 use Zend\Feed\Reader;
15 use Zend\Feed\Reader\Collection;
16 use Zend\Feed\Reader\Exception;
17
18 /**
19 */
20 class Rss extends AbstractFeed
21 {
22     /**
23      * Constructor
24      *
25      * @param  DOMDocument $dom
26      * @param  string $type
27      */
28     public function __construct(DOMDocument $dom, $type = null)
29     {
30         parent::__construct($dom, $type);
31
32         $manager = Reader\Reader::getExtensionManager();
33
34         $feed = $manager->get('DublinCore\Feed');
35         $feed->setDomDocument($dom);
36         $feed->setType($this->data['type']);
37         $feed->setXpath($this->xpath);
38         $this->extensions['DublinCore\Feed'] = $feed;
39
40         $feed = $manager->get('Atom\Feed');
41         $feed->setDomDocument($dom);
42         $feed->setType($this->data['type']);
43         $feed->setXpath($this->xpath);
44         $this->extensions['Atom\Feed'] = $feed;
45
46         if ($this->getType() !== Reader\Reader::TYPE_RSS_10
47             && $this->getType() !== Reader\Reader::TYPE_RSS_090
48         ) {
49             $xpathPrefix = '/rss/channel';
50         } else {
51             $xpathPrefix = '/rdf:RDF/rss:channel';
52         }
53         foreach ($this->extensions as $extension) {
54             $extension->setXpathPrefix($xpathPrefix);
55         }
56     }
57
58     /**
59      * Get a single author
60      *
61      * @param  int $index
62      * @return string|null
63      */
64     public function getAuthor($index = 0)
65     {
66         $authors = $this->getAuthors();
67
68         if (isset($authors[$index])) {
69             return $authors[$index];
70         }
71
72         return;
73     }
74
75     /**
76      * Get an array with feed authors
77      *
78      * @return array
79      */
80     public function getAuthors()
81     {
82         if (array_key_exists('authors', $this->data)) {
83             return $this->data['authors'];
84         }
85
86         $authors = [];
87         $authorsDc = $this->getExtension('DublinCore')->getAuthors();
88         if (! empty($authorsDc)) {
89             foreach ($authorsDc as $author) {
90                 $authors[] = [
91                     'name' => $author['name']
92                 ];
93             }
94         }
95
96         /**
97          * Technically RSS doesn't specific author element use at the feed level
98          * but it's supported on a "just in case" basis.
99          */
100         if ($this->getType() !== Reader\Reader::TYPE_RSS_10
101         && $this->getType() !== Reader\Reader::TYPE_RSS_090) {
102             $list = $this->xpath->query('//author');
103         } else {
104             $list = $this->xpath->query('//rss:author');
105         }
106         if ($list->length) {
107             foreach ($list as $author) {
108                 $string = trim($author->nodeValue);
109                 $data = [];
110                 // Pretty rough parsing - but it's a catchall
111                 if (preg_match("/^.*@[^ ]*/", $string, $matches)) {
112                     $data['email'] = trim($matches[0]);
113                     if (preg_match("/\((.*)\)$/", $string, $matches)) {
114                         $data['name'] = $matches[1];
115                     }
116                     $authors[] = $data;
117                 }
118             }
119         }
120
121         if (count($authors) == 0) {
122             $authors = $this->getExtension('Atom')->getAuthors();
123         } else {
124             $authors = new Reader\Collection\Author(
125                 Reader\Reader::arrayUnique($authors)
126             );
127         }
128
129         if (count($authors) == 0) {
130             $authors = null;
131         }
132
133         $this->data['authors'] = $authors;
134
135         return $this->data['authors'];
136     }
137
138     /**
139      * Get the copyright entry
140      *
141      * @return string|null
142      */
143     public function getCopyright()
144     {
145         if (array_key_exists('copyright', $this->data)) {
146             return $this->data['copyright'];
147         }
148
149         $copyright = null;
150
151         if ($this->getType() !== Reader\Reader::TYPE_RSS_10 &&
152             $this->getType() !== Reader\Reader::TYPE_RSS_090) {
153             $copyright = $this->xpath->evaluate('string(/rss/channel/copyright)');
154         }
155
156         if (! $copyright && $this->getExtension('DublinCore') !== null) {
157             $copyright = $this->getExtension('DublinCore')->getCopyright();
158         }
159
160         if (empty($copyright)) {
161             $copyright = $this->getExtension('Atom')->getCopyright();
162         }
163
164         if (! $copyright) {
165             $copyright = null;
166         }
167
168         $this->data['copyright'] = $copyright;
169
170         return $this->data['copyright'];
171     }
172
173     /**
174      * Get the feed creation date
175      *
176      * @return DateTime|null
177      */
178     public function getDateCreated()
179     {
180         return $this->getDateModified();
181     }
182
183     /**
184      * Get the feed modification date
185      *
186      * @return DateTime
187      * @throws Exception\RuntimeException
188      */
189     public function getDateModified()
190     {
191         if (array_key_exists('datemodified', $this->data)) {
192             return $this->data['datemodified'];
193         }
194
195         $date = null;
196
197         if ($this->getType() !== Reader\Reader::TYPE_RSS_10 &&
198             $this->getType() !== Reader\Reader::TYPE_RSS_090) {
199             $dateModified = $this->xpath->evaluate('string(/rss/channel/pubDate)');
200             if (! $dateModified) {
201                 $dateModified = $this->xpath->evaluate('string(/rss/channel/lastBuildDate)');
202             }
203             if ($dateModified) {
204                 $dateModifiedParsed = strtotime($dateModified);
205                 if ($dateModifiedParsed) {
206                     $date = new DateTime('@' . $dateModifiedParsed);
207                 } else {
208                     $dateStandards = [DateTime::RSS, DateTime::RFC822,
209                                            DateTime::RFC2822, null];
210                     foreach ($dateStandards as $standard) {
211                         try {
212                             $date = DateTime::createFromFormat($standard, $dateModified);
213                             break;
214                         } catch (\Exception $e) {
215                             if ($standard === null) {
216                                 throw new Exception\RuntimeException(
217                                     'Could not load date due to unrecognised'
218                                     .' format (should follow RFC 822 or 2822):'
219                                     . $e->getMessage(),
220                                     0,
221                                     $e
222                                 );
223                             }
224                         }
225                     }
226                 }
227             }
228         }
229
230         if (! $date) {
231             $date = $this->getExtension('DublinCore')->getDate();
232         }
233
234         if (! $date) {
235             $date = $this->getExtension('Atom')->getDateModified();
236         }
237
238         if (! $date) {
239             $date = null;
240         }
241
242         $this->data['datemodified'] = $date;
243
244         return $this->data['datemodified'];
245     }
246
247     /**
248      * Get the feed lastBuild date
249      *
250      * @throws Exception\RuntimeException
251      * @return DateTime
252      */
253     public function getLastBuildDate()
254     {
255         if (array_key_exists('lastBuildDate', $this->data)) {
256             return $this->data['lastBuildDate'];
257         }
258
259         $date = null;
260
261         if ($this->getType() !== Reader\Reader::TYPE_RSS_10 &&
262             $this->getType() !== Reader\Reader::TYPE_RSS_090) {
263             $lastBuildDate = $this->xpath->evaluate('string(/rss/channel/lastBuildDate)');
264             if ($lastBuildDate) {
265                 $lastBuildDateParsed = strtotime($lastBuildDate);
266                 if ($lastBuildDateParsed) {
267                     $date = new DateTime('@' . $lastBuildDateParsed);
268                 } else {
269                     $dateStandards = [DateTime::RSS, DateTime::RFC822,
270                                            DateTime::RFC2822, null];
271                     foreach ($dateStandards as $standard) {
272                         try {
273                             $date = DateTime::createFromFormat($standard, $lastBuildDateParsed);
274                             break;
275                         } catch (\Exception $e) {
276                             if ($standard === null) {
277                                 throw new Exception\RuntimeException(
278                                     'Could not load date due to unrecognised'
279                                     .' format (should follow RFC 822 or 2822):'
280                                     . $e->getMessage(),
281                                     0,
282                                     $e
283                                 );
284                             }
285                         }
286                     }
287                 }
288             }
289         }
290
291         if (! $date) {
292             $date = null;
293         }
294
295         $this->data['lastBuildDate'] = $date;
296
297         return $this->data['lastBuildDate'];
298     }
299
300     /**
301      * Get the feed description
302      *
303      * @return string|null
304      */
305     public function getDescription()
306     {
307         if (array_key_exists('description', $this->data)) {
308             return $this->data['description'];
309         }
310
311         if ($this->getType() !== Reader\Reader::TYPE_RSS_10 &&
312             $this->getType() !== Reader\Reader::TYPE_RSS_090) {
313             $description = $this->xpath->evaluate('string(/rss/channel/description)');
314         } else {
315             $description = $this->xpath->evaluate('string(/rdf:RDF/rss:channel/rss:description)');
316         }
317
318         if (! $description && $this->getExtension('DublinCore') !== null) {
319             $description = $this->getExtension('DublinCore')->getDescription();
320         }
321
322         if (empty($description)) {
323             $description = $this->getExtension('Atom')->getDescription();
324         }
325
326         if (! $description) {
327             $description = null;
328         }
329
330         $this->data['description'] = $description;
331
332         return $this->data['description'];
333     }
334
335     /**
336      * Get the feed ID
337      *
338      * @return string|null
339      */
340     public function getId()
341     {
342         if (array_key_exists('id', $this->data)) {
343             return $this->data['id'];
344         }
345
346         $id = null;
347
348         if ($this->getType() !== Reader\Reader::TYPE_RSS_10 &&
349             $this->getType() !== Reader\Reader::TYPE_RSS_090) {
350             $id = $this->xpath->evaluate('string(/rss/channel/guid)');
351         }
352
353         if (! $id && $this->getExtension('DublinCore') !== null) {
354             $id = $this->getExtension('DublinCore')->getId();
355         }
356
357         if (empty($id)) {
358             $id = $this->getExtension('Atom')->getId();
359         }
360
361         if (! $id) {
362             if ($this->getLink()) {
363                 $id = $this->getLink();
364             } elseif ($this->getTitle()) {
365                 $id = $this->getTitle();
366             } else {
367                 $id = null;
368             }
369         }
370
371         $this->data['id'] = $id;
372
373         return $this->data['id'];
374     }
375
376     /**
377      * Get the feed image data
378      *
379      * @return array|null
380      */
381     public function getImage()
382     {
383         if (array_key_exists('image', $this->data)) {
384             return $this->data['image'];
385         }
386
387         if ($this->getType() !== Reader\Reader::TYPE_RSS_10 &&
388             $this->getType() !== Reader\Reader::TYPE_RSS_090) {
389             $list = $this->xpath->query('/rss/channel/image');
390             $prefix = '/rss/channel/image[1]';
391         } else {
392             $list = $this->xpath->query('/rdf:RDF/rss:channel/rss:image');
393             $prefix = '/rdf:RDF/rss:channel/rss:image[1]';
394         }
395         if ($list->length > 0) {
396             $image = [];
397             $value = $this->xpath->evaluate('string(' . $prefix . '/url)');
398             if ($value) {
399                 $image['uri'] = $value;
400             }
401             $value = $this->xpath->evaluate('string(' . $prefix . '/link)');
402             if ($value) {
403                 $image['link'] = $value;
404             }
405             $value = $this->xpath->evaluate('string(' . $prefix . '/title)');
406             if ($value) {
407                 $image['title'] = $value;
408             }
409             $value = $this->xpath->evaluate('string(' . $prefix . '/height)');
410             if ($value) {
411                 $image['height'] = $value;
412             }
413             $value = $this->xpath->evaluate('string(' . $prefix . '/width)');
414             if ($value) {
415                 $image['width'] = $value;
416             }
417             $value = $this->xpath->evaluate('string(' . $prefix . '/description)');
418             if ($value) {
419                 $image['description'] = $value;
420             }
421         } else {
422             $image = null;
423         }
424
425         $this->data['image'] = $image;
426
427         return $this->data['image'];
428     }
429
430     /**
431      * Get the feed language
432      *
433      * @return string|null
434      */
435     public function getLanguage()
436     {
437         if (array_key_exists('language', $this->data)) {
438             return $this->data['language'];
439         }
440
441         $language = null;
442
443         if ($this->getType() !== Reader\Reader::TYPE_RSS_10 &&
444             $this->getType() !== Reader\Reader::TYPE_RSS_090) {
445             $language = $this->xpath->evaluate('string(/rss/channel/language)');
446         }
447
448         if (! $language && $this->getExtension('DublinCore') !== null) {
449             $language = $this->getExtension('DublinCore')->getLanguage();
450         }
451
452         if (empty($language)) {
453             $language = $this->getExtension('Atom')->getLanguage();
454         }
455
456         if (! $language) {
457             $language = $this->xpath->evaluate('string(//@xml:lang[1])');
458         }
459
460         if (! $language) {
461             $language = null;
462         }
463
464         $this->data['language'] = $language;
465
466         return $this->data['language'];
467     }
468
469     /**
470      * Get a link to the feed
471      *
472      * @return string|null
473      */
474     public function getLink()
475     {
476         if (array_key_exists('link', $this->data)) {
477             return $this->data['link'];
478         }
479
480         if ($this->getType() !== Reader\Reader::TYPE_RSS_10 &&
481             $this->getType() !== Reader\Reader::TYPE_RSS_090) {
482             $link = $this->xpath->evaluate('string(/rss/channel/link)');
483         } else {
484             $link = $this->xpath->evaluate('string(/rdf:RDF/rss:channel/rss:link)');
485         }
486
487         if (empty($link)) {
488             $link = $this->getExtension('Atom')->getLink();
489         }
490
491         if (! $link) {
492             $link = null;
493         }
494
495         $this->data['link'] = $link;
496
497         return $this->data['link'];
498     }
499
500     /**
501      * Get a link to the feed XML
502      *
503      * @return string|null
504      */
505     public function getFeedLink()
506     {
507         if (array_key_exists('feedlink', $this->data)) {
508             return $this->data['feedlink'];
509         }
510
511         $link = $this->getExtension('Atom')->getFeedLink();
512
513         if ($link === null || empty($link)) {
514             $link = $this->getOriginalSourceUri();
515         }
516
517         $this->data['feedlink'] = $link;
518
519         return $this->data['feedlink'];
520     }
521
522     /**
523      * Get the feed generator entry
524      *
525      * @return string|null
526      */
527     public function getGenerator()
528     {
529         if (array_key_exists('generator', $this->data)) {
530             return $this->data['generator'];
531         }
532
533         $generator = null;
534
535         if ($this->getType() !== Reader\Reader::TYPE_RSS_10 &&
536             $this->getType() !== Reader\Reader::TYPE_RSS_090) {
537             $generator = $this->xpath->evaluate('string(/rss/channel/generator)');
538         }
539
540         if (! $generator) {
541             if ($this->getType() !== Reader\Reader::TYPE_RSS_10 &&
542             $this->getType() !== Reader\Reader::TYPE_RSS_090) {
543                 $generator = $this->xpath->evaluate('string(/rss/channel/atom:generator)');
544             } else {
545                 $generator = $this->xpath->evaluate('string(/rdf:RDF/rss:channel/atom:generator)');
546             }
547         }
548
549         if (empty($generator)) {
550             $generator = $this->getExtension('Atom')->getGenerator();
551         }
552
553         if (! $generator) {
554             $generator = null;
555         }
556
557         $this->data['generator'] = $generator;
558
559         return $this->data['generator'];
560     }
561
562     /**
563      * Get the feed title
564      *
565      * @return string|null
566      */
567     public function getTitle()
568     {
569         if (array_key_exists('title', $this->data)) {
570             return $this->data['title'];
571         }
572
573         if ($this->getType() !== Reader\Reader::TYPE_RSS_10 &&
574             $this->getType() !== Reader\Reader::TYPE_RSS_090) {
575             $title = $this->xpath->evaluate('string(/rss/channel/title)');
576         } else {
577             $title = $this->xpath->evaluate('string(/rdf:RDF/rss:channel/rss:title)');
578         }
579
580         if (! $title && $this->getExtension('DublinCore') !== null) {
581             $title = $this->getExtension('DublinCore')->getTitle();
582         }
583
584         if (! $title) {
585             $title = $this->getExtension('Atom')->getTitle();
586         }
587
588         if (! $title) {
589             $title = null;
590         }
591
592         $this->data['title'] = $title;
593
594         return $this->data['title'];
595     }
596
597     /**
598      * Get an array of any supported Pusubhubbub endpoints
599      *
600      * @return array|null
601      */
602     public function getHubs()
603     {
604         if (array_key_exists('hubs', $this->data)) {
605             return $this->data['hubs'];
606         }
607
608         $hubs = $this->getExtension('Atom')->getHubs();
609
610         if (empty($hubs)) {
611             $hubs = null;
612         } else {
613             $hubs = array_unique($hubs);
614         }
615
616         $this->data['hubs'] = $hubs;
617
618         return $this->data['hubs'];
619     }
620
621     /**
622      * Get all categories
623      *
624      * @return Reader\Collection\Category
625      */
626     public function getCategories()
627     {
628         if (array_key_exists('categories', $this->data)) {
629             return $this->data['categories'];
630         }
631
632         if ($this->getType() !== Reader\Reader::TYPE_RSS_10 &&
633             $this->getType() !== Reader\Reader::TYPE_RSS_090) {
634             $list = $this->xpath->query('/rss/channel//category');
635         } else {
636             $list = $this->xpath->query('/rdf:RDF/rss:channel//rss:category');
637         }
638
639         if ($list->length) {
640             $categoryCollection = new Collection\Category;
641             foreach ($list as $category) {
642                 $categoryCollection[] = [
643                     'term' => $category->nodeValue,
644                     'scheme' => $category->getAttribute('domain'),
645                     'label' => $category->nodeValue,
646                 ];
647             }
648         } else {
649             $categoryCollection = $this->getExtension('DublinCore')->getCategories();
650         }
651
652         if (count($categoryCollection) == 0) {
653             $categoryCollection = $this->getExtension('Atom')->getCategories();
654         }
655
656         $this->data['categories'] = $categoryCollection;
657
658         return $this->data['categories'];
659     }
660
661     /**
662      * Read all entries to the internal entries array
663      *
664      */
665     protected function indexEntries()
666     {
667         if ($this->getType() !== Reader\Reader::TYPE_RSS_10 && $this->getType() !== Reader\Reader::TYPE_RSS_090) {
668             $entries = $this->xpath->evaluate('//item');
669         } else {
670             $entries = $this->xpath->evaluate('//rss:item');
671         }
672
673         foreach ($entries as $index => $entry) {
674             $this->entries[$index] = $entry;
675         }
676     }
677
678     /**
679      * Register the default namespaces for the current feed format
680      *
681      */
682     protected function registerNamespaces()
683     {
684         switch ($this->data['type']) {
685             case Reader\Reader::TYPE_RSS_10:
686                 $this->xpath->registerNamespace('rdf', Reader\Reader::NAMESPACE_RDF);
687                 $this->xpath->registerNamespace('rss', Reader\Reader::NAMESPACE_RSS_10);
688                 break;
689
690             case Reader\Reader::TYPE_RSS_090:
691                 $this->xpath->registerNamespace('rdf', Reader\Reader::NAMESPACE_RDF);
692                 $this->xpath->registerNamespace('rss', Reader\Reader::NAMESPACE_RSS_090);
693                 break;
694         }
695     }
696 }