├── .bootstrap.php
├── .github
└── workflows
│ └── ci.yml
├── .gitignore
├── .php-cs-fixer.dist.php
├── LICENSE
├── README.md
├── composer.json
├── doc
├── index.md
├── read-feed.md
└── render-feed.md
├── phpunit.xml.dist
├── src
├── Attachment.php
├── Author.php
├── Exceptions
│ ├── InvalidFeedException.php
│ └── RuntimeException.php
├── Feed.php
├── Hub.php
├── Item.php
├── Reader
│ ├── Reader.php
│ ├── ReaderBuilder.php
│ ├── ReaderInterface.php
│ └── Version1
│ │ └── FeedReader.php
├── Versions.php
└── Writer
│ ├── RendererFactory.php
│ ├── RendererInterface.php
│ └── Version1
│ └── Renderer.php
└── test
├── AttachmentTest.php
├── AuthorTest.php
├── FeedTest.php
├── Fixtures
├── authors.json
├── extension.json
├── microblog.json
├── podcast.json
└── simple.json
├── HubTest.php
├── ItemTest.php
├── Reader
├── ReaderBuilderTest.php
├── ReaderTest.php
└── Version1
│ └── FeedReaderTest.php
└── Writer
├── RendererFactoryTest.php
└── Version1
└── RendererTest.php
/.bootstrap.php:
--------------------------------------------------------------------------------
1 | in(__DIR__)
5 | ->exclude('vendor')
6 | ;
7 |
8 | return (new PhpCsFixer\Config())
9 | ->setRiskyAllowed(true)
10 | ->setRules([
11 | '@PSR2' => true,
12 | 'array_syntax' => ['syntax' => 'short'],
13 | 'blank_line_after_opening_tag' => true,
14 | 'declare_strict_types' => true,
15 | 'function_declaration' => ['closure_function_spacing' => 'none'],
16 | 'single_import_per_statement' => false,
17 | 'strict_comparison' => true,
18 | 'strict_param' => true,
19 | ])
20 | ->setFinder($finder)
21 | ;
22 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016 Jérémy DECOOL
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | JSONFeed
2 | ========
3 |
4 | [](https://travis-ci.org/jdecool/jsonfeed?branch=master)
5 | [](https://scrutinizer-ci.com/g/jdecool/jsonfeed/?branch=master)
6 | [](https://packagist.org/packages/jdecool/jsonfeed)
7 |
8 | [JSONFeed](https://jsonfeed.org) is a pragmatic syndication format, like RSS and Atom, but with one big difference:
9 | it’s JSON instead of XML.
10 |
11 | This library provides functionnalits for mananaging feed through your PHP code. It provides a natural syntax for accessing
12 | elements of feed.
13 |
14 | To learn how to use the library, [read the documentation](doc/index.md)
15 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "jdecool/jsonfeed",
3 | "description": "PHP JSONFeed library",
4 | "type": "library",
5 | "keywords": ["feed", "json", "jsonfeed"],
6 | "homepage": "http://jsonfeed.org",
7 | "license": "MIT",
8 | "authors": [
9 | {
10 | "name": "Jérémy DECOOL",
11 | "email": "contact@jdecool.fr"
12 | }
13 | ],
14 | "require": {
15 | "php": ">=7.3.0",
16 | "symfony/property-access": "^4.4|^5.0|^6.0"
17 | },
18 | "require-dev": {
19 | "phpunit/phpunit": "^9.5",
20 | "friendsofphp/php-cs-fixer": "^3.0"
21 | },
22 | "autoload": {
23 | "psr-4": {
24 | "JDecool\\JsonFeed\\": "src"
25 | }
26 | },
27 | "autoload-dev": {
28 | "psr-4": {
29 | "JDecool\\Test\\JsonFeed\\": "test"
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/doc/index.md:
--------------------------------------------------------------------------------
1 | Getting started with JSONFeed
2 | =============================
3 |
4 | ## Installation
5 |
6 | To use this library, you need to install it with [composer](https://getcomposer.org):
7 |
8 | ```bash
9 | composer require jdecool/jsonfeed
10 | ```
11 |
12 | ## Usage
13 |
14 | * [Render a feed](render-feed.md)
15 | * [Read a feed](read-feed.md)
16 |
--------------------------------------------------------------------------------
/doc/read-feed.md:
--------------------------------------------------------------------------------
1 | Read a feed
2 | ===========
3 |
4 | Read a feed is very simple. You just need to create a `Reader` which will parse
5 | the JSONFeed data and will return a `Feed` object.
6 |
7 | ```php
8 | $json = file_get_content('http://foo/bar');
9 |
10 | $builder = JDecool\JsonFeed\Reader\ReaderBuilder();
11 | $reader = $builder->build();
12 |
13 | $feed = $reader->createFromJson($json); // Will be a JDecool\JsonFeed\Feed object
14 | ```
15 |
16 | The `ReaderBuilder::build` method access a boolean as parameter to define if the
17 | parse will through an exception if an error is detected (default value is `true`).
18 |
--------------------------------------------------------------------------------
/doc/render-feed.md:
--------------------------------------------------------------------------------
1 | Render a feed
2 | =============
3 |
4 | To render JSONFeed, you need to create and fill a `Feed` object.
5 |
6 | ```php
7 | use DateTime;
8 | use DateTimeZone;
9 | use JDecool\JsonFeed\Author;
10 | use JDecool\JsonFeed\Feed;
11 | use JDecool\JsonFeed\Item;
12 |
13 | $author = new Author('Brent Simmons');
14 | $author->setUrl('http://example.org/');
15 | $author->setAvatar('https://example.org/avatar.png');
16 |
17 | $item = new Item('2347259');
18 | $item->setUrl('https://example.org/2347259');
19 | $item->setDatePublished(new DateTime('2016-02-09 14:22:00', new DateTimeZone('+0200')));
20 | $item->setContentText('Cats are neat. https://example.org/cats');
21 | $item->addExtension('blue_shed', [
22 | 'about' => 'https://blueshed-podcasts.com/json-feed-extension-docs',
23 | 'explicit': false,
24 | 'copyright' => '1948 by George Orwell',
25 | 'owner' => 'Big Brother and the Holding Company',
26 | 'subtitle' => 'All shouting, all the time. Double. Plus. Good.'
27 | ]);
28 |
29 | $feed = new Feed('Brent Simmons’s Microblog');
30 | $feed->setUserComment('This is a microblog feed. You can add this to your feed reader using the following URL: https://example.org/feed.json');
31 | $feed->setHomepageUrl('https://example.org/');
32 | $feed->setFeedUrl('https://example.org/feed.json');
33 | $feed->setAuthor($author);
34 | $feed->addItem($item);
35 | ```
36 |
37 | After that, you have to choose which renderer will render your feed:
38 |
39 | ```php
40 | use JDecool\JsonFeed\Writer\RendererFactory;
41 | use JDecool\JsonFeed\Versions;
42 |
43 | $factory = new RendererFactory();
44 | $renderer = $factory->createRenderer(Versions::VERSION_1);
45 | ```
46 |
47 | Finaly render your JSONFeed data:
48 |
49 | ```php
50 | header('Content-Type: application/json; charset=UTF-8');
51 | echo $renderer->render($feed);
52 | ```
53 |
--------------------------------------------------------------------------------
/phpunit.xml.dist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
12 |
13 |
14 |
15 |
16 | test
17 |
18 |
19 |
20 |
21 |
22 | ./src/
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/src/Attachment.php:
--------------------------------------------------------------------------------
1 | url = $url;
36 | $this->mimeType = $mimeType;
37 | }
38 |
39 | /**
40 | * Get the location of the attachment
41 | *
42 | * @return string
43 | */
44 | public function getUrl()
45 | {
46 | return $this->url;
47 | }
48 |
49 | /**
50 | * Get the type of the attachment
51 | *
52 | * @return string
53 | */
54 | public function getMimeType()
55 | {
56 | return $this->mimeType;
57 | }
58 |
59 | /**
60 | * Get the name for the attachment
61 | *
62 | * @return string
63 | */
64 | public function getTitle()
65 | {
66 | return $this->title;
67 | }
68 |
69 | /**
70 | * Set the name of the attachment
71 | *
72 | * @param string $title
73 | * @return Attachment
74 | */
75 | public function setTitle($title)
76 | {
77 | $this->title = $title;
78 |
79 | return $this;
80 | }
81 |
82 | /**
83 | * Get the size of content
84 | *
85 | * @return float|int
86 | */
87 | public function getSize()
88 | {
89 | return $this->size;
90 | }
91 |
92 | /**
93 | * Set the size of content
94 | *
95 | * @param float|int $size
96 | * @return Attachment
97 | */
98 | public function setSize($size)
99 | {
100 | $this->size = $size;
101 |
102 | return $this;
103 | }
104 |
105 | /**
106 | * Get the duration of the attachment
107 | *
108 | * @return float|int
109 | */
110 | public function getDuration()
111 | {
112 | return $this->duration;
113 | }
114 |
115 | /**
116 | * Specifies how long the attachment takes to listen to or watch
117 | *
118 | * @param float|int $duration
119 | * @return Attachment
120 | */
121 | public function setDuration($duration)
122 | {
123 | $this->duration = $duration;
124 |
125 | return $this;
126 | }
127 | }
128 |
--------------------------------------------------------------------------------
/src/Author.php:
--------------------------------------------------------------------------------
1 | name = $name;
29 | }
30 |
31 | /**
32 | * Get the author’s name
33 | *
34 | * @return string
35 | */
36 | public function getName()
37 | {
38 | return $this->name;
39 | }
40 |
41 | /**
42 | * Get the URL of a site owned by the author
43 | *
44 | * @return string
45 | */
46 | public function getUrl()
47 | {
48 | return $this->url;
49 | }
50 |
51 | /**
52 | * Set the URL of a site owned by the author
53 | *
54 | * @param string $url
55 | * @return Author
56 | */
57 | public function setUrl($url)
58 | {
59 | $this->url = $url;
60 |
61 | return $this;
62 | }
63 |
64 | /**
65 | * Get the URL for an image for the author
66 | *
67 | * @return string
68 | */
69 | public function getAvatar()
70 | {
71 | return $this->avatar;
72 | }
73 |
74 | /**
75 | * Set the URL for an image for the author
76 | *
77 | * @param string $avatar
78 | * @return Author
79 | */
80 | public function setAvatar($avatar)
81 | {
82 | $this->avatar = $avatar;
83 |
84 | return $this;
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/src/Exceptions/InvalidFeedException.php:
--------------------------------------------------------------------------------
1 | title = $title;
57 |
58 | $this->hubs = [];
59 | $this->items = [];
60 | }
61 |
62 | /**
63 | * Get the name of the feed
64 | *
65 | * @return string
66 | */
67 | public function getTitle()
68 | {
69 | return $this->title;
70 | }
71 |
72 | /**
73 | * Set the name of the feed
74 | *
75 | * @param string $title
76 | * @return Feed
77 | */
78 | public function setTitle($title)
79 | {
80 | $this->title = $title;
81 |
82 | return $this;
83 | }
84 |
85 | /**
86 | * Get the URL of the resource that the feed describes
87 | *
88 | * @return string
89 | */
90 | public function getHomepageUrl()
91 | {
92 | return $this->homepageUrl;
93 | }
94 |
95 | /**
96 | * Set the URL of the resource that the feed describes
97 | *
98 | * @param string $homepageUrl
99 | * @return Feed
100 | */
101 | public function setHomepageUrl($homepageUrl)
102 | {
103 | $this->homepageUrl = $homepageUrl;
104 |
105 | return $this;
106 | }
107 |
108 | /**
109 | * Get the URL of the feed, and serves as the unique identifier for the feed
110 | *
111 | * @return string
112 | */
113 | public function getFeedUrl()
114 | {
115 | return $this->feedUrl;
116 | }
117 |
118 | /**
119 | * Set the URL of the feed, and serves as the unique identifier for the feed
120 | *
121 | * @param string $feedUrl
122 | * @return Feed
123 | */
124 | public function setFeedUrl($feedUrl)
125 | {
126 | $this->feedUrl = $feedUrl;
127 |
128 | return $this;
129 | }
130 |
131 | /**
132 | * Get more detail, beyond the `title`, on what the feed is about
133 | *
134 | * @return string
135 | */
136 | public function getDescription()
137 | {
138 | return $this->description;
139 | }
140 |
141 | /**
142 | * Set more detail, beyond the `title`, on what the feed is about
143 | *
144 | * @param string $description
145 | * @return Feed
146 | */
147 | public function setDescription($description)
148 | {
149 | $this->description = $description;
150 |
151 | return $this;
152 | }
153 |
154 | /**
155 | * Get a description of the purpose of the feed
156 | *
157 | * @return string
158 | */
159 | public function getUserComment()
160 | {
161 | return $this->userComment;
162 | }
163 |
164 | /**
165 | * Set a description of the purpose of the feed
166 | *
167 | * @param string $userComment
168 | * @return Feed
169 | */
170 | public function setUserComment($userComment)
171 | {
172 | $this->userComment = $userComment;
173 |
174 | return $this;
175 | }
176 |
177 | /**
178 | * Get the URL of a feed that provides the next n items, where n is determined by the publisher
179 | *
180 | * @return string
181 | */
182 | public function getNextUrl()
183 | {
184 | return $this->nextUrl;
185 | }
186 |
187 | /**
188 | * Set the URL of a feed that provides the next n items, where n is determined by the publisher
189 | *
190 | * @param string $nextUrl
191 | * @return Feed
192 | */
193 | public function setNextUrl($nextUrl)
194 | {
195 | $this->nextUrl = $nextUrl;
196 |
197 | return $this;
198 | }
199 |
200 | /**
201 | * Get the URL of an image for the feed suitable to be used in a timeline, much the way an avatar might be used
202 | *
203 | * @return string
204 | */
205 | public function getIcon()
206 | {
207 | return $this->icon;
208 | }
209 |
210 | /**
211 | * Set the URL of an image for the feed suitable to be used in a timeline, much the way an avatar might be used
212 | *
213 | * @param string $icon
214 | * @return Feed
215 | */
216 | public function setIcon($icon)
217 | {
218 | $this->icon = $icon;
219 |
220 | return $this;
221 | }
222 |
223 | /**
224 | * Get the URL of an image for the feed suitable to be used in a source list
225 | *
226 | * @return string
227 | */
228 | public function getFavicon()
229 | {
230 | return $this->favicon;
231 | }
232 |
233 | /**
234 | * Set the URL of an image for the feed suitable to be used in a source list
235 | *
236 | * @param string $favicon
237 | * @return Feed
238 | */
239 | public function setFavicon($favicon)
240 | {
241 | $this->favicon = $favicon;
242 |
243 | return $this;
244 | }
245 |
246 | /**
247 | * Get the feed author
248 | *
249 | * @return Author
250 | */
251 | public function getAuthor()
252 | {
253 | return $this->author;
254 | }
255 |
256 | /**
257 | * Set the feed author
258 | *
259 | * @param Author $author
260 | * @return Feed
261 | */
262 | public function setAuthor(Author $author)
263 | {
264 | $this->author = $author;
265 |
266 | return $this;
267 | }
268 |
269 | /**
270 | * Set the feed author
271 | *
272 | * @param string $name
273 | * @param string $url
274 | * @return Feed
275 | */
276 | public function addAuthor($name, $url = null)
277 | {
278 | $author = new Author($name);
279 | $author->setUrl($url);
280 |
281 | return $this->setAuthor($author);
282 | }
283 |
284 | /**
285 | * Says whether or not the feed is finished
286 | *
287 | * @return bool
288 | */
289 | public function isExpired()
290 | {
291 | return $this->expired;
292 | }
293 |
294 | /**
295 | * Set the feed has expired
296 | *
297 | * @param bool $expired
298 | * @return Feed
299 | */
300 | public function setExpired($expired)
301 | {
302 | $this->expired = $expired;
303 |
304 | return $this;
305 | }
306 |
307 | /**
308 | * Get endpoints that can be used to subscribe to real-time notifications from the publisher of this feed
309 | *
310 | * @return Hub[]
311 | */
312 | public function getHubs()
313 | {
314 | return $this->hubs;
315 | }
316 |
317 | /**
318 | * Add endpoint that can be used to subscribe to real-time notifications from the publisher of this feed
319 | *
320 | * @param Hub $hub
321 | * @return Feed
322 | */
323 | public function addHub(Hub $hub)
324 | {
325 | if (!in_array($hub, $this->hubs, true)) {
326 | $this->hubs[] = $hub;
327 | }
328 |
329 | return $this;
330 | }
331 |
332 | /**
333 | * Set endpoints that can be used to subscribe to real-time notifications from the publisher of this feed
334 | *
335 | * @param Hub[] $hubs
336 | * @return Feed
337 | */
338 | public function setHubs(array $hubs)
339 | {
340 | $this->hubs = $hubs;
341 |
342 | return $this;
343 | }
344 |
345 | /**
346 | * Get feed items
347 | *
348 | * @return Item[]
349 | */
350 | public function getItems()
351 | {
352 | return $this->items;
353 | }
354 |
355 | /**
356 | * Add feed item
357 | *
358 | * @param Item $item
359 | * @return Feed
360 | */
361 | public function addItem(Item $item)
362 | {
363 | if (!in_array($item, $this->items, true)) {
364 | $this->items[] = $item;
365 | }
366 |
367 | return $this;
368 | }
369 |
370 | /**
371 | * Set feed items
372 | *
373 | * @param Item[] $items
374 | * @return Feed
375 | */
376 | public function setItems(array $items)
377 | {
378 | $this->items = $items;
379 |
380 | return $this;
381 | }
382 | }
383 |
--------------------------------------------------------------------------------
/src/Hub.php:
--------------------------------------------------------------------------------
1 | type = $type;
28 | $this->url = $url;
29 | }
30 |
31 | /**
32 | * Get hub type
33 | *
34 | * @return string
35 | */
36 | public function getType()
37 | {
38 | return $this->type;
39 | }
40 |
41 | /**
42 | * Get hub URL
43 | *
44 | * @return string
45 | */
46 | public function getUrl()
47 | {
48 | return $this->url;
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/src/Item.php:
--------------------------------------------------------------------------------
1 | id = $id;
65 |
66 | $this->tags = [];
67 | $this->attachments = [];
68 | $this->extensions = [];
69 | }
70 |
71 | /**
72 | * Get unique identifer for that item for that feed over time
73 | *
74 | * @return string
75 | */
76 | public function getId()
77 | {
78 | return $this->id;
79 | }
80 |
81 | /**
82 | * Get the URL of the resource described by the item
83 | *
84 | * @return string
85 | */
86 | public function getUrl()
87 | {
88 | return $this->url;
89 | }
90 |
91 | /**
92 | * Set the URL of the resource described by the item
93 | *
94 | * @param string $url
95 | * @return Item
96 | */
97 | public function setUrl($url)
98 | {
99 | $this->url = $url;
100 |
101 | return $this;
102 | }
103 |
104 | /**
105 | * Get the URL of a page elsewhere
106 | *
107 | * @return string
108 | */
109 | public function getExternalUrl()
110 | {
111 | return $this->externalUrl;
112 | }
113 |
114 | /**
115 | * Set the URL of a page elsewhere
116 | *
117 | * @param string $externalUrl
118 | * @return Item
119 | */
120 | public function setExternalUrl($externalUrl)
121 | {
122 | $this->externalUrl = $externalUrl;
123 |
124 | return $this;
125 | }
126 |
127 | /**
128 | * Get plaintext title
129 | *
130 | * @return string
131 | */
132 | public function getTitle()
133 | {
134 | return $this->title;
135 | }
136 |
137 | /**
138 | * Set plaintext title
139 | *
140 | * @param string $title
141 | * @return Item
142 | */
143 | public function setTitle($title)
144 | {
145 | $this->title = $title;
146 |
147 | return $this;
148 | }
149 |
150 | /**
151 | * Get the HTML content for the item
152 | *
153 | * @return string
154 | */
155 | public function getContentHtml()
156 | {
157 | return $this->contentHtml;
158 | }
159 |
160 | /**
161 | * Set the HTML content for the item
162 | *
163 | * @param string $contentHtml
164 | * @return Item
165 | */
166 | public function setContentHtml($contentHtml)
167 | {
168 | $this->contentHtml = $contentHtml;
169 |
170 | return $this;
171 | }
172 |
173 | /**
174 | * Get plain text content for the item
175 | *
176 | * @return string
177 | */
178 | public function getContentText()
179 | {
180 | return $this->contentText;
181 | }
182 |
183 | /**
184 | * Set plain text content for the item
185 | *
186 | * @param string $contentText
187 | * @return Item
188 | */
189 | public function setContentText($contentText)
190 | {
191 | $this->contentText = $contentText;
192 |
193 | return $this;
194 | }
195 |
196 | /**
197 | * Get a plain text sentence or two describing the item
198 | *
199 | * @return string
200 | */
201 | public function getSummary()
202 | {
203 | return $this->summary;
204 | }
205 |
206 | /**
207 | * Set a plain text sentence or two describing the item
208 | *
209 | * @param string $summary
210 | * @return Item
211 | */
212 | public function setSummary($summary)
213 | {
214 | $this->summary = $summary;
215 |
216 | return $this;
217 | }
218 |
219 | /**
220 | * Get the URL of the main image for the item
221 | *
222 | * @return string
223 | */
224 | public function getImage()
225 | {
226 | return $this->image;
227 | }
228 |
229 | /**
230 | * Set the URL of the main image for the item
231 | *
232 | * @param string $image
233 | * @return Item
234 | */
235 | public function setImage($image)
236 | {
237 | $this->image = $image;
238 |
239 | return $this;
240 | }
241 |
242 | /**
243 | * Get the URL of an image to use as a banner
244 | *
245 | * @return string
246 | */
247 | public function getBannerImage()
248 | {
249 | return $this->bannerImage;
250 | }
251 |
252 | /**
253 | * Set the URL of an image to use as a banner
254 | *
255 | * @param string $bannerImage
256 | * @return Item
257 | */
258 | public function setBannerImage($bannerImage)
259 | {
260 | $this->bannerImage = $bannerImage;
261 |
262 | return $this;
263 | }
264 |
265 | /**
266 | * Get the item published date
267 | *
268 | * @return DateTime
269 | */
270 | public function getDatePublished()
271 | {
272 | return $this->datePublished;
273 | }
274 |
275 | /**
276 | * Set the item published date
277 | *
278 | * @param DateTime $datePublished
279 | * @return Item
280 | */
281 | public function setDatePublished(DateTime $datePublished)
282 | {
283 | $this->datePublished = $datePublished;
284 |
285 | return $this;
286 | }
287 |
288 | /**
289 | * Get the item modified date
290 | *
291 | * @return DateTime
292 | */
293 | public function getDateModified()
294 | {
295 | return $this->dateModified;
296 | }
297 |
298 | /**
299 | * Set the item modified date
300 | *
301 | * @param DateTime $dateModified
302 | * @return Item
303 | */
304 | public function setDateModified(DateTime $dateModified)
305 | {
306 | $this->dateModified = $dateModified;
307 |
308 | return $this;
309 | }
310 |
311 | /**
312 | * Get item author
313 | *
314 | * @return Author
315 | */
316 | public function getAuthor()
317 | {
318 | return $this->author;
319 | }
320 |
321 | /**
322 | * Set item author
323 | *
324 | * @param Author $author
325 | * @return Item
326 | */
327 | public function setAuthor(Author $author)
328 | {
329 | $this->author = $author;
330 |
331 | return $this;
332 | }
333 |
334 | /**
335 | * Get item tags
336 | *
337 | * @return string[]
338 | */
339 | public function getTags()
340 | {
341 | return $this->tags;
342 | }
343 |
344 | /**
345 | * Add item tag
346 | * @param string $tag
347 | * @return Item
348 | */
349 | public function addTag($tag)
350 | {
351 | if (!in_array($tag, $this->tags, true)) {
352 | $this->tags[] = $tag;
353 | }
354 |
355 | return $this;
356 | }
357 |
358 | /**
359 | * Set item tags
360 | *
361 | * @param string[] $tags
362 | * @return Item
363 | */
364 | public function setTags(array $tags)
365 | {
366 | $this->tags = $tags;
367 |
368 | return $this;
369 | }
370 |
371 | /**
372 | * Get item attachments
373 | *
374 | * @return Attachment[]
375 | */
376 | public function getAttachments()
377 | {
378 | return $this->attachments;
379 | }
380 |
381 | /**
382 | * Add item attachment
383 | *
384 | * @param Attachment $attachment
385 | * @return Item
386 | */
387 | public function addAttachment(Attachment $attachment)
388 | {
389 | if (!in_array($attachment, $this->attachments, true)) {
390 | $this->attachments[] = $attachment;
391 | }
392 |
393 | return $this;
394 | }
395 |
396 | /**
397 | * Set item attachments
398 | *
399 | * @param Attachment[] $attachments
400 | * @return Item
401 | */
402 | public function setAttachments(array $attachments)
403 | {
404 | $this->attachments = $attachments;
405 |
406 | return $this;
407 | }
408 |
409 | /**
410 | * Add an extension to the item
411 | *
412 | * @param string $key
413 | * @param array $value
414 | * @return Item
415 | */
416 | public function addExtension($key, array $value)
417 | {
418 | if (!is_string($key)) {
419 | throw new InvalidArgumentException('Extension key must be a string');
420 | }
421 |
422 | $this->extensions[$key] = $value;
423 |
424 | return $this;
425 | }
426 |
427 | /**
428 | * Get all extensions
429 | *
430 | * @return array
431 | */
432 | public function getExtensions()
433 | {
434 | return $this->extensions;
435 | }
436 |
437 | /**
438 | * Get an extension
439 | *
440 | * @param string $key
441 | * @return array|null
442 | */
443 | public function getExtension($key)
444 | {
445 | if (!isset($this->extensions[$key])) {
446 | return null;
447 | }
448 |
449 | return $this->extensions[$key];
450 | }
451 | }
452 |
--------------------------------------------------------------------------------
/src/Reader/Reader.php:
--------------------------------------------------------------------------------
1 | readers = $readers;
23 | }
24 |
25 | /**
26 | * Read feed from JSON
27 | *
28 | * @param string $json
29 | * @return \JDecool\JsonFeed\Feed
30 | *
31 | * @throws InvalidFeedException
32 | * @throws RuntimeException
33 | */
34 | public function createFromJson($json)
35 | {
36 | $content = json_decode($json, true);
37 | if (!is_array($content)) {
38 | throw InvalidFeedException::invalidJsonException();
39 | }
40 |
41 | if (!isset($content['version'])) {
42 | throw InvalidFeedException::undefinedVersionException();
43 | }
44 |
45 | if (!isset($this->readers[$content['version']])) {
46 | throw RuntimeException::noReaderRegisteredException($content['version']);
47 | }
48 |
49 | return $this->readers[$content['version']]->readFromJson($json);
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/Reader/ReaderBuilder.php:
--------------------------------------------------------------------------------
1 | Version1\FeedReader::create($isErrorEnabled),
21 | ]);
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/Reader/ReaderInterface.php:
--------------------------------------------------------------------------------
1 | accessor = PropertyAccess::createPropertyAccessor();
43 | $this->isErrorEnabled = $isErrorEnabled;
44 | }
45 |
46 | /**
47 | * Define if errors are enable on parsing feed data
48 | *
49 | * @param bool $enable
50 | * @return FeedReader
51 | */
52 | public function enableErrorOnParsing($enable)
53 | {
54 | $this->isErrorEnabled = $enable;
55 |
56 | return $this;
57 | }
58 |
59 | /**
60 | * {@inheritdoc}
61 | */
62 | public function readFromJson($json)
63 | {
64 | $content = json_decode($json, true);
65 | if (!is_array($content)) {
66 | throw InvalidFeedException::invalidJsonException();
67 | }
68 |
69 | return $this->readFeedNode($content);
70 | }
71 |
72 | /**
73 | * Browse feed node
74 | *
75 | * @param array $content
76 | * @return Feed
77 | */
78 | private function readFeedNode(array $content)
79 | {
80 | $feed = new Feed('');
81 |
82 | foreach ($content as $key => $value) {
83 | if ('version' === $key) {
84 | continue;
85 | }
86 |
87 | switch ($key) {
88 | case 'author':
89 | $feed->setAuthor($this->readAuthorNode($value));
90 | break;
91 |
92 | case 'hubs':
93 | $feed->setHubs(array_map([$this, 'readHubNode'], $value));
94 | break;
95 |
96 | case 'items':
97 | $feed->setItems(array_map([$this, 'readItemNode'], $value));
98 | break;
99 |
100 | default:
101 | try {
102 | $this->accessor->setValue($feed, $key, $value);
103 | } catch (NoSuchPropertyException $e) {
104 | if ($this->isErrorEnabled) {
105 | throw InvalidFeedException::invalidFeedProperty($key);
106 | }
107 | }
108 | }
109 | }
110 |
111 | return $feed;
112 | }
113 |
114 | /**
115 | * Browse item node
116 | *
117 | * @param array $content
118 | * @return Item
119 | */
120 | private function readItemNode(array $content)
121 | {
122 | $id = isset($content['id']) ? $content['id'] : '';
123 |
124 | $item = new Item($id);
125 | foreach ($content as $key => $value) {
126 | if ('id' === $key) {
127 | continue;
128 | }
129 |
130 | switch ($key) {
131 | case 'attachments':
132 | $item->setAttachments(array_map([$this, 'readAttachmentNode'], $value));
133 | break;
134 |
135 | case 'author':
136 | $item->setAuthor($this->readAuthorNode($value));
137 | break;
138 |
139 | case 'date_published':
140 | case 'date_modified':
141 | $this->accessor->setValue($item, $key, new DateTime($value));
142 | break;
143 |
144 | default:
145 | try {
146 | if ('_' === $key[0]) {
147 | $item->addExtension(substr($key, 1), $value);
148 | } else {
149 | $this->accessor->setValue($item, $key, $value);
150 | }
151 | } catch (NoSuchPropertyException $e) {
152 | if ($this->isErrorEnabled) {
153 | throw InvalidFeedException::invalidItemProperty($key);
154 | }
155 | }
156 | break;
157 | }
158 | }
159 |
160 | return $item;
161 | }
162 |
163 | /**
164 | * Browse author node
165 | *
166 | * @param array $content
167 | * @return Author
168 | */
169 | private function readAuthorNode(array $content)
170 | {
171 | $name = (isset($content['name'])) ? $content['name'] : '';
172 |
173 | $author = new Author($name);
174 | foreach ($content as $key => $value) {
175 | if ('name' === $key) {
176 | continue;
177 | }
178 |
179 | try {
180 | $this->accessor->setValue($author, $key, $value);
181 | } catch (NoSuchPropertyException $e) {
182 | if ($this->isErrorEnabled) {
183 | throw InvalidFeedException::invalidAuthorProperty($key);
184 | }
185 | }
186 | }
187 |
188 | return $author;
189 | }
190 |
191 | /**
192 | * Browse hub node
193 | *
194 | * @param array $content
195 | * @return Hub
196 | */
197 | private function readHubNode(array $content)
198 | {
199 | $type = isset($content['type']) ? $content['type'] : '';
200 | $url = isset($content['url']) ? $content['url'] : '';
201 |
202 | return new Hub($type, $url);
203 | }
204 |
205 | /**
206 | * Browse attachment node
207 | *
208 | * @param array $content
209 | * @return Attachment
210 | */
211 | private function readAttachmentNode(array $content)
212 | {
213 | $url = isset($content['url']) ? $content['url'] : '';
214 | $mimeType = isset($content['mime_type']) ? $content['mime_type'] : '';
215 |
216 | $attachment = new Attachment($url, $mimeType);
217 | foreach ($content as $key => $value) {
218 | switch ($key) {
219 | case 'size_in_bytes':
220 | $attachment->setSize($value);
221 | break;
222 |
223 | case 'duration_in_seconds':
224 | $attachment->setDuration($value);
225 | break;
226 | }
227 | }
228 |
229 | return $attachment;
230 | }
231 | }
232 |
--------------------------------------------------------------------------------
/src/Versions.php:
--------------------------------------------------------------------------------
1 | renderers = [];
21 | }
22 |
23 | /**
24 | * Create specific renderer
25 | *
26 | * @param string $version
27 | * @return Version1\Renderer
28 | *
29 | * @throws RuntimeException
30 | */
31 | public function createRenderer($version = Versions::VERSION_1)
32 | {
33 | if (isset($this->renderers[$version])) {
34 | return $this->renderers[$version];
35 | }
36 |
37 | switch ($version) {
38 | case Versions::VERSION_1:
39 | return new Version1\Renderer();
40 | }
41 |
42 | throw RuntimeException::noRendererRegisteredException($version);
43 | }
44 |
45 | /**
46 | * Register a custom renderer
47 | *
48 | * @param string $key
49 | * @param RendererInterface $renderer
50 | * @return RendererFactory
51 | */
52 | public function registerRenderer($key, RendererInterface $renderer)
53 | {
54 | $this->renderers[$key] = $renderer;
55 |
56 | return $this;
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/src/Writer/RendererInterface.php:
--------------------------------------------------------------------------------
1 | flags = $flags;
33 | }
34 |
35 | /**
36 | * {@inheritdoc}
37 | */
38 | public function render(Feed $feed)
39 | {
40 | $result = [
41 | 'version' => Versions::VERSION_1,
42 | 'title' => $feed->getTitle(),
43 | ];
44 |
45 | if ($homepageUrl = $feed->getHomepageUrl()) {
46 | $result['home_page_url'] = $homepageUrl;
47 | }
48 |
49 | if ($feedUrl = $feed->getFeedUrl()) {
50 | $result['feed_url'] = $feedUrl;
51 | }
52 |
53 | if ($description = $feed->getDescription()) {
54 | $result['description'] = $description;
55 | }
56 |
57 | if ($userComment = $feed->getUserComment()) {
58 | $result['user_comment'] = $userComment;
59 | }
60 |
61 | if ($nextUrl = $feed->getNextUrl()) {
62 | $result['next_url'] = $nextUrl;
63 | }
64 |
65 | if ($icon = $feed->getIcon()) {
66 | $result['icon'] = $icon;
67 | }
68 |
69 | if ($favicon = $feed->getFavicon()) {
70 | $result['favicon'] = $favicon;
71 | }
72 |
73 | if ($author = $feed->getAuthor()) {
74 | $result['author'] = $this->renderAuthor($author);
75 | }
76 |
77 | if (null !== $expired = $feed->isExpired()) {
78 | $result['expired'] = (bool) $expired;
79 | }
80 |
81 | if ($items = $feed->getItems()) {
82 | $result['items'] = array_map(function(Item $item) {
83 | return $this->renderItem($item);
84 | }, $items);
85 | }
86 |
87 | if ($hubs = $feed->getHubs()) {
88 | $result['hubs'] = array_map(function(Hub $hub) {
89 | return $this->renderHub($hub);
90 | }, $hubs);
91 | }
92 |
93 | return json_encode($result, $this->flags);
94 | }
95 |
96 | /**
97 | * Render item
98 | *
99 | * @param Item $item
100 | * @return array
101 | */
102 | private function renderItem(Item $item)
103 | {
104 | $result = [
105 | 'id' => $item->getId(),
106 | ];
107 |
108 | if ($url = $item->getUrl()) {
109 | $result['url'] = $url;
110 | }
111 |
112 | if ($externalUrl = $item->getExternalUrl()) {
113 | $result['external_url'] = $externalUrl;
114 | }
115 |
116 | if ($title = $item->getTitle()) {
117 | $result['title'] = $title;
118 | }
119 |
120 | if ($contentHtml = $item->getContentHtml()) {
121 | $result['content_html'] = $contentHtml;
122 | }
123 |
124 | if ($contentText = $item->getContentText()) {
125 | $result['content_text'] = $contentText;
126 | }
127 |
128 | if ($summary = $item->getSummary()) {
129 | $result['summary'] = $summary;
130 | }
131 |
132 | if ($image = $item->getImage()) {
133 | $result['image'] = $image;
134 | }
135 |
136 | if ($bannerImage = $item->getBannerImage()) {
137 | $result['banner_image'] = $bannerImage;
138 | }
139 |
140 | if ($datePublished = $item->getDatePublished()) {
141 | $result['date_published'] = $datePublished->format(DateTime::ATOM);
142 | }
143 |
144 | if ($dateModified = $item->getDateModified()) {
145 | $result['date_modified'] = $dateModified->format(DateTime::ATOM);
146 | }
147 |
148 | if ($tags = $item->getTags()) {
149 | $result['tags'] = $tags;
150 | }
151 |
152 | if ($attachments = $item->getAttachments()) {
153 | $result['attachments'] = array_map(function(Attachment $attachment) {
154 | return $this->renderAttachment($attachment);
155 | }, $attachments);
156 | }
157 |
158 | if ($author = $item->getAuthor()) {
159 | $result['author'] = $this->renderAuthor($author);
160 | }
161 |
162 | if ($extensions = $item->getExtensions()) {
163 | foreach ($extensions as $key => $extension) {
164 | $result['_'.$key] = $extension;
165 | }
166 | }
167 |
168 | return $result;
169 | }
170 |
171 | /**
172 | * Render attachment
173 | *
174 | * @param Attachment $attachment
175 | * @return array
176 | */
177 | private function renderAttachment(Attachment $attachment)
178 | {
179 | $result = [
180 | 'url' => $attachment->getUrl(),
181 | 'mime_type' => $attachment->getMimeType(),
182 | ];
183 |
184 | if ($title = $attachment->getTitle()) {
185 | $result['title'] = $title;
186 | }
187 |
188 | if ($size = $attachment->getSize()) {
189 | $result['size_in_bytes'] = $size;
190 | }
191 |
192 | if ($duration = $attachment->getDuration()) {
193 | $result['duration_in_seconds'] = $duration;
194 | }
195 |
196 | return $result;
197 | }
198 |
199 | /**
200 | * Render author
201 | *
202 | * @param Author $author
203 | * @return array
204 | */
205 | private function renderAuthor(Author $author)
206 | {
207 | $result = [];
208 |
209 | if ($name = $author->getName()) {
210 | $result['name'] = $name;
211 | }
212 |
213 | if ($url = $author->getUrl()) {
214 | $result['url'] = $url;
215 | }
216 |
217 | if ($avatar = $author->getAvatar()) {
218 | $result['avatar'] = $avatar;
219 | }
220 |
221 | return $result;
222 | }
223 |
224 | /**
225 | * Render hub
226 | *
227 | * @param Hub $hub
228 | * @return array
229 | */
230 | private function renderHub(Hub $hub)
231 | {
232 | return [
233 | 'type' => $hub->getType(),
234 | 'url' => $hub->getUrl(),
235 | ];
236 | }
237 | }
238 |
--------------------------------------------------------------------------------
/test/AttachmentTest.php:
--------------------------------------------------------------------------------
1 | getUrl());
17 | static::assertEquals('application/bar', $attachment->getMimeType());
18 | static::assertNull($attachment->getTitle());
19 | static::assertNull($attachment->getSize());
20 | static::assertNull($attachment->getDuration());
21 | }
22 |
23 | public function testFullObject(): void
24 | {
25 | $attachment = new Attachment('file://foo', 'application/bar');
26 | $attachment
27 | ->setTitle('My title')
28 | ->setSize(500)
29 | ->setDuration(25)
30 | ;
31 |
32 | static::assertEquals('file://foo', $attachment->getUrl());
33 | static::assertEquals('application/bar', $attachment->getMimeType());
34 | static::assertEquals('My title', $attachment->getTitle());
35 | static::assertEquals(500, $attachment->getSize());
36 | static::assertEquals(25, $attachment->getDuration());
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/test/AuthorTest.php:
--------------------------------------------------------------------------------
1 | getName());
17 | static::assertNull($author->getUrl());
18 | static::assertNull($author->getAvatar());
19 | }
20 |
21 | public function testFullObject(): void
22 | {
23 | $author = new Author('foo');
24 | $author
25 | ->setUrl('file://bar')
26 | ->setAvatar('file://image')
27 | ;
28 |
29 | static::assertEquals('foo', $author->getName());
30 | static::assertEquals('file://bar', $author->getUrl());
31 | static::assertEquals('file://image', $author->getAvatar());
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/test/FeedTest.php:
--------------------------------------------------------------------------------
1 | getTitle());
18 | static::assertNull($feed->getHomepageUrl());
19 | static::assertNull($feed->getFeedUrl());
20 | static::assertNull($feed->getDescription());
21 | static::assertNull($feed->getUserComment());
22 | static::assertNull($feed->getNextUrl());
23 | static::assertNull($feed->getIcon());
24 | static::assertNull($feed->getFavicon());
25 | static::assertNull($feed->getAuthor());
26 | static::assertNull($feed->isExpired());
27 | static::assertEmpty($feed->getHubs());
28 | static::assertEmpty($feed->getItems());
29 | }
30 |
31 | public function testFeedWithOneItem(): void
32 | {
33 | $item = new Item('itemId');
34 | $feed = new Feed('My feed');
35 | $feed->addItem($item);
36 |
37 | $feedItems = $feed->getItems();
38 | static::assertEquals(1, count($feedItems));
39 | static::assertEquals($item, $feedItems[0]);
40 | }
41 |
42 | public function testFeedWithTwoItems(): void
43 | {
44 | $item1 = new Item('itemId1');
45 | $item2 = new Item('itemId2');
46 |
47 | $feed = new Feed('My feed');
48 | $feed->addItem($item1);
49 | $feed->addItem($item2);
50 |
51 | $feedItems = $feed->getItems();
52 | static::assertEquals(2, count($feedItems));
53 | static::assertEquals($item1, $feedItems[0]);
54 | static::assertEquals($item2, $feedItems[1]);
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/test/Fixtures/authors.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "https://jsonfeed.org/version/1",
3 | "title": "My Example Feed",
4 | "feed_url": "https://example.org/feed.json",
5 | "author": {
6 | "name": "Global Author"
7 | },
8 | "items": [
9 | {
10 | "id": "2",
11 | "content_text": "This is a second item.",
12 | "url": "https://example.org/2",
13 | "author": {
14 | "name": "Author 2"
15 | }
16 | },
17 | {
18 | "id": "1",
19 | "content_html": "
This is the first item.
",
20 | "url": "https://example.org/1",
21 | "author": {
22 | "name": "Author 1"
23 | }
24 | }
25 | ]
26 | }
27 |
--------------------------------------------------------------------------------
/test/Fixtures/extension.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "https://jsonfeed.org/version/1",
3 | "title": "My Example Feed",
4 | "items": [
5 | {
6 | "id": "2",
7 | "content_text": "This is a second item.",
8 | "url": "https://example.org/second-item",
9 | "_extItem2": {
10 | "foo": "value",
11 | "bar": "value"
12 | },
13 | "_extAuthor": {
14 | "john": "doe",
15 | "jane": "doe"
16 | }
17 | },
18 | {
19 | "id": "1",
20 | "content_html": "Hello, world!
",
21 | "url": "https://example.org/initial-post",
22 | "_extAuthor": {
23 | "john": "doe",
24 | "jane": "doe"
25 | }
26 | }
27 | ]
28 | }
29 |
--------------------------------------------------------------------------------
/test/Fixtures/microblog.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "https://jsonfeed.org/version/1",
3 | "user_comment": "This is a microblog feed. You can add this to your feed reader using the following URL: https://example.org/feed.json",
4 | "title": "Brent Simmons’s Microblog",
5 | "home_page_url": "https://example.org/",
6 | "feed_url": "https://example.org/feed.json",
7 | "author": {
8 | "name": "Brent Simmons",
9 | "url": "http://example.org/",
10 | "avatar": "https://example.org/avatar.png"
11 | },
12 | "items": [
13 | {
14 | "id": "2347259",
15 | "url": "https://example.org/2347259",
16 | "content_text": "Cats are neat. https://example.org/cats",
17 | "date_published": "2016-02-09T14:22:00+02:00"
18 | }
19 | ]
20 | }
21 |
--------------------------------------------------------------------------------
/test/Fixtures/podcast.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "https://jsonfeed.org/version/1",
3 | "user_comment": "This is a podcast feed. You can add this feed to your podcast client using the following URL: http://therecord.co/feed.json",
4 | "title": "The Record",
5 | "home_page_url": "http://therecord.co/",
6 | "feed_url": "http://therecord.co/feed.json",
7 | "items": [
8 | {
9 | "id": "http://therecord.co/chris-parrish",
10 | "title": "Special #1 - Chris Parrish",
11 | "url": "http://therecord.co/chris-parrish",
12 | "content_text": "Chris has worked at Adobe and as a founder of Rogue Sheep, which won an Apple Design Award for Postage. Chris’s new company is Aged & Distilled with Guy English — which shipped Napkin, a Mac app for visual collaboration. Chris is also the co-host of The Record. He lives on Bainbridge Island, a quick ferry ride from Seattle.",
13 | "content_html": "Chris has worked at Adobe and as a founder of Rogue Sheep, which won an Apple Design Award for Postage. Chris’s new company is Aged & Distilled with Guy English — which shipped Napkin, a Mac app for visual collaboration. Chris is also the co-host of The Record. He lives on Bainbridge Island, a quick ferry ride from Seattle.",
14 | "summary": "Brent interviews Chris Parrish, co-host of The Record and one-half of Aged & Distilled.",
15 | "date_published": "2014-05-09T14:04:00-07:00",
16 | "attachments": [
17 | {
18 | "url": "http://therecord.co/downloads/The-Record-sp1e1-ChrisParrish.m4a",
19 | "mime_type": "audio/x-m4a",
20 | "size_in_bytes": 89970236,
21 | "duration_in_seconds": 6629
22 | }
23 | ]
24 | }
25 | ]
26 | }
27 |
--------------------------------------------------------------------------------
/test/Fixtures/simple.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "https://jsonfeed.org/version/1",
3 | "title": "My Example Feed",
4 | "home_page_url": "https://example.org/",
5 | "feed_url": "https://example.org/feed.json",
6 | "items": [
7 | {
8 | "id": "2",
9 | "content_text": "This is a second item.",
10 | "url": "https://example.org/second-item"
11 | },
12 | {
13 | "id": "1",
14 | "content_html": "Hello, world!
",
15 | "url": "https://example.org/initial-post"
16 | }
17 | ]
18 | }
19 |
--------------------------------------------------------------------------------
/test/HubTest.php:
--------------------------------------------------------------------------------
1 | getType());
17 | static::assertEquals('file://bar', $hub->getUrl());
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/test/ItemTest.php:
--------------------------------------------------------------------------------
1 | getId());
19 | static::assertNull($item->getUrl());
20 | static::assertNull($item->getExternalUrl());
21 | static::assertNull($item->getTitle());
22 | static::assertNull($item->getContentHtml());
23 | static::assertNull($item->getContentText());
24 | static::assertNull($item->getSummary());
25 | static::assertNull($item->getImage());
26 | static::assertNull($item->getBannerImage());
27 | static::assertNull($item->getDatePublished());
28 | static::assertNull($item->getDateModified());
29 | static::assertEmpty($item->getAuthor());
30 | static::assertEmpty($item->getTags());
31 | static::assertEmpty($item->getAttachments());
32 | }
33 |
34 | public function testAddAuthor(): void
35 | {
36 | $author = new Author('foo');
37 |
38 | $item = new Item('myid');
39 | $item->setAuthor($author);
40 |
41 | static::assertEquals($author, $item->getAuthor());
42 | }
43 |
44 | public function testTagsEmpty(): void
45 | {
46 | $item = new Item('myid');
47 | static::assertEmpty($item->getTags());
48 | }
49 |
50 | public function testAddTagsOneElement(): void
51 | {
52 | $item = new Item('myid');
53 | $item->addTag('tag1');
54 |
55 | static::assertEquals(1, count($item->getTags()));
56 | static::assertEquals(['tag1'], $item->getTags());
57 | }
58 |
59 | public function testAddTagsTwoElements(): void
60 | {
61 | $item = new Item('myid');
62 | $item->addTag('tag1');
63 | $item->addTag('tag2');
64 |
65 | static::assertEquals(2, count($item->getTags()));
66 | static::assertEquals(['tag1', 'tag2'], $item->getTags());
67 | }
68 |
69 | public function testSetTags(): void
70 | {
71 | $tags = ['tag1', 'tag2'];
72 |
73 | $item = new Item('myid');
74 | $item->setTags($tags);
75 |
76 | static::assertEquals($tags, $item->getTags());
77 | }
78 |
79 | public function testAttachmentEmpty(): void
80 | {
81 | $item = new Item('myid');
82 | static::assertEmpty($item->getAttachments());
83 | }
84 |
85 | public function testAddAttachmentOneElement(): void
86 | {
87 | $attachment = new Attachment('foo1', 'bar1');
88 |
89 | $item = new Item('myid');
90 | $item->addAttachment($attachment);
91 |
92 | static::assertEquals(1, count($item->getAttachments()));
93 | static::assertEquals([$attachment], $item->getAttachments());
94 | }
95 |
96 | public function testAddAttachmentTwoElements(): void
97 | {
98 | $attachment1 = new Attachment('foo1', 'bar1');
99 | $attachment2 = new Attachment('foo2', 'bar2');
100 |
101 | $item = new Item('myid');
102 | $item->addAttachment($attachment1);
103 | $item->addAttachment($attachment2);
104 |
105 | static::assertEquals(2, count($item->getAttachments()));
106 | static::assertEquals([$attachment1, $attachment2], $item->getAttachments());
107 | }
108 |
109 | public function testSetAttachments(): void
110 | {
111 | $attachments = [
112 | new Attachment('foo1', 'bar1'),
113 | new Attachment('foo2', 'bar2'),
114 | ];
115 |
116 | $item = new Item('myid');
117 | $item->setAttachments($attachments);
118 |
119 | static::assertEquals(2, count($item->getAttachments()));
120 | static::assertEquals($attachments, $item->getAttachments());
121 | }
122 |
123 | public function testAddExtension(): void
124 | {
125 | $extension1 = [
126 | 'about' => 'https://blueshed-podcasts.com/json-feed-extension-docs',
127 | 'explicit' => false,
128 | 'copyright' => '1948 by George Orwell',
129 | 'owner' => 'Big Brother and the Holding Company',
130 | 'subtitle' => 'All shouting, all the time. Double. Plus. Good.'
131 | ];
132 |
133 | $item = new Item('myid');
134 | $item->addExtension('blue_shed', $extension1);
135 |
136 | static::assertEquals(1, count($item->getExtensions()));
137 | static::assertEquals($extension1, $item->getExtension('blue_shed'));
138 |
139 | $extension2 = [
140 | 'foo1' => 'bar1',
141 | 'foo2' => 'bar2',
142 | ];
143 | $item->addExtension('blue_shed2', $extension2);
144 |
145 | static::assertEquals(2, count($item->getExtensions()));
146 | static::assertEquals($extension1, $item->getExtension('blue_shed'));
147 | static::assertEquals($extension2, $item->getExtension('blue_shed2'));
148 | }
149 | }
150 |
--------------------------------------------------------------------------------
/test/Reader/ReaderBuilderTest.php:
--------------------------------------------------------------------------------
1 | build());
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/test/Reader/ReaderTest.php:
--------------------------------------------------------------------------------
1 | getMockBuilder('JDecool\JsonFeed\Reader\ReaderInterface')->getMock();
24 | $feedReader->expects($this->once())
25 | ->method('readFromJson');
26 |
27 | $reader = new Reader([
28 | 'foo' => $feedReader,
29 | ]);
30 |
31 | $reader->createFromJson($json);
32 | }
33 |
34 | public function testCreateFromJsonWithInvalidReader(): void
35 | {
36 | $json = <<expectException(RuntimeException::class);
46 | $this->expectExceptionMessage('No reader registered for version "foo"');
47 |
48 | $reader->createFromJson($json);
49 | }
50 |
51 | public function testCreateFromJsonWithInvalidString(): void
52 | {
53 | $json = <<expectException(InvalidFeedException::class);
63 | $this->expectExceptionMessage('Invalid JSONFeed string');
64 |
65 | $reader->createFromJson($json);
66 | }
67 |
68 | public function testCreateFromJsonWithoutVersion(): void
69 | {
70 | $json = <<expectException(InvalidFeedException::class);
79 | $this->expectExceptionMessage('Undefined JSONFeed version');
80 |
81 | $reader->createFromJson($json);
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/test/Reader/Version1/FeedReaderTest.php:
--------------------------------------------------------------------------------
1 | getFixtures('simple');
27 | $reader = FeedReader::create();
28 |
29 | $feed = $reader->readFromJson($input);
30 | static::assertInstanceOf('JDecool\JsonFeed\Feed', $feed);
31 | static::assertEquals('My Example Feed', $feed->getTitle());
32 | static::assertEquals('https://example.org/', $feed->getHomepageUrl());
33 | static::assertEquals('https://example.org/feed.json', $feed->getFeedUrl());
34 |
35 | $items = $feed->getItems();
36 | static::assertCount(2, $items);
37 |
38 | $item2 = new Item('2');
39 | $item2->setContentText('This is a second item.');
40 | $item2->setUrl('https://example.org/second-item');
41 | static::assertEquals($item2, $items[0]);
42 |
43 | $item1 = new Item('1');
44 | $item1->setContentHtml('Hello, world!
');
45 | $item1->setUrl('https://example.org/initial-post');
46 | static::assertEquals($item1, $items[1]);
47 | }
48 |
49 | public function testPodcastFeed(): void
50 | {
51 | $input = $this->getFixtures('podcast');
52 | $reader = FeedReader::create();
53 |
54 | $feed = $reader->readFromJson($input);
55 | static::assertInstanceOf('JDecool\JsonFeed\Feed', $feed);
56 | static::assertEquals('The Record', $feed->getTitle());
57 | static::assertEquals('http://therecord.co/', $feed->getHomepageUrl());
58 | static::assertEquals('http://therecord.co/feed.json', $feed->getFeedUrl());
59 | static::assertEquals('This is a podcast feed. You can add this feed to your podcast client using the following URL: http://therecord.co/feed.json', $feed->getUserComment());
60 |
61 | $items = $feed->getItems();
62 | static::assertCount(1, $items);
63 |
64 | $attachment = new Attachment('http://therecord.co/downloads/The-Record-sp1e1-ChrisParrish.m4a', 'audio/x-m4a');
65 | $attachment->setSize(89970236);
66 | $attachment->setDuration(6629);
67 |
68 | $item = new Item('http://therecord.co/chris-parrish');
69 | $item->setTitle('Special #1 - Chris Parrish');
70 | $item->setUrl('http://therecord.co/chris-parrish');
71 | $item->setContentText('Chris has worked at Adobe and as a founder of Rogue Sheep, which won an Apple Design Award for Postage. Chris’s new company is Aged & Distilled with Guy English — which shipped Napkin, a Mac app for visual collaboration. Chris is also the co-host of The Record. He lives on Bainbridge Island, a quick ferry ride from Seattle.');
72 | $item->setContentHtml('Chris has worked at Adobe and as a founder of Rogue Sheep, which won an Apple Design Award for Postage. Chris’s new company is Aged & Distilled with Guy English — which shipped Napkin, a Mac app for visual collaboration. Chris is also the co-host of The Record. He lives on Bainbridge Island, a quick ferry ride from Seattle.');
73 | $item->setSummary('Brent interviews Chris Parrish, co-host of The Record and one-half of Aged & Distilled.');
74 | $item->setDatePublished(new DateTime('2014-05-09T14:04:00-07:00'));
75 | $item->addAttachment($attachment);
76 | static::assertEquals($item, $items[0]);
77 | }
78 |
79 | public function testMicroblogFeed(): void
80 | {
81 | $input = $this->getFixtures('microblog');
82 | $reader = FeedReader::create();
83 |
84 | $feed = $reader->readFromJson($input);
85 | static::assertInstanceOf('JDecool\JsonFeed\Feed', $feed);
86 | static::assertEquals('Brent Simmons’s Microblog', $feed->getTitle());
87 | static::assertEquals('https://example.org/', $feed->getHomepageUrl());
88 | static::assertEquals('https://example.org/feed.json', $feed->getFeedUrl());
89 | static::assertEquals('This is a microblog feed. You can add this to your feed reader using the following URL: https://example.org/feed.json', $feed->getUserComment());
90 |
91 | $items = $feed->getItems();
92 | static::assertCount(1, $items);
93 |
94 | $item = new Item('2347259');
95 | $item->setUrl('https://example.org/2347259');
96 | $item->setContentText('Cats are neat. https://example.org/cats');
97 | $item->setDatePublished(new DateTime('2016-02-09T14:22:00+02:00'));
98 | static::assertEquals($item, $items[0]);
99 | }
100 |
101 | public function testAuthorsFeed(): void
102 | {
103 | $input = $this->getFixtures('authors');
104 | $reader = FeedReader::create();
105 |
106 | $feed = $reader->readFromJson($input);
107 | static::assertInstanceOf('JDecool\JsonFeed\Feed', $feed);
108 | static::assertEquals('My Example Feed', $feed->getTitle());
109 | static::assertEquals('Global Author', $feed->getAuthor()->getName());
110 | static::assertEquals('https://example.org/feed.json', $feed->getFeedUrl());
111 |
112 | $items = $feed->getItems();
113 | static::assertCount(2, $items);
114 |
115 | $item2Author = new Author('Author 2');
116 | $item2 = new Item('2');
117 | $item2->setUrl('https://example.org/2');
118 | $item2->setContentText('This is a second item.');
119 | $item2->setAuthor($item2Author);
120 | static::assertEquals('Author 2', $item2->getAuthor()->getName());
121 | static::assertEquals($item2, $items[0]);
122 |
123 | $item1Author = new Author('Author 1');
124 | $item1 = new Item('1');
125 | $item1->setUrl('https://example.org/1');
126 | $item1->setContentHtml('This is the first item.
');
127 | $item1->setAuthor($item1Author);
128 | static::assertEquals('Author 1', $item1->getAuthor()->getName());
129 | static::assertEquals($item1, $items[1]);
130 | }
131 |
132 | public function testReaderWithJsonSyntaxError(): void
133 | {
134 | $input = <<expectException(InvalidFeedException::class);
144 | $this->expectExceptionMessage('Invalid JSONFeed string');
145 |
146 | $reader->readFromJson($input);
147 | }
148 |
149 | public function testReaderWithInvalidProperty(): void
150 | {
151 | $input = <<expectException(InvalidFeedException::class);
162 | $this->expectExceptionMessage('Invalid feed property "custom"');
163 |
164 | $reader->readFromJson($input);
165 | }
166 |
167 | public function testReaderWithInvalidAuthorProperty(): void
168 | {
169 | $input = <<expectException(InvalidFeedException::class);
183 | $this->expectExceptionMessage('Invalid author property "foo"');
184 |
185 | $reader->readFromJson($input);
186 | }
187 |
188 | public function testReaderWithInvalidItemProperty(): void
189 | {
190 | $input = <<expectException(InvalidFeedException::class);
210 | $this->expectExceptionMessage('Invalid item property "foo"');
211 |
212 | $reader->readFromJson($input);
213 | }
214 |
215 | public function testReaderWithInvalidPropertyWithErrorEnabled(): void
216 | {
217 | $input = <<readFromJson($input);
228 | static::assertInstanceOf('JDecool\JsonFeed\Feed', $feed);
229 | static::assertEquals('Brent Simmons’s Microblog', $feed->getTitle());
230 | }
231 |
232 | public function testReadExtensions(): void
233 | {
234 | $input = $this->getFixtures('extension');
235 | $reader = FeedReader::create();
236 |
237 | $feed = $reader->readFromJson($input);
238 | static::assertInstanceOf('JDecool\JsonFeed\Feed', $feed);
239 | static::assertEquals('My Example Feed', $feed->getTitle());
240 |
241 | $items = $feed->getItems();
242 | static::assertCount(2, $items);
243 |
244 | $item = new Item('2');
245 | $item->setContentText('This is a second item.');
246 | $item->setUrl('https://example.org/second-item');
247 | $item->addExtension('extItem2', ['foo' => 'value', 'bar' => 'value']);
248 | $item->addExtension('extAuthor', ['john' => 'doe', 'jane' => 'doe']);
249 | static::assertEquals($item, $items[0]);
250 |
251 | $item = new Item('1');
252 | $item->setContentHtml('Hello, world!
');
253 | $item->setUrl('https://example.org/initial-post');
254 | $item->addExtension('extAuthor', ['john' => 'doe', 'jane' => 'doe']);
255 | static::assertEquals($item, $items[1]);
256 | }
257 |
258 | private function getFixtures($name)
259 | {
260 | return file_get_contents(self::$fixturesPath.'/'.$name.'.json');
261 | }
262 | }
263 |
--------------------------------------------------------------------------------
/test/Writer/RendererFactoryTest.php:
--------------------------------------------------------------------------------
1 | createRenderer();
20 | static::assertInstanceOf('JDecool\JsonFeed\Writer\Version1\Renderer', $renderer);
21 | }
22 |
23 | public function testCreateVersion1Renderer(): void
24 | {
25 | $factory = new RendererFactory();
26 |
27 | $renderer = $factory->createRenderer(Versions::VERSION_1);
28 | static::assertInstanceOf('JDecool\JsonFeed\Writer\Version1\Renderer', $renderer);
29 | }
30 |
31 | public function testCreateWrongVersionRenderer(): void
32 | {
33 | $factory = new RendererFactory();
34 |
35 | $this->expectException(RuntimeException::class);
36 | $this->expectExceptionMessage('No renderer registered for version "foo"');
37 |
38 | $factory->createRenderer('foo');
39 | }
40 |
41 | public function testRegisterCustomProvider(): void
42 | {
43 | $customRenderer = $this->getMockBuilder('JDecool\JsonFeed\Writer\RendererInterface')->getMock();
44 | $customRenderer->method('render')
45 | ->willReturn('custom render')
46 | ;
47 |
48 | $factory = new RendererFactory();
49 | $factory->registerRenderer('custom', $customRenderer);
50 |
51 | $renderer = $factory->createRenderer('custom');
52 | static::assertEquals('custom render', $renderer->render(new Feed('My feed')));
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/test/Writer/Version1/RendererTest.php:
--------------------------------------------------------------------------------
1 | setHomepageUrl('https://example.org/');
29 | $feed->setFeedUrl('https://example.org/feed.json');
30 |
31 | $item2 = new Item('2');
32 | $item2->setContentText('This is a second item.');
33 | $item2->setUrl('https://example.org/second-item');
34 | $feed->addItem($item2);
35 |
36 | $item1 = new Item('1');
37 | $item1->setContentHtml('Hello, world!
');
38 | $item1->setUrl('https://example.org/initial-post');
39 | $feed->addItem($item1);
40 |
41 | $expected = $this->getFixtures('simple');
42 |
43 | $render = new Renderer();
44 | static::assertJsonStringEqualsJsonString($expected, $render->render($feed));
45 | }
46 |
47 | public function testPodcastFeed(): void
48 | {
49 | $attachment = new Attachment('http://therecord.co/downloads/The-Record-sp1e1-ChrisParrish.m4a', 'audio/x-m4a');
50 | $attachment->setSize(89970236);
51 | $attachment->setDuration(6629);
52 |
53 | $item = new Item('http://therecord.co/chris-parrish');
54 | $item->setTitle('Special #1 - Chris Parrish');
55 | $item->setUrl('http://therecord.co/chris-parrish');
56 | $item->setContentHtml('Chris has worked at Adobe and as a founder of Rogue Sheep, which won an Apple Design Award for Postage. Chris’s new company is Aged & Distilled with Guy English — which shipped Napkin, a Mac app for visual collaboration. Chris is also the co-host of The Record. He lives on Bainbridge Island, a quick ferry ride from Seattle.');
57 | $item->setContentText('Chris has worked at Adobe and as a founder of Rogue Sheep, which won an Apple Design Award for Postage. Chris’s new company is Aged & Distilled with Guy English — which shipped Napkin, a Mac app for visual collaboration. Chris is also the co-host of The Record. He lives on Bainbridge Island, a quick ferry ride from Seattle.');
58 | $item->setSummary('Brent interviews Chris Parrish, co-host of The Record and one-half of Aged & Distilled.');
59 | $item->setDatePublished(new DateTime('2014-05-09 14:04:00', new DateTimeZone('Etc/GMT+7')));
60 | $item->addAttachment($attachment);
61 |
62 | $feed = new Feed('The Record');
63 | $feed->setUserComment('This is a podcast feed. You can add this feed to your podcast client using the following URL: http://therecord.co/feed.json');
64 | $feed->setHomepageUrl('http://therecord.co/');
65 | $feed->setFeedUrl('http://therecord.co/feed.json');
66 | $feed->addItem($item);
67 |
68 | $expected = $this->getFixtures('podcast');
69 |
70 | $render = new Renderer();
71 | static::assertJsonStringEqualsJsonString($expected, $render->render($feed));
72 | }
73 |
74 | public function testMicroblogFeed(): void
75 | {
76 | $author = new Author('Brent Simmons');
77 | $author->setUrl('http://example.org/');
78 | $author->setAvatar('https://example.org/avatar.png');
79 |
80 | $item = new Item('2347259');
81 | $item->setUrl('https://example.org/2347259');
82 | $item->setDatePublished(new DateTime('2016-02-09 14:22:00', new DateTimeZone('Etc/GMT-2')));
83 | $item->setContentText('Cats are neat. https://example.org/cats');
84 |
85 | $feed = new Feed('Brent Simmons’s Microblog');
86 | $feed->setUserComment('This is a microblog feed. You can add this to your feed reader using the following URL: https://example.org/feed.json');
87 | $feed->setHomepageUrl('https://example.org/');
88 | $feed->setFeedUrl('https://example.org/feed.json');
89 | $feed->setAuthor($author);
90 | $feed->addItem($item);
91 |
92 | $expected = $this->getFixtures('microblog');
93 |
94 | $render = new Renderer();
95 | static::assertJsonStringEqualsJsonString($expected, $render->render($feed));
96 | }
97 |
98 | public function testAuthorsFeed(): void
99 | {
100 | $feedAuthor = new Author('Global Author');
101 | $feed = new Feed('My Example Feed');
102 | $feed->setFeedUrl('https://example.org/feed.json');
103 | $feed->setAuthor($feedAuthor);
104 |
105 | $item2Author = new Author('Author 2');
106 | $item2 = new Item('2');
107 | $item2->setUrl('https://example.org/2');
108 | $item2->setContentText('This is a second item.');
109 | $item2->setAuthor($item2Author);
110 | $feed->addItem($item2);
111 |
112 | $item1Author = new Author('Author 1');
113 | $item1 = new Item('1');
114 | $item1->setUrl('https://example.org/1');
115 | $item1->setContentHtml('This is the first item.
');
116 | $item1->setAuthor($item1Author);
117 | $feed->addItem($item1);
118 |
119 | $expected = $this->getFixtures('authors');
120 |
121 | $render = new Renderer();
122 | static::assertJsonStringEqualsJsonString($expected, $render->render($feed));
123 | }
124 |
125 | public function testRenderExtension(): void
126 | {
127 | $feed = new Feed('My Example Feed');
128 |
129 | $item2 = new Item('2');
130 | $item2->setContentText('This is a second item.');
131 | $item2->setUrl('https://example.org/second-item');
132 | $item2->addExtension('extItem2', ['foo' => 'value', 'bar' => 'value']);
133 | $item2->addExtension('extAuthor', ['john' => 'doe', 'jane' => 'doe']);
134 | $feed->addItem($item2);
135 |
136 | $item1 = new Item('1');
137 | $item1->setContentHtml('Hello, world!
');
138 | $item1->setUrl('https://example.org/initial-post');
139 | $item1->addExtension('extAuthor', ['john' => 'doe', 'jane' => 'doe']);
140 | $feed->addItem($item1);
141 |
142 | $expected = $this->getFixtures('extension');
143 |
144 | $render = new Renderer();
145 | static::assertJsonStringEqualsJsonString($expected, $render->render($feed));
146 | }
147 |
148 | private function getFixtures($name)
149 | {
150 | return file_get_contents(self::$fixturesPath.'/'.$name.'.json');
151 | }
152 | }
153 |
--------------------------------------------------------------------------------