1
0
mirror of https://gitee.com/koogua/course-tencent-cloud.git synced 2025-06-25 04:07:17 +08:00

Merge branch 'koogua/v1.4.4' into develop

This commit is contained in:
koogua 2021-09-17 10:26:05 +08:00
commit 61b4e7696e
67 changed files with 745 additions and 372 deletions

View File

@ -1,3 +1,30 @@
### [v1.4.4](https://gitee.com/koogua/course-tencent-cloud/releases/v1.4.4)(2021-09-17)
- 后台增加邮件手机登录选择配置
- 增加移动端支付选项配置
- 首页增加秒杀,直播,提问,文章接口
- 增加秒杀列表列表接口
- 调整markdown解析安全级别
- 精简取消点赞以及取消收藏逻辑
- 修复浮点转整型精度丢失造成的支付回调失败
- 修复竖屏直播时造成的位置错乱
- 修复视频清晰度配置序列化问题
- 修复评论取消点赞数量不变问题
- 修复章节资源数量问题
- 修复删除课程后引发的用户课程列表错误问题
- 修正课程咨询列表查询条件
- 修正回答,兑换礼品说明重复转译的问题
- 资源下载查询主键由md5改为加密的ID
- 去除上传文件md5唯一索引
- 去除课程发布对章节的要求
- 去除点播回调中的处理数量限制
- 优化文章,课程,提问,群组全文搜索
- 优化直播列表数据结构
- 优化章节目录交互呈现
- 优化后台添加学员重复检查
- 优化订单发货逻辑
- 优化公众号订阅逻辑
### [v1.4.3](https://gitee.com/koogua/course-tencent-cloud/releases/v1.4.3)(2021-08-23)
- 优化邮件验证码

View File

@ -55,12 +55,10 @@ class CourseUserList extends Builder
$result = [];
foreach ($courses->toArray() as $course) {
if ($course['deleted'] == 0) {
$course['cover'] = $baseUrl . $course['cover'];
$course['attrs'] = json_decode($course['attrs'], true);
$result[$course['id']] = $course;
}
}
return $result;
}

View File

@ -36,6 +36,11 @@ class ResourceList extends Builder
$result = [];
foreach ($uploads->toArray() as $upload) {
$id = $this->crypt->encryptBase64($upload['id'], null, true);
$upload['url'] = $this->url->get(['for' => 'home.download', 'id' => $id]);
$result[$upload['id']] = $upload;
}

View File

@ -0,0 +1,44 @@
<?php
/**
* @copyright Copyright (c) 2021 深圳市酷瓜软件有限公司
* @license https://opensource.org/licenses/GPL-2.0
* @link https://www.koogua.com
*/
namespace App\Caches;
use App\Models\Article as ArticleModel;
use App\Repos\Article as ArticleRepo;
use App\Services\Logic\Article\ArticleList as ArticleListService;
class IndexArticleList extends Cache
{
protected $lifetime = 1 * 86400;
public function getLifetime()
{
return $this->lifetime;
}
public function getKey($id = null)
{
return 'index_article_list';
}
public function getContent($id = null)
{
$articleRepo = new ArticleRepo();
$where = ['published' => ArticleModel::PUBLISH_APPROVED];
$pager = $articleRepo->paginate($where, 'latest', 1, 10);
$service = new ArticleListService();
$pager = $service->handleArticles($pager);
return $pager->items ?: [];
}
}

View File

