diff --git a/app/Http/Admin/Services/Article.php b/app/Http/Admin/Services/Article.php index 3ba1443e..6a9931f2 100644 --- a/app/Http/Admin/Services/Article.php +++ b/app/Http/Admin/Services/Article.php @@ -169,6 +169,10 @@ class Article extends Service $data['title'] = $validator->checkTitle($post['title']); } + if (isset($post['keywords'])) { + $data['keywords'] = $validator->checkKeywords($post['keywords']); + } + if (isset($post['content'])) { $data['content'] = $validator->checkContent($post['content']); $data['word_count'] = WordUtil::getWordCount($data['content']); diff --git a/app/Http/Admin/Services/Help.php b/app/Http/Admin/Services/Help.php index 8e1a1144..1cdbddf1 100644 --- a/app/Http/Admin/Services/Help.php +++ b/app/Http/Admin/Services/Help.php @@ -103,6 +103,10 @@ class Help extends Service $data['content'] = $validator->checkContent($post['content']); } + if (isset($post['keywords'])) { + $data['keywords'] = $validator->checkKeywords($post['keywords']); + } + if (isset($post['priority'])) { $data['priority'] = $validator->checkPriority($post['priority']); } diff --git a/app/Http/Admin/Services/Package.php b/app/Http/Admin/Services/Package.php index ec92f2ae..c49ee55f 100644 --- a/app/Http/Admin/Services/Package.php +++ b/app/Http/Admin/Services/Package.php @@ -122,14 +122,14 @@ class Package extends Service $data = []; - if (isset($post['cover'])) { - $data['cover'] = $validator->checkCover($post['cover']); - } - if (isset($post['title'])) { $data['title'] = $validator->checkTitle($post['title']); } + if (isset($post['cover'])) { + $data['cover'] = $validator->checkCover($post['cover']); + } + if (isset($post['summary'])) { $data['summary'] = $validator->checkSummary($post['summary']); } diff --git a/app/Http/Admin/Services/Page.php b/app/Http/Admin/Services/Page.php index e48767a1..022afa64 100644 --- a/app/Http/Admin/Services/Page.php +++ b/app/Http/Admin/Services/Page.php @@ -86,6 +86,10 @@ class Page extends Service $data['content'] = $validator->checkContent($post['content']); } + if (isset($post['keywords'])) { + $data['keywords'] = $validator->checkKeywords($post['keywords']); + } + if (isset($post['published'])) { $data['published'] = $validator->checkPublishStatus($post['published']); } diff --git a/app/Http/Admin/Services/Question.php b/app/Http/Admin/Services/Question.php index d882b35d..52b548b7 100644 --- a/app/Http/Admin/Services/Question.php +++ b/app/Http/Admin/Services/Question.php @@ -167,6 +167,10 @@ class Question extends Service $data['content'] = $validator->checkContent($post['content']); } + if (isset($post['keywords'])) { + $data['keywords'] = $validator->checkKeywords($post['keywords']); + } + if (isset($post['anonymous'])) { $data['anonymous'] = $validator->checkAnonymousStatus($post['anonymous']); } diff --git a/app/Http/Admin/Services/Topic.php b/app/Http/Admin/Services/Topic.php index b463a716..55c2b581 100644 --- a/app/Http/Admin/Services/Topic.php +++ b/app/Http/Admin/Services/Topic.php @@ -112,6 +112,10 @@ class Topic extends Service $data['title'] = $validator->checkTitle($post['title']); } + if (isset($post['cover'])) { + $data['cover'] = $validator->checkCover($post['cover']); + } + if (isset($post['summary'])) { $data['summary'] = $validator->checkSummary($post['summary']); } diff --git a/app/Http/Admin/Views/article/edit_basic.volt b/app/Http/Admin/Views/article/edit_basic.volt index 05659162..d76f9e88 100644 --- a/app/Http/Admin/Views/article/edit_basic.volt +++ b/app/Http/Admin/Views/article/edit_basic.volt @@ -13,6 +13,12 @@
+
+ +
+ +
+
diff --git a/app/Http/Admin/Views/help/edit.volt b/app/Http/Admin/Views/help/edit.volt index e0f4798f..a04dcc97 100644 --- a/app/Http/Admin/Views/help/edit.volt +++ b/app/Http/Admin/Views/help/edit.volt @@ -23,6 +23,12 @@
+
+ +
+ +
+
diff --git a/app/Http/Admin/Views/page/edit.volt b/app/Http/Admin/Views/page/edit.volt index 98bca03f..3e8b4ac2 100644 --- a/app/Http/Admin/Views/page/edit.volt +++ b/app/Http/Admin/Views/page/edit.volt @@ -18,6 +18,12 @@
+
+ +
+ +
+
diff --git a/app/Http/Admin/Views/question/edit_basic.volt b/app/Http/Admin/Views/question/edit_basic.volt index e94340d9..5f9338ca 100644 --- a/app/Http/Admin/Views/question/edit_basic.volt +++ b/app/Http/Admin/Views/question/edit_basic.volt @@ -11,6 +11,12 @@
+
+ +
+ +
+
diff --git a/app/Http/Admin/Views/topic/edit.volt b/app/Http/Admin/Views/topic/edit.volt index 681d128f..f60bab0e 100644 --- a/app/Http/Admin/Views/topic/edit.volt +++ b/app/Http/Admin/Views/topic/edit.volt @@ -12,6 +12,16 @@
+
+ +
+ + +
+
+ +
+
@@ -38,6 +48,7 @@ {% block include_js %} {{ js_include('lib/xm-select.js') }} + {{ js_include('admin/js/cover.upload.js') }} {% endblock %} diff --git a/app/Library/Helper.php b/app/Library/Helper.php index 77c43302..03693d09 100644 --- a/app/Library/Helper.php +++ b/app/Library/Helper.php @@ -451,6 +451,32 @@ function kg_parse_summary($content, $length = 150) return kg_substr($content, 0, $length); } +/** + * 解析关键字 + * + * @param string $content + * @return string + */ +function kg_parse_keywords($content) +{ + $search = ['|', ';', ';', '、', ',']; + + $keywords = str_replace($search, '@', $content); + + $keywords = explode('@', $keywords); + + $list = []; + + foreach ($keywords as $keyword) { + $keyword = trim($keyword); + if (kg_strlen($keyword) > 1) { + $list[] = $keyword; + } + } + + return implode(',', $list); +} + /** * 解析内容中上传首图 * diff --git a/app/Models/Article.php b/app/Models/Article.php index 9e1072df..82629ed8 100644 --- a/app/Models/Article.php +++ b/app/Models/Article.php @@ -64,6 +64,13 @@ class Article extends Model */ public $content = ''; + /** + * 关键字 + * + * @var string + */ + public $keywords = ''; + /** * 标签 * diff --git a/app/Models/Category.php b/app/Models/Category.php index 29397dd1..73dae0b2 100644 --- a/app/Models/Category.php +++ b/app/Models/Category.php @@ -150,7 +150,7 @@ class Category extends Model public function beforeSave() { if (empty($this->icon)) { - $this->icon = kg_default_icon_path(); + $this->icon = kg_default_category_icon_path(); } elseif (Text::startsWith($this->icon, 'http')) { $this->icon = self::getIconPath($this->icon); } @@ -166,7 +166,7 @@ class Category extends Model public function afterFetch() { if (!Text::startsWith($this->icon, 'http')) { - $this->icon = kg_cos_icon_url($this->icon); + $this->icon = kg_cos_category_icon_url($this->icon); } } diff --git a/app/Models/Help.php b/app/Models/Help.php index 7886e2dd..9767b5de 100644 --- a/app/Models/Help.php +++ b/app/Models/Help.php @@ -34,6 +34,13 @@ class Help extends Model */ public $title = ''; + /** + * 关键字 + * + * @var string + */ + public $keywords = ''; + /** * 内容 * diff --git a/app/Models/Page.php b/app/Models/Page.php index 4318e8f8..d7dad7c3 100644 --- a/app/Models/Page.php +++ b/app/Models/Page.php @@ -34,6 +34,13 @@ class Page extends Model */ public $alias = ''; + /** + * 关键字 + * + * @var string + */ + public $keywords = ''; + /** * 内容 * diff --git a/app/Models/Question.php b/app/Models/Question.php index 128e1bc3..66b8e05c 100644 --- a/app/Models/Question.php +++ b/app/Models/Question.php @@ -85,6 +85,13 @@ class Question extends Model */ public $tags = []; + /** + * 关键字 + * + * @var string + */ + public $keywords = ''; + /** * 概要 * diff --git a/app/Models/Tag.php b/app/Models/Tag.php index c33d8b56..a294e00c 100644 --- a/app/Models/Tag.php +++ b/app/Models/Tag.php @@ -149,7 +149,7 @@ class Tag extends Model public function beforeSave() { if (empty($this->icon)) { - $this->icon = kg_default_icon_path(); + $this->icon = kg_default_category_icon_path(); } elseif (Text::startsWith($this->icon, 'http')) { $this->icon = self::getIconPath($this->icon); } @@ -169,7 +169,7 @@ class Tag extends Model public function afterFetch() { if (!Text::startsWith($this->icon, 'http')) { - $this->icon = kg_cos_icon_url($this->icon); + $this->icon = kg_cos_category_icon_url($this->icon); } if (is_string($this->scopes) && $this->scopes != 'all') { diff --git a/app/Models/Topic.php b/app/Models/Topic.php index 8b672d1f..861f4667 100644 --- a/app/Models/Topic.php +++ b/app/Models/Topic.php @@ -9,6 +9,7 @@ namespace App\Models; use App\Caches\MaxTopicId as MaxTopicIdCache; use Phalcon\Mvc\Model\Behavior\SoftDelete; +use Phalcon\Text; class Topic extends Model { @@ -27,6 +28,13 @@ class Topic extends Model */ public $title = ''; + /** + * 封面 + * + * @var string + */ + public $cover = ''; + /** * 简介 * @@ -96,6 +104,15 @@ class Topic extends Model $this->update_time = time(); } + public function beforeSave() + { + if (empty($this->cover)) { + $this->cover = kg_default_topic_cover_path(); + } elseif (Text::startsWith($this->cover, 'http')) { + $this->cover = self::getCoverPath($this->cover); + } + } + public function afterCreate() { $cache = new MaxTopicIdCache(); @@ -103,4 +120,20 @@ class Topic extends Model $cache->rebuild(); } + public function afterFetch() + { + if (!Text::startsWith($this->cover, 'http')) { + $this->cover = kg_cos_topic_cover_url($this->cover); + } + } + + public static function getCoverPath($url) + { + if (Text::startsWith($url, 'http')) { + return parse_url($url, PHP_URL_PATH); + } + + return $url; + } + } diff --git a/app/Services/Logic/Article/ArticleInfo.php b/app/Services/Logic/Article/ArticleInfo.php index fd2101aa..9f930e3f 100644 --- a/app/Services/Logic/Article/ArticleInfo.php +++ b/app/Services/Logic/Article/ArticleInfo.php @@ -49,6 +49,7 @@ class ArticleInfo extends LogicService 'title' => $article->title, 'cover' => $article->cover, 'summary' => $article->summary, + 'keywords' => $article->keywords, 'tags' => $article->tags, 'content' => $article->content, 'private' => $article->private, diff --git a/app/Services/Logic/Help/HelpInfo.php b/app/Services/Logic/Help/HelpInfo.php index 08c06143..a018fc10 100644 --- a/app/Services/Logic/Help/HelpInfo.php +++ b/app/Services/Logic/Help/HelpInfo.php @@ -28,6 +28,7 @@ class HelpInfo extends LogicService return [ 'id' => $help->id, 'title' => $help->title, + 'keywords' => $help->keywords, 'content' => $help->content, 'published' => $help->published, 'deleted' => $help->deleted, diff --git a/app/Services/Logic/Page/PageInfo.php b/app/Services/Logic/Page/PageInfo.php index ef7939da..08278bde 100644 --- a/app/Services/Logic/Page/PageInfo.php +++ b/app/Services/Logic/Page/PageInfo.php @@ -28,6 +28,7 @@ class PageInfo extends LogicService return [ 'id' => $page->id, 'title' => $page->title, + 'keywords' => $page->keywords, 'content' => $page->content, 'published' => $page->published, 'deleted' => $page->deleted, diff --git a/app/Services/Logic/Question/QuestionInfo.php b/app/Services/Logic/Question/QuestionInfo.php index 8f908a9b..7f2907a8 100644 --- a/app/Services/Logic/Question/QuestionInfo.php +++ b/app/Services/Logic/Question/QuestionInfo.php @@ -51,6 +51,7 @@ class QuestionInfo extends LogicService 'title' => $question->title, 'summary' => $question->summary, 'content' => $question->content, + 'keywords' => $question->keywords, 'tags' => $question->tags, 'bounty' => $question->bounty, 'anonymous' => $question->anonymous, diff --git a/app/Services/Logic/Topic/TopicInfo.php b/app/Services/Logic/Topic/TopicInfo.php index bbf365cd..85b87c30 100644 --- a/app/Services/Logic/Topic/TopicInfo.php +++ b/app/Services/Logic/Topic/TopicInfo.php @@ -28,6 +28,7 @@ class TopicInfo extends LogicService return [ 'id' => $topic->id, 'title' => $topic->title, + 'topic' => $topic->cover, 'summary' => $topic->summary, 'published' => $topic->published, 'deleted' => $topic->deleted, diff --git a/app/Validators/Article.php b/app/Validators/Article.php index 1e30364b..9656b652 100644 --- a/app/Validators/Article.php +++ b/app/Validators/Article.php @@ -107,6 +107,19 @@ class Article extends Validator return kg_clean_html($value); } + public function checkKeywords($keywords) + { + $keywords = $this->filter->sanitize($keywords, ['trim', 'string']); + + $length = kg_strlen($keywords); + + if ($length > 100) { + throw new BadRequestException('article.keyword_too_long'); + } + + return kg_parse_keywords($keywords); + } + public function checkSourceType($type) { if (!array_key_exists($type, ArticleModel::sourceTypes())) { diff --git a/app/Validators/Course.php b/app/Validators/Course.php index 002b36f6..eda4d6ff 100644 --- a/app/Validators/Course.php +++ b/app/Validators/Course.php @@ -148,7 +148,7 @@ class Course extends Validator $length = kg_strlen($keywords); if ($length > 100) { - throw new BadRequestException('course.keywords_too_long'); + throw new BadRequestException('course.keyword_too_long'); } $keywords = str_replace(['|', ';', ';', '、', ','], '@', $keywords); diff --git a/app/Validators/Help.php b/app/Validators/Help.php index 1f86d966..95d567ab 100644 --- a/app/Validators/Help.php +++ b/app/Validators/Help.php @@ -103,6 +103,19 @@ class Help extends Validator return kg_clean_html($value); } + public function checkKeywords($keywords) + { + $keywords = $this->filter->sanitize($keywords, ['trim', 'string']); + + $length = kg_strlen($keywords); + + if ($length > 100) { + throw new BadRequestException('help.keyword_too_long'); + } + + return kg_parse_keywords($keywords); + } + public function checkPriority($priority) { $value = $this->filter->sanitize($priority, ['trim', 'int']); diff --git a/app/Validators/Page.php b/app/Validators/Page.php index d893aff6..fa222509 100644 --- a/app/Validators/Page.php +++ b/app/Validators/Page.php @@ -107,6 +107,19 @@ class Page extends Validator return $value; } + public function checkKeywords($keywords) + { + $keywords = $this->filter->sanitize($keywords, ['trim', 'string']); + + $length = kg_strlen($keywords); + + if ($length > 100) { + throw new BadRequestException('page.keyword_too_long'); + } + + return kg_parse_keywords($keywords); + } + public function checkContent($content) { $value = $this->filter->sanitize($content, ['trim']); diff --git a/app/Validators/Question.php b/app/Validators/Question.php index a7625ea1..1481868c 100644 --- a/app/Validators/Question.php +++ b/app/Validators/Question.php @@ -89,6 +89,19 @@ class Question extends Validator return $value; } + public function checkKeywords($keywords) + { + $keywords = $this->filter->sanitize($keywords, ['trim', 'string']); + + $length = kg_strlen($keywords); + + if ($length > 100) { + throw new BadRequestException('question.keyword_too_long'); + } + + return kg_parse_keywords($keywords); + } + public function checkContent($content) { $value = $this->filter->sanitize($content, ['trim']); diff --git a/app/Validators/Topic.php b/app/Validators/Topic.php index 24bf5e89..5a146b02 100644 --- a/app/Validators/Topic.php +++ b/app/Validators/Topic.php @@ -10,6 +10,7 @@ namespace App\Validators; use App\Caches\MaxTopicId as MaxTopicIdCache; use App\Caches\Topic as TopicCache; use App\Exceptions\BadRequest as BadRequestException; +use App\Library\Validators\Common as CommonValidator; use App\Models\Topic as TopicModel; use App\Repos\Topic as TopicRepo; @@ -81,6 +82,17 @@ class Topic extends Validator return $value; } + public function checkCover($cover) + { + $value = $this->filter->sanitize($cover, ['trim', 'string']); + + if (!CommonValidator::url($value)) { + throw new BadRequestException('topic.invalid_cover'); + } + + return kg_cos_img_style_trim($value); + } + public function checkSummary($summary) { $value = $this->filter->sanitize($summary, ['trim', 'string']); diff --git a/config/errors.php b/config/errors.php index 23ea859d..d809b316 100644 --- a/config/errors.php +++ b/config/errors.php @@ -120,6 +120,7 @@ $error['nav.has_child_node'] = '不允许相关操作(存在子节点)'; $error['article.not_found'] = '文章不存在'; $error['article.title_too_short'] = '标题太短(少于2个字符)'; $error['article.title_too_long'] = '标题太长(多于50个字符)'; +$error['article.keyword_too_long'] = '关键字太长(多于100个字符)'; $error['article.content_too_short'] = '内容太短(少于10个字符)'; $error['article.content_too_long'] = '内容太长(多于30000个字符)'; $error['article.invalid_source_type'] = '无效的来源类型'; @@ -138,6 +139,7 @@ $error['article.delete_not_allowed'] = '当前不允许删除文章'; $error['question.not_found'] = '问题不存在'; $error['question.title_too_short'] = '标题太短(少于5个字符)'; $error['question.title_too_long'] = '标题太长(多于50个字符)'; +$error['question.keyword_too_long'] = '关键字太长(多于100个字符)'; $error['question.content_too_short'] = '内容太短(少于10个字符)'; $error['question.content_too_long'] = '内容太长(多于30000个字符)'; $error['question.invalid_publish_status'] = '无效的发布状态'; @@ -172,7 +174,7 @@ $error['course.not_found'] = '课程不存在'; $error['course.title_too_short'] = '标题太短(少于5个字符)'; $error['course.title_too_long'] = '标题太长(多于50个字符)'; $error['course.summary_too_long'] = '标题太长(多于255个字符)'; -$error['course.keywords_too_long'] = '关键字太长(多于100个字符)'; +$error['course.keyword_too_long'] = '关键字太长(多于100个字符)'; $error['course.details_too_long'] = '详情太长(多于5000个字符)'; $error['course.invalid_model'] = '无效的模型类别'; $error['course.invalid_level'] = '无效的难度级别'; @@ -203,6 +205,7 @@ $error['topic.not_found'] = '话题不存在'; $error['topic.title_too_short'] = '标题太短(少于2个字符)'; $error['topic.title_too_long'] = '标题太长(多于50个字符)'; $error['topic.summary_too_long'] = '简介太长(多于255个字符)'; +$error['topic.invalid_cover'] = '无效的封面'; $error['topic.invalid_publish_status'] = '无效的发布状态'; /** @@ -328,6 +331,7 @@ $error['page.title_too_short'] = '标题太短(少于2个字符)'; $error['page.title_too_long'] = '标题太长(多于50个字符)'; $error['page.alias_too_short'] = '别名太短(少于2个字符)'; $error['page.alias_too_long'] = '别名太长(多于50个字符)'; +$error['page.keyword_too_long'] = '关键字太长(多于100个字符)'; $error['page.content_too_short'] = '内容太短(少于10个字符)'; $error['page.content_too_long'] = '内容太长(多于30000个字符)'; $error['page.invalid_alias'] = '无效的别名(推荐使用英文作为别名)'; @@ -339,6 +343,7 @@ $error['page.invalid_publish_status'] = '无效的发布状态'; $error['help.not_found'] = '帮助不存在'; $error['help.title_too_short'] = '标题太短(少于2个字符)'; $error['help.title_too_long'] = '标题太长(多于50个字符)'; +$error['help.keyword_too_long'] = '关键字太长(多于100个字符)'; $error['help.content_too_short'] = '内容太短(少于10个字符)'; $error['help.content_too_long'] = '内容太长(多于30000个字符)'; $error['help.invalid_priority'] = '无效的排序数值(范围:1-255)'; diff --git a/db/migrations/20220801025747.php b/db/migrations/20220801025747.php index 6ceec26c..a86f33b1 100644 --- a/db/migrations/20220801025747.php +++ b/db/migrations/20220801025747.php @@ -6,8 +6,9 @@ */ use Phinx\Db\Adapter\MysqlAdapter; +use Phinx\Migration\AbstractMigration; -class V20220801025747 extends Phinx\Migration\AbstractMigration +final class V20220801025747 extends AbstractMigration { public function up() diff --git a/db/migrations/20220915084746.php b/db/migrations/20220915084746.php new file mode 100644 index 00000000..b65b34c8 --- /dev/null +++ b/db/migrations/20220915084746.php @@ -0,0 +1,127 @@ +alterArticleTable(); + $this->alterQuestionTable(); + $this->alterTopicTable(); + $this->alterPageTable(); + $this->alterHelpTable(); + $this->handleTopics(); + } + + protected function alterArticleTable() + { + $table = $this->table('kg_article'); + + if ($table->hasColumn('keywords') == false) { + $table->addColumn('keywords', 'string', [ + 'null' => false, + 'default' => '', + 'limit' => 100, + 'collation' => 'utf8mb4_general_ci', + 'encoding' => 'utf8mb4', + 'comment' => '关键字', + 'after' => 'summary', + ]); + } + + $table->save(); + } + + protected function alterQuestionTable() + { + $table = $this->table('kg_question'); + + if ($table->hasColumn('keywords') == false) { + $table->addColumn('keywords', 'string', [ + 'null' => false, + 'default' => '', + 'limit' => 100, + 'collation' => 'utf8mb4_general_ci', + 'encoding' => 'utf8mb4', + 'comment' => '关键字', + 'after' => 'tags', + ]); + } + + $table->save(); + } + + protected function alterPageTable() + { + $table = $this->table('kg_page'); + + if ($table->hasColumn('keywords') == false) { + $table->addColumn('keywords', 'string', [ + 'null' => false, + 'default' => '', + 'limit' => 100, + 'collation' => 'utf8mb4_general_ci', + 'encoding' => 'utf8mb4', + 'comment' => '关键字', + 'after' => 'alias', + ]); + } + + $table->save(); + } + + protected function alterHelpTable() + { + $table = $this->table('kg_help'); + + if ($table->hasColumn('keywords') == false) { + $table->addColumn('keywords', 'string', [ + 'null' => false, + 'default' => '', + 'limit' => 100, + 'collation' => 'utf8mb4_general_ci', + 'encoding' => 'utf8mb4', + 'comment' => '关键字', + 'after' => 'title', + ]); + } + + $table->save(); + } + + protected function alterTopicTable() + { + $table = $this->table('kg_topic'); + + if ($table->hasColumn('cover') == false) { + $table->addColumn('cover', 'string', [ + 'null' => false, + 'default' => '', + 'limit' => 100, + 'collation' => 'utf8mb4_general_ci', + 'encoding' => 'utf8mb4', + 'comment' => '封面', + 'after' => 'title', + ]); + } + + $table->save(); + } + + protected function handleTopics() + { + $this->getQueryBuilder() + ->update('kg_topic') + ->set('cover', '/img/default/topic_cover.png') + ->where(['cover' => '']) + ->execute(); + } + +}