This commit is contained in:
root
2026-03-04 00:23:03 +08:00
commit 6136d791f2
611 changed files with 65539 additions and 0 deletions

515
var/Widget/Base/Comments.php Executable file
View File

@@ -0,0 +1,515 @@
<?php
namespace Widget\Base;
use Typecho\Common;
use Typecho\Date;
use Typecho\Db\Exception;
use Typecho\Db\Query;
use Typecho\Router;
use Typecho\Router\ParamsDelegateInterface;
use Utils\AutoP;
use Utils\Markdown;
use Widget\Base;
use Widget\Contents\From;
if (!defined('__TYPECHO_ROOT_DIR__')) {
exit;
}
/**
* 评论基类
*
* @property int $coid
* @property int $cid
* @property int $created
* @property string author
* @property int $authorId
* @property int $ownerId
* @property string $mail
* @property string $url
* @property string $ip
* @property string $agent
* @property string $text
* @property string $type
* @property string status
* @property int $parent
* @property int $commentPage
* @property Date $date
* @property string $dateWord
* @property string $theId
* @property Contents $parentContent
* @property string $title
* @property string $permalink
* @property string $content
*/
class Comments extends Base implements QueryInterface, RowFilterInterface, PrimaryKeyInterface, ParamsDelegateInterface
{
/**
* @return string 获取主键
*/
public function getPrimaryKey(): string
{
return 'coid';
}
/**
* @param string $key
* @return string
*/
public function getRouterParam(string $key): string
{
switch ($key) {
case 'permalink':
return $this->parentContent->path;
case 'commentPage':
return $this->commentPage;
default:
return '{' . $key . '}';
}
}
/**
* 增加评论
*
* @param array $rows 评论结构数组
* @return integer
* @throws Exception
*/
public function insert(array $rows): int
{
/** 构建插入结构 */
$insertStruct = [
'cid' => $rows['cid'],
'created' => empty($rows['created']) ? $this->options->time : $rows['created'],
'author' => Common::strBy($rows['author'] ?? null),
'authorId' => empty($rows['authorId']) ? 0 : $rows['authorId'],
'ownerId' => empty($rows['ownerId']) ? 0 : $rows['ownerId'],
'mail' => Common::strBy($rows['mail'] ?? null),
'url' => Common::strBy($rows['url'] ?? null),
'ip' => Common::strBy($rows['ip'] ?? null, $this->request->getIp()),
'agent' => Common::strBy($rows['agent'] ?? null, $this->request->getAgent()),
'text' => Common::strBy($rows['text'] ?? null),
'type' => Common::strBy($rows['type'] ?? null, 'comment'),
'status' => Common::strBy($rows['status'] ?? null, 'approved'),
'parent' => empty($rows['parent']) ? 0 : $rows['parent'],
];
if (!empty($rows['coid'])) {
$insertStruct['coid'] = $rows['coid'];
}
/** 过长的客户端字符串要截断 */
if (Common::strLen($insertStruct['agent']) > 511) {
$insertStruct['agent'] = Common::subStr($insertStruct['agent'], 0, 511, '');
}
/** 首先插入部分数据 */
$insertId = $this->db->query($this->db->insert('table.comments')->rows($insertStruct));
/** 更新评论数 */
$num = $this->db->fetchObject($this->db->select(['COUNT(coid)' => 'num'])->from('table.comments')
->where('status = ? AND cid = ?', 'approved', $rows['cid']))->num;
$this->db->query($this->db->update('table.contents')->rows(['commentsNum' => $num])
->where('cid = ?', $rows['cid']));
return $insertId;
}
/**
* 更新评论
*
* @param array $rows 评论结构数组
* @param Query $condition 查询对象
* @return integer
* @throws Exception
*/
public function update(array $rows, Query $condition): int
{
/** 获取内容主键 */
$updateCondition = clone $condition;
$updateComment = $this->db->fetchObject($condition->select('cid')->from('table.comments')->limit(1));
if ($updateComment) {
$cid = $updateComment->cid;
} else {
return 0;
}
/** 构建插入结构 */
$preUpdateStruct = [
'author' => Common::strBy($rows['author'] ?? null),
'mail' => Common::strBy($rows['mail'] ?? null),
'url' => Common::strBy($rows['url'] ?? null),
'text' => Common::strBy($rows['text'] ?? null),
'status' => Common::strBy($rows['status'] ?? null, 'approved'),
];
$updateStruct = [];
foreach ($rows as $key => $val) {
if ((array_key_exists($key, $preUpdateStruct))) {
$updateStruct[$key] = $preUpdateStruct[$key];
}
}
/** 更新创建时间 */
if (!empty($rows['created'])) {
$updateStruct['created'] = $rows['created'];
}
/** 更新评论数据 */
$updateRows = $this->db->query($updateCondition->update('table.comments')->rows($updateStruct));
/** 更新评论数 */
$num = $this->db->fetchObject($this->db->select(['COUNT(coid)' => 'num'])->from('table.comments')
->where('status = ? AND cid = ?', 'approved', $cid))->num;
$this->db->query($this->db->update('table.contents')->rows(['commentsNum' => $num])
->where('cid = ?', $cid));
return $updateRows;
}
/**
* 删除数据
*
* @param Query $condition 查询对象
* @return integer
* @throws Exception
*/
public function delete(Query $condition): int
{
/** 获取内容主键 */
$deleteCondition = clone $condition;
$deleteComment = $this->db->fetchObject($condition->select('cid')->from('table.comments')->limit(1));
if ($deleteComment) {
$cid = $deleteComment->cid;
} else {
return 0;
}
/** 删除评论数据 */
$deleteRows = $this->db->query($deleteCondition->delete('table.comments'));
/** 更新评论数 */
$num = $this->db->fetchObject($this->db->select(['COUNT(coid)' => 'num'])->from('table.comments')
->where('status = ? AND cid = ?', 'approved', $cid))->num;
$this->db->query($this->db->update('table.contents')->rows(['commentsNum' => $num])
->where('cid = ?', $cid));
return $deleteRows;
}
/**
* 按照条件计算评论数量
*
* @param Query $condition 查询对象
* @return integer
* @throws Exception
*/
public function size(Query $condition): int
{
return $this->db->fetchObject($condition->select(['COUNT(coid)' => 'num'])->from('table.comments'))->num;
}
/**
* 将每行的值压入堆栈
*
* @param array $value 每行的值
* @return array
*/
public function push(array $value): array
{
$value = $this->filter($value);
return parent::push($value);
}
/**
* 通用过滤器
*
* @param array $row 需要过滤的行数据
* @return array
*/
public function filter(array $row): array
{
/** 处理默认空值 */
$row['author'] = $row['author'] ?? '';
$row['mail'] = $row['mail'] ?? '';
$row['url'] = $row['url'] ?? '';
$row['ip'] = $row['ip'] ?? '';
$row['agent'] = $row['agent'] ?? '';
$row['text'] = $row['text'] ?? '';
$row['date'] = new Date($row['created']);
return Comments::pluginHandle()->filter('filter', $row, $this);
}
/**
* 输出文章发布日期
*
* @param string|null $format 日期格式
*/
public function date(?string $format = null)
{
echo $this->date->format(empty($format) ? $this->options->commentDateFormat : $format);
}
/**
* 输出作者相关
*
* @param boolean|null $autoLink 是否自动加上链接
* @param boolean|null $noFollow 是否加上nofollow标签
*/
public function author(?bool $autoLink = null, ?bool $noFollow = null)
{
$autoLink = (null === $autoLink) ? $this->options->commentsShowUrl : $autoLink;
$noFollow = (null === $noFollow) ? $this->options->commentsUrlNofollow : $noFollow;
if ($this->url && $autoLink) {
echo '<a href="' . Common::safeUrl($this->url) . '"'
. ($noFollow ? ' rel="external nofollow"' : null) . '>' . $this->author . '</a>';
} else {
echo $this->author;
}
}
/**
* 调用gravatar输出用户头像
*
* @param integer $size 头像尺寸
* @param string|null $default 默认输出头像
*/
public function gravatar(int $size = 32, ?string $default = null, $highRes = false)
{
if ($this->options->commentsAvatar && 'comment' == $this->type) {
$rating = $this->options->commentsAvatarRating;
Comments::pluginHandle()->trigger($plugged)->call('gravatar', $size, $rating, $default, $this);
if (!$plugged) {
$url = Common::gravatarUrl($this->mail, $size, $rating, $default, $this->request->isSecure());
$srcset = '';
if ($highRes) {
$url2x = Common::gravatarUrl($this->mail, $size * 2, $rating, $default, $this->request->isSecure());
$url3x = Common::gravatarUrl($this->mail, $size * 3, $rating, $default, $this->request->isSecure());
$srcset = ' srcset="' . $url2x . ' 2x, ' . $url3x . ' 3x"';
}
echo '<img class="avatar" loading="lazy" src="' . $url . '"' . $srcset . ' alt="' .
$this->author . '" width="' . $size . '" height="' . $size . '" />';
}
}
}
/**
* 输出评论摘要
*
* @param integer $length 摘要截取长度
* @param string $trim 摘要后缀
*/
public function excerpt(int $length = 100, string $trim = '...')
{
echo Common::subStr(strip_tags($this->content), 0, $length, $trim);
}
/**
* 输出邮箱地址
*
* @param bool $link
* @return void
*/
public function mail(bool $link = false)
{
$mail = htmlspecialchars($this->mail);
echo $link ? 'mailto:' . $mail : $mail;
}
/**
* 获取查询对象
*
* @param mixed $fields
* @return Query
*/
public function select(...$fields): Query
{
return $this->db->select(...$fields)->from('table.comments');
}
/**
* markdown
*
* @param string|null $text
* @return string|null
*/
public function markdown(?string $text): ?string
{
$html = Comments::pluginHandle()->trigger($parsed)->filter('markdown', $text);
if (!$parsed) {
$html = Markdown::convert($text);
}
return $html;
}
/**
* autoP
*
* @param string|null $text
* @return string|null
*/
public function autoP(?string $text): ?string
{
$html = Comments::pluginHandle()->trigger($parsed)->filter('autoP', $text);
if (!$parsed) {
static $parser;
if (empty($parser)) {
$parser = new AutoP();
}
$html = $parser->parse($text);
}
return $html;
}
/**
* 获取当前内容结构
*
* @return Contents
*/
protected function ___parentContent(): Contents
{
return From::allocWithAlias($this->cid, ['cid' => $this->cid]);
}
/**
* 获取当前评论标题
*
* @return string|null
*/
protected function ___title(): ?string
{
return $this->parentContent->title;
}
/**
* 获取当前评论页码
*
* @return int
*/
protected function ___commentPage(): int
{
if ($this->options->commentsPageBreak) {
$coid = $this->coid;
$parent = $this->parent;
while ($parent > 0 && $this->options->commentsThreaded) {
$parentRows = $this->db->fetchRow($this->db->select('parent')->from('table.comments')
->where('coid = ? AND status = ?', $parent, 'approved')->limit(1));
if (!empty($parentRows)) {
$coid = $parent;
$parent = $parentRows['parent'];
} else {
break;
}
}
$select = $this->db->select('coid', 'parent')
->from('table.comments')
->where(
'cid = ? AND (status = ? OR coid = ?)',
$this->cid,
'approved',
$this->status !== 'approved' ? $this->coid : 0
)
->where('coid ' . ('DESC' == $this->options->commentsOrder ? '>=' : '<=') . ' ?', $coid)
->order('coid');
if ($this->options->commentsShowCommentOnly) {
$select->where('type = ?', 'comment');
}
$comments = $this->db->fetchAll($select);
$commentsMap = [];
$total = 0;
foreach ($comments as $comment) {
$commentsMap[$comment['coid']] = $comment['parent'];
if (0 == $comment['parent'] || !isset($commentsMap[$comment['parent']])) {
$total++;
}
}
return ceil($total / $this->options->commentsPageSize);
}
return 0;
}
/**
* 获取当前评论链接
*
* @return string
* @throws Exception
*/
protected function ___permalink(): string
{
if ($this->options->commentsPageBreak) {
return Router::url(
'comment_page',
$this,
$this->options->index
) . '#' . $this->theId;
}
return $this->parentContent->permalink . '#' . $this->theId;
}
/**
* 获取当前评论内容
*
* @return string|null
*/
protected function ___content(): ?string
{
$text = $this->parentContent->hidden ? _t('内容被隐藏') : $this->text;
$text = Comments::pluginHandle()->trigger($plugged)->filter('content', $text, $this);
if (!$plugged) {
$text = $this->options->commentsMarkdown ? $this->markdown($text)
: $this->autoP($text);
}
$text = Comments::pluginHandle()->filter('contentEx', $text, $this);
return Common::stripTags($text, '<p><br>' . $this->options->commentsHTMLTagAllowed);
}
/**
* 输出词义化日期
*
* @return string
*/
protected function ___dateWord(): string
{
return $this->date->word();
}
/**
* 锚点id
*
* @return string
*/
protected function ___theId(): string
{
return $this->type . '-' . $this->coid;
}
}

