diff options
Diffstat (limited to 'formats')
-rw-r--r-- | formats/AtomFormat.php | 97 | ||||
-rw-r--r-- | formats/HtmlFormat.php | 30 | ||||
-rw-r--r-- | formats/JsonFormat.php | 41 | ||||
-rw-r--r-- | formats/MrssFormat.php | 134 |
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 = '<br>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> |