From 04e8493822dfefcfe072b4598e1dfee37941db42 Mon Sep 17 00:00:00 2001 From: Timo Tijhof Date: Sat, 13 Apr 2019 23:56:06 +0100 Subject: [PATCH] changes: Split Feed.php into a class per file Change-Id: I1f11a52871dcb249b3ba790e484dd10fe8bb049f --- .phpcs.xml | 2 - autoload.php | 8 +- includes/Feed.php | 494 ------------------------------- includes/changes/AtomFeed.php | 111 +++++++ includes/changes/ChannelFeed.php | 129 ++++++++ includes/changes/FeedItem.php | 222 ++++++++++++++ includes/changes/RSSFeed.php | 93 ++++++ 7 files changed, 559 insertions(+), 500 deletions(-) delete mode 100644 includes/Feed.php create mode 100644 includes/changes/AtomFeed.php create mode 100644 includes/changes/ChannelFeed.php create mode 100644 includes/changes/FeedItem.php create mode 100644 includes/changes/RSSFeed.php diff --git a/.phpcs.xml b/.phpcs.xml index 170e16d4cb..df80740725 100644 --- a/.phpcs.xml +++ b/.phpcs.xml @@ -70,7 +70,6 @@ Whitelist existing violations, but enable the sniff to prevent any new occurrences. --> - */includes/Feed\.php */includes/installer/PhpBugTests\.php */includes/specials/SpecialMostinterwikis\.php */includes/compat/XMPReader\.php @@ -212,7 +211,6 @@ */includes/api/ApiRsd\.php */includes/compat/XMPReader\.php */includes/diff/DairikiDiff\.php - */includes/Feed\.php */includes/filerepo/file/LocalFile\.php */includes/htmlform/HTMLFormElement\.php */includes/libs/filebackend/FileBackendStore\.php diff --git a/autoload.php b/autoload.php index a74a0b8b34..e8ae399728 100644 --- a/autoload.php +++ b/autoload.php @@ -161,7 +161,7 @@ $wgAutoloadLocalClasses = [ 'ArrayUtils' => __DIR__ . '/includes/libs/ArrayUtils.php', 'Article' => __DIR__ . '/includes/page/Article.php', 'AssembleUploadChunksJob' => __DIR__ . '/includes/jobqueue/jobs/AssembleUploadChunksJob.php', - 'AtomFeed' => __DIR__ . '/includes/Feed.php', + 'AtomFeed' => __DIR__ . '/includes/changes/AtomFeed.php', 'AtomicSectionUpdate' => __DIR__ . '/includes/deferred/AtomicSectionUpdate.php', 'AttachLatest' => __DIR__ . '/maintenance/attachLatest.php', 'AugmentPageProps' => __DIR__ . '/includes/search/AugmentPageProps.php', @@ -253,7 +253,7 @@ $wgAutoloadLocalClasses = [ 'ChangesListSpecialPage' => __DIR__ . '/includes/specialpage/ChangesListSpecialPage.php', 'ChangesListStringOptionsFilter' => __DIR__ . '/includes/changes/ChangesListStringOptionsFilter.php', 'ChangesListStringOptionsFilterGroup' => __DIR__ . '/includes/changes/ChangesListStringOptionsFilterGroup.php', - 'ChannelFeed' => __DIR__ . '/includes/Feed.php', + 'ChannelFeed' => __DIR__ . '/includes/changes/ChannelFeed.php', 'CheckBadRedirects' => __DIR__ . '/maintenance/checkBadRedirects.php', 'CheckComposerLockUpToDate' => __DIR__ . '/maintenance/checkComposerLockUpToDate.php', 'CheckExtensionsCLI' => __DIR__ . '/maintenance/language/checkLanguage.inc', @@ -498,7 +498,7 @@ $wgAutoloadLocalClasses = [ 'FatalError' => __DIR__ . '/includes/exception/FatalError.php', 'FauxRequest' => __DIR__ . '/includes/FauxRequest.php', 'FauxResponse' => __DIR__ . '/includes/FauxResponse.php', - 'FeedItem' => __DIR__ . '/includes/Feed.php', + 'FeedItem' => __DIR__ . '/includes/changes/FeedItem.php', 'FeedUtils' => __DIR__ . '/includes/FeedUtils.php', 'FetchText' => __DIR__ . '/maintenance/fetchText.php', 'FewestrevisionsPage' => __DIR__ . '/includes/specials/SpecialFewestrevisions.php', @@ -1184,7 +1184,7 @@ $wgAutoloadLocalClasses = [ 'RCFeedEngine' => __DIR__ . '/includes/rcfeed/RCFeedEngine.php', 'RCFeedFormatter' => __DIR__ . '/includes/rcfeed/RCFeedFormatter.php', 'RESTBagOStuff' => __DIR__ . '/includes/libs/objectcache/RESTBagOStuff.php', - 'RSSFeed' => __DIR__ . '/includes/Feed.php', + 'RSSFeed' => __DIR__ . '/includes/changes/RSSFeed.php', 'RandomPage' => __DIR__ . '/includes/specials/SpecialRandompage.php', 'RangeChronologicalPager' => __DIR__ . '/includes/pager/RangeChronologicalPager.php', 'RangeDifference' => __DIR__ . '/includes/diff/RangeDifference.php', diff --git a/includes/Feed.php b/includes/Feed.php deleted file mode 100644 index 86e9bee69d..0000000000 --- a/includes/Feed.php +++ /dev/null @@ -1,494 +0,0 @@ - - * https://www.mediawiki.org/ - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * http://www.gnu.org/copyleft/gpl.html - * - * @file - */ - -/** - * @defgroup Feed Feed - */ - -/** - * A base class for basic support for outputting syndication feeds in RSS and other formats. - * - * @ingroup Feed - */ -class FeedItem { - /** @var Title */ - public $title; - - public $description; - - public $url; - - public $date; - - public $author; - - public $uniqueId; - - public $comments; - - public $rssIsPermalink = false; - - /** - * @param string|Title $title Item's title - * @param string $description - * @param string $url URL uniquely designating the item. - * @param string $date Item's date - * @param string $author Author's user name - * @param string $comments - */ - function __construct( $title, $description, $url, $date = '', $author = '', $comments = '' ) { - $this->title = $title; - $this->description = $description; - $this->url = $url; - $this->uniqueId = $url; - $this->date = $date; - $this->author = $author; - $this->comments = $comments; - } - - /** - * Encode $string so that it can be safely embedded in a XML document - * - * @param string $string String to encode - * @return string - */ - public function xmlEncode( $string ) { - $string = str_replace( "\r\n", "\n", $string ); - $string = preg_replace( '/[\x00-\x08\x0b\x0c\x0e-\x1f]/', '', $string ); - return htmlspecialchars( $string ); - } - - /** - * Get the unique id of this item; already xml-encoded - * @return string - */ - public function getUniqueID() { - $id = $this->getUniqueIdUnescaped(); - if ( $id ) { - return $this->xmlEncode( $id ); - } - } - - /** - * Get the unique id of this item, without any escaping - * @return string - */ - public function getUniqueIdUnescaped() { - if ( $this->uniqueId ) { - return wfExpandUrl( $this->uniqueId, PROTO_CURRENT ); - } - } - - /** - * Set the unique id of an item - * - * @param string $uniqueId Unique id for the item - * @param bool $rssIsPermalink Set to true if the guid (unique id) is a permalink (RSS feeds only) - */ - public function setUniqueId( $uniqueId, $rssIsPermalink = false ) { - $this->uniqueId = $uniqueId; - $this->rssIsPermalink = $rssIsPermalink; - } - - /** - * Get the title of this item; already xml-encoded - * - * @return string - */ - public function getTitle() { - return $this->xmlEncode( $this->title ); - } - - /** - * Get the URL of this item; already xml-encoded - * - * @return string - */ - public function getUrl() { - return $this->xmlEncode( $this->url ); - } - - /** Get the URL of this item without any escaping - * - * @return string - */ - public function getUrlUnescaped() { - return $this->url; - } - - /** - * Get the description of this item; already xml-encoded - * - * @return string - */ - public function getDescription() { - return $this->xmlEncode( $this->description ); - } - - /** - * Get the description of this item without any escaping - * - * @return string - */ - public function getDescriptionUnescaped() { - return $this->description; - } - - /** - * Get the language of this item - * - * @return string - */ - public function getLanguage() { - global $wgLanguageCode; - return LanguageCode::bcp47( $wgLanguageCode ); - } - - /** - * Get the date of this item - * - * @return string - */ - public function getDate() { - return $this->date; - } - - /** - * Get the author of this item; already xml-encoded - * - * @return string - */ - public function getAuthor() { - return $this->xmlEncode( $this->author ); - } - - /** - * Get the author of this item without any escaping - * - * @return string - */ - public function getAuthorUnescaped() { - return $this->author; - } - - /** - * Get the comment of this item; already xml-encoded - * - * @return string - */ - public function getComments() { - return $this->xmlEncode( $this->comments ); - } - - /** - * Get the comment of this item without any escaping - * - * @return string - */ - public function getCommentsUnescaped() { - return $this->comments; - } - - /** - * Quickie hack... strip out wikilinks to more legible form from the comment. - * - * @param string $text Wikitext - * @return string - */ - public static function stripComment( $text ) { - return preg_replace( '/\[\[([^]]*\|)?([^]]+)\]\]/', '\2', $text ); - } - /**#@-*/ -} - -/** - * Class to support the outputting of syndication feeds in Atom and RSS format. - * - * @ingroup Feed - */ -abstract class ChannelFeed extends FeedItem { - - /** @var TemplateParser */ - protected $templateParser; - - /** - * @param string|Title $title Feed's title - * @param string $description - * @param string $url URL uniquely designating the feed. - * @param string $date Feed's date - * @param string $author Author's user name - * @param string $comments - */ - function __construct( $title, $description, $url, $date = '', $author = '', $comments = '' ) { - parent::__construct( $title, $description, $url, $date, $author, $comments ); - $this->templateParser = new TemplateParser(); - } - - /** - * Generate Header of the feed - * @par Example: - * @code - * print ""; - * @endcode - */ - abstract public function outHeader(); - - /** - * Generate an item - * @par Example: - * @code - * print "..."; - * @endcode - * @param FeedItem $item - */ - abstract public function outItem( $item ); - - /** - * Generate Footer of the feed - * @par Example: - * @code - * print ""; - * @endcode - */ - abstract public function outFooter(); - - /** - * Setup and send HTTP headers. Don't send any content; - * content might end up being cached and re-sent with - * these same headers later. - * - * This should be called from the outHeader() method, - * but can also be called separately. - */ - public function httpHeaders() { - global $wgOut, $wgVaryOnXFP; - - # We take over from $wgOut, excepting its cache header info - $wgOut->disable(); - $mimetype = $this->contentType(); - header( "Content-type: $mimetype; charset=UTF-8" ); - - // Set a sane filename - $exts = MediaWiki\MediaWikiServices::getInstance()->getMimeAnalyzer() - ->getExtensionsForType( $mimetype ); - $ext = $exts ? strtok( $exts, ' ' ) : 'xml'; - header( "Content-Disposition: inline; filename=\"feed.{$ext}\"" ); - - if ( $wgVaryOnXFP ) { - $wgOut->addVaryHeader( 'X-Forwarded-Proto' ); - } - $wgOut->sendCacheControl(); - } - - /** - * Return an internet media type to be sent in the headers. - * - * @return string - */ - private function contentType() { - global $wgRequest; - - $ctype = $wgRequest->getVal( 'ctype', 'application/xml' ); - $allowedctypes = [ - 'application/xml', - 'text/xml', - 'application/rss+xml', - 'application/atom+xml' - ]; - - return ( in_array( $ctype, $allowedctypes ) ? $ctype : 'application/xml' ); - } - - /** - * Output the initial XML headers. - */ - protected function outXmlHeader() { - $this->httpHeaders(); - echo '' . "\n"; - } -} - -/** - * Generate a RSS feed - * - * @ingroup Feed - */ -class RSSFeed extends ChannelFeed { - - /** - * Format a date given a timestamp. If a timestamp is not given, nothing is returned - * - * @param int|null $ts Timestamp - * @return string|null Date string - */ - function formatTime( $ts ) { - if ( $ts ) { - return gmdate( 'D, d M Y H:i:s \G\M\T', wfTimestamp( TS_UNIX, $ts ) ); - } - } - - /** - * Output an RSS 2.0 header - */ - function outHeader() { - global $wgVersion; - - $this->outXmlHeader(); - // Manually escaping rather than letting Mustache do it because Mustache - // uses htmlentities, which does not work with XML - $templateParams = [ - 'title' => $this->getTitle(), - 'url' => $this->xmlEncode( wfExpandUrl( $this->getUrlUnescaped(), PROTO_CURRENT ) ), - 'description' => $this->getDescription(), - 'language' => $this->xmlEncode( $this->getLanguage() ), - 'version' => $this->xmlEncode( $wgVersion ), - 'timestamp' => $this->xmlEncode( $this->formatTime( wfTimestampNow() ) ) - ]; - print $this->templateParser->processTemplate( 'RSSHeader', $templateParams ); - } - - /** - * Output an RSS 2.0 item - * @param FeedItem $item Item to be output - */ - function outItem( $item ) { - // Manually escaping rather than letting Mustache do it because Mustache - // uses htmlentities, which does not work with XML - $templateParams = [ - "title" => $item->getTitle(), - "url" => $this->xmlEncode( wfExpandUrl( $item->getUrlUnescaped(), PROTO_CURRENT ) ), - "permalink" => $item->rssIsPermalink, - "uniqueID" => $item->getUniqueID(), - "description" => $item->getDescription(), - "date" => $this->xmlEncode( $this->formatTime( $item->getDate() ) ), - "author" => $item->getAuthor() - ]; - $comments = $item->getCommentsUnescaped(); - if ( $comments ) { - $commentsEscaped = $this->xmlEncode( wfExpandUrl( $comments, PROTO_CURRENT ) ); - $templateParams["comments"] = $commentsEscaped; - } - print $this->templateParser->processTemplate( 'RSSItem', $templateParams ); - } - - /** - * Output an RSS 2.0 footer - */ - function outFooter() { - print ""; - } -} - -/** - * Generate an Atom feed - * - * @ingroup Feed - */ -class AtomFeed extends ChannelFeed { - /** - * Format a date given timestamp, if one is given. - * - * @param string|int|null $timestamp - * @return string|null - */ - function formatTime( $timestamp ) { - if ( $timestamp ) { - // need to use RFC 822 time format at least for rss2.0 - return gmdate( 'Y-m-d\TH:i:s', wfTimestamp( TS_UNIX, $timestamp ) ); - } - } - - /** - * Outputs a basic header for Atom 1.0 feeds. - */ - function outHeader() { - global $wgVersion; - $this->outXmlHeader(); - // Manually escaping rather than letting Mustache do it because Mustache - // uses htmlentities, which does not work with XML - $templateParams = [ - 'language' => $this->xmlEncode( $this->getLanguage() ), - 'feedID' => $this->getFeedId(), - 'title' => $this->getTitle(), - 'url' => $this->xmlEncode( wfExpandUrl( $this->getUrlUnescaped(), PROTO_CURRENT ) ), - 'selfUrl' => $this->getSelfUrl(), - 'timestamp' => $this->xmlEncode( $this->formatTime( wfTimestampNow() ) ), - 'description' => $this->getDescription(), - 'version' => $this->xmlEncode( $wgVersion ), - ]; - print $this->templateParser->processTemplate( 'AtomHeader', $templateParams ); - } - - /** - * Atom 1.0 requires a unique, opaque IRI as a unique identifier - * for every feed we create. For now just use the URL, but who - * can tell if that's right? If we put options on the feed, do we - * have to change the id? Maybe? Maybe not. - * - * @return string - */ - private function getFeedId() { - return $this->getSelfUrl(); - } - - /** - * Atom 1.0 requests a self-reference to the feed. - * @return string - */ - private function getSelfUrl() { - global $wgRequest; - return htmlspecialchars( $wgRequest->getFullRequestURL() ); - } - - /** - * Output a given item. - * @param FeedItem $item - */ - function outItem( $item ) { - global $wgMimeType; - // Manually escaping rather than letting Mustache do it because Mustache - // uses htmlentities, which does not work with XML - $templateParams = [ - "uniqueID" => $item->getUniqueID(), - "title" => $item->getTitle(), - "mimeType" => $this->xmlEncode( $wgMimeType ), - "url" => $this->xmlEncode( wfExpandUrl( $item->getUrlUnescaped(), PROTO_CURRENT ) ), - "date" => $this->xmlEncode( $this->formatTime( $item->getDate() ) ), - "description" => $item->getDescription(), - "author" => $item->getAuthor() - ]; - print $this->templateParser->processTemplate( 'AtomItem', $templateParams ); - } - - /** - * Outputs the footer for Atom 1.0 feed (basically '\'). - */ - function outFooter() { - print ""; - } -} diff --git a/includes/changes/AtomFeed.php b/includes/changes/AtomFeed.php new file mode 100644 index 0000000000..a4ce0c1278 --- /dev/null +++ b/includes/changes/AtomFeed.php @@ -0,0 +1,111 @@ + + * https://www.mediawiki.org/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * + * @file + */ + +/** + * Generate an Atom feed. + * + * @ingroup Feed + */ +class AtomFeed extends ChannelFeed { + /** + * Format a date given timestamp, if one is given. + * + * @param string|int|null $timestamp + * @return string|null + */ + function formatTime( $timestamp ) { + if ( $timestamp ) { + // need to use RFC 822 time format at least for rss2.0 + return gmdate( 'Y-m-d\TH:i:s', wfTimestamp( TS_UNIX, $timestamp ) ); + } + } + + /** + * Outputs a basic header for Atom 1.0 feeds. + */ + function outHeader() { + global $wgVersion; + $this->outXmlHeader(); + // Manually escaping rather than letting Mustache do it because Mustache + // uses htmlentities, which does not work with XML + $templateParams = [ + 'language' => $this->xmlEncode( $this->getLanguage() ), + 'feedID' => $this->getFeedId(), + 'title' => $this->getTitle(), + 'url' => $this->xmlEncode( wfExpandUrl( $this->getUrlUnescaped(), PROTO_CURRENT ) ), + 'selfUrl' => $this->getSelfUrl(), + 'timestamp' => $this->xmlEncode( $this->formatTime( wfTimestampNow() ) ), + 'description' => $this->getDescription(), + 'version' => $this->xmlEncode( $wgVersion ), + ]; + print $this->templateParser->processTemplate( 'AtomHeader', $templateParams ); + } + + /** + * Atom 1.0 requires a unique, opaque IRI as a unique identifier + * for every feed we create. For now just use the URL, but who + * can tell if that's right? If we put options on the feed, do we + * have to change the id? Maybe? Maybe not. + * + * @return string + */ + private function getFeedId() { + return $this->getSelfUrl(); + } + + /** + * Atom 1.0 requests a self-reference to the feed. + * @return string + */ + private function getSelfUrl() { + global $wgRequest; + return htmlspecialchars( $wgRequest->getFullRequestURL() ); + } + + /** + * Output a given item. + * @param FeedItem $item + */ + function outItem( $item ) { + global $wgMimeType; + // Manually escaping rather than letting Mustache do it because Mustache + // uses htmlentities, which does not work with XML + $templateParams = [ + "uniqueID" => $item->getUniqueID(), + "title" => $item->getTitle(), + "mimeType" => $this->xmlEncode( $wgMimeType ), + "url" => $this->xmlEncode( wfExpandUrl( $item->getUrlUnescaped(), PROTO_CURRENT ) ), + "date" => $this->xmlEncode( $this->formatTime( $item->getDate() ) ), + "description" => $item->getDescription(), + "author" => $item->getAuthor() + ]; + print $this->templateParser->processTemplate( 'AtomItem', $templateParams ); + } + + /** + * Outputs the footer for Atom 1.0 feed (basically '\'). + */ + function outFooter() { + print ""; + } +} diff --git a/includes/changes/ChannelFeed.php b/includes/changes/ChannelFeed.php new file mode 100644 index 0000000000..a1b832e589 --- /dev/null +++ b/includes/changes/ChannelFeed.php @@ -0,0 +1,129 @@ + + * https://www.mediawiki.org/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * + * @file + */ + +/** + * Class to support the outputting of syndication feeds in Atom and RSS format. + * + * @ingroup Feed + */ +abstract class ChannelFeed extends FeedItem { + + /** @var TemplateParser */ + protected $templateParser; + + /** + * @param string|Title $title Feed's title + * @param string $description + * @param string $url URL uniquely designating the feed. + * @param string $date Feed's date + * @param string $author Author's user name + * @param string $comments + */ + function __construct( $title, $description, $url, $date = '', $author = '', $comments = '' ) { + parent::__construct( $title, $description, $url, $date, $author, $comments ); + $this->templateParser = new TemplateParser(); + } + + /** + * Generate Header of the feed + * @par Example: + * @code + * print ""; + * @endcode + */ + abstract public function outHeader(); + + /** + * Generate an item + * @par Example: + * @code + * print "..."; + * @endcode + * @param FeedItem $item + */ + abstract public function outItem( $item ); + + /** + * Generate Footer of the feed + * @par Example: + * @code + * print ""; + * @endcode + */ + abstract public function outFooter(); + + /** + * Setup and send HTTP headers. Don't send any content; + * content might end up being cached and re-sent with + * these same headers later. + * + * This should be called from the outHeader() method, + * but can also be called separately. + */ + public function httpHeaders() { + global $wgOut, $wgVaryOnXFP; + + # We take over from $wgOut, excepting its cache header info + $wgOut->disable(); + $mimetype = $this->contentType(); + header( "Content-type: $mimetype; charset=UTF-8" ); + + // Set a sane filename + $exts = MediaWiki\MediaWikiServices::getInstance()->getMimeAnalyzer() + ->getExtensionsForType( $mimetype ); + $ext = $exts ? strtok( $exts, ' ' ) : 'xml'; + header( "Content-Disposition: inline; filename=\"feed.{$ext}\"" ); + + if ( $wgVaryOnXFP ) { + $wgOut->addVaryHeader( 'X-Forwarded-Proto' ); + } + $wgOut->sendCacheControl(); + } + + /** + * Return an internet media type to be sent in the headers. + * + * @return string + */ + private function contentType() { + global $wgRequest; + + $ctype = $wgRequest->getVal( 'ctype', 'application/xml' ); + $allowedctypes = [ + 'application/xml', + 'text/xml', + 'application/rss+xml', + 'application/atom+xml' + ]; + + return ( in_array( $ctype, $allowedctypes ) ? $ctype : 'application/xml' ); + } + + /** + * Output the initial XML headers. + */ + protected function outXmlHeader() { + $this->httpHeaders(); + echo '' . "\n"; + } +} diff --git a/includes/changes/FeedItem.php b/includes/changes/FeedItem.php new file mode 100644 index 0000000000..a6a26152b7 --- /dev/null +++ b/includes/changes/FeedItem.php @@ -0,0 +1,222 @@ + + * https://www.mediawiki.org/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * + * @file + */ + +/** + * @defgroup Feed Feed + */ + +/** + * A base class for outputting syndication feeds (e.g. RSS and other formats). + * + * @ingroup Feed + */ +class FeedItem { + /** @var Title */ + public $title; + + public $description; + + public $url; + + public $date; + + public $author; + + public $uniqueId; + + public $comments; + + public $rssIsPermalink = false; + + /** + * @param string|Title $title Item's title + * @param string $description + * @param string $url URL uniquely designating the item. + * @param string $date Item's date + * @param string $author Author's user name + * @param string $comments + */ + function __construct( $title, $description, $url, $date = '', $author = '', $comments = '' ) { + $this->title = $title; + $this->description = $description; + $this->url = $url; + $this->uniqueId = $url; + $this->date = $date; + $this->author = $author; + $this->comments = $comments; + } + + /** + * Encode $string so that it can be safely embedded in a XML document + * + * @param string $string String to encode + * @return string + */ + public function xmlEncode( $string ) { + $string = str_replace( "\r\n", "\n", $string ); + $string = preg_replace( '/[\x00-\x08\x0b\x0c\x0e-\x1f]/', '', $string ); + return htmlspecialchars( $string ); + } + + /** + * Get the unique id of this item; already xml-encoded + * @return string + */ + public function getUniqueID() { + $id = $this->getUniqueIdUnescaped(); + if ( $id ) { + return $this->xmlEncode( $id ); + } + } + + /** + * Get the unique id of this item, without any escaping + * @return string + */ + public function getUniqueIdUnescaped() { + if ( $this->uniqueId ) { + return wfExpandUrl( $this->uniqueId, PROTO_CURRENT ); + } + } + + /** + * Set the unique id of an item + * + * @param string $uniqueId Unique id for the item + * @param bool $rssIsPermalink Set to true if the guid (unique id) is a permalink (RSS feeds only) + */ + public function setUniqueId( $uniqueId, $rssIsPermalink = false ) { + $this->uniqueId = $uniqueId; + $this->rssIsPermalink = $rssIsPermalink; + } + + /** + * Get the title of this item; already xml-encoded + * + * @return string + */ + public function getTitle() { + return $this->xmlEncode( $this->title ); + } + + /** + * Get the URL of this item; already xml-encoded + * + * @return string + */ + public function getUrl() { + return $this->xmlEncode( $this->url ); + } + + /** Get the URL of this item without any escaping + * + * @return string + */ + public function getUrlUnescaped() { + return $this->url; + } + + /** + * Get the description of this item; already xml-encoded + * + * @return string + */ + public function getDescription() { + return $this->xmlEncode( $this->description ); + } + + /** + * Get the description of this item without any escaping + * + * @return string + */ + public function getDescriptionUnescaped() { + return $this->description; + } + + /** + * Get the language of this item + * + * @return string + */ + public function getLanguage() { + global $wgLanguageCode; + return LanguageCode::bcp47( $wgLanguageCode ); + } + + /** + * Get the date of this item + * + * @return string + */ + public function getDate() { + return $this->date; + } + + /** + * Get the author of this item; already xml-encoded + * + * @return string + */ + public function getAuthor() { + return $this->xmlEncode( $this->author ); + } + + /** + * Get the author of this item without any escaping + * + * @return string + */ + public function getAuthorUnescaped() { + return $this->author; + } + + /** + * Get the comment of this item; already xml-encoded + * + * @return string + */ + public function getComments() { + return $this->xmlEncode( $this->comments ); + } + + /** + * Get the comment of this item without any escaping + * + * @return string + */ + public function getCommentsUnescaped() { + return $this->comments; + } + + /** + * Quickie hack... strip out wikilinks to more legible form from the comment. + * + * @param string $text Wikitext + * @return string + */ + public static function stripComment( $text ) { + return preg_replace( '/\[\[([^]]*\|)?([^]]+)\]\]/', '\2', $text ); + } + /**#@-*/ +} diff --git a/includes/changes/RSSFeed.php b/includes/changes/RSSFeed.php new file mode 100644 index 0000000000..3b34500ef1 --- /dev/null +++ b/includes/changes/RSSFeed.php @@ -0,0 +1,93 @@ + + * https://www.mediawiki.org/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * + * @file + */ + +/** + * Generate an RSS feed. + * + * @ingroup Feed + */ +class RSSFeed extends ChannelFeed { + + /** + * Format a date given a timestamp. If a timestamp is not given, nothing is returned + * + * @param int|null $ts Timestamp + * @return string|null Date string + */ + function formatTime( $ts ) { + if ( $ts ) { + return gmdate( 'D, d M Y H:i:s \G\M\T', wfTimestamp( TS_UNIX, $ts ) ); + } + } + + /** + * Output an RSS 2.0 header + */ + function outHeader() { + global $wgVersion; + + $this->outXmlHeader(); + // Manually escaping rather than letting Mustache do it because Mustache + // uses htmlentities, which does not work with XML + $templateParams = [ + 'title' => $this->getTitle(), + 'url' => $this->xmlEncode( wfExpandUrl( $this->getUrlUnescaped(), PROTO_CURRENT ) ), + 'description' => $this->getDescription(), + 'language' => $this->xmlEncode( $this->getLanguage() ), + 'version' => $this->xmlEncode( $wgVersion ), + 'timestamp' => $this->xmlEncode( $this->formatTime( wfTimestampNow() ) ) + ]; + print $this->templateParser->processTemplate( 'RSSHeader', $templateParams ); + } + + /** + * Output an RSS 2.0 item + * @param FeedItem $item Item to be output + */ + function outItem( $item ) { + // Manually escaping rather than letting Mustache do it because Mustache + // uses htmlentities, which does not work with XML + $templateParams = [ + "title" => $item->getTitle(), + "url" => $this->xmlEncode( wfExpandUrl( $item->getUrlUnescaped(), PROTO_CURRENT ) ), + "permalink" => $item->rssIsPermalink, + "uniqueID" => $item->getUniqueID(), + "description" => $item->getDescription(), + "date" => $this->xmlEncode( $this->formatTime( $item->getDate() ) ), + "author" => $item->getAuthor() + ]; + $comments = $item->getCommentsUnescaped(); + if ( $comments ) { + $commentsEscaped = $this->xmlEncode( wfExpandUrl( $comments, PROTO_CURRENT ) ); + $templateParams["comments"] = $commentsEscaped; + } + print $this->templateParser->processTemplate( 'RSSItem', $templateParams ); + } + + /** + * Output an RSS 2.0 footer + */ + function outFooter() { + print ""; + } +} -- 2.20.1