961
var/Widget/Base/Contents.php Executable file
View File

@@ -0,0 +1,961 @@
<?php
namespace Widget\Base;
use Typecho\Common;
use Typecho\Config;
use Typecho\Cookie;
use Typecho\Date;
use Typecho\Db\Exception;
use Typecho\Db\Query;
use Typecho\Plugin;
use Typecho\Router;
use Typecho\Router\ParamsDelegateInterface;
use Typecho\Widget;
use Utils\AutoP;
use Utils\Markdown;
use Widget\Base;
use Widget\Metas\Category\Rows;
use Widget\Upload;
use Widget\Users\Author;
use Widget\Metas\Category\Related as CategoryRelated;
use Widget\Metas\Tag\Related as TagRelated;
if (!defined('__TYPECHO_ROOT_DIR__')) {
exit;
}
/**
* 内容基类
*
* @property int $cid
* @property string $title
* @property string $slug
* @property int $created
* @property int $modified
* @property string $text
* @property int $order
* @property int $authorId
* @property string $template
* @property string $type
* @property string $status
* @property string|null $password
* @property int $commentsNum
* @property bool $allowComment
* @property bool $allowPing
* @property bool $allowFeed
* @property int $parent
* @property-read Users $author
* @property-read string $permalink
* @property-read string $path
* @property-read string $url
* @property-read string $feedUrl
* @property-read string $feedRssUrl
* @property-read string $feedAtomUrl
* @property-read bool $isMarkdown
* @property-read bool $hidden
* @property-read Date $date
* @property-read string $dateWord
* @property-read string[] $directory
* @property-read array[] $tags
* @property-read array[] $categories
* @property-read string $excerpt
* @property-read string $plainExcerpt
* @property-read string $summary
* @property-read string $content
* @property-read Config $fields
* @property-read Config $attachment
* @property-read string $theId
* @property-read string $respondId
* @property-read string $commentUrl
* @property-read string $trackbackUrl
* @property-read string $responseUrl
* @property-read string $year
* @property-read string $month
* @property-read string $day
*/
class Contents extends Base implements QueryInterface, RowFilterInterface, PrimaryKeyInterface, ParamsDelegateInterface
{
/**
* @return string 获取主键
*/
public function getPrimaryKey(): string
{
return 'cid';
}
/**
* @param string $key
* @return string
*/
public function getRouterParam(string $key): string
{
switch ($key) {
case 'cid':
return $this->cid;
case 'slug':
return urlencode($this->slug);
case 'directory':
return implode('/', array_map('urlencode', $this->directory));
case 'category':
return empty($this->categories) ? '' : urlencode($this->categories[0]['slug']);
case 'year':
return $this->date->year;
case 'month':
return $this->date->month;
case 'day':
return $this->date->day;
default:
return '{' . $key . '}';
}
}
/**
* 获取查询对象
*
* @param mixed $fields
* @return Query
*/
public function select(...$fields): Query
{
return $this->db->select(...$fields)->from('table.contents');
}
/**
* 插入内容
*
* @param array $rows 内容数组
* @return integer
* @throws Exception
*/
public function insert(array $rows): int
{
/** 构建插入结构 */
$insertStruct = [
'title' => !isset($rows['title']) || strlen($rows['title']) === 0
? null : htmlspecialchars($rows['title']),
'created' => empty($rows['created']) ? $this->options->time : $rows['created'],
'modified' => $this->options->time,
'text' => Common::strBy($rows['text'] ?? null),
'order' => empty($rows['order']) ? 0 : intval($rows['order']),
'authorId' => $rows['authorId'] ?? $this->user->uid,
'template' => Common::strBy($rows['template'] ?? null),
'type' => Common::strBy($rows['type'] ?? null, 'post'),
'status' => Common::strBy($rows['status'] ?? null, 'publish'),
'password' => Common::strBy($rows['password'] ?? null),
'commentsNum' => empty($rows['commentsNum']) ? 0 : $rows['commentsNum'],
'allowComment' => !empty($rows['allowComment']) && 1 == $rows['allowComment'] ? 1 : 0,
'allowPing' => !empty($rows['allowPing']) && 1 == $rows['allowPing'] ? 1 : 0,
'allowFeed' => !empty($rows['allowFeed']) && 1 == $rows['allowFeed'] ? 1 : 0,
'parent' => empty($rows['parent']) ? 0 : intval($rows['parent'])
];
if (!empty($rows['cid'])) {
$insertStruct['cid'] = $rows['cid'];
}
/** 首先插入部分数据 */
$insertId = $this->db->query($this->db->insert('table.contents')->rows($insertStruct));
/** 更新缩略名 */
if ($insertId > 0) {
$this->applySlug(Common::strBy($rows['slug'] ?? null), $insertId, $insertStruct['title']);
}
return $insertId;
}
/**
* 为内容应用缩略名
*
* @param string|null $slug 缩略名
* @param mixed $cid 内容id
* @param string $title 标题
* @return string
* @throws Exception
*/
public function applySlug(?string $slug, $cid, string $title = ''): string
{
if ($cid instanceof Query) {
$cid = $this->db->fetchObject($cid->select('cid')
->from('table.contents')->limit(1))->cid;
}
/** 生成一个非空的缩略名 */
if ((!isset($slug) || strlen($slug) === 0) && preg_match_all("/\w+/", $title, $matches)) {
$slug = implode('-', $matches[0]);
}
$slug = Common::slugName($slug, $cid);
$result = $slug;
/** 对草稿的slug做特殊处理 */
$draft = $this->db->fetchObject($this->db->select('type', 'parent')
->from('table.contents')->where('cid = ?', $cid));
if ('revision' === $draft->type && $draft->parent) {
$result = '@' . $result;
}
/** 判断是否在数据库中已经存在 */
$count = 1;
while (
$this->db->fetchObject($this->db->select(['COUNT(cid)' => 'num'])
->from('table.contents')->where('slug = ? AND cid <> ?', $result, $cid))->num > 0
) {
$result = $slug . '-' . $count;
$count++;
}
$this->db->query($this->db->update('table.contents')->rows(['slug' => $result])
->where('cid = ?', $cid));
return $result;
}
/**
* 更新内容
*
* @param array $rows 内容数组
* @param Query $condition 更新条件
* @return integer
* @throws Exception
*/
public function update(array $rows, Query $condition): int
{
/** 首先验证写入权限 */
if (!$this->isWriteable(clone $condition)) {
return 0;
}
/** 构建更新结构 */
$preUpdateStruct = [
'title' => !isset($rows['title']) || strlen($rows['title']) === 0
? null : htmlspecialchars($rows['title']),
'order' => empty($rows['order']) ? 0 : intval($rows['order']),
'text' => Common::strBy($rows['text'] ?? null),
'template' => Common::strBy($rows['template'] ?? null),
'type' => Common::strBy($rows['type'] ?? null, 'post'),
'status' => Common::strBy($rows['status'] ?? null, 'publish'),
'password' => Common::strBy($rows['password'] ?? null),
'allowComment' => !empty($rows['allowComment']) && 1 == $rows['allowComment'] ? 1 : 0,
'allowPing' => !empty($rows['allowPing']) && 1 == $rows['allowPing'] ? 1 : 0,
'allowFeed' => !empty($rows['allowFeed']) && 1 == $rows['allowFeed'] ? 1 : 0,
'parent' => empty($rows['parent']) ? 0 : intval($rows['parent'])
];
$updateStruct = [];
foreach ($rows as $key => $val) {
if (array_key_exists($key, $preUpdateStruct)) {
$updateStruct[$key] = $preUpdateStruct[$key];
}
}
/** 更新创建时间 */
if (isset($rows['created'])) {
$updateStruct['created'] = $rows['created'];
}
$updateStruct['modified'] = $this->options->time;
/** 首先插入部分数据 */
$updateCondition = clone $condition;
$updateRows = $this->db->query($condition->update('table.contents')->rows($updateStruct));
/** 更新缩略名 */
if ($updateRows > 0 && isset($rows['slug'])) {
$this->applySlug(strlen($rows['slug']) === 0 ? null : $rows['slug'], $updateCondition);
}
return $updateRows;
}
/**
* 内容是否可以被修改
*
* @param Query $condition 条件
* @return bool
* @throws Exception
*/
public function isWriteable(Query $condition): bool
{
$post = $this->db->fetchRow($condition->select('authorId')->from('table.contents')->limit(1));
return $post && ($this->user->pass('editor', true) || $post['authorId'] == $this->user->uid);
}
/**
* 删除内容
*
* @param Query $condition 查询对象
* @return integer
* @throws Exception
*/
public function delete(Query $condition): int
{
return $this->db->query($condition->delete('table.contents'));
}
/**
* 按照条件计算内容数量
*
* @param Query $condition 查询对象
* @return integer
* @throws Exception
*/
public function size(Query $condition): int
{
return $this->db->fetchObject($condition
->select(['COUNT(DISTINCT table.contents.cid)' => 'num'])
->from('table.contents')
->cleanAttribute('group'))->num;
}
/**
* 获取当前所有自定义模板
*
* @return array
*/
public function getTemplates(): array
{
$files = glob($this->options->themeFile($this->options->theme, '*.php'));
$result = [];
foreach ($files as $file) {
$info = Plugin::parseInfo($file);
$file = basename($file);
if ('index.php' != $file && 'custom' == $info['title']) {
$result[$file] = $info['description'];
}
}
return $result;
}
/**
* 将每行的值压入堆栈
*
* @param array $value 每行的值
* @return array
*/
public function push(array $value): array
{
$value = $this->filter($value);
return parent::push($value);
}
/**
* 通用过滤器
*
* @param array $row 需要过滤的行数据
* @return array
*/
public function filter(array $row): array
{
/** 处理默认空值 */
$row['title'] = $row['title'] ?? '';
$row['text'] = $row['text'] ?? '';
$row['slug'] = $row['slug'] ?? '';
$row['password'] = $row['password'] ?? '';
$row['date'] = new Date($row['created']);
return Contents::pluginHandle()->filter('filter', $row, $this);
}
/**
* 输出文章发布日期
*
* @param string|null $format 日期格式
*/
public function date(?string $format = null)
{
echo $this->date->format(empty($format) ? $this->options->postDateFormat : $format);
}
/**
* 输出文章内容
*
* @param mixed $more 文章截取后缀
*/
public function content($more = false)
{
echo false !== $more && false !== strpos($this->text, '<!--more-->') ?
$this->excerpt
. "<p class=\"more\"><a href=\"{$this->permalink}\" title=\"{$this->title}\">{$more}</a></p>"
: $this->content;
}
/**
* 输出文章摘要
*
* @param integer $length 摘要截取长度
* @param string $trim 摘要后缀
*/
public function excerpt(int $length = 100, string $trim = '...')
{
echo Common::subStr(strip_tags($this->excerpt), 0, $length, $trim);
}
/**
* 输出标题
*
* @param integer $length 标题截取长度
* @param string $trim 截取后缀
*/
public function title(int $length = 0, string $trim = '...')
{
$title = Contents::pluginHandle()->trigger($plugged)->filter('title', $this->title, $this);
if (!$plugged) {
echo $length > 0 ? Common::subStr($this->title, 0, $length, $trim) : $this->title;
} else {
echo $title;
}
}
/**
* 输出文章评论数
*
* @param ...$args
*/
public function commentsNum(...$args)
{
if (empty($args)) {
$args[] = '%d';
}
$num = intval($this->commentsNum);
echo sprintf($args[$num] ?? array_pop($args), $num);
}
/**
* 获取文章权限
*
* @param ...$permissions
*/
public function allow(...$permissions): bool
{
$allow = true;
foreach ($permissions as $permission) {
$permission = strtolower($permission);
if ('edit' == $permission) {
$allow &= ($this->user->pass('editor', true) || $this->authorId == $this->user->uid);
} else {
/** 对自动关闭反馈功能的支持 */
if (
('ping' == $permission || 'comment' == $permission) && $this->options->commentsPostTimeout > 0 &&
$this->options->commentsAutoClose
) {
if ($this->options->time - $this->created > $this->options->commentsPostTimeout) {
return false;
}
}
$allow &= ($this->row['allow' . ucfirst($permission)] == 1) && !$this->hidden;
}
}
return $allow;
}
/**
* 输出文章分类
*
* @param string $split 多个分类之间分隔符
* @param boolean $link 是否输出链接
* @param string|null $default 如果没有则输出
*/
public function category(string $split = ',', bool $link = true, ?string $default = null)
{
if (!empty($this->categories)) {
$result = [];
foreach ($this->categories as $category) {
$result[] = $link ? "<a href=\"{$category['permalink']}\">{$category['name']}</a>" : $category['name'];
}
echo implode($split, $result);
} else {
echo $default;
}
}
/**
* 输出文章多级分类
*
* @param string $split 多个分类之间分隔符
* @param boolean $link 是否输出链接
* @param string|null $default 如果没有则输出
* @throws Widget\Exception
*/
public function directory(string $split = '/', bool $link = true, ?string $default = null)
{
$category = $this->categories[0];
$directory = Rows::alloc()->getAllParents($category['mid']);
$directory[] = $category;
if ($directory) {
$result = [];
foreach ($directory as $category) {
$result[] = $link ? '<a href="' . $category['permalink'] . '">'
. $category['name'] . '</a>' : $category['name'];
}
echo implode($split, $result);
} else {
echo $default;
}
}
/**
* 输出文章标签
*
* @param string $split 多个标签之间分隔符
* @param boolean $link 是否输出链接
* @param string|null $default 如果没有则输出
*/
public function tags(string $split = ',', bool $link = true, ?string $default = null)
{
if (!empty($this->tags)) {
$result = [];
foreach ($this->tags as $tag) {
$result[] = $link ? "<a href=\"{$tag['permalink']}\">{$tag['name']}</a>" : $tag['name'];
}
echo implode($split, $result);
} else {
echo $default;
}
}
/**
* 输出当前作者
*
* @param string $item 需要输出的项目
*/
public function author(string $item = 'screenName')
{
if ($this->have()) {
echo $this->author->{$item};
}
}
/**
* @return string
*/
protected function ___title(): string
{
return $this->hidden ? _t('此内容被密码保护') : $this->row['title'];
}
/**
* @return string
*/
protected function ___text(): string
{
if ('attachment' == $this->type) {
if ($this->attachment->isImage) {
return '<img src="' . $this->attachment->url . '" alt="' .
$this->title . '" />';
} else {
return '<a href="' . $this->attachment->url . '" title="' .
$this->title . '">' . $this->title . '</a>';
}
} elseif ($this->hidden) {
return '<form class="protected" action="' . $this->security->getTokenUrl($this->permalink)
. '" method="post">' .
'<p class="word">' . _t('请输入密码访问') . '</p>' .
'<p><input type="password" class="text" name="protectPassword" />
<input type="hidden" name="protectCID" value="' . $this->cid . '" />
<input type="submit" class="submit" value="' . _t('提交') . '" /></p>' .
'</form>';
}
return $this->isMarkdown ? substr($this->row['text'], 15) : $this->row['text'];
}
/**
* @return bool
*/
protected function ___isMarkdown(): bool
{
return 0 === strpos($this->row['text'], '<!--markdown-->');
}
/**
* 是否为隐藏文章
*
* @return bool
*/
protected function ___hidden(): bool
{
if (
strlen($this->password) > 0 &&
$this->password !== Cookie::get('protectPassword_' . $this->cid) &&
$this->authorId != $this->user->uid &&
!$this->user->pass('editor', true)
) {
return true;
}
return false;
}
/**
* @return string
*/
protected function ___path(): string
{
return Router::url($this->type, $this);
}
/**
* @return string
*/
protected function ___permalink(): string
{
return Common::url($this->path, $this->options->index);
}
/**
* @return string
*/
protected function ___url(): string
{
return $this->permalink;
}
/**
* @return string
*/
protected function ___feedUrl(): string
{
return Router::url($this->type, $this, $this->options->feedUrl);
}
/**
* @return string
*/
protected function ___feedRssUrl(): string
{
return Router::url($this->type, $this, $this->options->feedRssUrl);
}
/**
* @return string
*/
protected function ___feedAtomUrl(): string
{
return Router::url($this->type, $this, $this->options->feedAtomUrl);
}
/**
* 多级目录结构
*
* @return array
*/
protected function ___directory(): array
{
$directory = [];
if (!empty($this->categories)) {
$directory = Rows::alloc()->getAllParentsSlug($this->categories[0]['mid']);
$directory[] = $this->categories[0]['slug'];
}
return $directory;
}
/**
* @return string
*/
protected function ___category(): string
{
return empty($this->categories) ? '' : $this->categories[0]['slug'];
}
/**
* @return array
*/
protected function ___categories(): array
{
return CategoryRelated::allocWithAlias($this->cid, ['cid' => $this->cid])
->toArray(['mid', 'name', 'slug', 'description', 'order', 'parent', 'count', 'permalink']);
}
/**
* 将tags取出
*
* @return array
*/
protected function ___tags(): array
{
return TagRelated::allocWithAlias($this->cid, ['cid' => $this->cid])
->toArray(['mid', 'name', 'slug', 'description', 'count', 'permalink']);
}
/**
* 文章作者
*
* @return Users
*/
protected function ___author(): Users
{
return Author::allocWithAlias($this->cid, ['uid' => $this->authorId]);
}
/**
* 获取词义化日期
*
* @return string
*/
protected function ___dateWord(): string
{
return $this->date->word();
}
/**
* 对文章的简短纯文本描述
*
* @deprecated
* @return string|null
*/
protected function ___description(): ?string
{
return $this->plainExcerpt;
}
/**
* @return Config|null
*/
protected function ___attachment(): ?Config
{
if ('attachment' == $this->type) {
$content = json_decode($this->row['text'], true);
//增加数据信息
$attachment = new Config($content);
$attachment->isImage = in_array($content['type'], [
'jpg', 'jpeg', 'gif', 'png', 'tiff', 'bmp', 'webp', 'avif'
]);
$attachment->url = Upload::attachmentHandle($attachment);
return $attachment;
}
return null;
}
/**
* ___fields
*
* @return Config
* @throws Exception
*/
protected function ___fields(): Config
{
$fields = [];
$rows = $this->db->fetchAll($this->db->select()->from('table.fields')
->where('cid = ?', $this->cid));
foreach ($rows as $row) {
$value = 'json' == $row['type'] ? json_decode($row['str_value'], true) : $row[$row['type'] . '_value'];
$fields[$row['name']] = $value;
}
return new Config($fields);
}
/**
* 获取文章内容摘要
*
* @return string|null
*/
protected function ___excerpt(): ?string
{
if ($this->hidden) {
return $this->text;
}
$content = Contents::pluginHandle()->filter('excerpt', $this->content, $this);
[$excerpt] = explode('<!--more-->', $content);
return Common::fixHtml(Contents::pluginHandle()->filter('excerptEx', $excerpt, $this));
}
/**
* 对文章的简短纯文本描述
*
* @return string|null
*/
protected function ___plainExcerpt(): ?string
{
$plainText = str_replace("\n", '', trim(strip_tags($this->excerpt)));
$plainText = $plainText ?: $this->title;
return Common::subStr($plainText, 0, 100);
}
/**
* markdown
*
* @param string|null $text
* @return string|null
*/
protected function markdown(?string $text): ?string
{
$html = Contents::pluginHandle()->trigger($parsed)->filter('markdown', $text);
if (!$parsed) {
$html = Markdown::convert($text);
}
return $html;
}
/**
* autoP
*
* @param string|null $text
* @return string|null
*/
protected function autoP(?string $text): ?string
{
$html = Contents::pluginHandle()->trigger($parsed)->filter('autoP', $text);
if (!$parsed && $text) {
static $parser;
if (empty($parser)) {
$parser = new AutoP();
}
$html = $parser->parse($text);
}
return $html;
}
/**
* 获取文章内容
*
* @return string|null
*/
protected function ___content(): ?string
{
if ($this->hidden) {
return $this->text;
}
$content = Contents::pluginHandle()->trigger($plugged)->filter('content', $this->text, $this);
if (!$plugged) {
$content = $this->isMarkdown ? $this->markdown($content)
: $this->autoP($content);
}
return Contents::pluginHandle()->filter('contentEx', $content, $this);
}
/**
* 输出文章的第一行作为摘要
*
* @return string|null
*/
protected function ___summary(): ?string
{
$content = $this->content;
$parts = preg_split("/(<\/\s*(?:p|blockquote|q|pre|table)\s*>)/i", $content, 2, PREG_SPLIT_DELIM_CAPTURE);
if (!empty($parts)) {
$content = $parts[0] . $parts[1];
}
return $content;
}
/**
* 锚点id
*
* @return string
*/
protected function ___theId(): string
{
return $this->type . '-' . $this->cid;
}
/**
* 回复框id
*
* @return string
*/
protected function ___respondId(): string
{
return 'respond-' . $this->theId;
}
/**
* 评论地址
*
* @return string
*/
protected function ___commentUrl(): string
{
/** 生成反馈地址 */
/** 评论 */
return Router::url(
'feedback',
['type' => 'comment', 'permalink' => $this->path],
$this->options->index
);
}
/**
* trackback地址
*
* @return string
*/
protected function ___trackbackUrl(): string
{
return Router::url(
'feedback',
['type' => 'trackback', 'permalink' => $this->path],
$this->options->index
);
}
/**
* 回复地址
*
* @return string
*/
protected function ___responseUrl(): string
{
return $this->permalink . '#' . $this->respondId;
}
/**
* @return string
*/
protected function ___year(): string
{
return $this->date->year;
}
/**
* @return string
*/
protected function ___month(): string
{
return $this->date->month;
}
/**
* @return string
*/
protected function ___day(): string
{
return $this->date->day;
}
}