@ -7,14 +7,14 @@
namespace App\Caches;
use App\Models\Chapter as ChapterModel;
use App\Models\ChapterLive as ChapterLiveModel;
use App\Repos\Chapter as ChapterRepo;
use App\Repos\Course as CourseRepo;
use App\Repos\User as UserRepo;
use Phalcon\Mvc\Model\Resultset;
use Phalcon\Mvc\Model\ResultsetInterface;
/**
* 直播课程
*/
class IndexLiveList extends Cache
{
@ -32,32 +32,11 @@ class IndexLiveList extends Cache
public function getContent($id = null)
{
/**
* 限制输出多少天数(一维限额)
*/
$dayLimit = 3;
$limit = 8;
/**
* 限制每天维度下的输出数(二维限额)
*/
$perDayLimit = 10;
$lives = $this->findChapterLives();
$beginTime = strtotime('today');
$endTime = strtotime("+30 days");
/**
* @var Resultset|ChapterLiveModel[] $lives
*/
$lives = ChapterLiveModel::query()
->betweenWhere('start_time', $beginTime, $endTime)
->orderBy('start_time ASC')
->execute();
if ($lives->count() == 0) {
return [];
}
$result = [];
if ($lives->count() == 0) return [];
$chapterIds = kg_array_column($lives->toArray(), 'chapter_id');
@ -77,53 +56,85 @@ class IndexLiveList extends Cache
$courses = $courseRepo->findByIds($courseIds);
$teacherIds = kg_array_column($courses->toArray(), 'teacher_id');
$userRepo = new UserRepo();
$users = $userRepo->findByIds($teacherIds);
$courseMapping = [];
foreach ($courses as $course) {
$courseMapping[$course->id] = $course;
}
$userMapping = [];
foreach ($users as $user) {
$userMapping[$user->id] = $user;
}
$result = [];
$flag = [];
foreach ($lives as $live) {
if (count($result) >= $dayLimit) {
break;
}
$day = date('y-m-d', $live->start_time);
if (isset($result[$day]) && count($result[$day]) >= $perDayLimit) {
continue;
}
$chapter = $chapterMapping[$live->chapter_id];
$course = $courseMapping[$chapter->course_id];
$teacher = $userMapping[$course->teacher_id];
$teacherInfo = [
'id' => $teacher->id,
'name' => $teacher->name,
'title' => $teacher->title,
'avatar' => $teacher->avatar,
];
$chapterInfo = [
'id' => $chapter->id,
'title' => $chapter->title,
'start_time' => $live->start_time,
'end_time' => $live->end_time,
];
$courseInfo = [
'id' => $course->id,
'title' => $course->title,
'cover' => $course->cover,
'market_price' => $course->market_price,
'vip_price' => $course->vip_price,
'model' => $course->model,
'level' => $course->level,
'user_count' => $course->user_count,
'lesson_count' => $course->lesson_count,
'teacher' => $teacherInfo,
];
$result[$day][] = [
if (!isset($flag[$course->id]) && count($flag) < $limit) {
$flag[$course->id] = 1;
$result[] = [
'id' => $live->id,
'status' => $live->status,
'start_time' => $live->start_time,
'end_time' => $live->end_time,
'course' => $courseInfo,
'chapter' => $chapterInfo,
];
}
}
return $result;
}
/**
* @return ResultsetInterface|Resultset|ChapterLiveModel[]
*/
protected function findChapterLives()
{
$startTime = strtotime('today');
$endTime = strtotime('+30 days');
return $this->modelsManager->createBuilder()
->columns('cl.*')
->addFrom(ChapterLiveModel::class, 'cl')
->join(ChapterModel::class, 'cl.chapter_id = c.id', 'c')
->betweenWhere('start_time', $startTime, $endTime)
->orderBy('start_time ASC')
->getQuery()
->execute();
}
}

View File

@ -0,0 +1,44 @@
<?php
/**
* @copyright Copyright (c) 2021 深圳市酷瓜软件有限公司
* @license https://opensource.org/licenses/GPL-2.0
* @link https://www.koogua.com
*/
namespace App\Caches;
use App\Models\Question as QuestionModel;
use App\Repos\Question as QuestionRepo;
use App\Services\Logic\Question\QuestionList as QuestionListService;
class IndexQuestionList extends Cache
{
protected $lifetime = 1 * 86400;
public function getLifetime()
{
return $this->lifetime;
}
public function getKey($id = null)
{
return 'index_question_list';
}
public function getContent($id = null)
{
$questionRepo = new QuestionRepo();
$where = ['published' => QuestionModel::PUBLISH_APPROVED];
$pager = $questionRepo->paginate($where, 'latest', 1, 10);
$service = new QuestionListService();
$pager = $service->handleQuestions($pager);
return $pager->items ?: [];
}
}

View File

@ -66,6 +66,7 @@ class IndexSimpleVipCourseList extends Cache
{
return CourseModel::query()
->where('published = 1')
->andWhere('market_price > vip_price')
->andWhere('vip_price >= 0')
->orderBy('score DESC')
->limit($limit)

View File

@ -114,6 +114,7 @@ class IndexVipCourseList extends Cache
return CourseModel::query()
->inWhere('category_id', $categoryIds)
->andWhere('published = 1')
->andWhere('market_price > vip_price')
->andWhere('vip_price >= 0')
->orderBy('score DESC')
->limit($limit)

View File

@ -17,6 +17,7 @@ use App\Models\Trade as TradeModel;
use App\Repos\Course as CourseRepo;
use App\Repos\ImGroup as ImGroupRepo;
use App\Repos\ImGroupUser as ImGroupUserRepo;
use App\Repos\ImUser as ImUserRepo;
use App\Repos\Order as OrderRepo;
use App\Repos\User as UserRepo;
use App\Services\Logic\Notice\OrderFinish as OrderFinishNotice;
@ -134,9 +135,7 @@ class DeliverTask extends Task
$courseUser->role_type = CourseUserModel::ROLE_STUDENT;
$courseUser->source_type = CourseUserModel::SOURCE_CHARGE;
if ($courseUser->create() === false) {
throw new \RuntimeException('Create Course User Failed');
}
$courseUser->create();
$courseRepo = new CourseRepo();
@ -150,6 +149,10 @@ class DeliverTask extends Task
$group = $groupRepo->findByCourseId($course->id);
$imUserRepo = new ImUserRepo();
$imUser = $imUserRepo->findById($order->owner_id);
$groupUserRepo = new ImGroupUserRepo();
$groupUser = $groupUserRepo->findGroupUser($group->id, $order->owner_id);
@ -160,10 +163,13 @@ class DeliverTask extends Task
$groupUser->group_id = $group->id;
$groupUser->user_id = $order->owner_id;
$groupUser->create();
if ($groupUser->create() === false) {
throw new \RuntimeException('Create Group User Failed');
}
$imUser->group_count += 1;
$imUser->update();
$group->user_count += 1;
$group->update();
}
}
@ -181,9 +187,7 @@ class DeliverTask extends Task
$courseUser->role_type = CourseUserModel::ROLE_STUDENT;
$courseUser->source_type = CourseUserModel::SOURCE_CHARGE;
if ($courseUser->create() === false) {
throw new \RuntimeException('Create Course User Failed');
}
$courseUser->create();
$courseRepo = new CourseRepo();
@ -197,6 +201,10 @@ class DeliverTask extends Task
$group = $groupRepo->findByCourseId($course->id);
$imUserRepo = new ImUserRepo();
$imUser = $imUserRepo->findById($order->owner_id);
$groupUserRepo = new ImGroupUserRepo();
$groupUser = $groupUserRepo->findGroupUser($group->id, $order->owner_id);
@ -207,10 +215,13 @@ class DeliverTask extends Task
$groupUser->group_id = $group->id;
$groupUser->user_id = $order->owner_id;
$groupUser->create();
if ($groupUser->create() === false) {
throw new \RuntimeException('Create Group User Failed');
}
$imUser->group_count += 1;
$imUser->update();
$group->user_count += 1;
$group->update();
}
}
}
@ -224,11 +235,10 @@ class DeliverTask extends Task
$user = $userRepo->findById($order->owner_id);
$user->vip_expiry_time = $itemInfo['vip']['expiry_time'];
$user->vip = 1;
if ($user->update() === false) {
throw new \RuntimeException('Update Vip Expiry Failed');
}
$user->update();
}
protected function handleOrderConsumePoint(OrderModel $order)

View File

@ -23,8 +23,6 @@ class VodEventTask extends Task
$handles = [];
$count = 0;
foreach ($events as $event) {
$handles[] = $event['EventHandle'];
@ -36,10 +34,6 @@ class VodEventTask extends Task
} elseif ($event['EventType'] == 'FileDeleted') {
$this->handleFileDeletedEvent($event);
}
$count++;
if ($count >= 12) break;
}
$this->confirmEvents($handles);

View File

@ -350,10 +350,12 @@ class SettingController extends Controller
$qqAuth = $settingService->getQQAuthSettings();
$weixinAuth = $settingService->getWeixinAuthSettings();
$weiboAuth = $settingService->getWeiboAuthSettings();
$localAuth = $settingService->getLocalAuthSettings();
$this->view->setVar('qq_auth', $qqAuth);
$this->view->setVar('weixin_auth', $weixinAuth);
$this->view->setVar('weibo_auth', $weiboAuth);
$this->view->setVar('local_auth', $localAuth);
}
}

View File

@ -1205,7 +1205,7 @@ class AuthNode extends Service
],
[
'id' => '5-1-12',
'title' => '开放登录',
'title' => '登录设置',
'type' => 'menu',
'route' => 'admin.setting.oauth',
],

View File

@ -124,6 +124,20 @@ class Resource extends Service
$chapter->resource_count = $chapterRepo->countResources($chapter->id);
$chapter->update();
$parent = $chapterRepo->findById($chapter->parent_id);
$lessons = $chapterRepo->findLessons($parent->id);
$resourceCount = 0;
foreach ($lessons as $lesson) {
$resourceCount += $chapterRepo->countResources($lesson->id);
}
$parent->resource_count = $resourceCount;
$parent->update();
}
protected function recountCourseResources(CourseModel $course)

View File

