From 661034f3c15db8e03fd19939526266e5fd330e33 Mon Sep 17 00:00:00 2001 From: koogua Date: Mon, 10 May 2021 21:35:07 +0800 Subject: [PATCH] =?UTF-8?q?=E9=98=B6=E6=AE=B5=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/Builders/ArticleFavoriteList.php | 10 +- app/Builders/QuestionFavoriteList.php | 84 +++++ app/Builders/QuestionList.php | 33 ++ app/Console/Tasks/SitemapTask.php | 33 +- .../Admin/Controllers/AnswerController.php | 81 +++++ .../Admin/Controllers/ArticleController.php | 40 +-- .../Controllers/ModerationController.php | 12 + .../Admin/Controllers/QuestionController.php | 195 ++++++++++ .../Admin/Controllers/UploadController.php | 1 - app/Http/Admin/Services/Answer.php | 105 ++++++ app/Http/Admin/Services/Article.php | 78 +--- app/Http/Admin/Services/AuthNode.php | 63 +++- app/Http/Admin/Services/Moderation.php | 42 +++ app/Http/Admin/Services/Question.php | 336 ++++++++++++++++++ app/Http/Admin/Views/article/add.volt | 11 - app/Http/Admin/Views/article/edit.volt | 1 - app/Http/Admin/Views/article/edit_basic.volt | 29 -- app/Http/Admin/Views/article/edit_desc.volt | 12 +- app/Http/Admin/Views/article/list.volt | 7 +- .../Views/article/{show.volt => review.volt} | 33 +- app/Http/Admin/Views/article/search.volt | 11 - app/Http/Admin/Views/audit/list.volt | 13 +- app/Http/Admin/Views/macros/question.volt | 18 + app/Http/Admin/Views/moderation/articles.volt | 29 +- .../Admin/Views/moderation/questions.volt | 77 ++++ app/Http/Admin/Views/question/add.volt | 24 ++ app/Http/Admin/Views/question/edit.volt | 61 ++++ app/Http/Admin/Views/question/edit_basic.volt | 35 ++ app/Http/Admin/Views/question/edit_desc.volt | 14 + app/Http/Admin/Views/question/list.volt | 160 +++++++++ app/Http/Admin/Views/question/review.volt | 85 +++++ app/Http/Admin/Views/question/search.volt | 93 +++++ app/Http/Admin/Views/setting/storage.volt | 4 - .../Home/Controllers/AnswerController.php | 53 ++- .../Home/Controllers/ArticleController.php | 24 +- .../Home/Controllers/CommentController.php | 8 + .../Home/Controllers/QuestionController.php | 9 + app/Http/Home/Services/Article.php | 161 +++------ app/Http/Home/Services/Question.php | 16 +- app/Http/Home/Views/answer/add.volt | 44 ++- app/Http/Home/Views/answer/edit.volt | 42 ++- app/Http/Home/Views/article/show.volt | 32 +- app/Http/Home/Views/article/sticky.volt | 2 +- app/Http/Home/Views/comment/list.volt | 13 +- app/Http/Home/Views/comment/replies.volt | 14 +- app/Http/Home/Views/macros/notification.volt | 23 ++ app/Http/Home/Views/question/answer_tips.volt | 17 + app/Http/Home/Views/question/answers.volt | 25 +- app/Http/Home/Views/question/edit.volt | 2 +- app/Http/Home/Views/question/show.volt | 55 ++- app/Http/Home/Views/question/sticky.volt | 2 +- app/Http/Home/Views/search/course.volt | 2 +- app/Http/Home/Views/search/group.volt | 8 +- app/Http/Home/Views/search/question.volt | 7 + app/Http/Home/Views/search/user.volt | 8 +- app/Http/Home/Views/tag/list.volt | 5 - app/Http/Home/Views/user/console/answers.volt | 8 +- .../Home/Views/user/console/favorites.volt | 4 +- .../Views/user/console/favorites_article.volt | 2 +- .../Views/user/console/favorites_course.volt | 2 +- .../user/console/favorites_question.volt | 36 ++ app/Library/Helper.php | 24 -- app/Models/Answer.php | 23 ++ app/Models/PointHistory.php | 7 +- app/Models/Reason.php | 18 + app/Repos/Answer.php | 2 +- app/Repos/Article.php | 2 +- app/Services/Logic/Answer/AnswerAccept.php | 7 +- app/Services/Logic/Answer/AnswerCreate.php | 12 +- app/Services/Logic/Answer/AnswerDelete.php | 2 + app/Services/Logic/Answer/AnswerUpdate.php | 2 + app/Services/Logic/Article/ArticleCreate.php | 46 +++ .../Logic/Article/ArticleDataTrait.php | 106 ++++++ app/Services/Logic/Article/ArticleDelete.php | 53 +++ app/Services/Logic/Article/ArticleUpdate.php | 52 +++ .../Logic/Comment/CommentCountTrait.php | 5 + .../Logic/Notice/System/QuestionAnswered.php | 46 +++ .../Logic/Notice/System/QuestionApproved.php | 28 ++ .../Logic/Notice/System/QuestionRejected.php | 29 ++ .../{AnswerAccept.php => AnswerAccepted.php} | 8 +- .../Logic/Point/History/AnswerLiked.php | 85 +++++ .../Logic/Question/QuestionDelete.php | 21 ++ app/Services/Logic/Question/QuestionInfo.php | 12 +- app/Services/Logic/Search/Question.php | 20 +- .../Logic/User/Console/ArticleList.php | 2 +- .../Logic/User/Console/FavoriteList.php | 34 ++ app/Services/MyStorage.php | 14 - app/Services/Search/ArticleDocument.php | 54 ++- app/Services/Search/QuestionDocument.php | 85 +++-- app/Validators/Article.php | 32 +- app/Validators/Question.php | 26 ++ config/errors.php | 13 +- db/migrations/20210430023157.php | 11 +- public/static/admin/css/common.css | 30 ++ public/static/admin/js/vditor.js | 3 +- public/static/home/css/common.css | 72 ++-- public/static/home/js/answer.js | 88 +---- public/static/home/js/article.show.js | 13 + public/static/home/js/comment.js | 8 +- public/static/home/js/question.show.js | 27 +- 100 files changed, 2869 insertions(+), 707 deletions(-) create mode 100644 app/Builders/QuestionFavoriteList.php create mode 100644 app/Http/Admin/Controllers/AnswerController.php create mode 100644 app/Http/Admin/Controllers/QuestionController.php create mode 100644 app/Http/Admin/Services/Answer.php create mode 100644 app/Http/Admin/Services/Question.php rename app/Http/Admin/Views/article/{show.volt => review.volt} (72%) create mode 100644 app/Http/Admin/Views/macros/question.volt create mode 100644 app/Http/Admin/Views/moderation/questions.volt create mode 100644 app/Http/Admin/Views/question/add.volt create mode 100644 app/Http/Admin/Views/question/edit.volt create mode 100644 app/Http/Admin/Views/question/edit_basic.volt create mode 100644 app/Http/Admin/Views/question/edit_desc.volt create mode 100644 app/Http/Admin/Views/question/list.volt create mode 100644 app/Http/Admin/Views/question/review.volt create mode 100644 app/Http/Admin/Views/question/search.volt create mode 100644 app/Http/Home/Views/question/answer_tips.volt create mode 100644 app/Http/Home/Views/user/console/favorites_question.volt create mode 100644 app/Services/Logic/Article/ArticleCreate.php create mode 100644 app/Services/Logic/Article/ArticleDataTrait.php create mode 100644 app/Services/Logic/Article/ArticleDelete.php create mode 100644 app/Services/Logic/Article/ArticleUpdate.php create mode 100644 app/Services/Logic/Notice/System/QuestionAnswered.php create mode 100644 app/Services/Logic/Notice/System/QuestionApproved.php create mode 100644 app/Services/Logic/Notice/System/QuestionRejected.php rename app/Services/Logic/Point/History/{AnswerAccept.php => AnswerAccepted.php} (86%) create mode 100644 app/Services/Logic/Point/History/AnswerLiked.php diff --git a/app/Builders/ArticleFavoriteList.php b/app/Builders/ArticleFavoriteList.php index 20e856a6..b81a55d7 100644 --- a/app/Builders/ArticleFavoriteList.php +++ b/app/Builders/ArticleFavoriteList.php @@ -4,6 +4,7 @@ namespace App\Builders; use App\Repos\Article as ArticleRepo; use App\Repos\User as UserRepo; +use Phalcon\Text; class ArticleFavoriteList extends Builder { @@ -38,7 +39,8 @@ class ArticleFavoriteList extends Builder $columns = [ 'id', 'title', 'cover', - 'view_count', 'like_count', 'comment_count', 'favorite_count', + 'view_count', 'like_count', + 'comment_count', 'favorite_count', ]; $articles = $articleRepo->findByIds($ids, $columns); @@ -48,7 +50,11 @@ class ArticleFavoriteList extends Builder $result = []; foreach ($articles->toArray() as $article) { - $article['cover'] = $baseUrl . $article['cover']; + + if (!empty($article['cover']) && !Text::startsWith($article['cover'], 'http')) { + $article['cover'] = $baseUrl . $article['cover']; + } + $result[$article['id']] = $article; } diff --git a/app/Builders/QuestionFavoriteList.php b/app/Builders/QuestionFavoriteList.php new file mode 100644 index 00000000..0f2cba55 --- /dev/null +++ b/app/Builders/QuestionFavoriteList.php @@ -0,0 +1,84 @@ +getQuestions($relations); + + foreach ($relations as $key => $value) { + $relations[$key]['question'] = $questions[$value['question_id']] ?? new \stdClass(); + } + + return $relations; + } + + public function handleUsers(array $relations) + { + $users = $this->getUsers($relations); + + foreach ($relations as $key => $value) { + $relations[$key]['user'] = $users[$value['user_id']] ?? new \stdClass(); + } + + return $relations; + } + + public function getQuestions(array $relations) + { + $ids = kg_array_column($relations, 'question_id'); + + $questionRepo = new QuestionRepo(); + + $columns = [ + 'id', 'title', 'cover', + 'view_count', 'like_count', + 'answer_count', 'favorite_count', + ]; + + $questions = $questionRepo->findByIds($ids, $columns); + + $baseUrl = kg_cos_url(); + + $result = []; + + foreach ($questions->toArray() as $question) { + + if (!empty($question['cover']) && !Text::startsWith($question['cover'], 'http')) { + $question['cover'] = $baseUrl . $question['cover']; + } + + $result[$question['id']] = $question; + } + + return $result; + } + + public function getUsers(array $relations) + { + $ids = kg_array_column($relations, 'user_id'); + + $userRepo = new UserRepo(); + + $users = $userRepo->findByIds($ids, ['id', 'name', 'avatar']); + + $baseUrl = kg_cos_url(); + + $result = []; + + foreach ($users->toArray() as $user) { + $user['avatar'] = $baseUrl . $user['avatar']; + $result[$user['id']] = $user; + } + + return $result; + } + +} diff --git a/app/Builders/QuestionList.php b/app/Builders/QuestionList.php index 95299dfb..3987fac9 100644 --- a/app/Builders/QuestionList.php +++ b/app/Builders/QuestionList.php @@ -2,6 +2,8 @@ namespace App\Builders; +use App\Caches\CategoryList as CategoryListCache; +use App\Models\Category as CategoryModel; use App\Repos\User as UserRepo; class QuestionList extends Builder @@ -16,6 +18,17 @@ class QuestionList extends Builder return $questions; } + public function handleCategories(array $articles) + { + $categories = $this->getCategories(); + + foreach ($articles as $key => $article) { + $articles[$key]['category'] = $categories[$article['category_id']] ?? new \stdClass(); + } + + return $articles; + } + public function handleUsers(array $questions) { $users = $this->getUsers($questions); @@ -28,6 +41,26 @@ class QuestionList extends Builder return $questions; } + public function getCategories() + { + $cache = new CategoryListCache(); + + $items = $cache->get(CategoryModel::TYPE_QUESTION); + + if (empty($items)) return []; + + $result = []; + + foreach ($items as $item) { + $result[$item['id']] = [ + 'id' => $item['id'], + 'name' => $item['name'], + ]; + } + + return $result; + } + public function getUsers($questions) { $ownerIds = kg_array_column($questions, 'owner_id'); diff --git a/app/Console/Tasks/SitemapTask.php b/app/Console/Tasks/SitemapTask.php index ad686103..a86751c8 100644 --- a/app/Console/Tasks/SitemapTask.php +++ b/app/Console/Tasks/SitemapTask.php @@ -8,6 +8,7 @@ use App\Models\Course as CourseModel; use App\Models\Help as HelpModel; use App\Models\ImGroup as ImGroupModel; use App\Models\Page as PageModel; +use App\Models\Question as QuestionModel; use App\Models\Topic as TopicModel; use App\Models\User as UserModel; use App\Services\Service as AppService; @@ -37,6 +38,7 @@ class SitemapTask extends Task $this->addIndex(); $this->addCourses(); $this->addArticles(); + $this->addQuestions(); $this->addTeachers(); $this->addTopics(); $this->addImGroups(); @@ -66,7 +68,11 @@ class SitemapTask extends Task /** * @var Resultset|CourseModel[] $courses */ - $courses = CourseModel::query()->where('published = 1')->execute(); + $courses = CourseModel::query() + ->where('published = 1') + ->orderBy('id DESC') + ->limit(500) + ->execute(); if ($courses->count() == 0) return; @@ -81,7 +87,11 @@ class SitemapTask extends Task /** * @var Resultset|ArticleModel[] $articles */ - $articles = ArticleModel::query()->where('published = 1')->execute(); + $articles = ArticleModel::query() + ->where('published = :published:', ['published' => ArticleModel::PUBLISH_APPROVED]) + ->orderBy('id DESC') + ->limit(500) + ->execute(); if ($articles->count() == 0) return; @@ -91,6 +101,25 @@ class SitemapTask extends Task } } + protected function addQuestions() + { + /** + * @var Resultset|QuestionModel[] $questions + */ + $questions = QuestionModel::query() + ->where('published = :published:', ['published' => QuestionModel::PUBLISH_APPROVED]) + ->orderBy('id DESC') + ->limit(500) + ->execute(); + + if ($questions->count() == 0) return; + + foreach ($questions as $question) { + $loc = sprintf('%s/question/%s', $this->siteUrl, $question->id); + $this->sitemap->addItem($loc, 0.8); + } + } + protected function addTeachers() { /** diff --git a/app/Http/Admin/Controllers/AnswerController.php b/app/Http/Admin/Controllers/AnswerController.php new file mode 100644 index 00000000..f2f3e38e --- /dev/null +++ b/app/Http/Admin/Controllers/AnswerController.php @@ -0,0 +1,81 @@ +getAnswers(); + + $this->view->setVar('pager', $pager); + } + + /** + * @Post("/{id:[0-9]+}/update", name="admin.answer.update") + */ + public function updateAction($id) + { + $answerService = new AnswerService(); + + $answerService->updateAnswer($id); + + $content = ['msg' => '更新回答成功']; + + return $this->jsonSuccess($content); + } + + /** + * @Post("/{id:[0-9]+}/delete", name="admin.answer.delete") + */ + public function deleteAction($id) + { + $answerService = new AnswerService(); + + $answerService->deleteAnswer($id); + + $content = [ + 'location' => $this->request->getHTTPReferer(), + 'msg' => '删除回答成功', + ]; + + return $this->jsonSuccess($content); + } + + /** + * @Post("/{id:[0-9]+}/restore", name="admin.answer.restore") + */ + public function restoreAction($id) + { + $answerService = new AnswerService(); + + $answerService->restoreAnswer($id); + + $content = [ + 'location' => $this->request->getHTTPReferer(), + 'msg' => '还原回答成功', + ]; + + return $this->jsonSuccess($content); + } + +} diff --git a/app/Http/Admin/Controllers/ArticleController.php b/app/Http/Admin/Controllers/ArticleController.php index 7fd4a3b4..26efde1d 100644 --- a/app/Http/Admin/Controllers/ArticleController.php +++ b/app/Http/Admin/Controllers/ArticleController.php @@ -54,19 +54,6 @@ class ArticleController extends Controller $this->view->setVar('pager', $pager); } - /** - * @Get("/list/pending", name="admin.article.pending_list") - */ - public function pendingListAction() - { - $articleService = new ArticleService(); - - $pager = $articleService->getPendingArticles(); - - $this->view->pick('article/pending_list'); - $this->view->setVar('pager', $pager); - } - /** * @Get("/add", name="admin.article.add") */ @@ -106,10 +93,8 @@ class ArticleController extends Controller { $articleService = new ArticleService(); - $rejectOptions = $articleService->getRejectOptions(); $article = $articleService->getArticle($id); - $this->view->setVar('reject_options', $rejectOptions); $this->view->setVar('article', $article); } @@ -184,22 +169,31 @@ class ArticleController extends Controller } /** - * @Post("/{id:[0-9]+}/review", name="admin.article.review") + * @Route("/{id:[0-9]+}/review", name="admin.article.review") */ public function reviewAction($id) { $articleService = new ArticleService(); - $articleService->reviewArticle($id); + if ($this->request->isPost()) { - $location = $this->url->get(['for' => 'admin.article.pending_list']); + $articleService->reviewArticle($id); - $content = [ - 'location' => $location, - 'msg' => '审核文章成功', - ]; + $location = $this->url->get(['for' => 'admin.mod.articles']); - return $this->jsonSuccess($content); + $content = [ + 'location' => $location, + 'msg' => '审核文章成功', + ]; + + return $this->jsonSuccess($content); + } + + $rejectOptions = $articleService->getRejectOptions(); + $article = $articleService->getArticle($id); + + $this->view->setVar('reject_options', $rejectOptions); + $this->view->setVar('article', $article); } } diff --git a/app/Http/Admin/Controllers/ModerationController.php b/app/Http/Admin/Controllers/ModerationController.php index 75c81808..f5fb5dbb 100644 --- a/app/Http/Admin/Controllers/ModerationController.php +++ b/app/Http/Admin/Controllers/ModerationController.php @@ -22,4 +22,16 @@ class ModerationController extends Controller $this->view->setVar('pager', $pager); } + /** + * @Get("/questions", name="admin.mod.questions") + */ + public function questionsAction() + { + $modService = new ModerationService(); + + $pager = $modService->getQuestions(); + + $this->view->setVar('pager', $pager); + } + } diff --git a/app/Http/Admin/Controllers/QuestionController.php b/app/Http/Admin/Controllers/QuestionController.php new file mode 100644 index 00000000..ac959bfe --- /dev/null +++ b/app/Http/Admin/Controllers/QuestionController.php @@ -0,0 +1,195 @@ +url->get( + ['for' => 'admin.category.list'], + ['type' => CategoryModel::TYPE_ARTICLE] + ); + + $this->response->redirect($location); + } + + /** + * @Get("/search", name="admin.question.search") + */ + public function searchAction() + { + $questionService = new QuestionService(); + + $publishTypes = $questionService->getPublishTypes(); + $categories = $questionService->getCategories(); + $xmTags = $questionService->getXmTags(0); + + $this->view->setVar('publish_types', $publishTypes); + $this->view->setVar('categories', $categories); + $this->view->setVar('xm_tags', $xmTags); + } + + /** + * @Get("/list", name="admin.question.list") + */ + public function listAction() + { + $questionService = new QuestionService(); + + $pager = $questionService->getQuestions(); + + $this->view->setVar('pager', $pager); + } + + /** + * @Get("/add", name="admin.question.add") + */ + public function addAction() + { + $questionService = new QuestionService(); + + $categories = $questionService->getCategories(); + + $this->view->setVar('categories', $categories); + } + + /** + * @Get("/{id:[0-9]+}/edit", name="admin.question.edit") + */ + public function editAction($id) + { + $questionService = new QuestionService(); + + $publishTypes = $questionService->getPublishTypes(); + $categories = $questionService->getCategories(); + $question = $questionService->getQuestion($id); + $xmTags = $questionService->getXmTags($id); + + $this->view->setVar('publish_types', $publishTypes); + $this->view->setVar('categories', $categories); + $this->view->setVar('question', $question); + $this->view->setVar('xm_tags', $xmTags); + } + + /** + * @Get("/{id:[0-9]+}/show", name="admin.question.show") + */ + public function showAction($id) + { + $questionService = new QuestionService(); + + $question = $questionService->getQuestion($id); + + $this->view->setVar('question', $question); + } + + /** + * @Post("/create", name="admin.question.create") + */ + public function createAction() + { + $questionService = new QuestionService(); + + $question = $questionService->createQuestion(); + + $location = $this->url->get([ + 'for' => 'admin.question.edit', + 'id' => $question->id, + ]); + + $content = [ + 'location' => $location, + 'msg' => '创建问题成功', + ]; + + return $this->jsonSuccess($content); + } + + /** + * @Post("/{id:[0-9]+}/update", name="admin.question.update") + */ + public function updateAction($id) + { + $questionService = new QuestionService(); + + $questionService->updateQuestion($id); + + $content = ['msg' => '更新问题成功']; + + return $this->jsonSuccess($content); + } + + /** + * @Post("/{id:[0-9]+}/delete", name="admin.question.delete") + */ + public function deleteAction($id) + { + $questionService = new QuestionService(); + + $questionService->deleteQuestion($id); + + $content = [ + 'location' => $this->request->getHTTPReferer(), + 'msg' => '删除问题成功', + ]; + + return $this->jsonSuccess($content); + } + + /** + * @Post("/{id:[0-9]+}/restore", name="admin.question.restore") + */ + public function restoreAction($id) + { + $questionService = new QuestionService(); + + $questionService->restoreQuestion($id); + + $content = [ + 'location' => $this->request->getHTTPReferer(), + 'msg' => '还原问题成功', + ]; + + return $this->jsonSuccess($content); + } + + /** + * @Route("/{id:[0-9]+}/review", name="admin.question.review") + */ + public function reviewAction($id) + { + $questionService = new QuestionService(); + + if ($this->request->isPost()) { + + $questionService->reviewQuestion($id); + + $location = $this->url->get(['for' => 'admin.mod.questions']); + + $content = [ + 'location' => $location, + 'msg' => '审核问题成功', + ]; + + return $this->jsonSuccess($content); + } + + $rejectOptions = $questionService->getRejectOptions(); + $question = $questionService->getQuestion($id); + + $this->view->setVar('reject_options', $rejectOptions); + $this->view->setVar('question', $question); + } + +} diff --git a/app/Http/Admin/Controllers/UploadController.php b/app/Http/Admin/Controllers/UploadController.php index 22f2e82f..b34c5f3a 100644 --- a/app/Http/Admin/Controllers/UploadController.php +++ b/app/Http/Admin/Controllers/UploadController.php @@ -148,7 +148,6 @@ class UploadController extends Controller $items['user_avatar'] = $service->uploadDefaultUserAvatar(); $items['group_avatar'] = $service->uploadDefaultGroupAvatar(); - $items['article_cover'] = $service->uploadDefaultArticleCover(); $items['course_cover'] = $service->uploadDefaultCourseCover(); $items['package_cover'] = $service->uploadDefaultPackageCover(); $items['gift_cover'] = $service->uploadDefaultGiftCover(); diff --git a/app/Http/Admin/Services/Answer.php b/app/Http/Admin/Services/Answer.php new file mode 100644 index 00000000..093304e6 --- /dev/null +++ b/app/Http/Admin/Services/Answer.php @@ -0,0 +1,105 @@ +getParams(); + + $params['deleted'] = $params['deleted'] ?? 0; + + $sort = $pagerQuery->getSort(); + $page = $pagerQuery->getPage(); + $limit = $pagerQuery->getLimit(); + + $answerRepo = new AnswerRepo(); + + $pager = $answerRepo->paginate($params, $sort, $page, $limit); + + return $this->handleAnswers($pager); + } + + public function getAnswer($id) + { + return $this->findOrFail($id); + } + + public function updateAnswer($id) + { + $answer = $this->findOrFail($id); + + $post = $this->request->getPost(); + + $validator = new AnswerValidator(); + + $data = []; + + if (isset($post['content'])) { + $data['content'] = $validator->checkContent($post['content']); + } + + if (isset($post['published'])) { + $data['published'] = $validator->checkPublishStatus($post['published']); + } + + $answer->update($data); + + return $answer; + } + + public function deleteAnswer($id) + { + $page = $this->findOrFail($id); + + $page->deleted = 1; + + $page->update(); + + return $page; + } + + public function restoreAnswer($id) + { + $page = $this->findOrFail($id); + + $page->deleted = 0; + + $page->update(); + + return $page; + } + + protected function findOrFail($id) + { + $validator = new AnswerValidator(); + + return $validator->checkAnswer($id); + } + + protected function handleAnswers($pager) + { + if ($pager->total_items > 0) { + + $builder = new AnswerListBuilder(); + + $pipeA = $pager->items->toArray(); + $pipeB = $builder->handleUsers($pipeA); + $pipeC = $builder->objects($pipeB); + + $pager->items = $pipeC; + } + + return $pager; + } + +} diff --git a/app/Http/Admin/Services/Article.php b/app/Http/Admin/Services/Article.php index 98674a74..84c2d84a 100644 --- a/app/Http/Admin/Services/Article.php +++ b/app/Http/Admin/Services/Article.php @@ -7,15 +7,14 @@ use App\Caches\Article as ArticleCache; use App\Library\Paginator\Query as PagerQuery; use App\Library\Utils\Word as WordUtil; use App\Models\Article as ArticleModel; -use App\Models\ArticleTag as ArticleTagModel; use App\Models\Category as CategoryModel; use App\Models\Reason as ReasonModel; use App\Models\User as UserModel; use App\Repos\Article as ArticleRepo; -use App\Repos\ArticleTag as ArticleTagRepo; use App\Repos\Category as CategoryRepo; use App\Repos\Tag as TagRepo; use App\Repos\User as UserRepo; +use App\Services\Logic\Article\ArticleDataTrait; use App\Services\Logic\Notice\System\ArticleApproved as ArticleApprovedNotice; use App\Services\Logic\Notice\System\ArticleRejected as ArticleRejectedNotice; use App\Services\Logic\Point\History\ArticlePost as ArticlePostPointHistory; @@ -25,14 +24,7 @@ use App\Validators\Article as ArticleValidator; class Article extends Service { - public function getArticleModel() - { - $article = new ArticleModel(); - - $article->afterFetch(); - - return $article; - } + use ArticleDataTrait; public function getXmTags($id) { @@ -164,14 +156,6 @@ class Article 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']); - } - if (isset($post['content'])) { $data['content'] = $validator->checkContent($post['content']); $data['word_count'] = WordUtil::getWordCount($data['content']); @@ -262,9 +246,12 @@ class Article extends Service $article = $this->findOrFail($id); + $validator = new ArticleValidator(); + if ($type == 'approve') { $article->published = ArticleModel::PUBLISH_APPROVED; } elseif ($type == 'reject') { + $validator->checkRejectReason($reason); $article->published = ArticleModel::PUBLISH_REJECTED; } @@ -309,61 +296,6 @@ class Article extends Service return $validator->checkArticle($id); } - protected function saveTags(ArticleModel $article, $tagIds) - { - $originTagIds = []; - - /** - * 修改数据后,afterFetch设置的属性会失效,重新执行 - */ - $article->afterFetch(); - - if ($article->tags) { - $originTagIds = kg_array_column($article->tags, 'id'); - } - - $newTagIds = $tagIds ? explode(',', $tagIds) : []; - $addedTagIds = array_diff($newTagIds, $originTagIds); - - if ($addedTagIds) { - foreach ($addedTagIds as $tagId) { - $articleTag = new ArticleTagModel(); - $articleTag->article_id = $article->id; - $articleTag->tag_id = $tagId; - $articleTag->create(); - } - } - - $deletedTagIds = array_diff($originTagIds, $newTagIds); - - if ($deletedTagIds) { - $articleTagRepo = new ArticleTagRepo(); - foreach ($deletedTagIds as $tagId) { - $articleTag = $articleTagRepo->findArticleTag($article->id, $tagId); - if ($articleTag) { - $articleTag->delete(); - } - } - } - - $articleTags = []; - - if ($newTagIds) { - $tagRepo = new TagRepo(); - $tags = $tagRepo->findByIds($newTagIds); - if ($tags->count() > 0) { - $articleTags = []; - foreach ($tags as $tag) { - $articleTags[] = ['id' => $tag->id, 'name' => $tag->name]; - } - } - } - - $article->tags = $articleTags; - - $article->update(); - } - protected function handleArticles($pager) { if ($pager->total_items > 0) { diff --git a/app/Http/Admin/Services/AuthNode.php b/app/Http/Admin/Services/AuthNode.php index 04e00268..656a2bd4 100644 --- a/app/Http/Admin/Services/AuthNode.php +++ b/app/Http/Admin/Services/AuthNode.php @@ -272,12 +272,6 @@ class AuthNode extends Service 'type' => 'button', 'route' => 'admin.article.edit', ], - [ - 'id' => '1-7-6', - 'title' => '文章分类', - 'type' => 'menu', - 'route' => 'admin.article.category', - ], [ 'id' => '1-7-5', 'title' => '删除文章', @@ -288,7 +282,7 @@ class AuthNode extends Service 'id' => '1-7-9', 'title' => '文章详情', 'type' => 'button', - 'route' => 'admin.article.review', + 'route' => 'admin.article.show', ], [ 'id' => '1-7-10', @@ -298,6 +292,55 @@ class AuthNode extends Service ], ], ], + [ + 'id' => '1-10', + 'title' => '问答管理', + 'type' => 'menu', + 'children' => [ + [ + 'id' => '1-10-1', + 'title' => '问题列表', + 'type' => 'menu', + 'route' => 'admin.question.list', + ], + [ + 'id' => '1-10-2', + 'title' => '搜索问题', + 'type' => 'menu', + 'route' => 'admin.question.search', + ], + [ + 'id' => '1-10-3', + 'title' => '添加问题', + 'type' => 'menu', + 'route' => 'admin.question.add', + ], + [ + 'id' => '1-10-4', + 'title' => '编辑问题', + 'type' => 'button', + 'route' => 'admin.question.edit', + ], + [ + 'id' => '1-10-5', + 'title' => '删除问题', + 'type' => 'button', + 'route' => 'admin.question.delete', + ], + [ + 'id' => '1-10-9', + 'title' => '问题详情', + 'type' => 'button', + 'route' => 'admin.question.show', + ], + [ + 'id' => '1-10-10', + 'title' => '审核问题', + 'type' => 'button', + 'route' => 'admin.question.review', + ], + ], + ], [ 'id' => '1-8', 'title' => '标签管理', @@ -387,6 +430,12 @@ class AuthNode extends Service 'type' => 'menu', 'route' => 'admin.mod.articles', ], + [ + 'id' => '2-10-2', + 'title' => '问题列表', + 'type' => 'menu', + 'route' => 'admin.mod.questions', + ], ], ], [ diff --git a/app/Http/Admin/Services/Moderation.php b/app/Http/Admin/Services/Moderation.php index 68b69cc0..bef5a221 100644 --- a/app/Http/Admin/Services/Moderation.php +++ b/app/Http/Admin/Services/Moderation.php @@ -3,9 +3,12 @@ namespace App\Http\Admin\Services; use App\Builders\ArticleList as ArticleListBuilder; +use App\Builders\QuestionList as QuestionListBuilder; use App\Library\Paginator\Query as PagerQuery; use App\Models\Article as ArticleModel; +use App\Models\Question as QuestionModel; use App\Repos\Article as ArticleRepo; +use App\Repos\Question as QuestionRepo; class Moderation extends Service { @@ -30,6 +33,26 @@ class Moderation extends Service return $this->handleArticles($pager); } + public function getQuestions() + { + $pagerQuery = new PagerQuery(); + + $params = $pagerQuery->getParams(); + + $params['published'] = QuestionModel::PUBLISH_PENDING; + $params['deleted'] = 0; + + $sort = $pagerQuery->getSort(); + $page = $pagerQuery->getPage(); + $limit = $pagerQuery->getLimit(); + + $questionRepo = new QuestionRepo(); + + $pager = $questionRepo->paginate($params, $sort, $page, $limit); + + return $this->handleQuestions($pager); + } + protected function handleArticles($pager) { if ($pager->total_items > 0) { @@ -49,4 +72,23 @@ class Moderation extends Service return $pager; } + protected function handleQuestions($pager) + { + if ($pager->total_items > 0) { + + $builder = new QuestionListBuilder(); + + $items = $pager->items->toArray(); + + $pipeA = $builder->handleQuestions($items); + $pipeB = $builder->handleCategories($pipeA); + $pipeC = $builder->handleUsers($pipeB); + $pipeD = $builder->objects($pipeC); + + $pager->items = $pipeD; + } + + return $pager; + } + } diff --git a/app/Http/Admin/Services/Question.php b/app/Http/Admin/Services/Question.php new file mode 100644 index 00000000..1fe04d72 --- /dev/null +++ b/app/Http/Admin/Services/Question.php @@ -0,0 +1,336 @@ +findAll(['published' => 1], 'priority'); + + if ($allTags->count() == 0) return []; + + $questionTagIds = []; + + if ($id > 0) { + $question = $this->findOrFail($id); + if (!empty($question->tags)) { + $questionTagIds = kg_array_column($question->tags, 'id'); + } + } + + $list = []; + + foreach ($allTags as $tag) { + $selected = in_array($tag->id, $questionTagIds); + $list[] = [ + 'name' => $tag->name, + 'value' => $tag->id, + 'selected' => $selected, + ]; + } + + return $list; + } + + public function getCategories() + { + $categoryRepo = new CategoryRepo(); + + return $categoryRepo->findAll([ + 'type' => CategoryModel::TYPE_ARTICLE, + 'level' => 1, + 'published' => 1, + ]); + } + + public function getPublishTypes() + { + return QuestionModel::publishTypes(); + } + + public function getRejectOptions() + { + return ReasonModel::questionRejectOptions(); + } + + public function getQuestions() + { + $pagerQuery = new PagerQuery(); + + $params = $pagerQuery->getParams(); + + if (!empty($params['xm_tag_ids'])) { + $params['tag_id'] = explode(',', $params['xm_tag_ids']); + } + + $params['deleted'] = $params['deleted'] ?? 0; + + $sort = $pagerQuery->getSort(); + $page = $pagerQuery->getPage(); + $limit = $pagerQuery->getLimit(); + + $questionRepo = new QuestionRepo(); + + $pager = $questionRepo->paginate($params, $sort, $page, $limit); + + return $this->handleQuestions($pager); + } + + public function getQuestion($id) + { + return $this->findOrFail($id); + } + + public function createQuestion() + { + $post = $this->request->getPost(); + + $user = $this->getLoginUser(); + + $validator = new QuestionValidator(); + + $title = $validator->checkTitle($post['title']); + + $question = new QuestionModel(); + + $question->owner_id = $user->id; + $question->title = $title; + + $question->create(); + + $this->incrUserQuestionCount($user); + + $this->eventsManager->fire('Question:afterCreate', $this, $question); + + return $question; + } + + public function updateQuestion($id) + { + $post = $this->request->getPost(); + + $question = $this->findOrFail($id); + + $validator = new QuestionValidator(); + + $data = []; + + if (isset($post['category_id'])) { + $category = $validator->checkCategory($post['category_id']); + $data['category_id'] = $category->id; + } + + if (isset($post['title'])) { + $data['title'] = $validator->checkTitle($post['title']); + } + + if (isset($post['content'])) { + $data['content'] = $validator->checkContent($post['content']); + } + + if (isset($post['anonymous'])) { + $data['anonymous'] = $validator->checkAnonymousStatus($post['anonymous']); + } + + if (isset($post['closed'])) { + $data['closed'] = $validator->checkCloseStatus($post['closed']); + } + + if (isset($post['published'])) { + $data['published'] = $validator->checkPublishStatus($post['published']); + } + + if (isset($post['xm_tag_ids'])) { + $this->saveTags($question, $post['xm_tag_ids']); + } + + $question->update($data); + + $this->rebuildQuestionIndex($question); + + $this->eventsManager->fire('Question:afterUpdate', $this, $question); + + return $question; + } + + public function deleteQuestion($id) + { + $question = $this->findOrFail($id); + + $question->deleted = 1; + + $question->update(); + + $userRepo = new UserRepo(); + + $owner = $userRepo->findById($question->owner_id); + + $this->decrUserQuestionCount($owner); + + $this->rebuildQuestionIndex($question); + + $this->eventsManager->fire('Question:afterDelete', $this, $question); + + return $question; + } + + public function restoreQuestion($id) + { + $question = $this->findOrFail($id); + + $question->deleted = 0; + + $question->update(); + + $userRepo = new UserRepo(); + + $owner = $userRepo->findById($question->owner_id); + + $this->incrUserQuestionCount($owner); + + $this->rebuildQuestionIndex($question); + + $this->eventsManager->fire('Question:afterRestore', $this, $question); + + return $question; + } + + public function reviewQuestion($id) + { + $type = $this->request->getPost('type', ['trim', 'string']); + $reason = $this->request->getPost('reason', ['trim', 'string']); + + $question = $this->findOrFail($id); + + $validator = new QuestionValidator(); + + if ($type == 'approve') { + $question->published = QuestionModel::PUBLISH_APPROVED; + } elseif ($type == 'reject') { + $validator->checkRejectReason($reason); + $question->published = QuestionModel::PUBLISH_REJECTED; + } + + $question->update(); + + $sender = $this->getLoginUser(); + + if ($type == 'approve') { + + $this->rebuildQuestionIndex($question); + + $this->handlePostPoint($question); + + $notice = new QuestionApprovedNotice(); + + $notice->handle($question, $sender); + + $this->eventsManager->fire('Question:afterApprove', $this, $question); + + } elseif ($type == 'reject') { + + $options = ReasonModel::questionRejectOptions(); + + if (array_key_exists($reason, $options)) { + $reason = $options[$reason]; + } + + $notice = new QuestionRejectedNotice(); + + $notice->handle($question, $sender, $reason); + + $this->eventsManager->fire('Question:afterReject', $this, $question); + } + + return $question; + } + + protected function findOrFail($id) + { + $validator = new QuestionValidator(); + + return $validator->checkQuestion($id); + } + + protected function handleQuestions($pager) + { + if ($pager->total_items > 0) { + + $builder = new QuestionListBuilder(); + + $items = $pager->items->toArray(); + + $pipeA = $builder->handleQuestions($items); + $pipeB = $builder->handleCategories($pipeA); + $pipeC = $builder->handleUsers($pipeB); + $pipeD = $builder->objects($pipeC); + + $pager->items = $pipeD; + } + + return $pager; + } + + protected function incrUserQuestionCount(UserModel $user) + { + $user->question_count += 1; + + $user->update(); + } + + protected function decrUserQuestionCount(UserModel $user) + { + if ($user->question_count > 0) { + $user->question_count -= 1; + $user->update(); + } + } + + protected function rebuildQuestionCache(QuestionModel $question) + { + $cache = new QuestionCache(); + + $cache->rebuild($question->id); + } + + protected function rebuildQuestionIndex(QuestionModel $question) + { + $sync = new QuestionIndexSync(); + + $sync->addItem($question->id); + } + + protected function handlePostPoint(QuestionModel $question) + { + if ($question->published != QuestionModel::PUBLISH_APPROVED) return; + + $service = new QuestionPostPointHistory(); + + $service->handle($question); + } + +} diff --git a/app/Http/Admin/Views/article/add.volt b/app/Http/Admin/Views/article/add.volt index 81e6d1e4..369bd8c8 100644 --- a/app/Http/Admin/Views/article/add.volt +++ b/app/Http/Admin/Views/article/add.volt @@ -6,17 +6,6 @@
添加文章
-
- -
- -
-
diff --git a/app/Http/Admin/Views/article/edit.volt b/app/Http/Admin/Views/article/edit.volt index 9cfa6e0b..179f055e 100644 --- a/app/Http/Admin/Views/article/edit.volt +++ b/app/Http/Admin/Views/article/edit.volt @@ -33,7 +33,6 @@ {{ js_include('https://cdn.jsdelivr.net/npm/vditor/dist/index.min.js', false) }} {{ js_include('lib/xm-select.js') }} - {{ js_include('admin/js/cover.upload.js') }} {{ js_include('admin/js/vditor.js') }} {% endblock %} diff --git a/app/Http/Admin/Views/article/edit_basic.volt b/app/Http/Admin/Views/article/edit_basic.volt index 8857b583..1889a8b1 100644 --- a/app/Http/Admin/Views/article/edit_basic.volt +++ b/app/Http/Admin/Views/article/edit_basic.volt @@ -1,33 +1,12 @@ {% set source_url_display = article.source_type == 1 ? 'display:none' : 'display:block' %}
-
- -
- - -
-
- -
-
-
- -
- -
-
@@ -50,14 +29,6 @@
-
- -
- {% for value,title in publish_types %} - - {% endfor %} -
-
diff --git a/app/Http/Admin/Views/article/edit_desc.volt b/app/Http/Admin/Views/article/edit_desc.volt index 70123beb..185096f2 100644 --- a/app/Http/Admin/Views/article/edit_desc.volt +++ b/app/Http/Admin/Views/article/edit_desc.volt @@ -1,20 +1,12 @@
- -
+
- -
- -
-
-
- -
+
diff --git a/app/Http/Admin/Views/article/list.volt b/app/Http/Admin/Views/article/list.volt index 95eab222..10cdcdb6 100644 --- a/app/Http/Admin/Views/article/list.volt +++ b/app/Http/Admin/Views/article/list.volt @@ -52,6 +52,7 @@ {% for item in pager.items %} {% set owner_url = url({'for':'home.user.show','id':item.owner.id}) %} {% set preview_url = url({'for':'home.article.show','id':item.id}) %} + {% set review_url = url({'for':'admin.article.review','id':item.id}) %} {% set edit_url = url({'for':'admin.article.edit','id':item.id}) %} {% set update_url = url({'for':'admin.article.update','id':item.id}) %} {% set delete_url = url({'for':'admin.article.delete','id':item.id}) %} @@ -85,7 +86,11 @@
    -
  • 预览文章
  • + {% if item.published == 1 %} +
  • 审核文章
  • + {% elseif item.published == 2 %} +
  • 预览文章
  • + {% endif %}
  • 编辑文章
  • {% if item.deleted == 0 %}
  • 删除文章
  • diff --git a/app/Http/Admin/Views/article/show.volt b/app/Http/Admin/Views/article/review.volt similarity index 72% rename from app/Http/Admin/Views/article/show.volt rename to app/Http/Admin/Views/article/review.volt index 106706e9..55ac9f67 100644 --- a/app/Http/Admin/Views/article/show.volt +++ b/app/Http/Admin/Views/article/review.volt @@ -2,24 +2,27 @@ {% block content %} - {% set list_url = url({'for':'admin.article.pending_list'}) %} +
    + 审核内容 +
    -
    - +
    +
    {{ article.title }}
    +
    {{ article.content }}
    + {% if article.tags %} +
    + {% for item in article.tags %} + {{ item['name'] }} + {% endfor %} +
    + {% endif %}
    - -
    - -
    -
    {{ article.content|parse_markdown }}
    -
    -
    +
    + 审核意见 +
    + +
    diff --git a/app/Http/Admin/Views/article/search.volt b/app/Http/Admin/Views/article/search.volt index 69b89d9f..a156ef3c 100644 --- a/app/Http/Admin/Views/article/search.volt +++ b/app/Http/Admin/Views/article/search.volt @@ -24,17 +24,6 @@
    -
    - -
    - -
    -
    diff --git a/app/Http/Admin/Views/audit/list.volt b/app/Http/Admin/Views/audit/list.volt index b1b739d7..b40d81be 100644 --- a/app/Http/Admin/Views/audit/list.volt +++ b/app/Http/Admin/Views/audit/list.volt @@ -40,20 +40,15 @@ {% for item in pager.items %} - {% set list_by_id_url = url({'for':'admin.audit.list'},{'user_id':item.user_id}) %} - {% set list_by_ip_url = url({'for':'admin.audit.list'},{'user_ip':item.user_ip}) %} - {% set list_by_route_url = url({'for':'admin.audit.list'},{'req_route':item.req_route}) %} - {% set list_by_path_url = url({'for':'admin.audit.list'},{'req_path':item.req_path}) %} {% set show_url = url({'for':'admin.audit.show','id':item.id}) %} {{ item.user_id }} - {{ item.user_name }} + {{ item.user_name }} - {{ item.user_ip }} - 位置 + {{ item.user_ip }} - {{ item.req_route }} - {{ item.req_path }} + {{ item.req_route }} + {{ item.req_path }} {{ date('Y-m-d H:i:s',item.create_time) }} diff --git a/app/Http/Admin/Views/macros/question.volt b/app/Http/Admin/Views/macros/question.volt new file mode 100644 index 00000000..6593e6ea --- /dev/null +++ b/app/Http/Admin/Views/macros/question.volt @@ -0,0 +1,18 @@ +{%- macro publish_status(type) %} + {% if type == 1 %} + 审核中 + {% elseif type == 2 %} + 已发布 + {% elseif type == 3 %} + 未通过 + {% else %} + 未知 + {% endif %} +{%- endmacro %} + +{%- macro tags_info(items) %} + {% for item in items %} + {% set comma = loop.last ? '' : ',' %} + {{ item.name ~ comma }} + {% endfor %} +{%- endmacro %} \ No newline at end of file diff --git a/app/Http/Admin/Views/moderation/articles.volt b/app/Http/Admin/Views/moderation/articles.volt index 87a3229b..63b3782a 100644 --- a/app/Http/Admin/Views/moderation/articles.volt +++ b/app/Http/Admin/Views/moderation/articles.volt @@ -2,6 +2,7 @@ {% block content %} + {{ partial('macros/common') }} {{ partial('macros/article') }}
    @@ -18,15 +19,13 @@ - 文章 作者 - 来源 - 评论 + 终端 时间 操作 @@ -34,14 +33,12 @@ {% for item in pager.items %} {% set owner_url = url({'for':'home.user.show','id':item.owner.id}) %} - {% set show_url = url({'for':'admin.article.show','id':item.id}) %} + {% set review_url = url({'for':'admin.article.review','id':item.id}) %} -

    标题:{{ item.title }}({{ item.id }})

    +

    标题:{{ item.title }}

    - {% if item.category.id is defined %} - 分类:{{ item.category.name }} - {% endif %} + 来源:{{ source_info(item.source_type,item.source_url) }} {% if item.tags %} 标签:{{ tags_info(item.tags) }} {% endif %} @@ -51,13 +48,9 @@

    昵称:{{ item.owner.name }}

    编号:{{ item.owner.id }}

    - {{ source_info(item.source_type,item.source_url) }} - {% if item.allow_comment == 1 %} - 开启 - {% else %} - 关闭 - {% endif %} +

    类型:{{ client_type(item.client_type) }}

    +

    地址:{{ item.client_ip }}

    {% if item.update_time > 0 %} @@ -67,7 +60,7 @@ {% endif %} - 详情 + 详情 {% endfor %} @@ -76,4 +69,10 @@ {{ partial('partials/pager') }} +{% endblock %} + +{% block include_js %} + + {{ js_include('admin/js/ip2region.js') }} + {% endblock %} \ No newline at end of file diff --git a/app/Http/Admin/Views/moderation/questions.volt b/app/Http/Admin/Views/moderation/questions.volt new file mode 100644 index 00000000..6e45647a --- /dev/null +++ b/app/Http/Admin/Views/moderation/questions.volt @@ -0,0 +1,77 @@ +{% extends 'templates/main.volt' %} + +{% block content %} + + {{ partial('macros/common') }} + {{ partial('macros/question') }} + +
    +
    + + 问题审核 + +
    +
    + + + + + + + + + + + + + + + + + + + + {% for item in pager.items %} + {% set owner_url = url({'for':'home.user.show','id':item.owner.id}) %} + {% set review_url = url({'for':'admin.question.review','id':item.id}) %} + + + + + + + + {% endfor %} + +
    问题作者终端时间操作
    +

    标题:{{ item.title }}

    +

    + {% if item.tags %} + 标签:{{ tags_info(item.tags) }} + {% endif %} +

    +
    +

    昵称:{{ item.owner.name }}

    +

    编号:{{ item.owner.id }}

    +
    +

    类型:{{ client_type(item.client_type) }}

    +

    地址:{{ item.client_ip }}

    +
    + {% if item.update_time > 0 %} + {{ date('Y-m-d H:i:s',item.update_time) }} + {% else %} + {{ date('Y-m-d H:i:s',item.create_time) }} + {% endif %} + + 详情 +
    + + {{ partial('partials/pager') }} + +{% endblock %} + +{% block include_js %} + + {{ js_include('admin/js/ip2region.js') }} + +{% endblock %} \ No newline at end of file diff --git a/app/Http/Admin/Views/question/add.volt b/app/Http/Admin/Views/question/add.volt new file mode 100644 index 00000000..e6cd2a19 --- /dev/null +++ b/app/Http/Admin/Views/question/add.volt @@ -0,0 +1,24 @@ +{% extends 'templates/main.volt' %} + +{% block content %} + + +
    + 添加问题 +
    +
    + +
    + +
    +
    +
    + +
    + + +
    +
    + + +{% endblock %} diff --git a/app/Http/Admin/Views/question/edit.volt b/app/Http/Admin/Views/question/edit.volt new file mode 100644 index 00000000..c99a7fc0 --- /dev/null +++ b/app/Http/Admin/Views/question/edit.volt @@ -0,0 +1,61 @@ +{% extends 'templates/main.volt' %} + +{% block content %} + +
    + 编辑问题 +
    + +
    +
      +
    • 基本信息
    • +
    • 内容详情
    • +
    +
    +
    + {{ partial('question/edit_basic') }} +
    +
    + {{ partial('question/edit_desc') }} +
    +
    +
    + +{% endblock %} + +{% block link_css %} + + {{ css_link('https://cdn.jsdelivr.net/npm/vditor/dist/index.css', false) }} + +{% endblock %} + +{% block include_js %} + + {{ js_include('https://cdn.jsdelivr.net/npm/vditor/dist/index.min.js', false) }} + {{ js_include('lib/xm-select.js') }} + {{ js_include('admin/js/vditor.js') }} + +{% endblock %} + +{% block inline_js %} + + + +{% endblock %} \ No newline at end of file diff --git a/app/Http/Admin/Views/question/edit_basic.volt b/app/Http/Admin/Views/question/edit_basic.volt new file mode 100644 index 00000000..e94340d9 --- /dev/null +++ b/app/Http/Admin/Views/question/edit_basic.volt @@ -0,0 +1,35 @@ +
    +
    + +
    + +
    +
    +
    + +
    +
    +
    +
    +
    + +
    + + +
    +
    +
    + +
    + + +
    +
    +
    + +
    + + +
    +
    +
    \ No newline at end of file diff --git a/app/Http/Admin/Views/question/edit_desc.volt b/app/Http/Admin/Views/question/edit_desc.volt new file mode 100644 index 00000000..b355bd47 --- /dev/null +++ b/app/Http/Admin/Views/question/edit_desc.volt @@ -0,0 +1,14 @@ +
    +
    +
    +
    + +
    +
    +
    +
    + + +
    +
    +
    \ No newline at end of file diff --git a/app/Http/Admin/Views/question/list.volt b/app/Http/Admin/Views/question/list.volt new file mode 100644 index 00000000..7d8d209a --- /dev/null +++ b/app/Http/Admin/Views/question/list.volt @@ -0,0 +1,160 @@ +{% extends 'templates/main.volt' %} + +{% block content %} + + {{ partial('macros/question') }} + + {% set add_url = url({'for':'admin.question.add'}) %} + {% set search_url = url({'for':'admin.question.search'}) %} + + + + + + + + + + + + + + + + + + + + + + + + + + + + {% for item in pager.items %} + {% set owner_url = url({'for':'home.user.show','id':item.owner.id}) %} + {% set preview_url = url({'for':'home.question.show','id':item.id}) %} + {% set review_url = url({'for':'admin.question.review','id':item.id}) %} + {% set edit_url = url({'for':'admin.question.edit','id':item.id}) %} + {% set update_url = url({'for':'admin.question.update','id':item.id}) %} + {% set delete_url = url({'for':'admin.question.delete','id':item.id}) %} + {% set restore_url = url({'for':'admin.question.restore','id':item.id}) %} + {% set answer_url = url({'for':'admin.answer.list'},{'item_id':item.id}) %} + + + + + + + + + + + {% endfor %} + +
    问题状态浏览回答点赞收藏讨论操作
    +

    标题:{{ item.title }}({{ item.id }})

    +

    + {% if item.category.id is defined %} + 分类:{{ item.category.name }} + {% endif %} + {% if item.tags %} + 标签:{{ tags_info(item.tags) }} + {% endif %} +

    +

    + 作者:{{ item.owner.name }} + 创建:{{ date('Y-m-d',item.create_time) }} +

    +
    {{ publish_status(item.published) }}{{ item.view_count }}{{ item.answer_count }}{{ item.like_count }}{{ item.favorite_count }} +
    + + +
    +
    + + {{ partial('partials/pager') }} + +{% endblock %} + +{% block inline_js %} + + + +{% endblock %} \ No newline at end of file diff --git a/app/Http/Admin/Views/question/review.volt b/app/Http/Admin/Views/question/review.volt new file mode 100644 index 00000000..8ad1193b --- /dev/null +++ b/app/Http/Admin/Views/question/review.volt @@ -0,0 +1,85 @@ +{% extends 'templates/main.volt' %} + +{% block content %} + +
    + 审核内容 +
    + +
    +
    {{ question.title }}
    +
    {{ question.content }}
    + {% if question.tags %} +
    + {% for item in question.tags %} + {{ item['name'] }} + {% endfor %} +
    + {% endif %} +
    + +
    + 审核意见 +
    + +
    +
    + +
    + + +
    +
    + +
    + +
    + + +
    +
    +
    + +{% endblock %} + +{% block link_css %} + + {{ css_link('home/css/markdown.css') }} + +{% endblock %} + +{% block inline_js %} + + + +{% endblock %} \ No newline at end of file diff --git a/app/Http/Admin/Views/question/search.volt b/app/Http/Admin/Views/question/search.volt new file mode 100644 index 00000000..75e6aca3 --- /dev/null +++ b/app/Http/Admin/Views/question/search.volt @@ -0,0 +1,93 @@ +{% extends 'templates/main.volt' %} + +{% block content %} + +
    +
    + 搜索问题 +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    +
    +
    +
    +
    + +
    + {% for value,title in publish_types %} + + {% endfor %} +
    +
    +
    + +
    + + +
    +
    +
    + +
    + + +
    +
    +
    + +
    + + +
    +
    +
    + +
    + + +
    +
    +
    + +{% endblock %} + +{% block include_js %} + + {{ js_include('lib/xm-select.js') }} + +{% endblock %} + +{% block inline_js %} + + + +{% endblock %} \ No newline at end of file diff --git a/app/Http/Admin/Views/setting/storage.volt b/app/Http/Admin/Views/setting/storage.volt index 93d58fee..e0f7b07d 100644 --- a/app/Http/Admin/Views/setting/storage.volt +++ b/app/Http/Admin/Views/setting/storage.volt @@ -121,10 +121,6 @@ 群组头像 public/static/admin/img/default/group_cover.png - - 文章封面 - public/static/admin/img/default/article_cover.png - 课程封面 public/static/admin/img/default/course_cover.png diff --git a/app/Http/Home/Controllers/AnswerController.php b/app/Http/Home/Controllers/AnswerController.php index 628a867a..df126771 100644 --- a/app/Http/Home/Controllers/AnswerController.php +++ b/app/Http/Home/Controllers/AnswerController.php @@ -3,6 +3,7 @@ namespace App\Http\Home\Controllers; use App\Http\Home\Services\Answer as AnswerService; +use App\Http\Home\Services\Question as QuestionService; use App\Services\Logic\Answer\AnswerAccept as AnswerAcceptService; use App\Services\Logic\Answer\AnswerCreate as AnswerCreateService; use App\Services\Logic\Answer\AnswerDelete as AnswerDeleteService; @@ -21,7 +22,13 @@ class AnswerController extends Controller */ public function addAction() { + $id = $this->request->getQuery('question_id', 'int', 0); + $service = new QuestionService(); + + $question = $service->getQuestion($id); + + $this->view->setVar('question', $question); } /** @@ -33,6 +40,11 @@ class AnswerController extends Controller $answer = $service->getAnswer($id); + $service = new QuestionService(); + + $question = $service->getQuestion($answer->question_id); + + $this->view->setVar('question', $question); $this->view->setVar('answer', $answer); } @@ -55,9 +67,17 @@ class AnswerController extends Controller { $service = new AnswerCreateService(); - $service->handle(); + $answer = $service->handle(); - $content = ['msg' => '创建答案成功']; + $location = $this->url->get([ + 'for' => 'home.question.show', + 'id' => $answer->question_id, + ]); + + $content = [ + 'location' => $location, + 'msg' => '创建回答成功', + ]; return $this->jsonSuccess($content); } @@ -69,9 +89,17 @@ class AnswerController extends Controller { $service = new AnswerUpdateService(); - $service->handle($id); + $answer = $service->handle($id); - $content = ['msg' => '更新答案成功']; + $location = $this->url->get([ + 'for' => 'home.question.show', + 'id' => $answer->question_id, + ]); + + $content = [ + 'location' => $location, + 'msg' => '更新回答成功', + ]; return $this->jsonSuccess($content); } @@ -83,13 +111,16 @@ class AnswerController extends Controller { $service = new AnswerDeleteService(); - $service->handle($id); + $answer = $service->handle($id); - $location = $this->url->get(['for' => 'home.uc.answers']); + $location = $this->url->get([ + 'for' => 'home.question.show', + 'id' => $answer->question_id, + ]); $content = [ 'location' => $location, - 'msg' => '删除答案成功', + 'msg' => '删除回答成功', ]; return $this->jsonSuccess($content); @@ -123,4 +154,12 @@ class AnswerController extends Controller return $this->jsonSuccess(['data' => $data, 'msg' => $msg]); } + /** + * @Route("/{id:[0-9]+}/report", name="home.answer.report") + */ + public function reportAction($id) + { + + } + } diff --git a/app/Http/Home/Controllers/ArticleController.php b/app/Http/Home/Controllers/ArticleController.php index 21021b08..36adbfd8 100644 --- a/app/Http/Home/Controllers/ArticleController.php +++ b/app/Http/Home/Controllers/ArticleController.php @@ -4,10 +4,13 @@ namespace App\Http\Home\Controllers; use App\Http\Home\Services\Article as ArticleService; use App\Http\Home\Services\ArticleQuery as ArticleQueryService; +use App\Services\Logic\Article\ArticleCreate as ArticleCreateService; +use App\Services\Logic\Article\ArticleDelete as ArticleDeleteService; use App\Services\Logic\Article\ArticleFavorite as ArticleFavoriteService; use App\Services\Logic\Article\ArticleInfo as ArticleInfoService; use App\Services\Logic\Article\ArticleLike as ArticleLikeService; use App\Services\Logic\Article\ArticleList as ArticleListService; +use App\Services\Logic\Article\ArticleUpdate as ArticleUpdateService; use App\Services\Logic\Article\HotAuthorList as HotAuthorListService; use App\Services\Logic\Article\RelatedArticleList as RelatedArticleListService; use Phalcon\Mvc\View; @@ -122,6 +125,7 @@ class ArticleController extends Controller } $this->seo->prependTitle($article['title']); + $this->seo->setDescription($article['summary']); $this->view->setVar('article', $article); } @@ -144,9 +148,9 @@ class ArticleController extends Controller */ public function createAction() { - $service = new ArticleService(); + $service = new ArticleCreateService(); - $service->createArticle(); + $service->handle(); $location = $this->url->get(['for' => 'home.uc.articles']); @@ -163,9 +167,9 @@ class ArticleController extends Controller */ public function updateAction($id) { - $service = new ArticleService(); + $service = new ArticleUpdateService(); - $service->updateArticle($id); + $service->handle($id); $location = $this->url->get(['for' => 'home.uc.articles']); @@ -182,9 +186,9 @@ class ArticleController extends Controller */ public function deleteAction($id) { - $service = new ArticleService(); + $service = new ArticleDeleteService(); - $service->deleteArticle($id); + $service->handle($id); $location = $this->url->get(['for' => 'home.uc.articles']); @@ -224,4 +228,12 @@ class ArticleController extends Controller return $this->jsonSuccess(['data' => $data, 'msg' => $msg]); } + /** + * @Route("/{id:[0-9]+}/report", name="home.article.report") + */ + public function reportAction($id) + { + + } + } diff --git a/app/Http/Home/Controllers/CommentController.php b/app/Http/Home/Controllers/CommentController.php index 934c10f4..9448e362 100644 --- a/app/Http/Home/Controllers/CommentController.php +++ b/app/Http/Home/Controllers/CommentController.php @@ -121,4 +121,12 @@ class CommentController extends Controller return $this->jsonSuccess(['msg' => '删除评论成功']); } + /** + * @Route("/{id:[0-9]+}/report", name="home.comment.report") + */ + public function reportAction($id) + { + + } + } diff --git a/app/Http/Home/Controllers/QuestionController.php b/app/Http/Home/Controllers/QuestionController.php index 57c8d207..a691a5ea 100644 --- a/app/Http/Home/Controllers/QuestionController.php +++ b/app/Http/Home/Controllers/QuestionController.php @@ -127,6 +127,7 @@ class QuestionController extends Controller $question = $service->handle($id); $this->seo->prependTitle($question['title']); + $this->seo->setDescription($question['summary']); $this->view->setVar('question', $question); } @@ -249,4 +250,12 @@ class QuestionController extends Controller return $this->jsonSuccess(['data' => $data, 'msg' => $msg]); } + /** + * @Route("/{id:[0-9]+}/report", name="home.question.report") + */ + public function reportAction($id) + { + + } + } diff --git a/app/Http/Home/Services/Article.php b/app/Http/Home/Services/Article.php index ac172aa2..420de389 100644 --- a/app/Http/Home/Services/Article.php +++ b/app/Http/Home/Services/Article.php @@ -2,143 +2,76 @@ namespace App\Http\Home\Services; -use App\Http\Admin\Services\Article as ArticleService; -use App\Library\Utils\Word as WordUtil; use App\Models\Article as ArticleModel; -use App\Traits\Client as ClientTrait; -use App\Validators\Article as ArticleValidator; +use App\Models\Category as CategoryModel; +use App\Repos\Category as CategoryRepo; +use App\Repos\Tag as TagRepo; +use App\Services\Logic\ArticleTrait; -class Article extends ArticleService +class Article extends Service { - use ClientTrait; + use ArticleTrait; - public function createArticle() + public function getArticleModel() { - $post = $this->request->getPost(); - - $user = $this->getLoginUser(); - $article = new ArticleModel(); - $data = $this->handlePostData($post); - - $data['client_type'] = $this->getClientType(); - $data['client_ip'] = $this->getClientIp(); - $data['owner_id'] = $user->id; - - $article->create($data); - - if (isset($post['xm_tag_ids'])) { - $this->saveTags($article, $post['xm_tag_ids']); - } - - $this->incrUserArticleCount($user); - - $this->eventsManager->fire('Article:afterCreate', $this, $article); + $article->afterFetch(); return $article; } - public function updateArticle($id) + public function getXmTags($id) { - $post = $this->request->getPost(); + $tagRepo = new TagRepo(); - $article = $this->findOrFail($id); + $allTags = $tagRepo->findAll(['published' => 1], 'priority'); - $data = $this->handlePostData($post); + if ($allTags->count() == 0) return []; - $data['client_type'] = $this->getClientType(); - $data['client_ip'] = $this->getClientIp(); + $articleTagIds = []; - if ($article->published == ArticleModel::PUBLISH_REJECTED) { - $data['published'] = ArticleModel::PUBLISH_PENDING; - } - - /** - * 当通过审核后,禁止修改部分文章属性 - */ - if ($article->published == ArticleModel::PUBLISH_APPROVED) { - unset( - $data['title'], - $data['content'], - $data['cover'], - $data['source_type'], - $data['source_url'], - $data['category_id'], - $post['xm_tag_ids'], - ); - } - - $article->update($data); - - if (isset($post['xm_tag_ids'])) { - $this->saveTags($article, $post['xm_tag_ids']); - } - - $this->eventsManager->fire('Article:afterUpdate', $this, $article); - - return $article; - } - - public function deleteArticle($id) - { - $article = $this->findOrFail($id); - - $user = $this->getLoginUser(); - - $validator = new ArticleValidator(); - - $validator->checkOwner($user->id, $article->owner_id); - - $article->deleted = 1; - - $article->update(); - - $this->decrUserArticleCount($user); - - $this->rebuildArticleIndex($article); - - $this->eventsManager->fire('Article:afterDelete', $this, $article); - - return $article; - } - - protected function handlePostData($post) - { - $data = []; - - $validator = new ArticleValidator(); - - $data['title'] = $validator->checkTitle($post['title']); - $data['content'] = $validator->checkContent($post['content']); - $data['word_count'] = WordUtil::getWordCount($data['content']); - - if (isset($post['category_id'])) { - $category = $validator->checkCategory($post['category_id']); - $data['category_id'] = $category->id; - } - - if (isset($post['cover'])) { - $data['cover'] = $validator->checkCover($post['cover']); - } - - if (isset($post['source_type'])) { - $data['source_type'] = $validator->checkSourceType($post['source_type']); - if ($post['source_type'] != ArticleModel::SOURCE_ORIGIN) { - $data['source_url'] = $validator->checkSourceUrl($post['source_url']); + if ($id > 0) { + $article = $this->checkArticle($id); + if (!empty($article->tags)) { + $articleTagIds = kg_array_column($article->tags, 'id'); } } - if (isset($post['allow_comment'])) { - $data['allow_comment'] = $validator->checkAllowCommentStatus($post['allow_comment']); + $list = []; + + foreach ($allTags as $tag) { + $selected = in_array($tag->id, $articleTagIds); + $list[] = [ + 'name' => $tag->name, + 'value' => $tag->id, + 'selected' => $selected, + ]; } - if (isset($post['private'])) { - $data['private'] = $validator->checkPrivateStatus($post['private']); - } + return $list; + } - return $data; + public function getCategories() + { + $categoryRepo = new CategoryRepo(); + + return $categoryRepo->findAll([ + 'type' => CategoryModel::TYPE_ARTICLE, + 'level' => 1, + 'published' => 1, + ]); + } + + public function getSourceTypes() + { + return ArticleModel::sourceTypes(); + } + + public function getArticle($id) + { + return $this->checkArticle($id); } } diff --git a/app/Http/Home/Services/Question.php b/app/Http/Home/Services/Question.php index 800432d6..c8af59cc 100644 --- a/app/Http/Home/Services/Question.php +++ b/app/Http/Home/Services/Question.php @@ -2,7 +2,9 @@ namespace App\Http\Home\Services; +use App\Models\Category as CategoryModel; use App\Models\Question as QuestionModel; +use App\Repos\Category as CategoryRepo; use App\Repos\Tag as TagRepo; use App\Services\Logic\QuestionTrait; use App\Services\Logic\Service as LogicService; @@ -21,9 +23,15 @@ class Question extends LogicService return $question; } - public function getQuestion($id) + public function getCategories() { - return $this->checkQuestion($id); + $categoryRepo = new CategoryRepo(); + + return $categoryRepo->findAll([ + 'type' => CategoryModel::TYPE_ARTICLE, + 'level' => 1, + 'published' => 1, + ]); } public function getXmTags($id) @@ -57,5 +65,9 @@ class Question extends LogicService return $list; } + public function getQuestion($id) + { + return $this->checkQuestion($id); + } } diff --git a/app/Http/Home/Views/answer/add.volt b/app/Http/Home/Views/answer/add.volt index 6fd31eab..0a7bc5c9 100644 --- a/app/Http/Home/Views/answer/add.volt +++ b/app/Http/Home/Views/answer/add.volt @@ -1,21 +1,37 @@ -{% extends 'templates/layer.volt' %} +{% extends 'templates/main.volt' %} {% block content %} -
    -
    -
    -
    - -
    + + +
    +
    + +
    +
    + +
    +
    +
    +
    +
    + +
    +
    +
    +
    + + +
    +
    +
    -
    -
    - - -
    -
    - +
    {% endblock %} diff --git a/app/Http/Home/Views/answer/edit.volt b/app/Http/Home/Views/answer/edit.volt index f447c9de..bbed9ada 100644 --- a/app/Http/Home/Views/answer/edit.volt +++ b/app/Http/Home/Views/answer/edit.volt @@ -1,20 +1,36 @@ -{% extends 'templates/layer.volt' %} +{% extends 'templates/main.volt' %} {% block content %} -
    -
    -
    -
    - -
    + + +
    +
    + +
    +
    + +
    +
    +
    +
    +
    + +
    +
    +
    +
    + +
    +
    +
    -
    -
    - -
    -
    - +
    {% endblock %} diff --git a/app/Http/Home/Views/article/show.volt b/app/Http/Home/Views/article/show.volt index 25f555fd..b152742d 100644 --- a/app/Http/Home/Views/article/show.volt +++ b/app/Http/Home/Views/article/show.volt @@ -5,8 +5,11 @@ {{ partial('macros/article') }} {% set article_list_url = url({'for':'home.article.list'}) %} - {% set related_url = url({'for':'home.article.related','id':article.id}) %} - {% set owner_url = url({'for':'home.user.show','id':article.owner.id}) %} + {% set article_report_url = url({'for':'home.article.report','id':article.id}) %} + {% set article_edit_url = url({'for':'home.article.edit','id':article.id}) %} + {% set article_delete_url = url({'for':'home.article.delete','id':article.id}) %} + {% set article_owner_url = url({'for':'home.user.show','id':article.owner.id}) %} + {% set article_related_url = url({'for':'home.article.related','id':article.id}) %}
    - +
    diff --git a/app/Http/Home/Views/article/sticky.volt b/app/Http/Home/Views/article/sticky.volt index 50be63ab..5e3cef4f 100644 --- a/app/Http/Home/Views/article/sticky.volt +++ b/app/Http/Home/Views/article/sticky.volt @@ -20,7 +20,7 @@
    - +
    {{ article.favorite_count }}
    diff --git a/app/Http/Home/Views/comment/list.volt b/app/Http/Home/Views/comment/list.volt index ab6dee7c..f4c86d44 100644 --- a/app/Http/Home/Views/comment/list.volt +++ b/app/Http/Home/Views/comment/list.volt @@ -1,6 +1,7 @@ {% if pager.total_pages > 0 %} {% for item in pager.items %} {% set owner_url = url({'for':'home.user.show','id':item.owner.id}) %} + {% set report_url = url({'for':'home.comment.report','id':item.id}) %} {% set like_url = url({'for':'home.comment.like','id':item.id}) %} {% set delete_url = url({'for':'home.comment.delete','id':item.id}) %} {% set reply_url = url({'for':'home.comment.reply','id':item.id}) %} @@ -25,26 +26,26 @@
    {% if item.me.liked == 1 %} - 已赞 + 已赞 {% else %} - 点赞 + 点赞 {% endif %}
    {{ item.reply_count }} - 回应 + 回应
    - 回复 + 回复
    - 举报 + 举报
    {% if item.owner.id == auth_user.id %}
    - 删除 + 删除
    {% endif %}
    diff --git a/app/Http/Home/Views/comment/replies.volt b/app/Http/Home/Views/comment/replies.volt index fb97af36..8d14e8cd 100644 --- a/app/Http/Home/Views/comment/replies.volt +++ b/app/Http/Home/Views/comment/replies.volt @@ -3,7 +3,7 @@ {% set owner_url = url({'for':'home.user.show','id':item.owner.id}) %} {% set like_url = url({'for':'home.comment.like','id':item.id}) %} {% set delete_url = url({'for':'home.comment.delete','id':item.id}) %} - {% set reply_create_url = url({'for':'home.comment.create_reply','id':item.id}) %} + {% set reply_url = url({'for':'home.comment.reply','id':item.id}) %}
    @@ -29,22 +29,22 @@
    {% if item.me.liked == 1 %} - 已赞 + 已赞 {% else %} - 点赞 + 点赞 {% endif %}
    - 回复 + 回复
    - 举报 + 举报
    {% if item.owner.id == auth_user.id %}
    - 删除 + 删除
    {% endif %}
    @@ -52,7 +52,7 @@