255
var/Widget/Base/Metas.php Executable file
View File

@@ -0,0 +1,255 @@
<?php
namespace Widget\Base;
use Typecho\Common;
use Typecho\Db\Exception;
use Typecho\Db\Query;
use Typecho\Router;
use Typecho\Router\ParamsDelegateInterface;
use Widget\Base;
if (!defined('__TYPECHO_ROOT_DIR__')) {
exit;
}
/**
* 描述性数据组件
*
* @property int $mid
* @property string $name
* @property string $title
* @property string $slug
* @property string $type
* @property string $description
* @property int $count
* @property int $order
* @property int $parent
* @property-read string $theId
* @property-read string $url
* @property-read string $permalink
* @property-read string[] $directory
* @property-read string $feedUrl
* @property-read string $feedRssUrl
* @property-read string $feedAtomUrl
*/
class Metas extends Base implements QueryInterface, RowFilterInterface, PrimaryKeyInterface, ParamsDelegateInterface
{
/**
* @return string 获取主键
*/
public function getPrimaryKey(): string
{
return 'mid';
}
/**
* @param string $key
* @return string
*/
public function getRouterParam(string $key): string
{
switch ($key) {
case 'mid':
return (string)$this->mid;
case 'slug':
return urlencode($this->slug);
case 'directory':
return implode('/', array_map('urlencode', $this->directory));
default:
return '{' . $key . '}';
}
}
/**
* 获取记录总数
*
* @param Query $condition 计算条件
* @return integer
* @throws Exception
*/
public function size(Query $condition): int
{
return $this->db->fetchObject($condition->select(['COUNT(mid)' => 'num'])->from('table.metas'))->num;
}
/**
* 将每行的值压入堆栈
*
* @param array $value 每行的值
* @return array
*/
public function push(array $value): array
{
$value = $this->filter($value);
return parent::push($value);
}
/**
* 通用过滤器
*
* @param array $row 需要过滤的行数据
* @return array
*/
public function filter(array $row): array
{
return Metas::pluginHandle()->filter('filter', $row, $this);
}
/**
* 更新记录
*
* @param array $rows 记录更新值
* @param Query $condition 更新条件
* @return integer
* @throws Exception
*/
public function update(array $rows, Query $condition): int
{
return $this->db->query($condition->update('table.metas')->rows($rows));
}
/**
* 获取原始查询对象
*
* @param mixed $fields
* @return Query
* @throws Exception
*/
public function select(...$fields): Query
{
return $this->db->select(...$fields)->from('table.metas');
}
/**
* 删除记录
*
* @param Query $condition 删除条件
* @return integer
* @throws Exception
*/
public function delete(Query $condition): int
{
return $this->db->query($condition->delete('table.metas'));
}
/**
* 插入一条记录
*
* @param array $rows 记录插入值
* @return integer
* @throws Exception
*/
public function insert(array $rows): int
{
return $this->db->query($this->db->insert('table.metas')->rows($rows));
}
/**
* 根据tag获取ID
*
* @param mixed $inputTags 标签名
* @return array|int
* @throws Exception
*/
public function scanTags($inputTags)
{
$tags = is_array($inputTags) ? $inputTags : [$inputTags];
$result = [];
foreach ($tags as $tag) {
if (empty($tag)) {
continue;
}
$row = $this->db->fetchRow($this->select()
->where('type = ?', 'tag')
->where('name = ?', $tag)->limit(1));
if ($row) {
$result[] = $row['mid'];
} else {
$slug = Common::slugName($tag);
if ($slug) {
$result[] = $this->insert([
'name' => $tag,
'slug' => $slug,
'type' => 'tag',
'count' => 0,
'order' => 0,
]);
}
}
}
return is_array($inputTags) ? $result : current($result);
}
/**
* 锚点id
*
* @access protected
* @return string
*/
protected function ___theId(): string
{
return $this->type . '-' . $this->mid;
}
/**
* @return string
*/
protected function ___title(): string
{
return $this->name;
}
/**
* @return array
*/
protected function ___directory(): array
{
return [];
}
/**
* @return string
*/
protected function ___permalink(): string
{
return Router::url($this->type, $this, $this->options->index);
}
/**
* @return string
*/
protected function ___url(): string
{
return $this->permalink;
}
/**
* @return string
*/
protected function ___feedUrl(): string
{
return Router::url($this->type, $this, $this->options->feedUrl);
}
/**
* @return string
*/
protected function ___feedRssUrl(): string
{
return Router::url($this->type, $this, $this->options->feedRssUrl);
}
/**
* @return string
*/
protected function ___feedAtomUrl(): string
{
return Router::url($this->type, $this, $this->options->feedAtomUrl);
}
}

