diff --git a/plugins/Common.php b/plugins/Common.php index 993546a..8bfddec 100644 --- a/plugins/Common.php +++ b/plugins/Common.php @@ -14,6 +14,9 @@ Class Common { ' ', ';', ';', + '.', + '%', + ':', ); return str_replace($findChars, '', $str); diff --git a/plugins/Html.php b/plugins/Html.php index fd179c9..67cc5a7 100644 --- a/plugins/Html.php +++ b/plugins/Html.php @@ -94,4 +94,29 @@ eof; return $gacode; } + + //根据收藏和分类,获取单个收藏视频的所在分类 + public static function getFavsTags($filename, $tags) { + $fileTags = array(); + + foreach($tags as $tag_id => $item) { + if (in_array($filename, $item['files'])) { + array_push($fileTags, $item['name']); + } + } + + return $fileTags; + } + + //获取只包含分类名的数组 + public static function getTagNames($tags) { + $tmp_arr = array(); + + foreach ($tags as $id => $tag) { + array_push($tmp_arr, $tag['name']); + } + + return $tmp_arr; + } + } diff --git a/themes/tajian/controller/FrontapiController.php b/themes/tajian/controller/FrontapiController.php index fb9ab45..7e32fae 100644 --- a/themes/tajian/controller/FrontapiController.php +++ b/themes/tajian/controller/FrontapiController.php @@ -158,7 +158,8 @@ Class FrontApiController extends SiteController { $done = $done && $this->saveBotTask($shareUrl); if (!empty($tagName)) { - $done = $done && $this->saveVideoToTag($shareUrl, $tagName); + $video_id = $this->getVideoId($shareUrl); + $done = $done && $this->saveVideoToTag($video_id, $tagName); } //保存任务日志 @@ -189,13 +190,14 @@ Class FrontApiController extends SiteController { //保存分享视频到tag分类 //TODO: 如果高并发,需要避免数据被覆盖的问题 - protected function saveVideoToTag($url, $tagName) { + protected function saveVideoToTag($video_id, $tagName) { + if (empty($video_id) || empty($tagName)) {return false;} + $tag_dir = __DIR__ . '/../../../www/' . FSC::$app['config']['content_directory'] . FSC::$app['config']['tajian']['tag_dir']; if (!is_dir($tag_dir)) { mkdir($tag_dir, 0755, true); } - $video_id = $this->getVideoId($url); $filepath = realpath($tag_dir) . "/{$tagName}.txt"; if (file_exists($filepath)) { $content = file_get_contents($filepath); @@ -215,6 +217,35 @@ Class FrontApiController extends SiteController { } } + //从分类中删除视频 + protected function deleteVideoFromTag($filename, $tagName) { + if (empty($filename) || empty($tagName)) {return false;} + + $tag_dir = __DIR__ . '/../../../www/' . FSC::$app['config']['content_directory'] . FSC::$app['config']['tajian']['tag_dir']; + if (!is_dir($tag_dir)) { + mkdir($tag_dir, 0755, true); + } + + $filepath = realpath($tag_dir) . "/{$tagName}.txt"; + if (file_exists($filepath)) { + $content = file_get_contents($filepath); + $videos = explode("\n", $content); + $last_id = array_pop($videos); + if (!empty($last_id)) { + array_push($videos, $last_id); + } + + $key = array_search($filename, $videos); + if ($key !== false) { + unset($videos[$key]); + } + + return file_put_contents($filepath, implode("\n", $videos)) !== false; + } + + return false; + } + //保存任务日志 protected function saveTaskLog($url, $title, $tagName) { $logFile = __DIR__ . '/../../../runtime/' . FSC::$app['config']['tajian']['task_log']; @@ -327,6 +358,51 @@ eof; return $done; } + //删除收藏的视频相关的所有文件 + protected function deleteVideoFiles($filename) { + $data_dir = __DIR__ . '/../../../www/' . FSC::$app['config']['content_directory'] . FSC::$app['config']['tajian']['data_dir']; + if (!is_dir($data_dir)) { + return false; + } + + $done = true; + + try { + $data_dir = realpath($data_dir); + + //删除.url文件 + $filepath_url = "{$data_dir}/{$filename}.url"; + if (file_exists($filepath_url)) { + unlink($filepath_url); + } + + //删除标题 + $filepath_title = "{$data_dir}/{$filename}_title.txt"; + if (file_exists($filepath_title)) { + unlink($filepath_title); + } + + //删除图片文件 + $imgTypes = array('jpg', 'jpeg', 'png', 'gif', 'webp'); + foreach ($imgTypes as $cover_type) { + $filepath_cover = "{$data_dir}/{$filename}.{$cover_type}"; + if (file_exists($filepath_cover)) { + unlink($filepath_cover); + } + } + + //删除图片描述文件 + $filepath_desc = "{$data_dir}/{$filename}_cover.txt"; + if (file_exists($filepath_desc)) { + unlink($filepath_desc); + } + }catch(Exception $err) { + $done = false; + } + + return $done; + } + //HeroUnion任务数据通知回传接口 /** * task_id @@ -785,13 +861,10 @@ eof; $tags_current = $this->sortTags($menus_sorted, $tags_current); } //获取只包含分类名的数组 - $tmp_arr = array(); - foreach ($tags_current as $id => $tag) { - array_push($tmp_arr, $tag['name']); - } + $allTags = Html::getTagNames($tags_current); //保存 - $saved = $this->saveTags($tags, $tmp_arr); + $saved = $this->saveTags($tags, $allTags); if (!empty($saved)) { $msg = "分类已保存"; $code = 1; @@ -840,6 +913,8 @@ eof; if (empty($tag_to_delete)) { $err = "参数错误,缺少tag传参"; + }else { + $tag_to_delete = Common::cleanSpecialChars($tag_to_delete); } if (empty($err)) { //如果数据检查通过,尝试保存 @@ -907,28 +982,14 @@ eof; $scanner->setWebRoot(FSC::$app['config']['content_directory']); $dirTree = $scanner->scan(__DIR__ . '/../../../www/' . FSC::$app['config']['content_directory'], 3); - $menus_sorted = array(); //Readme_sort.txt 说明文件内容,一级目录菜单从上到下的排序 - $readmeFile = $scanner->getDefaultReadme(); - if (!empty($readmeFile)) { - if (!empty($readmeFile['sort'])) { - $menus_sorted = explode("\n", $readmeFile['sort']); - } - } - //获取tags分类 $tags_current = $this->getTags($dirTree); - //排序 - if (!empty($menus_sorted) && !empty($tags_current)) { - $tags_current = $this->sortTags($menus_sorted, $tags_current); - } + //获取只包含分类名的数组 - $tmp_arr = array(); - foreach ($tags_current as $id => $tag) { - array_push($tmp_arr, $tag['name']); - } - + $allTags = Html::getTagNames($tags_current); + //最多添加 20 个分类 - if (count($tmp_arr) >= 20) { + if (count($allTags) >= 20) { $err = '最多添加 20 个分类,请合理规划视频分类哦'; }else { //保存 @@ -946,7 +1007,141 @@ eof; return $this->renderJson(compact('code', 'msg', 'err')); } - //TODO: 视频管理 + //视频管理:把视频从分类中删除、添加视频到某个分类、删除视频 + public function actionDeletefav() { + $ip = $this->getUserIp(); + $check_time = 120; //2 分钟内 + $max_time_in_minutes = 60; //最多 60 次 + + $isUserGotRequestLimit = $this->requestLimit($ip, $max_time_in_minutes, $check_time); + if ($isUserGotRequestLimit) { + $this->logError("Request limit got, ip: {$ip}"); + throw new Exception('Oops,操作太快了,请喝杯咖啡休息会吧...'); + } + + //只允许修改自己的数据 + $loginedUser = Common::getUserFromSession(); + if (empty($loginedUser['username'])) { + throw new Exception('Oops,你还没登录哦'); + }else if ( + !empty(FSC::$app['config']['multipleUserUriParse']) + && (empty(FSC::$app['user_id']) || FSC::$app['user_id'] != $loginedUser['username']) + ) { + throw new Exception('Oops,请求地址有误'); + } + + + //返回给视图的变量 + $code = 0; + $msg = ''; + $err = ''; + + //用户提交的数据检查 + $postParams = $this->post(); + if (!empty($postParams)) { + $video_id = $this->post('id', ''); + $video_filename = $this->post('filename', ''); + + if (empty($video_id) || empty($video_filename)) { + $err = "参数错误,缺少id和filename传参"; + }else { + $video_id = Common::cleanSpecialChars($video_id); + $video_filename = Common::cleanSpecialChars($video_filename); + } + + if (empty($err)) { //如果数据检查通过,尝试保存 + //获取已有的分类 + $scanner = new DirScanner(); + $scanner->setWebRoot(FSC::$app['config']['content_directory']); + $dirTree = $scanner->scan(__DIR__ . '/../../../www/' . FSC::$app['config']['content_directory'], 3); + + //获取tags分类 + $tags_current = $this->getTags($dirTree); + + //获取当前视频的所属分类数组 + $myTags = Html::getFavsTags($video_filename, $tags_current); + foreach ($myTags as $item) { + $this->deleteVideoFromTag($video_filename, $item); //从分类中删除此视频 + } + + //删除此视频的所有文件 + $saved = $this->deleteVideoFiles($video_filename); + if (!empty($saved)) { + $msg = "视频已删除"; + $code = 1; + }else { + $err = '视频删除失败,请稍后重试'; + } + } + } + + return $this->renderJson(compact('code', 'msg', 'err')); + } + + public function actionUpdatefavstag() { + $ip = $this->getUserIp(); + $check_time = 120; //2 分钟内 + $max_time_in_minutes = 60; //最多 60 次 + + $isUserGotRequestLimit = $this->requestLimit($ip, $max_time_in_minutes, $check_time); + if ($isUserGotRequestLimit) { + $this->logError("Request limit got, ip: {$ip}"); + throw new Exception('Oops,操作太快了,请喝杯咖啡休息会吧...'); + } + + //只允许修改自己的数据 + $loginedUser = Common::getUserFromSession(); + if (empty($loginedUser['username'])) { + throw new Exception('Oops,你还没登录哦'); + }else if ( + !empty(FSC::$app['config']['multipleUserUriParse']) + && (empty(FSC::$app['user_id']) || FSC::$app['user_id'] != $loginedUser['username']) + ) { + throw new Exception('Oops,请求地址有误'); + } + + + //返回给视图的变量 + $code = 0; + $msg = ''; + $err = ''; + + //用户提交的数据检查 + $postParams = $this->post(); + if (!empty($postParams)) { + $video_id = $this->post('id', ''); + $video_filename = $this->post('filename', ''); + $tagName = $this->post('tag', ''); + $action = $this->post('do', 'remove'); //添加:add,移除:remove + + if (empty($video_id) || empty($video_filename) || empty($tagName)) { + $err = "参数错误,缺少id、filename、tag传参"; + }else { + $video_id = Common::cleanSpecialChars($video_id); + $video_filename = Common::cleanSpecialChars($video_filename); + $tagName = Common::cleanSpecialChars($tagName); + } + + if (empty($err)) { //如果数据检查通过,尝试保存 + $saved = false; + + if ($action == 'add') { + $saved = $this->saveVideoToTag($video_filename, $tagName); //添加视频到分类 + }else { + $saved = $this->deleteVideoFromTag($video_filename, $tagName); //从分类中删除此视频 + } + + if ($saved !== false) { + $msg = "操作完成"; + $code = 1; + }else { + $err = '操作失败,请稍后重试'; + } + } + } + + return $this->renderJson(compact('code', 'msg', 'err')); + } } diff --git a/themes/tajian/controller/MyController.php b/themes/tajian/controller/MyController.php index 7740163..91561e5 100644 --- a/themes/tajian/controller/MyController.php +++ b/themes/tajian/controller/MyController.php @@ -28,6 +28,9 @@ Class MyController extends SiteController { $dirTree = $scanner->scan(__DIR__ . '/../../../www/' . FSC::$app['config']['content_directory'], 3); $scanResults = $scanner->getScanResults(); + //获取目录 + $menus = $scanner->getMenus(); + $readmeFile = $scanner->getDefaultReadme(); if (!empty($readmeFile)) { if (!empty($readmeFile['sort'])) { @@ -40,6 +43,9 @@ Class MyController extends SiteController { $htmlReadme = $scanner->fixMDUrls($readmeFile['realpath'], $htmlReadme); } + //默认显示的目录 + $cateId = $menus[0]['id']; + //获取tags分类 $tags = $this->getTags($dirTree); @@ -53,7 +59,7 @@ Class MyController extends SiteController { $pageTitle = "{$defaultTitle} | " . FSC::$app['config']['site_name']; $params = compact( - 'dirTree', 'scanResults', + 'cateId', 'dirTree', 'scanResults', 'htmlReadme', 'tags', 'nickname' ); return $this->render($viewName, $params, $pageTitle); @@ -80,4 +86,11 @@ Class MyController extends SiteController { return $this->actionIndex($viewName, $defaultTitle); } + //管理收藏 + public function actionFavs() { + $defaultTitle = "管理收藏"; + $viewName = 'favs'; + return $this->actionIndex($viewName, $defaultTitle); + } + } \ No newline at end of file diff --git a/themes/tajian/controller/SiteController.php b/themes/tajian/controller/SiteController.php index 825cae6..d46f3d1 100644 --- a/themes/tajian/controller/SiteController.php +++ b/themes/tajian/controller/SiteController.php @@ -220,6 +220,8 @@ Class SiteController extends Controller { //删除分类 protected function deleteTag($tag) { + if (empty($tag)) {return false;} + $done = false; try { diff --git a/themes/tajian/views/my/favs.php b/themes/tajian/views/my/favs.php new file mode 100644 index 0000000..16b7e0e --- /dev/null +++ b/themes/tajian/views/my/favs.php @@ -0,0 +1,83 @@ + + +
+