@ -15,6 +15,11 @@ use App\Services\WeChat as WeChatService;
class Setting extends Service
{
public function getLocalAuthSettings()
{
return $this->getSettings('oauth.local');
}
public function getQQAuthSettings()
{
$oauth = $this->getSettings('oauth.qq');

View File

@ -103,7 +103,7 @@ class Student extends Service
$data['user_id'] = $user->id;
$data['expiry_time'] = $expiryTime;
$validator->checkIfJoined($post['course_id'], $post['user_id']);
$validator->checkIfImported($post['course_id'], $post['user_id']);
$courseUser = new CourseUserModel();

View File

@ -4,12 +4,16 @@
<div class="layui-tab layui-tab-brief">
<ul class="layui-tab-title kg-tab-title">
<li class="layui-this">QQ登录</li>
<li class="layui-this">本地登录</li>
<li>QQ登录</li>
<li>微信登录</li>
<li>新浪微博</li>
<li>微博登录</li>
</ul>
<div class="layui-tab-content">
<div class="layui-tab-item layui-show">
{{ partial('setting/oauth_local') }}
</div>
<div class="layui-tab-item">
{{ partial('setting/oauth_qq') }}
</div>
<div class="layui-tab-item">

View File

@ -0,0 +1,24 @@
<form class="layui-form kg-form" method="POST" action="{{ url({'for':'admin.setting.oauth'}) }}">
<div class="layui-form-item">
<label class="layui-form-label">开启手机登录</label>
<div class="layui-input-block">
<input type="radio" name="login_with_phone" value="1" title="是" {% if local_auth.login_with_phone == "1" %}checked="checked"{% endif %}>
<input type="radio" name="login_with_phone" value="0" title="否" {% if local_auth.login_with_phone == "0" %}checked="checked"{% endif %}>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">开启邮箱登录</label>
<div class="layui-input-block">
<input type="radio" name="login_with_email" value="1" title="是" {% if local_auth.login_with_email == "1" %}checked="checked"{% endif %}>
<input type="radio" name="login_with_email" value="0" title="否" {% if local_auth.login_with_email == "0" %}checked="checked"{% endif %}>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label"></label>
<div class="layui-input-block">
<button class="layui-btn" lay-submit="true" lay-filter="go">提交</button>
<button type="button" class="kg-back layui-btn layui-btn-primary">返回</button>
<input type="hidden" name="section" value="oauth.local">
</div>
</div>
</form>

View File

@ -54,12 +54,11 @@ class AnswerController extends Controller
$answer = $service->handle();
$content = [
'answer' => $answer,
'msg' => '创建回答成功',
];
$service = new AnswerInfoService();
return $this->jsonSuccess($content);
$answer = $service->handle($answer->id);
return $this->jsonSuccess(['answer' => $answer]);
}
/**
@ -71,12 +70,7 @@ class AnswerController extends Controller
$answer = $service->handle($id);
$content = [
'answer' => $answer,
'msg' => '更新回答成功',
];
return $this->jsonSuccess($content);
return $this->jsonSuccess(['answer' => $answer]);
}
/**
@ -105,20 +99,6 @@ class AnswerController extends Controller
return $this->jsonSuccess(['data' => $data, 'msg' => $msg]);
}
/**
* @Post("/{id:[0-9]+}/unaccept", name="api.answer.unaccept")
*/
public function unacceptAction($id)
{
$service = new AnswerAcceptService();
$data = $service->handle($id);
$msg = $data['action'] == 'do' ? '采纳成功' : '取消采纳成功';
return $this->jsonSuccess(['data' => $data, 'msg' => $msg]);
}
/**
* @Post("/{id:[0-9]+}/like", name="api.answer.like")
*/
@ -133,18 +113,4 @@ class AnswerController extends Controller
return $this->jsonSuccess(['data' => $data, 'msg' => $msg]);
}
/**
* @Post("/{id:[0-9]+}/like", name="api.answer.unlike")
*/
public function unlikeAction($id)
{
$service = new AnswerLikeService();
$data = $service->handle($id);
$msg = $data['action'] == 'do' ? '点赞成功' : '取消点赞成功';
return $this->jsonSuccess(['data' => $data, 'msg' => $msg]);
}
}

View File

@ -82,20 +82,6 @@ class ArticleController extends Controller
return $this->jsonSuccess(['data' => $data, 'msg' => $msg]);
}
/**
* @Post("/{id:[0-9]+}/unfavorite", name="api.article.unfavorite")
*/
public function unfavoriteAction($id)
{
$service = new ArticleFavoriteService();
$data = $service->handle($id);
$msg = $data['action'] == 'do' ? '收藏成功' : '取消收藏成功';
return $this->jsonSuccess(['data' => $data, 'msg' => $msg]);
}
/**
* @Post("/{id:[0-9]+}/like", name="api.article.like")
*/
@ -110,18 +96,4 @@ class ArticleController extends Controller
return $this->jsonSuccess(['data' => $data, 'msg' => $msg]);
}
/**
* @Post("/{id:[0-9]+}/like", name="api.article.unlike")
*/
public function unlikeAction($id)
{
$service = new ArticleLikeService();
$data = $service->handle($id);
$msg = $data['action'] == 'do' ? '点赞成功' : '取消点赞成功';
return $this->jsonSuccess(['data' => $data, 'msg' => $msg]);
}
}

View File

@ -7,12 +7,12 @@
namespace App\Http\Api\Controllers;
use App\Services\Logic\Article\CommentList as CommentListService;
use App\Services\Logic\Chapter\ChapterInfo as ChapterInfoService;
use App\Services\Logic\Chapter\ChapterLike as ChapterLikeService;
use App\Services\Logic\Chapter\ConsultList as ChapterConsultListService;
use App\Services\Logic\Chapter\Learning as ChapterLearningService;
use App\Services\Logic\Chapter\ResourceList as ChapterResourceListService;
use App\Services\Logic\Chapter\CommentList as CommentListService;
use App\Services\Logic\Chapter\ConsultList as ConsultListService;
use App\Services\Logic\Chapter\Learning as LearningService;
use App\Services\Logic\Chapter\ResourceList as ResourceListService;
/**
* @RoutePrefix("/api/chapter")
@ -37,7 +37,7 @@ class ChapterController extends Controller
*/
public function consultsAction($id)
{
$service = new ChapterConsultListService();
$service = new ConsultListService();
$pager = $service->handle($id);
@ -49,7 +49,7 @@ class ChapterController extends Controller
*/
public function resourcesAction($id)
{
$service = new ChapterResourceListService();
$service = new ResourceListService();
$resources = $service->handle($id);
@ -91,7 +91,7 @@ class ChapterController extends Controller
*/
public function learningAction($id)
{
$service = new ChapterLearningService();
$service = new LearningService();
$service->handle($id);

View File

@ -102,18 +102,4 @@ class CommentController extends Controller
return $this->jsonSuccess(['data' => $data, 'msg' => $msg]);
}
/**
* @Post("/{id:[0-9]+}/like", name="home.comment.like")
*/
public function unlikeAction($id)
{
$service = new CommentLikeService();
$data = $service->handle($id);
$msg = $data['action'] == 'do' ? '点赞成功' : '取消点赞成功';
return $this->jsonSuccess(['data' => $data, 'msg' => $msg]);
}
}

View File

@ -89,18 +89,4 @@ class ConsultController extends Controller
return $this->jsonSuccess(['data' => $data, 'msg' => $msg]);
}
/**
* @Post("/{id:[0-9]+}/unlike", name="api.consult.unlike")
*/
public function unlikeAction($id)
{
$service = new ConsultLikeService();
$data = $service->handle($id);
$msg = $data['action'] == 'do' ? '点赞成功' : '取消点赞成功';
return $this->jsonSuccess(['data' => $data, 'msg' => $msg]);
}
}

View File

@ -120,18 +120,4 @@ class CourseController extends Controller
return $this->jsonSuccess(['data' => $data, 'msg' => $msg]);
}
/**
* @Post("/{id:[0-9]+}/unfavorite", name="api.course.unfavorite")
*/
public function unfavoriteAction($id)
{
$service = new CourseFavoriteService();
$data = $service->handle($id);
$msg = $data['action'] == 'do' ? '收藏成功' : '取消收藏成功';
return $this->jsonSuccess(['data' => $data, 'msg' => $msg]);
}
}

View File