83
var/Widget/Base/Options.php Executable file
View File

@@ -0,0 +1,83 @@
<?php
namespace Widget\Base;
use Typecho\Db\Exception;
use Typecho\Db\Query;
use Widget\Base;
if (!defined('__TYPECHO_ROOT_DIR__')) {
exit;
}
/**
* 全局选项组件
*
* @link typecho
* @package Widget
* @copyright Copyright (c) 2008 Typecho team (http://www.typecho.org)
* @license GNU General Public License 2.0
*/
class Options extends Base implements QueryInterface
{
/**
* 获取原始查询对象
*
* @param mixed ...$fields
* @return Query
* @throws Exception
*/
public function select(...$fields): Query
{
return $this->db->select(...$fields)->from('table.options');
}
/**
* 插入一条记录
*
* @param array $rows 记录插入值
* @return integer
* @throws Exception
*/
public function insert(array $rows): int
{
return $this->db->query($this->db->insert('table.options')->rows($rows));
}
/**
* 更新记录
*
* @param array $rows 记录更新值
* @param Query $condition 更新条件
* @return integer
* @throws Exception
*/
public function update(array $rows, Query $condition): int
{
return $this->db->query($condition->update('table.options')->rows($rows));
}
/**
* 删除记录
*
* @param Query $condition 删除条件
* @return integer
* @throws Exception
*/
public function delete(Query $condition): int
{
return $this->db->query($condition->delete('table.options'));
}
/**
* 获取记录总数
*
* @param Query $condition 计算条件
* @return integer
* @throws Exception
*/
public function size(Query $condition): int
{
return $this->db->fetchObject($condition->select(['COUNT(name)' => 'num'])->from('table.options'))->num;
}
}