勾选视频下方的分类,将该视频归类到对应的分类;取消勾选,则将视频从该分类中移除。

+
+ $file) { + //跳过非.url文件 + if (!in_array($file['extension'], $videoExts) || empty($file['shortcut'])) { + continue; + } + + $snapshot = !empty($file['cover']) ? $imgPreffix . $file['cover'] : '/img/default.png'; + $title = !empty($file['title']) ? Html::mb_substr($file['title'], 0, 33, 'utf-8') : $file['filename']; + + $platform = Html::getShareVideosPlatform($file['shortcut']['url']); + + $pubDate = date('m/d', $file['fstat']['ctime']); + + $imgSrc = $index < 4 ? " src=\"{$snapshot}\"" : ''; + $imgAlt = $index < 4 ? " alt=\"{$title}\"" : ''; + $imgCls = $index < 4 ? '' : 'lazy'; + + $myTags = Html::getFavsTags($file['filename'], $viewData['tags']); + $tagsHtml = ''; + + foreach ($allTags as $tagName) { + $tagChecked = in_array($tagName, $myTags) ? ' checked="checked"' : ''; + $tagsHtml .= << {$tagName} +eof; + } + + echo << +
+ + + + +
+ +
+ {$tagsHtml} +
+
+eof; + } + } + ?> + +
\ No newline at end of file diff --git a/themes/tajian/views/my/index.php b/themes/tajian/views/my/index.php index bda80d1..6c05305 100644 --- a/themes/tajian/views/my/index.php +++ b/themes/tajian/views/my/index.php @@ -21,7 +21,7 @@ if (!empty(FSC::$app['config']['multipleUserUriParse']) && !empty(FSC::$app['use
diff --git a/www/css/tajian.css b/www/css/tajian.css index 1e9276d..d5fe252 100644 --- a/www/css/tajian.css +++ b/www/css/tajian.css @@ -249,6 +249,8 @@ a:link{text-decoration:none;} .tajian_index .kfwx{float:right;border-radius:6px} .btn-primary,.btn-danger,.tajian_index .loginbtn{border:1px solid #50509d;background-color:#50509d;color:#FFF;padding:3px 10px;border-radius:4px;cursor:pointer} .btn-danger{background-color:#d32d2d;border-color:#e96b5b} +.btn-primary:hover,.btn-danger:hover{font-weight:500} +.bi{width:1em;height:1em;display:inline-block;vertical-align:-.125em;fill:currentcolor} /* 注册/登录 */ .twocol label{display:block} @@ -265,6 +267,12 @@ a:link{text-decoration:none;} .tag-item .btn-danger{float:right} .tag-item img{cursor:pointer} +/* 收藏管理 */ +.favmg-item .btn-del{position:absolute;right:5px;bottom:5px} +.favmg-item .act_tags{min-height:40px;background-color:#EEE;padding:4px;margin-right:4px;margin-top:5px} +.favmg-item .act_tags label{display:inline-block;cursor:pointer} +.favmg-item .act_tags label input{cursor:pointer} + /* layout index */ body.layout_index{background-color:#e5f1f3} .layout_index .top_nav{padding-left:20%;padding-right:20%} diff --git a/www/img/trash.svg b/www/img/trash.svg new file mode 100644 index 0000000..5162aac --- /dev/null +++ b/www/img/trash.svg @@ -0,0 +1,4 @@ + + + + diff --git a/www/js/tajian.js b/www/js/tajian.js index c3ae765..4f5d931 100644 --- a/www/js/tajian.js +++ b/www/js/tajian.js @@ -11,6 +11,8 @@ var taJian = { saveTags: '/frontapi/savetags', //保存分类 deleteTag: '/frontapi/deletetag', //删除分类 addTag: '/frontapi/addtag', //添加分类 + updateFavsTag: '/frontapi/updatefavstag', //修改视频的分类 + deleteFav: '/frontapi/deletefav', //删除收藏的视频 sendSmsCode: '/frontapi/sendsmscode', //发送短信验证码 register: '/frontapi/createuser', //注册 login: '/frontapi/loginuser' //登入 @@ -538,4 +540,66 @@ if ($('#tag_new_form').get(0)) { }); } +// 视频管理 +if ($('#favmg').get(0)) { + $('#favmg .act_tags input[type=checkbox]').change(function(e) { + var checkbox = e.target; + var action = checkbox.checked ? 'add' : 'remove'; + + var label = $(checkbox).parents('label'), + video_id = label.attr('data-video-id'), + filename = label.attr('data-filename'), + tag = label.attr('data-tag'); + + $(checkbox).prop('disabled', true); + var datas = { + 'do': action, + 'id': video_id, + 'filename': filename, + 'tag': tag + }; + publicAjax(taJian.apis.updateFavsTag, 'POST', datas, function (data) { + $(checkbox).prop('disabled', false); + if (data.code != 1) { + $(checkbox).prop('checked', !checkbox.checked); + alert(data.err); + } + }, function (jqXHR, textStatus, errorThrown) { + $(checkbox).prop('disabled', false); + alert('网络请求失败,请重试。'); + }); + }); + + $('#favmg .favmg-item .btn-del').click(function(e) { + var target = e.target; + + if (target.tagName.toLowerCase() != 'button') { + target = $(target).parents('.btn-del'); + } + + var video_id = $(target).attr('data-video-id'), + filename = $(target).attr('data-filename'); + + //console.log('clicked', video_id, filename); + if (confirm('确定删除此视频吗?')) { + $(target).prop('disabled', true); + var datas = { + 'id': video_id, + 'filename': filename + }; + publicAjax(taJian.apis.deleteFav, 'POST', datas, function (data) { + $(target).prop('disabled', false); + if (data.code == 1) { + location.reload(); + }else { + alert(data.err); + } + }, function (jqXHR, textStatus, errorThrown) { + $(target).prop('disabled', false); + alert('网络请求失败,请重试。'); + }); + } + }); +} + })();