summaryrefslogtreecommitdiff
path: root/formats
diff options
context:
space:
mode:
Diffstat (limited to 'formats')
-rw-r--r--formats/AtomFormat.php97
-rw-r--r--formats/HtmlFormat.php30
-rw-r--r--formats/JsonFormat.php41
-rw-r--r--formats/MrssFormat.php134
4 files changed, 212 insertions, 90 deletions
diff --git a/formats/AtomFormat.php b/formats/AtomFormat.php
index bb5e30e..1159a61 100644
--- a/formats/AtomFormat.php
+++ b/formats/AtomFormat.php
@@ -1,21 +1,30 @@
<?php
/**
-* Atom
-* Documentation Source http://en.wikipedia.org/wiki/Atom_%28standard%29 and
-* http://tools.ietf.org/html/rfc4287
-*/
+ * AtomFormat - RFC 4287: The Atom Syndication Format
+ * https://tools.ietf.org/html/rfc4287
+ *
+ * Validator:
+ * https://validator.w3.org/feed/
+ */
class AtomFormat extends FormatAbstract{
+ const LIMIT_TITLE = 140;
+
public function stringify(){
- $https = isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on' ? 's' : '';
- $httpHost = isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : '';
- $httpInfo = isset($_SERVER['PATH_INFO']) ? $_SERVER['PATH_INFO'] : '';
+ $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'] : '';
- $serverRequestUri = isset($_SERVER['REQUEST_URI']) ? $this->xml_encode($_SERVER['REQUEST_URI']) : '';
+ $feedUrl = $this->xml_encode($urlPrefix . $urlHost . $urlRequest);
$extraInfos = $this->getExtraInfos();
$title = $this->xml_encode($extraInfos['name']);
$uri = !empty($extraInfos['uri']) ? $extraInfos['uri'] : REPOSITORY;
+ // since we can't guarantee that all items have an author,
+ // a global feed author is mandatory
+ $feedAuthor = 'RSS-Bridge';
+
$uriparts = parse_url($uri);
if(!empty($extraInfos['icon'])) {
$icon = $extraInfos['icon'];
@@ -27,11 +36,40 @@ class AtomFormat extends FormatAbstract{
$entries = '';
foreach($this->getItems() as $item) {
+ $entryTimestamp = $item->getTimestamp();
+ $entryTitle = $item->getTitle();
+ $entryContent = $item->getContent();
+ $entryUri = $item->getURI();
+ $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))
+ $entryTimestamp = $this->lastModified;
+
+ if (empty($entryTitle)) {
+ $entryTitle = str_replace("\n", ' ', strip_tags($entryContent));
+ if (strlen($entryTitle) > self::LIMIT_TITLE) {
+ $wrapPos = strpos(wordwrap($entryTitle, self::LIMIT_TITLE), "\n");
+ $entryTitle = substr($entryTitle, 0, $wrapPos) . '...';
+ }
+ }
+
+ if (empty($entryContent))
+ $entryContent = $entryTitle;
+
$entryAuthor = $this->xml_encode($item->getAuthor());
- $entryTitle = $this->xml_encode($item->getTitle());
- $entryUri = $this->xml_encode($item->getURI());
- $entryTimestamp = $this->xml_encode(date(DATE_ATOM, $item->getTimestamp()));
- $entryContent = $this->xml_encode($this->sanitizeHtml($item->getContent()));
+ $entryTitle = $this->xml_encode($entryTitle);
+ $entryUri = $this->xml_encode($entryUri);
+ $entryTimestamp = $this->xml_encode(gmdate(DATE_ATOM, $entryTimestamp));
+ $entryContent = $this->xml_encode($this->sanitizeHtml($entryContent));
$entryEnclosures = '';
foreach($item->getEnclosures() as $enclosure) {
@@ -49,16 +87,28 @@ class AtomFormat extends FormatAbstract{
. PHP_EOL;
}
+ $entryLinkAlternate = '';
+ if (!empty($entryUri)) {
+ $entryLinkAlternate = '<link rel="alternate" type="text/html" href="'
+ . $entryUri
+ . '"/>';
+ }
+
+ if (!empty($entryAuthor)) {
+ $entryAuthor = '<author><name>'
+ . $entryAuthor
+ . '</name></author>';
+ }
+
$entries .= <<<EOD
<entry>
- <author>
- <name>{$entryAuthor}</name>
- </author>
<title type="html">{$entryTitle}</title>
- <link rel="alternate" type="text/html" href="{$entryUri}" />
- <id>{$entryUri}</id>
+ <published>{$entryTimestamp}</published>
<updated>{$entryTimestamp}</updated>
+ <id>{$entryID}</id>
+ {$entryLinkAlternate}
+ {$entryAuthor}
<content type="html">{$entryContent}</content>
{$entryEnclosures}
{$entryCategories}
@@ -67,21 +117,24 @@ class AtomFormat extends FormatAbstract{
EOD;
}
- $feedTimestamp = date(DATE_ATOM, time());
- $charset = $this->getCharset();
+ $feedTimestamp = gmdate(DATE_ATOM, $this->lastModified);
+ $charset = $this->getCharset();
/* Data are prepared, now let's begin the "MAGIE !!!" */
$toReturn = <<<EOD
<?xml version="1.0" encoding="{$charset}"?>
-<feed xmlns="http://www.w3.org/2005/Atom" xmlns:thr="http://purl.org/syndication/thread/1.0">
+<feed xmlns="http://www.w3.org/2005/Atom">
<title type="text">{$title}</title>
- <id>http{$https}://{$httpHost}{$httpInfo}/</id>
+ <id>{$feedUrl}</id>
<icon>{$icon}</icon>
<logo>{$icon}</logo>
<updated>{$feedTimestamp}</updated>
+ <author>
+ <name>{$feedAuthor}</name>
+ </author>
<link rel="alternate" type="text/html" href="{$uri}" />
- <link rel="self" href="http{$https}://{$httpHost}{$serverRequestUri}" />
+ <link rel="self" type="application/atom+xml" href="{$feedUrl}" />
{$entries}
</feed>
EOD;
diff --git a/formats/HtmlFormat.php b/formats/HtmlFormat.php
index 052bedc..ebb6b78 100644
--- a/formats/HtmlFormat.php
+++ b/formats/HtmlFormat.php
@@ -4,8 +4,21 @@ class HtmlFormat extends FormatAbstract {
$extraInfos = $this->getExtraInfos();
$title = htmlspecialchars($extraInfos['name']);
$uri = htmlspecialchars($extraInfos['uri']);
- $atomquery = str_replace('format=Html', 'format=Atom', htmlentities($_SERVER['QUERY_STRING']));
- $mrssquery = str_replace('format=Html', 'format=Mrss', htmlentities($_SERVER['QUERY_STRING']));
+
+ // Dynamically build buttons for all formats (except HTML)
+ $formatFac = new FormatFactory();
+ $formatFac->setWorkingDir(PATH_LIB_FORMATS);
+
+ $buttons = '';
+
+ foreach($formatFac->getFormatNames() as $format) {
+ if(strcasecmp($format, 'HTML') === 0) {
+ continue;
+ }
+
+ $query = str_replace('format=Html', 'format=' . $format, htmlentities($_SERVER['QUERY_STRING']));
+ $buttons .= $this->buildButton($format, $query) . PHP_EOL;
+ }
$entries = '';
foreach($this->getItems() as $item) {
@@ -82,18 +95,17 @@ EOD;
<html>
<head>
<meta charset="{$charset}">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>{$title}</title>
<link href="static/HtmlFormat.css" rel="stylesheet">
- <link rel="alternate" type="application/atom+xml" title="Atom" href="./?{$atomquery}" />
- <link rel="alternate" type="application/rss+xml" title="RSS" href="/?{$mrssquery}" />
+ <link rel="icon" type="image/png" href="static/favicon.png">
<meta name="robots" content="noindex, follow">
</head>
<body>
<h1 class="pagetitle"><a href="{$uri}" target="_blank">{$title}</a></h1>
<div class="buttons">
<a href="./#bridge-{$_GET['bridge']}"><button class="backbutton">← back to rss-bridge</button></a>
- <a href="./?{$atomquery}"><button class="rss-feed">RSS feed (ATOM)</button></a>
- <a href="./?{$mrssquery}"><button class="rss-feed">RSS feed (MRSS)</button></a>
+ {$buttons}
</div>
{$entries}
</body>
@@ -113,4 +125,10 @@ EOD;
return parent::display();
}
+
+ private function buildButton($format, $query) {
+ return <<<EOD
+<a href="./?{$query}"><button class="rss-feed">{$format}</button></a>
+EOD;
+ }
}
diff --git a/formats/JsonFormat.php b/formats/JsonFormat.php
index fafe7a5..5d09162 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/formats/MrssFormat.php b/formats/MrssFormat.php
index 34b9a92..836a361 100644
--- a/formats/MrssFormat.php
+++ b/formats/MrssFormat.php
@@ -1,18 +1,45 @@
<?php
/**
-* Mrss
-* Documentation Source http://www.rssboard.org/media-rss
-*/
+ * MrssFormat - RSS 2.0 + Media RSS
+ * http://www.rssboard.org/rss-specification
+ * http://www.rssboard.org/media-rss
+ *
+ * Validators:
+ * https://validator.w3.org/feed/
+ * http://www.rssboard.org/rss-validator/
+ *
+ * Notes about the implementation:
+ *
+ * - The item author is not supported as it needs to be an e-mail address to be
+ * valid.
+ * - The RSS specification does not explicitly allow to have more than one
+ * enclosure as every item is meant to provide one "story", thus having
+ * multiple enclosures per item may lead to unexpected behavior.
+ * On top of that, it requires to have a length specified, which RSS-Bridge
+ * can't provide.
+ * - The Media RSS extension comes in handy, since it allows to have multiple
+ * enclosures, even though they recommend to have only one enclosure because
+ * of the one-story-per-item reason. It only requires to specify the URL,
+ * everything else is optional.
+ * - Since the Media RSS extension has its own namespace, the output is a valid
+ * RSS 2.0 feed that works with feed readers that don't support the extension.
+ */
class MrssFormat extends FormatAbstract {
+ const ALLOWED_IMAGE_EXT = array(
+ '.gif', '.jpg', '.png'
+ );
+
public function stringify(){
- $https = isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on' ? 's' : '';
- $httpHost = isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : '';
- $httpInfo = isset($_SERVER['PATH_INFO']) ? $_SERVER['PATH_INFO'] : '';
+ $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'] : '';
- $serverRequestUri = isset($_SERVER['REQUEST_URI']) ? $this->xml_encode($_SERVER['REQUEST_URI']) : '';
+ $feedUrl = $this->xml_encode($urlPrefix . $urlHost . $urlRequest);
$extraInfos = $this->getExtraInfos();
$title = $this->xml_encode($extraInfos['name']);
+ $icon = $extraInfos['icon'];
if(!empty($extraInfos['uri'])) {
$uri = $this->xml_encode($extraInfos['uri']);
@@ -20,34 +47,48 @@ class MrssFormat extends FormatAbstract {
$uri = REPOSITORY;
}
- $uriparts = parse_url($uri);
- $icon = $this->xml_encode($uriparts['scheme'] . '://' . $uriparts['host'] . '/favicon.ico');
-
$items = '';
foreach($this->getItems() as $item) {
- $itemAuthor = $this->xml_encode($item->getAuthor());
+ $itemTimestamp = $item->getTimestamp();
$itemTitle = $this->xml_encode($item->getTitle());
$itemUri = $this->xml_encode($item->getURI());
- $itemTimestamp = $this->xml_encode(date(DATE_RFC2822, $item->getTimestamp()));
$itemContent = $this->xml_encode($this->sanitizeHtml($item->getContent()));
+ $entryID = $item->getUid();
+ $isPermaLink = 'false';
+
+ if (empty($entryID) && !empty($itemUri)) { // Fallback to provided URI
+ $entryID = $itemUri;
+ $isPermaLink = 'true';
+ }
+
+ if (empty($entryID)) // Fallback to title and content
+ $entryID = hash('sha1', $itemTitle . $itemContent);
+
+ $entryTitle = '';
+ if (!empty($itemTitle))
+ $entryTitle = '<title>' . $itemTitle . '</title>';
+
+ $entryLink = '';
+ if (!empty($itemUri))
+ $entryLink = '<link>' . $itemUri . '</link>';
+
+ $entryPublished = '';
+ if (!empty($itemTimestamp)) {
+ $entryPublished = '<pubDate>'
+ . $this->xml_encode(gmdate(DATE_RFC2822, $itemTimestamp))
+ . '</pubDate>';
+ }
+
+ $entryDescription = '';
+ if (!empty($itemContent))
+ $entryDescription = '<description>' . $itemContent . '</description>';
- $entryEnclosuresWarning = '';
$entryEnclosures = '';
- if(!empty($item->getEnclosures())) {
- $entryEnclosures .= '<enclosure url="'
- . $this->xml_encode($item->getEnclosures()[0])
- . '" type="' . getMimeType($item->getEnclosures()[0]) . '" />';
-
- if(count($item->getEnclosures()) > 1) {
- $entryEnclosures .= PHP_EOL;
- $entryEnclosuresWarning = '&lt;br&gt;Warning:
-Some media files might not be shown to you. Consider using the ATOM format instead!';
- foreach($item->getEnclosures() as $enclosure) {
- $entryEnclosures .= '<atom:link rel="enclosure" href="'
- . $enclosure . '" type="' . getMimeType($enclosure) . '" />'
- . PHP_EOL;
- }
- }
+ foreach($item->getEnclosures() as $enclosure) {
+ $entryEnclosures .= '<media:content url="'
+ . $this->xml_encode($enclosure)
+ . '" type="' . getMimeType($enclosure) . '"/>'
+ . PHP_EOL;
}
$entryCategories = '';
@@ -60,12 +101,11 @@ Some media files might not be shown to you. Consider using the ATOM format inste
$items .= <<<EOD
<item>
- <title>{$itemTitle}</title>
- <link>{$itemUri}</link>
- <guid isPermaLink="true">{$itemUri}</guid>
- <pubDate>{$itemTimestamp}</pubDate>
- <description>{$itemContent}{$entryEnclosuresWarning}</description>
- <author>{$itemAuthor}</author>
+ {$entryTitle}
+ {$entryLink}
+ <guid isPermaLink="{$isPermaLink}">{$entryID}</guid>
+ {$entryPublished}
+ {$entryDescription}
{$entryEnclosures}
{$entryCategories}
</item>
@@ -75,22 +115,28 @@ EOD;
$charset = $this->getCharset();
- /* xml attributes need to have certain characters escaped to be w3c compliant */
- $imageTitle = htmlspecialchars($title, ENT_COMPAT);
+ $feedImage = '';
+ if (!empty($icon) && in_array(substr($icon, -4), self::ALLOWED_IMAGE_EXT)) {
+ $feedImage .= <<<EOD
+ <image>
+ <url>{$icon}</url>
+ <title>{$title}</title>
+ <link>{$uri}</link>
+ </image>
+EOD;
+ }
+
/* Data are prepared, now let's begin the "MAGIE !!!" */
$toReturn = <<<EOD
<?xml version="1.0" encoding="{$charset}"?>
-<rss version="2.0"
-xmlns:dc="http://purl.org/dc/elements/1.1/"
-xmlns:media="http://search.yahoo.com/mrss/"
-xmlns:atom="http://www.w3.org/2005/Atom">
+<rss version="2.0" xmlns:media="http://search.yahoo.com/mrss/" xmlns:atom="http://www.w3.org/2005/Atom">
<channel>
<title>{$title}</title>
- <link>http{$https}://{$httpHost}{$httpInfo}/</link>
+ <link>{$uri}</link>
<description>{$title}</description>
- <image url="{$icon}" title="{$imageTitle}" link="{$uri}"/>
- <atom:link rel="alternate" type="text/html" href="{$uri}" />
- <atom:link rel="self" href="http{$https}://{$httpHost}{$serverRequestUri}" />
+ {$feedImage}
+ <atom:link rel="alternate" type="text/html" href="{$uri}"/>
+ <atom:link rel="self" href="{$feedUrl}" type="application/atom+xml"/>
{$items}
</channel>
</rss>