View File

@@ -0,0 +1,13 @@
<?php
namespace Widget\Base;
interface PrimaryKeyInterface
{
/**
* 获取主键
*
* @return string
*/
public function getPrimaryKey(): string;
}

View File

@@ -0,0 +1,56 @@
<?php
namespace Widget\Base;
use Typecho\Db\Query;
/**
* Base Query Interface
*/
interface QueryInterface
{
/**
* 查询方法
*
* @param mixed $fields 字段
* @return Query
*/
public function select(...$fields): Query;
/**
* 获得所有记录数
*
* @access public
* @param Query $condition 查询对象
* @return integer
*/
public function size(Query $condition): int;
/**
* 增加记录方法
*
* @access public
* @param array $rows 字段对应值
* @return integer
*/
public function insert(array $rows): int;
/**
* 更新记录方法
*
* @access public
* @param array $rows 字段对应值
* @param Query $condition 查询对象
* @return integer
*/
public function update(array $rows, Query $condition): int;
/**
* 删除记录方法
*
* @access public
* @param Query $condition 查询对象
* @return integer
*/
public function delete(Query $condition): int;
}

View File

@@ -0,0 +1,17 @@
<?php
namespace Widget\Base;
/**
* 行过滤器接口
*/
interface RowFilterInterface
{
/**
* 过滤行
*
* @param array $row
* @return array
*/
public function filter(array $row): array;
}