@ -0,0 +1,48 @@
<?php
/**
* @copyright Copyright (c) 2021 深圳市酷瓜软件有限公司
* @license https://opensource.org/licenses/GPL-2.0
* @link https://www.koogua.com
*/
namespace App\Http\Api\Controllers;
use App\Services\Logic\FlashSale\OrderCreate as OrderCreateService;
use App\Services\Logic\FlashSale\SaleList as SaleListService;
use App\Services\Logic\Order\OrderInfo as OrderInfoService;
/**
* @RoutePrefix("/api/flash/sale")
*/
class FlashSaleController extends Controller
{
/**
* @Get("/list", name="api.flash_sale.list")
*/
public function listAction()
{
$service = new SaleListService();
$sales = $service->handle();
return $this->jsonSuccess(['sales' => $sales]);
}
/**
* @Post("/order", name="api.flash_sale.order")
*/
public function orderAction()
{
$service = new OrderCreateService();
$order = $service->handle();
$service = new OrderInfoService();
$order = $service->handle($order->sn);
return $this->jsonSuccess(['order' => $order]);
}
}

View File

@ -7,6 +7,9 @@
namespace App\Http\Api\Controllers;
use App\Caches\IndexArticleList;
use App\Caches\IndexLiveList;
use App\Caches\IndexQuestionList;
use App\Caches\IndexSimpleFeaturedCourseList;
use App\Caches\IndexSimpleFreeCourseList;
use App\Caches\IndexSimpleNewCourseList;
@ -31,6 +34,42 @@ class IndexController extends Controller
return $this->jsonSuccess(['slides' => $slides]);
}
/**
* @Get("/articles", name="api.index.articles")
*/
public function articlesAction()
{
$cache = new IndexArticleList();
$articles = $cache->get();
return $this->jsonSuccess(['articles' => $articles]);
}
/**
* @Get("/questions", name="api.index.questions")
*/
public function questionsAction()
{
$cache = new IndexQuestionList();
$questions = $cache->get();
return $this->jsonSuccess(['questions' => $questions]);
}
/**
* @Get("/lives", name="api.index.lives")
*/
public function livesAction()
{
$cache = new IndexLiveList();
$lives = $cache->get();
return $this->jsonSuccess(['lives' => $lives]);
}
/**
* @Get("/courses/featured", name="api.index.featured_courses")
*/

View File

@ -86,6 +86,24 @@ class PublicController extends Controller
return $this->jsonSuccess(['captcha' => $captcha]);
}
/**
* @Get("/payment/info", name="api.public.payment_info")
*/
public function paymentInfoAction()
{
$service = new AppService();
$alipay = $service->getSettings('pay.alipay');
$wxpay = $service->getSettings('pay.wxpay');
$content = [
'alipay' => ['enabled' => $alipay['enabled']],
'wxpay' => ['enabled' => $wxpay['enabled']],
];
return $this->jsonSuccess($content);
}
/**
* @Get("/reward/options", name="api.public.reward_options")
*/

View File

@ -95,20 +95,6 @@ class QuestionController extends Controller
return $this->jsonSuccess(['data' => $data, 'msg' => $msg]);
}
/**
* @Post("/{id:[0-9]+}/unfavorite", name="api.question.unfavorite")
*/
public function unfavoriteAction($id)
{
$service = new QuestionFavoriteService();
$data = $service->handle($id);
$msg = $data['action'] == 'do' ? '收藏成功' : '取消收藏成功';
return $this->jsonSuccess(['data' => $data, 'msg' => $msg]);
}
/**
* @Post("/{id:[0-9]+}/like", name="api.question.like")
*/
@ -123,18 +109,4 @@ class QuestionController extends Controller
return $this->jsonSuccess(['data' => $data, 'msg' => $msg]);
}
/**
* @Post("/{id:[0-9]+}/like", name="api.question.unlike")
*/
public function unlikeAction($id)
{
$service = new QuestionLikeService();
$data = $service->handle($id);
$msg = $data['action'] == 'do' ? '点赞成功' : '取消点赞成功';
return $this->jsonSuccess(['data' => $data, 'msg' => $msg]);
}
}

View File

@ -89,18 +89,4 @@ class ReviewController extends Controller
return $this->jsonSuccess(['data' => $data, 'msg' => $msg]);
}
/**
* @Post("/{id:[0-9]+}/unlike", name="api.review.unlike")
*/
public function unlikeAction($id)
{
$service = new ReviewLikeService();
$data = $service->handle($id);
$msg = $data['action'] == 'do' ? '点赞成功' : '取消点赞成功';
return $this->jsonSuccess(['data' => $data, 'msg' => $msg]);
}
}

View File

