├── docker-compose.development.yml
├── .gitignore
├── Makefile
├── .travis.yml
├── tests
├── Bootstrap.php
└── Suin
│ └── RSSWriter
│ ├── FeedTest.php
│ ├── ChannelTest.php
│ └── ItemTest.php
├── docker-compose.yml
├── src
└── Suin
│ └── RSSWriter
│ ├── FeedInterface.php
│ ├── SimpleXMLElement.php
│ ├── Feed.php
│ ├── ItemInterface.php
│ ├── ChannelInterface.php
│ ├── Item.php
│ └── Channel.php
├── composer.json
├── phpunit.xml.dist
├── examples
└── simple-feed.php
└── README.md
/docker-compose.development.yml:
--------------------------------------------------------------------------------
1 | docker-compose.yml
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /.idea
2 | /vendor
3 | /composer.lock
4 | /tests/cover
5 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | test:
2 | docker-compose up php54
3 | docker-compose up php55
4 | docker-compose up php56
5 | docker-compose up php70
6 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | sudo: false
2 |
3 | language: php
4 |
5 | php:
6 | - 5.4
7 | - 5.5
8 | - 5.6
9 | - 7.0
10 | - master
11 | - hhvm
12 |
13 | matrix:
14 | fast_finish: true
15 | allow_failures:
16 | - php: master
17 |
18 | install:
19 | - composer install --prefer-dist
20 |
21 | script:
22 | - vendor/bin/phpunit --coverage-text
23 |
24 | cache:
25 | directories:
26 | - $HOME/.composer/cache
27 |
--------------------------------------------------------------------------------
/tests/Bootstrap.php:
--------------------------------------------------------------------------------
1 | =5.4.0"
22 | },
23 | "require-dev": {
24 | "phpunit/phpunit": ">=4.8.36 <6.0",
25 | "mockery/mockery": ">=0.9 <1.0",
26 | "suin/xoopsunit": ">=1.2"
27 | },
28 | "autoload": {
29 | "psr-0": {
30 | "Suin\\RSSWriter": "src"
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/Suin/RSSWriter/SimpleXMLElement.php:
--------------------------------------------------------------------------------
1 | addChild($name, null, $namespace);
35 | $dom = dom_import_simplexml($element);
36 | $elementOwner = $dom->ownerDocument;
37 | $dom->appendChild($elementOwner->createCDATASection($value));
38 | return $element;
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/phpunit.xml.dist:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
10 | tests
11 |
12 |
13 |
14 |
15 |
23 |
24 |
25 |
26 |
27 | src
28 |
29 | src
30 |
31 |
32 |
33 | vendor
34 |
35 |
36 |
37 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/src/Suin/RSSWriter/Feed.php:
--------------------------------------------------------------------------------
1 | channels[] = $channel;
24 | return $this;
25 | }
26 |
27 | /**
28 | * Render XML
29 | * @return string
30 | */
31 | public function render()
32 | {
33 | $xml = new SimpleXMLElement('', LIBXML_NOERROR | LIBXML_ERR_NONE | LIBXML_ERR_FATAL);
34 |
35 | foreach ($this->channels as $channel) {
36 | $toDom = dom_import_simplexml($xml);
37 | $fromDom = dom_import_simplexml($channel->asXML());
38 | $toDom->appendChild($toDom->ownerDocument->importNode($fromDom, true));
39 | }
40 |
41 | $dom = new DOMDocument('1.0', 'UTF-8');
42 | $dom->appendChild($dom->importNode(dom_import_simplexml($xml), true));
43 | $dom->formatOutput = true;
44 | return $dom->saveXML();
45 | }
46 |
47 | /**
48 | * Render XML
49 | * @return string
50 | */
51 | public function __toString()
52 | {
53 | return $this->render();
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/examples/simple-feed.php:
--------------------------------------------------------------------------------
1 | title('Channel Title')
18 | ->description('Channel Description')
19 | ->url('http://blog.example.com')
20 | ->feedUrl('http://blog.example.com/rss')
21 | ->language('en-US')
22 | ->copyright('Copyright 2012, Foo Bar')
23 | ->pubDate(strtotime('Tue, 21 Aug 2012 19:50:37 +0900'))
24 | ->lastBuildDate(strtotime('Tue, 21 Aug 2012 19:50:37 +0900'))
25 | ->ttl(60)
26 | ->pubsubhubbub('http://example.com/feed.xml', 'http://pubsubhubbub.appspot.com') // This is optional. Specify PubSubHubbub discovery if you want.
27 | ->appendTo($feed);
28 |
29 | // Blog item
30 | $item = new Item();
31 | $item
32 | ->title('Blog Entry Title')
33 | ->description('
Blog body
')
34 | ->contentEncoded('Blog body
')
35 | ->url('http://blog.example.com/2012/08/21/blog-entry/')
36 | ->author('john@smith.com')
37 | ->creator('John Smith')
38 | ->pubDate(strtotime('Tue, 21 Aug 2012 19:50:37 +0900'))
39 | ->guid('http://blog.example.com/2012/08/21/blog-entry/', true)
40 | ->preferCdata(true) // By this, title and description become CDATA wrapped HTML.
41 | ->appendTo($channel);
42 |
43 | // Podcast item
44 | $item = new Item();
45 | $item
46 | ->title('Some Podcast Entry')
47 | ->description('Podcast body
')
48 | ->url('http://podcast.example.com/2012/08/21/podcast-entry/')
49 | ->enclosure('http://podcast.example.com/2012/08/21/podcast.mp3', 4889, 'audio/mpeg')
50 | ->appendTo($channel);
51 |
52 | echo $feed; // or echo $feed->render();
53 |
--------------------------------------------------------------------------------
/src/Suin/RSSWriter/ItemInterface.php:
--------------------------------------------------------------------------------
1 | channelInterface);
14 | $feed = new Feed();
15 | $this->assertSame($feed, $feed->addChannel($channel));
16 | $this->assertAttributeSame([$channel], 'channels', $feed);
17 | }
18 |
19 | public function testRender()
20 | {
21 | $feed = new Feed();
22 | $xml1 = new SimpleXMLElement('channel1');
23 | $xml2 = new SimpleXMLElement('channel2');
24 | $xml3 = new SimpleXMLElement('channel3');
25 | $channel1 = $this->createMock($this->channelInterface);
26 | $channel1->expects($this->once())->method('asXML')->will($this->returnValue($xml1));
27 | $channel2 = $this->createMock($this->channelInterface);
28 | $channel2->expects($this->once())->method('asXML')->will($this->returnValue($xml2));
29 | $channel3 = $this->createMock($this->channelInterface);
30 | $channel3->expects($this->once())->method('asXML')->will($this->returnValue($xml3));
31 | $this->reveal($feed)->attr('channels', [$channel1, $channel2, $channel3]);
32 | $expect = '
33 |
34 | channel1
35 | channel2
36 | channel3
37 |
38 | ';
39 | $this->assertXmlStringEqualsXmlString($expect, $feed->render());
40 | }
41 |
42 | public function testRender_with_japanese()
43 | {
44 | $feed = new Feed();
45 | $xml1 = new SimpleXMLElement('日本語1');
46 | $xml2 = new SimpleXMLElement('日本語2');
47 | $xml3 = new SimpleXMLElement('日本語3');
48 | $channel1 = $this->createMock($this->channelInterface);
49 | $channel1->expects($this->once())->method('asXML')->will($this->returnValue($xml1));
50 | $channel2 = $this->createMock($this->channelInterface);
51 | $channel2->expects($this->once())->method('asXML')->will($this->returnValue($xml2));
52 | $channel3 = $this->createMock($this->channelInterface);
53 | $channel3->expects($this->once())->method('asXML')->will($this->returnValue($xml3));
54 | $this->reveal($feed)->attr('channels', [$channel1, $channel2, $channel3]);
55 | $expect = <<< 'XML'
56 |
57 |
58 |
59 | 日本語1
60 |
61 |
62 | 日本語2
63 |
64 |
65 | 日本語3
66 |
67 |
68 |
69 | XML;
70 | $this->assertSame($expect, $feed->render());
71 |
72 | }
73 |
74 | public function test__toString()
75 | {
76 | $feed = new Feed();
77 | $xml1 = new SimpleXMLElement('channel1');
78 | $xml2 = new SimpleXMLElement('channel2');
79 | $xml3 = new SimpleXMLElement('channel3');
80 | $channel1 = $this->createMock($this->channelInterface);
81 | $channel1->expects($this->once())->method('asXML')->will($this->returnValue($xml1));
82 | $channel2 = $this->createMock($this->channelInterface);
83 | $channel2->expects($this->once())->method('asXML')->will($this->returnValue($xml2));
84 | $channel3 = $this->createMock($this->channelInterface);
85 | $channel3->expects($this->once())->method('asXML')->will($this->returnValue($xml3));
86 | $this->reveal($feed)->attr('channels', [$channel1, $channel2, $channel3]);
87 | $expect = '
88 |
89 | channel1
90 | channel2
91 | channel3
92 |
93 | ';
94 | $this->assertXmlStringEqualsXmlString($expect, strval($feed));
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # \Suin\RSSWriter
2 |
3 | `\Suin\RSSWriter` is yet another simple RSS writer library for PHP 5.4 or later. This component is Licensed under MIT license.
4 |
5 | This library can also be used to publish Podcasts.
6 |
7 | [](https://packagist.org/packages/suin/php-rss-writer)
8 | [](https://packagist.org/packages/suin/php-rss-writer)
9 | [](https://packagist.org/packages/suin/php-rss-writer)
10 | [](https://packagist.org/packages/suin/php-rss-writer)
11 | [](https://travis-ci.org/suin/php-rss-writer)
12 | [](https://www.codacy.com/app/suinyeze/php-rss-writer)
13 |
14 | ## Quick demo
15 |
16 |
17 | ```php
18 | $feed = new Feed();
19 |
20 | $channel = new Channel();
21 | $channel
22 | ->title('Channel Title')
23 | ->description('Channel Description')
24 | ->url('http://blog.example.com')
25 | ->feedUrl('http://blog.example.com/rss')
26 | ->language('en-US')
27 | ->copyright('Copyright 2012, Foo Bar')
28 | ->pubDate(strtotime('Tue, 21 Aug 2012 19:50:37 +0900'))
29 | ->lastBuildDate(strtotime('Tue, 21 Aug 2012 19:50:37 +0900'))
30 | ->ttl(60)
31 | ->pubsubhubbub('http://example.com/feed.xml', 'http://pubsubhubbub.appspot.com') // This is optional. Specify PubSubHubbub discovery if you want.
32 | ->appendTo($feed);
33 |
34 | // Blog item
35 | $item = new Item();
36 | $item
37 | ->title('Blog Entry Title')
38 | ->description('Blog body
')
39 | ->contentEncoded('Blog body
')
40 | ->url('http://blog.example.com/2012/08/21/blog-entry/')
41 | ->author('John Smith')
42 | ->pubDate(strtotime('Tue, 21 Aug 2012 19:50:37 +0900'))
43 | ->guid('http://blog.example.com/2012/08/21/blog-entry/', true)
44 | ->preferCdata(true) // By this, title and description become CDATA wrapped HTML.
45 | ->appendTo($channel);
46 |
47 | // Podcast item
48 | $item = new Item();
49 | $item
50 | ->title('Some Podcast Entry')
51 | ->description('Podcast body
')
52 | ->url('http://podcast.example.com/2012/08/21/podcast-entry/')
53 | ->enclosure('http://podcast.example.com/2012/08/21/podcast.mp3', 4889, 'audio/mpeg')
54 | ->appendTo($channel);
55 |
56 | echo $feed; // or echo $feed->render();
57 | ```
58 |
59 | Output:
60 |
61 | ```xml
62 |
63 |
64 |
65 | Channel Title
66 | http://blog.example.com
67 | Channel Description
68 | en-US
69 | Copyright 2012, Foo Bar
70 | Tue, 21 Aug 2012 10:50:37 +0000
71 | Tue, 21 Aug 2012 10:50:37 +0000
72 | 60
73 |
74 |
75 | -
76 |
77 | http://blog.example.com/2012/08/21/blog-entry/
78 | Blog body]]>
79 | Blog body]]>
80 | http://blog.example.com/2012/08/21/blog-entry/
81 | Tue, 21 Aug 2012 10:50:37 +0000
82 | John Smith
83 |
84 | -
85 | Some Podcast Entry
86 | http://podcast.example.com/2012/08/21/podcast-entry/
87 | <div>Podcast body</div>
88 |
89 |
90 |
91 |
92 | ```
93 |
94 | ## Installation
95 |
96 | ### Easy installation
97 |
98 | You can install directly via [Composer](https://getcomposer.org/):
99 |
100 | ```bash
101 | $ composer require suin/php-rss-writer
102 | ```
103 |
104 | ### Manual installation
105 |
106 | Add the following code to your `composer.json` file:
107 |
108 | ```json
109 | {
110 | "require": {
111 | "suin/php-rss-writer": ">=1.0"
112 | }
113 | }
114 | ```
115 |
116 | ...and run composer to install it:
117 |
118 | ```bash
119 | $ composer install
120 | ```
121 |
122 | Finally, include `vendor/autoload.php` in your product:
123 |
124 | ```php
125 | require_once 'vendor/autoload.php';
126 | ```
127 |
128 | ## How to use
129 |
130 | The [`examples`](examples) directory contains usage examples for RSSWriter.
131 |
132 | If you want to know APIs, please see [`FeedInterface`](src/Suin/RSSWriter/FeedInterface.php), [`ChannelInterface`](src/Suin/RSSWriter/ChannelInterface.php) and [`ItemInterface`](src/Suin/RSSWriter/ItemInterface.php).
133 |
134 | ## How to Test
135 |
136 | ```sh
137 | $ vendor/bin/phpunit
138 | ```
139 |
140 | ## Test through PHP 5.4 ~ PHP 7.0
141 |
142 | ```console
143 | $ docker-compose up
144 | ```
145 |
146 | ## License
147 |
148 | MIT license
149 |
--------------------------------------------------------------------------------
/src/Suin/RSSWriter/Item.php:
--------------------------------------------------------------------------------
1 | title = $title;
49 | return $this;
50 | }
51 |
52 | public function url($url)
53 | {
54 | $this->url = $url;
55 | return $this;
56 | }
57 |
58 | public function description($description)
59 | {
60 | $this->description = $description;
61 | return $this;
62 | }
63 |
64 | public function contentEncoded($content)
65 | {
66 | $this->contentEncoded = $content;
67 | return $this;
68 | }
69 |
70 | public function category($name, $domain = null)
71 | {
72 | $this->categories[] = [$name, $domain];
73 | return $this;
74 | }
75 |
76 | public function categories(array $categories)
77 | {
78 | foreach ($categories as $cat) {
79 | $domain = null;
80 | if (is_array($cat) && !empty($cat)) {
81 | $domain = isset($cat[1]) ? $cat[1] : null;
82 | $cat = $cat[0];
83 | }
84 | $this->category($cat, $domain);
85 | }
86 | return $this;
87 | }
88 |
89 | public function guid($guid, $isPermalink = false)
90 | {
91 | $this->guid = $guid;
92 | $this->isPermalink = $isPermalink;
93 | return $this;
94 | }
95 |
96 | public function pubDate($pubDate)
97 | {
98 | $this->pubDate = $pubDate;
99 | return $this;
100 | }
101 |
102 | public function enclosure($url, $length = 0, $type = 'audio/mpeg')
103 | {
104 | $this->enclosure = ['url' => $url, 'length' => $length, 'type' => $type];
105 | return $this;
106 | }
107 |
108 | public function author($author)
109 | {
110 | $this->author = $author;
111 | return $this;
112 | }
113 |
114 | public function creator($creator)
115 | {
116 | $this->creator = $creator;
117 | return $this;
118 | }
119 |
120 | public function preferCdata($preferCdata)
121 | {
122 | $this->preferCdata = (bool)$preferCdata;
123 | return $this;
124 | }
125 |
126 | public function appendTo(ChannelInterface $channel)
127 | {
128 | $channel->addItem($this);
129 | return $this;
130 | }
131 |
132 | public function asXML()
133 | {
134 | $xml = new SimpleXMLElement(' ', LIBXML_NOERROR | LIBXML_ERR_NONE | LIBXML_ERR_FATAL);
135 |
136 | if ($this->title) {
137 | if ($this->preferCdata) {
138 | $xml->addCdataChild('title', $this->title);
139 | } else {
140 | $xml->addChild('title', $this->title);
141 | }
142 | }
143 |
144 | if ($this->url) {
145 | $xml->addChild('link', $this->url);
146 | }
147 |
148 | // At least one of or must be present
149 | if ($this->description || ! $this->title) {
150 | if ($this->preferCdata) {
151 | $xml->addCdataChild('description', $this->description);
152 | } else {
153 | $xml->addChild('description', $this->description);
154 | }
155 | }
156 |
157 | if ($this->contentEncoded) {
158 | $xml->addCdataChild('xmlns:content:encoded', $this->contentEncoded);
159 | }
160 |
161 | foreach ($this->categories as $category) {
162 | $element = $xml->addChild('category', $category[0]);
163 |
164 | if (isset($category[1])) {
165 | $element->addAttribute('domain', $category[1]);
166 | }
167 | }
168 |
169 | if ($this->guid) {
170 | $guid = $xml->addChild('guid', $this->guid);
171 |
172 | if ($this->isPermalink === false) {
173 | $guid->addAttribute('isPermaLink', 'false');
174 | }
175 | }
176 |
177 | if ($this->pubDate !== null) {
178 | $xml->addChild('pubDate', date(DATE_RSS, $this->pubDate));
179 | }
180 |
181 | if (is_array($this->enclosure) && (count($this->enclosure) == 3)) {
182 | $element = $xml->addChild('enclosure');
183 | $element->addAttribute('url', $this->enclosure['url']);
184 | $element->addAttribute('type', $this->enclosure['type']);
185 |
186 | if ($this->enclosure['length']) {
187 | $element->addAttribute('length', $this->enclosure['length']);
188 | }
189 | }
190 |
191 | if (!empty($this->author)) {
192 | $xml->addChild('author', $this->author);
193 | }
194 |
195 | if (!empty($this->creator)) {
196 | $xml->addChild('dc:creator', $this->creator,"http://purl.org/dc/elements/1.1/");
197 | }
198 |
199 | return $xml;
200 | }
201 | }
202 |
--------------------------------------------------------------------------------
/src/Suin/RSSWriter/Channel.php:
--------------------------------------------------------------------------------
1 | title = $title;
52 | return $this;
53 | }
54 |
55 | /**
56 | * Set channel URL
57 | * @param string $url
58 | * @return $this
59 | */
60 | public function url($url)
61 | {
62 | $this->url = $url;
63 | return $this;
64 | }
65 |
66 | /**
67 | * Set URL of this feed
68 | * @param string $url
69 | * @return $this;
70 | */
71 | public function feedUrl($url)
72 | {
73 | $this->feedUrl = $url;
74 | return $this;
75 | }
76 |
77 | /**
78 | * Set channel description
79 | * @param string $description
80 | * @return $this
81 | */
82 | public function description($description)
83 | {
84 | $this->description = $description;
85 | return $this;
86 | }
87 |
88 | /**
89 | * Set ISO639 language code
90 | *
91 | * The language the channel is written in. This allows aggregators to group all
92 | * Italian language sites, for example, on a single page. A list of allowable
93 | * values for this element, as provided by Netscape, is here. You may also use
94 | * values defined by the W3C.
95 | *
96 | * @param string $language
97 | * @return $this
98 | */
99 | public function language($language)
100 | {
101 | $this->language = $language;
102 | return $this;
103 | }
104 |
105 | /**
106 | * Set channel copyright
107 | * @param string $copyright
108 | * @return $this
109 | */
110 | public function copyright($copyright)
111 | {
112 | $this->copyright = $copyright;
113 | return $this;
114 | }
115 |
116 | /**
117 | * Set channel published date
118 | * @param int $pubDate Unix timestamp
119 | * @return $this
120 | */
121 | public function pubDate($pubDate)
122 | {
123 | $this->pubDate = $pubDate;
124 | return $this;
125 | }
126 |
127 | /**
128 | * Set channel last build date
129 | * @param int $lastBuildDate Unix timestamp
130 | * @return $this
131 | */
132 | public function lastBuildDate($lastBuildDate)
133 | {
134 | $this->lastBuildDate = $lastBuildDate;
135 | return $this;
136 | }
137 |
138 | /**
139 | * Set channel ttl (minutes)
140 | * @param int $ttl
141 | * @return $this
142 | */
143 | public function ttl($ttl)
144 | {
145 | $this->ttl = $ttl;
146 | return $this;
147 | }
148 |
149 | /**
150 | * Enable PubSubHubbub discovery
151 | * @param string $feedUrl
152 | * @param string $hubUrl
153 | * @return $this
154 | */
155 | public function pubsubhubbub($feedUrl, $hubUrl)
156 | {
157 | $this->pubsubhubbub = [
158 | 'feedUrl' => $feedUrl,
159 | 'hubUrl' => $hubUrl,
160 | ];
161 | return $this;
162 | }
163 |
164 | /**
165 | * Add item object
166 | * @param ItemInterface $item
167 | * @return $this
168 | */
169 | public function addItem(ItemInterface $item)
170 | {
171 | $this->items[] = $item;
172 | return $this;
173 | }
174 |
175 | /**
176 | * Append to feed
177 | * @param FeedInterface $feed
178 | * @return $this
179 | */
180 | public function appendTo(FeedInterface $feed)
181 | {
182 | $feed->addChannel($this);
183 | return $this;
184 | }
185 |
186 | /**
187 | * Return XML object
188 | * @return SimpleXMLElement
189 | */
190 | public function asXML()
191 | {
192 | $xml = new SimpleXMLElement('', LIBXML_NOERROR | LIBXML_ERR_NONE | LIBXML_ERR_FATAL);
193 | $xml->addChild('title', $this->title);
194 | $xml->addChild('link', $this->url);
195 | $xml->addChild('description', $this->description);
196 |
197 | if($this->feedUrl !== null) {
198 | $link = $xml->addChild('atom:link', '', "http://www.w3.org/2005/Atom");
199 | $link->addAttribute('href',$this->feedUrl);
200 | $link->addAttribute('type','application/rss+xml');
201 | $link->addAttribute('rel','self');
202 | }
203 |
204 | if ($this->language !== null) {
205 | $xml->addChild('language', $this->language);
206 | }
207 |
208 | if ($this->copyright !== null) {
209 | $xml->addChild('copyright', $this->copyright);
210 | }
211 |
212 | if ($this->pubDate !== null) {
213 | $xml->addChild('pubDate', date(DATE_RSS, $this->pubDate));
214 | }
215 |
216 | if ($this->lastBuildDate !== null) {
217 | $xml->addChild('lastBuildDate', date(DATE_RSS, $this->lastBuildDate));
218 | }
219 |
220 | if ($this->ttl !== null) {
221 | $xml->addChild('ttl', $this->ttl);
222 | }
223 |
224 | if ($this->pubsubhubbub !== null) {
225 | $feedUrl = $xml->addChild('xmlns:atom:link');
226 | $feedUrl->addAttribute('rel', 'self');
227 | $feedUrl->addAttribute('href', $this->pubsubhubbub['feedUrl']);
228 | $feedUrl->addAttribute('type', 'application/rss+xml');
229 |
230 | $hubUrl = $xml->addChild('xmlns:atom:link');
231 | $hubUrl->addAttribute('rel', 'hub');
232 | $hubUrl->addAttribute('href', $this->pubsubhubbub['hubUrl']);
233 | }
234 |
235 | foreach ($this->items as $item) {
236 | $toDom = dom_import_simplexml($xml);
237 | $fromDom = dom_import_simplexml($item->asXML());
238 | $toDom->appendChild($toDom->ownerDocument->importNode($fromDom, true));
239 | }
240 |
241 | return $xml;
242 | }
243 | }
244 |
--------------------------------------------------------------------------------
/tests/Suin/RSSWriter/ChannelTest.php:
--------------------------------------------------------------------------------
1 | assertSame($channel, $channel->title($title));
15 | $this->assertAttributeSame($title, 'title', $channel);
16 | }
17 |
18 | public function testUrl()
19 | {
20 | $url = uniqid();
21 | $channel = new Channel();
22 | $this->assertSame($channel, $channel->url($url));
23 | $this->assertAttributeSame($url, 'url', $channel);
24 | }
25 |
26 | public function testFeedUrl()
27 | {
28 | $channel = new Channel();
29 | $this->assertSame($channel, $channel->feedUrl('http://example.com/feed.xml'));
30 | $feedUrlXml = '';
31 | $this->assertContains($feedUrlXml, $channel->asXML()->asXML());
32 | }
33 |
34 | public function testDescription()
35 | {
36 | $description = uniqid();
37 | $channel = new Channel();
38 | $this->assertSame($channel, $channel->description($description));
39 | $this->assertAttributeSame($description, 'description', $channel);
40 | }
41 |
42 | public function testLanguage()
43 | {
44 | $language = uniqid();
45 | $channel = new Channel();
46 | $this->assertSame($channel, $channel->language($language));
47 | $this->assertAttributeSame($language, 'language', $channel);
48 | }
49 |
50 | public function testCopyright()
51 | {
52 | $copyright = uniqid();
53 | $channel = new Channel();
54 | $this->assertSame($channel, $channel->copyright($copyright));
55 | $this->assertAttributeSame($copyright, 'copyright', $channel);
56 | }
57 |
58 | public function testPubDate()
59 | {
60 | $pubDate = mt_rand(0, 9999999);
61 | $channel = new Channel();
62 | $this->assertSame($channel, $channel->pubDate($pubDate));
63 | $this->assertAttributeSame($pubDate, 'pubDate', $channel);
64 | }
65 |
66 | public function testLastBuildDate()
67 | {
68 | $lastBuildDate = mt_rand(0, 9999999);
69 | $channel = new Channel();
70 | $this->assertSame($channel, $channel->lastBuildDate($lastBuildDate));
71 | $this->assertAttributeSame($lastBuildDate, 'lastBuildDate', $channel);
72 | }
73 |
74 | public function testTtl()
75 | {
76 | $ttl = mt_rand(0, 99999999);
77 | $channel = new Channel();
78 | $this->assertSame($channel, $channel->ttl($ttl));
79 | $this->assertAttributeSame($ttl, 'ttl', $channel);
80 | }
81 |
82 | public function testPubsubhubbub()
83 | {
84 | $channel = new Channel();
85 | $channel->pubsubhubbub('http://example.com/feed.xml', 'http://pubsubhubbub.appspot.com');
86 | $xml = $channel->asXML()->asXML();
87 | $this->assertContains('', $xml);
88 | $this->assertContains('', $xml);
89 | }
90 |
91 | public function testAddItem()
92 | {
93 | $item = $this->createMock($this->itemInterface);
94 | $channel = new Channel();
95 | $this->assertSame($channel, $channel->addItem($item));
96 | $this->assertAttributeSame([$item], 'items', $channel);
97 | }
98 |
99 | public function testAppendTo()
100 | {
101 | $channel = new Channel();
102 | $feed = $this->createMock($this->feedInterface);
103 | $feed->expects($this->once())->method('addChannel')->with($channel);
104 | $this->assertSame($channel, $channel->appendTo($feed));
105 | }
106 |
107 | /**
108 | * @param $expect
109 | * @param array $data
110 | * @dataProvider dataForAsXML
111 | */
112 | public function testAsXML($expect, array $data)
113 | {
114 | $data = (object)$data;
115 | $channel = new Channel();
116 |
117 | foreach ($data as $key => $value) {
118 | $this->reveal($channel)->attr($key, $value);
119 | }
120 |
121 | $this->assertXmlStringEqualsXmlString($expect, $channel->asXML()->asXML());
122 | }
123 |
124 | public static function dataForAsXML()
125 | {
126 | $now = time();
127 | $nowString = date(DATE_RSS, $now);
128 |
129 | return [
130 | [
131 | "
132 |
133 | GoUpstate.com News Headlines
134 | http://www.goupstate.com/
135 | The latest news from GoUpstate.com, a Spartanburg Herald-Journal Web site.
136 |
137 | ",
138 | [
139 | 'title' => "GoUpstate.com News Headlines",
140 | 'url' => 'http://www.goupstate.com/',
141 | 'description' => "The latest news from GoUpstate.com, a Spartanburg Herald-Journal Web site.",
142 | ]
143 | ],
144 | [
145 | "
146 |
147 | GoUpstate.com News Headlines
148 | http://www.goupstate.com/
149 | The latest news from GoUpstate.com, a Spartanburg Herald-Journal Web site.
150 | en-us
151 |
152 | ",
153 | [
154 | 'title' => "GoUpstate.com News Headlines",
155 | 'url' => 'http://www.goupstate.com/',
156 | 'description' => "The latest news from GoUpstate.com, a Spartanburg Herald-Journal Web site.",
157 | 'language' => 'en-us',
158 | ]
159 | ],
160 | [
161 | "
162 |
163 | GoUpstate.com News Headlines
164 | http://www.goupstate.com/
165 | The latest news from GoUpstate.com, a Spartanburg Herald-Journal Web site.
166 | {$nowString}
167 |
168 | ",
169 | [
170 | 'title' => "GoUpstate.com News Headlines",
171 | 'url' => 'http://www.goupstate.com/',
172 | 'description' => "The latest news from GoUpstate.com, a Spartanburg Herald-Journal Web site.",
173 | 'pubDate' => $now,
174 | ]
175 | ],
176 | [
177 | "
178 |
179 | GoUpstate.com News Headlines
180 | http://www.goupstate.com/
181 | The latest news from GoUpstate.com, a Spartanburg Herald-Journal Web site.
182 | {$nowString}
183 |
184 | ",
185 | [
186 | 'title' => "GoUpstate.com News Headlines",
187 | 'url' => 'http://www.goupstate.com/',
188 | 'description' => "The latest news from GoUpstate.com, a Spartanburg Herald-Journal Web site.",
189 | 'lastBuildDate' => $now,
190 | ]
191 | ],
192 | [
193 | "
194 |
195 | GoUpstate.com News Headlines
196 | http://www.goupstate.com/
197 | The latest news from GoUpstate.com, a Spartanburg Herald-Journal Web site.
198 | 60
199 |
200 | ",
201 | [
202 | 'title' => "GoUpstate.com News Headlines",
203 | 'url' => 'http://www.goupstate.com/',
204 | 'description' => "The latest news from GoUpstate.com, a Spartanburg Herald-Journal Web site.",
205 | 'ttl' => 60,
206 | ]
207 | ],
208 | [
209 | "
210 |
211 | GoUpstate.com News Headlines
212 | http://www.goupstate.com/
213 | The latest news from GoUpstate.com, a Spartanburg Herald-Journal Web site.
214 | Copyright 2002, Spartanburg Herald-Journal
215 |
216 | ",
217 | [
218 | 'title' => "GoUpstate.com News Headlines",
219 | 'url' => 'http://www.goupstate.com/',
220 | 'description' => "The latest news from GoUpstate.com, a Spartanburg Herald-Journal Web site.",
221 | 'copyright' => "Copyright 2002, Spartanburg Herald-Journal",
222 | ]
223 | ],
224 | ];
225 | }
226 |
227 | public function testAppendTo_with_items()
228 | {
229 | $channel = new Channel();
230 |
231 | $xml1 = new SimpleXMLElement('- item1
');
232 | $xml2 = new SimpleXMLElement('- item2
');
233 | $xml3 = new SimpleXMLElement('- item3
');
234 |
235 | $item1 = $this->createMock($this->itemInterface);
236 | $item1->expects($this->once())->method('asXML')->will($this->returnValue($xml1));
237 | $item2 = $this->createMock($this->itemInterface);
238 | $item2->expects($this->once())->method('asXML')->will($this->returnValue($xml2));
239 | $item3 = $this->createMock($this->itemInterface);
240 | $item3->expects($this->once())->method('asXML')->will($this->returnValue($xml3));
241 |
242 | $this->reveal($channel)
243 | ->attr('title', "GoUpstate.com News Headlines")
244 | ->attr('url', 'http://www.goupstate.com/')
245 | ->attr('description', "The latest news from GoUpstate.com, a Spartanburg Herald-Journal Web site.")
246 | ->attr('items', [$item1, $item2, $item3]);
247 |
248 | $expect = '
249 |
250 | GoUpstate.com News Headlines
251 | http://www.goupstate.com/
252 | The latest news from GoUpstate.com, a Spartanburg Herald-Journal Web site.
253 | -
254 | item1
255 |
256 | -
257 | item2
258 |
259 | -
260 | item3
261 |
262 |
263 | ';
264 |
265 | $this->assertXmlStringEqualsXmlString($expect, $channel->asXML()->asXML());
266 | }
267 | }
268 |
--------------------------------------------------------------------------------
/tests/Suin/RSSWriter/ItemTest.php:
--------------------------------------------------------------------------------
1 | assertSame($item, $item->title($title));
16 | $this->assertAttributeSame($title, 'title', $item);
17 | }
18 |
19 | public function testUrl()
20 | {
21 | $url = uniqid();
22 | $item = new Item();
23 | $this->assertSame($item, $item->url($url));
24 | $this->assertAttributeSame($url, 'url', $item);
25 | }
26 |
27 | public function testDescription()
28 | {
29 | $description = uniqid();
30 | $item = new Item();
31 | $this->assertSame($item, $item->description($description));
32 | $this->assertAttributeSame($description, 'description', $item);
33 | }
34 |
35 | public function testContentEncoded()
36 | {
37 | $item = new Item();
38 | $this->assertSame($item, $item->contentEncoded('contents
'));
39 | $this->assertAttributeSame('contents
', 'contentEncoded', $item);
40 |
41 | $feed = new Feed();
42 | $channel = new Channel();
43 | $item->appendTo($channel);
44 | $channel->appendTo($feed);
45 |
46 | $expected = '
47 |
48 |
49 |
50 |
51 |
52 | -
53 |
54 | contents]]>
55 |
56 |
57 | ';
58 | $this->assertXmlStringEqualsXmlString($expected, $feed->render());
59 | }
60 |
61 | public function testCategory()
62 | {
63 | $category = uniqid();
64 | $item = new Item();
65 | $this->assertSame($item, $item->category($category));
66 | $this->assertAttributeSame([
67 | [$category, null],
68 | ], 'categories', $item);
69 | }
70 |
71 | public function testCategory_with_domain()
72 | {
73 | $category = uniqid();
74 | $domain = uniqid();
75 | $item = new Item();
76 | $this->assertSame($item, $item->category($category, $domain));
77 | $this->assertAttributeSame([
78 | [$category, $domain],
79 | ], 'categories', $item);
80 | }
81 |
82 | public function testCategories()
83 | {
84 | $categories = ['a', 'b', ['c', 'domain'], 'd', ['e']];
85 | $stored_categories = [
86 | ['a', null],
87 | ['b', null],
88 | ['c', 'domain'],
89 | ['d', null],
90 | ['e', null],
91 | ];
92 | $item = new Item();
93 | $item->categories($categories);
94 | $this->assertAttributeSame($stored_categories, 'categories', $item);
95 | }
96 |
97 | public function testGuid()
98 | {
99 | $guid = uniqid();
100 | $item = new Item();
101 | $this->assertSame($item, $item->guid($guid));
102 | $this->assertAttributeSame($guid, 'guid', $item);
103 | }
104 |
105 | public function testGuid_with_permalink()
106 | {
107 | $item = new Item();
108 | $item->guid('guid', true);
109 | $this->assertAttributeSame(true, 'isPermalink', $item);
110 |
111 | $item->guid('guid', false);
112 | $this->assertAttributeSame(false, 'isPermalink', $item);
113 |
114 | $item->guid('guid'); // default
115 | $this->assertAttributeSame(false, 'isPermalink', $item);
116 | }
117 |
118 | public function testPubDate()
119 | {
120 | $pubDate = mt_rand(1000000, 9999999);
121 | $item = new Item();
122 | $this->assertSame($item, $item->pubDate($pubDate));
123 | $this->assertAttributeSame($pubDate, 'pubDate', $item);
124 | }
125 |
126 | public function testAppendTo()
127 | {
128 | $item = new Item();
129 | $channel = $this->createMock($this->channelInterface);
130 | $channel->expects($this->once())->method('addItem')->with($item);
131 | $this->assertSame($item, $item->appendTo($channel));
132 | }
133 |
134 | public function testEnclosure()
135 | {
136 | $url = uniqid();
137 | $enclosure = ['url' => $url, 'length' => 0, 'type' => 'audio/mpeg'];
138 | $item = new Item();
139 | $this->assertSame($item, $item->enclosure($url));
140 | $this->assertAttributeSame($enclosure, 'enclosure', $item);
141 | }
142 |
143 | public function testAuthor()
144 | {
145 | $author = uniqid();
146 | $item = new Item();
147 | $this->assertSame($item, $item->author($author));
148 | $this->assertAttributeSame($author, 'author', $item);
149 | }
150 |
151 | public function testCreator()
152 | {
153 | $creator = uniqid();
154 | $item = new Item();
155 | $this->assertSame($item, $item->creator($creator));
156 |
157 | $creatorXml = '' . $creator . '';
158 | $this->assertContains($creatorXml, $item->asXML()->asXML());
159 | }
160 |
161 | public function testPreferCdata()
162 | {
163 | $item = new Item();
164 | $item->title('title
');
165 | $item->description('description
');
166 |
167 | // By default, prefer no CDATA on title and description
168 | $actualXml = $item->asXML()->asXML();
169 | $this->assertContains('<h1>title</h1>', $actualXml);
170 | $this->assertContains('<p>description</p>', $actualXml);
171 |
172 | // Once prefer-cdata is enabled, title and description is wrapped by CDATA
173 | $item->preferCdata(true);
174 | $actualXml = $item->asXML()->asXML();
175 | $this->assertContains('title]]>', $actualXml);
176 | $this->assertContains('description]]>', $actualXml);
177 |
178 | // Of course, prefer-cdata can be disabled again
179 | $item->preferCdata(false);
180 | $actualXml = $item->asXML()->asXML();
181 | $this->assertContains('<h1>title</h1>', $actualXml);
182 | $this->assertContains('<p>description</p>', $actualXml);
183 |
184 | // And like other APIs `preferCdata` is also fluent interface
185 | $obj = $item->preferCdata(true);
186 | $this->assertSame($obj, $item);
187 | }
188 |
189 | public function testAsXML()
190 | {
191 | $now = time();
192 | $nowString = date(DATE_RSS, $now);
193 |
194 | $data = [
195 | 'title' => "Venice Film Festival Tries to Quit Sinking",
196 | 'url' => 'http://nytimes.com/2004/12/07FEST.html',
197 | 'description' => "Some of the most heated chatter at the Venice Film Festival this week was about the way that the arrival of the stars at the Palazzo del Cinema was being staged.",
198 | 'categories' => [
199 | ["Grateful Dead", null],
200 | ["MSFT", 'http://www.fool.com/cusips'],
201 | ],
202 | 'guid' => "http://inessential.com/2002/09/01.php#a2",
203 | 'isPermalink' => true,
204 | 'pubDate' => $now,
205 | 'enclosure' => [
206 | 'url' => 'http://link-to-audio-file.com/test.mp3',
207 | 'length' => 4992,
208 | 'type' => 'audio/mpeg'
209 | ],
210 | 'author' => 'John Smith'
211 | ];
212 |
213 | $item = new Item();
214 |
215 | foreach ($data as $key => $value) {
216 | $this->reveal($item)->attr($key, $value);
217 | }
218 |
219 | $expect = "
220 | -
221 | {$data['title']}
222 | {$data['url']}
223 | {$data['description']}
224 | {$data['categories'][0][0]}
225 | {$data['categories'][1][0]}
226 | {$data['guid']}
227 | {$nowString}
228 |
229 | {$data['author']}
230 |
231 | ";
232 | $this->assertXmlStringEqualsXmlString($expect, $item->asXML()->asXML());
233 | }
234 |
235 | public function testAsXML_false_permalink()
236 | {
237 | $now = time();
238 | $nowString = date(DATE_RSS, $now);
239 |
240 | $data = [
241 | 'title' => "Venice Film Festival Tries to Quit Sinking",
242 | 'url' => 'http://nytimes.com/2004/12/07FEST.html',
243 | 'description' => "Some of the most heated chatter at the Venice Film Festival this week was about the way that the arrival of the stars at the Palazzo del Cinema was being staged.",
244 | 'categories' => [
245 | ["Grateful Dead", null],
246 | ["MSFT", 'http://www.fool.com/cusips'],
247 | ],
248 | 'guid' => "http://inessential.com/2002/09/01.php#a2",
249 | 'isPermalink' => false,
250 | 'pubDate' => $now,
251 | 'enclosure' => [
252 | 'url' => 'http://link-to-audio-file.com/test.mp3',
253 | 'length' => 4992,
254 | 'type' => 'audio/mpeg'
255 | ],
256 | 'author' => 'John Smith'
257 | ];
258 |
259 | $item = new Item();
260 |
261 | foreach ($data as $key => $value) {
262 | $this->reveal($item)->attr($key, $value);
263 | }
264 |
265 | $expect = "
266 | -
267 | {$data['title']}
268 | {$data['url']}
269 | {$data['description']}
270 | {$data['categories'][0][0]}
271 | {$data['categories'][1][0]}
272 | {$data['guid']}
273 | {$nowString}
274 |
275 | {$data['author']}
276 |
277 | ";
278 | $this->assertXmlStringEqualsXmlString($expect, $item->asXML()->asXML());
279 | }
280 |
281 | public function testAsXML_test_Japanese()
282 | {
283 | $data = [
284 | 'title' => "Venice Film Festival",
285 | 'url' => 'http://nytimes.com/2004/12/07FEST.html',
286 | 'description' => "Some of the most heated chatter at the Venice Film Festival this week was about the way that the arrival of the stars at the Palazzo del Cinema was being staged.",
287 | ];
288 |
289 | $item = new Item();
290 |
291 | foreach ($data as $key => $value) {
292 | $this->reveal($item)->attr($key, $value);
293 | }
294 |
295 | $expect = "
296 | -
297 | {$data['title']}
298 | {$data['url']}
299 | {$data['description']}
300 |
301 | ";
302 |
303 | $this->assertXmlStringEqualsXmlString($expect, $item->asXML()->asXML());
304 | }
305 |
306 | public function test_with_amp()
307 | {
308 | $item = new Item();
309 | $item
310 | ->title('test&test')
311 | ->url('url&url')
312 | ->description('desc&desc');
313 | $expect = '
314 | - test&testurl&urldesc&desc
315 | ';
316 |
317 | $this->assertSame($expect, $item->asXML()->asXML());
318 | }
319 |
320 | public function test_fail_safe_against_invalid_string()
321 | {
322 | $item = new Item();
323 | $item
324 | ->title("test\0test")
325 | ->url("url\0test")
326 | ->description("desc\0desc");
327 | $expect = '
328 | - testurldesc
329 | ';
330 |
331 | $this->assertSame($expect, $item->asXML()->asXML());
332 | }
333 | }
334 |
--------------------------------------------------------------------------------