279
var/Widget/Base/TreeTrait.php Executable file
View File

@@ -0,0 +1,279 @@
<?php
namespace Widget\Base;
use Typecho\Config;
use Typecho\Db\Exception;
/**
* 处理树状数据结构
*/
trait TreeTrait
{
/**
* 树状数据结构
*
* @var array
* @access private
*/
private array $treeRows = [];
/**
* 顶层节点
*
* @var array
* @access private
*/
private array $top = [];
/**
* 所有节点哈希表
*
* @var array
* @access private
*/
private array $map = [];
/**
* 顺序流
*
* @var array
* @access private
*/
private array $orders = [];
/**
* 所有子节点列表
*
* @var array
* @access private
*/
private array $childNodes = [];
/**
* 所有父节点列表
*
* @var array
* @access private
*/
private array $parents = [];
/**
* 根据深度余数输出
*
* @param ...$args
*/
public function levelsAlt(...$args)
{
$this->altBy($this->levels, ...$args);
}
/**
* 获取某个节点所有父级节点缩略名
*
* @param int $id
* @return array
*/
public function getAllParentsSlug(int $id): array
{
$parents = [];
if (isset($this->parents[$id])) {
foreach ($this->parents[$id] as $parent) {
$parents[] = $this->map[$parent]['slug'];
}
}
return $parents;
}
/**
* 获取某个节点下的所有子节点
*
* @param int $id
* @return array
*/
public function getAllChildIds(int $id): array
{
return $this->childNodes[$id] ?? [];
}
/**
* 获取某个节点下的子节点
*
* @param int $id
* @return array
*/
public function getChildIds(int $id): array
{
return $id > 0 ? ($this->treeRows[$id] ?? []) : $this->top;
}
/**
* 获取某个节点所有父级节点
*
* @param int $id
* @return array
*/
public function getAllParents(int $id): array
{
$parents = [];
if (isset($this->parents[$id])) {
foreach ($this->parents[$id] as $parent) {
$parents[] = $this->map[$parent];
}
}
return $parents;
}
/**
* 获取多个节点
*
* @param array $ids
* @param integer $ignore
* @return array
*/
public function getRows(array $ids, int $ignore = 0): array
{
$result = [];
if (!empty($ids)) {
foreach ($ids as $id) {
if (!$ignore || ($ignore != $id && !$this->hasParent($id, $ignore))) {
$result[] = $this->map[$id];
}
}
}
return $result;
}
/**
* @param int $id
* @return array|null
*/
public function getRow(int $id): ?array
{
return $this->map[$id] ?? null;
}
/**
* 是否拥有某个父级节点
*
* @param mixed $id
* @param mixed $parentId
* @return bool
*/
public function hasParent($id, $parentId): bool
{
if (isset($this->parents[$id])) {
foreach ($this->parents[$id] as $parent) {
if ($parent == $parentId) {
return true;
}
}
}
return false;
}
/**
* @return array
*/
abstract protected function initTreeRows(): array;
/**
* @param Config $parameter
* @throws Exception
*/
protected function initParameter(Config $parameter)
{
$parameter->setDefault('ignore=0&current=');
$rows = $this->initTreeRows();
$pk = $this->getPrimaryKey();
// Sort by order asc
usort($rows, function ($a, $b) {
return $a['order'] <=> $b['order'];
});
foreach ($rows as $row) {
$row['levels'] = 0;
$this->map[$row[$pk]] = $row;
}
// 读取数据
foreach ($this->map as $id => $row) {
$parent = $row['parent'];
if (0 != $parent && isset($this->map[$parent])) {
$this->treeRows[$parent][] = $id;
} else {
$this->top[] = $id;
}
}
// 预处理深度
$this->levelWalkCallback($this->top);
$this->map = array_map([$this, 'filter'], $this->map);
}
/**
* @return array
*/
protected function ___directory(): array
{
$directory = $this->getAllParentsSlug($this->{$this->getPrimaryKey()});
$directory[] = $this->slug;
return $directory;
}
/**
* 获取所有子节点
*
* @return array
*/
protected function ___children(): array
{
$id = $this->{$this->getPrimaryKey()};
return $this->getRows($this->getChildIds($id));
}
/**
* 预处理节点迭代
*
* @param array $rows
* @param array $parents
*/
private function levelWalkCallback(array $rows, array $parents = [])
{
foreach ($parents as $parent) {
if (!isset($this->childNodes[$parent])) {
$this->childNodes[$parent] = [];
}
$this->childNodes[$parent] = array_merge($this->childNodes[$parent], $rows);
}
foreach ($rows as $id) {
$this->orders[] = $id;
$parent = $this->map[$id]['parent'];
if (0 != $parent && isset($this->map[$parent])) {
$levels = $this->map[$parent]['levels'] + 1;
$this->map[$id]['levels'] = $levels;
}
$this->parents[$id] = $parents;
if (!empty($this->treeRows[$id])) {
$new = $parents;
$new[] = $id;
$this->levelWalkCallback($this->treeRows[$id], $new);
}
}
}
}

