1<?php
2
3/**
4 * @see       https://github.com/laminas/laminas-feed for the canonical source repository
5 * @copyright https://github.com/laminas/laminas-feed/blob/master/COPYRIGHT.md
6 * @license   https://github.com/laminas/laminas-feed/blob/master/LICENSE.md New BSD License
7 */
8
9namespace Laminas\Feed\Writer\Renderer\Entry;
10
11use DateTime;
12use DOMDocument;
13use DOMElement;
14use Laminas\Feed\Uri;
15use Laminas\Feed\Writer;
16use Laminas\Feed\Writer\Renderer;
17
18class Rss extends Renderer\AbstractRenderer implements Renderer\RendererInterface
19{
20    public function __construct(Writer\Entry $container)
21    {
22        parent::__construct($container);
23    }
24
25    /**
26     * Render RSS entry
27     *
28     * @return $this
29     */
30    public function render()
31    {
32        $this->dom                     = new DOMDocument('1.0', $this->container->getEncoding());
33        $this->dom->formatOutput       = true;
34        $this->dom->substituteEntities = false;
35        $entry                         = $this->dom->createElement('item');
36        $this->dom->appendChild($entry);
37
38        $this->_setTitle($this->dom, $entry);
39        $this->_setDescription($this->dom, $entry);
40        $this->_setDateCreated($this->dom, $entry);
41        $this->_setDateModified($this->dom, $entry);
42        $this->_setLink($this->dom, $entry);
43        $this->_setId($this->dom, $entry);
44        $this->_setAuthors($this->dom, $entry);
45        $this->_setEnclosure($this->dom, $entry);
46        $this->_setCommentLink($this->dom, $entry);
47        $this->_setCategories($this->dom, $entry);
48        foreach ($this->extensions as $ext) {
49            $ext->setType($this->getType());
50            $ext->setRootElement($this->getRootElement());
51            $ext->setDomDocument($this->getDomDocument(), $entry);
52            $ext->render();
53        }
54
55        return $this;
56    }
57
58    /**
59     * Set entry title
60     *
61     * @param  DOMDocument $dom
62     * @param  DOMElement $root
63     * @return void
64     * @throws Writer\Exception\InvalidArgumentException
65     */
66    // @codingStandardsIgnoreStart
67    protected function _setTitle(DOMDocument $dom, DOMElement $root)
68    {
69        // @codingStandardsIgnoreEnd
70        if (! $this->getDataContainer()->getDescription()
71            && ! $this->getDataContainer()->getTitle()
72        ) {
73            $message   = 'RSS 2.0 entry elements SHOULD contain exactly one'
74                . ' title element but a title has not been set. In addition, there'
75                . ' is no description as required in the absence of a title.';
76            $exception = new Writer\Exception\InvalidArgumentException($message);
77            if (! $this->ignoreExceptions) {
78                throw $exception;
79            } else {
80                $this->exceptions[] = $exception;
81                return;
82            }
83        }
84        $title = $dom->createElement('title');
85        $root->appendChild($title);
86        $text = $dom->createTextNode($this->getDataContainer()->getTitle());
87        $title->appendChild($text);
88    }
89
90    /**
91     * Set entry description
92     *
93     * @param  DOMDocument $dom
94     * @param  DOMElement $root
95     * @return void
96     * @throws Writer\Exception\InvalidArgumentException
97     */
98    // @codingStandardsIgnoreStart
99    protected function _setDescription(DOMDocument $dom, DOMElement $root)
100    {
101        // @codingStandardsIgnoreEnd
102        if (! $this->getDataContainer()->getDescription()
103            && ! $this->getDataContainer()->getTitle()
104        ) {
105            $message   = 'RSS 2.0 entry elements SHOULD contain exactly one'
106                . ' description element but a description has not been set. In'
107                . ' addition, there is no title element as required in the absence'
108                . ' of a description.';
109            $exception = new Writer\Exception\InvalidArgumentException($message);
110            if (! $this->ignoreExceptions) {
111                throw $exception;
112            } else {
113                $this->exceptions[] = $exception;
114                return;
115            }
116        }
117        if (! $this->getDataContainer()->getDescription()) {
118            return;
119        }
120        $subtitle = $dom->createElement('description');
121        $root->appendChild($subtitle);
122        $text = $dom->createCDATASection($this->getDataContainer()->getDescription());
123        $subtitle->appendChild($text);
124    }
125
126    /**
127     * Set date entry was last modified
128     *
129     * @param  DOMDocument $dom
130     * @param  DOMElement $root
131     * @return void
132     */
133    // @codingStandardsIgnoreStart
134    protected function _setDateModified(DOMDocument $dom, DOMElement $root)
135    {
136        // @codingStandardsIgnoreEnd
137        if (! $this->getDataContainer()->getDateModified()) {
138            return;
139        }
140
141        $updated = $dom->createElement('pubDate');
142        $root->appendChild($updated);
143        $text = $dom->createTextNode(
144            $this->getDataContainer()->getDateModified()->format(DateTime::RSS)
145        );
146        $updated->appendChild($text);
147    }
148
149    /**
150     * Set date entry was created
151     *
152     * @param  DOMDocument $dom
153     * @param  DOMElement $root
154     * @return void
155     */
156    // @codingStandardsIgnoreStart
157    protected function _setDateCreated(DOMDocument $dom, DOMElement $root)
158    {
159        // @codingStandardsIgnoreEnd
160        if (! $this->getDataContainer()->getDateCreated()) {
161            return;
162        }
163        if (! $this->getDataContainer()->getDateModified()) {
164            $this->getDataContainer()->setDateModified(
165                $this->getDataContainer()->getDateCreated()
166            );
167        }
168    }
169
170    /**
171     * Set entry authors
172     *
173     * @param  DOMDocument $dom
174     * @param  DOMElement $root
175     * @return void
176     */
177    // @codingStandardsIgnoreStart
178    protected function _setAuthors(DOMDocument $dom, DOMElement $root)
179    {
180        // @codingStandardsIgnoreEnd
181        $authors = $this->container->getAuthors();
182        if (! $authors || empty($authors)) {
183            return;
184        }
185        foreach ($authors as $data) {
186            $author = $this->dom->createElement('author');
187            $name   = $data['name'];
188            if (array_key_exists('email', $data)) {
189                $name = $data['email'] . ' (' . $data['name'] . ')';
190            }
191            $text = $dom->createTextNode($name);
192            $author->appendChild($text);
193            $root->appendChild($author);
194        }
195    }
196
197    /**
198     * Set entry enclosure
199     *
200     * @param  DOMDocument $dom
201     * @param  DOMElement $root
202     * @return void
203     * @throws Writer\Exception\InvalidArgumentException
204     */
205    // @codingStandardsIgnoreStart
206    protected function _setEnclosure(DOMDocument $dom, DOMElement $root)
207    {
208        // @codingStandardsIgnoreEnd
209        $data = $this->container->getEnclosure();
210        if (! $data || empty($data)) {
211            return;
212        }
213        if (! isset($data['type'])) {
214            $exception = new Writer\Exception\InvalidArgumentException('Enclosure "type" is not set');
215            if (! $this->ignoreExceptions) {
216                throw $exception;
217            } else {
218                $this->exceptions[] = $exception;
219                return;
220            }
221        }
222        if (! isset($data['length'])) {
223            $exception = new Writer\Exception\InvalidArgumentException('Enclosure "length" is not set');
224            if (! $this->ignoreExceptions) {
225                throw $exception;
226            } else {
227                $this->exceptions[] = $exception;
228                return;
229            }
230        }
231        if ((int) $data['length'] < 0 || ! ctype_digit((string) $data['length'])) {
232            $exception = new Writer\Exception\InvalidArgumentException(
233                'Enclosure "length" must be an integer indicating the content\'s length in bytes'
234            );
235            if (! $this->ignoreExceptions) {
236                throw $exception;
237            }
238
239            $this->exceptions[] = $exception;
240            return;
241        }
242        $enclosure = $this->dom->createElement('enclosure');
243        $enclosure->setAttribute('type', $data['type']);
244        $enclosure->setAttribute('length', $data['length']);
245        $enclosure->setAttribute('url', $data['uri']);
246        $root->appendChild($enclosure);
247    }
248
249    /**
250     * Set link to entry
251     *
252     * @param  DOMDocument $dom
253     * @param  DOMElement $root
254     * @return void
255     */
256    // @codingStandardsIgnoreStart
257    protected function _setLink(DOMDocument $dom, DOMElement $root)
258    {
259        // @codingStandardsIgnoreEnd
260        if (! $this->getDataContainer()->getLink()) {
261            return;
262        }
263        $link = $dom->createElement('link');
264        $root->appendChild($link);
265        $text = $dom->createTextNode($this->getDataContainer()->getLink());
266        $link->appendChild($text);
267    }
268
269    /**
270     * Set entry identifier
271     *
272     * @param  DOMDocument $dom
273     * @param  DOMElement $root
274     * @return void
275     */
276    // @codingStandardsIgnoreStart
277    protected function _setId(DOMDocument $dom, DOMElement $root)
278    {
279        // @codingStandardsIgnoreEnd
280        if (! $this->getDataContainer()->getId()
281            && ! $this->getDataContainer()->getLink()
282        ) {
283            return;
284        }
285
286        $id = $dom->createElement('guid');
287        $root->appendChild($id);
288        if (! $this->getDataContainer()->getId()) {
289            $this->getDataContainer()->setId(
290                $this->getDataContainer()->getLink()
291            );
292        }
293        $text = $dom->createTextNode($this->getDataContainer()->getId());
294        $id->appendChild($text);
295
296        $uri = Uri::factory($this->getDataContainer()->getId());
297        if (! $uri->isValid() || ! $uri->isAbsolute()) {
298            /** @see http://www.rssboard.org/rss-profile#element-channel-item-guid */
299            $id->setAttribute('isPermaLink', 'false');
300        }
301    }
302
303    /**
304     * Set link to entry comments
305     *
306     * @param  DOMDocument $dom
307     * @param  DOMElement $root
308     * @return void
309     */
310    // @codingStandardsIgnoreStart
311    protected function _setCommentLink(DOMDocument $dom, DOMElement $root)
312    {
313        // @codingStandardsIgnoreEnd
314        $link = $this->getDataContainer()->getCommentLink();
315        if (! $link) {
316            return;
317        }
318        $clink = $this->dom->createElement('comments');
319        $text  = $dom->createTextNode($link);
320        $clink->appendChild($text);
321        $root->appendChild($clink);
322    }
323
324    /**
325     * Set entry categories
326     *
327     * @param DOMDocument $dom
328     * @param DOMElement $root
329     * @return void
330     */
331    // @codingStandardsIgnoreStart
332    protected function _setCategories(DOMDocument $dom, DOMElement $root)
333    {
334        // @codingStandardsIgnoreEnd
335        $categories = $this->getDataContainer()->getCategories();
336        if (! $categories) {
337            return;
338        }
339        foreach ($categories as $cat) {
340            $category = $dom->createElement('category');
341            if (isset($cat['scheme'])) {
342                $category->setAttribute('domain', $cat['scheme']);
343            }
344            $text = $dom->createCDATASection($cat['term']);
345            $category->appendChild($text);
346            $root->appendChild($category);
347        }
348    }
349}
350