Files
typecho/var/Typecho/Feed.php
joyqi 3caebb3b20 v1.3.0 (#1661)
* Add feed widget

* add feed render

* Add CommentPage widget

* New theme (#1390)

* 调整忽略目录

* add theme

* fix theme scss build

Co-authored-by: fen <f3nb0x@gmail.com>

* s/is_writeable/is_writable/g

* New upgrade method

* merge new fixes from master

* add pgsql ssl mode support (ref #1600) (#1623)

* Feat/code refactor (#1626)

* remove all magic methods, add type for class properties

* refactor codes

* fix all

* refactor code

* fix type

* fix all

* fix request is method

* fix all

* fix router

* fix get page

* fix 1.3.0 upgrade

* [feat] support high resolution avatar

* fix types in i18n component

* Implement Ctrl+S or Command+S for save draft (#1628)

* Implement Ctrl+S or Command+S for save draft

* rename

* add Typecho.savePost

* fix upload file size

* add new uploader

* replace new uploader

* fix textarea change

* fix preview

* refactor post edit

* fix issue

* fix page edit

---------

Co-authored-by: joyqi <joyqi@segmentfault.com>
Co-authored-by: joyqi <magike.net@gmail.com>

* fix #1632

* Add svg to image types

* Feat/tree pages (#1646)

* add tree trait

* finish category tree trait

* support select fields

* fix select fields

* refactor admin trait

* fix draft status

* Add new contents type "revision"

* minor refactor

* add more tree view abstracts

* add tree trait to pages

* get ready for tree view pages

* improve page edit

* fix revision

* fix slug

* add router params delegate

* fix params delegate

* fix

* fix

* fix all

* fix all

* fix tree

* fix page link

* fix feed

* fix page

* fix permalink

* fix permalink input

* fix offset query

* Support IDN (#1629)

* Support IDN

* use js

* Optimize code

* Optimize code

* fix URL script

* remove unnecessary use

---------

Co-authored-by: joyqi <joyqi@segmentfault.com>

* fix input element

* fix #1651, close #1653

* Use json instead of serialize (#1624)

* Use json instead of serialize

* Fix Upgrade code

* add tree trait

* finish category tree trait

* support select fields

* fix select fields

* refactor admin trait

* fix draft status

* Add new contents type "revision"

* minor refactor

* add more tree view abstracts

* add tree trait to pages

* get ready for tree view pages

* improve page edit

* fix revision

* fix slug

* add router params delegate

* fix params delegate

* fix

* fix

* fix all

* fix all

* fix tree

* fix page link

* fix feed

* fix page

* fix permalink

* fix permalink input

* fix offset query

* Fix typo

* remove proxy methods

* remove unnecessary useage

---------

Co-authored-by: joyqi <joyqi@segmentfault.com>
Co-authored-by: joyqi <magike.net@gmail.com>

* Fix Prevent XSS vulnerability in default theme (#1654)

* Fix Prevent XSS vulnerability in default theme

* Update var/Typecho/Db/Adapter/Pdo.php

* fix the getter

---------

Co-authored-by: joyqi <joyqi@segmentfault.com>

* add throwCallback to widget response

* fix: cut down fields when selecting recent posts

* fix typo errors

* fix typo errors

* fix http client cookie

* add throw finish

* fix theme lang

* fix default theme

* fix query

* add open graph and twitter card support
add canonical link

* fix canonical link meta

* fix theme classic-22

* remove unnecessary scss file when packaging

* init plugin signal

* improve: remove feather-icon js file

* fix: typo

* improve: post detail layout

* fix tags saving

* improve: nav search

* fix: theme screenshot

* fix: theme page layout

* remove php 7.2/7.3 env

---------

Co-authored-by: fen <f3nb0x@gmail.com>
Co-authored-by: Lu Fei <52o@qq52o.cn>
2023-12-30 23:02:25 +08:00

413 lines
12 KiB
PHP

<?php
namespace Typecho;
/**
* Feed
*
* @package Feed
*/
class Feed
{
/** 定义RSS 1.0类型 */
public const RSS1 = 'RSS 1.0';
/** 定义RSS 2.0类型 */
public const RSS2 = 'RSS 2.0';
/** 定义ATOM 1.0类型 */
public const ATOM1 = 'ATOM 1.0';
/** 定义RSS时间格式 */
public const DATE_RFC822 = 'r';
/** 定义ATOM时间格式 */
public const DATE_W3CDTF = 'c';
/** 定义行结束符 */
public const EOL = "\n";
/**
* feed状态
*
* @access private
* @var string
*/
private string $type;
/**
* 字符集编码
*
* @access private
* @var string
*/
private string $charset;
/**
* 语言状态
*
* @access private
* @var string
*/
private string $lang;
/**
* 聚合地址
*
* @access private
* @var string
*/
private string $feedUrl;
/**
* 基本地址
*
* @access private
* @var string
*/
private string $baseUrl;
/**
* 聚合标题
*
* @access private
* @var string
*/
private string $title;
/**
* 聚合副标题
*
* @access private
* @var string
*/
private string $subTitle;
/**
* 版本信息
*
* @access private
* @var string
*/
private string $version;
/**
* 所有的items
*
* @access private
* @var array
*/
private array $items = [];
/**
* 创建Feed对象
*
* @param $version
* @param string $type
* @param string $charset
* @param string $lang
*/
public function __construct($version, string $type = self::RSS2, string $charset = 'UTF-8', string $lang = 'en')
{
$this->version = $version;
$this->type = $type;
$this->charset = $charset;
$this->lang = $lang;
}
/**
* @return string
*/
public function getType(): string
{
return $this->type;
}
/**
* 设置标题
*
* @param string $title 标题
*/
public function setTitle(string $title)
{
$this->title = $title;
}
/**
* 设置副标题
*
* @param string|null $subTitle 副标题
*/
public function setSubTitle(?string $subTitle)
{
$this->subTitle = $subTitle;
}
/**
* 设置聚合地址
*
* @param string $feedUrl 聚合地址
*/
public function setFeedUrl(string $feedUrl)
{
$this->feedUrl = $feedUrl;
}
/**
* @return string
*/
public function getFeedUrl(): string
{
return $this->feedUrl;
}
/**
* 设置主页
*
* @param string $baseUrl 主页地址
*/
public function setBaseUrl(string $baseUrl)
{
$this->baseUrl = $baseUrl;
}
/**
* $item的格式为
* <code>
* array (
* 'title' => 'xxx',
* 'content' => 'xxx',
* 'excerpt' => 'xxx',
* 'date' => 'xxx',
* 'link' => 'xxx',
* 'author' => 'xxx',
* 'comments' => 'xxx',
* 'commentsUrl'=> 'xxx',
* 'commentsFeedUrl' => 'xxx',
* )
* </code>
*
* @param array $item
*/
public function addItem(array $item)
{
$this->items[] = $item;
}
/**
* 输出字符串
*
* @return string
*/
public function __toString(): string
{
$result = '<?xml version="1.0" encoding="' . $this->charset . '"?>' . self::EOL;
if (self::RSS1 == $this->type) {
$result .= '<rdf:RDF
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns="http://purl.org/rss/1.0/"
xmlns:dc="http://purl.org/dc/elements/1.1/">' . self::EOL;
$content = '';
$links = [];
$lastUpdate = 0;
foreach ($this->items as $item) {
$content .= '<item rdf:about="' . $item['link'] . '">' . self::EOL;
$content .= '<title>' . htmlspecialchars($item['title']) . '</title>' . self::EOL;
$content .= '<link>' . $item['link'] . '</link>' . self::EOL;
$content .= '<dc:date>' . $this->dateFormat($item['date']) . '</dc:date>' . self::EOL;
$content .= '<description>' . strip_tags($item['content']) . '</description>' . self::EOL;
if (!empty($item['suffix'])) {
$content .= $item['suffix'];
}
$content .= '</item>' . self::EOL;
$links[] = $item['link'];
if ($item['date'] > $lastUpdate) {
$lastUpdate = $item['date'];
}
}
$result .= '<channel rdf:about="' . $this->feedUrl . '">
<title>' . htmlspecialchars($this->title) . '</title>
<link>' . $this->baseUrl . '</link>
<description>' . htmlspecialchars($this->subTitle ?? '') . '</description>
<items>
<rdf:Seq>' . self::EOL;
foreach ($links as $link) {
$result .= '<rdf:li resource="' . $link . '"/>' . self::EOL;
}
$result .= '</rdf:Seq>
</items>
</channel>' . self::EOL;
$result .= $content . '</rdf:RDF>';
} elseif (self::RSS2 == $this->type) {
$result .= '<rss version="2.0"
xmlns:content="http://purl.org/rss/1.0/modules/content/"
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
xmlns:atom="http://www.w3.org/2005/Atom"
xmlns:wfw="http://wellformedweb.org/CommentAPI/">
<channel>' . self::EOL;
$content = '';
$lastUpdate = 0;
foreach ($this->items as $item) {
$content .= '<item>' . self::EOL;
$content .= '<title>' . htmlspecialchars($item['title']) . '</title>' . self::EOL;
$content .= '<link>' . $item['link'] . '</link>' . self::EOL;
$content .= '<guid>' . $item['link'] . '</guid>' . self::EOL;
$content .= '<pubDate>' . $this->dateFormat($item['date']) . '</pubDate>' . self::EOL;
$content .= '<dc:creator>' . htmlspecialchars($item['author']->screenName)
. '</dc:creator>' . self::EOL;
if (!empty($item['category']) && is_array($item['category'])) {
foreach ($item['category'] as $category) {
$content .= '<category><![CDATA[' . $category['name'] . ']]></category>' . self::EOL;
}
}
if (!empty($item['excerpt'])) {
$content .= '<description><![CDATA[' . strip_tags($item['excerpt'])
. ']]></description>' . self::EOL;
}
if (!empty($item['content'])) {
$content .= '<content:encoded xml:lang="' . $this->lang . '"><![CDATA['
. self::EOL .
$item['content'] . self::EOL .
']]></content:encoded>' . self::EOL;
}
if (isset($item['comments']) && strlen($item['comments']) > 0) {
$content .= '<slash:comments>' . $item['comments'] . '</slash:comments>' . self::EOL;
}
$content .= '<comments>' . $item['link'] . '#comments</comments>' . self::EOL;
if (!empty($item['commentsFeedUrl'])) {
$content .= '<wfw:commentRss>' . $item['commentsFeedUrl'] . '</wfw:commentRss>' . self::EOL;
}
if (!empty($item['suffix'])) {
$content .= $item['suffix'];
}
$content .= '</item>' . self::EOL;
if ($item['date'] > $lastUpdate) {
$lastUpdate = $item['date'];
}
}
$result .= '<title>' . htmlspecialchars($this->title) . '</title>
<link>' . $this->baseUrl . '</link>
<atom:link href="' . $this->feedUrl . '" rel="self" type="application/rss+xml" />
<language>' . $this->lang . '</language>
<description>' . htmlspecialchars($this->subTitle ?? '') . '</description>
<lastBuildDate>' . $this->dateFormat($lastUpdate) . '</lastBuildDate>
<pubDate>' . $this->dateFormat($lastUpdate) . '</pubDate>' . self::EOL;
$result .= $content . '</channel>
</rss>';
} elseif (self::ATOM1 == $this->type) {
$result .= '<feed xmlns="http://www.w3.org/2005/Atom"
xmlns:thr="http://purl.org/syndication/thread/1.0"
xml:lang="' . $this->lang . '"
xml:base="' . $this->baseUrl . '"
>' . self::EOL;
$content = '';
$lastUpdate = 0;
foreach ($this->items as $item) {
$content .= '<entry>' . self::EOL;
$content .= '<title type="html"><![CDATA[' . $item['title'] . ']]></title>' . self::EOL;
$content .= '<link rel="alternate" type="text/html" href="' . $item['link'] . '" />' . self::EOL;
$content .= '<id>' . $item['link'] . '</id>' . self::EOL;
$content .= '<updated>' . $this->dateFormat($item['date']) . '</updated>' . self::EOL;
$content .= '<published>' . $this->dateFormat($item['date']) . '</published>' . self::EOL;
$content .= '<author>
<name>' . $item['author']->screenName . '</name>
<uri>' . $item['author']->url . '</uri>
</author>' . self::EOL;
if (!empty($item['category']) && is_array($item['category'])) {
foreach ($item['category'] as $category) {
$content .= '<category scheme="' . $category['permalink'] . '" term="'
. $category['name'] . '" />' . self::EOL;
}
}
if (!empty($item['excerpt'])) {
$content .= '<summary type="html"><![CDATA[' . htmlspecialchars($item['excerpt'])
. ']]></summary>' . self::EOL;
}
if (!empty($item['content'])) {
$content .= '<content type="html" xml:base="' . $item['link']
. '" xml:lang="' . $this->lang . '"><![CDATA['
. self::EOL .
$item['content'] . self::EOL .
']]></content>' . self::EOL;
}
if (isset($item['comments']) && strlen($item['comments']) > 0) {
$content .= '<link rel="replies" type="text/html" href="' . $item['link']
. '#comments" thr:count="' . $item['comments'] . '" />' . self::EOL;
if (!empty($item['commentsFeedUrl'])) {
$content .= '<link rel="replies" type="application/atom+xml" href="'
. $item['commentsFeedUrl'] . '" thr:count="' . $item['comments'] . '"/>' . self::EOL;
}
}
if (!empty($item['suffix'])) {
$content .= $item['suffix'];
}
$content .= '</entry>' . self::EOL;
if ($item['date'] > $lastUpdate) {
$lastUpdate = $item['date'];
}
}
$result .= '<title type="text">' . htmlspecialchars($this->title) . '</title>
<subtitle type="text">' . htmlspecialchars($this->subTitle ?? '') . '</subtitle>
<updated>' . $this->dateFormat($lastUpdate) . '</updated>
<generator uri="https://typecho.org/" version="' . $this->version . '">Typecho</generator>
<link rel="alternate" type="text/html" href="' . $this->baseUrl . '" />
<id>' . $this->feedUrl . '</id>
<link rel="self" type="application/atom+xml" href="' . $this->feedUrl . '" />
';
$result .= $content . '</feed>';
}
return $result;
}
/**
* 获取Feed时间格式
*
* @param integer $stamp 时间戳
* @return string
*/
public function dateFormat(int $stamp): string
{
if (self::RSS2 == $this->type) {
return date(self::DATE_RFC822, $stamp);
} elseif (self::RSS1 == $this->type || self::ATOM1 == $this->type) {
return date(self::DATE_W3CDTF, $stamp);
}
return '';
}
}