ArticleController.php 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648
  1. <?php
  2. namespace App\Http\Controllers;
  3. use Illuminate\Http\Request;
  4. use Illuminate\Support\Str;
  5. use Illuminate\Support\Facades\DB;
  6. use Illuminate\Support\Facades\Log;
  7. use App\Models\Article;
  8. use App\Models\ArticleCollection;
  9. use App\Models\Collection;
  10. use App\Models\CustomBook;
  11. use App\Models\CustomBookId;
  12. use App\Models\Sentence;
  13. use App\Http\Resources\ArticleResource;
  14. use App\Http\Api\AuthApi;
  15. use App\Http\Api\ShareApi;
  16. use App\Http\Api\StudioApi;
  17. use App\Http\Api\ChannelApi;
  18. use App\Http\Api\SentenceApi;
  19. use App\Tools\OpsLog;
  20. class ArticleController extends Controller
  21. {
  22. public static function userCanRead($user_uid,Article $article){
  23. if($article->status === 30 ){
  24. return true;
  25. }
  26. if(empty($user_uid)){
  27. return false;
  28. }
  29. //私有文章,判断是否为所有者
  30. if($user_uid === $article->owner){
  31. return true;
  32. }
  33. //非所有者
  34. //判断是否为文章协作者
  35. $power = ShareApi::getResPower($user_uid,$article->uid);
  36. if($power >= 10 ){
  37. return true;
  38. }
  39. //无读取权限
  40. //判断文集是否有读取权限
  41. $inCollection = ArticleCollection::where('article_id',$article->uid)
  42. ->select('collect_id')
  43. ->groupBy('collect_id')->get();
  44. if(!$inCollection){
  45. return false;
  46. }
  47. //查找与文章同主人的文集
  48. $collections = Collection::whereIn('uid',$inCollection)
  49. ->where('owner',$article->owner)
  50. ->select('uid')
  51. ->get();
  52. if(!$collections){
  53. return false;
  54. }
  55. //查找与文章同主人的文集是否是共享的
  56. $power = 0;
  57. foreach ($collections as $collection) {
  58. # code...
  59. $currPower = ShareApi::getResPower($user_uid,$collection->uid);
  60. if($currPower >= 10){
  61. return true;
  62. }
  63. }
  64. return false;
  65. }
  66. public static function userCanEdit($user_uid,$article){
  67. if(empty($user_uid)){
  68. return false;
  69. }
  70. //私有文章,判断是否为所有者
  71. if($user_uid === $article->owner){
  72. return true;
  73. }
  74. //非所有者
  75. //判断是否为文章协作者
  76. $power = ShareApi::getResPower($user_uid,$article->uid);
  77. if($power >= 20 ){
  78. return true;
  79. }
  80. //无读取权限
  81. //判断文集是否有读取权限
  82. $inCollection = ArticleCollection::where('article_id',$article->uid)
  83. ->select('collect_id')
  84. ->groupBy('collect_id')->get();
  85. if(!$inCollection){
  86. return false;
  87. }
  88. //查找与文章同主人的文集
  89. $collections = Collection::whereIn('uid',$inCollection)
  90. ->where('owner',$article->owner)
  91. ->select('uid')
  92. ->get();
  93. if(!$collections){
  94. return false;
  95. }
  96. //查找与文章同主人的文集是否是共享的
  97. $power = 0;
  98. foreach ($collections as $collection) {
  99. # code...
  100. $currPower = ShareApi::getResPower($user_uid,$collection->uid);
  101. if($currPower >= 20){
  102. return true;
  103. }
  104. }
  105. return false;
  106. }
  107. public static function userCanManage($user_uid,$studioName){
  108. if(empty($user_uid)){
  109. return false;
  110. }
  111. //判断是否为所有者
  112. if($user_uid === StudioApi::getIdByName($studioName)){
  113. return true;
  114. }else{
  115. return false;
  116. }
  117. }
  118. /**
  119. * Display a listing of the resource.
  120. *
  121. * @return \Illuminate\Http\Response
  122. */
  123. public function index(Request $request)
  124. {
  125. //
  126. $field = ['uid','title','subtitle',
  127. 'summary','owner','lang',
  128. 'status','editor_id','updated_at','created_at'];
  129. if($request->get('content')==="true"){
  130. $field[] = 'content';
  131. $field[] = 'content_type';
  132. }
  133. $table = Article::select($field);
  134. switch ($request->get('view')) {
  135. case 'template':
  136. $studioId = StudioApi::getIdByName($request->get('studio_name'));
  137. $table = $table->where('owner', $studioId);
  138. break;
  139. case 'studio':
  140. # 获取studio内所有 article
  141. $user = AuthApi::current($request);
  142. if(!$user){
  143. return $this->error(__('auth.failed'),[],401);
  144. }
  145. //判断当前用户是否有指定的studio的权限
  146. $studioId = StudioApi::getIdByName($request->get('name'));
  147. if($user['user_uid'] !== $studioId){
  148. return $this->error(__('auth.failed'),[],403);
  149. }
  150. if($request->get('view2','my')==='my'){
  151. $table = $table->where('owner', $studioId);
  152. }else{
  153. //协作
  154. $resList = ShareApi::getResList($studioId,3);
  155. $resId=[];
  156. foreach ($resList as $res) {
  157. $resId[] = $res['res_id'];
  158. }
  159. $table = $table->whereIn('uid', $resId)->where('owner','<>', $studioId);
  160. }
  161. //根据anthology过滤
  162. if($request->has('anthology')){
  163. switch ($request->get('anthology')) {
  164. case 'all':
  165. break;
  166. case 'none':
  167. # 我的文集
  168. $myCollection = Collection::where('owner',$studioId)->select('uid')->get();
  169. //收录在我的文集里面的文章
  170. $articles = ArticleCollection::whereIn('collect_id',$myCollection)
  171. ->select('article_id')->groupBy('article_id')->get();
  172. //不在这些范围之内的文章
  173. $table = $table->whereNotIn('uid',$articles);
  174. break;
  175. default:
  176. $articles = ArticleCollection::where('collect_id',$request->get('anthology'))
  177. ->select('article_id')->get();
  178. $table = $table->whereIn('uid',$articles);
  179. break;
  180. }
  181. }
  182. break;
  183. case 'public':
  184. $table = $table->where('status',30);
  185. break;
  186. default:
  187. $this->error("view error");
  188. break;
  189. }
  190. //处理搜索
  191. if($request->has("search") && !empty($request->get("search"))){
  192. $table = $table->where('title', 'like', "%".$request->get("search")."%");
  193. }
  194. if($request->has("subtitle") && !empty($request->get("subtitle"))){
  195. $table = $table->where('subtitle', 'like', $request->get("subtitle"));
  196. }
  197. //获取记录总条数
  198. $count = $table->count();
  199. //处理排序
  200. $table = $table->orderBy($request->get("order",'updated_at'),
  201. $request->get("dir",'desc'));
  202. //处理分页
  203. $table = $table->skip($request->get("offset",0))
  204. ->take($request->get("limit",1000));
  205. //获取数据
  206. $result = $table->get();
  207. return $this->ok(["rows"=>ArticleResource::collection($result),"count"=>$count]);
  208. }
  209. /**
  210. * Display a listing of the resource.
  211. *
  212. * @return \Illuminate\Http\Response
  213. */
  214. public function showMyNumber(Request $request){
  215. $user = AuthApi::current($request);
  216. if(!$user){
  217. return $this->error(__('auth.failed'));
  218. }
  219. //判断当前用户是否有指定的studio的权限
  220. $studioId = StudioApi::getIdByName($request->get('studio'));
  221. if($user['user_uid'] !== $studioId){
  222. return $this->error(__('auth.failed'));
  223. }
  224. //我的
  225. $my = Article::where('owner', $studioId)->count();
  226. //协作
  227. $resList = ShareApi::getResList($studioId,3);
  228. $resId=[];
  229. foreach ($resList as $res) {
  230. $resId[] = $res['res_id'];
  231. }
  232. $collaboration = Article::whereIn('uid', $resId)->where('owner','<>', $studioId)->count();
  233. return $this->ok(['my'=>$my,'collaboration'=>$collaboration]);
  234. }
  235. /**
  236. * Store a newly created resource in storage.
  237. *
  238. * @param \Illuminate\Http\Request $request
  239. * @return \Illuminate\Http\Response
  240. */
  241. public function store(Request $request)
  242. {
  243. //判断权限
  244. $user = AuthApi::current($request);
  245. if(!$user){
  246. Log::error('未登录');
  247. return $this->error(__('auth.failed'),[],401);
  248. }else{
  249. $user_uid=$user['user_uid'];
  250. }
  251. $canManage = ArticleController::userCanManage($user_uid,$request->get('studio'));
  252. if(!$canManage){
  253. Log::error('userCanManage 失败');
  254. //判断是否有文集权限
  255. if($request->has('anthologyId')){
  256. $currPower = ShareApi::getResPower($user_uid,$request->get('anthologyId'));
  257. if($currPower <= 10){
  258. Log::error('没有文集编辑权限');
  259. return $this->error(__('auth.failed'),[],403);
  260. }
  261. }else{
  262. Log::error('没有文集id');
  263. return $this->error(__('auth.failed'),[],403);
  264. }
  265. }
  266. //权限判断结束
  267. //查询标题是否重复
  268. /*
  269. if(Article::where('title',$request->get('title'))->where('owner',$studioUuid)->exists()){
  270. return $this->error(__('validation.exists'));
  271. }*/
  272. $newArticle = new Article;
  273. DB::transaction(function() use($user,$request,$newArticle){
  274. $studioUuid = StudioApi::getIdByName($request->get('studio'));
  275. //新建文章,加入文集必须都成功。否则回滚
  276. $newArticle->id = app('snowflake')->id();
  277. $newArticle->uid = Str::uuid();
  278. $newArticle->title = $request->get('title');
  279. $newArticle->lang = $request->get('lang');
  280. $newArticle->owner = $studioUuid;
  281. $newArticle->owner_id = $user['user_id'];
  282. $newArticle->editor_id = $user['user_id'];
  283. $newArticle->parent = $request->get('parentId');
  284. $newArticle->create_time = time()*1000;
  285. $newArticle->modify_time = time()*1000;
  286. $newArticle->save();
  287. OpsLog::debug($user['user_uid'],$newArticle);
  288. if(Str::isUuid($request->get('anthologyId'))){
  289. $articleMap = new ArticleCollection();
  290. $articleMap->id = app('snowflake')->id();
  291. $articleMap->article_id = $newArticle->uid;
  292. $articleMap->collect_id = $request->get('anthologyId');
  293. $articleMap->title = Article::find($newArticle->uid)->title;
  294. $articleMap->level = 1;
  295. $articleMap->save();
  296. }
  297. });
  298. if(Str::isUuid($newArticle->uid)){
  299. return $this->ok($newArticle);
  300. }else{
  301. return $this->error('fail');
  302. }
  303. }
  304. /**
  305. * Display the specified resource.
  306. * @param \Illuminate\Http\Request $request
  307. * @param \App\Models\Article $article
  308. * @return \Illuminate\Http\Response
  309. */
  310. public function show(Request $request,Article $article)
  311. {
  312. //
  313. if(!$article){
  314. return $this->error("no recorder");
  315. }
  316. //判断权限
  317. $user = AuthApi::current($request);
  318. if(!$user){
  319. $user_uid="";
  320. }else{
  321. $user_uid=$user['user_uid'];
  322. }
  323. $canRead = ArticleController::userCanRead($user_uid,$article);
  324. if(!$canRead){
  325. return $this->error(__('auth.failed'),403,403);
  326. }
  327. return $this->ok(new ArticleResource($article));
  328. }
  329. /**
  330. * Display the specified resource.
  331. * @param \Illuminate\Http\Request $request
  332. * @param string $article
  333. * @return \Illuminate\Http\Response
  334. */
  335. public function preview(Request $request,string $articleId)
  336. {
  337. //
  338. $article = Article::find($articleId);
  339. if(!$article){
  340. return $this->error("no recorder");
  341. }
  342. //判断权限
  343. $user = AuthApi::current($request);
  344. if(!$user){
  345. $user_uid="";
  346. }else{
  347. $user_uid=$user['user_uid'];
  348. }
  349. $canRead = ArticleController::userCanRead($user_uid,$article);
  350. if(!$canRead){
  351. return $this->error(__('auth.failed'),[],401);
  352. }
  353. if($request->has('content')){
  354. $article->content = $request->get('content');
  355. return $this->ok(new ArticleResource($article));
  356. }else{
  357. return $this->error('no content',[],200);
  358. }
  359. }
  360. /**
  361. * Update the specified resource in storage.
  362. *
  363. * @param \Illuminate\Http\Request $request
  364. * @param \App\Models\Article $article
  365. * @return \Illuminate\Http\Response
  366. */
  367. public function update(Request $request, Article $article)
  368. {
  369. //
  370. if(!$article){
  371. return $this->error("no recorder");
  372. }
  373. //鉴权
  374. $user = AuthApi::current($request);
  375. if(!$user){
  376. return $this->error(__('auth.failed'),401,401);
  377. }else{
  378. $user_uid=$user['user_uid'];
  379. }
  380. $canEdit = ArticleController::userCanEdit($user_uid,$article);
  381. if(!$canEdit){
  382. return $this->error(__('auth.failed'),401,401);
  383. }
  384. /*
  385. //查询标题是否重复
  386. if(Article::where('title',$request->get('title'))
  387. ->where('owner',$article->owner)
  388. ->where('uid',"<>",$article->uid)
  389. ->exists()){
  390. return $this->error(__('validation.exists'));
  391. }*/
  392. $content = $request->get('content');
  393. if($request->get('to_tpl')===true){
  394. /**
  395. * 转化为模版
  396. */
  397. $tplContent = $this->toTpl($content,
  398. $request->get('anthology_id'),
  399. $user);
  400. $content = $tplContent;
  401. }
  402. $article->title = $request->get('title');
  403. $article->subtitle = $request->get('subtitle');
  404. $article->summary = $request->get('summary');
  405. $article->content = $content;
  406. $article->lang = $request->get('lang');
  407. $article->status = $request->get('status',10);
  408. $article->editor_id = $user['user_id'];
  409. $article->modify_time = time()*1000;
  410. $article->save();
  411. OpsLog::debug($user_uid,$article);
  412. return $this->ok($article);
  413. }
  414. /**
  415. * Remove the specified resource from storage.
  416. * @param \Illuminate\Http\Request $request
  417. * @param \App\Models\Article $article
  418. * @return \Illuminate\Http\Response
  419. */
  420. public function destroy(Request $request,Article $article)
  421. {
  422. //
  423. $user = AuthApi::current($request);
  424. if(!$user){
  425. return $this->error(__('auth.failed'));
  426. }
  427. //判断当前用户是否有指定的studio的权限
  428. if($user['user_uid'] !== $article->owner){
  429. return $this->error(__('auth.failed'));
  430. }
  431. $delete = 0;
  432. DB::transaction(function() use($article,$delete){
  433. //TODO 删除文集中的文章
  434. $delete = $article->delete();
  435. ArticleMapController::deleteArticle($article->uid);
  436. });
  437. return $this->ok($delete);
  438. }
  439. public function toTpl($content,$anthologyId,$user){
  440. //查询书号
  441. if(!Str::isUuid($anthologyId)){
  442. throw new \Exception('anthology Id not uuid');
  443. }
  444. $bookId = $this->getBookId($anthologyId,$user);
  445. $tpl = $this->convertToTpl($content,$bookId['book'],$bookId['paragraph']);
  446. //保存原文到句子表
  447. $customBook = $this->getCustomBookByBookId($bookId['book']);
  448. $sentenceSave = new SentenceApi;
  449. $auth = $sentenceSave->auth($customBook->channel_id,$user['user_uid']);
  450. if(!$auth){
  451. throw new \Exception('auth fail');
  452. }
  453. foreach ($tpl['sentences'] as $key => $sentence) {
  454. $sentenceSave->store($sentence,$user);
  455. }
  456. return $tpl['content'];
  457. }
  458. private function getCustomBookByBookId($bookId){
  459. return CustomBook::where('book_id',$bookId)->first();
  460. }
  461. private function getBookId($anthologyId,$user){
  462. $anthology = Collection::where('uid',$anthologyId)->first();
  463. if(!$anthology){
  464. throw new \Exception('anthology not exists id='.$anthologyId);
  465. }
  466. $bookId = $anthology->book_id;
  467. if(empty($bookId)){
  468. //生成 book id
  469. $newBookId = CustomBook::max('book_id') + 1;
  470. $newBook = new CustomBook;
  471. $newBook->id = app('snowflake')->id();
  472. $newBook->book_id = $newBookId;
  473. $newBook->title = $anthology->title;
  474. $newBook->owner = $anthology->owner;
  475. $newBook->editor_id = $user['user_id'];
  476. $newBook->lang = $anthology->lang;
  477. $newBook->status = $anthology->status;
  478. //查询anthology所在的studio有没有符合要求的channel 没有的话,建立
  479. $channelId = ChannelApi::userBookGetOrCreate($anthology->owner,$anthology->lang,$anthology->status);
  480. if($channelId === false){
  481. throw new \Exception('user book get fail studio='.$anthology->owner.' language='.$anthology->lang);
  482. }
  483. $newBook->channel_id = $channelId;
  484. $ok = $newBook->save();
  485. if(!$ok){
  486. throw new \Exception('user book create fail studio='.$anthology->owner.' language='.$anthology->lang);
  487. }
  488. CustomBookId::where('key','max_book_number')->update(['value'=>$newBookId]);
  489. $bookId = $newBookId;
  490. $anthology->book_id = $newBookId;
  491. $anthology->save();
  492. }else{
  493. $channelId = CustomBook::where('book_id',$bookId)->value('channel_id');
  494. }
  495. $maxPara = Sentence::where('channel_uid',$channelId)
  496. ->where('book_id',$bookId)->max('paragraph');
  497. if(!$maxPara){
  498. $maxPara = 0;
  499. }
  500. return ['book'=>$bookId,'paragraph'=>$maxPara+1];
  501. }
  502. public function convertToTpl($content,$bookId,$paraStart){
  503. $newSentence = array();
  504. $para = $paraStart;
  505. $sentNum = 1;
  506. $newText = "";
  507. $isTable=false;
  508. $isList=false;
  509. $newSent="";
  510. $sentences = explode("\n",$content);
  511. foreach ($sentences as $row) {
  512. //$data 为一行文本
  513. $listHead= "";
  514. $isList = false;
  515. $heading = false;
  516. $title = false;
  517. $trimData = trim($row);
  518. # 判断是否为list
  519. $listLeft =strstr($row,"- ",true);
  520. if($listLeft !== FALSE){
  521. if(ctype_space($listLeft) || empty($listLeft)){
  522. # - 左侧是空,判定为list
  523. $isList=true;
  524. $iListPos = mb_strpos($row,'- ',0,"UTF-8");
  525. $listHead = mb_substr($row,0,$iListPos+2,"UTF-8");
  526. $listBody = mb_substr($row,$iListPos+2,mb_strlen($row,"UTF-8")-$iListPos+2,"UTF-8");
  527. }
  528. }
  529. # TODO 判断是否为标题
  530. $headingStart =mb_strpos($row,"# ",0,'UTF-8');
  531. if($headingStart !== false){
  532. $headingLeft = mb_substr($row,0,$headingStart+2,'UTF-8');
  533. $title = mb_substr($row,$headingStart+2,null,'UTF-8');
  534. if(str_replace('#','', trim($headingLeft)) === ''){
  535. # 除了#没有其他东西,那么是标题
  536. $heading = $headingLeft;
  537. $newText .= $headingLeft;
  538. $newText .='{{'."{$bookId}-{$para}-{$sentNum}-{$sentNum}"."}}\n";
  539. $newSentence[] = $this->newSent($bookId,$para,$sentNum,$sentNum,$title);
  540. $newSent="";
  541. $para++;
  542. $sentNum = 1;
  543. continue;
  544. }
  545. }
  546. //判断是否为表格开始
  547. if(mb_substr($trimData,0,1,"UTF-8") == "|"){
  548. $isTable=true;
  549. }
  550. if($trimData!="" && $isTable == true){
  551. //如果是表格 不新增句子
  552. $newSent .= "{$row}\n";
  553. continue;
  554. }
  555. if($isList == true){
  556. $newSent .= $listBody;
  557. }else{
  558. $newSent .= $trimData;
  559. }
  560. #生成句子编号
  561. if($trimData==""){
  562. #空行
  563. if(strlen($newSent)>0){
  564. //之前有内容
  565. $newText .='{{'."{$bookId}-{$para}-{$sentNum}-{$sentNum}"."}}\n";
  566. $newSentence[] = $this->newSent($bookId,$para,$sentNum,$sentNum,$newSent);
  567. $newSent="";
  568. }
  569. #新的段落 不插入数据库
  570. $para++;
  571. $sentNum = 1;
  572. $newText .="\n";
  573. $isTable = false; //表格开始标记
  574. $isList = false;
  575. continue;
  576. }else{
  577. $sentNum=$sentNum+10;
  578. }
  579. if(mb_substr($trimData,0,2,"UTF-8")=="{{"){
  580. #已经有的句子链接不处理
  581. $newText .= $trimData."\n";
  582. }else{
  583. $newText .= $listHead;
  584. $newText .='{{'."{$bookId}-{$para}-{$sentNum}-{$sentNum}"."}}\n";
  585. $newSentence[] = $this->newSent($bookId,$para,$sentNum,$sentNum,$newSent);
  586. $newSent="";
  587. }
  588. }
  589. return [
  590. 'content' =>$newText,
  591. 'sentences' =>$newSentence,
  592. ];
  593. }
  594. private function newSent($book,$para,$start,$end,$content){
  595. return array(
  596. 'book_id'=>$book,
  597. 'paragraph'=>$para,
  598. 'word_start'=>$start,
  599. 'word_end'=>$end,
  600. 'content'=>$content,
  601. );
  602. }
  603. }