Security update for Core, with self-updated composer
[yaffs-website] / vendor / zendframework / zend-feed / src / Writer / AbstractFeed.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\Writer;
11
12 use DateTime;
13 use Zend\Feed\Uri;
14 use Zend\Validator;
15
16 class AbstractFeed
17 {
18     /**
19      * Contains all Feed level date to append in feed output
20      *
21      * @var array
22      */
23     protected $data = [];
24
25     /**
26      * Holds the value "atom" or "rss" depending on the feed type set when
27      * when last exported.
28      *
29      * @var string
30      */
31     protected $type = null;
32
33     /**
34      * @var $extensions
35      */
36     protected $extensions;
37
38     /**
39      * Constructor: Primarily triggers the registration of core extensions and
40      * loads those appropriate to this data container.
41      *
42      */
43     public function __construct()
44     {
45         Writer::registerCoreExtensions();
46         $this->_loadExtensions();
47     }
48
49     /**
50      * Set a single author
51      *
52      * The following option keys are supported:
53      * 'name'  => (string) The name
54      * 'email' => (string) An optional email
55      * 'uri'   => (string) An optional and valid URI
56      *
57      * @param array $author
58      * @throws Exception\InvalidArgumentException If any value of $author not follow the format.
59      * @return AbstractFeed
60      */
61     public function addAuthor(array $author)
62     {
63         // Check array values
64         if (! array_key_exists('name', $author)
65             || empty($author['name'])
66             || ! is_string($author['name'])
67         ) {
68             throw new Exception\InvalidArgumentException(
69                 'Invalid parameter: author array must include a "name" key with a non-empty string value'
70             );
71         }
72
73         if (isset($author['email'])) {
74             if (empty($author['email']) || ! is_string($author['email'])) {
75                 throw new Exception\InvalidArgumentException(
76                     'Invalid parameter: "email" array value must be a non-empty string'
77                 );
78             }
79         }
80         if (isset($author['uri'])) {
81             if (empty($author['uri']) || ! is_string($author['uri']) ||
82                 ! Uri::factory($author['uri'])->isValid()
83             ) {
84                 throw new Exception\InvalidArgumentException(
85                     'Invalid parameter: "uri" array value must be a non-empty string and valid URI/IRI'
86                 );
87             }
88         }
89
90         $this->data['authors'][] = $author;
91
92         return $this;
93     }
94
95     /**
96      * Set an array with feed authors
97      *
98      * @see addAuthor
99      * @param array $authors
100      * @return AbstractFeed
101      */
102     public function addAuthors(array $authors)
103     {
104         foreach ($authors as $author) {
105             $this->addAuthor($author);
106         }
107
108         return $this;
109     }
110
111     /**
112      * Set the copyright entry
113      *
114      * @param  string      $copyright
115      * @throws Exception\InvalidArgumentException
116      * @return AbstractFeed
117      */
118     public function setCopyright($copyright)
119     {
120         if (empty($copyright) || ! is_string($copyright)) {
121             throw new Exception\InvalidArgumentException('Invalid parameter: parameter must be a non-empty string');
122         }
123         $this->data['copyright'] = $copyright;
124
125         return $this;
126     }
127
128     /**
129      * Set the feed creation date
130      *
131      * @param null|int|DateTime
132      * @throws Exception\InvalidArgumentException
133      * @return AbstractFeed
134      */
135     public function setDateCreated($date = null)
136     {
137         if ($date === null) {
138             $date = new DateTime();
139         } elseif (is_int($date)) {
140             $date = new DateTime('@' . $date);
141         } elseif (! $date instanceof DateTime) {
142             throw new Exception\InvalidArgumentException('Invalid DateTime object or UNIX Timestamp'
143                                                          . ' passed as parameter');
144         }
145         $this->data['dateCreated'] = $date;
146
147         return $this;
148     }
149
150     /**
151      * Set the feed modification date
152      *
153      * @param null|int|DateTime
154      * @throws Exception\InvalidArgumentException
155      * @return AbstractFeed
156      */
157     public function setDateModified($date = null)
158     {
159         if ($date === null) {
160             $date = new DateTime();
161         } elseif (is_int($date)) {
162             $date = new DateTime('@' . $date);
163         } elseif (! $date instanceof DateTime) {
164             throw new Exception\InvalidArgumentException('Invalid DateTime object or UNIX Timestamp'
165                                                          . ' passed as parameter');
166         }
167         $this->data['dateModified'] = $date;
168
169         return $this;
170     }
171
172     /**
173      * Set the feed last-build date. Ignored for Atom 1.0.
174      *
175      * @param null|int|DateTime
176      * @throws Exception\InvalidArgumentException
177      * @return AbstractFeed
178      */
179     public function setLastBuildDate($date = null)
180     {
181         if ($date === null) {
182             $date = new DateTime();
183         } elseif (is_int($date)) {
184             $date = new DateTime('@' . $date);
185         } elseif (! $date instanceof DateTime) {
186             throw new Exception\InvalidArgumentException('Invalid DateTime object or UNIX Timestamp'
187                                                          . ' passed as parameter');
188         }
189         $this->data['lastBuildDate'] = $date;
190
191         return $this;
192     }
193
194     /**
195      * Set the feed description
196      *
197      * @param string $description
198      * @throws Exception\InvalidArgumentException
199      * @return AbstractFeed
200      */
201     public function setDescription($description)
202     {
203         if (empty($description) || ! is_string($description)) {
204             throw new Exception\InvalidArgumentException('Invalid parameter: parameter must be a non-empty string');
205         }
206         $this->data['description'] = $description;
207
208         return $this;
209     }
210
211     /**
212      * Set the feed generator entry
213      *
214      * @param array|string $name
215      * @param null|string $version
216      * @param null|string $uri
217      * @throws Exception\InvalidArgumentException
218      * @return AbstractFeed
219      */
220     public function setGenerator($name, $version = null, $uri = null)
221     {
222         if (is_array($name)) {
223             $data = $name;
224             if (empty($data['name']) || ! is_string($data['name'])) {
225                 throw new Exception\InvalidArgumentException('Invalid parameter: "name" must be a non-empty string');
226             }
227             $generator = ['name' => $data['name']];
228             if (isset($data['version'])) {
229                 if (empty($data['version']) || ! is_string($data['version'])) {
230                     throw new Exception\InvalidArgumentException(
231                         'Invalid parameter: "version" must be a non-empty string'
232                     );
233                 }
234                 $generator['version'] = $data['version'];
235             }
236             if (isset($data['uri'])) {
237                 if (empty($data['uri']) || ! is_string($data['uri']) || ! Uri::factory($data['uri'])->isValid()) {
238                     throw new Exception\InvalidArgumentException(
239                         'Invalid parameter: "uri" must be a non-empty string and a valid URI/IRI'
240                     );
241                 }
242                 $generator['uri'] = $data['uri'];
243             }
244         } else {
245             if (empty($name) || ! is_string($name)) {
246                 throw new Exception\InvalidArgumentException('Invalid parameter: "name" must be a non-empty string');
247             }
248             $generator = ['name' => $name];
249             if (isset($version)) {
250                 if (empty($version) || ! is_string($version)) {
251                     throw new Exception\InvalidArgumentException(
252                         'Invalid parameter: "version" must be a non-empty string'
253                     );
254                 }
255                 $generator['version'] = $version;
256             }
257             if (isset($uri)) {
258                 if (empty($uri) || ! is_string($uri) || ! Uri::factory($uri)->isValid()) {
259                     throw new Exception\InvalidArgumentException(
260                         'Invalid parameter: "uri" must be a non-empty string and a valid URI/IRI'
261                     );
262                 }
263                 $generator['uri'] = $uri;
264             }
265         }
266         $this->data['generator'] = $generator;
267
268         return $this;
269     }
270
271     /**
272      * Set the feed ID - URI or URN (via PCRE pattern) supported
273      *
274      * @param string $id
275      * @throws Exception\InvalidArgumentException
276      * @return AbstractFeed
277      */
278     public function setId($id)
279     {
280         // @codingStandardsIgnoreStart
281         if ((empty($id) || ! is_string($id) || ! Uri::factory($id)->isValid())
282             && ! preg_match("#^urn:[a-zA-Z0-9][a-zA-Z0-9\-]{1,31}:([a-zA-Z0-9\(\)\+\,\.\:\=\@\;\$\_\!\*\-]|%[0-9a-fA-F]{2})*#", $id)
283             && ! $this->_validateTagUri($id)
284         ) {
285             // @codingStandardsIgnoreEnd
286             throw new Exception\InvalidArgumentException(
287                 'Invalid parameter: parameter must be a non-empty string and valid URI/IRI'
288             );
289         }
290         $this->data['id'] = $id;
291
292         return $this;
293     }
294
295     /**
296      * Validate a URI using the tag scheme (RFC 4151)
297      *
298      * @param string $id
299      * @return bool
300      */
301     // @codingStandardsIgnoreStart
302     protected function _validateTagUri($id)
303     {
304         // @codingStandardsIgnoreEnd
305         if (preg_match(
306             '/^tag:(?P<name>.*),(?P<date>\d{4}-?\d{0,2}-?\d{0,2}):(?P<specific>.*)(.*:)*$/',
307             $id,
308             $matches
309         )) {
310             $dvalid = false;
311             $date = $matches['date'];
312             $d6 = strtotime($date);
313             if ((strlen($date) == 4) && $date <= date('Y')) {
314                 $dvalid = true;
315             } elseif ((strlen($date) == 7) && ($d6 < strtotime("now"))) {
316                 $dvalid = true;
317             } elseif ((strlen($date) == 10) && ($d6 < strtotime("now"))) {
318                 $dvalid = true;
319             }
320             $validator = new Validator\EmailAddress;
321             if ($validator->isValid($matches['name'])) {
322                 $nvalid = true;
323             } else {
324                 $nvalid = $validator->isValid('info@' . $matches['name']);
325             }
326             return $dvalid && $nvalid;
327         }
328         return false;
329     }
330
331     /**
332      * Set a feed image (URI at minimum). Parameter is a single array with the
333      * required key 'uri'. When rendering as RSS, the required keys are 'uri',
334      * 'title' and 'link'. RSS also specifies three optional parameters 'width',
335      * 'height' and 'description'. Only 'uri' is required and used for Atom rendering.
336      *
337      * @param array $data
338      * @throws Exception\InvalidArgumentException
339      * @return AbstractFeed
340      */
341     public function setImage(array $data)
342     {
343         if (empty($data['uri']) || ! is_string($data['uri'])
344             || ! Uri::factory($data['uri'])->isValid()
345         ) {
346             throw new Exception\InvalidArgumentException('Invalid parameter: parameter \'uri\''
347             . ' must be a non-empty string and valid URI/IRI');
348         }
349         $this->data['image'] = $data;
350
351         return $this;
352     }
353
354     /**
355      * Set the feed language
356      *
357      * @param string $language
358      * @throws Exception\InvalidArgumentException
359      * @return AbstractFeed
360      */
361     public function setLanguage($language)
362     {
363         if (empty($language) || ! is_string($language)) {
364             throw new Exception\InvalidArgumentException('Invalid parameter: parameter must be a non-empty string');
365         }
366         $this->data['language'] = $language;
367
368         return $this;
369     }
370
371     /**
372      * Set a link to the HTML source
373      *
374      * @param string $link
375      * @throws Exception\InvalidArgumentException
376      * @return AbstractFeed
377      */
378     public function setLink($link)
379     {
380         if (empty($link) || ! is_string($link) || ! Uri::factory($link)->isValid()) {
381             throw new Exception\InvalidArgumentException(
382                 'Invalid parameter: parameter must be a non-empty string and valid URI/IRI'
383             );
384         }
385         $this->data['link'] = $link;
386
387         return $this;
388     }
389
390     /**
391      * Set a link to an XML feed for any feed type/version
392      *
393      * @param string $link
394      * @param string $type
395      * @throws Exception\InvalidArgumentException
396      * @return AbstractFeed
397      */
398     public function setFeedLink($link, $type)
399     {
400         if (empty($link) || ! is_string($link) || ! Uri::factory($link)->isValid()) {
401             throw new Exception\InvalidArgumentException(
402                 'Invalid parameter: "link"" must be a non-empty string and valid URI/IRI'
403             );
404         }
405         if (! in_array(strtolower($type), ['rss', 'rdf', 'atom'])) {
406             throw new Exception\InvalidArgumentException(
407                 'Invalid parameter: "type"; You must declare the type of feed the link points to, i.e. RSS, RDF or Atom'
408             );
409         }
410         $this->data['feedLinks'][strtolower($type)] = $link;
411
412         return $this;
413     }
414
415     /**
416      * Set the feed title
417      *
418      * @param string $title
419      * @throws Exception\InvalidArgumentException
420      * @return AbstractFeed
421      */
422     public function setTitle($title)
423     {
424         if (empty($title) || ! is_string($title)) {
425             throw new Exception\InvalidArgumentException('Invalid parameter: parameter must be a non-empty string');
426         }
427         $this->data['title'] = $title;
428
429         return $this;
430     }
431
432     /**
433      * Set the feed character encoding
434      *
435      * @param string $encoding
436      * @throws Exception\InvalidArgumentException
437      * @return AbstractFeed
438      */
439     public function setEncoding($encoding)
440     {
441         if (empty($encoding) || ! is_string($encoding)) {
442             throw new Exception\InvalidArgumentException('Invalid parameter: parameter must be a non-empty string');
443         }
444         $this->data['encoding'] = $encoding;
445
446         return $this;
447     }
448
449     /**
450      * Set the feed's base URL
451      *
452      * @param string $url
453      * @throws Exception\InvalidArgumentException
454      * @return AbstractFeed
455      */
456     public function setBaseUrl($url)
457     {
458         if (empty($url) || ! is_string($url) || ! Uri::factory($url)->isValid()) {
459             throw new Exception\InvalidArgumentException('Invalid parameter: "url" array value'
460             . ' must be a non-empty string and valid URI/IRI');
461         }
462         $this->data['baseUrl'] = $url;
463
464         return $this;
465     }
466
467     /**
468      * Add a Pubsubhubbub hub endpoint URL
469      *
470      * @param string $url
471      * @throws Exception\InvalidArgumentException
472      * @return AbstractFeed
473      */
474     public function addHub($url)
475     {
476         if (empty($url) || ! is_string($url) || ! Uri::factory($url)->isValid()) {
477             throw new Exception\InvalidArgumentException('Invalid parameter: "url" array value'
478             . ' must be a non-empty string and valid URI/IRI');
479         }
480         if (! isset($this->data['hubs'])) {
481             $this->data['hubs'] = [];
482         }
483         $this->data['hubs'][] = $url;
484
485         return $this;
486     }
487
488     /**
489      * Add Pubsubhubbub hub endpoint URLs
490      *
491      * @param array $urls
492      * @return AbstractFeed
493      */
494     public function addHubs(array $urls)
495     {
496         foreach ($urls as $url) {
497             $this->addHub($url);
498         }
499
500         return $this;
501     }
502
503     /**
504      * Add a feed category
505      *
506      * @param array $category
507      * @throws Exception\InvalidArgumentException
508      * @return AbstractFeed
509      */
510     public function addCategory(array $category)
511     {
512         if (! isset($category['term'])) {
513             throw new Exception\InvalidArgumentException('Each category must be an array and '
514             . 'contain at least a "term" element containing the machine '
515             . ' readable category name');
516         }
517         if (isset($category['scheme'])) {
518             if (empty($category['scheme'])
519                 || ! is_string($category['scheme'])
520                 || ! Uri::factory($category['scheme'])->isValid()
521             ) {
522                 throw new Exception\InvalidArgumentException('The Atom scheme or RSS domain of'
523                 . ' a category must be a valid URI');
524             }
525         }
526         if (! isset($this->data['categories'])) {
527             $this->data['categories'] = [];
528         }
529         $this->data['categories'][] = $category;
530
531         return $this;
532     }
533
534     /**
535      * Set an array of feed categories
536      *
537      * @param array $categories
538      * @return AbstractFeed
539      */
540     public function addCategories(array $categories)
541     {
542         foreach ($categories as $category) {
543             $this->addCategory($category);
544         }
545
546         return $this;
547     }
548
549     /**
550      * Get a single author
551      *
552      * @param  int $index
553      * @return string|null
554      */
555     public function getAuthor($index = 0)
556     {
557         if (isset($this->data['authors'][$index])) {
558             return $this->data['authors'][$index];
559         }
560
561         return;
562     }
563
564     /**
565      * Get an array with feed authors
566      *
567      * @return array
568      */
569     public function getAuthors()
570     {
571         if (! array_key_exists('authors', $this->data)) {
572             return;
573         }
574         return $this->data['authors'];
575     }
576
577     /**
578      * Get the copyright entry
579      *
580      * @return string|null
581      */
582     public function getCopyright()
583     {
584         if (! array_key_exists('copyright', $this->data)) {
585             return;
586         }
587         return $this->data['copyright'];
588     }
589
590     /**
591      * Get the feed creation date
592      *
593      * @return string|null
594      */
595     public function getDateCreated()
596     {
597         if (! array_key_exists('dateCreated', $this->data)) {
598             return;
599         }
600         return $this->data['dateCreated'];
601     }
602
603     /**
604      * Get the feed modification date
605      *
606      * @return string|null
607      */
608     public function getDateModified()
609     {
610         if (! array_key_exists('dateModified', $this->data)) {
611             return;
612         }
613         return $this->data['dateModified'];
614     }
615
616     /**
617      * Get the feed last-build date
618      *
619      * @return string|null
620      */
621     public function getLastBuildDate()
622     {
623         if (! array_key_exists('lastBuildDate', $this->data)) {
624             return;
625         }
626         return $this->data['lastBuildDate'];
627     }
628
629     /**
630      * Get the feed description
631      *
632      * @return string|null
633      */
634     public function getDescription()
635     {
636         if (! array_key_exists('description', $this->data)) {
637             return;
638         }
639         return $this->data['description'];
640     }
641
642     /**
643      * Get the feed generator entry
644      *
645      * @return string|null
646      */
647     public function getGenerator()
648     {
649         if (! array_key_exists('generator', $this->data)) {
650             return;
651         }
652         return $this->data['generator'];
653     }
654
655     /**
656      * Get the feed ID
657      *
658      * @return string|null
659      */
660     public function getId()
661     {
662         if (! array_key_exists('id', $this->data)) {
663             return;
664         }
665         return $this->data['id'];
666     }
667
668     /**
669      * Get the feed image URI
670      *
671      * @return array
672      */
673     public function getImage()
674     {
675         if (! array_key_exists('image', $this->data)) {
676             return;
677         }
678         return $this->data['image'];
679     }
680
681     /**
682      * Get the feed language
683      *
684      * @return string|null
685      */
686     public function getLanguage()
687     {
688         if (! array_key_exists('language', $this->data)) {
689             return;
690         }
691         return $this->data['language'];
692     }
693
694     /**
695      * Get a link to the HTML source
696      *
697      * @return string|null
698      */
699     public function getLink()
700     {
701         if (! array_key_exists('link', $this->data)) {
702             return;
703         }
704         return $this->data['link'];
705     }
706
707     /**
708      * Get a link to the XML feed
709      *
710      * @return string|null
711      */
712     public function getFeedLinks()
713     {
714         if (! array_key_exists('feedLinks', $this->data)) {
715             return;
716         }
717         return $this->data['feedLinks'];
718     }
719
720     /**
721      * Get the feed title
722      *
723      * @return string|null
724      */
725     public function getTitle()
726     {
727         if (! array_key_exists('title', $this->data)) {
728             return;
729         }
730         return $this->data['title'];
731     }
732
733     /**
734      * Get the feed character encoding
735      *
736      * @return string|null
737      */
738     public function getEncoding()
739     {
740         if (! array_key_exists('encoding', $this->data)) {
741             return 'UTF-8';
742         }
743         return $this->data['encoding'];
744     }
745
746     /**
747      * Get the feed's base url
748      *
749      * @return string|null
750      */
751     public function getBaseUrl()
752     {
753         if (! array_key_exists('baseUrl', $this->data)) {
754             return;
755         }
756         return $this->data['baseUrl'];
757     }
758
759     /**
760      * Get the URLs used as Pubsubhubbub hubs endpoints
761      *
762      * @return string|null
763      */
764     public function getHubs()
765     {
766         if (! array_key_exists('hubs', $this->data)) {
767             return;
768         }
769         return $this->data['hubs'];
770     }
771
772     /**
773      * Get the feed categories
774      *
775      * @return string|null
776      */
777     public function getCategories()
778     {
779         if (! array_key_exists('categories', $this->data)) {
780             return;
781         }
782         return $this->data['categories'];
783     }
784
785     /**
786      * Resets the instance and deletes all data
787      *
788      * @return void
789      */
790     public function reset()
791     {
792         $this->data = [];
793     }
794
795     /**
796      * Set the current feed type being exported to "rss" or "atom". This allows
797      * other objects to gracefully choose whether to execute or not, depending
798      * on their appropriateness for the current type, e.g. renderers.
799      *
800      * @param string $type
801      * @return AbstractFeed
802      */
803     public function setType($type)
804     {
805         $this->type = $type;
806         return $this;
807     }
808
809     /**
810      * Retrieve the current or last feed type exported.
811      *
812      * @return string Value will be "rss" or "atom"
813      */
814     public function getType()
815     {
816         return $this->type;
817     }
818
819     /**
820      * Unset a specific data point
821      *
822      * @param string $name
823      * @return AbstractFeed
824      */
825     public function remove($name)
826     {
827         if (isset($this->data[$name])) {
828             unset($this->data[$name]);
829         }
830         return $this;
831     }
832
833     /**
834      * Method overloading: call given method on first extension implementing it
835      *
836      * @param  string $method
837      * @param  array $args
838      * @return mixed
839      * @throws Exception\BadMethodCallException if no extensions implements the method
840      */
841     public function __call($method, $args)
842     {
843         foreach ($this->extensions as $extension) {
844             try {
845                 return call_user_func_array([$extension, $method], $args);
846             } catch (Exception\BadMethodCallException $e) {
847             }
848         }
849         throw new Exception\BadMethodCallException(
850             'Method: ' . $method . ' does not exist and could not be located on a registered Extension'
851         );
852     }
853
854     /**
855      * Load extensions from Zend\Feed\Writer\Writer
856      *
857      * @throws Exception\RuntimeException
858      * @return void
859      */
860     // @codingStandardsIgnoreStart
861     protected function _loadExtensions()
862     {
863         // @codingStandardsIgnoreEnd
864         $all     = Writer::getExtensions();
865         $manager = Writer::getExtensionManager();
866         $exts    = $all['feed'];
867         foreach ($exts as $ext) {
868             if (! $manager->has($ext)) {
869                 throw new Exception\RuntimeException(
870                     sprintf('Unable to load extension "%s"; could not resolve to class', $ext)
871                 );
872             }
873             $this->extensions[$ext] = $manager->get($ext);
874             $this->extensions[$ext]->setEncoding($this->getEncoding());
875         }
876     }
877 }