126
var/Widget/Base/TreeViewTrait.php Executable file
View File

@@ -0,0 +1,126 @@
<?php
namespace Widget\Base;
use Typecho\Config;
trait TreeViewTrait
{
use TreeTrait;
/**
* treeViewRows
*
* @param mixed $rowOptions 输出选项
* @param string $type 类型
* @param string $func 回调函数
* @param int $current 当前项
*/
protected function listRows(Config $rowOptions, string $type, string $func, int $current = 0)
{
$this->stack = $this->getRows($this->top);
if ($this->have()) {
echo '<' . $rowOptions->wrapTag . (empty($rowOptions->wrapClass)
? '' : ' class="' . $rowOptions->wrapClass . '"') . '>';
while ($this->next()) {
$this->treeViewRowsCallback($rowOptions, $type, $func, $current);
}
echo '</' . $rowOptions->wrapTag . '>';
}
$this->stack = $this->map;
}
/**
* 列出分类回调
*
* @param Config $rowOptions 输出选项
* @param string $type 类型
* @param string $func 回调函数
* @param int $current 当前项
*/
private function treeViewRowsCallback(Config $rowOptions, string $type, string $func, int $current): void
{
if (function_exists($func)) {
call_user_func($func, $this, $rowOptions);
return;
}
$id = $this->{$this->getPrimaryKey()};
$classes = [];
if ($rowOptions->itemClass) {
$classes[] = $rowOptions->itemClass;
}
$classes[] = $type . '-level-' . $this->levels;
echo '<' . $rowOptions->itemTag . ' class="'
. implode(' ', $classes);
if ($this->levels > 0) {
echo " {$type}-child";
$this->levelsAlt(" {$type}-level-odd", " {$type}-level-even");
} else {
echo " {$type}-parent";
}
if ($id == $current) {
echo " {$type}-active";
} elseif (
isset($this->childNodes[$id]) && in_array($current, $this->childNodes[$id])
) {
echo " {$type}-parent-active";
}
echo '"><a href="' . $this->permalink . '">' . $this->title . '</a>';
if ($rowOptions->showCount) {
printf($rowOptions->countTemplate, intval($this->count));
}
if ($rowOptions->showFeed) {
printf($rowOptions->feedTemplate, $this->feedUrl);
}
if ($this->children) {
$this->treeViewRows($rowOptions, $type, $func, $current);
}
echo '</' . $rowOptions->itemTag . '>';
}
/**
* treeViewRows
*
* @param Config $rowOptions 输出选项
* @param string $type 类型
* @param string $func 回调函数
* @param int $current 当前项
*/
private function treeViewRows(Config $rowOptions, string $type, string $func, int $current)
{
$children = $this->children;
if ($children) {
//缓存变量便于还原
$tmp = $this->row;
$this->sequence++;
//在子评论之前输出
echo '<' . $rowOptions->wrapTag . (empty($rowOptions->wrapClass)
? '' : ' class="' . $rowOptions->wrapClass . '"') . '>';
foreach ($children as $child) {
$this->row = $child;
$this->treeViewRowsCallback($rowOptions, $type, $func, $current);
$this->row = $tmp;
}
//在子评论之后输出
echo '</' . $rowOptions->wrapTag . '>';
$this->sequence--;
}
}
}

