From 394149b1147d8e5e4339635b21f8627e375d22ac Mon Sep 17 00:00:00 2001 From: LogMANOriginal Date: Sun, 3 Feb 2019 20:56:41 +0100 Subject: [PATCH] core: Add item uid (#1017) 'uid' represents the unique id for a feed item. This item is null by default and can be set to any string value. The provided string value is always hashed to sha1 to make it the same length in all cases. References #977, #1005 --- formats/AtomFormat.php | 11 +++-- formats/JsonFormat.php | 41 +++++++++++-------- lib/FeedItem.php | 37 +++++++++++++++++ .../expectedAtomFormat/feed.common.xml | 2 +- .../expectedJsonFormat/feed.common.json | 2 +- tests/samples/feed.common.json | 1 + 6 files changed, 71 insertions(+), 23 deletions(-) diff --git a/formats/AtomFormat.php b/formats/AtomFormat.php index 1e01a0ff..02d7d61a 100644 --- a/formats/AtomFormat.php +++ b/formats/AtomFormat.php @@ -40,10 +40,15 @@ class AtomFormat extends FormatAbstract{ $entryTitle = $this->xml_encode($item->getTitle()); $entryContent = $item->getContent(); $entryUri = $item->getURI(); + $entryID = ''; - // the item id must be a valid unique URI - $entryID = $this->xml_encode($entryUri); - if (empty($entryID)) + if (!empty($item->getUid())) + $entryID = 'urn:sha1:' . $item->getUid(); + + if (empty($entryID)) // Fallback to provided URI + $entryID = $this->xml_encode($entryUri); + + if (empty($entryID)) // Fallback to title and content $entryID = 'urn:sha1:' . hash('sha1', $entryTitle . $entryContent); if (empty($entryTimestamp)) diff --git a/formats/JsonFormat.php b/formats/JsonFormat.php index fafe7a5a..5d091628 100644 --- a/formats/JsonFormat.php +++ b/formats/JsonFormat.php @@ -16,21 +16,22 @@ class JsonFormat extends FormatAbstract { 'content', 'enclosures', 'categories', + 'uid', ); public function stringify(){ - $urlScheme = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on') ? 'https://' : 'http://'; - $urlHost = (isset($_SERVER['HTTP_HOST'])) ? $_SERVER['HTTP_HOST'] : ''; - $urlPath = (isset($_SERVER['PATH_INFO'])) ? $_SERVER['PATH_INFO'] : ''; - $urlRequest = (isset($_SERVER['REQUEST_URI'])) ? $_SERVER['REQUEST_URI'] : ''; + $urlPrefix = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on') ? 'https://' : 'http://'; + $urlHost = (isset($_SERVER['HTTP_HOST'])) ? $_SERVER['HTTP_HOST'] : ''; + $urlPath = (isset($_SERVER['PATH_INFO'])) ? $_SERVER['PATH_INFO'] : ''; + $urlRequest = (isset($_SERVER['REQUEST_URI'])) ? $_SERVER['REQUEST_URI'] : ''; $extraInfos = $this->getExtraInfos(); $data = array( - 'version' => 'https://jsonfeed.org/version/1', - 'title' => (!empty($extraInfos['name'])) ? $extraInfos['name'] : $urlHost, - 'home_page_url' => (!empty($extraInfos['uri'])) ? $extraInfos['uri'] : REPOSITORY, - 'feed_url' => $urlScheme . $urlHost . $urlRequest + 'version' => 'https://jsonfeed.org/version/1', + 'title' => (!empty($extraInfos['name'])) ? $extraInfos['name'] : $urlHost, + 'home_page_url' => (!empty($extraInfos['uri'])) ? $extraInfos['uri'] : REPOSITORY, + 'feed_url' => $urlPrefix . $urlHost . $urlRequest ); if (!empty($extraInfos['icon'])) { @@ -42,20 +43,24 @@ class JsonFormat extends FormatAbstract { foreach ($this->getItems() as $item) { $entry = array(); - $entryAuthor = $item->getAuthor(); - $entryTitle = $item->getTitle(); - $entryUri = $item->getURI(); - $entryTimestamp = $item->getTimestamp(); - $entryContent = $this->sanitizeHtml($item->getContent()); - $entryEnclosures = $item->getEnclosures(); - $entryCategories = $item->getCategories(); + $entryAuthor = $item->getAuthor(); + $entryTitle = $item->getTitle(); + $entryUri = $item->getURI(); + $entryTimestamp = $item->getTimestamp(); + $entryContent = $this->sanitizeHtml($item->getContent()); + $entryEnclosures = $item->getEnclosures(); + $entryCategories = $item->getCategories(); $vendorFields = $item->toArray(); foreach (self::VENDOR_EXCLUDES as $key) { unset($vendorFields[$key]); } - $entry['id'] = $entryUri; + $entry['id'] = $item->getUid(); + + if (empty($entry['id'])) { + $entry['id'] = $entryUri; + } if (!empty($entryTitle)) { $entry['title'] = $entryTitle; @@ -82,8 +87,8 @@ class JsonFormat extends FormatAbstract { $entry['attachments'] = array(); foreach ($entryEnclosures as $enclosure) { $entry['attachments'][] = array( - 'url' => $enclosure, - 'mime_type' => getMimeType($enclosure) + 'url' => $enclosure, + 'mime_type' => getMimeType($enclosure) ); } } diff --git a/lib/FeedItem.php b/lib/FeedItem.php index 2812da6b..aedf615a 100644 --- a/lib/FeedItem.php +++ b/lib/FeedItem.php @@ -55,6 +55,9 @@ class FeedItem { /** @var array List of category names or tags */ protected $categories = array(); + /** @var string Unique ID for the current item */ + protected $uid = null; + /** @var array Associative list of additional parameters */ protected $misc = array(); // Custom parameters @@ -391,6 +394,37 @@ class FeedItem { return $this; } + /** + * Get unique id + * + * Use {@see FeedItem::setUid()} to set the unique id. + * + * @param string The unique id. + */ + public function getUid() { + return $this->uid; + } + + /** + * Set unique id. + * + * Use {@see FeedItem::getUid()} to get the unique id. + * + * @param string $uid A string that uniquely identifies the current item + * @return self + */ + public function setUid($uid) { + $this->uid = null; // Clear previous data + + if(!is_string($uid)) { + Debug::log('Unique id must be a string!'); + } else { + $this->uid = sha1($uid); + } + + return $this; + } + /** * Add miscellaneous elements to the item. * @@ -426,6 +460,7 @@ class FeedItem { 'content' => $this->content, 'enclosures' => $this->enclosures, 'categories' => $this->categories, + 'uid' => $this->uid, ), $this->misc ); } @@ -454,6 +489,7 @@ class FeedItem { case 'content': $this->setContent($value); break; case 'enclosures': $this->setEnclosures($value); break; case 'categories': $this->setCategories($value); break; + case 'uid': $this->setUid($value); break; default: $this->addMisc($name, $value); } } @@ -476,6 +512,7 @@ class FeedItem { case 'content': return $this->getContent(); case 'enclosures': return $this->getEnclosures(); case 'categories': return $this->getCategories(); + case 'uid': return $this->getUid(); default: if(array_key_exists($name, $this->misc)) return $this->misc[$name]; diff --git a/tests/samples/expectedAtomFormat/feed.common.xml b/tests/samples/expectedAtomFormat/feed.common.xml index 0d696c03..80cb0df4 100644 --- a/tests/samples/expectedAtomFormat/feed.common.xml +++ b/tests/samples/expectedAtomFormat/feed.common.xml @@ -57,7 +57,7 @@ Atom draft-07 snapshot 2005-07-31T12:29:29+00:00 2005-07-31T12:29:29+00:00 - http://example.org/2005/04/02/atom + urn:sha1:dd6b6c920d3b340ab9e07faf6682f2a7c4f70134 Mark Pilgrim diff --git a/tests/samples/expectedJsonFormat/feed.common.json b/tests/samples/expectedJsonFormat/feed.common.json index 5a0e1b5a..0b3e97b0 100644 --- a/tests/samples/expectedJsonFormat/feed.common.json +++ b/tests/samples/expectedJsonFormat/feed.common.json @@ -26,7 +26,7 @@ }, "content_html": "

We — Manton Reece and Brent Simmons — have noticed that JSON has become the developers’ choice for APIs, and that developers will often go out of their way to avoid XML. JSON is simpler to read and write, and it’s less prone to bugs.

\n\n

So we developed JSON Feed, a format similar to RSS and Atom but in JSON. It reflects the lessons learned from our years of work reading and publishing feeds.

\n\n

See the spec. It’s at version 1, which may be the only version ever needed. If future versions are needed, version 1 feeds will still be valid feeds.

\n\n

Notes

\n\n

We have a WordPress plugin and, coming soon, a JSON Feed Parser for Swift. As more code is written, by us and others, we’ll update the code page.

\n\n

See Mapping RSS and Atom to JSON Feed for more on the similarities between the formats.

\n\n

This website — the Markdown files and supporting resources — is up on GitHub, and you’re welcome to comment there.

\n\n

This website is also a blog, and you can subscribe to the RSS feed or the JSON feed (if your reader supports it).

\n\n

We worked with a number of people on this over the course of several months. We list them, and thank them, at the bottom of the spec. But — most importantly — Craig Hockenberry spent a little time making it look pretty. :)

