From 007a505ec1793e7441ce25313bd0e1b2bd086ce2 Mon Sep 17 00:00:00 2001 From: Marcel Werk Date: Thu, 1 Feb 2024 18:13:25 +0100 Subject: [PATCH 01/10] Add API for RSS feed generation --- .../lib/system/rssFeed/RssFeed.class.php | 53 +++++ .../system/rssFeed/RssFeedCategory.class.php | 12 ++ .../system/rssFeed/RssFeedChannel.class.php | 188 +++++++++++++++++ .../system/rssFeed/RssFeedEnclosure.class.php | 13 ++ .../lib/system/rssFeed/RssFeedItem.class.php | 195 ++++++++++++++++++ .../system/rssFeed/RssFeedSource.class.php | 12 ++ .../lib/system/rssFeed/XmlElement.class.php | 30 +++ 7 files changed, 503 insertions(+) create mode 100644 wcfsetup/install/files/lib/system/rssFeed/RssFeed.class.php create mode 100644 wcfsetup/install/files/lib/system/rssFeed/RssFeedCategory.class.php create mode 100644 wcfsetup/install/files/lib/system/rssFeed/RssFeedChannel.class.php create mode 100644 wcfsetup/install/files/lib/system/rssFeed/RssFeedEnclosure.class.php create mode 100644 wcfsetup/install/files/lib/system/rssFeed/RssFeedItem.class.php create mode 100644 wcfsetup/install/files/lib/system/rssFeed/RssFeedSource.class.php create mode 100644 wcfsetup/install/files/lib/system/rssFeed/XmlElement.class.php diff --git a/wcfsetup/install/files/lib/system/rssFeed/RssFeed.class.php b/wcfsetup/install/files/lib/system/rssFeed/RssFeed.class.php new file mode 100644 index 00000000000..a3d8354614c --- /dev/null +++ b/wcfsetup/install/files/lib/system/rssFeed/RssFeed.class.php @@ -0,0 +1,53 @@ +channels[] = $channel; + + return $this; + } + + public function render(): string + { + $header = <<<'EOT' + + + EOT; + + $element = new XmlElement( + $header, + LIBXML_NOERROR | LIBXML_ERR_NONE | LIBXML_ERR_FATAL + ); + + foreach ($this->channels as $channel) { + $toDom = \dom_import_simplexml($element); + $fromDom = \dom_import_simplexml($channel->getXML()); + $toDom->appendChild($toDom->ownerDocument->importNode($fromDom, true)); + } + + $dom = new \DOMDocument('1.0', 'UTF-8'); + $dom->appendChild($dom->importNode(\dom_import_simplexml($element), true)); + $dom->formatOutput = true; + + return $dom->saveXML(); + } + + public function __toString(): string + { + return $this->render(); + } +} diff --git a/wcfsetup/install/files/lib/system/rssFeed/RssFeedCategory.class.php b/wcfsetup/install/files/lib/system/rssFeed/RssFeedCategory.class.php new file mode 100644 index 00000000000..1bac8b54cbb --- /dev/null +++ b/wcfsetup/install/files/lib/system/rssFeed/RssFeedCategory.class.php @@ -0,0 +1,12 @@ +title = $title; + + return $this; + } + + public function description(string $description): static + { + $this->description = $description; + + return $this; + } + + public function link(string $link): static + { + $this->link = $link; + + return $this; + } + + public function atomLinkSelf(string $link): static + { + $this->atomLinkSelf = $link; + + return $this; + } + + public function language(string $language): static + { + $this->language = $language; + + return $this; + } + + public function copyright(string $copyright): static + { + $this->copyright = $copyright; + + return $this; + } + + public function lastBuildDate(string $date): static + { + $this->lastBuildDate = $date; + + return $this; + } + + public function lastBuildDateFromTimestamp(int $timestamp): static + { + return $this->lastBuildDate(\gmdate('r', $timestamp)); + } + + public function pubDate(string $date): static + { + $this->pubDate = $date; + + return $this; + } + + public function pubDateFromTimestamp(int $timestamp): static + { + return $this->pubDate(\gmdate('r', $timestamp)); + } + + public function ttl(int $ttl): static + { + $this->ttl = $ttl; + + return $this; + } + + public function category(string $name, ?string $domain = null): static + { + $this->categories[] = new RssFeedCategory($name, $domain); + + return $this; + } + + public function item(RssFeedItem $item): static + { + $this->items[] = $item; + + return $this; + } + + public function getXML(): \SimpleXMLElement + { + $this->integrityCheck(); + + $element = new XmlElement( + '', + LIBXML_NOERROR | LIBXML_ERR_NONE | LIBXML_ERR_FATAL + ); + + if (isset($this->title)) { + $element->addChild('title', $this->title); + } + if (isset($this->description)) { + $element->addChild('description', $this->description); + } + if (isset($this->link)) { + $element->addChild('link', $this->link); + } + if (isset($this->language)) { + $element->addChild('language', $this->language); + } + if (isset($this->copyright)) { + $element->addChild('copyright', $this->copyright); + } + if (isset($this->lastBuildDate)) { + $element->addChild('lastBuildDate', $this->lastBuildDate); + } + if (isset($this->pubDate)) { + $element->addChild('pubDate', $this->pubDate); + } + + if (isset($this->atomLinkSelf)) { + $atomLink = $element->addChild('xmlns:atom:link'); + $atomLink->addAttribute('href', $this->atomLinkSelf); + $atomLink->addAttribute('rel', 'self'); + $atomLink->addAttribute('type', 'application/rss+xml'); + } + + $element->addChild('ttl', $this->ttl); + $element->addChild('generator', 'WoltLab Suite' . (SHOW_VERSION_NUMBER ? ' ' . \WCF_VERSION : '')); + + foreach ($this->categories as $category) { + $categoryElement = $element->addChild('category', $category->name); + if ($category->domain !== null) { + $categoryElement->addAttribute('domain', $category->domain); + } + } + + foreach ($this->items as $item) { + $toDom = \dom_import_simplexml($element); + $fromDom = \dom_import_simplexml($item->getXML()); + $toDom->appendChild($toDom->ownerDocument->importNode($fromDom, true)); + } + + return $element; + } + + private function integrityCheck(): void + { + // Title, description and link are required. + if (!isset($this->title)) { + throw new BadMethodCallException("missing parameter 'title'"); + } + + if (!isset($this->description)) { + throw new BadMethodCallException("missing parameter 'description'"); + } + + if (!isset($this->link)) { + throw new BadMethodCallException("missing parameter 'link'"); + } + } +} diff --git a/wcfsetup/install/files/lib/system/rssFeed/RssFeedEnclosure.class.php b/wcfsetup/install/files/lib/system/rssFeed/RssFeedEnclosure.class.php new file mode 100644 index 00000000000..f87419767ca --- /dev/null +++ b/wcfsetup/install/files/lib/system/rssFeed/RssFeedEnclosure.class.php @@ -0,0 +1,13 @@ +title = $title; + + return $this; + } + + public function description(string $description): static + { + $this->description = $description; + + return $this; + } + + public function link(string $link): static + { + $this->link = $link; + + return $this; + } + + public function pubDate(string $pubDate): static + { + $this->pubDate = $pubDate; + + return $this; + } + + public function pubDateFromTimestamp(int $timestamp): static + { + return $this->pubDate(\gmdate('r', $timestamp)); + } + + public function creator(string $creator): static + { + $this->creator = $creator; + + return $this; + } + + public function guid(string $guid, bool $isPermalink = true): static + { + $this->guid = $guid; + $this->guidIsPermalink = $isPermalink; + + return $this; + } + + public function enclosure(string $url, int $length, string $type): static + { + $this->enclosure = new RssFeedEnclosure($url, $length, $type); + + return $this; + } + + public function contentEncoded(string $content): static + { + $this->contentEncoded = $content; + + return $this; + } + + public function comments(string $url): static + { + $this->comments = $url; + + return $this; + } + + public function slashComments(int $comments): static + { + $this->slashComments = $comments; + + return $this; + } + + public function category(string $name, ?string $domain = null): static + { + $this->categories[] = new RssFeedCategory($name, $domain); + + return $this; + } + + public function author(string $email): static + { + $this->author = $email; + + return $this; + } + + public function source(string $name, string $url): static + { + $this->source = new RssFeedSource($name, $url); + + return $this; + } + + public function getXML(): \SimpleXMLElement + { + $this->integrityCheck(); + + $element = new XmlElement( + '', + LIBXML_NOERROR | LIBXML_ERR_NONE | LIBXML_ERR_FATAL + ); + + if (isset($this->title)) { + $element->addChild('title', $this->title); + } + if (isset($this->link)) { + $element->addChild('link', $this->link); + } + if (isset($this->author)) { + $element->addChild('author', $this->author); + } + if (isset($this->description)) { + $element->addChildCData('description', $this->description); + } + if (isset($this->comments)) { + $element->addChild('comments', $this->comments); + } + if (isset($this->slashComments)) { + $element->addChild('xmlns:slash:comments', $this->slashComments); + } + if (isset($this->guid)) { + $guidElement = $element->addChild('guid', $this->guid); + if (!$this->guidIsPermalink) { + $guidElement->addAttribute('isPermaLink', 'false'); + } + } + if (isset($this->pubDate)) { + $element->addChild('pubDate', $this->pubDate); + } + if (isset($this->creator)) { + $element->addChild('xmlns:dc:creator', $this->creator); + } + if (isset($this->contentEncoded)) { + $element->addChildCData('xmlns:content:encoded', $this->contentEncoded); + } + if (isset($this->source)) { + $sourceElement = $element->addChild('source', $this->source->name); + $sourceElement->addAttribute('url', $this->source->url); + } + if (isset($this->enclosure)) { + $enclosureElement = $element->addChild('enclosure'); + $enclosureElement->addAttribute('url', $this->enclosure->url); + $enclosureElement->addAttribute('type', $this->enclosure->type); + $enclosureElement->addAttribute('length', $this->enclosure->length); + } + + foreach ($this->categories as $category) { + $categoryElement = $element->addChild('category', $category->name); + if ($category->domain !== null) { + $categoryElement->addAttribute('domain', $category->domain); + } + } + + return $element; + } + + private function integrityCheck(): void + { + // All elements of an item are optional, however at least one of title or description must be present. + if (!isset($this->title) && !isset($this->description)) { + throw new BadMethodCallException("feed item needs either a 'title' or 'description'"); + } + } +} diff --git a/wcfsetup/install/files/lib/system/rssFeed/RssFeedSource.class.php b/wcfsetup/install/files/lib/system/rssFeed/RssFeedSource.class.php new file mode 100644 index 00000000000..b209ef314a7 --- /dev/null +++ b/wcfsetup/install/files/lib/system/rssFeed/RssFeedSource.class.php @@ -0,0 +1,12 @@ +addChild($name); + $child->addCData($value); + + return $child; + } + + private function addCData(string $value): void + { + $node = \dom_import_simplexml($this); + $no = $node->ownerDocument; + $node->appendChild($no->createCDATASection($value)); + } +} From 893f873cb021a2e0dc036de5f4d053b6f9a28c21 Mon Sep 17 00:00:00 2001 From: Marcel Werk Date: Thu, 1 Feb 2024 18:13:43 +0100 Subject: [PATCH 02/10] Make use of the new API for the article feed --- .../article/content/ArticleContent.class.php | 33 ++++-- .../lib/page/AbstractRssFeedPage.class.php | 78 ++++++++++++ .../lib/page/ArticleRssFeedPage.class.php | 111 ++++++++++++++++++ 3 files changed, 210 insertions(+), 12 deletions(-) create mode 100644 wcfsetup/install/files/lib/page/AbstractRssFeedPage.class.php create mode 100644 wcfsetup/install/files/lib/page/ArticleRssFeedPage.class.php diff --git a/wcfsetup/install/files/lib/data/article/content/ArticleContent.class.php b/wcfsetup/install/files/lib/data/article/content/ArticleContent.class.php index 52f9965a6d9..d079ffbd2ea 100644 --- a/wcfsetup/install/files/lib/data/article/content/ArticleContent.class.php +++ b/wcfsetup/install/files/lib/data/article/content/ArticleContent.class.php @@ -87,18 +87,7 @@ public function getFormattedTeaser() if ($this->teaser) { return \nl2br(StringUtil::encodeHTML($this->teaser), false); } else { - $htmlOutputProcessor = new HtmlOutputProcessor(); - $htmlOutputProcessor->setOutputType('text/simplified-html'); - $htmlOutputProcessor->enableUgc = false; - $htmlOutputProcessor->process( - $this->content, - 'com.woltlab.wcf.article.content', - $this->articleContentID, - false, - $this->languageID - ); - - return MessageUtil::truncateFormattedMessage($htmlOutputProcessor->getHtml(), 500); + return MessageUtil::truncateFormattedMessage($this->getSimplifiedFormattedContent(), 500); } } @@ -122,6 +111,26 @@ public function getFormattedContent() return $processor->getHtml(); } + /** + * Returns a simplified version of the formatted content. + * @since 6.1 + */ + public function getSimplifiedFormattedContent(): string + { + $htmlOutputProcessor = new HtmlOutputProcessor(); + $htmlOutputProcessor->setOutputType('text/simplified-html'); + $htmlOutputProcessor->enableUgc = false; + $htmlOutputProcessor->process( + $this->content, + 'com.woltlab.wcf.article.content', + $this->articleContentID, + false, + $this->languageID + ); + + return $htmlOutputProcessor->getHtml(); + } + /** * Returns article object. * diff --git a/wcfsetup/install/files/lib/page/AbstractRssFeedPage.class.php b/wcfsetup/install/files/lib/page/AbstractRssFeedPage.class.php new file mode 100644 index 00000000000..95f70608e4f --- /dev/null +++ b/wcfsetup/install/files/lib/page/AbstractRssFeedPage.class.php @@ -0,0 +1,78 @@ + + * @since 6.1 + */ +abstract class AbstractRssFeedPage extends AbstractAuthedPage +{ + /** + * @inheritDoc + */ + public $useTemplate = false; + + /** + * parsed contents of $_REQUEST['id'] + * @var int[] + */ + public array $objectIDs = []; + + #[\Override] + public function readParameters() + { + parent::readParameters(); + + if (isset($_REQUEST['id'])) { + if (\is_array($_REQUEST['id'])) { + // ?id[]=1337&id[]=9001 + $this->objectIDs = ArrayUtil::toIntegerArray($_REQUEST['id']); + } else { + // ?id=1337 or ?id=1337,9001 + $this->objectIDs = ArrayUtil::toIntegerArray(\explode(',', $_REQUEST['id'])); + } + } + } + + #[\Override] + public function show() + { + parent::show(); + if ($this->getPsr7Response()) { + return; + } + + $output = $this->getRssFeed()->render(); + + @\header('Content-Type: application/rss+xml; charset=UTF-8'); + + echo $output; + } + + protected function getDefaultChannel(): RssFeedChannel + { + $channel = new RssFeedChannel(); + $channel + ->title(WCF::getLanguage()->get(\PAGE_TITLE)) + ->description(WCF::getLanguage()->get(\PAGE_DESCRIPTION)) + ->link(WCF::getPath()) + ->language(WCF::getLanguage()->getFixedLanguageCode()) + ->pubDateFromTimestamp(\TIME_NOW) + ->lastBuildDateFromTimestamp(\TIME_NOW) + ->atomLinkSelf(WCF::getRequestURI()); + + return $channel; + } + + protected abstract function getRssFeed(): RssFeed; +} diff --git a/wcfsetup/install/files/lib/page/ArticleRssFeedPage.class.php b/wcfsetup/install/files/lib/page/ArticleRssFeedPage.class.php new file mode 100644 index 00000000000..084d9fcd432 --- /dev/null +++ b/wcfsetup/install/files/lib/page/ArticleRssFeedPage.class.php @@ -0,0 +1,111 @@ + + * @since 6.1 + */ +class ArticleRssFeedPage extends AbstractRssFeedPage +{ + public ArticleCategory $category; + public int $categoryID = 0; + public AccessibleArticleList $articles; + + #[\Override] + public function readParameters() + { + parent::readParameters(); + + if (isset($_REQUEST['id'])) { + $this->categoryID = \intval($_REQUEST['id']); + $this->category = ArticleCategory::getCategory($this->categoryID); + if ($this->category === null) { + throw new IllegalLinkException(); + } + if (!$this->category->isAccessible()) { + throw new PermissionDeniedException(); + } + } + } + + #[\Override] + public function readData() + { + parent::readData(); + + if ($this->categoryID) { + $this->articles = new CategoryArticleList($this->categoryID); + } else { + $this->articles = new AccessibleArticleList(); + } + $this->articles->sqlOrderBy = 'article.time ' . ARTICLE_SORT_ORDER; + $this->articles->sqlLimit = 20; + $this->articles->readObjects(); + } + + #[\Override] + protected function getRssFeed(): RssFeed + { + $feed = new RssFeed(); + $channel = $this->getDefaultChannel(); + if (isset($this->category)) { + $channel->title($this->category->getTitle()); + $channel->description($this->category->getDecoratedObject()->getDescription()); + } else { + $channel->title(WCF::getLanguage()->get('wcf.article.articles')); + } + + if ($this->articles->valid()) { + $channel->lastBuildDateFromTimestamp($this->articles->current()->getTime()); + } + $feed->channel($channel); + + foreach ($this->articles as $article) { + $item = new RssFeedItem(); + $item + ->title($article->getTitle()) + ->link($article->getLink()) + ->description(StringUtil::truncateHTML($article->getFormattedTeaser(), 255)) + ->pubDateFromTimestamp($article->time) + ->creator($article->username) + ->guid($article->getLink()) + ->contentEncoded($article->getArticleContent()->getSimplifiedFormattedContent()) + ->slashComments($article->getArticleContent()->comments); + + if ($article->getImage() !== null) { + $item->enclosure( + $article->getImage()->getThumbnailLink('small'), + $article->getImage()->smallThumbnailSize, + $article->getImage()->smallThumbnailType + ); + } + + $category = $article->getDecoratedObject()->getCategory(); + if ($category !== null) { + $item->category($category->getTitle()); + foreach ($category->getParentCategories() as $category) { + $item->category($category->getTitle()); + } + } + + $channel->item($item); + } + + return $feed; + } +} From 36f9aaac75d96a336c2c76d271683ed8750f6296 Mon Sep 17 00:00:00 2001 From: Marcel Werk Date: Fri, 2 Feb 2024 12:33:44 +0100 Subject: [PATCH 03/10] Apply suggestions from code review Co-authored-by: Alexander Ebert --- wcfsetup/install/files/lib/system/rssFeed/RssFeed.class.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wcfsetup/install/files/lib/system/rssFeed/RssFeed.class.php b/wcfsetup/install/files/lib/system/rssFeed/RssFeed.class.php index a3d8354614c..4af7d98077a 100644 --- a/wcfsetup/install/files/lib/system/rssFeed/RssFeed.class.php +++ b/wcfsetup/install/files/lib/system/rssFeed/RssFeed.class.php @@ -26,7 +26,7 @@ public function render(): string xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/" > - EOT; + EOT; $element = new XmlElement( $header, From 85b2d28d30c0bee496b136b98248a07c9470a67b Mon Sep 17 00:00:00 2001 From: Marcel Werk Date: Sun, 4 Feb 2024 14:25:42 +0100 Subject: [PATCH 04/10] Make use of the `objectIDs` parameter --- wcfsetup/install/files/lib/page/ArticleRssFeedPage.class.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/wcfsetup/install/files/lib/page/ArticleRssFeedPage.class.php b/wcfsetup/install/files/lib/page/ArticleRssFeedPage.class.php index 084d9fcd432..bf4d575869a 100644 --- a/wcfsetup/install/files/lib/page/ArticleRssFeedPage.class.php +++ b/wcfsetup/install/files/lib/page/ArticleRssFeedPage.class.php @@ -31,8 +31,8 @@ public function readParameters() { parent::readParameters(); - if (isset($_REQUEST['id'])) { - $this->categoryID = \intval($_REQUEST['id']); + if ($this->objectIDs !== []) { + $this->categoryID = \reset($this->objectIDs); $this->category = ArticleCategory::getCategory($this->categoryID); if ($this->category === null) { throw new IllegalLinkException(); From 36583b620d1e7d15bb670d99aba39880ea7f7afc Mon Sep 17 00:00:00 2001 From: Marcel Werk Date: Sun, 4 Feb 2024 14:26:28 +0100 Subject: [PATCH 05/10] Change references in templates to the new page --- com.woltlab.wcf/templates/articleList.tpl | 6 +++--- com.woltlab.wcf/templates/categoryArticleList.tpl | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/com.woltlab.wcf/templates/articleList.tpl b/com.woltlab.wcf/templates/articleList.tpl index 8492fa0a294..518f45856fc 100644 --- a/com.woltlab.wcf/templates/articleList.tpl +++ b/com.woltlab.wcf/templates/articleList.tpl @@ -7,9 +7,9 @@ {/if} {if $__wcf->getUser()->userID} - + {else} - + {/if} {/capture} @@ -104,7 +104,7 @@ {/capture} {capture assign='contentInteractionDropdownItems'} -
  • {lang}wcf.global.button.rss{/lang}
  • +
  • {lang}wcf.global.button.rss{/lang}
  • {/capture} {include file='header'} diff --git a/com.woltlab.wcf/templates/categoryArticleList.tpl b/com.woltlab.wcf/templates/categoryArticleList.tpl index 392e7c8fadc..1db7eec872e 100644 --- a/com.woltlab.wcf/templates/categoryArticleList.tpl +++ b/com.woltlab.wcf/templates/categoryArticleList.tpl @@ -12,9 +12,9 @@ {/if} {if $__wcf->getUser()->userID} - + {else} - + {/if} {/capture} @@ -69,7 +69,7 @@ {/capture} {capture assign='contentInteractionDropdownItems'} -
  • {lang}wcf.global.button.rss{/lang}
  • +
  • {lang}wcf.global.button.rss{/lang}
  • {/capture} {include file='header'} From a9da8426408c70fc88c0c153eb2bcc21c8cc69ba Mon Sep 17 00:00:00 2001 From: Marcel Werk Date: Sun, 4 Feb 2024 14:48:34 +0100 Subject: [PATCH 06/10] Add redirect to new rss feed page --- .../files/lib/page/AbstractFeedPage.class.php | 25 +++++++++++++++++++ .../files/lib/page/ArticleFeedPage.class.php | 3 +++ 2 files changed, 28 insertions(+) diff --git a/wcfsetup/install/files/lib/page/AbstractFeedPage.class.php b/wcfsetup/install/files/lib/page/AbstractFeedPage.class.php index cbf9a54144c..6b2a7f85617 100644 --- a/wcfsetup/install/files/lib/page/AbstractFeedPage.class.php +++ b/wcfsetup/install/files/lib/page/AbstractFeedPage.class.php @@ -2,8 +2,10 @@ namespace wcf\page; +use wcf\system\request\LinkHandler; use wcf\system\WCF; use wcf\util\ArrayUtil; +use wcf\util\HeaderUtil; /** * Generates RSS 2-Feeds. @@ -11,6 +13,7 @@ * @author Tim Duesterhus * @copyright 2001-2019 WoltLab GmbH * @license GNU Lesser General Public License + * @deprecated 6.1 use `AbstractRssFeedPage` instead */ abstract class AbstractFeedPage extends AbstractAuthedPage { @@ -96,4 +99,26 @@ public function show() // show template WCF::getTPL()->display($this->templateName, $this->application, false); } + + protected function redirectToNewPage(string $className): void + { + $parameters = []; + $url = ''; + if ($this->objectIDs !== []) { + if (\count($this->objectIDs) === 1) { + $parameters['id'] = \reset($this->objectIDs); + } else { + $url = 'id=' . \implode(',', $this->objectIDs); + } + } + if (isset($_REQUEST['at'])) { + $parameters['at'] = $_REQUEST['at']; + } + HeaderUtil::redirect( + LinkHandler::getInstance()->getControllerLink($className, $parameters, $url), + true, + false + ); + exit; + } } diff --git a/wcfsetup/install/files/lib/page/ArticleFeedPage.class.php b/wcfsetup/install/files/lib/page/ArticleFeedPage.class.php index d1692c44342..e0a47030be9 100644 --- a/wcfsetup/install/files/lib/page/ArticleFeedPage.class.php +++ b/wcfsetup/install/files/lib/page/ArticleFeedPage.class.php @@ -15,6 +15,7 @@ * @copyright 2001-2019 WoltLab GmbH * @license GNU Lesser General Public License * @since 3.0 + * @deprecated 6.1 use `ArticleRssFeedPage` instead */ class ArticleFeedPage extends AbstractFeedPage { @@ -47,6 +48,8 @@ public function readParameters() throw new PermissionDeniedException(); } } + + $this->redirectToNewPage(ArticleRssFeedPage::class); } /** From b5eb998ac5d17e9bf14bbdc05cd32016f895ce4d Mon Sep 17 00:00:00 2001 From: Marcel Werk Date: Sun, 4 Feb 2024 14:48:44 +0100 Subject: [PATCH 07/10] Deprecate old code --- .../install/files/lib/data/IFeedEntryWithEnclosure.class.php | 1 + wcfsetup/install/files/lib/data/article/FeedArticle.class.php | 1 + .../install/files/lib/data/article/FeedArticleList.class.php | 1 + 3 files changed, 3 insertions(+) diff --git a/wcfsetup/install/files/lib/data/IFeedEntryWithEnclosure.class.php b/wcfsetup/install/files/lib/data/IFeedEntryWithEnclosure.class.php index 9ff7df58ec2..132cf8c5ec2 100644 --- a/wcfsetup/install/files/lib/data/IFeedEntryWithEnclosure.class.php +++ b/wcfsetup/install/files/lib/data/IFeedEntryWithEnclosure.class.php @@ -10,6 +10,7 @@ * @author Marcel Werk * @copyright 2001-2019 WoltLab GmbH * @license GNU Lesser General Public License + * @deprecated 6.1 use `wcf\system\rssFeed\RssFeedItem` instead */ interface IFeedEntryWithEnclosure extends IFeedEntry { diff --git a/wcfsetup/install/files/lib/data/article/FeedArticle.class.php b/wcfsetup/install/files/lib/data/article/FeedArticle.class.php index 5b839da19f6..a1ba081d5aa 100644 --- a/wcfsetup/install/files/lib/data/article/FeedArticle.class.php +++ b/wcfsetup/install/files/lib/data/article/FeedArticle.class.php @@ -14,6 +14,7 @@ * @copyright 2001-2019 WoltLab GmbH * @license GNU Lesser General Public License * @since 3.0 + * @deprecated 6.1 */ class FeedArticle extends ViewableArticle implements IFeedEntryWithEnclosure { diff --git a/wcfsetup/install/files/lib/data/article/FeedArticleList.class.php b/wcfsetup/install/files/lib/data/article/FeedArticleList.class.php index af6642e36c5..aa7926cf3b9 100644 --- a/wcfsetup/install/files/lib/data/article/FeedArticleList.class.php +++ b/wcfsetup/install/files/lib/data/article/FeedArticleList.class.php @@ -9,6 +9,7 @@ * @copyright 2001-2019 WoltLab GmbH * @license GNU Lesser General Public License * @since 3.0 + * @deprecated 6.1 * * @method FeedArticle current() * @method FeedArticle[] getObjects() From c049e54a2064d174a748e1a97280403c743e1482 Mon Sep 17 00:00:00 2001 From: Marcel Werk Date: Sun, 4 Feb 2024 15:12:50 +0100 Subject: [PATCH 08/10] Add missing phpdoc --- .../install/files/lib/system/rssFeed/RssFeed.class.php | 8 ++++++++ .../files/lib/system/rssFeed/RssFeedCategory.class.php | 8 ++++++++ .../files/lib/system/rssFeed/RssFeedChannel.class.php | 8 ++++++++ .../files/lib/system/rssFeed/RssFeedEnclosure.class.php | 8 ++++++++ .../files/lib/system/rssFeed/RssFeedItem.class.php | 8 ++++++++ .../files/lib/system/rssFeed/RssFeedSource.class.php | 8 ++++++++ .../install/files/lib/system/rssFeed/XmlElement.class.php | 8 ++++++++ 7 files changed, 56 insertions(+) diff --git a/wcfsetup/install/files/lib/system/rssFeed/RssFeed.class.php b/wcfsetup/install/files/lib/system/rssFeed/RssFeed.class.php index 4af7d98077a..871eee124b4 100644 --- a/wcfsetup/install/files/lib/system/rssFeed/RssFeed.class.php +++ b/wcfsetup/install/files/lib/system/rssFeed/RssFeed.class.php @@ -2,6 +2,14 @@ namespace wcf\system\rssFeed; +/** + * Represents an rss feed. + * + * @author Marcel Werk + * @copyright 2001-2024 WoltLab GmbH + * @license GNU Lesser General Public License + * @since 6.1 + */ final class RssFeed { /** diff --git a/wcfsetup/install/files/lib/system/rssFeed/RssFeedCategory.class.php b/wcfsetup/install/files/lib/system/rssFeed/RssFeedCategory.class.php index 1bac8b54cbb..e3f7497410f 100644 --- a/wcfsetup/install/files/lib/system/rssFeed/RssFeedCategory.class.php +++ b/wcfsetup/install/files/lib/system/rssFeed/RssFeedCategory.class.php @@ -2,6 +2,14 @@ namespace wcf\system\rssFeed; +/** + * Represents an rss feed category. + * + * @author Marcel Werk + * @copyright 2001-2024 WoltLab GmbH + * @license GNU Lesser General Public License + * @since 6.1 + */ final class RssFeedCategory { public function __construct( diff --git a/wcfsetup/install/files/lib/system/rssFeed/RssFeedChannel.class.php b/wcfsetup/install/files/lib/system/rssFeed/RssFeedChannel.class.php index ecbed1aedb4..d5aa375e404 100644 --- a/wcfsetup/install/files/lib/system/rssFeed/RssFeedChannel.class.php +++ b/wcfsetup/install/files/lib/system/rssFeed/RssFeedChannel.class.php @@ -4,6 +4,14 @@ use BadMethodCallException; +/** + * Represents an rss feed channel. + * + * @author Marcel Werk + * @copyright 2001-2024 WoltLab GmbH + * @license GNU Lesser General Public License + * @since 6.1 + */ final class RssFeedChannel { private string $title; diff --git a/wcfsetup/install/files/lib/system/rssFeed/RssFeedEnclosure.class.php b/wcfsetup/install/files/lib/system/rssFeed/RssFeedEnclosure.class.php index f87419767ca..664940772db 100644 --- a/wcfsetup/install/files/lib/system/rssFeed/RssFeedEnclosure.class.php +++ b/wcfsetup/install/files/lib/system/rssFeed/RssFeedEnclosure.class.php @@ -2,6 +2,14 @@ namespace wcf\system\rssFeed; +/** + * Represents an rss feed enclosure. + * + * @author Marcel Werk + * @copyright 2001-2024 WoltLab GmbH + * @license GNU Lesser General Public License + * @since 6.1 + */ final class RssFeedEnclosure { public function __construct( diff --git a/wcfsetup/install/files/lib/system/rssFeed/RssFeedItem.class.php b/wcfsetup/install/files/lib/system/rssFeed/RssFeedItem.class.php index 0599a427e4d..202ddfca4e6 100644 --- a/wcfsetup/install/files/lib/system/rssFeed/RssFeedItem.class.php +++ b/wcfsetup/install/files/lib/system/rssFeed/RssFeedItem.class.php @@ -4,6 +4,14 @@ use BadMethodCallException; +/** + * Represents an rss feed item. + * + * @author Marcel Werk + * @copyright 2001-2024 WoltLab GmbH + * @license GNU Lesser General Public License + * @since 6.1 + */ final class RssFeedItem { private string $title; diff --git a/wcfsetup/install/files/lib/system/rssFeed/RssFeedSource.class.php b/wcfsetup/install/files/lib/system/rssFeed/RssFeedSource.class.php index b209ef314a7..9d06ac440d5 100644 --- a/wcfsetup/install/files/lib/system/rssFeed/RssFeedSource.class.php +++ b/wcfsetup/install/files/lib/system/rssFeed/RssFeedSource.class.php @@ -2,6 +2,14 @@ namespace wcf\system\rssFeed; +/** + * Represents an rss source category. + * + * @author Marcel Werk + * @copyright 2001-2024 WoltLab GmbH + * @license GNU Lesser General Public License + * @since 6.1 + */ final class RssFeedSource { public function __construct( diff --git a/wcfsetup/install/files/lib/system/rssFeed/XmlElement.class.php b/wcfsetup/install/files/lib/system/rssFeed/XmlElement.class.php index df94cd70f72..72fc0ff5b7e 100644 --- a/wcfsetup/install/files/lib/system/rssFeed/XmlElement.class.php +++ b/wcfsetup/install/files/lib/system/rssFeed/XmlElement.class.php @@ -2,6 +2,14 @@ namespace wcf\system\rssFeed; +/** + * Simplifies the use of SimpleXMLElement within the generation of RSS feeds. + * + * @author Marcel Werk + * @copyright 2001-2024 WoltLab GmbH + * @license GNU Lesser General Public License + * @since 6.1 + */ final class XmlElement extends \SimpleXMLElement { public function addChild(string $name, ?string $value = null, ?string $namespace = null): ?static From 885219cb79d74fca23a31916753ed37ea79c83ad Mon Sep 17 00:00:00 2001 From: Marcel Werk Date: Sun, 4 Feb 2024 15:19:36 +0100 Subject: [PATCH 09/10] Use new implementation for notification feed page --- .../templates/notificationList.tpl | 4 +- .../lib/page/NotificationFeedPage.class.php | 3 + .../page/NotificationRssFeedPage.class.php | 63 +++++++++++++++++++ 3 files changed, 68 insertions(+), 2 deletions(-) create mode 100644 wcfsetup/install/files/lib/page/NotificationRssFeedPage.class.php diff --git a/com.woltlab.wcf/templates/notificationList.tpl b/com.woltlab.wcf/templates/notificationList.tpl index e5e849ec070..02660820d03 100644 --- a/com.woltlab.wcf/templates/notificationList.tpl +++ b/com.woltlab.wcf/templates/notificationList.tpl @@ -1,7 +1,7 @@ {capture assign='contentTitleBadge'}{#$__wcf->getUserNotificationHandler()->countAllNotifications()}{/capture} {capture assign='headContent'} - + {/capture} {capture assign='contentInteractionPagination'} @@ -15,7 +15,7 @@ {/capture} {capture assign='contentInteractionDropdownItems'} -
  • {lang}wcf.global.button.rss{/lang}
  • +
  • {lang}wcf.global.button.rss{/lang}
  • {/capture} {include file='header'} diff --git a/wcfsetup/install/files/lib/page/NotificationFeedPage.class.php b/wcfsetup/install/files/lib/page/NotificationFeedPage.class.php index 1a52b1ad1b2..e4c25a79e08 100644 --- a/wcfsetup/install/files/lib/page/NotificationFeedPage.class.php +++ b/wcfsetup/install/files/lib/page/NotificationFeedPage.class.php @@ -13,6 +13,7 @@ * @copyright 2001-2019 WoltLab GmbH * @license GNU Lesser General Public License * @since 3.0 + * @deprecated 6.1 use `NotificationRssFeedPage` instead */ class NotificationFeedPage extends AbstractFeedPage { @@ -28,6 +29,8 @@ public function readParameters() } $this->title = WCF::getLanguage()->get('wcf.user.menu.community.notification'); + + $this->redirectToNewPage(NotificationRssFeedPage::class); } /** diff --git a/wcfsetup/install/files/lib/page/NotificationRssFeedPage.class.php b/wcfsetup/install/files/lib/page/NotificationRssFeedPage.class.php new file mode 100644 index 00000000000..f2ff35b4aa2 --- /dev/null +++ b/wcfsetup/install/files/lib/page/NotificationRssFeedPage.class.php @@ -0,0 +1,63 @@ + + * @since 6.1 + */ +class NotificationRssFeedPage extends AbstractRssFeedPage +{ + #[\Override] + public function readParameters() + { + parent::readParameters(); + + if (!WCF::getUser()->userID) { + throw new IllegalLinkException(); + } + } + + #[\Override] + protected function getRssFeed(): RssFeed + { + $feed = new RssFeed(); + $channel = $this->getDefaultChannel(); + $channel->title(WCF::getLanguage()->get('wcf.user.menu.community.notification')); + $feed->channel($channel); + + $notifications = UserNotificationHandler::getInstance()->getNotifications(20); + if ($notifications['notifications'] !== []) { + $channel->lastBuildDateFromTimestamp($notifications['notifications'][0]['time']); + } + + foreach ($notifications['notifications'] as $notification) { + $event = $notification['event']; + \assert($event instanceof AbstractUserNotificationEvent); + + $item = new RssFeedItem(); + $item + ->title($event->getTitle()) + ->link($event->getLink()) + ->description($event->getExcerpt()) + ->pubDateFromTimestamp($event->getTime()) + ->creator($event->getAuthor()->username) + ->guid($event->getLink()) + ->contentEncoded($event->getFormattedMessage()); + $channel->item($item); + } + + return $feed; + } +} From f922c091e21891f4a2198a2c443a2370b6c15a58 Mon Sep 17 00:00:00 2001 From: Marcel Werk Date: Sun, 4 Feb 2024 15:19:45 +0100 Subject: [PATCH 10/10] Deprecate old code --- wcfsetup/install/files/lib/data/IFeedEntry.class.php | 1 + 1 file changed, 1 insertion(+) diff --git a/wcfsetup/install/files/lib/data/IFeedEntry.class.php b/wcfsetup/install/files/lib/data/IFeedEntry.class.php index 6d3698ea685..c59e6a22c47 100644 --- a/wcfsetup/install/files/lib/data/IFeedEntry.class.php +++ b/wcfsetup/install/files/lib/data/IFeedEntry.class.php @@ -8,6 +8,7 @@ * @author Tim Duesterhus * @copyright 2001-2019 WoltLab GmbH * @license GNU Lesser General Public License + * @deprecated 6.1 use `wcf\system\rssFeed\RssFeedItem` instead */ interface IFeedEntry extends IMessage {