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
This commit is contained in:
joyqi
2023-11-28 13:57:18 +08:00
committed by GitHub
parent 4c1c42ad40
commit e89b1bbcf6
53 changed files with 2463 additions and 1698 deletions

View File

@@ -120,7 +120,7 @@ $isAllComments = ('on' == $request->get('__typecho_all_comments') || 'on' == \Ty
<td valign="top" class="kit-hidden-mb">
<div class="comment-avatar">
<?php if ('comment' == $comments->type): ?>
<?php $comments->gravatar(40); ?>
<?php $comments->gravatar(40, null, true); ?>
<?php endif; ?>
<?php if ('comment' != $comments->type): ?>
<?php _e('引用'); ?>

View File

@@ -35,6 +35,7 @@ $pages = \Widget\Contents\Page\Admin::alloc();
</div>
<div class="search" role="search">
<?php $pages->backLink(); ?>
<?php if ('' != $request->keywords): ?>
<a href="<?php $options->adminUrl('manage-pages.php'); ?>"><?php _e('&laquo; 取消筛选'); ?></a>
<?php endif; ?>
@@ -61,7 +62,7 @@ $pages = \Widget\Contents\Page\Admin::alloc();
<th class="kit-hidden-mb"></th>
<th class="kit-hidden-mb"></th>
<th><?php _e('标题'); ?></th>
<th><?php _e('缩略名'); ?></th>
<th><?php _e('子页面'); ?></th>
<th class="kit-hidden-mb"><?php _e('作者'); ?></th>
<th><?php _e('日期'); ?></th>
</tr>
@@ -80,8 +81,10 @@ $pages = \Widget\Contents\Page\Admin::alloc();
<td>
<a href="<?php $options->adminUrl('write-page.php?cid=' . $pages->cid); ?>"><?php $pages->title(); ?></a>
<?php
if ($pages->hasSaved || 'page_draft' == $pages->type) {
if ('page_draft' == $pages->type) {
echo '<em class="status">' . _t('草稿') . '</em>';
} elseif ($pages->revision) {
echo '<em class="status">' . _t('有修订版') . '</em>';
}
if ('hidden' == $pages->status) {
@@ -97,12 +100,18 @@ $pages = \Widget\Contents\Page\Admin::alloc();
class="i-exlink"></i></a>
<?php endif; ?>
</td>
<td><?php $pages->slug(); ?></td>
<td>
<?php if (count($pages->children) > 0): ?>
<a href="<?php $options->adminUrl('manage-pages.php?parent=' . $pages->cid); ?>"><?php echo _n('一个页面', '%d个页面', count($pages->children)); ?></a>
<?php else: ?>
<a href="<?php $options->adminUrl('write-page.php?parent=' . $pages->cid); ?>"><?php echo _e('新增'); ?></a>
<?php endif; ?>
</td>
<td class="kit-hidden-mb"><?php $pages->author(); ?></td>
<td>
<?php if ($pages->hasSaved): ?>
<?php if ('page_draft' == $pages->type || $pages->revision): ?>
<span class="description">
<?php $modifyDate = new \Typecho\Date($pages->modified); ?>
<?php $modifyDate = new \Typecho\Date($pages->revision ? $pages->revision['modified'] : $pages->modified); ?>
<?php _e('保存于 %s', $modifyDate->word()); ?>
</span>
<?php else: ?>
@@ -132,7 +141,7 @@ include 'common-js.php';
include 'table-js.php';
?>
<?php if (!isset($request->status) || 'publish' == $request->get('status')): ?>
<?php if (!$request->is('keywords')): ?>
<script type="text/javascript">
(function () {
$(document).ready(function () {

View File

@@ -148,8 +148,10 @@ $isAllPosts = ('on' == $request->get('__typecho_all_posts') || 'on' == \Typecho\
<td>
<a href="<?php $options->adminUrl('write-post.php?cid=' . $posts->cid); ?>"><?php $posts->title(); ?></a>
<?php
if ($posts->hasSaved || 'post_draft' == $posts->type) {
if ('post_draft' == $posts->type) {
echo '<em class="status">' . _t('草稿') . '</em>';
} elseif ($posts->revision) {
echo '<em class="status">' . _t('有修订版') . '</em>';
}
if ('hidden' == $posts->status) {
@@ -175,19 +177,18 @@ $isAllPosts = ('on' == $request->get('__typecho_all_posts') || 'on' == \Typecho\
href="<?php $options->adminUrl('manage-posts.php?__typecho_all_posts=off&uid=' . $posts->author->uid); ?>"><?php $posts->author(); ?></a>
</td>
<td class="kit-hidden-mb"><?php $categories = $posts->categories;
$length = count($categories); ?>
<?php foreach ($categories as $key => $val): ?>
while ($categories->next()): ?>
<?php echo '<a href="';
$options->adminUrl('manage-posts.php?category=' . $val['mid']
$options->adminUrl('manage-posts.php?category=' . $categories->mid
. (isset($request->uid) ? '&uid=' . $request->filter('encode')->uid : '')
. (isset($request->status) ? '&status=' . $request->filter('encode')->status : ''));
echo '">' . $val['name'] . '</a>' . ($key < $length - 1 ? ', ' : ''); ?>
<?php endforeach; ?>
echo '">' . $categories->name . '</a>' . ($categories->sequence < $categories->length - 1 ? ', ' : ''); ?>
<?php endwhile; ?>
</td>
<td>
<?php if ($posts->hasSaved): ?>
<?php if ('post_draft' == $posts->type || $posts->revision): ?>
<span class="description">
<?php $modifyDate = new \Typecho\Date($posts->modified); ?>
<?php $modifyDate = new \Typecho\Date($posts->revision ? $posts->revision['modified'] : $posts->modified); ?>
<?php _e('保存于 %s', $modifyDate->word()); ?>
</span>
<?php else: ?>

View File

@@ -2,7 +2,19 @@
include 'common.php';
include 'header.php';
include 'menu.php';
\Widget\Contents\Page\Edit::alloc()->prepare()->to($page);
$page = \Widget\Contents\Page\Edit::alloc()->prepare();
$parentPageId = $page->getParent();
$parentPages = [0 => _t('不选择')];
$parents = \Widget\Contents\Page\Admin::allocWithAlias(
'options',
'ignoreRequest=1' . ($request->is('cid') ? '&ignore=' . $request->get('cid') : '')
);
while ($parents->next()) {
$parentPages[$parents->cid] = str_repeat('&nbsp;&nbsp;&nbsp;&nbsp;', $parents->levels) . $parents->title;
}
?>
<div class="main">
<div class="body container">
@@ -14,7 +26,7 @@ include 'menu.php';
<?php if ($page->draft['cid'] != $page->cid): ?>
<?php $pageModifyDate = new \Typecho\Date($page->draft['modified']); ?>
<cite
class="edit-draft-notice"><?php _e('当前正在编辑的是保存于%s的草稿, 你可以<a href="%s">删除它</a>', $pageModifyDate->word(),
class="edit-draft-notice"><?php _e('正在编辑的是保存于 %s 的修订版, 你可以 <a href="%s">删除它</a>', $pageModifyDate->word(),
$security->getIndex('/action/contents-page-edit?do=deleteDraft&cid=' . $page->cid)); ?></cite>
<?php else: ?>
<cite class="edit-draft-notice"><?php _e('当前正在编辑的是未发布的草稿'); ?></cite>
@@ -38,12 +50,18 @@ include 'menu.php';
?>
<p class="mono url-slug">
<label for="slug" class="sr-only"><?php _e('网址缩略名'); ?></label>
<?php echo preg_replace("/\{slug\}/i", $input, $permalink); ?>
<?php echo preg_replace_callback("/\{(slug|directory)\}/i", function ($matches) use ($input) {
if ($matches[1] == 'slug') {
return $input;
} else {
return '{directory/' . $input . '}';
}
}, $permalink); ?>
</p>
<p>
<label for="text" class="sr-only"><?php _e('页面内容'); ?></label>
<textarea style="height: <?php $options->editorSize(); ?>px" autocomplete="off" id="text"
name="text" class="w-100 mono"><?php echo htmlspecialchars($page->text ?? ''); ?></textarea>
name="text" class="w-100 mono"><?php echo htmlspecialchars($page->text); ?></textarea>
</p>
<?php include 'custom-fields.php'; ?>
@@ -105,6 +123,19 @@ include 'menu.php';
<p class="description"><?php _e('如果你为此页面选择了一个自定义模板, 系统将按照你选择的模板文件展现它'); ?></p>
</section>
<section class="typecho-post-option">
<label for="parent" class="typecho-label"><?php _e('父级页面'); ?></label>
<p>
<select name="parent" id="parent">
<?php foreach ($parentPages as $pageId => $pageTitle): ?>
<option
value="<?php echo $pageId; ?>"<?php if ($pageId == ($page->parent ?? $parentPageId)): ?> selected="true"<?php endif; ?>><?php echo $pageTitle; ?></option>
<?php endforeach; ?>
</select>
</p>
<p class="description"><?php _e('如果你设定了父级页面, 此页面将作为子页面呈现'); ?></p>
</section>
<?php \Typecho\Plugin::factory('admin/write-page.php')->call('option', $page); ?>
<button type="button" id="advance-panel-btn" class="btn btn-xs"><?php _e('高级选项'); ?> <i

View File

@@ -2,7 +2,8 @@
include 'common.php';
include 'header.php';
include 'menu.php';
\Widget\Contents\Post\Edit::alloc()->prepare()->to($post);
$post = \Widget\Contents\Post\Edit::alloc()->prepare();
?>
<div class="main">
<div class="body container">
@@ -14,7 +15,7 @@ include 'menu.php';
<?php if ($post->draft['cid'] != $post->cid): ?>
<?php $postModifyDate = new \Typecho\Date($post->draft['modified']); ?>
<cite
class="edit-draft-notice"><?php _e('你正在编辑的是保存于 %s 的草稿, 你也可以 <a href="%s">删除它</a>', $postModifyDate->word(),
class="edit-draft-notice"><?php _e('你正在编辑的是保存于 %s 的修订版, 你也可以 <a href="%s">删除它</a>', $postModifyDate->word(),
$security->getIndex('/action/contents-post-edit?do=deleteDraft&cid=' . $post->cid)); ?></cite>
<?php else: ?>
<cite class="edit-draft-notice"><?php _e('当前正在编辑的是未发布的草稿'); ?></cite>
@@ -47,7 +48,7 @@ include 'menu.php';
<p>
<label for="text" class="sr-only"><?php _e('文章内容'); ?></label>
<textarea style="height: <?php $options->editorSize(); ?>px" autocomplete="off" id="text"
name="text" class="w-100 mono"><?php echo htmlspecialchars($post->text ?? ''); ?></textarea>
name="text" class="w-100 mono"><?php echo htmlspecialchars($post->text); ?></textarea>
</p>
<?php include 'custom-fields.php'; ?>
@@ -94,13 +95,7 @@ include 'menu.php';
<label class="typecho-label"><?php _e('分类'); ?></label>
<?php \Widget\Metas\Category\Rows::alloc()->to($category); ?>
<ul>
<?php
if ($post->have()) {
$categories = array_column($post->categories, 'mid');
} else {
$categories = [];
}
?>
<?php $categories = $post->categories->toArray('mid'); ?>
<?php while ($category->next()): ?>
<li><?php echo str_repeat('&nbsp;&nbsp;&nbsp;&nbsp;', $category->levels); ?><input
type="checkbox" id="category-<?php $category->mid(); ?>"

View File

@@ -10,7 +10,7 @@ namespace Typecho;
* @copyright Copyright (c) 2008 Typecho team (http://www.typecho.org)
* @license GNU General Public License 2.0
*/
class Config implements \Iterator, \ArrayAccess
class Config extends \stdClass implements \Iterator, \ArrayAccess
{
/**
* 当前配置

View File

@@ -387,9 +387,11 @@ class Db
/** 选择连接池 */
$handle = $this->selectDb($op);
/** 如果是查询对象,则将其转换为查询语句 */
$sql = $query instanceof Query ? $query->prepare($query) : $query;
/** 提交查询 */
$resource = $this->adapter->query($query instanceof Query ?
$query->prepare($query) : $query, $handle, $op, $action, $table);
$resource = $this->adapter->query($sql, $handle, $op, $action, $table);
if ($action) {
//根据查询动作返回相应资源

View File

@@ -2,6 +2,7 @@
namespace Typecho;
use Typecho\Router\ParamsDelegateInterface;
use Typecho\Router\Parser;
use Typecho\Router\Exception as RouterException;
@@ -106,18 +107,31 @@ class Router
* 路由反解析函数
*
* @param string $name 路由配置表名称
* @param array|null $value 路由填充值
* @param mixed $value 路由填充值
* @param string|null $prefix 最终合成路径的前缀
* @return string
*/
public static function url(string $name, ?array $value = null, ?string $prefix = null): string
{
public static function url(
string $name,
$value = null,
?string $prefix = null
): string {
if (!isset(self::$routingTable[$name])) {
return '#';
}
$route = self::$routingTable[$name];
//交换数组键值
$pattern = [];
foreach ($route['params'] as $row) {
$pattern[$row] = $value[$row] ?? '{' . $row . '}';
foreach ($route['params'] as $param) {
if (is_array($value) && isset($value[$param])) {
$pattern[$param] = $value[$param];
} elseif ($value instanceof ParamsDelegateInterface) {
$pattern[$param] = $value->getRouterParam($param);
} else {
$pattern[$param] = '{' . $param . '}';
}
}
return Common::url(vsprintf($route['format'], $pattern), $prefix);

View File

@@ -0,0 +1,8 @@
<?php
namespace Typecho\Router;
interface ParamsDelegateInterface
{
public function getRouterParam(string $key): string;
}

View File

@@ -274,24 +274,59 @@ abstract class Widget
return $variable = $this;
}
/**
* 按模版渲染
*
* @param string $template 模版
* @return string
*/
public function template(string $template): string
{
return preg_replace_callback(
"/\{([_a-z0-9]+)\}/i",
function (array $matches) {
return $this->{$matches[1]};
},
$template
);
}
/**
* 格式化解析堆栈内的所有数据
*
* @param string $format 数据格式
* @param string $template 模版
*/
public function parse(string $format)
public function parse(string $template)
{
while ($this->next()) {
echo preg_replace_callback(
"/\{([_a-z0-9]+)\}/i",
function (array $matches) {
return $this->{$matches[1]};
},
$format
);
echo $this->template($template);
}
}
/**
* @param string|array $column
* @return array
*/
public function toArray($column): array
{
$result = [];
while ($this->next()) {
if (is_array($column)) {
$item = [];
foreach ($column as $key) {
$item[$key] = $this->{$key};
}
$result[] = $item;
} else {
$result[] = $this->{$column};
}
}
return $result;
}
/**
* 返回堆栈每一行的值
*
@@ -336,9 +371,20 @@ abstract class Widget
* @param mixed ...$args
*/
public function alt(...$args)
{
$this->altBy($this->sequence, ...$args);
}
/**
* 根据余数输出
*
* @param int $current
* @param mixed ...$args
*/
public function altBy(int $current, ...$args)
{
$num = count($args);
$split = $this->sequence % $num;
$split = $current % $num;
echo $args[(0 == $split ? $num : $split) - 1];
}
@@ -386,18 +432,20 @@ abstract class Widget
*/
public function __get(string $name)
{
if (array_key_exists($name, $this->row)) {
$method = '___' . $name;
$key = '#' . $name;
if (array_key_exists($key, $this->row)) {
return $this->row[$key];
} elseif (method_exists($this, $method)) {
$this->row[$key] = $this->$method();
return $this->row[$key];
} elseif (array_key_exists($name, $this->row)) {
return $this->row[$name];
} else {
$method = '___' . $name;
if (method_exists($this, $method)) {
return $this->$method();
} else {
$return = self::pluginHandle()->trigger($plugged)->call($method, $this);
if ($plugged) {
return $return;
}
$return = self::pluginHandle()->trigger($plugged)->call($method, $this);
if ($plugged) {
return $return;
}
}

View File

@@ -45,5 +45,9 @@ class Upgrade
$db->query($db->update('table.options')
->rows(['name' => 'commentsRequireUrl'])
->where('name = ?', 'commentsRequireURL'));
$db->query($db->update('table.contents')
->rows(['type' => 'revision'])
->where('parent <> 0 AND (type = ? OR type = ?)', 'post_draft', 'page_draft'));
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -4,13 +4,14 @@ namespace Widget\Base;
use Typecho\Common;
use Typecho\Date;
use Typecho\Db;
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;
@@ -36,13 +37,35 @@ if (!defined('__TYPECHO_ROOT_DIR__')) {
* @property Date $date
* @property string $dateWord
* @property string $theId
* @property array $parentContent
* @property Contents $parentContent
* @property string $title
* @property string $permalink
* @property string $content
*/
class Comments extends Base implements QueryInterface
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;
default:
return '{' . $key . '}';
}
}
/**
* 增加评论
*
@@ -178,30 +201,6 @@ class Comments extends Base implements QueryInterface
return $deleteRows;
}
/**
* 评论是否可以被修改
*
* @param Query|null $condition 条件
* @return bool
* @throws Exception
*/
public function commentIsWriteable(?Query $condition = null): bool
{
if (empty($condition)) {
if ($this->have() && ($this->user->pass('editor', true) || $this->ownerId == $this->user->uid)) {
return true;
}
} else {
$post = $this->db->fetchRow($condition->select('ownerId')->from('table.comments')->limit(1));
if ($post && ($this->user->pass('editor', true) || $post['ownerId'] == $this->user->uid)) {
return true;
}
}
return false;
}
/**
* 按照条件计算评论数量
*
@@ -229,21 +228,21 @@ class Comments extends Base implements QueryInterface
/**
* 通用过滤器
*
* @param array $value 需要过滤的行数据
* @param array $row 需要过滤的行数据
* @return array
*/
public function filter(array $value): array
public function filter(array $row): array
{
/** 处理默认空值 */
$value['author'] = $value['author'] ?? '';
$value['mail'] = $value['mail'] ?? '';
$value['url'] = $value['url'] ?? '';
$value['ip'] = $value['ip'] ?? '';
$value['agent'] = $value['agent'] ?? '';
$value['text'] = $value['text'] ?? '';
$row['author'] = $row['author'] ?? '';
$row['mail'] = $row['mail'] ?? '';
$row['url'] = $row['url'] ?? '';
$row['ip'] = $row['ip'] ?? '';
$row['agent'] = $row['agent'] ?? '';
$row['text'] = $row['text'] ?? '';
$value['date'] = new Date($value['created']);
return Comments::pluginHandle()->call('filter', $value, $this);
$row['date'] = new Date($row['created']);
return Comments::pluginHandle()->call('filter', $row, $this);
}
/**
@@ -329,28 +328,12 @@ class Comments extends Base implements QueryInterface
/**
* 获取查询对象
*
* @param mixed $fields
* @return Query
* @throws Exception
*/
public function select(): Query
public function select(...$fields): Query
{
return $this->db->select(
'table.comments.coid',
'table.comments.cid',
'table.comments.author',
'table.comments.mail',
'table.comments.url',
'table.comments.ip',
'table.comments.authorId',
'table.comments.ownerId',
'table.comments.agent',
'table.comments.text',
'table.comments.type',
'table.comments.status',
'table.comments.parent',
'table.comments.created'
)
->from('table.comments');
return $this->db->select(...$fields)->from('table.comments');
}
/**
@@ -396,14 +379,11 @@ class Comments extends Base implements QueryInterface
/**
* 获取当前内容结构
*
* @return array|null
* @throws Exception
* @return Contents
*/
protected function ___parentContent(): ?array
protected function ___parentContent(): Contents
{
return $this->db->fetchRow(Contents::alloc()->select()
->where('table.contents.cid = ?', $this->cid)
->limit(1), [Contents::alloc(), 'filter']);
return From::allocWithAlias($this->cid, ['cid' => $this->cid]);
}
/**
@@ -413,7 +393,7 @@ class Comments extends Base implements QueryInterface
*/
protected function ___title(): ?string
{
return $this->parentContent['title'];
return $this->parentContent->title;
}
/**
@@ -424,7 +404,6 @@ class Comments extends Base implements QueryInterface
*/
protected function ___permalink(): string
{
if ($this->options->commentsPageBreak && 'approved' == $this->status) {
$coid = $this->coid;
$parent = $this->parent;
@@ -442,7 +421,7 @@ class Comments extends Base implements QueryInterface
}
$select = $this->db->select('coid', 'parent')
->from('table.comments')->where('cid = ? AND status = ?', $this->parentContent['cid'], 'approved')
->from('table.comments')->where('cid = ? AND status = ?', $this->cid, 'approved')
->where('coid ' . ('DESC' == $this->options->commentsOrder ? '>=' : '<=') . ' ?', $coid)
->order('coid');
@@ -465,7 +444,7 @@ class Comments extends Base implements QueryInterface
$currentPage = ceil($total / $this->options->commentsPageSize);
$pageRow = ['permalink' => $this->parentContent['pathinfo'], 'commentPage' => $currentPage];
$pageRow = ['permalink' => $this->parentContent->path, 'commentPage' => $currentPage];
return Router::url(
'comment_page',
$pageRow,
@@ -473,7 +452,7 @@ class Comments extends Base implements QueryInterface
) . '#' . $this->theId;
}
return $this->parentContent['permalink'] . '#' . $this->theId;
return $this->parentContent->permalink . '#' . $this->theId;
}
/**
@@ -483,7 +462,7 @@ class Comments extends Base implements QueryInterface
*/
protected function ___content(): ?string
{
$text = $this->parentContent['hidden'] ? _t('内容被隐藏') : $this->text;
$text = $this->parentContent->hidden ? _t('内容被隐藏') : $this->text;
$text = Comments::pluginHandle()->trigger($plugged)->call('content', $text, $this);
if (!$plugged) {

View File

@@ -10,6 +10,7 @@ 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;
@@ -17,6 +18,8 @@ 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;
@@ -42,22 +45,20 @@ if (!defined('__TYPECHO_ROOT_DIR__')) {
* @property bool $allowPing
* @property bool $allowFeed
* @property int $parent
* @property int $parentId
* @property-read Users $author
* @property-read string $permalink
* @property-read string $pathinfo
* @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 string $category
* @property-read Date $date
* @property-read string $dateWord
* @property-read string[] $directory
* @property-read array $tags
* @property-read array $categories
* @property-read Metas $tags
* @property-read Metas $categories
* @property-read string $excerpt
* @property-read string $plainExcerpt
* @property-read string $summary
@@ -70,35 +71,51 @@ if (!defined('__TYPECHO_ROOT_DIR__')) {
* @property-read string $trackbackUrl
* @property-read string $responseUrl
*/
class Contents extends Base implements QueryInterface
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 urlencode($this->categories->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
* @throws Exception
*/
public function select(): Query
public function select(...$fields): Query
{
return $this->db->select(
'table.contents.cid',
'table.contents.title',
'table.contents.slug',
'table.contents.created',
'table.contents.authorId',
'table.contents.modified',
'table.contents.type',
'table.contents.status',
'table.contents.text',
'table.contents.commentsNum',
'table.contents.order',
'table.contents.template',
'table.contents.password',
'table.contents.allowComment',
'table.contents.allowPing',
'table.contents.allowFeed',
'table.contents.parent'
)->from('table.contents');
return $this->db->select(...$fields)->from('table.contents');
}
/**
@@ -271,165 +288,6 @@ class Contents extends Base implements QueryInterface
return $this->db->query($condition->delete('table.contents'));
}
/**
* 删除自定义字段
*
* @param integer $cid
* @return integer
* @throws Exception
*/
public function deleteFields(int $cid): int
{
return $this->db->query($this->db->delete('table.fields')
->where('cid = ?', $cid));
}
/**
* 保存自定义字段
*
* @param array $fields
* @param mixed $cid
* @return void
* @throws Exception
*/
public function applyFields(array $fields, $cid)
{
$exists = array_flip(array_column($this->db->fetchAll($this->db->select('name')
->from('table.fields')->where('cid = ?', $cid)), 'name'));
foreach ($fields as $name => $value) {
$type = 'str';
if (is_array($value) && 2 == count($value)) {
$type = $value[0];
$value = $value[1];
} elseif (strpos($name, ':') > 0) {
[$type, $name] = explode(':', $name, 2);
}
if (!$this->checkFieldName($name)) {
continue;
}
$isFieldReadOnly = Contents::pluginHandle()->trigger($plugged)->call('isFieldReadOnly', $name);
if ($plugged && $isFieldReadOnly) {
continue;
}
if (isset($exists[$name])) {
unset($exists[$name]);
}
$this->setField($name, $type, $value, $cid);
}
foreach ($exists as $name => $value) {
$this->db->query($this->db->delete('table.fields')
->where('cid = ? AND name = ?', $cid, $name));
}
}
/**
* 检查字段名是否符合要求
*
* @param string $name
* @return boolean
*/
public function checkFieldName(string $name): bool
{
return preg_match("/^[_a-z][_a-z0-9]*$/i", $name);
}
/**
* 设置单个字段
*
* @param string $name
* @param string $type
* @param mixed $value
* @param integer $cid
* @return integer|bool
* @throws Exception
*/
public function setField(string $name, string $type, $value, int $cid)
{
if (
empty($name) || !$this->checkFieldName($name)
|| !in_array($type, ['str', 'int', 'float', 'json'])
) {
return false;
}
if ($type === 'json') {
$value = json_encode($value);
}
$exist = $this->db->fetchRow($this->db->select('cid')->from('table.fields')
->where('cid = ? AND name = ?', $cid, $name));
$rows = [
'type' => $type,
'str_value' => 'str' == $type || 'json' == $type ? $value : null,
'int_value' => 'int' == $type ? intval($value) : 0,
'float_value' => 'float' == $type ? floatval($value) : 0
];
if (empty($exist)) {
$rows['cid'] = $cid;
$rows['name'] = $name;
return $this->db->query($this->db->insert('table.fields')->rows($rows));
} else {
return $this->db->query($this->db->update('table.fields')
->rows($rows)
->where('cid = ? AND name = ?', $cid, $name));
}
}
/**
* 自增一个整形字段
*
* @param string $name
* @param integer $value
* @param integer $cid
* @return integer
* @throws Exception
*/
public function incrIntField(string $name, int $value, int $cid)
{
if (!$this->checkFieldName($name)) {
return false;
}
$exist = $this->db->fetchRow($this->db->select('type')->from('table.fields')
->where('cid = ? AND name = ?', $cid, $name));
if (empty($exist)) {
return $this->db->query($this->db->insert('table.fields')
->rows([
'cid' => $cid,
'name' => $name,
'type' => 'int',
'str_value' => null,
'int_value' => $value,
'float_value' => 0
]));
} else {
$struct = [
'str_value' => null,
'float_value' => null
];
if ('int' != $exist['type']) {
$struct['type'] = 'int';
}
return $this->db->query($this->db->update('table.fields')
->rows($struct)
->expression('int_value', 'int_value ' . ($value >= 0 ? '+' : '') . $value)
->where('cid = ? AND name = ?', $cid, $name));
}
}
/**
* 按照条件计算内容数量
*
@@ -482,140 +340,19 @@ class Contents extends Base implements QueryInterface
/**
* 通用过滤器
*
* @param array $value 需要过滤的行数据
* @param array $row 需要过滤的行数据
* @return array
*/
public function filter(array $value): array
public function filter(array $row): array
{
/** 处理默认空值 */
$value['title'] = $value['title'] ?? '';
$value['text'] = $value['text'] ?? '';
$value['slug'] = $value['slug'] ?? '';
$row['title'] = $row['title'] ?? '';
$row['text'] = $row['text'] ?? '';
$row['slug'] = $row['slug'] ?? '';
$row['password'] = $row['password'] ?? '';
$row['date'] = new Date($row['created']);
/** 取出所有分类 */
$value['categories'] = $this->db->fetchAll($this->db
->select()->from('table.metas')
->join('table.relationships', 'table.relationships.mid = table.metas.mid')
->where('table.relationships.cid = ?', $value['cid'])
->where('table.metas.type = ?', 'category'), [Rows::alloc(), 'filter']);
$value['category'] = '';
$value['directory'] = [];
/** 取出第一个分类作为slug条件 */
if (!empty($value['categories'])) {
/** 使用自定义排序 */
usort($value['categories'], function ($a, $b) {
$field = 'order';
if ($a['order'] == $b['order']) {
$field = 'mid';
}
return $a[$field] < $b[$field] ? - 1 : 1;
});
$value['category'] = $value['categories'][0]['slug'];
$value['directory'] = Rows::alloc()
->getAllParentsSlug($value['categories'][0]['mid']);
$value['directory'][] = $value['category'];
}
$value['date'] = new Date($value['created']);
/** 生成日期 */
$value['year'] = $value['date']->year;
$value['month'] = $value['date']->month;
$value['day'] = $value['date']->day;
/** 生成访问权限 */
$value['hidden'] = false;
/** 获取路由类型并判断此类型在路由表中是否存在 */
$type = $value['type'];
$routeExists = (null != Router::get($type));
$tmpSlug = $value['slug'];
$tmpCategory = $value['category'];
$tmpDirectory = $value['directory'];
$value['slug'] = urlencode($value['slug']);
$value['category'] = urlencode($value['category']);
$value['directory'] = implode('/', array_map('urlencode', $value['directory']));
/** 生成静态路径 */
$value['pathinfo'] = $routeExists ? Router::url($type, $value) : '#';
/** 生成静态链接 */
$value['url'] = $value['permalink'] = Common::url($value['pathinfo'], $this->options->index);
/** 处理附件 */
if ('attachment' == $type) {
$content = @unserialize($value['text']);
//增加数据信息
$value['attachment'] = new Config($content);
$value['attachment']->isImage = in_array($content['type'], ['jpg', 'jpeg', 'gif', 'png', 'tiff', 'bmp', 'webp', 'avif', 'svg']);
$value['attachment']->url = Upload::attachmentHandle($value);
if ($value['attachment']->isImage) {
$value['text'] = '<img src="' . $value['attachment']->url . '" alt="' .
$value['title'] . '" />';
} else {
$value['text'] = '<a href="' . $value['attachment']->url . '" title="' .
$value['title'] . '">' . $value['title'] . '</a>';
}
}
/** 处理Markdown **/
if (isset($value['text'])) {
$value['isMarkdown'] = (0 === strpos($value['text'], '<!--markdown-->'));
if ($value['isMarkdown']) {
$value['text'] = substr($value['text'], 15);
}
}
/** 生成聚合链接 */
/** RSS 2.0 */
$value['feedUrl'] = $routeExists ? Router::url($type, $value, $this->options->feedUrl) : '#';
/** RSS 1.0 */
$value['feedRssUrl'] = $routeExists ? Router::url($type, $value, $this->options->feedRssUrl) : '#';
/** ATOM 1.0 */
$value['feedAtomUrl'] = $routeExists ? Router::url($type, $value, $this->options->feedAtomUrl) : '#';
$value['slug'] = $tmpSlug;
$value['category'] = $tmpCategory;
$value['directory'] = $tmpDirectory;
/** 处理密码保护流程 */
if (
strlen($value['password'] ?? '') > 0 &&
$value['password'] !== Cookie::get('protectPassword_' . $value['cid']) &&
$value['authorId'] != $this->user->uid &&
!$this->user->pass('editor', true)
) {
$value['hidden'] = true;
}
$value = Contents::pluginHandle()->call('filter', $value, $this);
/** 如果访问权限被禁止 */
if ($value['hidden']) {
$value['text'] = '<form class="protected" action="' . $this->security->getTokenUrl($value['permalink'])
. '" method="post">' .
'<p class="word">' . _t('请输入密码访问') . '</p>' .
'<p><input type="password" class="text" name="protectPassword" />
<input type="hidden" name="protectCID" value="' . $value['cid'] . '" />
<input type="submit" class="submit" value="' . _t('提交') . '" /></p>' .
'</form>';
$value['title'] = _t('此内容被密码保护');
$value['tags'] = [];
$value['commentsNum'] = 0;
}
return $value;
return Contents::pluginHandle()->call('filter', $row, $this);
}
/**
@@ -725,12 +462,12 @@ class Contents extends Base implements QueryInterface
public function category(string $split = ',', bool $link = true, ?string $default = null)
{
$categories = $this->categories;
if ($categories) {
if ($categories->have()) {
$result = [];
foreach ($categories as $category) {
$result[] = $link ? '<a href="' . $category['permalink'] . '">'
. $category['name'] . '</a>' : $category['name'];
while ($categories->next()) {
$result[] = $link ? $categories->template('<a href="{permalink}">{name}</a>') : $categories->name;
}
echo implode($split, $result);
@@ -776,12 +513,13 @@ class Contents extends Base implements QueryInterface
*/
public function tags(string $split = ',', bool $link = true, ?string $default = null)
{
/** 取出tags */
if ($this->tags) {
$tags = $this->tags;
if ($tags->have()) {
$result = [];
foreach ($this->tags as $tag) {
$result[] = $link ? '<a href="' . $tag['permalink'] . '">'
. $tag['name'] . '</a>' : $tag['name'];
while ($tags->next()) {
$result[] = $link ? $tags->template('<a href="{permalink}">{name}</a>') : $tags->name;
}
echo implode($split, $result);
@@ -803,18 +541,149 @@ class Contents extends Base implements QueryInterface
}
/**
* 将tags取出
* @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
* @throws Exception
*/
protected function ___tags(): array
protected function ___directory(): array
{
return $this->db->fetchAll($this->db
->select()->from('table.metas')
->join('table.relationships', 'table.relationships.mid = table.metas.mid')
->where('table.relationships.cid = ?', $this->cid)
->where('table.metas.type = ?', 'tag'), [Metas::alloc(), 'filter']);
$directory = [];
$category = $this->categories;
if ($category->have()) {
$directory = Rows::alloc()->getAllParentsSlug($category->mid);
$directory[] = $category->slug;
}
return $directory;
}
/**
* @return Metas
*/
protected function ___categories(): Metas
{
return CategoryRelated::allocWithAlias($this->cid, ['cid' => $this->cid]);
}
/**
* 将tags取出
*
* @return Metas
*/
protected function ___tags(): Metas
{
return TagRelated::allocWithAlias($this->cid, ['cid' => $this->cid]);
}
/**
@@ -837,16 +706,6 @@ class Contents extends Base implements QueryInterface
return $this->date->word();
}
/**
* 获取父id
*
* @return int|null
*/
protected function ___parentId(): ?int
{
return $this->row['parent'];
}
/**
* 对文章的简短纯文本描述
*
@@ -855,7 +714,28 @@ class Contents extends Base implements QueryInterface
*/
protected function ___description(): ?string
{
return $this->___plainExcerpt();
return $this->plainExcerpt;
}
/**
* @return Config|null
*/
protected function ___attachment(): ?Config
{
if ('attachment' == $this->type) {
$content = @unserialize($this->row['text']);
//增加数据信息
$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;
}
/**
@@ -889,14 +769,8 @@ class Contents extends Base implements QueryInterface
return $this->text;
}
$content = Contents::pluginHandle()->trigger($plugged)->call('excerpt', $this->text, $this);
if (!$plugged) {
$content = $this->isMarkdown ? $this->markdown($content)
: $this->autoP($content);
}
$contents = explode('<!--more-->', $content);
[$excerpt] = $contents;
$content = Contents::pluginHandle()->call('excerpt', $this->content, $this);
[$excerpt] = explode('<!--more-->', $content);
return Common::fixHtml(Contents::pluginHandle()->call('excerptEx', $excerpt, $this));
}
@@ -919,7 +793,7 @@ class Contents extends Base implements QueryInterface
* @param string|null $text
* @return string|null
*/
public function markdown(?string $text): ?string
protected function markdown(?string $text): ?string
{
$html = Contents::pluginHandle()->trigger($parsed)->call('markdown', $text);
@@ -936,7 +810,7 @@ class Contents extends Base implements QueryInterface
* @param string|null $text
* @return string|null
*/
public function autoP(?string $text): ?string
protected function autoP(?string $text): ?string
{
$html = Contents::pluginHandle()->trigger($parsed)->call('autoP', $text);
@@ -1021,7 +895,7 @@ class Contents extends Base implements QueryInterface
/** 评论 */
return Router::url(
'feedback',
['type' => 'comment', 'permalink' => $this->pathinfo],
['type' => 'comment', 'permalink' => $this->path],
$this->options->index
);
}
@@ -1035,7 +909,7 @@ class Contents extends Base implements QueryInterface
{
return Router::url(
'feedback',
['type' => 'trackback', 'permalink' => $this->pathinfo],
['type' => 'trackback', 'permalink' => $this->path],
$this->options->index
);
}
@@ -1049,45 +923,4 @@ class Contents extends Base implements QueryInterface
{
return $this->permalink . '#' . $this->respondId;
}
/**
* 获取页面偏移
*
* @param string $column 字段名
* @param integer $offset 偏移值
* @param string $type 类型
* @param string|null $status 状态值
* @param integer $authorId 作者
* @param integer $pageSize 分页值
* @return integer
* @throws Exception
*/
protected function getPageOffset(
string $column,
int $offset,
string $type,
?string $status = null,
int $authorId = 0,
int $pageSize = 20
): int {
$select = $this->db->select(['COUNT(table.contents.cid)' => 'num'])->from('table.contents')
->where("table.contents.{$column} > {$offset}")
->where(
"table.contents.type = ? OR (table.contents.type = ? AND table.contents.parent = ?)",
$type,
$type . '_draft',
0
);
if (!empty($status)) {
$select->where("table.contents.status = ?", $status);
}
if ($authorId > 0) {
$select->where('table.contents.authorId = ?', $authorId);
}
$count = $this->db->fetchObject($select)->num + 1;
return ceil($count / $pageSize);
}
}

View File

@@ -2,10 +2,10 @@
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__')) {
@@ -17,6 +17,7 @@ if (!defined('__TYPECHO_ROOT_DIR__')) {
*
* @property int $mid
* @property string $name
* @property string $title
* @property string $slug
* @property string $type
* @property string $description
@@ -26,12 +27,39 @@ if (!defined('__TYPECHO_ROOT_DIR__')) {
* @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
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 '';
}
}
/**
* 获取记录总数
*
@@ -59,62 +87,12 @@ class Metas extends Base implements QueryInterface
/**
* 通用过滤器
*
* @param array $value 需要过滤的行数据
* @param array $row 需要过滤的行数据
* @return array
*/
public function filter(array $value): array
public function filter(array $row): array
{
//生成静态链接
$type = $value['type'];
$routeExists = (null != Router::get($type));
$tmpSlug = $value['slug'];
$value['slug'] = urlencode($value['slug']);
$value['url'] = $value['permalink'] = $routeExists ? Router::url($type, $value, $this->options->index) : '#';
/** 生成聚合链接 */
/** RSS 2.0 */
$value['feedUrl'] = $routeExists ? Router::url($type, $value, $this->options->feedUrl) : '#';
/** RSS 1.0 */
$value['feedRssUrl'] = $routeExists ? Router::url($type, $value, $this->options->feedRssUrl) : '#';
/** ATOM 1.0 */
$value['feedAtomUrl'] = $routeExists ? Router::url($type, $value, $this->options->feedAtomUrl) : '#';
$value['slug'] = $tmpSlug;
return Metas::pluginHandle()->call('filter', $value, $this);
}
/**
* 获取最大排序
*
* @param string $type
* @param int $parent
* @return integer
* @throws Exception
*/
public function getMaxOrder(string $type, int $parent = 0): int
{
return $this->db->fetchObject($this->db->select(['MAX(order)' => 'maxOrder'])
->from('table.metas')
->where('type = ? AND parent = ?', $type, $parent))->maxOrder ?? 0;
}
/**
* 对数据按照sort字段排序
*
* @param array $metas
* @param string $type
*/
public function sort(array $metas, string $type)
{
foreach ($metas as $sort => $mid) {
$this->update(
['order' => $sort + 1],
$this->db->sql()->where('mid = ?', $mid)->where('type = ?', $type)
);
}
return Metas::pluginHandle()->call('filter', $row, $this);
}
/**
@@ -130,58 +108,16 @@ class Metas extends Base implements QueryInterface
return $this->db->query($condition->update('table.metas')->rows($rows));
}
/**
* 合并数据
*
* @param integer $mid 数据主键
* @param string $type 数据类型
* @param array $metas 需要合并的数据集
* @throws Exception
*/
public function merge(int $mid, string $type, array $metas)
{
$contents = array_column($this->db->fetchAll($this->db->select('cid')
->from('table.relationships')
->where('mid = ?', $mid)), 'cid');
foreach ($metas as $meta) {
if ($mid != $meta) {
$existsContents = array_column($this->db->fetchAll($this->db
->select('cid')->from('table.relationships')
->where('mid = ?', $meta)), 'cid');
$where = $this->db->sql()->where('mid = ? AND type = ?', $meta, $type);
$this->delete($where);
$diffContents = array_diff($existsContents, $contents);
$this->db->query($this->db->delete('table.relationships')->where('mid = ?', $meta));
foreach ($diffContents as $content) {
$this->db->query($this->db->insert('table.relationships')
->rows(['mid' => $mid, 'cid' => $content]));
$contents[] = $content;
}
$this->update(['parent' => $mid], $this->db->sql()->where('parent = ?', $meta));
unset($existsContents);
}
}
$num = $this->db->fetchObject($this->db
->select(['COUNT(mid)' => 'num'])->from('table.relationships')
->where('table.relationships.mid = ?', $mid))->num;
$this->update(['count' => $num], $this->db->sql()->where('mid = ?', $mid));
}
/**
* 获取原始查询对象
*
* @param mixed $fields
* @return Query
* @throws Exception
*/
public function select(): Query
public function select(...$fields): Query
{
return $this->db->select()->from('table.metas');
return $this->db->select(...$fields)->from('table.metas');
}
/**
@@ -196,47 +132,6 @@ class Metas extends Base implements QueryInterface
return $this->db->query($condition->delete('table.metas'));
}
/**
* 根据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);
}
/**
* 插入一条记录
*
@@ -249,50 +144,6 @@ class Metas extends Base implements QueryInterface
return $this->db->query($this->db->insert('table.metas')->rows($rows));
}
/**
* 清理没有任何内容的标签
*
* @throws Exception
*/
public function clearTags()
{
// 取出count为0的标签
$tags = array_column($this->db->fetchAll($this->db->select('mid')
->from('table.metas')->where('type = ? AND count = ?', 'tags', 0)), 'mid');
foreach ($tags as $tag) {
// 确认是否已经没有关联了
$content = $this->db->fetchRow($this->db->select('cid')
->from('table.relationships')->where('mid = ?', $tag)
->limit(1));
if (empty($content)) {
$this->db->query($this->db->delete('table.metas')
->where('mid = ?', $tag));
}
}
}
/**
* 根据内容的指定类别和状态更新相关meta的计数信息
*
* @param int $mid meta id
* @param string $type 类别
* @param string $status 状态
* @throws Exception
*/
public function refreshCountByTypeAndStatus(int $mid, string $type, string $status = 'publish')
{
$num = $this->db->fetchObject($this->db->select(['COUNT(table.contents.cid)' => 'num'])->from('table.contents')
->join('table.relationships', 'table.contents.cid = table.relationships.cid')
->where('table.relationships.mid = ?', $mid)
->where('table.contents.type = ?', $type)
->where('table.contents.status = ?', $status))->num;
$this->db->query($this->db->update('table.metas')->rows(['count' => $num])
->where('mid = ?', $mid));
}
/**
* 锚点id
*
@@ -303,4 +154,60 @@ class Metas extends Base implements QueryInterface
{
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);
}
}

View File

@@ -23,13 +23,13 @@ class Options extends Base implements QueryInterface
/**
* 获取原始查询对象
*
* @access public
* @param mixed ...$fields
* @return Query
* @throws Exception
*/
public function select(): Query
public function select(...$fields): Query
{
return $this->db->select()->from('table.options');
return $this->db->select(...$fields)->from('table.options');
}
/**

View File

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

View File

@@ -12,9 +12,10 @@ interface QueryInterface
/**
* 查询方法
*
* @param mixed $fields 字段
* @return Query
*/
public function select(): Query;
public function select(...$fields): Query;
/**
* 获得所有记录数

View File

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

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);
}
}
}
}

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--;
}
}
}

View File

@@ -6,8 +6,8 @@ use Typecho\Common;
use Typecho\Config;
use Typecho\Db\Exception;
use Typecho\Db\Query;
use Typecho\Plugin;
use Typecho\Router;
use Typecho\Router\ParamsDelegateInterface;
use Widget\Base;
if (!defined('__TYPECHO_ROOT_DIR__')) {
@@ -34,8 +34,16 @@ if (!defined('__TYPECHO_ROOT_DIR__')) {
* @property-read string $feedRssUrl
* @property-read string $feedAtomUrl
*/
class Users extends Base implements QueryInterface
class Users extends Base implements QueryInterface, RowFilterInterface, PrimaryKeyInterface, ParamsDelegateInterface
{
/**
* @return string 获取主键
*/
public function getPrimaryKey(): string
{
return 'uid';
}
/**
* 将每行的值压入堆栈
*
@@ -51,38 +59,38 @@ class Users extends Base implements QueryInterface
/**
* 通用过滤器
*
* @param array $value 需要过滤的行数据
* @param array $row 需要过滤的行数据
* @return array
*/
public function filter(array $value): array
public function filter(array $row): array
{
//生成静态链接
$routeExists = (null != Router::get('author'));
return Users::pluginHandle()->call('filter', $row, $this);
}
$value['permalink'] = $routeExists ? Router::url('author', $value, $this->options->index) : '#';
/** 生成聚合链接 */
/** RSS 2.0 */
$value['feedUrl'] = $routeExists ? Router::url('author', $value, $this->options->feedUrl) : '#';
/** RSS 1.0 */
$value['feedRssUrl'] = $routeExists ? Router::url('author', $value, $this->options->feedRssUrl) : '#';
/** ATOM 1.0 */
$value['feedAtomUrl'] = $routeExists ? Router::url('author', $value, $this->options->feedAtomUrl) : '#';
return Users::pluginHandle()->call('filter', $value, $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(): Query
public function select(...$fields): Query
{
return $this->db->select()->from('table.users');
return $this->db->select(...$fields)->from('table.users');
}
/**
@@ -149,6 +157,38 @@ class Users extends Base implements QueryInterface
$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
*

View File

@@ -30,7 +30,7 @@ class CommentPage extends Base implements ActionInterface
}
$currentCommentUrl = Router::url('comment_page', [
'permalink' => $archive->pathinfo,
'permalink' => $archive->path,
'commentPage' => $page
], $this->options->index);

View File

@@ -9,6 +9,7 @@ use Typecho\Widget\Exception;
use Typecho\Widget\Helper\PageNavigator\Box;
use Widget\Base\Comments;
use Widget\Base\Contents;
use Widget\Contents\From;
if (!defined('__TYPECHO_ROOT_DIR__')) {
exit;
@@ -142,14 +143,12 @@ class Admin extends Comments
/**
* 获取当前内容结构
*
* @return array|null
* @return Contents
* @throws Db\Exception
*/
protected function ___parentContent(): ?array
protected function ___parentContent(): Contents
{
$cid = $this->request->is('cid') ? $this->request->filter('int')->get('cid') : $this->cid;
return $this->db->fetchRow(Contents::alloc()->select()
->where('table.contents.cid = ?', $cid)
->limit(1), [Contents::alloc(), 'filter']);
return From::allocWithAlias($cid, ['cid' => $cid]);
}
}

View File

@@ -8,6 +8,7 @@ use Typecho\Router;
use Typecho\Widget\Exception;
use Typecho\Widget\Helper\PageNavigator\Box;
use Widget\Base\Comments;
use Widget\Base\Contents;
if (!defined('__TYPECHO_ROOT_DIR__')) {
exit;
@@ -60,7 +61,14 @@ class Archive extends Comments
*/
protected function initParameter(Config $parameter)
{
$parameter->setDefault('parentId=0&commentPage=0&commentsNum=0&allowComment=1');
$parameter->setDefault([
'parentId' => 0,
'respondId' => '',
'commentPage' => 0,
'commentsNum' => 0,
'allowComment' => 1,
'parentContent' => null,
]);
}
/**
@@ -233,10 +241,7 @@ class Archive extends Comments
}
$template = array_merge($default, $config);
$pageRow = $this->parameter->parentContent;
$pageRow['permalink'] = $pageRow['pathinfo'];
$query = Router::url('comment_page', $pageRow, $this->options->index);
$query = Router::url('comment_page', $this, $this->options->index);
self::pluginHandle()->trigger($hasNav)->call(
'pageNav',
@@ -381,9 +386,7 @@ class Archive extends Comments
*/
public function levelsAlt(...$args)
{
$num = count($args);
$split = $this->levels % $num;
echo $args[(0 == $split ? $num : $split) - 1];
$this->altBy($this->levels, ...$args);
}
/**
@@ -393,12 +396,8 @@ class Archive extends Comments
*/
public function alt(...$args)
{
$num = count($args);
$sequence = $this->levels <= 0 ? $this->sequence : $this->order;
$split = $sequence % $num;
echo $args[(0 == $split ? $num : $split) - 1];
$this->altBy($sequence, ...$args);
}
/**
@@ -460,7 +459,7 @@ class Archive extends Comments
if (!$plugged) {
$replyId = $this->request->filter('int')->get('replyTo');
echo '<a id="cancel-comment-reply-link" href="' . $this->parameter->parentContent['permalink'] . '#' . $this->parameter->respondId .
echo '<a id="cancel-comment-reply-link" href="' . $this->parameter->parentContent->permalink . '#' . $this->parameter->respondId .
'" rel="nofollow"' . ($replyId ? '' : ' style="display:none"') . ' onclick="return TypechoComment.cancelReply();">' . $word . '</a>';
}
}
@@ -473,13 +472,12 @@ class Archive extends Comments
*/
protected function ___permalink(): string
{
if ($this->options->commentsPageBreak) {
$pageRow = ['permalink' => $this->parentContent['pathinfo'], 'commentPage' => $this->currentPage];
$pageRow = ['permalink' => $this->parentContent->path, 'commentPage' => $this->currentPage];
return Router::url('comment_page', $pageRow, $this->options->index) . '#' . $this->theId;
}
return $this->parentContent['permalink'] . '#' . $this->theId;
return $this->parentContent->permalink . '#' . $this->theId;
}
/**
@@ -506,9 +504,9 @@ class Archive extends Comments
/**
* 重载内容获取
*
* @return array|null
* @return Contents
*/
protected function ___parentContent(): ?array
protected function ___parentContent(): Contents
{
return $this->parameter->parentContent;
}

View File

@@ -3,6 +3,7 @@
namespace Widget\Comments;
use Typecho\Db\Exception;
use Typecho\Db\Query;
use Widget\Base\Comments;
use Widget\ActionInterface;
use Widget\Notice;
@@ -47,6 +48,30 @@ class Edit extends Comments implements ActionInterface
$this->response->goBack();
}
/**
* 评论是否可以被修改
*
* @param Query|null $condition 条件
* @return bool
* @throws Exception|\Typecho\Widget\Exception
*/
public function commentIsWriteable(?Query $condition = null): bool
{
if (empty($condition)) {
if ($this->have() && ($this->user->pass('editor', true) || $this->ownerId == $this->user->uid)) {
return true;
}
} else {
$post = $this->db->fetchRow($condition->select('ownerId')->from('table.comments')->limit(1));
if ($post && ($this->user->pass('editor', true) || $post['ownerId'] == $this->user->uid)) {
return true;
}
}
return false;
}
/**
* 标记评论状态
*

View File

@@ -0,0 +1,108 @@
<?php
namespace Widget\Contents;
use Typecho\Config;
use Typecho\Db\Exception as DbException;
use Typecho\Db\Query;
use Typecho\Widget\Exception;
use Typecho\Widget\Helper\PageNavigator\Box;
/**
* 文章管理列表组件
*
* @property-read array? $revision
*/
trait AdminTrait
{
/**
* 所有文章个数
*
* @var integer|null
*/
private ?int $total;
/**
* 当前页
*
* @var integer
*/
private int $currentPage;
/**
* @return void
*/
protected function initPage()
{
$this->parameter->setDefault('pageSize=20');
$this->currentPage = $this->request->get('page', 1);
}
/**
* @param Query $select
* @return void
*/
protected function searchQuery(Query $select)
{
if ($this->request->is('keywords')) {
$keywords = $this->request->filter('search')->get('keywords');
$args = [];
$keywordsList = explode(' ', $keywords);
$args[] = implode(' OR ', array_fill(0, count($keywordsList), 'table.contents.title LIKE ?'));
foreach ($keywordsList as $keyword) {
$args[] = '%' . $keyword . '%';
}
$select->where(...$args);
}
}
/**
* @param Query $select
* @return void
*/
protected function countTotal(Query $select)
{
$countSql = clone $select;
$this->total = $this->size($countSql);
}
/**
* 输出分页
*
* @throws Exception
* @throws DbException
*/
public function pageNav()
{
$query = $this->request->makeUriByRequest('page={page}');
/** 使用盒状分页 */
$nav = new Box(
$this->total,
$this->currentPage,
$this->parameter->pageSize,
$query
);
$nav->render('&laquo;', '&raquo;');
}
/**
* @return array|null
* @throws DbException
*/
protected function ___revision(): ?array
{
return $this->db->fetchRow(
$this->select('cid', 'modified')
->where(
'table.contents.parent = ? AND table.contents.type = ?',
$this->cid,
'revision'
)
->limit(1)
);
}
}

View File

@@ -5,9 +5,8 @@ namespace Widget\Contents\Attachment;
use Typecho\Config;
use Typecho\Db;
use Typecho\Db\Exception;
use Typecho\Db\Query;
use Typecho\Widget\Helper\PageNavigator\Box;
use Widget\Base\Contents;
use Widget\Contents\AdminTrait;
if (!defined('__TYPECHO_ROOT_DIR__')) {
exit;
@@ -23,26 +22,7 @@ if (!defined('__TYPECHO_ROOT_DIR__')) {
*/
class Admin extends Contents
{
/**
* 用于计算数值的语句对象
*
* @var Query
*/
private Query $countSql;
/**
* 所有文章个数
*
* @var integer|null
*/
private ?int $total;
/**
* 当前页
*
* @var integer
*/
private int $currentPage;
use AdminTrait;
/**
* 执行函数
@@ -52,8 +32,7 @@ class Admin extends Contents
*/
public function execute()
{
$this->parameter->setDefault('pageSize=20');
$this->currentPage = $this->request->get('page', 1);
$this->initPage();
/** 构建基础查询 */
$select = $this->select()->where('table.contents.type = ?', 'attachment');
@@ -64,20 +43,8 @@ class Admin extends Contents
}
/** 过滤标题 */
if (null != ($keywords = $this->request->filter('search')->get('keywords'))) {
$args = [];
$keywordsList = explode(' ', $keywords);
$args[] = implode(' OR ', array_fill(0, count($keywordsList), 'table.contents.title LIKE ?'));
foreach ($keywordsList as $keyword) {
$args[] = '%' . $keyword . '%';
}
call_user_func_array([$select, 'where'], $args);
}
/** 给计算数目对象赋值,克隆对象 */
$this->countSql = clone $select;
$this->searchQuery($select);
$this->countTotal($select);
/** 提交查询 */
$select->order('table.contents.created', Db::SORT_DESC)
@@ -86,27 +53,6 @@ class Admin extends Contents
$this->db->fetchAll($select, [$this, 'push']);
}
/**
* 输出分页
*
* @return void
* @throws Exception|\Typecho\Widget\Exception
*/
public function pageNav()
{
$query = $this->request->makeUriByRequest('page={page}');
/** 使用盒状分页 */
$nav = new Box(
!isset($this->total) ? $this->total = $this->size($this->countSql) : $this->total,
$this->currentPage,
$this->parameter->pageSize,
$query
);
$nav->render('&laquo;', '&raquo;');
}
/**
* 所属文章
*

View File

@@ -2,7 +2,6 @@
namespace Widget\Contents;
use Typecho\Common;
use Typecho\Config;
use Typecho\Db\Exception as DbException;
use Typecho\Validate;
@@ -12,8 +11,170 @@ use Typecho\Widget\Helper\Layout;
use Widget\Base\Contents;
use Widget\Base\Metas;
/**
* 内容编辑组件
*/
trait EditTrait
{
/**
* 删除自定义字段
*
* @param integer $cid
* @return integer
* @throws DbException
*/
public function deleteFields(int $cid): int
{
return $this->db->query($this->db->delete('table.fields')
->where('cid = ?', $cid));
}
/**
* 保存自定义字段
*
* @param array $fields
* @param mixed $cid
* @return void
* @throws Exception
*/
public function applyFields(array $fields, $cid)
{
$exists = array_flip(array_column($this->db->fetchAll($this->db->select('name')
->from('table.fields')->where('cid = ?', $cid)), 'name'));
foreach ($fields as $name => $value) {
$type = 'str';
if (is_array($value) && 2 == count($value)) {
$type = $value[0];
$value = $value[1];
} elseif (strpos($name, ':') > 0) {
[$type, $name] = explode(':', $name, 2);
}
if (!$this->checkFieldName($name)) {
continue;
}
$isFieldReadOnly = Contents::pluginHandle()->trigger($plugged)->call('isFieldReadOnly', $name);
if ($plugged && $isFieldReadOnly) {
continue;
}
if (isset($exists[$name])) {
unset($exists[$name]);
}
$this->setField($name, $type, $value, $cid);
}
foreach ($exists as $name => $value) {
$this->db->query($this->db->delete('table.fields')
->where('cid = ? AND name = ?', $cid, $name));
}
}
/**
* 检查字段名是否符合要求
*
* @param string $name
* @return boolean
*/
public function checkFieldName(string $name): bool
{
return preg_match("/^[_a-z][_a-z0-9]*$/i", $name);
}
/**
* 设置单个字段
*
* @param string $name
* @param string $type
* @param mixed $value
* @param integer $cid
* @return integer|bool
* @throws Exception
*/
public function setField(string $name, string $type, $value, int $cid)
{
if (
empty($name) || !$this->checkFieldName($name)
|| !in_array($type, ['str', 'int', 'float', 'json'])
) {
return false;
}
if ($type === 'json') {
$value = json_encode($value);
}
$exist = $this->db->fetchRow($this->db->select('cid')->from('table.fields')
->where('cid = ? AND name = ?', $cid, $name));
$rows = [
'type' => $type,
'str_value' => 'str' == $type || 'json' == $type ? $value : null,
'int_value' => 'int' == $type ? intval($value) : 0,
'float_value' => 'float' == $type ? floatval($value) : 0
];
if (empty($exist)) {
$rows['cid'] = $cid;
$rows['name'] = $name;
return $this->db->query($this->db->insert('table.fields')->rows($rows));
} else {
return $this->db->query($this->db->update('table.fields')
->rows($rows)
->where('cid = ? AND name = ?', $cid, $name));
}
}
/**
* 自增一个整形字段
*
* @param string $name
* @param integer $value
* @param integer $cid
* @return integer
* @throws Exception
*/
public function incrIntField(string $name, int $value, int $cid)
{
if (!$this->checkFieldName($name)) {
return false;
}
$exist = $this->db->fetchRow($this->db->select('type')->from('table.fields')
->where('cid = ? AND name = ?', $cid, $name));
if (empty($exist)) {
return $this->db->query($this->db->insert('table.fields')
->rows([
'cid' => $cid,
'name' => $name,
'type' => 'int',
'str_value' => null,
'int_value' => $value,
'float_value' => 0
]));
} else {
$struct = [
'str_value' => null,
'float_value' => null
];
if ('int' != $exist['type']) {
$struct['type'] = 'int';
}
return $this->db->query($this->db->update('table.fields')
->rows($struct)
->expression('int_value', 'int_value ' . ($value >= 0 ? '+' : '') . $value)
->where('cid = ? AND name = ?', $cid, $name));
}
}
/**
* getFieldItems
*
@@ -169,12 +330,12 @@ trait EditTrait
}
/**
* 删除草稿
* 删除内容
*
* @param integer $cid 草稿id
* @throws DbException
*/
protected function deleteDraft(int $cid, bool $hasMetas = true)
protected function deleteContent(int $cid, bool $hasMetas = true)
{
$this->delete($this->db->sql()->where('cid = ?', $cid));
@@ -308,7 +469,7 @@ trait EditTrait
*/
protected function setTags(int $cid, ?string $tags, bool $beforeCount = true, bool $afterCount = true)
{
$tags = str_replace('', ',', $tags);
$tags = str_replace('', ',', $tags ?? '');
$tags = array_unique(array_map('trim', explode(',', $tags)));
$tags = array_filter($tags, [Validate::class, 'xssCheck']);
@@ -427,7 +588,7 @@ trait EditTrait
/** 如果它本身不是草稿, 需要删除其草稿 */
if (!$isDraftToPublish && $this->draft) {
$cid = $this->draft['cid'];
$this->deleteDraft($cid);
$this->deleteContent($cid);
$this->deleteFields($cid);
}
@@ -489,6 +650,11 @@ trait EditTrait
/** 如果草稿已经存在 */
if ($this->draft) {
$isRevision = !preg_match("/_draft$/", $this->type);
if ($isRevision) {
$contents['parent'] = $this->cid;
$contents['type'] = 'revision';
}
/** 直接将草稿状态更改 */
if ($this->update($contents, $this->db->sql()->where('cid = ?', $this->draft['cid']))) {
@@ -497,6 +663,7 @@ trait EditTrait
} else {
if ($this->have()) {
$contents['parent'] = $this->cid;
$contents['type'] = 'revision';
}
/** 发布一个新内容 */
@@ -536,6 +703,47 @@ trait EditTrait
return $this->draft['cid'];
}
/**
* 获取页面偏移
*
* @param string $column 字段名
* @param integer $offset 偏移值
* @param string $type 类型
* @param string|null $status 状态值
* @param integer $authorId 作者
* @param integer $pageSize 分页值
* @return integer
* @throws DbException
*/
protected function getPageOffset(
string $column,
int $offset,
string $type,
?string $status = null,
int $authorId = 0,
int $pageSize = 20
): int {
$select = $this->db->select(['COUNT(table.contents.cid)' => 'num'])->from('table.contents')
->where("table.contents.{$column} > {$offset}")
->where(
"table.contents.type = ? OR (table.contents.type = ? AND table.contents.parent = ?)",
$type,
$type . '_draft',
0
);
if (!empty($status)) {
$select->where("table.contents.status = ?", $status);
}
if ($authorId > 0) {
$select->where('table.contents.authorId = ?', $authorId);
}
$count = $this->db->fetchObject($select)->num + 1;
return ceil($count / $pageSize);
}
/**
* @param array $contents
* @return void

View File

@@ -0,0 +1,79 @@
<?php
namespace Widget\Contents;
use Typecho\Db\Exception;
use Widget\Base\Contents;
use Widget\Base\TreeTrait;
if (!defined('__TYPECHO_ROOT_DIR__')) {
exit;
}
/**
* 单个内容组件
*/
class From extends Contents
{
use TreeTrait {
initParameter as initTreeParameter;
___directory as ___treeDirectory;
}
/**
* @return void
* @throws Exception
*/
public function execute()
{
$query = null;
if (isset($this->parameter->cid)) {
$query = $this->select()->where('cid = ?', $this->parameter->cid);
} elseif (isset($this->parameter->query)) {
$query = $this->parameter->query;
}
if ($query) {
$this->db->fetchAll($query, [$this, 'push']);
if ($this->type == 'page') {
$this->initTreeParameter($this->parameter);
}
}
}
/**
* @return array
*/
protected function ___directory(): array
{
return $this->type == 'page' ? $this->___treeDirectory() : parent::___directory();
}
/**
* @return array
* @throws Exception
*/
protected function initTreeRows(): array
{
return $this->db->fetchAll($this->select(
'table.contents.cid',
'table.contents.title',
'table.contents.slug',
'table.contents.created',
'table.contents.authorId',
'table.contents.modified',
'table.contents.type',
'table.contents.status',
'table.contents.commentsNum',
'table.contents.order',
'table.contents.parent',
'table.contents.template',
'table.contents.password',
'table.contents.allowComment',
'table.contents.allowPing',
'table.contents.allowFeed'
)->where('table.contents.type = ?', 'page'));
}
}

View File

@@ -4,7 +4,11 @@ namespace Widget\Contents\Page;
use Typecho\Common;
use Typecho\Db;
use Widget\Contents\Post\Admin as PostAdmin;
use Typecho\Widget\Exception;
use Widget\Base\Contents;
use Widget\Base\TreeTrait;
use Widget\Contents\AdminTrait;
use Widget\Contents\From;
if (!defined('__TYPECHO_ROOT_DIR__')) {
exit;
@@ -18,8 +22,16 @@ if (!defined('__TYPECHO_ROOT_DIR__')) {
* @copyright Copyright (c) 2008 Typecho team (http://www.typecho.org)
* @license GNU General Public License 2.0
*/
class Admin extends PostAdmin
class Admin extends Contents
{
use AdminTrait;
use TreeTrait;
/**
* @var int 父级页面
*/
private int $parentId = 0;
/**
* 执行函数
*
@@ -29,30 +41,104 @@ class Admin extends PostAdmin
*/
public function execute()
{
/** 过滤状态 */
$select = $this->select()->where(
'table.contents.type = ? OR (table.contents.type = ? AND table.contents.parent = ?)',
'page',
'page_draft',
0
);
$this->parameter->setDefault('ignoreRequest=0');
/** 过滤标题 */
if (null != ($keywords = $this->request->get('keywords'))) {
$args = [];
$keywordsList = explode(' ', $keywords);
$args[] = implode(' OR ', array_fill(0, count($keywordsList), 'table.contents.title LIKE ?'));
if ($this->parameter->ignoreRequest) {
$this->stack = $this->getRows($this->orders, $this->parameter->ignore);
} elseif ($this->request->is('keywords')) {
$select = $this->select('table.contents.cid')
->where('table.contents.type = ? OR table.contents.type = ?', 'page', 'page_draft');
$this->searchQuery($select);
foreach ($keywordsList as $keyword) {
$args[] = '%' . Common::filterSearchQuery($keyword) . '%';
$ids = array_column($this->db->fetchAll($select), 'cid');
$this->stack = $this->getRows($ids);
} else {
$this->parentId = $this->request->filter('int')->get('parent', 0);
$this->stack = $this->getRows($this->getChildIds($this->parentId));
}
}
/**
* 向上的返回链接
*
* @throws Db\Exception
*/
public function backLink()
{
if ($this->parentId) {
$page = $this->getRow($this->parentId);
if (!empty($page)) {
$parent = $this->getRow($page['parent']);
if ($parent) {
echo '<a href="'
. Common::url('manage-pages.php?parent=' . $parent['mid'], $this->options->adminUrl)
. '">';
} else {
echo '<a href="' . Common::url('manage-pages.php', $this->options->adminUrl) . '">';
}
echo '&laquo; ';
_e('返回父级页面');
echo '</a>';
}
}
}
call_user_func_array([$select, 'where'], $args);
/**
* 获取菜单标题
*
* @return string|null
* @throws Db\Exception|Exception
*/
public function getMenuTitle(): ?string
{
if ($this->parentId) {
$page = $this->getRow($this->parentId);
if (!empty($page)) {
return _t('管理 %s 的子页面', $page['title']);
}
} else {
return null;
}
/** 提交查询 */
$select->order('table.contents.order');
throw new Exception(_t('页面不存在'), 404);
}
$this->db->fetchAll($select, [$this, 'push']);
/**
* 获取菜单标题
*
* @return string
*/
public function getAddLink(): string
{
return 'write-page.php' . ($this->parentId ? '?parent=' . $this->parentId : '');
}
/**
* @return array
* @throws Db\Exception
*/
protected function initTreeRows(): array
{
$select = $this->select(
'table.contents.cid',
'table.contents.title',
'table.contents.slug',
'table.contents.created',
'table.contents.authorId',
'table.contents.modified',
'table.contents.type',
'table.contents.status',
'table.contents.commentsNum',
'table.contents.order',
'table.contents.parent',
'table.contents.template',
'table.contents.password',
)->where('table.contents.type = ? OR table.contents.type = ?', 'page', 'page_draft');
return $this->db->fetchAll($select);
}
}

View File

@@ -20,11 +20,7 @@ if (!defined('__TYPECHO_ROOT_DIR__')) {
/**
* 编辑页面组件
*
* @author qining
* @category typecho
* @package Widget
* @copyright Copyright (c) 2008 Typecho team (http://www.typecho.org)
* @license GNU General Public License 2.0
* @property-read array $draft
*/
class Edit extends Contents implements ActionInterface
{
@@ -64,6 +60,7 @@ class Edit extends Contents implements ActionInterface
$contents['title'] = $this->request->get('title', _t('未命名页面'));
$contents['created'] = $this->getCreated();
$contents['visibility'] = ('hidden' == $contents['visibility'] ? 'hidden' : 'publish');
$contents['parent'] = $this->getParent();
if ($this->request->is('markdown=1') && $this->options->markdown) {
$contents['text'] = '<!--markdown-->' . $contents['text'];
@@ -92,7 +89,8 @@ class Edit extends Contents implements ActionInterface
Notice::alloc()->highlight($this->theId);
/** 页面跳转 */
$this->response->redirect(Common::url('manage-pages.php?', $this->options->adminUrl));
$this->response->redirect(Common::url('manage-pages.php'
. ($this->parent ? '?parent=' . $this->parent : ''), $this->options->adminUrl));
} else {
/** 保存文章 */
$contents['type'] = 'page_draft';
@@ -151,7 +149,7 @@ class Edit extends Contents implements ActionInterface
// 处理草稿
$draft = $this->db->fetchRow($this->db->select('cid')
->from('table.contents')
->where('table.contents.parent = ? AND table.contents.type = ?', $page, 'page_draft')
->where('table.contents.parent = ? AND table.contents.type = ?', $page, 'revision')
->limit(1));
if (!empty($draft)) {
@@ -192,6 +190,7 @@ class Edit extends Contents implements ActionInterface
foreach ($pages as $page) {
// 删除插件接口
self::pluginHandle()->call('delete', $page, $this);
$parent = $this->db->fetchObject($this->select()->where('cid = ?', $page))->parent;
if ($this->delete($this->db->sql()->where('cid = ?', $page))) {
/** 删除评论 */
@@ -211,17 +210,24 @@ class Edit extends Contents implements ActionInterface
/** 删除草稿 */
$draft = $this->db->fetchRow($this->db->select('cid')
->from('table.contents')
->where('table.contents.parent = ? AND table.contents.type = ?', $page, 'page_draft')
->where('table.contents.parent = ? AND table.contents.type = ?', $page, 'revision')
->limit(1));
/** 删除自定义字段 */
$this->deleteFields($page);
if ($draft) {
$this->deleteDraft($draft['cid'], false);
$this->deleteContent($draft['cid'], false);
$this->deleteFields($draft['cid']);
}
// update parent
$this->update(
['parent' => $parent],
$this->db->sql()->where('parent = ?', $page)
->where('type = ? OR type = ?', 'page', 'page_draft')
);
// 完成删除插件接口
self::pluginHandle()->call('finishDelete', $page, $this);
@@ -254,11 +260,11 @@ class Edit extends Contents implements ActionInterface
/** 删除草稿 */
$draft = $this->db->fetchRow($this->db->select('cid')
->from('table.contents')
->where('table.contents.parent = ? AND table.contents.type = ?', $page, 'page_draft')
->where('table.contents.parent = ? AND table.contents.type = ?', $page, 'revision')
->limit(1));
if ($draft) {
$this->deleteDraft($draft['cid']);
$this->deleteContent($draft['cid'], false);
$this->deleteFields($draft['cid']);
$deleteCount++;
}
@@ -328,6 +334,55 @@ class Edit extends Contents implements ActionInterface
$this->response->redirect($this->options->adminUrl);
}
/**
* 获取网页标题
*
* @return string
*/
public function getMenuTitle(): string
{
$this->prepare();
if ($this->have()) {
return _t('编辑 %s', $this->title);
}
if ($this->request->is('parent')) {
$page = $this->db->fetchRow($this->select()
->where('table.contents.type = ? OR table.contents.type', 'page', 'page_draft')
->where('table.contents.cid = ?', $this->request->filter('int')->get('parent')));
if (!empty($page)) {
return _t('新增 %s 的子页面', $page['title']);
}
}
throw new Exception(_t('页面不存在'), 404);
}
/**
* @return int
*/
public function getParent(): int
{
if ($this->request->is('parent')) {
$parent = $this->request->filter('int')->get('parent');
if (!$this->have() || $this->cid != $parent) {
$parentPage = $this->db->fetchRow($this->select()
->where('table.contents.type = ? OR table.contents.type = ?', 'page', 'page_draft')
->where('table.contents.cid = ?', $parent));
if (!empty($parentPage)) {
return $parent;
}
}
}
return 0;
}
/**
* @return string
*/

View File

@@ -2,8 +2,10 @@
namespace Widget\Contents\Page;
use Typecho\Db;
use Typecho\Config;
use Typecho\Db\Exception;
use Widget\Base\Contents;
use Widget\Base\TreeViewTrait;
if (!defined('__TYPECHO_ROOT_DIR__')) {
exit;
@@ -13,26 +15,52 @@ if (!defined('__TYPECHO_ROOT_DIR__')) {
* 独立页面列表组件
*
* @author qining
* @category typecho
* @page typecho
* @package Widget
* @copyright Copyright (c) 2008 Typecho team (http://www.typecho.org)
* @license GNU General Public License 2.0
*/
class Rows extends Contents
{
use TreeViewTrait;
/**
* 执行函数
*
* @access public
* @return void
* @throws Db\Exception
* @throws Exception
*/
public function execute()
{
$select = $this->select()->where('table.contents.type = ?', 'page')
$this->stack = $this->getRows($this->orders, $this->parameter->ignore);
}
/**
* @return array
* @throws Exception
*/
protected function initTreeRows(): array
{
$select = $this->select(
'table.contents.cid',
'table.contents.title',
'table.contents.slug',
'table.contents.created',
'table.contents.authorId',
'table.contents.modified',
'table.contents.type',
'table.contents.status',
'table.contents.commentsNum',
'table.contents.order',
'table.contents.parent',
'table.contents.template',
'table.contents.password',
'table.contents.allowComment',
'table.contents.allowPing',
'table.contents.allowFeed'
)->where('table.contents.type = ?', 'page')
->where('table.contents.status = ?', 'publish')
->where('table.contents.created < ?', $this->options->time)
->order('table.contents.order');
->where('table.contents.created < ?', $this->options->time);
//去掉自定义首页
$frontPage = explode(':', $this->options->frontPage);
@@ -40,6 +68,34 @@ class Rows extends Contents
$select->where('table.contents.cid <> ?', $frontPage[1]);
}
$this->db->fetchAll($select, [$this, 'push']);
return $this->db->fetchAll($select);
}
/**
* treeViewPages
*
* @param mixed $pageOptions 输出选项
*/
public function listPages($pageOptions = null)
{
//初始化一些变量
$pageOptions = Config::factory($pageOptions);
$pageOptions->setDefault([
'wrapTag' => 'ul',
'wrapClass' => '',
'itemTag' => 'li',
'itemClass' => '',
'showCount' => false,
'showFeed' => false,
'countTemplate' => '(%d)',
'feedTemplate' => '<a href="%s">RSS</a>'
]);
// 插件插件接口
self::pluginHandle()->trigger($plugged)->call('listPages', $pageOptions, $this);
if (!$plugged) {
$this->listRows($pageOptions, 'treeViewPagesCallback', intval($this->parameter->current));
}
}
}

View File

@@ -6,9 +6,8 @@ use Typecho\Cookie;
use Typecho\Db;
use Typecho\Db\Exception as DbException;
use Typecho\Widget\Exception;
use Typecho\Db\Query;
use Typecho\Widget\Helper\PageNavigator\Box;
use Widget\Base\Contents;
use Widget\Contents\AdminTrait;
if (!defined('__TYPECHO_ROOT_DIR__')) {
exit;
@@ -24,26 +23,7 @@ if (!defined('__TYPECHO_ROOT_DIR__')) {
*/
class Admin extends Contents
{
/**
* 用于计算数值的语句对象
*
* @var Query
*/
private Query $countSql;
/**
* 所有文章个数
*
* @var integer|null
*/
private ?int $total;
/**
* 当前页
*
* @var integer
*/
private int $currentPage;
use AdminTrait;
/**
* 获取菜单标题
@@ -61,28 +41,6 @@ class Admin extends Contents
throw new Exception(_t('用户不存在'), 404);
}
/**
* 重载过滤函数
*
* @param array $value
* @return array
* @throws DbException
*/
public function filter(array $value): array
{
$value = parent::filter($value);
if (!empty($value['parent'])) {
$parent = $this->db->fetchObject($this->select()->where('cid = ?', $value['parent']));
if (!empty($parent)) {
$value['commentsNum'] = $parent->commentsNum;
}
}
return $value;
}
/**
* 执行函数
*
@@ -90,8 +48,7 @@ class Admin extends Contents
*/
public function execute()
{
$this->parameter->setDefault('pageSize=20');
$this->currentPage = $this->request->get('page', 1);
$this->initPage();
/** 构建基础查询 */
$select = $this->select();
@@ -108,8 +65,10 @@ class Admin extends Contents
}
if ('on' != Cookie::get('__typecho_all_posts')) {
$select->where('table.contents.authorId = ?',
$this->request->filter('int')->get('uid', $this->user->uid));
$select->where(
'table.contents.authorId = ?',
$this->request->filter('int')->get('uid', $this->user->uid)
);
}
}
}
@@ -126,10 +85,9 @@ class Admin extends Contents
);
} else {
$select->where(
'table.contents.type = ? OR (table.contents.type = ? AND table.contents.parent = ?)',
'table.contents.type = ? OR table.contents.type = ?',
'post',
'post_draft',
0
'post_draft'
);
}
@@ -139,21 +97,8 @@ class Admin extends Contents
->where('table.relationships.mid = ?', $category);
}
/** 过滤标题 */
if (null != ($keywords = $this->request->filter('search')->get('keywords'))) {
$args = [];
$keywordsList = explode(' ', $keywords);
$args[] = implode(' OR ', array_fill(0, count($keywordsList), 'table.contents.title LIKE ?'));
foreach ($keywordsList as $keyword) {
$args[] = '%' . $keyword . '%';
}
call_user_func_array([$select, 'where'], $args);
}
/** 给计算数目对象赋值,克隆对象 */
$this->countSql = clone $select;
$this->searchQuery($select);
$this->countTotal($select);
/** 提交查询 */
$select->order('table.contents.cid', Db::SORT_DESC)
@@ -161,55 +106,4 @@ class Admin extends Contents
$this->db->fetchAll($select, [$this, 'push']);
}
/**
* 输出分页
*
* @throws Exception
* @throws DbException
*/
public function pageNav()
{
$query = $this->request->makeUriByRequest('page={page}');
/** 使用盒状分页 */
$nav = new Box(
!isset($this->total) ? $this->total = $this->size($this->countSql) : $this->total,
$this->currentPage,
$this->parameter->pageSize,
$query
);
$nav->render('&laquo;', '&raquo;');
}
/**
* 当前文章的草稿
*
* @return bool
* @throws DbException
*/
protected function ___hasSaved(): bool
{
if (in_array($this->type, ['post_draft', 'page_draft'])) {
return true;
}
$savedPost = $this->db->fetchRow($this->db->select('cid', 'modified', 'status')
->from('table.contents')
->where(
'table.contents.parent = ? AND (table.contents.type = ? OR table.contents.type = ?)',
$this->cid,
'post_draft',
'page_draft'
)
->limit(1));
if ($savedPost) {
$this->modified = $savedPost['modified'];
return true;
}
return false;
}
}

View File

@@ -5,7 +5,6 @@ namespace Widget\Contents\Post;
use Typecho\Config;
use Typecho\Db;
use Typecho\Router;
use Typecho\Widget;
use Widget\Base;
if (!defined('__TYPECHO_ROOT_DIR__')) {

View File

@@ -20,6 +20,8 @@ if (!defined('__TYPECHO_ROOT_DIR__')) {
/**
* 编辑文章组件
*
* @property-read array $draft
*/
class Edit extends Contents implements ActionInterface
{
@@ -198,7 +200,7 @@ class Edit extends Contents implements ActionInterface
// 处理草稿
$draft = $this->db->fetchRow($this->db->select('cid')
->from('table.contents')
->where('table.contents.parent = ? AND table.contents.type = ?', $post, 'post_draft')
->where('table.contents.parent = ? AND table.contents.type = ?', $post, 'revision')
->limit(1));
if (!empty($draft)) {
@@ -264,14 +266,14 @@ class Edit extends Contents implements ActionInterface
/** 删除草稿 */
$draft = $this->db->fetchRow($this->db->select('cid')
->from('table.contents')
->where('table.contents.parent = ? AND table.contents.type = ?', $post, 'post_draft')
->where('table.contents.parent = ? AND table.contents.type = ?', $post, 'revision')
->limit(1));
/** 删除自定义字段 */
$this->deleteFields($post);
if ($draft) {
$this->deleteDraft($draft['cid']);
$this->deleteContent($draft['cid']);
$this->deleteFields($draft['cid']);
}
@@ -313,11 +315,11 @@ class Edit extends Contents implements ActionInterface
/** 删除草稿 */
$draft = $this->db->fetchRow($this->db->select('cid')
->from('table.contents')
->where('table.contents.parent = ? AND table.contents.type = ?', $post, 'post_draft')
->where('table.contents.parent = ? AND table.contents.type = ?', $post, 'revision')
->limit(1));
if ($draft) {
$this->deleteDraft($draft['cid']);
$this->deleteContent($draft['cid']);
$this->deleteFields($draft['cid']);
$deleteCount++;
}

View File

@@ -2,11 +2,14 @@
namespace Widget\Contents;
use Typecho\Common;
use Typecho\Db\Exception as DbException;
use Typecho\Widget\Exception;
use Widget\Base\Metas;
use Widget\Metas\From as MetasFrom;
/**
* 编辑准备组件
*/
trait PrepareEditTrait
{
@@ -37,19 +40,14 @@ trait PrepareEditTrait
}
if ($hasDraft) {
if ($type . '_draft' === $this->type && $this->parent) {
$this->response->redirect(
Common::url('write-' . $type . '.php?cid=' . $this->parent, $this->options->adminUrl)
);
}
$draft = $this->type === $type . '_draft' ? $this->row : $this->db->fetchRow($this->select()
->where('table.contents.parent = ? AND table.contents.type = ?', $this->cid, $type . '_draft')
->where('table.contents.parent = ? AND table.contents.type = ?', $this->cid, 'revision')
->limit(1), [$this, 'filter']);
if (isset($draft)) {
$draft['parent'] = $this->row['parent']; // keep parent
$draft['slug'] = ltrim($draft['slug'], '@');
$draft['type'] = $type;
$draft['type'] = $this->type;
$draft['draft'] = $draft;
$draft['cid'] = $this->cid;
$draft['tags'] = $this->db->fetchAll($this->db
@@ -85,7 +83,6 @@ trait PrepareEditTrait
return _t('编辑 %s', $this->prepare()->title);
}
/**
* 获取权限
*
@@ -111,4 +108,32 @@ trait PrepareEditTrait
return $allow;
}
/**
* @return string
*/
protected function ___title(): string
{
return $this->have() ? $this->row['title'] : '';
}
/**
* @return string
*/
protected function ___text(): string
{
return $this->have() ? ($this->isMarkdown ? substr($this->row['text'], 15) : $this->row['text']) : '';
}
/**
* @return Metas
*/
protected function ___categories(): Metas
{
return $this->have() ? parent::___categories()
: MetasFrom::allocWithAlias(
'category:' . $this->options->defaultCategory,
['mid' => $this->options->defaultCategory]
);
}
}

View File

@@ -3,7 +3,6 @@
namespace Widget\Contents;
use Typecho\Db;
use Typecho\Db\Query;
use Typecho\Db\Exception;
use Widget\Base\Contents;
@@ -33,7 +32,24 @@ class Related extends Contents
if ($this->parameter->tags) {
$tagsGroup = implode(',', array_column($this->parameter->tags, 'mid'));
$this->db->fetchAll($this->select()
$this->db->fetchAll($this->select(
'DISTINCT table.contents.cid',
'table.contents.title',
'table.contents.slug',
'table.contents.created',
'table.contents.authorId',
'table.contents.modified',
'table.contents.type',
'table.contents.status',
'table.contents.text',
'table.contents.commentsNum',
'table.contents.order',
'table.contents.template',
'table.contents.password',
'table.contents.allowComment',
'table.contents.allowPing',
'table.contents.allowFeed'
)
->join('table.relationships', 'table.contents.cid = table.relationships.cid')
->where('table.relationships.mid IN (' . $tagsGroup . ')')
->where('table.contents.cid <> ?', $this->parameter->cid)
@@ -45,33 +61,4 @@ class Related extends Contents
->limit($this->parameter->limit), [$this, 'push']);
}
}
/**
* 获取查询对象
*
* @return Query
* @throws Exception
*/
public function select(): Query
{
return $this->db->select(
'DISTINCT table.contents.cid',
'table.contents.title',
'table.contents.slug',
'table.contents.created',
'table.contents.authorId',
'table.contents.modified',
'table.contents.type',
'table.contents.status',
'table.contents.text',
'table.contents.commentsNum',
'table.contents.order',
'table.contents.template',
'table.contents.password',
'table.contents.allowComment',
'table.contents.allowPing',
'table.contents.allowFeed'
)
->from('table.contents');
}
}

View File

@@ -91,18 +91,18 @@ class Feed extends Contents
switch ($feedType) {
case FeedGenerator::RSS1:
$currentFeedUrl = $archive->getFeedRssUrl();
$currentFeedUrl = $archive->getArchiveFeedRssUrl();
break;
case FeedGenerator::ATOM1:
$currentFeedUrl = $archive->getFeedAtomUrl();
$currentFeedUrl = $archive->getArchiveFeedAtomUrl();
break;
default:
$currentFeedUrl = $archive->getFeedUrl();
$currentFeedUrl = $archive->getArchiveFeedUrl();
break;
}
$feed->setBaseUrl($archive->getArchiveUrl());
$feed->setSubTitle($archive->getDescription());
$feed->setSubTitle($archive->getArchiveDescription());
}
$this->checkPermalink($currentFeedUrl);
@@ -181,8 +181,9 @@ class Feed extends Contents
'link' => $archive->permalink,
'author' => $archive->author,
'excerpt' => $archive->plainExcerpt,
'category' => $archive->categories->toArray(['name', 'permalink']),
'comments' => $archive->commentsNum,
'commentsFeedUrl' => Common::url($archive->pathinfo, $feed->getFeedUrl()),
'commentsFeedUrl' => Common::url($archive->path, $feed->getFeedUrl()),
'suffix' => $suffix
]);
}

View File

@@ -10,6 +10,7 @@ use Widget\Contents\Attachment\Edit as AttachmentEdit;
use Widget\Contents\Post\Edit as PostEdit;
use Widget\Contents\Page\Edit as PageEdit;
use Widget\Contents\Post\Admin as PostAdmin;
use Widget\Contents\Page\Admin as PageAdmin;
use Widget\Comments\Admin as CommentsAdmin;
use Widget\Metas\Category\Admin as CategoryAdmin;
use Widget\Metas\Category\Edit as CategoryEdit;
@@ -95,11 +96,13 @@ class Menu extends Base
[[PostEdit::class, 'getMenuTitle'], [PostEdit::class, 'getMenuTitle'], 'write-post.php?cid=', 'contributor', true],
[_t('创建页面'), _t('创建新页面'), 'write-page.php', 'editor'],
[[PageEdit::class, 'getMenuTitle'], [PageEdit::class, 'getMenuTitle'], 'write-page.php?cid=', 'editor', true],
[[PageEdit::class, 'getMenuTitle'], [PageEdit::class, 'getMenuTitle'], 'write-page.php?parent=', 'editor', true],
],
[
[_t('文章'), _t('管理文章'), 'manage-posts.php', 'contributor', false, 'write-post.php'],
[[PostAdmin::class, 'getMenuTitle'], [PostAdmin::class, 'getMenuTitle'], 'manage-posts.php?uid=', 'contributor', true],
[_t('独立页面'), _t('管理独立页面'), 'manage-pages.php', 'editor', false, 'write-page.php'],
[[PageAdmin::class, 'getMenuTitle'], [PageAdmin::class, 'getMenuTitle'], 'manage-pages.php?parent=', 'editor', true, [PageAdmin::class, 'getAddLink']],
[_t('评论'), _t('管理评论'), 'manage-comments.php', 'contributor'],
[[CommentsAdmin::class, 'getMenuTitle'], [CommentsAdmin::class, 'getMenuTitle'], 'manage-comments.php?cid=', 'contributor', true],
[_t('分类'), _t('管理分类'), 'manage-categories.php', 'editor', false, 'category.php'],

View File

@@ -5,6 +5,8 @@ namespace Widget\Metas\Category;
use Typecho\Common;
use Typecho\Db;
use Typecho\Widget\Exception;
use Widget\Base\Metas;
use Widget\Base\TreeTrait;
if (!defined('__TYPECHO_ROOT_DIR__')) {
exit;
@@ -13,22 +15,23 @@ if (!defined('__TYPECHO_ROOT_DIR__')) {
/**
* Category Admin
*/
class Admin extends Rows
class Admin extends Metas
{
use InitTreeRowsTrait;
use TreeTrait;
/**
* @var int Parent category
*/
private int $parentId = 0;
/**
* 执行函数
*
* @throws Db\Exception
*/
public function execute()
{
$select = $this->db->select('mid')->from('table.metas')->where('type = ?', 'category');
$select->where('parent = ?', $this->request->get('parent', 0));
$this->stack = $this->getCategories(array_column(
$this->db->fetchAll($select->order('table.metas.order')),
'mid'
));
$this->parentId = $this->request->filter('int')->get('parent', 0);
$this->stack = $this->getRows($this->getChildIds($this->parentId));
}
/**
@@ -38,13 +41,11 @@ class Admin extends Rows
*/
public function backLink()
{
if ($this->request->is('parent')) {
$category = $this->db->fetchRow($this->select()
->where('type = ? AND mid = ?', 'category', $this->request->get('parent')));
if ($this->parentId) {
$category = $this->getRow($this->parentId);
if (!empty($category)) {
$parent = $this->db->fetchRow($this->select()
->where('type = ? AND mid = ?', 'category', $category['parent']));
$parent = $this->getRow($category['parent']);
if ($parent) {
echo '<a href="'
@@ -69,9 +70,8 @@ class Admin extends Rows
*/
public function getMenuTitle(): ?string
{
if ($this->request->is('parent')) {
$category = $this->db->fetchRow($this->select()
->where('type = ? AND mid = ?', 'category', $this->request->get('parent')));
if ($this->parentId) {
$category = $this->getRow($this->parentId);
if (!empty($category)) {
return _t('管理 %s 的子分类', $category['name']);
@@ -90,10 +90,6 @@ class Admin extends Rows
*/
public function getAddLink(): string
{
if ($this->request->is('parent')) {
return 'category.php?parent=' . $this->request->filter('int')->get('parent');
} else {
return 'category.php';
}
return 'category.php' . ($this->parentId ? '?parent=' . $this->parentId : '');
}
}

View File

@@ -8,6 +8,7 @@ use Typecho\Validate;
use Typecho\Widget\Helper\Form;
use Widget\Base\Metas;
use Widget\ActionInterface;
use Widget\Metas\EditTrait;
use Widget\Notice;
if (!defined('__TYPECHO_ROOT_DIR__')) {
@@ -24,8 +25,11 @@ if (!defined('__TYPECHO_ROOT_DIR__')) {
*/
class Edit extends Metas implements ActionInterface
{
use EditTrait;
/**
* 入口函数
* @throws \Exception
*/
public function execute()
{
@@ -78,6 +82,7 @@ class Edit extends Metas implements ActionInterface
*
* @param string $name 分类名
* @return boolean
* @throws Exception
*/
public function nameToSlug(string $name): bool
{
@@ -345,6 +350,7 @@ class Edit extends Metas implements ActionInterface
/**
* 合并分类
* @throws Exception
*/
public function mergeCategory()
{
@@ -376,6 +382,7 @@ class Edit extends Metas implements ActionInterface
/**
* 分类排序
* @throws Exception
*/
public function sortCategory()
{
@@ -458,23 +465,22 @@ class Edit extends Metas implements ActionInterface
*/
public function getMenuTitle(): ?string
{
if (isset($this->request->mid)) {
if ($this->request->is('mid')) {
$category = $this->db->fetchRow($this->select()
->where('type = ? AND mid = ?', 'category', $this->request->mid));
->where('type = ? AND mid = ?', 'category', $this->request->filter('int')->get('mid')));
if (!empty($category)) {
return _t('编辑分类 %s', $category['name']);
}
}
if (isset($this->request->parent)) {
if ($this->request->is('parent')) {
$category = $this->db->fetchRow($this->select()
->where('type = ? AND mid = ?', 'category', $this->request->parent));
->where('type = ? AND mid = ?', 'category', $this->request->filter('int')->get('parent')));
if (!empty($category)) {
return _t('新增 %s 的子分类', $category['name']);
}
} else {
return null;
}
@@ -487,6 +493,7 @@ class Edit extends Metas implements ActionInterface
*
* @access public
* @return void
* @throws Exception
*/
public function action()
{

View File

@@ -0,0 +1,21 @@
<?php
namespace Widget\Metas\Category;
use Typecho\Db\Exception;
/**
* Trait InitTreeRowsTrait
*/
trait InitTreeRowsTrait
{
/**
* @return array
* @throws Exception
*/
protected function initTreeRows(): array
{
return $this->db->fetchAll($this->select()
->where('type = ?', 'category'));
}
}

View File

@@ -0,0 +1,38 @@
<?php
namespace Widget\Metas\Category;
use Typecho\Db\Exception;
use Widget\Base\Metas;
use Widget\Base\TreeTrait;
if (!defined('__TYPECHO_ROOT_DIR__')) {
exit;
}
class Related extends Metas
{
use InitTreeRowsTrait;
use TreeTrait;
/**
* @return void
* @throws Exception
*/
public function execute()
{
$ids = array_column($this->db->fetchAll($this->select('table.metas.mid')
->join('table.relationships', 'table.relationships.mid = table.metas.mid')
->where('table.relationships.cid = ?', $this->parameter->cid)
->where('table.metas.type = ?', 'category')), 'mid');
usort($ids, function ($a, $b) {
$orderA = array_search($a, $this->orders);
$orderB = array_search($b, $this->orders);
return $orderA <=> $orderB;
});
$this->stack = $this->getRows($ids);
}
}

View File

@@ -3,8 +3,8 @@
namespace Widget\Metas\Category;
use Typecho\Config;
use Typecho\Db;
use Widget\Base\Metas;
use Widget\Base\TreeViewTrait;
if (!defined('__TYPECHO_ROOT_DIR__')) {
exit;
@@ -22,127 +22,8 @@ if (!defined('__TYPECHO_ROOT_DIR__')) {
*/
class Rows extends Metas
{
/**
* 树状分类结构
*
* @var array
* @access private
*/
private array $treeViewCategories = [];
/**
* _categoryOptions
*
* @var Config|null
* @access private
*/
private ?Config $categoryOptions = null;
/**
* 顶层分类
*
* @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 Config $parameter
*/
protected function initParameter(Config $parameter)
{
$parameter->setDefault('ignore=0&current=');
$select = $this->select()->where('type = ?', 'category');
$categories = $this->db->fetchAll($select->order('table.metas.order', Db::SORT_ASC));
foreach ($categories as $category) {
$category['levels'] = 0;
$this->map[$category['mid']] = $category;
}
// 读取数据
foreach ($this->map as $mid => $category) {
$parent = $category['parent'];
if (0 != $parent && isset($this->map[$parent])) {
$this->treeViewCategories[$parent][] = $mid;
} else {
$this->top[] = $mid;
}
}
// 预处理深度
$this->levelWalkCallback($this->top);
$this->map = array_map([$this, 'filter'], $this->map);
}
/**
* 预处理分类迭代
*
* @param array $categories
* @param array $parents
*/
private function levelWalkCallback(array $categories, array $parents = [])
{
foreach ($parents as $parent) {
if (!isset($this->childNodes[$parent])) {
$this->childNodes[$parent] = [];
}
$this->childNodes[$parent] = array_merge($this->childNodes[$parent], $categories);
}
foreach ($categories as $mid) {
$this->orders[] = $mid;
$parent = $this->map[$mid]['parent'];
if (0 != $parent && isset($this->map[$parent])) {
$levels = $this->map[$parent]['levels'] + 1;
$this->map[$mid]['levels'] = $levels;
}
$this->parents[$mid] = $parents;
if (!empty($this->treeViewCategories[$mid])) {
$new = $parents;
$new[] = $mid;
$this->levelWalkCallback($this->treeViewCategories[$mid], $new);
}
}
}
use InitTreeRowsTrait;
use TreeViewTrait;
/**
* 执行函数
@@ -151,7 +32,7 @@ class Rows extends Metas
*/
public function execute()
{
$this->stack = $this->getCategories($this->orders);
$this->stack = $this->getRows($this->orders, $this->parameter->ignore);
}
/**
@@ -162,8 +43,8 @@ class Rows extends Metas
public function listCategories($categoryOptions = null)
{
//初始化一些变量
$this->categoryOptions = Config::factory($categoryOptions);
$this->categoryOptions->setDefault([
$categoryOptions = Config::factory($categoryOptions);
$categoryOptions->setDefault([
'wrapTag' => 'ul',
'wrapClass' => '',
'itemTag' => 'li',
@@ -175,258 +56,15 @@ class Rows extends Metas
]);
// 插件插件接口
self::pluginHandle()->trigger($plugged)->call('listCategories', $this->categoryOptions, $this);
self::pluginHandle()->trigger($plugged)->call('listCategories', $categoryOptions, $this);
if (!$plugged) {
$this->stack = $this->getCategories($this->top);
if ($this->have()) {
echo '<' . $this->categoryOptions->wrapTag . (empty($this->categoryOptions->wrapClass)
? '' : ' class="' . $this->categoryOptions->wrapClass . '"') . '>';
while ($this->next()) {
$this->treeViewCategoriesCallback();
}
echo '</' . $this->categoryOptions->wrapTag . '>';
}
$this->stack = $this->map;
$this->listRows(
$categoryOptions,
'category',
'treeViewCategoriesCallback',
intval($this->parameter->current)
);
}
}
/**
* 列出分类回调
*/
private function treeViewCategoriesCallback(): void
{
$categoryOptions = $this->categoryOptions;
if (function_exists('treeViewCategories')) {
treeViewCategories($this, $categoryOptions);
return;
}
$classes = [];
if ($categoryOptions->itemClass) {
$classes[] = $categoryOptions->itemClass;
}
$classes[] = 'category-level-' . $this->levels;
echo '<' . $categoryOptions->itemTag . ' class="'
. implode(' ', $classes);
if ($this->levels > 0) {
echo ' category-child';
$this->levelsAlt(' category-level-odd', ' category-level-even');
} else {
echo ' category-parent';
}
if ($this->mid == $this->parameter->current) {
echo ' category-active';
} elseif (
isset($this->childNodes[$this->mid]) && in_array($this->parameter->current, $this->childNodes[$this->mid])
) {
echo ' category-parent-active';
}
echo '"><a href="' . $this->permalink . '">' . $this->name . '</a>';
if ($categoryOptions->showCount) {
printf($categoryOptions->countTemplate, intval($this->count));
}
if ($categoryOptions->showFeed) {
printf($categoryOptions->feedTemplate, $this->feedUrl);
}
if ($this->children) {
$this->treeViewCategories();
}
echo '</' . $categoryOptions->itemTag . '>';
}
/**
* 根据深度余数输出
*
* @param ...$args
*/
public function levelsAlt(...$args)
{
$num = count($args);
$split = $this->levels % $num;
echo $args[(0 == $split ? $num : $split) - 1];
}
/**
* treeViewCategories
*
* @access public
* @return void
*/
public function treeViewCategories()
{
$children = $this->children;
if ($children) {
//缓存变量便于还原
$tmp = $this->row;
$this->sequence++;
//在子评论之前输出
echo '<' . $this->categoryOptions->wrapTag . (empty($this->categoryOptions->wrapClass)
? '' : ' class="' . $this->categoryOptions->wrapClass . '"') . '>';
foreach ($children as $child) {
$this->row = $child;
$this->treeViewCategoriesCallback();
$this->row = $tmp;
}
//在子评论之后输出
echo '</' . $this->categoryOptions->wrapTag . '>';
$this->sequence--;
}
}
/**
* 将每行的值压入堆栈
*
* @access public
* @param array $value 每行的值
* @return array
*/
public function filter(array $value): array
{
$value['directory'] = $this->getAllParentsSlug($value['mid']);
$value['directory'][] = $value['slug'];
$tmpCategoryTree = $value['directory'];
$value['directory'] = implode('/', array_map('urlencode', $value['directory']));
$value = parent::filter($value);
$value['directory'] = $tmpCategoryTree;
return $value;
}
/**
* 获取某个分类所有父级节点缩略名
*
* @param mixed $mid
* @access public
* @return array
*/
public function getAllParentsSlug($mid): array
{
$parents = [];
if (isset($this->parents[$mid])) {
foreach ($this->parents[$mid] as $parent) {
$parents[] = $this->map[$parent]['slug'];
}
}
return $parents;
}
/**
* 获取某个分类下的所有子节点
*
* @param mixed $mid
* @access public
* @return array
*/
public function getAllChildren($mid): array
{
return $this->childNodes[$mid] ?? [];
}
/**
* 获取某个分类所有父级节点
*
* @param mixed $mid
* @access public
* @return array
*/
public function getAllParents($mid): array
{
$parents = [];
if (isset($this->parents[$mid])) {
foreach ($this->parents[$mid] as $parent) {
$parents[] = $this->map[$parent];
}
}
return $parents;
}
/**
* 获取单个分类
*
* @param integer $mid
* @return mixed
*/
public function getCategory(int $mid)
{
return $this->map[$mid] ?? null;
}
/**
* 子评论
*
* @return array
*/
protected function ___children(): array
{
return isset($this->treeViewCategories[$this->mid]) ?
$this->getCategories($this->treeViewCategories[$this->mid]) : [];
}
/**
* 获取多个分类
*
* @param mixed $mids
* @return array
*/
public function getCategories($mids): array
{
$result = [];
if (!empty($mids)) {
foreach ($mids as $mid) {
if (
!$this->parameter->ignore
|| ($this->parameter->ignore != $mid
&& !$this->hasParent($mid, $this->parameter->ignore))
) {
$result[] = $this->map[$mid];
}
}
}
return $result;
}
/**
* 是否拥有某个父级分类
*
* @param mixed $mid
* @param mixed $parentId
* @return bool
*/
public function hasParent($mid, $parentId): bool
{
if (isset($this->parents[$mid])) {
foreach ($this->parents[$mid] as $parent) {
if ($parent == $parentId) {
return true;
}
}
}
return false;
}
}

View File

@@ -0,0 +1,104 @@
<?php
namespace Widget\Metas;
use Typecho\Db\Exception;
trait EditTrait
{
/**
* 获取最大排序
*
* @param string $type
* @param int $parent
* @return integer
* @throws Exception
*/
public function getMaxOrder(string $type, int $parent = 0): int
{
return $this->db->fetchObject($this->select(['MAX(order)' => 'maxOrder'])
->where('type = ? AND parent = ?', $type, $parent))->maxOrder ?? 0;
}
/**
* 对数据按照sort字段排序
*
* @param array $metas
* @param string $type
* @throws Exception
*/
public function sort(array $metas, string $type)
{
foreach ($metas as $sort => $mid) {
$this->update(
['order' => $sort + 1],
$this->db->sql()->where('mid = ?', $mid)->where('type = ?', $type)
);
}
}
/**
* 合并数据
*
* @param integer $mid 数据主键
* @param string $type 数据类型
* @param array $metas 需要合并的数据集
* @throws Exception
*/
public function merge(int $mid, string $type, array $metas)
{
$contents = array_column($this->db->fetchAll($this->db->select('cid')
->from('table.relationships')
->where('mid = ?', $mid)), 'cid');
foreach ($metas as $meta) {
if ($mid != $meta) {
$existsContents = array_column($this->db->fetchAll($this->db
->select('cid')->from('table.relationships')
->where('mid = ?', $meta)), 'cid');
$where = $this->db->sql()->where('mid = ? AND type = ?', $meta, $type);
$this->delete($where);
$diffContents = array_diff($existsContents, $contents);
$this->db->query($this->db->delete('table.relationships')->where('mid = ?', $meta));
foreach ($diffContents as $content) {
$this->db->query($this->db->insert('table.relationships')
->rows(['mid' => $mid, 'cid' => $content]));
$contents[] = $content;
}
$this->update(['parent' => $mid], $this->db->sql()->where('parent = ?', $meta));
unset($existsContents);
}
}
$num = $this->db->fetchObject($this->db
->select(['COUNT(mid)' => 'num'])->from('table.relationships')
->where('table.relationships.mid = ?', $mid))->num;
$this->update(['count' => $num], $this->db->sql()->where('mid = ?', $mid));
}
/**
* 根据内容的指定类别和状态更新相关meta的计数信息
*
* @param int $mid meta id
* @param string $type 类别
* @param string $status 状态
* @throws Exception
*/
protected function refreshCountByTypeAndStatus(int $mid, string $type, string $status = 'publish')
{
$num = $this->db->fetchObject($this->db->select(['COUNT(table.contents.cid)' => 'num'])->from('table.contents')
->join('table.relationships', 'table.contents.cid = table.relationships.cid')
->where('table.relationships.mid = ?', $mid)
->where('table.contents.type = ?', $type)
->where('table.contents.status = ?', $status))->num;
$this->db->query($this->db->update('table.metas')->rows(['count' => $num])
->where('mid = ?', $mid));
}
}

39
var/Widget/Metas/From.php Normal file
View File

@@ -0,0 +1,39 @@
<?php
namespace Widget\Metas;
use Typecho\Db\Exception;
use Widget\Base\Metas;
use Widget\Base\TreeTrait;
use Widget\Metas\Category\InitTreeRowsTrait;
class From extends Metas
{
use InitTreeRowsTrait;
use TreeTrait {
initParameter as initTreeParameter;
}
/**
* @return void
* @throws Exception
*/
public function execute()
{
$query = null;
if (isset($this->parameter->mid)) {
$query = $this->select()->where('mid = ?', $this->parameter->mid);
} elseif (isset($this->parameter->query)) {
$query = $this->parameter->query;
}
if ($query) {
$this->db->fetchAll($query, [$this, 'push']);
if ($this->type == 'category') {
$this->initTreeParameter($this->parameter);
}
}
}
}

View File

@@ -7,6 +7,7 @@ use Typecho\Db\Exception;
use Typecho\Widget\Helper\Form;
use Widget\Base\Metas;
use Widget\ActionInterface;
use Widget\Metas\EditTrait;
use Widget\Notice;
if (!defined('__TYPECHO_ROOT_DIR__')) {
@@ -24,6 +25,8 @@ if (!defined('__TYPECHO_ROOT_DIR__')) {
*/
class Edit extends Metas implements ActionInterface
{
use EditTrait;
/**
* 入口函数
*/
@@ -78,6 +81,7 @@ class Edit extends Metas implements ActionInterface
*
* @param string $name 标签名
* @return boolean
* @throws Exception
*/
public function nameToSlug(string $name): bool
{
@@ -358,6 +362,31 @@ class Edit extends Metas implements ActionInterface
$this->response->goBack();
}
/**
* 清理没有任何内容的标签
*
* @throws Exception
*/
public function clearTags()
{
// 取出count为0的标签
$tags = array_column($this->db->fetchAll($this->select('mid')
->where('type = ? AND count = ?', 'tags', 0)), 'mid');
foreach ($tags as $tag) {
// 确认是否已经没有关联了
$content = $this->db->fetchRow($this->db->select('cid')
->from('table.relationships')->where('mid = ?', $tag)
->limit(1));
if (empty($content)) {
$this->db->query($this->db->delete('table.metas')
->where('mid = ?', $tag));
}
}
}
/**
* 入口函数,绑定事件
*
@@ -375,4 +404,46 @@ class Edit extends Metas implements ActionInterface
$this->on($this->request->is('do=refresh'))->refreshTag();
$this->response->redirect($this->options->adminUrl);
}
/**
* 根据tag获取ID
*
* @param mixed $inputTags 标签名
* @return array|int
* @throws Exception
*/
private 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);
}
}

View File

@@ -0,0 +1,26 @@
<?php
namespace Widget\Metas\Tag;
use Widget\Base\Metas;
if (!defined('__TYPECHO_ROOT_DIR__')) {
exit;
}
/**
* 相关信息输出组件
*/
class Related extends Metas
{
/**
* @return void
*/
public function execute()
{
$this->db->fetchAll($this->select()
->join('table.relationships', 'table.relationships.mid = table.metas.mid')
->where('table.relationships.cid = ?', $this->parameter->cid)
->where('table.metas.type = ?', 'tag'), [$this, 'push']);
}
}

View File

@@ -35,7 +35,9 @@ class Permalink extends Options implements ActionInterface
*/
public function checkPagePattern($value): bool
{
return strpos($value, '{slug}') !== false || strpos($value, '{cid}') !== false;
return strpos($value, '{slug}') !== false
|| strpos($value, '{cid}') !== false
|| strpos($value, '{directory}') !== false;
}
/**
@@ -278,7 +280,7 @@ RewriteRule . {$basePath}index.php [L]
null,
$this->decodeRule($this->options->routingTable['page']['url']),
_t('独立页面路径'),
_t('可用参数: <code>{cid}</code> 页面 ID, <code>{slug}</code> 页面缩略名')
_t('可用参数: <code>{cid}</code> 页面 ID, <code>{slug}</code> 页面缩略名, <code>{directory}</code> 多级页面')
. '<br />' . _t('请在路径中至少包含上述的一项参数.')
);
$pagePattern->input->setAttribute('class', 'mono w-60');

View File

@@ -3,6 +3,7 @@
namespace Widget;
use Typecho\Common;
use Typecho\Config;
use Typecho\Date;
use Typecho\Db\Exception;
use Typecho\Plugin;
@@ -43,19 +44,19 @@ class Upload extends Contents implements ActionInterface
/**
* 获取实际文件绝对访问路径
*
* @param array $content 文件相关信息
* @param Config $attachment 文件相关信息
* @return string
*/
public static function attachmentHandle(array $content): string
public static function attachmentHandle(Config $attachment): string
{
$result = Plugin::factory(Upload::class)->trigger($hasPlugged)->call('attachmentHandle', $content);
$result = Plugin::factory(Upload::class)->trigger($hasPlugged)->call('attachmentHandle', $attachment);
if ($hasPlugged) {
return $result;
}
$options = Options::alloc();
return Common::url(
$content['attachment']->path,
$attachment->path,
defined('__TYPECHO_UPLOAD_URL__') ? __TYPECHO_UPLOAD_URL__ : $options->siteUrl
);
}

View File

@@ -27,7 +27,7 @@ class Author extends Users
*/
public function execute()
{
if ($this->parameter->uid) {
if (isset($this->parameter->uid)) {
$this->db->fetchRow($this->select()
->where('uid = ?', $this->parameter->uid), [$this, 'push']);
}

View File

@@ -14,7 +14,6 @@ use Typecho\Widget;
use Typecho\Widget\Exception as WidgetException;
use Widget\Base\Comments;
use Widget\Base\Contents;
use Widget\Base\Metas;
use Widget\Contents\Page\Admin as PageAdmin;
use Widget\Contents\Post\Admin as PostAdmin;
use Widget\Contents\Attachment\Admin as AttachmentAdmin;
@@ -23,6 +22,7 @@ use Widget\Contents\Page\Edit as PageEdit;
use Widget\Contents\Attachment\Edit as AttachmentEdit;
use Widget\Metas\Category\Edit as CategoryEdit;
use Widget\Metas\Category\Rows as CategoryRows;
use Widget\Metas\From as MetasFrom;
use Widget\Metas\Tag\Cloud;
use Widget\Comments\Edit as CommentsEdit;
use Widget\Comments\Admin as CommentsAdmin;
@@ -635,26 +635,28 @@ class XmlRpc extends Contents implements ActionInterface, Hook
/** 构造出查询语句并且查询*/
$key = Common::filterSearchQuery($category);
$key = '%' . $key . '%';
$select = Metas::alloc()->select()->where(
'table.metas.type = ? AND (table.metas.name LIKE ? OR slug LIKE ?)',
'category',
$key,
$key
);
$select = $this->db->select()
->from('table.metas')
->where(
'table.metas.type = ? AND (table.metas.name LIKE ? OR slug LIKE ?)',
'category',
$key,
$key
);
if ($maxResults > 0) {
$select->limit($maxResults);
}
/** 不要category push到contents的容器中 */
$categories = $this->db->fetchAll($select);
$categories = MetasFrom::alloc(['query' => $select]);
/** 初始化categorise数组*/
$categoryStructs = [];
foreach ($categories as $category) {
while ($categories->next()) {
$categoryStructs[] = [
'category_id' => $category['mid'],
'category_name' => $category['name'],
'category_id' => $categories->mid,
'category_name' => $categories->name,
];
}