@ -7,10 +7,11 @@
namespace App\Http\Api\Controllers;
use App\Services\Logic\Search\Article as ArticleSearchService;
use App\Services\Logic\Search\Course as CourseSearchService;
use App\Services\Logic\Search\Group as GroupSearchService;
use App\Services\Logic\Search\User as UserSearchService;
use App\Services\Logic\Search\Article as ArticleSearch;
use App\Services\Logic\Search\Course as CourseSearch;
use App\Services\Logic\Search\Group as GroupSearch;
use App\Services\Logic\Search\Question as QuestionSearch;
use App\Services\Logic\Search\User as UserSearch;
/**
* @RoutePrefix("/api/search")
@ -45,22 +46,25 @@ class SearchController extends Controller
/**
* @param string $type
* @return ArticleSearchService|CourseSearchService|GroupSearchService|UserSearchService
* @return ArticleSearch|QuestionSearch|CourseSearch|GroupSearch|UserSearch
*/
protected function getSearchService($type)
{
switch ($type) {
case 'article':
$service = new ArticleSearchService();
$service = new ArticleSearch();
break;
case 'question':
$service = new QuestionSearch();
break;
case 'group':
$service = new GroupSearchService();
$service = new GroupSearch();
break;
case 'user':
$service = new UserSearchService();
$service = new UserSearch();
break;
default:
$service = new CourseSearchService();
$service = new CourseSearch();
break;
}

View File

@ -25,13 +25,15 @@ class PublicController extends \Phalcon\Mvc\Controller
use SecurityTrait;
/**
* @Get("/download/{md5}", name="home.download")
* @Get("/download/{id}", name="home.download")
*/
public function downloadAction($md5)
public function downloadAction($id)
{
$id = $this->crypt->decryptBase64($id, null, true);
$repo = new UploadRepo();
$file = $repo->findByMd5($md5);
$file = $repo->findById($id);
if ($file) {

View File

@ -90,6 +90,18 @@ class WeChatOfficialAccount extends Service
protected function handleSubscribeEvent($message)
{
$openId = $message['FromUserName'] ?? '';
$eventKey = $message['EventKey'] ?? '';
/**
* 带场景值的关注事件
*/
$userId = str_replace('qrscene_', '', $eventKey);
if ($userId && $openId) {
$this->saveWechatSubscribe($userId, $openId);
}
return new TextMessage('开心呀,我们又多了一个小伙伴!');
}
@ -115,26 +127,8 @@ class WeChatOfficialAccount extends Service
$userId = str_replace('qrscene_', '', $eventKey);
$userRepo = new UserRepo();
$user = $userRepo->findById($userId);
if (!$user) return $this->emptyReply();
$subscribeRepo = new WeChatSubscribeRepo();
$subscribe = $subscribeRepo->findByOpenId($openId);
if ($subscribe) {
if ($subscribe->user_id != $userId) {
$subscribe->user_id = $userId;
}
$subscribe->update();
} else {
$subscribe = new WeChatSubscribeModel();
$subscribe->user_id = $userId;
$subscribe->open_id = $openId;
$subscribe->create();
if ($userId && $openId) {
$this->saveWechatSubscribe($userId, $openId);
}
return $this->emptyReply();
@ -200,4 +194,31 @@ class WeChatOfficialAccount extends Service
return new TextMessage('没有匹配的服务哦!');
}
protected function saveWechatSubscribe($userId, $openId)
{
if (!$userId || !$openId) return;
$userRepo = new UserRepo();
$user = $userRepo->findById($userId);
if (!$user) return;
$subscribeRepo = new WeChatSubscribeRepo();
$subscribe = $subscribeRepo->findByOpenId($openId);
if ($subscribe) {
if ($subscribe->user_id != $userId) {
$subscribe->user_id = $userId;
$subscribe->update();
}
} else {
$subscribe = new WeChatSubscribeModel();
$subscribe->user_id = $userId;
$subscribe->open_id = $openId;
$subscribe->create();
}
}
}

View File

@ -2,6 +2,9 @@
{% block content %}
{% set login_with_phone = oauth_provider.local.login_with_phone == 1 %}
{% set login_with_email = oauth_provider.local.login_with_email == 1 %}
<div class="layui-breadcrumb breadcrumb">
<a href="/">首页</a>
<a><cite>登录</cite></a>

View File

@ -1,8 +1,20 @@
<form class="layui-form account-form" method="POST" action="{{ url({'for':'home.account.pwd_login'}) }}">
{% if login_with_phone and login_with_email %}
<div class="layui-form-item">
<label class="layui-icon layui-icon-username"></label>
<input class="layui-input" type="text" name="account" autocomplete="off" placeholder="手机 / 邮箱" lay-verify="required">
</div>
{% elseif login_with_email %}
<div class="layui-form-item">
<label class="layui-icon layui-icon-email"></label>
<input class="layui-input" type="text" name="account" autocomplete="off" placeholder="邮箱" lay-verify="email">
</div>
{% else %}
<div class="layui-form-item">
<label class="layui-icon layui-icon-cellphone"></label>
<input class="layui-input" type="text" name="account" autocomplete="off" placeholder="手机" lay-verify="phone">
</div>
{% endif %}
<div class="layui-form-item">
<label class="layui-icon layui-icon-password"></label>
<input class="layui-input" type="password" name="password" autocomplete="off" placeholder="密码" lay-verify="required">

View File

@ -9,11 +9,10 @@
<th width="15%">操作</th>
</tr>
{% for item in items %}
{% set download_url = url({'for':'home.download','md5':item.md5}) %}
<tr>
<td>{{ item.name }}</td>
<td>{{ item.size|human_size }}</td>
<td><a class="layui-btn layui-btn-sm" href="{{ download_url }}" target="_blank">下载</a></td>
<td><a class="layui-btn layui-btn-sm" href="{{ item.url }}" target="_blank">下载</a></td>
</tr>
{% endfor %}
</table>

View File

@ -34,8 +34,8 @@
<div class="course-tab-wrap wrap">
<div class="layui-tab layui-tab-brief course-tab">
<ul class="layui-tab-title">
<li class="layui-this">目录</li>
<li>详情</li>
<li class="layui-this">详情</li>
<li>目录</li>
{% if show_tab_packages %}
<li>套餐<span class="tab-count">{{ course.package_count }}</span></li>
{% endif %}
@ -48,10 +48,10 @@
</ul>
<div class="layui-tab-content">
<div class="layui-tab-item layui-show">
{{ partial('course/show_catalog') }}
<div class="course-details markdown-body">{{ course.details }}</div>
</div>
<div class="layui-tab-item">
<div class="course-details markdown-body">{{ course.details }}</div>
{{ partial('course/show_catalog') }}
</div>
{% if show_tab_packages %}
{% set packages_url = url({'for':'home.course.packages','id':course.id}) %}

View File

@ -5,11 +5,14 @@
<i class="layui-icon layui-icon-play"></i>
<span class="title">{{ lesson.title }}</span>
{% if lesson.free == 1 %}
<span class="layui-badge free-badge">试听</span>
<span class="iconfont icon-trial"></span>
{% endif %}
{% if lesson.me.duration > 0 %}
<span class="study-time" title="学习时长:{{ lesson.me.duration|duration }}"><i class="layui-icon layui-icon-time"></i></span>
{% endif %}
{% if priv == 'deny' %}
<span class="iconfont icon-lock"></span>
{% endif %}
<span class="duration">{{ lesson.attrs.duration|duration }}</span>
</a>
{%- endmacro %}
@ -21,11 +24,14 @@
<i class="layui-icon layui-icon-video"></i>
<span class="title">{{ lesson.title }}</span>
{% if lesson.free == 1 %}
<span class="layui-badge free-badge">试听</span>
<span class="iconfont icon-trial"></span>
{% endif %}
{% if lesson.me.duration > 0 %}
<span class="study-time" title="学习时长:{{ lesson.me.duration|duration }}"><i class="layui-icon layui-icon-time"></i></span>
{% endif %}
{% if priv == 'deny' %}
<span class="iconfont icon-lock"></span>
{% endif %}
<span class="live" title="{{ date('Y-m-d H:i',lesson.attrs.start_time) }}">{{ live_status_info(lesson) }}</span>
</a>
{%- endmacro %}
@ -37,11 +43,14 @@
<i class="layui-icon layui-icon-read"></i>
<span class="title">{{ lesson.title }}</span>
{% if lesson.free == 1 %}
<span class="layui-badge free-badge">试读</span>
<span class="iconfont icon-trial"></span>
{% endif %}
{% if lesson.me.duration > 0 %}
<span class="study-time" title="学习时长:{{ lesson.me.duration|duration }}"><i class="layui-icon layui-icon-time"></i></span>
{% endif %}
{% if priv == 'deny' %}
<span class="iconfont icon-lock"></span>
{% endif %}
</a>
{%- endmacro %}
@ -57,7 +66,7 @@
{%- endmacro %}
{%- macro live_status_info(lesson) %}
{% if lesson.attrs.stream.status == 'active' %}
{% if lesson.attrs.start_time < time() and lesson.attrs.end_time > time() %}
<span class="active">{{ date('m月d日 H:i',lesson.attrs.start_time) }} 直播中</span>
{% elseif lesson.attrs.start_time > time() %}
<span class="pending">{{ date('m月d日 H:i',lesson.attrs.start_time) }} 倒计时</span>

View File

@ -42,7 +42,7 @@
{% endif %}
{% if auth_user.id > 0 %}
<li class="layui-nav-item">
<a href="javascript:">创建</a>
<a href="javascript:"><i class="layui-icon layui-icon-add-circle"></i> 发布</a>
<dl class="layui-nav-child">
<dd><a href="{{ url({'for':'home.question.add'}) }}" target="_blank">提问题</a></dd>
<dd><a href="{{ url({'for':'home.article.add'}) }}" target="_blank">写文章</a></dd>

View File

@ -13,8 +13,8 @@
{% else %}
{{ icon_link('favicon.ico') }}
{% endif %}
<link rel="preload" href="//at.alicdn.com/t/font_2760791_c83l29k7bz.woff2" as="font" type="font/woff2" crossorigin="anonymous">
<link rel="stylesheet" href="//at.alicdn.com/t/font_2760791_c83l29k7bz.css">
<link rel="preload" href="//at.alicdn.com/t/font_2760791_mj6x0o9n15s.woff2" as="font" type="font/woff2" crossorigin="anonymous">
<link rel="stylesheet" href="//at.alicdn.com/t/font_2760791_mj6x0o9n15s.css">
{{ css_link('lib/layui/css/layui.css') }}
{{ css_link('home/css/common.css') }}
{% block link_css %}{% endblock %}

View File

@ -16,7 +16,7 @@ class AppInfo
protected $link = 'https://koogua.com';
protected $version = '1.4.3';
protected $version = '1.4.4';
public function __get($name)
{

View File

@ -413,7 +413,7 @@ function kg_cos_icon_url($path, $style = null)
/**
* 清除存储图片处理样式
*
* @param $path
* @param string $path
* @return string
*/
function kg_cos_img_style_trim($path)
@ -424,16 +424,18 @@ function kg_cos_img_style_trim($path)
/**
* 解析markdown内容
*
* @param $content
* @param string $content
* @param string $htmlInput (escape|strip)
* @param bool $allowUnsafeLinks
* @return string
*/
function kg_parse_markdown($content)
function kg_parse_markdown($content, $htmlInput = 'escape', $allowUnsafeLinks = false)
{
$content = str_replace('!content_800', '', $content);
$parser = new League\CommonMark\GithubFlavoredMarkdownConverter([
'html_input' => 'strip',
'allow_unsafe_links' => false,
'html_input' => $htmlInput,
'allow_unsafe_links' => $allowUnsafeLinks,
]);
return $parser->convertToHtml($content);
@ -442,7 +444,7 @@ function kg_parse_markdown($content)
/**
* 解析内容摘要
*
* @param $content
* @param string $content
* @param int $length
* @return string
*/

View File

@ -78,6 +78,18 @@ class Chapter extends Repository
->execute();
}
/**
* @param int $id
* @return ResultsetInterface|Resultset|ChapterModel[]
*/
public function findLessons($id)
{
return ChapterModel::query()
->where('parent_id = :parent_id:', ['parent_id' => $id])
->andWhere('deleted = 0')
->execute();
}
/**
* @param string $fileId
* @return ChapterModel|Model|bool

View File

@ -14,11 +14,16 @@ class OAuthProvider extends LogicService
public function handle()
{
$local = $this->getSettings('oauth.local');
$weixin = $this->getSettings('oauth.weixin');
$weibo = $this->getSettings('oauth.weibo');
$qq = $this->getSettings('oauth.qq');
return [
'local' => [
'login_with_phone' => $local['login_with_phone'],
'login_with_email' => $local['login_with_email'],
],
'weixin' => ['enabled' => $weixin['enabled']],
'weibo' => ['enabled' => $weibo['enabled']],
'qq' => ['enabled' => $qq['enabled']],

View File

@ -72,8 +72,6 @@ class ArticleList extends LogicService
'source_type' => $article['source_type'],
'source_url' => $article['source_url'],
'tags' => $article['tags'],
'category' => $category,
'owner' => $owner,
'private' => $article['private'],
'published' => $article['published'],
'closed' => $article['closed'],
@ -83,6 +81,8 @@ class ArticleList extends LogicService
'favorite_count' => $article['favorite_count'],
'create_time' => $article['create_time'],
'update_time' => $article['update_time'],
'category' => $category,
'owner' => $owner,
];
}

View File

@ -8,6 +8,7 @@
namespace App\Services\Logic\Chapter;
use App\Library\Paginator\Query as PagerQuery;
use App\Models\Consult as ConsultModel;
use App\Repos\Consult as ConsultRepo;
use App\Services\Logic\ChapterTrait;
use App\Services\Logic\Course\ConsultListTrait;
@ -31,8 +32,9 @@ class ConsultList extends LogicService
$params = [
'chapter_id' => $chapter->id,
'published' => ConsultModel::PUBLISH_APPROVED,
'deleted' => 0,
'private' => 0,
'published' => 1,
];
$consultRepo = new ConsultRepo();

View File

@ -50,7 +50,7 @@ class CommentLike extends LogicService
$isFirstTime = false;
$commentLike->comment_id = $commentLike->deleted == 1 ? 0 : 1;
$commentLike->deleted = $commentLike->deleted == 1 ? 0 : 1;
$commentLike->update();
}

View File

@ -64,7 +64,12 @@ class ConsultCreate extends LogicService
$validator = new ConsultValidator();
$question = $validator->checkQuestion($post['question']);
$private = 0;
if (isset($post['private'])) {
$private = $validator->checkPrivateStatus($post['private']);
}
$validator->checkIfDuplicated($course->id, $user->id, $question);
@ -101,7 +106,12 @@ class ConsultCreate extends LogicService
$validator = new ConsultValidator();
$question = $validator->checkQuestion($post['question']);
$private = 0;
if (isset($post['private'])) {
$private = $validator->checkPrivateStatus($post['private']);
}
$validator->checkIfDuplicated($course->id, $user->id, $question);

View File

@ -8,6 +8,7 @@
namespace App\Services\Logic\Course;
use App\Library\Paginator\Query as PagerQuery;
use App\Models\Consult as ConsultModel;
use App\Repos\Consult as ConsultRepo;
use App\Services\Logic\CourseTrait;
use App\Services\Logic\Service as LogicService;
@ -30,9 +31,9 @@ class ConsultList extends LogicService
$params = [
'course_id' => $course->id,
'private' => 0,
'published' => 1,
'published' => ConsultModel::PUBLISH_APPROVED,
'deleted' => 0,
'private' => 0,
];
$consultRepo = new ConsultRepo();

View File

@ -52,12 +52,17 @@ class LiveList extends LogicService
$items = [];
foreach ($lives as $live) {
$course = $courses[$live['course_id']] ?? new \stdClass();
$chapter = $chapters[$live['chapter_id']] ?? new \stdClass();
$items[] = [
'course' => $courses[$live['course_id']] ?? new \stdClass(),
'chapter' => $chapters[$live['chapter_id']] ?? new \stdClass(),
'id' => $live['id'],
'status' => $live['status'],
'start_time' => $live['start_time'],
'end_time' => $live['end_time'],
'course' => $course,
'chapter' => $chapter,
];
}

View File

@ -47,6 +47,8 @@ class QuestionList extends LogicService
$builder = new QuestionListBuilder();
$categories = $builder->getCategories();
$questions = $pager->items->toArray();
$users = $builder->getUsers($questions);
@ -57,6 +59,8 @@ class QuestionList extends LogicService
$question['tags'] = json_decode($question['tags'], true);
$category = $categories[$question['category_id']] ?? new \stdClass();
$owner = $users[$question['owner_id']] ?? new \stdClass();
$lastReplier = $users[$question['last_replier_id']] ?? new \stdClass();
@ -81,6 +85,7 @@ class QuestionList extends LogicService
'create_time' => $question['create_time'],
'update_time' => $question['update_time'],
'last_replier' => $lastReplier,
'category' => $category,
'owner' => $owner,
];
}

View File

@ -64,6 +64,16 @@ class Article extends Handler
foreach ($pager->items as $item) {
$category = json_decode($item['category'], true);
$owner = json_decode($item['owner'], true);
$tags = json_decode($item['tags'], true);
$owner['avatar'] = $owner['avatar'] ?: kg_default_user_avatar_path();
if (!empty($owner['avatar']) && !Text::startsWith($owner['avatar'], 'http')) {
$owner['avatar'] = $baseUrl . $owner['avatar'];
}
if (!empty($item['cover']) && !Text::startsWith($item['cover'], 'http')) {
$item['cover'] = $baseUrl . $item['cover'];
}
@ -78,9 +88,9 @@ class Article extends Handler
'like_count' => (int)$item['like_count'],
'favorite_count' => (int)$item['favorite_count'],
'comment_count' => (int)$item['comment_count'],
'tags' => json_decode($item['tags'], true),
'owner' => json_decode($item['owner'], true),
'category' => json_decode($item['category'], true),
'category' => $category,
'owner' => $owner,
'tags' => $tags,
];
}

View File

@ -10,6 +10,7 @@ namespace App\Services\Logic\Search;
use App\Library\Paginator\Adapter\XunSearch as XunSearchPaginator;
use App\Library\Paginator\Query as PagerQuery;
use App\Services\Search\CourseSearcher as CourseSearcherService;
use Phalcon\Text;
class Course extends Handler
{
@ -63,7 +64,24 @@ class Course extends Handler
foreach ($pager->items as $item) {
/**
* 后补的字段,给默认值防止出错
*/
$item['tags'] = $item['tags'] ?: '[]';
$category = json_decode($item['category'], true);
$teacher = json_decode($item['teacher'], true);
$tags = json_decode($item['tags'], true);
$teacher['avatar'] = $teacher['avatar'] ?: kg_default_user_avatar_path();
if (!empty($teacher['avatar']) && !Text::startsWith($teacher['avatar'], 'http')) {
$teacher['avatar'] = $baseUrl . $teacher['avatar'];
}
if (!empty($item['cover']) && !Text::startsWith($item['cover'], 'http')) {
$item['cover'] = $baseUrl . $item['cover'];
}
$items[] = [
'id' => (int)$item['id'],
@ -78,8 +96,9 @@ class Course extends Handler
'lesson_count' => (int)$item['lesson_count'],
'review_count' => (int)$item['review_count'],
'favorite_count' => (int)$item['favorite_count'],
'teacher' => json_decode($item['teacher'], true),
'category' => json_decode($item['category'], true),
'category' => $category,
'teacher' => $teacher,
'tags' => $tags,
];
}

View File

@ -10,6 +10,7 @@ namespace App\Services\Logic\Search;
use App\Library\Paginator\Adapter\XunSearch as XunSearchPaginator;
use App\Library\Paginator\Query as PagerQuery;
use App\Services\Search\GroupSearcher as GroupSearcherService;
use Phalcon\Text;
class Group extends Handler
{
@ -63,7 +64,11 @@ class Group extends Handler
foreach ($pager->items as $item) {
$owner = json_decode($item['owner'], true);
if (!empty($item['avatar']) && !Text::startsWith($item['avatar'], 'http')) {
$item['avatar'] = $baseUrl . $item['avatar'];
}
$items[] = [
'id' => (int)$item['id'],
@ -73,7 +78,7 @@ class Group extends Handler
'about' => (string)$item['about'],
'user_count' => (int)$item['user_count'],
'msg_count' => (int)$item['msg_count'],
'owner' => json_decode($item['owner'], true),
'owner' => $owner,
];
}

View File

@ -64,6 +64,17 @@ class Question extends Handler
foreach ($pager->items as $item) {
$lastReplier = json_decode($item['last_replier'], true);
$category = json_decode($item['category'], true);
$owner = json_decode($item['owner'], true);
$tags = json_decode($item['tags'], true);
$owner['avatar'] = $owner['avatar'] ?: kg_default_user_avatar_path();
if (!empty($owner['avatar']) && !Text::startsWith($owner['avatar'], 'http')) {
$owner['avatar'] = $baseUrl . $owner['avatar'];
}
if (!empty($item['cover']) && !Text::startsWith($item['cover'], 'http')) {
$item['cover'] = $baseUrl . $item['cover'];
}
@ -71,13 +82,13 @@ class Question extends Handler
$lastAnswer = json_decode($item['last_answer'], true);
if (!empty($lastAnswer['cover']) && !Text::startsWith($lastAnswer['cover'], 'http')) {
$item['last_answer'] = $baseUrl . $lastAnswer['cover'];
$lastAnswer['cover'] = $baseUrl . $lastAnswer['cover'];
}
$acceptAnswer = json_decode($item['accept_answer'], true);
if (!empty($acceptAnswer['cover']) && !Text::startsWith($acceptAnswer['cover'], 'http')) {
$item['accept_answer'] = $baseUrl . $acceptAnswer['cover'];
$acceptAnswer['cover'] = $baseUrl . $acceptAnswer['cover'];
}
$items[] = [
@ -95,12 +106,12 @@ class Question extends Handler
'answer_count' => (int)$item['answer_count'],
'comment_count' => (int)$item['comment_count'],
'favorite_count' => (int)$item['favorite_count'],
'category' => json_decode($item['category'], true),
'tags' => json_decode($item['tags'], true),
'owner' => json_decode($item['owner'], true),
'last_replier' => json_decode($item['last_replier'], true),
'last_answer' => $item['last_answer'],
'accept_answer' => $item['accept_answer'],
'accept_answer' => $acceptAnswer,
'last_answer' => $lastAnswer,
'last_replier' => $lastReplier,
'category' => $category,
'owner' => $owner,
'tags' => $tags,
];
}

View File

@ -8,6 +8,7 @@
namespace App\Services\Search;
use App\Models\Article as ArticleModel;
use App\Models\User as UserModel;
use App\Repos\Category as CategoryRepo;
use App\Repos\User as UserRepo;
use Phalcon\Di\Injectable;
@ -80,9 +81,12 @@ class ArticleDocument extends Injectable
$user = $userRepo->findById($id);
$user->avatar = UserModel::getAvatarPath($user->avatar);
return kg_json_encode([
'id' => $user->id,
'name' => $user->name,
'avatar' => $user->avatar,
]);
}

View File

@ -7,9 +7,10 @@
namespace App\Services\Search;
use App\Models\Category as CategoryModel;
use App\Models\Course as CourseModel;
use App\Models\User as UserModel;
use App\Repos\Category as CategoryRepo;
use App\Repos\User as UserRepo;
use Phalcon\Di\Injectable;
class CourseDocument extends Injectable
@ -44,24 +45,20 @@ class CourseDocument extends Injectable
$course->attrs = kg_json_encode($course->attrs);
}
if (is_array($course->tags) || is_object($course->tags)) {
$course->tags = kg_json_encode($course->tags);
}
$teacher = '{}';
if ($course->teacher_id > 0) {
$record = UserModel::findFirst($course->teacher_id);
$teacher = kg_json_encode([
'id' => $record->id,
'name' => $record->name,
]);
$teacher = $this->handleUser($course->teacher_id);
}
$category = '{}';
if ($course->category_id > 0) {
$record = CategoryModel::findFirst($course->category_id);
$category = kg_json_encode([
'id' => $record->id,
'name' => $record->name,
]);
$category = $this->handleCategory($course->category_id);
}
$course->cover = CourseModel::getCoverPath($course->cover);
@ -83,6 +80,7 @@ class CourseDocument extends Injectable
'model' => $course->model,
'level' => $course->level,
'attrs' => $course->attrs,
'tags' => $course->tags,
'category' => $category,
'teacher' => $teacher,
'user_count' => $course->user_count,
@ -92,4 +90,31 @@ class CourseDocument extends Injectable
];
}
protected function handleUser($id)
{
$userRepo = new UserRepo();
$user = $userRepo->findById($id);
$user->avatar = UserModel::getAvatarPath($user->avatar);
return kg_json_encode([
'id' => $user->id,
'name' => $user->name,
'avatar' => $user->avatar,
]);
}
protected function handleCategory($id)
{
$categoryRepo = new CategoryRepo();
$category = $categoryRepo->findById($id);
return kg_json_encode([
'id' => $category->id,
'name' => $category->name,
]);
}
}

