1
0
mirror of https://gitee.com/koogua/course-tencent-cloud.git synced 2025-08-04 21:41:38 +08:00

优化上传,调试markdown编辑器

This commit is contained in:
xiaochong0302 2020-08-06 20:53:30 +08:00
parent 4f9f6ff15e
commit f589c5011a
27 changed files with 635 additions and 335 deletions

View File

@ -15,14 +15,17 @@ class UploadController extends Controller
*/ */
public function uploadCoverImageAction() public function uploadCoverImageAction()
{ {
$storageService = new StorageService(); $service = new StorageService();
$key = $storageService->uploadCoverImage(); $file = $service->uploadCoverImage();
$url = $storageService->getCiImageUrl($key); if ($file) {
return $this->jsonSuccess([
if ($url) { 'data' => [
return $this->jsonSuccess(['data' => ['src' => $url, 'title' => '']]); 'src' => $service->getCiImageUrl($file->path),
'title' => $file->name,
]
]);
} else { } else {
return $this->jsonError(['msg' => '上传文件失败']); return $this->jsonError(['msg' => '上传文件失败']);
} }
@ -33,30 +36,38 @@ class UploadController extends Controller
*/ */
public function uploadAvatarImageAction() public function uploadAvatarImageAction()
{ {
$storageService = new StorageService(); $service = new StorageService();
$key = $storageService->uploadAvatarImage(); $file = $service->uploadAvatarImage();
$url = $storageService->getCiImageUrl($key); if ($file) {
return $this->jsonSuccess([
if ($url) { 'data' => [
return $this->jsonSuccess(['data' => ['src' => $url, 'title' => '']]); 'src' => $service->getCiImageUrl($file->path),
'title' => $file->name,
]
]);
} else { } else {
return $this->jsonError(['msg' => '上传文件失败']); return $this->jsonError(['msg' => '上传文件失败']);
} }
} }
/** /**
* @Post("/img/content", name="admin.upload.content_img") * @Post("/img/editor", name="admin.upload.editor_img")
*/ */
public function uploadContentImageAction() public function uploadEditorImageAction()
{ {
$storageService = new StorageService(); $service = new StorageService();
$url = $storageService->uploadContentImage(); $file = $service->uploadEditorImage();
if ($url) { if ($file) {
return $this->jsonSuccess(['data' => ['src' => $url, 'title' => '']]); return $this->jsonSuccess([
'data' => [
'src' => $service->getCiImageUrl($file->path),
'title' => $file->name,
]
]);
} else { } else {
return $this->jsonError(['msg' => '上传文件失败']); return $this->jsonError(['msg' => '上传文件失败']);
} }

View File

@ -112,22 +112,23 @@ class ChapterContent extends Service
$validator = new ChapterLiveValidator(); $validator = new ChapterLiveValidator();
$data = []; $startTime = $validator->checkStartTime($post['start_time']);
$endTime = $validator->checkEndTime($post['end_time']);
$data['start_time'] = $validator->checkStartTime($post['start_time']); $validator->checkTimeRange($startTime, $endTime);
$data['end_time'] = $validator->checkEndTime($post['end_time']);
$validator->checkTimeRange($post['start_time'], $post['end_time']); $live->update([
'start_time' => $startTime,
$live->update($data); 'end_time' => $endTime,
]);
/** /**
* @var array $attrs * @var array $attrs
*/ */
$attrs = $chapter->attrs; $attrs = $chapter->attrs;
$attrs['start_time'] = $data['start_time']; $attrs['start_time'] = $startTime;
$attrs['end_time'] = $data['end_time']; $attrs['end_time'] = $endTime;
$chapter->update(['attrs' => $attrs]); $chapter->update(['attrs' => $attrs]);
@ -146,19 +147,17 @@ class ChapterContent extends Service
$validator = new ChapterReadValidator(); $validator = new ChapterReadValidator();
$data = []; $content = $validator->checkContent($post['content']);
$data['content'] = $validator->checkContent($post['content']); $read->update(['content' => $content]);
$read->update($data);
/** /**
* @var array $attrs * @var array $attrs
*/ */
$attrs = $chapter->attrs; $attrs = $chapter->attrs;
$attrs['word_count'] = WordUtil::getWordCount($read->content); $attrs['word_count'] = WordUtil::getWordCount($content);
$attrs['duration'] = WordUtil::getWordDuration($read->content); $attrs['duration'] = WordUtil::getWordDuration($content);
$chapter->update(['attrs' => $attrs]); $chapter->update(['attrs' => $attrs]);

View File

@ -1,7 +1,9 @@
<div id="editor"></div>
<form class="layui-form kg-form" method="POST" action="{{ url({'for':'admin.chapter.content','id':chapter.id}) }}"> <form class="layui-form kg-form" method="POST" action="{{ url({'for':'admin.chapter.content','id':chapter.id}) }}">
<div class="layui-form-item"> <div class="layui-form-item">
<textarea name="content" class="layui-hide" id="kg-layedit">{{ read.content }}</textarea> <textarea class="layui-hide" name="content">{{ read.content }}</textarea>
</div> </div>
<div class="layui-form-item"> <div class="layui-form-item">
@ -9,9 +11,60 @@
<div class="layui-input-block"> <div class="layui-input-block">
<button class="kg-submit layui-btn" lay-submit="true" lay-filter="go">提交</button> <button class="kg-submit layui-btn" lay-submit="true" lay-filter="go">提交</button>
<button type="button" class="kg-back layui-btn layui-btn-primary">返回</button> <button type="button" class="kg-back layui-btn layui-btn-primary">返回</button>
<input type="hidden" name="chapter_id" value="{{ chapter.id }}">
</div> </div>
</div> </div>
</form> </form>
{{ partial('partials/layedit') }} <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/vditor/dist/index.css"/>
<script src="https://cdn.jsdelivr.net/npm/vditor/dist/index.min.js" defer></script>
<script>
layui.use(['jquery'], function () {
var $ = layui.jquery;
var $content = $('textarea[name=content]');
var vditor = new Vditor('editor', {
minHeight: 420,
outline: true,
tab: " ",
resize: {
enable: true
},
cache: {
enable: false
},
preview: {
markdown: {
chinesePunct: true
}
},
counter: {
enable: true,
max: 60000
},
upload: {
url: '/admin/upload/img/editor',
max: 10 * 1024 * 1024,
accept: 'image/*',
headers: {
'X-Csrf-Token': $('meta[name="csrf-token"]').attr('content'),
'X-Requested-With': 'XMLHttpRequest'
},
success: function (editor, responseText) {
console.log(editor, responseText);
var json = JSON.parse(responseText);
var img = '![](' + json.data.src + ')';
vditor.insertValue(img);
}
},
value: $content.val()
});
$('.kg-submit').on('click', function () {
$content.val(vditor.getValue());
});
});
</script>

View File

@ -0,0 +1,17 @@
<form class="layui-form kg-form" method="POST" action="{{ url({'for':'admin.chapter.content','id':chapter.id}) }}">
<div class="layui-form-item">
<textarea name="content" class="layui-hide" id="kg-layedit">{{ read.content }}</textarea>
</div>
<div class="layui-form-item">
<label class="layui-form-label"></label>
<div class="layui-input-block">
<button class="kg-submit layui-btn" lay-submit="true" lay-filter="go">提交</button>
<button type="button" class="kg-back layui-btn layui-btn-primary">返回</button>
</div>
</div>
</form>
{{ partial('partials/layedit') }}

View File

@ -3,13 +3,14 @@
namespace App\Http\Web\Controllers; namespace App\Http\Web\Controllers;
use App\Library\CsrfToken as CsrfTokenService; use App\Library\CsrfToken as CsrfTokenService;
use App\Models\ContentImage as ContentImageModel; use App\Repos\UploadFile as UploadFileRepo;
use App\Services\Pay\Alipay as AlipayService; use App\Services\Pay\Alipay as AlipayService;
use App\Services\Pay\Wxpay as WxpayService; use App\Services\Pay\Wxpay as WxpayService;
use App\Services\Storage as StorageService; use App\Services\Storage as StorageService;
use App\Traits\Response as ResponseTrait; use App\Traits\Response as ResponseTrait;
use App\Traits\Security as SecurityTrait; use App\Traits\Security as SecurityTrait;
use PHPQRCode\QRcode as PHPQRCode; use Phalcon\Text;
use PHPQRCode\QRcode;
class PublicController extends \Phalcon\Mvc\Controller class PublicController extends \Phalcon\Mvc\Controller
{ {
@ -18,24 +19,28 @@ class PublicController extends \Phalcon\Mvc\Controller
use SecurityTrait; use SecurityTrait;
/** /**
* @Get("/content/img/{id:[0-9]+}", name="web.content_img") * @Get("/img/{id:[0-9]+}", name="web.img")
*/ */
public function contentImageAction($id) public function imageAction($id)
{ {
$image = ContentImageModel::findFirst($id); $repo = new UploadFileRepo();
if (!$image) { $file = $repo->findById($id);
if ($file && Text::startsWith($file->mime, 'image')) {
$service = new StorageService();
$location = $service->getCiImageUrl($file->path);
$this->response->redirect($location);
} else {
$this->response->setStatusCode(404); $this->response->setStatusCode(404);
return $this->response; return $this->response;
} }
$storageService = new StorageService();
$location = $storageService->getCiImageUrl($image->path);
$this->response->redirect($location);
} }
/** /**
@ -49,7 +54,7 @@ class PublicController extends \Phalcon\Mvc\Controller
$url = urldecode($text); $url = urldecode($text);
PHPQRcode::png($url, false, $level, $size); QRcode::png($url, false, $level, $size);
$this->response->send(); $this->response->send();

View File

@ -1,7 +1,7 @@
{% if pager.total_pages > 0 %} {% if pager.total_pages > 0 %}
<div class="review-list"> <div class="review-list">
{% for item in pager.items %} {% for item in pager.items %}
{% set item.answer = item.answer ? item.answer : '稍安勿燥,请耐心等待回复吧' %} {% set item.answer = item.answer ? item.answer : '请耐心等待回复吧' %}
{% set owner_url = url({'for':'web.user.show','id':item.owner.id}) %} {% set owner_url = url({'for':'web.user.show','id':item.owner.id}) %}
{% set consult_url = url({'for':'web.consult.show','id':item.id}) %} {% set consult_url = url({'for':'web.consult.show','id':item.id}) %}
{% set like_url = url({'for':'web.consult.like','id':item.id}) %} {% set like_url = url({'for':'web.consult.like','id':item.id}) %}

View File

@ -27,7 +27,7 @@
</thead> </thead>
<tbody> <tbody>
{% for item in pager.items %} {% for item in pager.items %}
{% set answer = item.answer ? item.answer : '<span class="gray">稍安勿燥,请耐心等待回复吧</span>' %} {% set answer = item.answer ? item.answer : '请耐心等待回复吧' %}
{% set show_url = url({'for':'web.consult.show','id':item.id}) %} {% set show_url = url({'for':'web.consult.show','id':item.id}) %}
{% set edit_url = url({'for':'web.consult.edit','id':item.id}) %} {% set edit_url = url({'for':'web.consult.edit','id':item.id}) %}
{% set delete_url = url({'for':'web.consult.delete','id':item.id}) %} {% set delete_url = url({'for':'web.consult.delete','id':item.id}) %}

View File

@ -275,7 +275,7 @@ function kg_time_ago($time)
if ($diff > 365 * 86400) { if ($diff > 365 * 86400) {
return date('Y-m-d', $time); return date('Y-m-d', $time);
} elseif ($diff > 30 * 86400) { } elseif ($diff > 30 * 86400) {
return floor($diff / 30 / 86400) . '月前'; return floor($diff / 30 / 86400) . '月前';
} elseif ($diff > 7 * 86400) { } elseif ($diff > 7 * 86400) {
return floor($diff / 7 / 86400) . '周前'; return floor($diff / 7 / 86400) . '周前';
} elseif ($diff > 86400) { } elseif ($diff > 86400) {

View File

@ -5,53 +5,53 @@ namespace App\Library\Utils;
class FileInfo class FileInfo
{ {
public static function isVideo($mine) public static function isVideo($mime)
{ {
$case1 = self::isSecure($mine); $case1 = self::isSecure($mime);
$case2 = strpos($mine, 'video') !== false; $case2 = strpos($mime, 'video') !== false;
return $case1 && $case2; return $case1 && $case2;
} }
public static function isAudio($mine) public static function isAudio($mime)
{ {
$case1 = self::isSecure($mine); $case1 = self::isSecure($mime);
$case2 = strpos($mine, 'audio') !== false; $case2 = strpos($mime, 'audio') !== false;
return $case1 && $case2; return $case1 && $case2;
} }
public static function isImage($mine) public static function isImage($mime)
{ {
$case1 = self::isSecure($mine); $case1 = self::isSecure($mime);
$case2 = strpos($mine, 'image') !== false; $case2 = strpos($mime, 'image') !== false;
return $case1 && $case2; return $case1 && $case2;
} }
public static function isSecure($mine) public static function isSecure($mime)
{ {
return in_array($mine, self::getMineTypes()); return in_array($mime, self::getMimeTypes());
} }
public static function getMineType($file) public static function getMimeType($file)
{ {
$info = new \finfo(FILEINFO_MIME_TYPE); $info = new \finfo(FILEINFO_MIME_TYPE);
return $info->file($file); return $info->file($file);
} }
public static function getMineTypeByExt($ext) public static function getMimeTypeByExt($ext)
{ {
$mineTypes = self::getMineTypes(); $mimeTypes = self::getMimeTypes();
return $mineTypes[$ext] ?? null; return $mimeTypes[$ext] ?? null;
} }
public static function getMineTypes() public static function getMimeTypes()
{ {
return [ return [
'aac' => 'audio/aac', 'aac' => 'audio/aac',

View File

@ -5,12 +5,6 @@ namespace App\Models;
class ChapterRead extends Model class ChapterRead extends Model
{ {
/**
* 格式类型
*/
const FORMAT_HTML = 'html';
const FORMAT_MARKDOWN = 'markdown';
/** /**
* 主键编号 * 主键编号
* *
@ -39,13 +33,6 @@ class ChapterRead extends Model
*/ */
public $content; public $content;
/**
* 格式
*
* @var string
*/
public $format;
/** /**
* 创建时间 * 创建时间
* *
@ -82,12 +69,4 @@ class ChapterRead extends Model
$this->update_time = time(); $this->update_time = time();
} }
public static function formatTypes()
{
return [
self::FORMAT_HTML => 'html',
self::FORMAT_MARKDOWN => 'markdown',
];
}
} }

View File

@ -66,7 +66,7 @@ class ContentImage extends Model
public function beforeUpdate() public function beforeUpdate()
{ {
$this->update_at = time(); $this->update_time = time();
} }
} }

100
app/Models/UploadFile.php Normal file
View File

@ -0,0 +1,100 @@
<?php
namespace App\Models;
use Phalcon\Mvc\Model\Behavior\SoftDelete;
class UploadFile extends Model
{
/**
* 主键编号
*
* @var int
*/
public $id;
/**
* 名称
*
* @var string
*/
public $name;
/**
* 路径
*
* @var string
*/
public $path;
/**
* mime
*
* @var string
*/
public $mime;
/**
* md5
*
* @var string
*/
public $md5;
/**
* 大小
*
* @var int
*/
public $size;
/**
* 删除标识
*
* @var int
*/
public $deleted;
/**
* 创建时间
*
* @var int
*/
public $create_time;
/**
* 更新时间
*
* @var int
*/
public $update_time;
public function getSource(): string
{
return 'kg_upload_file';
}
public function initialize()
{
parent::initialize();
$this->addBehavior(
new SoftDelete([
'field' => 'deleted',
'value' => 1,
])
);
}
public function beforeCreate()
{
$this->create_time = time();
}
public function beforeUpdate()
{
$this->update_time = time();
}
}

32
app/Repos/UploadFile.php Normal file
View File

@ -0,0 +1,32 @@
<?php
namespace App\Repos;
use App\Models\UploadFile as UploadFileModel;
use Phalcon\Mvc\Model;
class UploadFile extends Repository
{
/**
* @param int $id
* @return UploadFileModel|Model|bool
*/
public function findById($id)
{
return UploadFileModel::findFirst($id);
}
/**
* @param string $md5
* @return UploadFileModel|Model|bool
*/
public function findByMd5($md5)
{
return UploadFileModel::findFirst([
'conditions' => 'md5 = :md5:',
'bind' => ['md5' => $md5],
]);
}
}

View File

@ -48,7 +48,9 @@ class CourseStats extends Service
public function updateScore($courseId) public function updateScore($courseId)
{ {
/**
* @todo 计算综合评分
*/
} }
public function updateReadAttrs($courseId) public function updateReadAttrs($courseId)

View File

@ -21,7 +21,7 @@ class ConsultReply extends FrontendService
$validator = new ConsultValidator(); $validator = new ConsultValidator();
$validator->checkTeacher($consult, $user); $validator->checkReplyPriv($consult, $user);
$answer = $validator->checkAnswer($post['answer']); $answer = $validator->checkAnswer($post['answer']);

View File

@ -21,9 +21,7 @@ class ConsultUpdate extends FrontendService
$validator = new ConsultValidator(); $validator = new ConsultValidator();
$validator->checkOwner($user->id, $consult->owner_id); $validator->checkEditPriv($consult, $user);
$validator->checkConsultEdit($consult);
$data = []; $data = [];

View File

@ -3,13 +3,22 @@
namespace App\Services; namespace App\Services;
use App\Library\Utils\FileInfo; use App\Library\Utils\FileInfo;
use App\Models\ContentImage as ContentImageModel; use App\Models\UploadFile as UploadFileModel;
use App\Repos\UploadFile as UploadFileRepo;
use Phalcon\Logger\Adapter\File as FileLogger; use Phalcon\Logger\Adapter\File as FileLogger;
use Qcloud\Cos\Client as CosClient; use Qcloud\Cos\Client as CosClient;
class Storage extends Service class Storage extends Service
{ {
/**
* 文件类型
*/
const TYPE_IMAGE = 'image';
const TYPE_VIDEO = 'video';
const TYPE_AUDIO = 'audio';
const TYPE_FILE = 'file';
/** /**
* @var array * @var array
*/ */
@ -50,74 +59,99 @@ class Storage extends Service
/** /**
* 上传封面图片 * 上传封面图片
* *
* @return mixed * @return UploadFileModel|bool
*/ */
public function uploadCoverImage() public function uploadCoverImage()
{ {
return $this->uploadImage('/img/cover/'); return $this->upload('/img/cover/', self::TYPE_IMAGE);
} }
/** /**
* 上传内容图片 * 上传编辑器图片
* *
* @return string|bool * @return UploadFileModel|bool
*/ */
public function uploadContentImage() public function uploadEditorImage()
{ {
$path = $this->uploadImage('/img/content/'); return $this->upload('/img/editor/', self::TYPE_IMAGE);
if (!$path) return false;
$contentImage = new ContentImageModel();
$contentImage->path = $path;
$contentImage->create();
return $this->url->get([
'for' => 'web.content_img',
'id' => $contentImage->id,
]);
} }
/** /**
* 上传头像图片 * 上传头像图片
* *
* @return string|bool * @return UploadFileModel|bool
*/ */
public function uploadAvatarImage() public function uploadAvatarImage()
{ {
return $this->uploadImage('/img/avatar/'); return $this->upload('/img/avatar/', self::TYPE_IMAGE);
} }
/** /**
* 上传图片 * 上传im图片
*
* @return UploadFileModel|bool
*/
public function uploadImImage()
{
return $this->upload('/im/img/', self::TYPE_IMAGE);
}
/**
* 上传im文件
*/
public function uploadImFile()
{
return $this->upload('/im/file/', self::TYPE_FILE);
}
/**
* 上传文件
* *
* @param string $prefix * @param string $prefix
* @return string|bool * @param string $type
* @return UploadFileModel|bool
*/ */
public function uploadImage($prefix = '') protected function upload($prefix = '', $type = self::TYPE_IMAGE)
{ {
$paths = []; $list = [];
if ($this->request->hasFiles(true)) { if ($this->request->hasFiles(true)) {
$files = $this->request->getUploadedFiles(true); $files = $this->request->getUploadedFiles(true);
$uploadFileRepo = new UploadFileRepo();
foreach ($files as $file) { foreach ($files as $file) {
if (!FileInfo::isImage($file->getRealType())) {
if ($this->checkUploadFile($file->getRealType(), $type) == false) {
continue; continue;
} }
$extension = $this->getFileExtension($file->getName());
$keyName = $this->generateFileName($extension, $prefix); $md5 = md5_file($file->getTempName());
$path = $this->putFile($keyName, $file->getTempName());
if ($path) { $uploadFile = $uploadFileRepo->findByMd5($md5);
$paths[] = $path;
if ($uploadFile == false) {
$extension = $this->getFileExtension($file->getName());
$keyName = $this->generateFileName($extension, $prefix);
$path = $this->putFile($keyName, $file->getTempName());
$uploadFile = new UploadFileModel();
$uploadFile->mime = $file->getRealType();
$uploadFile->size = $file->getSize();
$uploadFile->path = $path;
$uploadFile->md5 = $md5;
$uploadFile->create();
} }
$list[] = $uploadFile;
} }
} }
return $paths[0] ?: false; return $list[0] ?: false;
} }
/** /**
@ -127,7 +161,7 @@ class Storage extends Service
* @param string $body * @param string $body
* @return string|bool * @return string|bool
*/ */
public function putString($key, $body) protected function putString($key, $body)
{ {
$bucket = $this->settings['bucket_name']; $bucket = $this->settings['bucket_name'];
@ -158,7 +192,7 @@ class Storage extends Service
* @param string $fileName * @param string $fileName
* @return mixed string|bool * @return mixed string|bool
*/ */
public function putFile($key, $fileName) protected function putFile($key, $fileName)
{ {
$bucket = $this->settings['bucket_name']; $bucket = $this->settings['bucket_name'];
@ -190,7 +224,7 @@ class Storage extends Service
* @param string $key * @param string $key
* @return string|bool * @return string|bool
*/ */
public function deleteFile($key) protected function deleteObject($key)
{ {
$bucket = $this->settings['bucket_name']; $bucket = $this->settings['bucket_name'];
@ -292,12 +326,39 @@ class Storage extends Service
return strtolower($extension); return strtolower($extension);
} }
/**
* 检查上传文件
*
* @param string $mime
* @param string $type
* @return bool
*/
protected function checkUploadFile($mime, $type)
{
switch ($type) {
case self::TYPE_IMAGE:
$result = FileInfo::isImage($mime);
break;
case self::TYPE_VIDEO:
$result = FileInfo::isVideo($mime);
break;
case self::TYPE_AUDIO:
$result = FileInfo::isAudio($mime);
break;
default:
$result = FileInfo::isSecure($mime);
break;
}
return $result;
}
/** /**
* 获取CosClient * 获取CosClient
* *
* @return CosClient * @return CosClient
*/ */
public function getCosClient() protected function getCosClient()
{ {
$secret = $this->getSectionSettings('secret'); $secret = $this->getSectionSettings('secret');

View File

@ -43,8 +43,8 @@ class Throttle extends Service
{ {
$authUser = $this->getAuthUser(); $authUser = $this->getAuthUser();
if (!empty($authUser->id)) { if (!empty($authUser['id'])) {
return md5($authUser->id); return md5($authUser['id']);
} }
$httpHost = $this->request->getHttpHost(); $httpHost = $this->request->getHttpHost();

View File

@ -8,7 +8,6 @@ use App\Exceptions\BadRequest as BadRequestException;
use App\Models\Chapter as ChapterModel; use App\Models\Chapter as ChapterModel;
use App\Models\Course as CourseModel; use App\Models\Course as CourseModel;
use App\Repos\Chapter as ChapterRepo; use App\Repos\Chapter as ChapterRepo;
use App\Repos\Course as CourseRepo;
class Chapter extends Validator class Chapter extends Validator
{ {
@ -138,21 +137,17 @@ class Chapter extends Validator
public function checkPublishAbility(ChapterModel $chapter) public function checkPublishAbility(ChapterModel $chapter)
{ {
$courseRepo = new CourseRepo();
$course = $courseRepo->findById($chapter->course_id);
$attrs = $chapter->attrs; $attrs = $chapter->attrs;
if ($course->model == CourseModel::MODEL_VOD) { if ($chapter->model == CourseModel::MODEL_VOD) {
if ($attrs['duration'] == 0) { if ($attrs['duration'] == 0) {
throw new BadRequestException('chapter.vod_not_ready'); throw new BadRequestException('chapter.vod_not_ready');
} }
} elseif ($course->model == CourseModel::MODEL_LIVE) { } elseif ($chapter->model == CourseModel::MODEL_LIVE) {
if ($attrs['start_time'] == 0) { if ($attrs['start_time'] == 0) {
throw new BadRequestException('chapter.live_time_empty'); throw new BadRequestException('chapter.live_time_empty');
} }
} elseif ($course->model == CourseModel::MODEL_READ) { } elseif ($chapter->model == CourseModel::MODEL_READ) {
if ($attrs['word_count'] == 0) { if ($attrs['word_count'] == 0) {
throw new BadRequestException('chapter.read_not_ready'); throw new BadRequestException('chapter.read_not_ready');
} }

View File

@ -9,7 +9,7 @@ class ChapterRead extends Validator
public function checkContent($content) public function checkContent($content)
{ {
$value = $this->filter->sanitize($content, ['trim']); $value = $this->filter->sanitize($content, ['trim', 'striptags']);
$length = kg_strlen($value); $length = kg_strlen($value);
@ -17,7 +17,7 @@ class ChapterRead extends Validator
throw new BadRequestException('chapter_read.content_too_short'); throw new BadRequestException('chapter_read.content_too_short');
} }
if ($length > 65535) { if ($length > 60000) {
throw new BadRequestException('chapter_read.content_too_long'); throw new BadRequestException('chapter_read.content_too_long');
} }

View File

@ -92,7 +92,7 @@ class Consult extends Validator
return $status; return $status;
} }
public function checkTeacher(ConsultModel $consult, UserModel $user) public function checkReplyPriv(ConsultModel $consult, UserModel $user)
{ {
$repo = new CourseRepo(); $repo = new CourseRepo();
@ -110,8 +110,10 @@ class Consult extends Validator
} }
} }
public function checkConsultEdit(ConsultModel $consult) public function checkEditPriv(ConsultModel $consult, UserModel $user)
{ {
$this->checkOwner($user->id, $consult->owner_id);
/** /**
* (1)已回复不允许修改提问 * (1)已回复不允许修改提问
* (2)发表三天以后不能修改提问 * (2)发表三天以后不能修改提问

View File

@ -108,11 +108,11 @@ class Course extends Validator
public function checkDetails($details) public function checkDetails($details)
{ {
$value = $this->filter->sanitize($details, ['trim']); $value = $this->filter->sanitize($details, ['trim', 'striptags']);
$length = kg_strlen($value); $length = kg_strlen($value);
if ($length > 3000) { if ($length > 5000) {
throw new BadRequestException('course.details_too_long'); throw new BadRequestException('course.details_too_long');
} }

View File

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

View File

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

View File

@ -1,7 +1,7 @@
{ {
"require": { "require": {
"ext-phalcon": "~3.4", "ext-phalcon": "~3.4",
"ext-redis": "~4.3", "ext-redis": "~5.0",
"ext-pdo": "*", "ext-pdo": "*",
"ext-json": "*", "ext-json": "*",
"ext-fileinfo": "*", "ext-fileinfo": "*",
@ -19,7 +19,8 @@
"whichbrowser/parser": "^2.0", "whichbrowser/parser": "^2.0",
"hightman/xunsearch": "^1.4.14", "hightman/xunsearch": "^1.4.14",
"aferrandini/phpqrcode": "1.0.1", "aferrandini/phpqrcode": "1.0.1",
"xiaochong0302/ip2region": "^1.0" "xiaochong0302/ip2region": "^1.0",
"joyqi/hyper-down": "dev-master"
}, },
"require-dev": { "require-dev": {
"odan/phinx-migrations-generator": "^4.6", "odan/phinx-migrations-generator": "^4.6",

409
composer.lock generated
View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"content-hash": "d847018715ab8103c3b5e04db1807223", "content-hash": "d95c023895ee3acc72bcd13024da3eb3",
"packages": [ "packages": [
{ {
"name": "aferrandini/phpqrcode", "name": "aferrandini/phpqrcode",
@ -77,16 +77,16 @@
} }
] ]
}, },
"require": { "require": {
"php": "^7.2" "php": "^7.2"
}, },
"require-dev": { "require-dev": {
"doctrine/coding-standard": "^6.0", "doctrine/coding-standard": "^6.0",
"phpstan/phpstan": "^0.11.8", "phpstan/phpstan": "^0.11.8",
"phpunit/phpunit": "^8.2" "phpunit/phpunit": "^8.2"
}, },
"type": "library", "type": "library",
"extra": { "extra": {
"branch-alias": { "branch-alias": {
"dev-master": "1.1.x-dev" "dev-master": "1.1.x-dev"
} }
@ -574,35 +574,77 @@
} }
], ],
"description": "xunsearch php sdk, include yii, yii2 supports", "description": "xunsearch php sdk, include yii, yii2 supports",
"homepage": "http://www.xunsearch.com/", "homepage": "http://www.xunsearch.com/",
"keywords": [ "keywords": [
"search engine", "search engine",
"xunsearch", "xunsearch",
"yii", "yii",
"yii2" "yii2"
], ],
"time": "2019-11-01T02:17:32+00:00" "time": "2019-11-01T02:17:32+00:00"
}, },
{
"name": "joyqi/hyper-down",
"version": "dev-master",
"source": {
"type": "git",
"url": "https://github.com/SegmentFault/HyperDown.git",
"reference": "e9bf808ff8cc1736b15a669e46f1d81f77ce026b"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/SegmentFault/HyperDown/zipball/e9bf808ff8cc1736b15a669e46f1d81f77ce026b",
"reference": "e9bf808ff8cc1736b15a669e46f1d81f77ce026b",
"shasum": "",
"mirrors": [
{
"url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
"preferred": true
}
]
},
"require": {
"php": ">=5.4.0"
},
"type": "library",
"autoload": {
"psr-4": {
"HyperDown\\": "./"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD"
],
"authors": [
{ {
"name": "monolog/monolog", "name": "joyqi",
"version": "2.0.0", "email": "joyqi@segmentfault.com"
"source": { }
"type": "git", ],
"url": "https://github.com/Seldaek/monolog.git", "description": "A light weight markdown parser library",
"reference": "68545165e19249013afd1d6f7485aecff07a2d22" "time": "2019-11-18T09:50:06+00:00"
}, },
"dist": { {
"type": "zip", "name": "monolog/monolog",
"url": "https://api.github.com/repos/Seldaek/monolog/zipball/68545165e19249013afd1d6f7485aecff07a2d22", "version": "2.0.0",
"reference": "68545165e19249013afd1d6f7485aecff07a2d22", "source": {
"shasum": "", "type": "git",
"mirrors": [ "url": "https://github.com/Seldaek/monolog.git",
{ "reference": "68545165e19249013afd1d6f7485aecff07a2d22"
"url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", },
"preferred": true "dist": {
} "type": "zip",
] "url": "https://api.github.com/repos/Seldaek/monolog/zipball/68545165e19249013afd1d6f7485aecff07a2d22",
}, "reference": "68545165e19249013afd1d6f7485aecff07a2d22",
"shasum": "",
"mirrors": [
{
"url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
"preferred": true
}
]
},
"require": { "require": {
"php": "^7.2", "php": "^7.2",
"psr/log": "^1.0.1" "psr/log": "^1.0.1"
@ -693,16 +735,16 @@
"require": { "require": {
"php": ">=5.3.2" "php": ">=5.3.2"
}, },
"require-dev": { "require-dev": {
"phpunit/phpunit": "~4.0|~5.0" "phpunit/phpunit": "~4.0|~5.0"
}, },
"type": "library", "type": "library",
"autoload": { "autoload": {
"psr-4": { "psr-4": {
"Cron\\": "src/Cron/" "Cron\\": "src/Cron/"
} }
}, },
"notification-url": "https://packagist.org/downloads/", "notification-url": "https://packagist.org/downloads/",
"license": [ "license": [
"MIT" "MIT"
], ],
@ -1890,16 +1932,16 @@
"require": { "require": {
"workerman/workerman": ">=3.1.8" "workerman/workerman": ">=3.1.8"
}, },
"type": "library", "type": "library",
"autoload": { "autoload": {
"psr-4": { "psr-4": {
"GatewayWorker\\": "./src" "GatewayWorker\\": "./src"
} }
}, },
"notification-url": "https://packagist.org/downloads/", "notification-url": "https://packagist.org/downloads/",
"license": [ "license": [
"MIT" "MIT"
], ],
"homepage": "http://www.workerman.net", "homepage": "http://www.workerman.net",
"keywords": [ "keywords": [
"communication", "communication",
@ -1960,30 +2002,30 @@
} }
] ]
}, },
"require": { "require": {
"php": ">=5.3" "php": ">=5.3"
}, },
"suggest": { "suggest": {
"ext-event": "For better performance. " "ext-event": "For better performance. "
}, },
"type": "library", "type": "library",
"autoload": { "autoload": {
"psr-4": { "psr-4": {
"Workerman\\": "./" "Workerman\\": "./"
} }
}, },
"notification-url": "https://packagist.org/downloads/", "notification-url": "https://packagist.org/downloads/",
"license": [ "license": [
"MIT" "MIT"
],
"authors": [
{
"name": "walkor",
"email": "walkor@workerman.net",
"homepage": "http://www.workerman.net",
"role": "Developer"
}
], ],
"authors": [
{
"name": "walkor",
"email": "walkor@workerman.net",
"homepage": "http://www.workerman.net",
"role": "Developer"
}
],
"description": "An asynchronous event driven PHP framework for easily building fast, scalable network applications.", "description": "An asynchronous event driven PHP framework for easily building fast, scalable network applications.",
"homepage": "http://www.workerman.net", "homepage": "http://www.workerman.net",
"keywords": [ "keywords": [
@ -2022,16 +2064,16 @@
} }
}, },
"notification-url": "https://packagist.org/downloads/", "notification-url": "https://packagist.org/downloads/",
"license": [ "license": [
"MIT" "MIT"
], ],
"authors": [ "authors": [
{ {
"name": "xiaochong0302", "name": "xiaochong0302",
"email": "xiaochong0302@gmail.com" "email": "xiaochong0302@gmail.com"
} }
], ],
"description": "ip2region扩展包", "description": "ip2region扩展包",
"keywords": [ "keywords": [
"Ip2Region" "Ip2Region"
], ],
@ -2077,16 +2119,16 @@
"Yansongda\\Pay\\": "src" "Yansongda\\Pay\\": "src"
} }
}, },
"notification-url": "https://packagist.org/downloads/", "notification-url": "https://packagist.org/downloads/",
"license": [ "license": [
"MIT" "MIT"
], ],
"authors": [ "authors": [
{ {
"name": "yansongda", "name": "yansongda",
"email": "me@yansongda.cn" "email": "me@yansongda.cn"
} }
], ],
"description": "专注 Alipay 和 WeChat 的支付扩展包", "description": "专注 Alipay 和 WeChat 的支付扩展包",
"keywords": [ "keywords": [
"alipay", "alipay",
@ -2125,16 +2167,16 @@
"phpunit/phpunit": "^7.5", "phpunit/phpunit": "^7.5",
"predis/predis": "^1.1" "predis/predis": "^1.1"
}, },
"suggest": { "suggest": {
"predis/predis": "Allows to use throttle feature" "predis/predis": "Allows to use throttle feature"
}, },
"type": "library", "type": "library",
"autoload": { "autoload": {
"psr-4": { "psr-4": {
"Yansongda\\Supports\\": "src/" "Yansongda\\Supports\\": "src/"
} }
}, },
"notification-url": "https://packagist.org/downloads/", "notification-url": "https://packagist.org/downloads/",
"license": [ "license": [
"MIT" "MIT"
], ],
@ -2390,16 +2432,16 @@
"psr-4": { "psr-4": {
"Cake\\Cache\\": "." "Cake\\Cache\\": "."
} }
}, },
"notification-url": "https://packagist.jp/downloads/", "notification-url": "https://packagist.jp/downloads/",
"license": [ "license": [
"MIT" "MIT"
], ],
"authors": [ "authors": [
{ {
"name": "CakePHP Community", "name": "CakePHP Community",
"homepage": "https://github.com/cakephp/cache/graphs/contributors" "homepage": "https://github.com/cakephp/cache/graphs/contributors"
} }
], ],
"description": "Easy to use Caching library with support for multiple caching backends", "description": "Easy to use Caching library with support for multiple caching backends",
"homepage": "https://cakephp.org", "homepage": "https://cakephp.org",
@ -2718,16 +2760,16 @@
}, },
"notification-url": "https://packagist.jp/downloads/", "notification-url": "https://packagist.jp/downloads/",
"license": [ "license": [
"MIT" "MIT"
], ],
"authors": [ "authors": [
{ {
"name": "CakePHP Community", "name": "CakePHP Community",
"homepage": "https://github.com/cakephp/utility/graphs/contributors" "homepage": "https://github.com/cakephp/utility/graphs/contributors"
} }
], ],
"description": "CakePHP Utility classes such as Inflector, String, Hash, and Security", "description": "CakePHP Utility classes such as Inflector, String, Hash, and Security",
"homepage": "https://cakephp.org", "homepage": "https://cakephp.org",
"keywords": [ "keywords": [
"cakephp", "cakephp",
"hash", "hash",
@ -3005,23 +3047,23 @@
"overtrue/phplint": "^1.1", "overtrue/phplint": "^1.1",
"phpstan/phpstan-shim": "^0.11", "phpstan/phpstan-shim": "^0.11",
"phpunit/phpunit": "^7.0", "phpunit/phpunit": "^7.0",
"squizlabs/php_codesniffer": "^3.4" "squizlabs/php_codesniffer": "^3.4"
}, },
"bin": [ "bin": [
"./bin/phinx-migrations" "./bin/phinx-migrations"
], ],
"type": "library", "type": "library",
"autoload": { "autoload": {
"psr-4": { "psr-4": {
"Odan\\Migration\\": "src/Migration/" "Odan\\Migration\\": "src/Migration/"
} }
}, },
"notification-url": "https://packagist.jp/downloads/", "notification-url": "https://packagist.jp/downloads/",
"license": [ "license": [
"MIT" "MIT"
], ],
"description": "Migration generator for Phinx", "description": "Migration generator for Phinx",
"homepage": "https://github.com/odan/phinx-migrations-generator", "homepage": "https://github.com/odan/phinx-migrations-generator",
"keywords": [ "keywords": [
"database", "database",
"generator", "generator",
@ -3106,16 +3148,16 @@
} }
] ]
}, },
"require": { "require": {
"php": ">=5.3.0" "php": ">=5.3.0"
}, },
"type": "library", "type": "library",
"extra": { "extra": {
"branch-alias": { "branch-alias": {
"dev-master": "1.0.x-dev" "dev-master": "1.0.x-dev"
} }
}, },
"autoload": { "autoload": {
"psr-4": { "psr-4": {
"Psr\\Container\\": "src/" "Psr\\Container\\": "src/"
} }
@ -3715,16 +3757,16 @@
], ],
"authors": [ "authors": [
{ {
"name": "Nicolas Grekas", "name": "Nicolas Grekas",
"email": "p@tchwork.com" "email": "p@tchwork.com"
}, },
{ {
"name": "Symfony Community", "name": "Symfony Community",
"homepage": "https://symfony.com/contributors" "homepage": "https://symfony.com/contributors"
} }
], ],
"description": "Generic abstractions related to writing services", "description": "Generic abstractions related to writing services",
"homepage": "https://symfony.com", "homepage": "https://symfony.com",
"keywords": [ "keywords": [
"abstractions", "abstractions",
"contracts", "contracts",
@ -3837,16 +3879,16 @@
} }
] ]
}, },
"require": { "require": {
"php": "^7.2.5", "php": "^7.2.5",
"symfony/polyfill-ctype": "~1.8" "symfony/polyfill-ctype": "~1.8"
}, },
"conflict": { "conflict": {
"symfony/console": "<4.4" "symfony/console": "<4.4"
}, },
"require-dev": { "require-dev": {
"symfony/console": "^4.4|^5.0" "symfony/console": "^4.4|^5.0"
}, },
"suggest": { "suggest": {
"symfony/console": "For validating YAML files using the lint command" "symfony/console": "For validating YAML files using the lint command"
}, },
@ -3858,16 +3900,16 @@
}, },
"autoload": { "autoload": {
"psr-4": { "psr-4": {
"Symfony\\Component\\Yaml\\": "" "Symfony\\Component\\Yaml\\": ""
}, },
"exclude-from-classmap": [ "exclude-from-classmap": [
"/Tests/" "/Tests/"
] ]
}, },
"notification-url": "https://packagist.jp/downloads/", "notification-url": "https://packagist.jp/downloads/",
"license": [ "license": [
"MIT" "MIT"
], ],
"authors": [ "authors": [
{ {
"name": "Fabien Potencier", "name": "Fabien Potencier",
@ -3941,14 +3983,17 @@
], ],
"aliases": [], "aliases": [],
"minimum-stability": "stable", "minimum-stability": "stable",
"stability-flags": [], "stability-flags": {
"joyqi/hyper-down": 20
},
"prefer-stable": false, "prefer-stable": false,
"prefer-lowest": false, "prefer-lowest": false,
"platform": { "platform": {
"ext-phalcon": "~3.4", "ext-phalcon": "~3.4",
"ext-redis": "~4.3", "ext-redis": "~5.0",
"ext-pdo": "*", "ext-pdo": "*",
"ext-json": "*" "ext-json": "*",
"ext-fileinfo": "*"
}, },
"platform-dev": [], "platform-dev": [],
"plugin-api-version": "1.1.0" "plugin-api-version": "1.1.0"

View File

@ -103,7 +103,7 @@ $error['course.title_too_short'] = '标题太短少于5个字符';
$error['course.title_too_long'] = '标题太长多于50个字符'; $error['course.title_too_long'] = '标题太长多于50个字符';
$error['course.summary_too_long'] = '标题太长多于255个字符'; $error['course.summary_too_long'] = '标题太长多于255个字符';
$error['course.keywords_too_long'] = '关键字太长多于100个字符'; $error['course.keywords_too_long'] = '关键字太长多于100个字符';
$error['course.details_too_long'] = '详情太长(多于3000个字符'; $error['course.details_too_long'] = '详情太长(多于5000个字符';
$error['course.invalid_model'] = '无效的模型类别'; $error['course.invalid_model'] = '无效的模型类别';
$error['course.invalid_level'] = '无效的难度级别'; $error['course.invalid_level'] = '无效的难度级别';
$error['course.invalid_cover'] = '无效的封面'; $error['course.invalid_cover'] = '无效的封面';
@ -184,7 +184,7 @@ $error['chapter_live.time_too_long'] = '直播时间太长超过3小时';
*/ */
$error['chapter_read.not_found'] = '文章不存在'; $error['chapter_read.not_found'] = '文章不存在';
$error['chapter_read.content_too_short'] = '文章内容太短少于10个字符'; $error['chapter_read.content_too_short'] = '文章内容太短少于10个字符';
$error['chapter_read.content_too_long'] = '文章内容太长多于65535个字符)'; $error['chapter_read.content_too_long'] = '文章内容太长多于60000个字符)';
/** /**
* 评价相关 * 评价相关
@ -229,7 +229,7 @@ $error['help.not_found'] = '帮助不存在';
$error['help.title_too_short'] = '标题太短少于2个字符'; $error['help.title_too_short'] = '标题太短少于2个字符';
$error['help.title_too_long'] = '标题太长多于50个字符'; $error['help.title_too_long'] = '标题太长多于50个字符';
$error['help.content_too_short'] = '内容太短少于10个字符'; $error['help.content_too_short'] = '内容太短少于10个字符';
$error['help.content_too_long'] = '内容太长(多于3000个字符'; $error['help.content_too_long'] = '内容太长(多于60000个字符';
$error['help.invalid_priority'] = '无效的排序数值范围1-255'; $error['help.invalid_priority'] = '无效的排序数值范围1-255';
$error['help.invalid_publish_status'] = '无效的发布状态'; $error['help.invalid_publish_status'] = '无效的发布状态';