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:
@@ -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('引用'); ?>
|
||||
|
||||
@@ -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('« 取消筛选'); ?></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 () {
|
||||
|
||||
@@ -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: ?>
|
||||
|
||||
@@ -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(' ', $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
|
||||
|
||||
@@ -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(' ', $category->levels); ?><input
|
||||
type="checkbox" id="category-<?php $category->mid(); ?>"
|
||||
|
||||
@@ -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
|
||||
{
|
||||
/**
|
||||
* 当前配置
|
||||
|
||||
@@ -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) {
|
||||
//根据查询动作返回相应资源
|
||||
|
||||
@@ -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);
|
||||
|
||||
8
var/Typecho/Router/ParamsDelegateInterface.php
Normal file
8
var/Typecho/Router/ParamsDelegateInterface.php
Normal file
@@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace Typecho\Router;
|
||||
|
||||
interface ParamsDelegateInterface
|
||||
{
|
||||
public function getRouterParam(string $key): string;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
13
var/Widget/Base/PrimaryKeyInterface.php
Normal file
13
var/Widget/Base/PrimaryKeyInterface.php
Normal file
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace Widget\Base;
|
||||
|
||||
interface PrimaryKeyInterface
|
||||
{
|
||||
/**
|
||||
* 获取主键
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getPrimaryKey(): string;
|
||||
}
|
||||
@@ -12,9 +12,10 @@ interface QueryInterface
|
||||
/**
|
||||
* 查询方法
|
||||
*
|
||||
* @param mixed $fields 字段
|
||||
* @return Query
|
||||
*/
|
||||
public function select(): Query;
|
||||
public function select(...$fields): Query;
|
||||
|
||||
/**
|
||||
* 获得所有记录数
|
||||
|
||||
17
var/Widget/Base/RowFilterInterface.php
Normal file
17
var/Widget/Base/RowFilterInterface.php
Normal file
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace Widget\Base;
|
||||
|
||||
/**
|
||||
* 行过滤器接口
|
||||
*/
|
||||
interface RowFilterInterface
|
||||
{
|
||||
/**
|
||||
* 过滤行
|
||||
*
|
||||
* @param array $row
|
||||
* @return array
|
||||
*/
|
||||
public function filter(array $row): array;
|
||||
}
|
||||
279
var/Widget/Base/TreeTrait.php
Normal file
279
var/Widget/Base/TreeTrait.php
Normal 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¤t=');
|
||||
|
||||
$rows = $this->initTreeRows();
|
||||
$pk = $this->getPrimaryKey();
|
||||
|
||||
// Sort by order asc
|
||||
usort($rows, function ($a, $b) {
|
||||
return $a['order'] <=> $b['order'];
|
||||
});
|
||||
|
||||
foreach ($rows as $row) {
|
||||
$row['levels'] = 0;
|
||||
$this->map[$row[$pk]] = $row;
|
||||
}
|
||||
|
||||
// 读取数据
|
||||
foreach ($this->map as $id => $row) {
|
||||
$parent = $row['parent'];
|
||||
|
||||
if (0 != $parent && isset($this->map[$parent])) {
|
||||
$this->treeRows[$parent][] = $id;
|
||||
} else {
|
||||
$this->top[] = $id;
|
||||
}
|
||||
}
|
||||
|
||||
// 预处理深度
|
||||
$this->levelWalkCallback($this->top);
|
||||
$this->map = array_map([$this, 'filter'], $this->map);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
protected function ___directory(): array
|
||||
{
|
||||
$directory = $this->getAllParentsSlug($this->{$this->getPrimaryKey()});
|
||||
$directory[] = $this->slug;
|
||||
return $directory;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有子节点
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function ___children(): array
|
||||
{
|
||||
$id = $this->{$this->getPrimaryKey()};
|
||||
return $this->getRows($this->getChildIds($id));
|
||||
}
|
||||
|
||||
/**
|
||||
* 预处理节点迭代
|
||||
*
|
||||
* @param array $rows
|
||||
* @param array $parents
|
||||
*/
|
||||
private function levelWalkCallback(array $rows, array $parents = [])
|
||||
{
|
||||
foreach ($parents as $parent) {
|
||||
if (!isset($this->childNodes[$parent])) {
|
||||
$this->childNodes[$parent] = [];
|
||||
}
|
||||
|
||||
$this->childNodes[$parent] = array_merge($this->childNodes[$parent], $rows);
|
||||
}
|
||||
|
||||
foreach ($rows as $id) {
|
||||
$this->orders[] = $id;
|
||||
$parent = $this->map[$id]['parent'];
|
||||
|
||||
if (0 != $parent && isset($this->map[$parent])) {
|
||||
$levels = $this->map[$parent]['levels'] + 1;
|
||||
$this->map[$id]['levels'] = $levels;
|
||||
}
|
||||
|
||||
$this->parents[$id] = $parents;
|
||||
|
||||
if (!empty($this->treeRows[$id])) {
|
||||
$new = $parents;
|
||||
$new[] = $id;
|
||||
$this->levelWalkCallback($this->treeRows[$id], $new);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
126
var/Widget/Base/TreeViewTrait.php
Normal file
126
var/Widget/Base/TreeViewTrait.php
Normal 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--;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
*
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 标记评论状态
|
||||
*
|
||||
|
||||
108
var/Widget/Contents/AdminTrait.php
Normal file
108
var/Widget/Contents/AdminTrait.php
Normal 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('«', '»');
|
||||
}
|
||||
|
||||
/**
|
||||
* @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)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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('«', '»');
|
||||
}
|
||||
|
||||
/**
|
||||
* 所属文章
|
||||
*
|
||||
|
||||
@@ -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
|
||||
|
||||
79
var/Widget/Contents/From.php
Normal file
79
var/Widget/Contents/From.php
Normal 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'));
|
||||
}
|
||||
}
|
||||
@@ -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 '« ';
|
||||
_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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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('«', '»');
|
||||
}
|
||||
|
||||
/**
|
||||
* 当前文章的草稿
|
||||
*
|
||||
* @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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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__')) {
|
||||
|
||||
@@ -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++;
|
||||
}
|
||||
|
||||
@@ -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]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -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'],
|
||||
|
||||
@@ -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 : '');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
{
|
||||
|
||||
21
var/Widget/Metas/Category/InitTreeRowsTrait.php
Normal file
21
var/Widget/Metas/Category/InitTreeRowsTrait.php
Normal 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'));
|
||||
}
|
||||
}
|
||||
38
var/Widget/Metas/Category/Related.php
Normal file
38
var/Widget/Metas/Category/Related.php
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -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¤t=');
|
||||
|
||||
$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;
|
||||
}
|
||||
}
|
||||
|
||||
104
var/Widget/Metas/EditTrait.php
Normal file
104
var/Widget/Metas/EditTrait.php
Normal 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
39
var/Widget/Metas/From.php
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
26
var/Widget/Metas/Tag/Related.php
Normal file
26
var/Widget/Metas/Tag/Related.php
Normal 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']);
|
||||
}
|
||||
}
|
||||
@@ -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');
|
||||
|
||||
@@ -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
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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']);
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user