View File

@ -9,6 +9,7 @@ namespace App\Services\Search;
use App\Models\ImGroup as GroupModel;
use App\Models\User as UserModel;
use App\Repos\User as UserRepo;
use Phalcon\Di\Injectable;
class GroupDocument extends Injectable
@ -42,11 +43,7 @@ class GroupDocument extends Injectable
$owner = '{}';
if ($group->owner_id > 0) {
$record = UserModel::findFirst($group->owner_id);
$owner = kg_json_encode([
'id' => $record->id,
'name' => $record->name,
]);
$owner = $this->handleUser($group->owner_id);
}
$group->avatar = GroupModel::getAvatarPath($group->avatar);
@ -62,4 +59,19 @@ class GroupDocument extends Injectable
];
}
protected function handleUser($id)
{
$userRepo = new UserRepo();
$user = $userRepo->findById($id);
$user->avatar = UserModel::getAvatarPath($user->avatar);
return kg_json_encode([
'id' => $user->id,
'name' => $user->name,
'avatar' => $user->avatar,
]);
}
}

View File

@ -8,6 +8,7 @@
namespace App\Services\Search;
use App\Models\Question as QuestionModel;
use App\Models\User as UserModel;
use App\Repos\Answer as AnswerRepo;
use App\Repos\Category as CategoryRepo;
use App\Repos\User as UserRepo;
@ -93,11 +94,11 @@ class QuestionDocument extends Injectable
'answer_count' => $question->answer_count,
'comment_count' => $question->comment_count,
'favorite_count' => $question->favorite_count,
'accept_answer' => $acceptAnswer,
'last_answer' => $lastAnswer,
'last_replier' => $lastReplier,
'category' => $category,
'owner' => $owner,
'last_replier' => $lastReplier,
'last_answer' => $lastAnswer,
'accept_answer' => $acceptAnswer,
];
}
@ -107,9 +108,12 @@ class QuestionDocument extends Injectable
$user = $userRepo->findById($id);
$user->avatar = UserModel::getAvatarPath($user->avatar);
return kg_json_encode([
'id' => $user->id,
'name' => $user->name,
'avatar' => $user->avatar,
]);
}

