84ae0773d351b3fdd6ff7f3be07c9da34ecec1b1
[yaffs-website] / vendor / zendframework / zend-feed / src / Writer / Renderer / Entry / Atom.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\Renderer\Entry;
11
12 use DateTime;
13 use DOMDocument;
14 use DOMElement;
15 use Zend\Feed\Uri;
16 use Zend\Feed\Writer;
17 use Zend\Feed\Writer\Renderer;
18 use Zend\Validator;
19
20 class Atom extends Renderer\AbstractRenderer implements Renderer\RendererInterface
21 {
22     /**
23      * Constructor
24      *
25      * @param  Writer\Entry $container
26      */
27     public function __construct(Writer\Entry $container)
28     {
29         parent::__construct($container);
30     }
31
32     /**
33      * Render atom entry
34      *
35      * @return Atom
36      */
37     public function render()
38     {
39         $this->dom = new DOMDocument('1.0', $this->container->getEncoding());
40         $this->dom->formatOutput = true;
41         $entry = $this->dom->createElementNS(Writer\Writer::NAMESPACE_ATOM_10, 'entry');
42         $this->dom->appendChild($entry);
43
44         $this->_setSource($this->dom, $entry);
45         $this->_setTitle($this->dom, $entry);
46         $this->_setDescription($this->dom, $entry);
47         $this->_setDateCreated($this->dom, $entry);
48         $this->_setDateModified($this->dom, $entry);
49         $this->_setLink($this->dom, $entry);
50         $this->_setId($this->dom, $entry);
51         $this->_setAuthors($this->dom, $entry);
52         $this->_setEnclosure($this->dom, $entry);
53         $this->_setContent($this->dom, $entry);
54         $this->_setCategories($this->dom, $entry);
55
56         foreach ($this->extensions as $ext) {
57             $ext->setType($this->getType());
58             $ext->setRootElement($this->getRootElement());
59             $ext->setDOMDocument($this->getDOMDocument(), $entry);
60             $ext->render();
61         }
62
63         return $this;
64     }
65
66     /**
67      * Set entry title
68      *
69      * @param  DOMDocument $dom
70      * @param  DOMElement $root
71      * @return void
72      * @throws Writer\Exception\InvalidArgumentException
73      */
74     // @codingStandardsIgnoreStart
75     protected function _setTitle(DOMDocument $dom, DOMElement $root)
76     {
77         // @codingStandardsIgnoreEnd
78         if (! $this->getDataContainer()->getTitle()) {
79             $message = 'Atom 1.0 entry elements MUST contain exactly one'
80             . ' atom:title element but a title has not been set';
81             $exception = new Writer\Exception\InvalidArgumentException($message);
82             if (! $this->ignoreExceptions) {
83                 throw $exception;
84             } else {
85                 $this->exceptions[] = $exception;
86                 return;
87             }
88         }
89         $title = $dom->createElement('title');
90         $root->appendChild($title);
91         $title->setAttribute('type', 'html');
92         $cdata = $dom->createCDATASection($this->getDataContainer()->getTitle());
93         $title->appendChild($cdata);
94     }
95
96     /**
97      * Set entry description
98      *
99      * @param  DOMDocument $dom
100      * @param  DOMElement $root
101      * @return void
102      */
103     // @codingStandardsIgnoreStart
104     protected function _setDescription(DOMDocument $dom, DOMElement $root)
105     {
106         // @codingStandardsIgnoreEnd
107         if (! $this->getDataContainer()->getDescription()) {
108             return; // unless src content or base64
109         }
110         $subtitle = $dom->createElement('summary');
111         $root->appendChild($subtitle);
112         $subtitle->setAttribute('type', 'html');
113         $cdata = $dom->createCDATASection(
114             $this->getDataContainer()->getDescription()
115         );
116         $subtitle->appendChild($cdata);
117     }
118
119     /**
120      * Set date entry was modified
121      *
122      * @param  DOMDocument $dom
123      * @param  DOMElement $root
124      * @return void
125      * @throws Writer\Exception\InvalidArgumentException
126      */
127     // @codingStandardsIgnoreStart
128     protected function _setDateModified(DOMDocument $dom, DOMElement $root)
129     {
130         // @codingStandardsIgnoreEnd
131         if (! $this->getDataContainer()->getDateModified()) {
132             $message = 'Atom 1.0 entry elements MUST contain exactly one'
133             . ' atom:updated element but a modification date has not been set';
134             $exception = new Writer\Exception\InvalidArgumentException($message);
135             if (! $this->ignoreExceptions) {
136                 throw $exception;
137             } else {
138                 $this->exceptions[] = $exception;
139                 return;
140             }
141         }
142
143         $updated = $dom->createElement('updated');
144         $root->appendChild($updated);
145         $text = $dom->createTextNode(
146             $this->getDataContainer()->getDateModified()->format(DateTime::ATOM)
147         );
148         $updated->appendChild($text);
149     }
150
151     /**
152      * Set date entry was created
153      *
154      * @param  DOMDocument $dom
155      * @param  DOMElement $root
156      * @return void
157      */
158     // @codingStandardsIgnoreStart
159     protected function _setDateCreated(DOMDocument $dom, DOMElement $root)
160     {
161         // @codingStandardsIgnoreEnd
162         if (! $this->getDataContainer()->getDateCreated()) {
163             return;
164         }
165         $el = $dom->createElement('published');
166         $root->appendChild($el);
167         $text = $dom->createTextNode(
168             $this->getDataContainer()->getDateCreated()->format(DateTime::ATOM)
169         );
170         $el->appendChild($text);
171     }
172
173     /**
174      * Set entry authors
175      *
176      * @param  DOMDocument $dom
177      * @param  DOMElement $root
178      * @return void
179      */
180     // @codingStandardsIgnoreStart
181     protected function _setAuthors(DOMDocument $dom, DOMElement $root)
182     {
183         // @codingStandardsIgnoreEnd
184         $authors = $this->container->getAuthors();
185         if ((! $authors || empty($authors))) {
186             /**
187              * This will actually trigger an Exception at the feed level if
188              * a feed level author is not set.
189              */
190             return;
191         }
192         foreach ($authors as $data) {
193             $author = $this->dom->createElement('author');
194             $name = $this->dom->createElement('name');
195             $author->appendChild($name);
196             $root->appendChild($author);
197             $text = $dom->createTextNode($data['name']);
198             $name->appendChild($text);
199             if (array_key_exists('email', $data)) {
200                 $email = $this->dom->createElement('email');
201                 $author->appendChild($email);
202                 $text = $dom->createTextNode($data['email']);
203                 $email->appendChild($text);
204             }
205             if (array_key_exists('uri', $data)) {
206                 $uri = $this->dom->createElement('uri');
207                 $author->appendChild($uri);
208                 $text = $dom->createTextNode($data['uri']);
209                 $uri->appendChild($text);
210             }
211         }
212     }
213
214     /**
215      * Set entry enclosure
216      *
217      * @param  DOMDocument $dom
218      * @param  DOMElement $root
219      * @return void
220      */
221     // @codingStandardsIgnoreStart
222     protected function _setEnclosure(DOMDocument $dom, DOMElement $root)
223     {
224         // @codingStandardsIgnoreEnd
225         $data = $this->container->getEnclosure();
226         if ((! $data || empty($data))) {
227             return;
228         }
229         $enclosure = $this->dom->createElement('link');
230         $enclosure->setAttribute('rel', 'enclosure');
231         if (isset($data['type'])) {
232             $enclosure->setAttribute('type', $data['type']);
233         }
234         if (isset($data['length'])) {
235             $enclosure->setAttribute('length', $data['length']);
236         }
237         $enclosure->setAttribute('href', $data['uri']);
238         $root->appendChild($enclosure);
239     }
240
241     // @codingStandardsIgnoreStart
242     protected function _setLink(DOMDocument $dom, DOMElement $root)
243     {
244         // @codingStandardsIgnoreEnd
245         if (! $this->getDataContainer()->getLink()) {
246             return;
247         }
248         $link = $dom->createElement('link');
249         $root->appendChild($link);
250         $link->setAttribute('rel', 'alternate');
251         $link->setAttribute('type', 'text/html');
252         $link->setAttribute('href', $this->getDataContainer()->getLink());
253     }
254
255     /**
256      * Set entry identifier
257      *
258      * @param  DOMDocument $dom
259      * @param  DOMElement $root
260      * @return void
261      * @throws Writer\Exception\InvalidArgumentException
262      */
263     // @codingStandardsIgnoreStart
264     protected function _setId(DOMDocument $dom, DOMElement $root)
265     {
266         // @codingStandardsIgnoreEnd
267         if (! $this->getDataContainer()->getId()
268         && ! $this->getDataContainer()->getLink()) {
269             $message = 'Atom 1.0 entry elements MUST contain exactly one '
270             . 'atom:id element, or as an alternative, we can use the same '
271             . 'value as atom:link however neither a suitable link nor an '
272             . 'id have been set';
273             $exception = new Writer\Exception\InvalidArgumentException($message);
274             if (! $this->ignoreExceptions) {
275                 throw $exception;
276             } else {
277                 $this->exceptions[] = $exception;
278                 return;
279             }
280         }
281
282         if (! $this->getDataContainer()->getId()) {
283             $this->getDataContainer()->setId(
284                 $this->getDataContainer()->getLink()
285             );
286         }
287         if (! Uri::factory($this->getDataContainer()->getId())->isValid()
288             && ! preg_match(
289                 "#^urn:[a-zA-Z0-9][a-zA-Z0-9\-]{1,31}:([a-zA-Z0-9\(\)\+\,\.\:\=\@\;\$\_\!\*\-]|%[0-9a-fA-F]{2})*#",
290                 $this->getDataContainer()->getId()
291             )
292             && ! $this->_validateTagUri($this->getDataContainer()->getId())
293         ) {
294             throw new Writer\Exception\InvalidArgumentException('Atom 1.0 IDs must be a valid URI/IRI');
295         }
296         $id = $dom->createElement('id');
297         $root->appendChild($id);
298         $text = $dom->createTextNode($this->getDataContainer()->getId());
299         $id->appendChild($text);
300     }
301
302     /**
303      * Validate a URI using the tag scheme (RFC 4151)
304      *
305      * @param string $id
306      * @return bool
307      */
308     // @codingStandardsIgnoreStart
309     protected function _validateTagUri($id)
310     {
311         // @codingStandardsIgnoreEnd
312         if (preg_match(
313             '/^tag:(?P<name>.*),(?P<date>\d{4}-?\d{0,2}-?\d{0,2}):(?P<specific>.*)(.*:)*$/',
314             $id,
315             $matches
316         )) {
317             $dvalid = false;
318             $date = $matches['date'];
319             $d6 = strtotime($date);
320             if ((strlen($date) == 4) && $date <= date('Y')) {
321                 $dvalid = true;
322             } elseif ((strlen($date) == 7) && ($d6 < strtotime("now"))) {
323                 $dvalid = true;
324             } elseif ((strlen($date) == 10) && ($d6 < strtotime("now"))) {
325                 $dvalid = true;
326             }
327             $validator = new Validator\EmailAddress;
328             if ($validator->isValid($matches['name'])) {
329                 $nvalid = true;
330             } else {
331                 $nvalid = $validator->isValid('info@' . $matches['name']);
332             }
333             return $dvalid && $nvalid;
334         }
335         return false;
336     }
337
338     /**
339      * Set entry content
340      *
341      * @param  DOMDocument $dom
342      * @param  DOMElement $root
343      * @return void
344      * @throws Writer\Exception\InvalidArgumentException
345      */
346     // @codingStandardsIgnoreStart
347     protected function _setContent(DOMDocument $dom, DOMElement $root)
348     {
349         // @codingStandardsIgnoreEnd
350         $content = $this->getDataContainer()->getContent();
351         if (! $content && ! $this->getDataContainer()->getLink()) {
352             $message = 'Atom 1.0 entry elements MUST contain exactly one '
353             . 'atom:content element, or as an alternative, at least one link '
354             . 'with a rel attribute of "alternate" to indicate an alternate '
355             . 'method to consume the content.';
356             $exception = new Writer\Exception\InvalidArgumentException($message);
357             if (! $this->ignoreExceptions) {
358                 throw $exception;
359             } else {
360                 $this->exceptions[] = $exception;
361                 return;
362             }
363         }
364         if (! $content) {
365             return;
366         }
367         $element = $dom->createElement('content');
368         $element->setAttribute('type', 'xhtml');
369         $xhtmlElement = $this->_loadXhtml($content);
370         $deep = version_compare(PHP_VERSION, '7', 'ge') ? 1 : true;
371         $xhtml = $dom->importNode($xhtmlElement, $deep);
372         $element->appendChild($xhtml);
373         $root->appendChild($element);
374     }
375
376     /**
377      * Load a HTML string and attempt to normalise to XML
378      */
379     // @codingStandardsIgnoreStart
380     protected function _loadXhtml($content)
381     {
382         // @codingStandardsIgnoreEnd
383         if (class_exists('tidy', false)) {
384             $tidy = new \tidy;
385             $config = [
386                 'output-xhtml' => true,
387                 'show-body-only' => true,
388                 'quote-nbsp' => false
389             ];
390             $encoding = str_replace('-', '', $this->getEncoding());
391             $tidy->parseString($content, $config, $encoding);
392             $tidy->cleanRepair();
393             $xhtml = (string) $tidy;
394         } else {
395             $xhtml = $content;
396         }
397         $xhtml = preg_replace([
398             "/(<[\/]?)([a-zA-Z]+)/"
399         ], '$1xhtml:$2', $xhtml);
400         $dom = new DOMDocument('1.0', $this->getEncoding());
401         $dom->loadXML(
402             '<xhtml:div xmlns:xhtml="http://www.w3.org/1999/xhtml">'
403             . $xhtml
404             . '</xhtml:div>'
405         );
406         return $dom->documentElement;
407     }
408
409     /**
410      * Set entry categories
411      *
412      * @param  DOMDocument $dom
413      * @param  DOMElement $root
414      * @return void
415      */
416     // @codingStandardsIgnoreStart
417     protected function _setCategories(DOMDocument $dom, DOMElement $root)
418     {
419         // @codingStandardsIgnoreEnd
420         $categories = $this->getDataContainer()->getCategories();
421         if (! $categories) {
422             return;
423         }
424         foreach ($categories as $cat) {
425             $category = $dom->createElement('category');
426             $category->setAttribute('term', $cat['term']);
427             if (isset($cat['label'])) {
428                 $category->setAttribute('label', $cat['label']);
429             } else {
430                 $category->setAttribute('label', $cat['term']);
431             }
432             if (isset($cat['scheme'])) {
433                 $category->setAttribute('scheme', $cat['scheme']);
434             }
435             $root->appendChild($category);
436         }
437     }
438
439     /**
440      * Append Source element (Atom 1.0 Feed Metadata)
441      *
442      * @param  DOMDocument $dom
443      * @param  DOMElement $root
444      * @return void
445      */
446     // @codingStandardsIgnoreStart
447     protected function _setSource(DOMDocument $dom, DOMElement $root)
448     {
449         // @codingStandardsIgnoreEnd
450         $source = $this->getDataContainer()->getSource();
451         if (! $source) {
452             return;
453         }
454         $renderer = new Renderer\Feed\AtomSource($source);
455         $renderer->setType($this->getType());
456         $element = $renderer->render()->getElement();
457         $imported = $dom->importNode($element, true);
458         $root->appendChild($imported);
459     }
460 }