" },{ - "id": "http://example.org/2005/04/02/atom", + "id": "dd6b6c920d3b340ab9e07faf6682f2a7c4f70134", "url": "http://example.org/2005/04/02/atom", "title": "Atom draft-07 snapshot", "date_modified": "2005-07-31T12:29:29+00:00", diff --git a/tests/samples/feed.common.json b/tests/samples/feed.common.json index be0c56a9..080ba01a 100644 --- a/tests/samples/feed.common.json +++ b/tests/samples/feed.common.json @@ -25,6 +25,7 @@ "content": "

We — Manton Reece and Brent Simmons — have noticed that JSON has become the developers’ choice for APIs, and that developers will often go out of their way to avoid XML. JSON is simpler to read and write, and it’s less prone to bugs.

\n\n

So we developed JSON Feed, a format similar to RSS and Atom but in JSON. It reflects the lessons learned from our years of work reading and publishing feeds.

\n\n

See the spec. It’s at version 1, which may be the only version ever needed. If future versions are needed, version 1 feeds will still be valid feeds.

\n\n

Notes

\n\n

We have a WordPress plugin and, coming soon, a JSON Feed Parser for Swift. As more code is written, by us and others, we’ll update the code page.

\n\n

See Mapping RSS and Atom to JSON Feed for more on the similarities between the formats.

\n\n

This website — the Markdown files and supporting resources — is up on GitHub, and you’re welcome to comment there.

\n\n

This website is also a blog, and you can subscribe to the RSS feed or the JSON feed (if your reader supports it).

\n\n

We worked with a number of people on this over the course of several months. We list them, and thank them, at the bottom of the spec. But — most importantly — Craig Hockenberry spent a little time making it look pretty. :)

" },{ "uri": "http://example.org/2005/04/02/atom", + "uid": "tag:example.org,2003:3.2397", "title": "Atom draft-07 snapshot", "timestamp": 1122812969, "author": "Mark Pilgrim",