View File

@ -56,7 +56,7 @@ class Answer extends Validator
public function checkContent($content)
{
$value = $this->filter->sanitize($content, ['trim', 'string']);
$value = $this->filter->sanitize($content, ['trim']);
$length = kg_strlen($value);

View File

@ -205,7 +205,7 @@ class Chapter extends Validator
if ($attrs['word_count'] == 0) {
throw new BadRequestException('chapter.read_not_ready');
}
} elseif ($chapter->model == CourseModel::MODEL_READ) {
} elseif ($chapter->model == CourseModel::MODEL_OFFLINE) {
if ($attrs['start_time'] == 0) {
throw new BadRequestException('chapter.offline_time_empty');
}

View File

@ -246,29 +246,6 @@ class Course extends Validator
if ($course->teacher_id == 0) {
throw new BadRequestException('course.teacher_not_assigned');
}
$courseRepo = new CourseRepo();
$chapters = $courseRepo->findChapters($course->id);
$totalCount = $publishedCount = 0;
foreach ($chapters as $chapter) {
if ($chapter->parent_id > 0 && $chapter->published == 1) {
$publishedCount++;
}
if ($chapter->parent_id > 0) {
$totalCount++;
}
}
if ($publishedCount == 0) {
throw new BadRequestException('course.pub_chapter_not_found');
}
if ($publishedCount / $totalCount < 0.2) {
throw new BadRequestException('course.pub_chapter_not_enough');
}
}
}