209
var/Widget/Base/Users.php Executable file
View File

@@ -0,0 +1,209 @@
<?php
namespace Widget\Base;
use Typecho\Common;
use Typecho\Config;
use Typecho\Db\Exception;
use Typecho\Db\Query;
use Typecho\Router;
use Typecho\Router\ParamsDelegateInterface;
use Widget\Base;
if (!defined('__TYPECHO_ROOT_DIR__')) {
exit;
}
/**
* 用户抽象类
*
* @property int $uid
* @property string $name
* @property string $password
* @property string $mail
* @property string $url
* @property string $screenName
* @property int $created
* @property int $activated
* @property int $logged
* @property string $group
* @property string $authCode
* @property-read Config $personalOptions
* @property-read string $permalink
* @property-read string $feedUrl
* @property-read string $feedRssUrl
* @property-read string $feedAtomUrl
*/
class Users extends Base implements QueryInterface, RowFilterInterface, PrimaryKeyInterface, ParamsDelegateInterface
{
/**
* @return string 获取主键
*/
public function getPrimaryKey(): string
{
return 'uid';
}
/**
* 将每行的值压入堆栈
*
* @param array $value 每行的值
* @return array
*/
public function push(array $value): array
{
$value = $this->filter($value);
return parent::push($value);
}
/**
* 通用过滤器
*
* @param array $row 需要过滤的行数据
* @return array
*/
public function filter(array $row): array
{
return Users::pluginHandle()->filter('filter', $row, $this);
}
/**
* @param string $key
* @return string
*/
public function getRouterParam(string $key): string
{
switch ($key) {
case 'uid':
return $this->uid;
default:
return '{' . $key . '}';
}
}
/**
* 查询方法
*
* @param mixed $fields
* @return Query
* @throws Exception
*/
public function select(...$fields): Query
{
return $this->db->select(...$fields)->from('table.users');
}
/**
* 获得所有记录数
*
* @param Query $condition 查询对象
* @return integer
* @throws Exception
*/
public function size(Query $condition): int
{
return $this->db->fetchObject($condition->select(['COUNT(uid)' => 'num'])->from('table.users'))->num;
}
/**
* 增加记录方法
*
* @param array $rows 字段对应值
* @return integer
* @throws Exception
*/
public function insert(array $rows): int
{
return $this->db->query($this->db->insert('table.users')->rows($rows));
}
/**
* 更新记录方法
*
* @param array $rows 字段对应值
* @param Query $condition 查询对象
* @return integer
* @throws Exception
*/
public function update(array $rows, Query $condition): int
{
return $this->db->query($condition->update('table.users')->rows($rows));
}
/**
* 删除记录方法
*
* @param Query $condition 查询对象
* @return integer
* @throws Exception
*/
public function delete(Query $condition): int
{
return $this->db->query($condition->delete('table.users'));
}
/**
* 调用gravatar输出用户头像
*
* @param integer $size 头像尺寸
* @param string $rating 头像评级
* @param string|null $default 默认输出头像
* @param string|null $class 默认css class
*/
public function gravatar(int $size = 40, string $rating = 'X', ?string $default = null, ?string $class = null)
{
$url = Common::gravatarUrl($this->mail, $size, $rating, $default, $this->request->isSecure());
echo '<img' . (empty($class) ? '' : ' class="' . $class . '"') . ' src="' . $url . '" alt="' .
$this->screenName . '" width="' . $size . '" height="' . $size . '" />';
}
/**
* @return string
*/
protected function ___permalink(): string
{
return Router::url('author', $this, $this->options->index);
}
/**
* @return string
*/
protected function ___feedUrl(): string
{
return Router::url('author', $this, $this->options->feedUrl);
}
/**
* @return string
*/
protected function ___feedRssUrl(): string
{
return Router::url('author', $this, $this->options->feedRssUrl);
}
/**
* @return string
*/
protected function ___feedAtomUrl(): string
{
return Router::url('author', $this, $this->options->feedAtomUrl);
}
/**
* personalOptions
*
* @return Config
* @throws Exception
*/
protected function ___personalOptions(): Config
{
$rows = $this->db->fetchAll($this->db->select()
->from('table.options')->where('user = ?', $this->uid));
$options = [];
foreach ($rows as $row) {
$options[$row['name']] = $row['value'];
}
return new Config($options);
}
}