View File

@ -9,6 +9,7 @@ namespace App\Validators;
use App\Exceptions\BadRequest as BadRequestException;
use App\Library\Validators\Common as CommonValidator;
use App\Models\CourseUser as CourseUserModel;
use App\Repos\CourseUser as CourseUserRepo;
class CourseUser extends Validator
@ -65,14 +66,14 @@ class CourseUser extends Validator
return strtotime($value);
}
public function checkIfJoined($courseId, $userId)
public function checkIfImported($courseId, $userId)
{
$repo = new CourseUserRepo();
$courseUser = $repo->findCourseStudent($courseId, $userId);
if ($courseUser) {
throw new BadRequestException('course_user.has_joined');
if ($courseUser && $courseUser->source_type == CourseUserModel::SOURCE_IMPORT) {
throw new BadRequestException('course_user.has_imported');
}
}

View File

@ -84,7 +84,7 @@ class PointGift extends Validator
public function checkDetails($details)
{
$value = $this->filter->sanitize($details, ['trim', 'string']);
$value = $this->filter->sanitize($details, ['trim']);
$length = kg_strlen($value);

View File

@ -184,8 +184,6 @@ $error['course.invalid_refund_expiry'] = '无效的退款期限';
$error['course.invalid_feature_status'] = '无效的推荐状态';
$error['course.invalid_publish_status'] = '无效的发布状态';
$error['course.teacher_not_assigned'] = '尚未指定授课教师';
$error['course.pub_chapter_not_found'] = '尚未发现已发布的课时';
$error['course.pub_chapter_not_enough'] = '已发布的课时太少小于20%';
/**
* 面授课程相关
@ -240,7 +238,7 @@ $error['package.invalid_expiry'] = '无效的期限范围1~60';
$error['course_user.not_found'] = '课程学员关系不存在';
$error['course_user.invalid_expiry_time'] = '无效的过期时间';
$error['course_user.review_not_allowed'] = '当前不允许评价课程';
$error['course_user.has_joined'] = '已经加入过该课程';
$error['course_user.has_imported'] = '已经加入过该课程';
$error['course_user.has_reviewed'] = '已经评价过该课程';
/**

View File

@ -61,6 +61,9 @@ tokenizer = full
[attrs]
type = string
[tags]
type = string
[category]
type = string

View File

@ -0,0 +1,27 @@
<?php
/**
* @copyright Copyright (c) 2021 深圳市酷瓜软件有限公司
* @license https://opensource.org/licenses/GPL-2.0
* @link https://www.koogua.com
*/
use Phinx\Migration\AbstractMigration;
final class V20210825111618 extends AbstractMigration
{
public function up()
{
$this->alterUploadTable();
}
protected function alterUploadTable()
{
$table = $this->table('kg_upload');
$table->removeIndexByName('md5')->save();
$table->addIndex('md5')->save();
}
}

View File

@ -0,0 +1,36 @@
<?php
/**
* @copyright Copyright (c) 2021 深圳市酷瓜软件有限公司
* @license https://opensource.org/licenses/GPL-2.0
* @link https://www.koogua.com
*/
use Phinx\Migration\AbstractMigration;
final class V20210916072842 extends AbstractMigration
{
public function up()
{
$this->handleLocalAuthSetting();
}
protected function handleLocalAuthSetting()
{
$rows = [
[
'section' => 'oauth.local',
'item_key' => 'login_with_phone',
'item_value' => '1',
],
[
'section' => 'oauth.local',
'item_key' => 'login_with_email',
'item_value' => '1',
]
];
$this->table('kg_setting')->insert($rows)->save();
}
}

View File

@ -1029,7 +1029,9 @@
margin: 0 5px;
}
.lesson-item .free-badge {
.lesson-item .icon-trial {
color: red;
font-size: 24px;
margin-right: 5px;
}
@ -1347,12 +1349,16 @@
}
.player-wrap {
position: relative;
width: 760px;
height: 428px;
margin-bottom: 20px;
}
.dplayer {
width: 760px;
height: 428px;
}
.chat-wrap .layui-card-header {
text-align: center;
}